内存地址是指计算机内存中存储变量或对象的地址。内存空间大小就是寻址能力,即能访问到多少个地址,比如 32 位机器内存空间大小就是 2^32 = 4294967296,也就是 4 GB 。每个变量或对象在内存中都有一个唯一的地址,通过该地址可以访问和操作该变量或对象。注意一 个内存地址对应一个字节,以 int 类型的变量为例,其占据 4 个内存地址,其中首个内存地址就是这个变量的地址。
#includeint main() { int vals[4]{}; printf("val1 address = %p\n", &vals[0]); printf("val2 address = %p\n", &vals[1]); printf("val3 address = %p\n", &vals[2]); printf("val4 address = %p\n", &vals[3]); return 0; }
上面代码的输出为:
val1 address = 0000005420F6F978 val2 address = 0000005420F6F97C val3 address = 0000005420F6F980 val4 address = 0000005420F6F984
为了能够说明 1 个 int 类型的变量占据 4 个内存地址,我们在上面的代码中使用占据连续内存的数组来做测试,由这个输出可以看出:数组 vals 的第一个元素所占据的内存地址由 0000005420F6F978 到 0000005420F6F97B (再往下的一个地址就是第二个元素的首地址 0000005420F6F97C),刚好是 4 个内存地址,其首个内存地址 0000005420F6F978 就是这个数组 vals 的第一个元素的地址(同时也是这个数组变量 vals 的地址)。
常量指针(const pointer)和指针常量(pointer to const)是两个不同的概念,常量指针指的是其指向变量的值不可改变,但是指针本身是可以改变的,可以指向其他变量;指针常量指的是指针本身是常量,其不可以再指向其他变量。
常量指针的样例代码:
const int val1 = 1; int *ptr1 = &val1; //错误:必须使用常量指针 const int *ptr1 = &val1; //OK *ptr1 = 2;
指针常量的样例代码:
int val1 = 1; int val2 = 2; int const *ptr1 = &val1; //OK *ptr1 = &val2; //错误:指针本身是常量,其不可以再指向其他变量。
野指针出现的原因主要有以下三种:
(1)指针变量未初始化。局部指针变量的默认值是一个随机值,如果此时访问该指针则会引起程序崩溃。所以,指针变量在创建的同时应当被初始化,要么将指针设置为 nullptr ,要么让它指向合法的内存( new 出来的对象或者现有的一个对象)。
(2)释放内存后没有将指针设置为 nullptr 。不管是 free 还是 delete 在释放内存时,只是把指针所指的内存给释放掉了,但此时指针的值依然是之前内存空间的首地址。此时访问该指针则会引起程序崩溃。
(3)指针操作超越变量作用范围。栈内存在函数结束时会被释放,如果将其内存地址通过指针返回给调用者,此时再访问则会引起程序崩溃。
nullptr 关键字是在 C++11 标准中引入的,用于表示空指针。在 C++11 及以后的版本中,nullptr 替代了 C++98/03 中的 NULL 或 0 作为空指针的表示。该关键字可以避免函数重载问题,如下为样例代码:
void overLoadFunc(int* val); void overLoadFunc(int val); int main() { overLoadFunc( NULL ); // 期待调用 overLoadFunc(int* val); 但实际调用却是 overLoadFunc(int val); }
上面代码中的 overLoadFunc( NULL ); 实际调用的是 overLoadFunc(int val); 。其原因是 NULL 本身就是整数 0 ,因此进入了整型参数的重载函数。
指针的指针是一个指向指针的指针。指针可以指向所有数据类型的变量(基本类型、结构体类型、类类型等),而指针自身也是一种变量,所以指针自然也可以指向指针。指针的指针通常用于处理二维数组、动态分配的二维数组或处理指针数组等。
如下样例可以帮助理解指针的指针:
#includeint main() { int val1 = 1; int *ptr1 = &val1; int **ptr2 = &ptr1; printf("ptr1 address = %p\n", &ptr1); printf("ptr1 address = %p\n", &(*ptr2)); printf("ptr2 value = %p\n", ptr2); return 0; }
上面代码的输出为:
ptr1 address = 000000C4A839F758 ptr1 address = 000000C4A839F758 ptr2 value = 000000C4A839F758
由结果可以看出,指向指针的指针变量 ptr2 保存了指针变量 ptr1的地址( 000000C4A839F758 )。 其中代码第 10 行 int **ptr2 = &ptr1; 定义了一个指向指针的指针,这里用了两个星号*,其保存的值就是指针变量 ptr1的地址。
第 11、 12、 13 行代码尤为重要:
第 11 行代码 printf("ptr1 address = %p\n", &ptr1); ,其中的 &ptr1 是对指针变量 ptr1 做取地址操作。
第 12 行代码 printf("ptr1 address = %p\n", &(*ptr2)); ,其中的 (*ptr2) 是对指针变量 ptr2 做解引用操作,再对其做取地址操作,相当于直接对指针变量 ptr1 做取地址操作。
第 13 行代码 printf("ptr2 value = %p\n", ptr2); ,对指向指针的指针取值,直接用其变量名即可。
使用指针的指针来处理二维数组时,其对应内存的创建以及释放都需要使用循环:
(1)创建二维数组
int** vals = new int*[2]; for (size_t i = 0; i < 2; i++) { vals[i] = new int[3](); }
(2)释放二维数组的内存
int** vals = new int*[2]; for (size_t i = 0; i < 2; i++) { vals[i] = new int[3](); } for (size_t i = 0; i < 2; i++) { delete[] vals[i]; //注意 delete 一定要加上中括号 [] }
这里尤其注意 delete[] 的使用。
上一篇:缓存和分布式锁 笔记