指针定义
变量:内存变量的简称
&变量名
:获取变量的起始地址
长度:
- int:四字节
- long:四字节
- long long:八字节
- double:八字节
- 指针:32 位电脑四字节,64 位电脑八字节
指针变量:简称指针,用于存放变量的起始地址
语法:数据类型* 指针名
指针使用
指针声明但未赋值时,是不能使用的
*指针名
:解引用,用于获得内存中的值
起始地址决定了变量的位置,数据类型决定了占用内存的大小以及如何操作数据
指针的意义
指针存放了变量的地址,通过指针操作变量效果与变量名一样
通过指针传递变量叫做地址传递,地址传递的意义:
- 可以在函数中修改实参的值
- 可以减少内存拷贝,提升性能
常量指针和指针常量
常量指针
语法:const 数据类型* 指针名;
常量指针不能通过解引用来修改内存地址中的值
注意:
- 指针的指向是可以改变的
- 一般用于修饰形参,表示禁止修改内存中的值
- 如果形参不需要改变,建议加上
const
指针常量
语法:数据类型* const 指针名;
指针常量的指向不可以改变
注意:
- 在定义的同时必须初始化,否则无意义
- 可以通过解引用修改内存中的值
- 指针常量是引用的基础
常指针常量
语法:const 数据类型* const 指针名;
常指针常量指向的对象不能改变,解引用的值也不能改变
void*
C++ 中 void
表示无类型,主要有三个用于
- 表示无返回值
- 表示无参数
- 函数的形参使用
void*
,表示接受任意数据类型的指针
注意事项:
- 不能用
void
声明变量,他不能代表一个真实的变量 - 不能对
void*
指针直接解引用,要先进行类型转换 - 其他类型指针赋值给
void*
指针不需要类型转换 void*
赋值给其他类型的指针需要类型转换
C++ 内存管理
栈和堆的主要区别:
- 管理方式不同:栈自动管理;堆需要手动释放
- 空间大小不同:栈很小,一般只有8M;堆的大小仅受物理内存限制
- 分配效率不同:栈比堆快一些
- 是否产生碎片:栈不会产生碎片;堆会产生碎片
new 和 delete
使用堆区的内存(动态分配内存)有四个步骤:
- 声明一个指针
- 用
new
运算符想系统申请一块内存,让指针指向这块内存 - 通过指针解引用的方法,像使用变量一样使用这块内存
- 如果这块内存不用了,用
delete
运算符释放它
语法:
- 申请内存:
new 数据类型(初始值); // C++11 支持 {}
- 内存释放:
delete 地址
注意:
- 动态分配出来的内存没有变量名,只能通过指针来操作其中的数据
- 动态分配的内存不再使用时,必须通过
delete
释放 - 动态分配的内存生命周期与程序相同
- 就算指针的作用于失效,所指向的内存也不会被释放
- 用指针跟踪已分配的内存时,不能跟丢
二级指针
二级指针用于存放指针变量的地址
语法:数据类型** 指针名;
使用指针的目的:
- 传递地址
- 存放动态分配的内存地址
在函数中:
- 传递普通变量的地址,形参用指针
- 传递指针的地址,形参用二级指针
- 把普通变量的地址传入函数后,可以在函数中修改变量的值
- 把指针的地址传入函数后,可以再函数中修改指针的值
空指针
- 对空指针解引用,程序会崩溃
- 对空指针使用
delete
,系统会忽略操作 - 初始化指针和释放内存后,应该把指针指向空指针
nullptr
- 在函数中,应该有判断形参是否是空指针的代码,让程序更加稳健
野指针
野指针指向的不是一个有效地址
访问野指针,可能会造成程序的崩溃
可能出现野指针的情况:
- 指针在定义时没有初始化
- 内存释放后,指针为置空
- 指针指向的变量已超越作用于
规避办法:
- 指针初始化为
nullptr
- 内存被释放后,将指针指向
nullptr
- 函数不要返回局部变量的地址
指针和数组
将指针 +1 后,增加的量等于它指向的数据类型的字节数
数组的地址:
- 数组在内存中占用的空间是连续的
- C++ 将数组名解释为数组第 0 个元素的地址
- 数组第 0 个元素的地址和数组首地址相同
- 数组第 n 个元素的地址 = 数组首地址 + n
- 数组名[下标] = *(数组首地址 + 下标)
注意,数组名不一定是地址:
sizeof(数组名)
可以返回数组占用的字节数- 指针的值可以修改,但数组名是常量,不能修改
数组与函数
数组用作函数参数的基础:地址[下标] = *(地址 + 下标)
一维数组用于函数参数:
- 只能传函数的地址
- 要传入数组的长度
形式(两种形式等价):
void func( int* arr, int length);
void func( int arr[], int length);
注意:
- 在函数中,可以用数组表示法,也可以用指针表示法
- 在函数中,不要对指针名用
sizeof
运算符,它不是数组名
堆区创建一维数组
- 创建数组:
数据类型* 指针名 = new 数据类型[数组长度];
- 释放内存:
delete[] 指针
注意:
- 动态创建的数组没有数组名,因此不能使用
sizeof
运算符 - 可以使用数组表示法和指针表示法使用动态创建的数组
- 必须使用
delete[]
来释放内存 - 不要用
delete[]
释放不是new[]
分配的内存 - 不要用
delete[]
释放同一块内存两次(野指针问题) - 对空指针使用
delete[]
是安全的,因此释放内存后应该把数组指针置空 - 如果内存不足,调用
new
会产生异常,导致程序中止;如果在new
关键字后面加上(std::nothrow)
选项,则会返回nullptr
,且不会产生异常。例如:new (std::nothrow) int[1000];
delete[]
不需要指定数组大小
二维数组
- 行指针:
数据类型 (*行指针名)[行的大小];
- 例:
int (*p1)[3];
案例:
int bh[2][3] = {{11, 12, 13}, {21, 22, 23}}; int (*p)[3] = bh;
二维函数做函数形参
void func( int (*p)[3], int length );
void func( int p[][3], int length );
- 在函数中,将 p 当做二维数组使用即可
多维数组
int bh[2][3][4]; memset(bh, 0, siezeof(bh)); int (*p)[3][4] = bh;
在函数中,将 p 当做传入的三维数组使用即可
函数指针和回调函数
函数的类型:
- 返回值
- 参数列表
- 函数名和形参名不决定函数的类型
函数指针
- 语法:
返回值类型 (*指针名)(形参类型列表);
- 案例:
int (*pfa)( int, string );
- 形参名称可写可不写
代码案例:
pfa = func; pfa(3, "Hello"); // C++ 风格调用 (*pfa)(3, "Hello"); // C 风格调用
回调函数:通过函数指针调用的函数
指针函数
返回值是地址的函数叫指针函数