【c++】【基础】【primer_plus】【第四章】指针与内存

指针

指针一直是c/c++学习的难点。指针是用来获取变量在内存中地址的变量。

指针使用解除引用符*来声明,而变量则可以通过取地址符&获取其在内存中的地址。故指针的声明与初始化如下代码所示。其中声明了一个指针类型的变量p,它指向变量a的地址。

1
2
int a = 5;
int* p = &a; // & 取地址

在这之后,p便代表着变量a的地址。我们可以通过解除引用符*来从指针变量获取其地址上的值,代码如下。

1
cout << *p << endl;     // * 解除引用,取值

危险指针

没有指向具有一定意义内存的指针称为空指针。如指向了nullptrNULL的指针。如果使用了空指针,将有可能造成内存访问异常,获取不到正确的数据。

除了空指针外,还有一类指针更危险且隐藏极深,那便是野指针。野指针一般出现于以下几个场景。

指针变量未初始化。此可手动初始化为nullptrNULL

指针释放后之后未置空。此时也可在释放后手动初始化为nullptrNULL

指针操作超越变量作用域。注意不要返回指向栈内存空间的指针或引用。

除此之外,还要注意,数字不能直接赋值给指针,需要强制类型转换方可。如下代码所示。

1
2
int* p;
p = (int*)0xB8000000;

指针算术

指针变量+1后,并非内存地址对应的数字+1,而是增加它指向的数据类型的字节数,即指向下一个元素。如下代码所示。

1
2
3
4
5
int a[10];
cout << a << endl;
cout << &a << endl;
cout << a+1 << endl;
cout << &a + 1 << endl;

输出结果如下。

1
2
3
4
0x6afed8
0x6afed8 // 虽然在数值上一致
0x6afedc // 但+1后发现不一样,代表的元素不一样
0x6aff00

这是因为对数组取地址时,得到的是整个数组的地址,而非第一个元素的地址,即该指针的数据类型为数组类型,而非元素类型。

1
2
int (*p)[10] = &a;      // 即&a的类型为 int(*)[10]
// 此时 (*p)[0] 即为 a[0]

另外,对指针使用sizeof()得到的是指针的长度,即使指针指向一个数组。一般指针的长度为4字节。

对于数组而言,a[i]等价于*(a + i)。所以有时候会说数组的本质也还是指针运算而已。

c++中的内存存储

自动存储

在函数内部定义的常规变量、局部变量等都使用使用自动存储空间,称为自动变量。它们在函数被调用时(或所在代码块为活动代码块时)自动产生,在函数结束时(或离开代码块时)消亡。

自动变量将存储在栈空间中,执行其所在代码块时入栈,离开代码块时出栈,且先进后出(FILO)。

静态存储

在函数外定义的变量、在函数内使用static声明的静态变量、全局变量等都使用静态存储空间,称为静态变量。它们在整个程序执行期间都存在,我们会在后面第九章详细论述此种存储方式。

动态存储

除了以上存储空间外,c++还有一个自由存储空间,称为内存池,或堆空间。动态存储空间将变量的创建和删除权限交给开发人员。理论上讲,我们可以在任何地方创建变量,同时可以在任何地方删除该变量,且在该变量存在的整个周期内,它都是存储在堆空间的某个具体位置上的。

换句话数,动态数据的生命周期不完全受程序和函数的生存时间控制,我们可在一个函数中创建,在另一个函数中删除,对内存有了更大的控制权。但这也导致一些变量可能占用的自由存储空间不连续,使得跟踪内存位置更加困难。

动态内存分配

我们使用new关键字来创建存储在堆空间的动态变量,使用delete关键字来删除该动态变量。newdelete的基本使用格式如下。

1
type_name* point_name = new type_name;  // new
1
delete point_name;                      // delete

下面以int为例。

1
2
3
4
5
6
int* p = new int;                       // new

// 一些操作
*p = 10;

delete p; // delete

需要注意的是,使用delete释放new申请的内存时,并不会删除指针本身,还是可以重新分配内存给该指针。与此同时,newdelete要配对使用,否则会发生内存泄漏(memory leak),导致堆空间有无法回收的内存。

更不要尝试释放已经释放的内存块,其结果将不确定,故不要创建两个指向同一内存块的指针。但是对空指针delete是安全的,所以我们最好在delete之后加上一句置空的代码。

1
2
3
int* p = new int;
delete p;
p = nullptr; // 或 p = NULL;

动态复合类型

使用new也可以创建动态数组。其一般格式如下。

1
type_name* point_name = new type_name[element_num];     // new
1
delete[] point_name;                                    // delete

同样以int为例。

1
2
3
4
5
6
7
int* p = new int[10];                                   // new

// 一些操作
*p = 10;
p[1] = 20;

delete[] p; // delete

其中数组的第一个元素为*p,也是p[0],数组下标为i个元素为p[i],参见上述指针算术。

使用new也可创建结构体。此时不再使用.进行成员访问,而是替换为->

1
2
3
4
5
6
7
struct pos {
int x, y;
};

pos* p = new pos;
cout << p->x << endl; // 使用->访问成员
cout << (*p).y << endl; // 也可使用*取结构体的值