编程笔记

前言:该文章用于记录个人平时的阅读笔记或者编程随想。内容比较片段化,有些地方略过,有些地方会直接引用,不会细讲,目的只是一个reminder。

C++ 智能指针

C++ 智能指针能够自动释放指针指向的内存,避免内存泄露的危险。常用的C++智能指针有shared_ptr,auto_ptr,unique_ptr和weak_ptr。

常见的内存错误

1.内存分配不成功却使用了它。并不是所有内存分配都会成功,在使用之前使用assert(p!=NULL)来检查指针是否为NULL。如果是用malloc或new来申请内存,应该用if(p==NULL) 或if(p!=NULL)进行防错处理。
2.内存分配成功却忘记初始化而直接使用。由于没有初始化指针会乱指导致产生野指针。在变量声明时就可以直接初始化或者指向NULL。

1
2
char *p = NULL;
char *str = (char *) malloc(100);

3.内存分配和初始化成功,但操作内存越界、尤其是在使用for循环时,容易混淆下标的范围。
4.忘记释放内存,造成内存泄露。如果不是使用智能指针,一个new对应一个delete(malloc与free相同),切记切记!
5.多次释放同一个内存。
6.使用free或delete释放了内存后,没有将指针指向NULL。导致产生“野指针”。
7.已经释放内存了却还使用它。

注意事项

内容修改和比较

常量字符串不能修改。虽然从语法上看没有错误,编译器不能发现错误,但是在运行时会出错。

1
2
3
4
5
6
char a[] = “hello”;
a[0] = ‘X’;
cout << a << endl;
char *p = “world”; // 注意p指向常量字符串
p[0] = ‘X’; // 编译器不能发现该错误
cout << p << endl;

数组不能直接进行复制和比较。将数组a内容复制给b不能简单使用b = a,否则产生编译错误,应该使用strcpy。同理使用strcmp比较两个数组。

1
2
3
4
5
6
7
8
9
10
11
// 数组…
char a[] = "hello";
char b[10];
strcpy(b, a); // 不能用 b = a;
if(strcmp(b, a) == 0) // 不能用 if (b == a)

// 指针…
int len = strlen(a);
char *p = (char *)malloc(sizeof(char)*(len+1));
strcpy(p,a); // 不要用 p = a;
if(strcmp(p, a) == 0) // 不要用 if (p == a)

注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。如下示例中,不论数组a的容量是多少,sizeof(a)始终等于sizeof(char *)。

1
2
3
4
void Func(char a[100])
{
 cout<< sizeof(a) << endl; // 4字节而不是100字节
}

指针参数的传递

不要使用指针去申请动态内存。下例中str最后还是NULL。

1
2
3
4
5
6
7
8
9
10
void GetMemory(char *p, int num)
{
 p = (char *)malloc(sizeof(char) * num);
}
void Test(void)
{
 char *str = NULL;
 GetMemory(str, 100); // str 仍然为 NULL
 strcpy(str, "hello"); // 运行错误
}

毛病出在函数GetMemory中。编译器总是要为函数的每个参数制作临时副本,指针参数p的副本是 _p,编译器使 _p = p。如果函数体内的程序修改了_p的内容,就导致参数p的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中,_p申请了新的内存,只是把_p所指的内存地址改变了,但是p丝毫未变。所以函数GetMemory并不能输出任何东西。事实上,每执行一次GetMemory就会泄露一块内存,因为没有用free释放内存。

如果非得要用指针参数去申请内存,那么应该改用“指向指针的指针”,见示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
void GetMemory2(char **p, int num)
{
 *p = (char *)malloc(sizeof(char) * num);
}

void Test2(void)
{
 char *str = NULL;
 GetMemory2(&str, 100); // 注意参数是 &str,而不是str
 strcpy(str, "hello");
 cout<< str << endl;
 free(str);
}

由于“指向指针的指针”这个概念不容易理解,我们可以用函数返回值来传递动态内存。这种方法更加简单,见示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
char *GetMemory3(int num)
{
 char *p = (char *)malloc(sizeof(char) * num);
 return p;
}

void Test3(void)
{
 char *str = NULL;
 str = GetMemory3(100);
 strcpy(str, "hello");
 cout<< str << endl;
 free(str);
}

用函数返回值来传递动态内存这种方法虽然好用,但是常常有人把return语句用错了。这里强调不要用return语句返回指向“栈内存”的指针,因为该内存在函数结束时自动消亡,见示例:

1
2
3
4
5
6
7
8
9
10
11
12
char *GetString(void)
{
 char p[] = "hello world";
 return p; // 编译器将提出警告
}

void Test4(void)
{
 char *str = NULL;
 str = GetString(); // str 的内容是垃圾
 cout<< str << endl;
}

用调试器逐步跟踪Test4,发现执行str = GetString语句后str不再是NULL指针,但是str的内容不是“hello world”而是垃圾。

如果把上述示例改写成如下示例,会怎么样?

1
2
3
4
5
6
7
8
9
10
11
char *GetString2(void)
{
 char *p = "hello world";
 return p;
}
void Test5(void)
{
 char *str = NULL;
 str = GetString2();
 cout<< str << endl;
}

函数Test5运行虽然不会出错,但是函数GetString2的设计概念却是错误的。因为GetString2内的“hello world”是常量字符串,位于静态存储区,它在程序生命期内恒定不变。无论什么时候调用GetString2,它返回的始终是同一个“只读”的内存块。

相关阅读

------ The End ^_^ Thanks for reading ------