指针(pointer)是一个值为内存地址的变量(或数据对象),而内存地址就类似于身份证号码。

指针的定义和初始化
数据类型 * 指针变量名;
int* ptr_num; //声明了一个int型的指针ptr_num
char* ptr_name; //声明了一个char型的指针ptr_name
注意:
int* p的写法偏向于地址,即p就是一个地址变量,表示一个十六进制地址。int *p的写法偏向于值,*p是一个整型变量,能够表示一个整型值。int* ptr1 = #int *ptr2 = nullptr;
- 声明中的 * 号和使用中的 * 号意义完全不同
取地址、取值操作:
int num = 1024;
int* ptr_num;
ptr_num = # // 取地址
*ptr_num = 1111; // 取值
这里关于 char* 类型的打印有一些需要注意的:
char ch = 'a';
char* ptr_ch = &ch;
cout << ptr_ch << '\t' << (void*)ptr_ch << endl;
输出结果
a 0x61fe17
这里可以发现,ptr_ch 的打印结果并不是一个地址,这是因为 char* 类型在 C++ 中被默认为是一个字符串,所以打印的话是将地址作为一个字符串来打印的。如果想要打印出地址的话,可以将其强制转换成 void* 类型进行输出。
空指针
空指针不指向任何对象,在试图使用一个指针之前可以先检查是否为空。
定义
int *ptr1 = nullptr; // 等价于 int *ptr1 = 0;
int *ptr2 = 0; // 直接将ptr2初始化为字面常量0
// 或者包含cstdlib头文件,使用NULL
int *ptr3 = NULL
定义指针的时候一定要给一个值,不然会有默认值,是一个随机的内存地址,称为野指针,这是非常危险的。
void* 指针
一种特殊的指针类型,可以存放任意对象的地址。
注意:
void*指针存放一个内存地址,地址指向的内容是什么类型不能确定,所以不能通过它去修改地址内容。void*类型指针一般用来和别的指针比较,作为函数的输入和输出或是赋值给另一个void*指针。
指针注意点
- 指针可存放任何基本数据类型、数组和其他所有高级数据结构的地址。
- 若指针已声明为指向某种类型数据的地址,那么他不能用于存储其他类型数据的地址。
- 应为指针指定一个地址之后,才能在语句中使用指针,不然直接使用野指针是很危险的。
引用
所谓的引用就是为对象起了另一个别名。
用法
int& int_value = 1024;
// ref_value指向int_value,是int_value的一个别名
int& ref_value = int_value;
// 错误:引用必须被初始化
int& ref_value2;
注意点
- 引用并非对象,只是为一个已经存在的对象起的别名。
- 引用只能绑定在对象上,不能与字面值或某个表达式的计算结果绑定在一起。
- 引用必须初始化,所以使用引用之前不需要测试其有效性,因此使用引用可能会比使用指针效率高。
例如
*ptr;想要直接取ptr的值,就必须要检验ptr这个指针是否初始化了,而引用则不需要。
这里有一句话
指向常量的引用是非法的。
这句话其实是错误的,引用是可以指向常量的,因为有 const 这种常量类型存在。
int& ref_value = 10; // 这种写法是错误的
// 如果想要指向常量可以这样写
const int& ref_value = 10;
指针和引用
- 引用其实就是对指针进行了简单封装,底层仍然是指针。
- 获取引用地址时,编译器会进行内部转换。
例如:
int num = 108;
int* ptr_num = #
int& ref_num = num;
// 上面两个语句是等价的,ref_num == *ptr_num。
cout << boolalpha;
cout << &num << '\t' << &ref_num << '\t' << ptr_num << '\t'
<< (ref_num == *ptr_num) << endl;
输出结果
0x61fe0c 0x61fe0c 0x61fe0c true
指针和数组
数组:
- 存储在一块连续的内存空间中
- 数组名就是这块连续内存空间的首地址
虽然数组名和指针存放的都是地址,使用指针指向数组的首地址就可以和数组名一样正常访问数组,但是看如下代码:
double score[] {11, 22, 33};
double *ptr_score = score;
cout << sizeof(score) << '\t' << sizeof(ptr_score) << endl;
输出结果
24 8
可以发现,数组名和指针还是有区别的,score 代表的是 double[3] 这么一个数组类型,而 ptr_score 是一个指针类型,不是同一个东西。
此外,指针可以进行算术运算修改,但是数组名是不可以修改的。
指针算术运算
- 指针的递增和递减(++、--)
- 指针加上或减去某个整数值
注意点
- 一个类型为T的指针的移动,以sizeof(T)为移动单位。
- 这种对指针的加减是可能会越界的,如果一个指向数组的指针,超过了数组的存储范围,就会指向未知的内存地址,这是很危险的。
动态分配内存
可以用户自定义需要使用的内存空间,通过关键字 new 来分配内存空间。
int* p = new int;
char* p1 = new char[10];
delete p;
delete [] p1;
- 指针真正的用武之地:在运行阶段分配未命名的内存空间
- 在此情况下,只能通过指针来访问内存!
使用关键字 delete 释放内存,new 分配的内存一定要释放,不然有可能造成内存泄漏问题。
- 与
new配对使用 - 不要释放已释放的内存
- 不能是否声明变量分配的内存
注意: 不要创建两个指向同一内存块的指针,有可能误删两次
int* ptr = new int;
int* ptr1 = ptr;
delete ptr;
delete ptr1;
使用 new 创建动态分配的数组
new 运算符返回第一个元素的地址,数组的释放使用 delete[] 释放内存
int* int_array = new int[10];
delete [] int_array;
关于 new 和 delete 使用的规则
- 不要使用
delete释放不是new分配的内存 - 不要使用
delete释放同一内存两次 - 如果使用
new[]为数组分配内存,则对应delete[]释放内存 - 对空指针使用
delete是安全的
程序的内存分配
- 栈区(stack)
- 由编译器自动分配释放,一般存放函数的参数值、局部变量的值等
- 操作方式类似数据结构中的栈-先进后出
- 堆区(heap)
- 一般由程序员分配释放,若程序不释放,并且程序结束时操作系统没有回收,那么就会造成内存泄漏
- 注意: 与数据结构中的堆是两回事,分配方式类似链表
- 全局区(静态区-static)
- 全局变量和静态变量是存储在一起的
- 程序结束后由系统释放
- 文字常量区
- 常量字符串就放在这里,程序结束后由系统释放
- 程序代码区
- 存放函数体的二进制代码
int num1 = 0; //全局初始化区
int* ptr1; //全局未初始化区
int main() {
//栈区
int num2;
//栈区 "xxxx"是在常量区的, str是在栈区的
char str[] = "xxxx";
//栈区
char* ptr2 = nullptr;
//全局(静态)初始化区
static int num3 = 1024;
//分配的内存在堆区
ptr1 = new int[10];
ptr2 = new char[20];
//注意:ptr1和ptr2本身是在栈区中的
delete [] ptr1;
delete [] ptr2;
return 0;
}
用指针创建二维数组
int (*p)[2] = new int[4][2];
这里相当于 p 指向了一块大小为4的内存空间首地址,这个空间里存放了4个大小为2的一维数组的首地址。
关于指针创建的二维数组其在内存的分配图如下:
