C++ 指针操作

指针定义

变量:内存变量的简称

&变量名:获取变量的起始地址

长度:

  • int:四字节
  • long:四字节
  • long long:八字节
  • double:八字节
  • 指针:32 位电脑四字节,64 位电脑八字节

指针变量:简称指针,用于存放变量的起始地址

语法:数据类型* 指针名

指针使用

指针声明但未赋值时,是不能使用的

*指针名:解引用,用于获得内存中的值

起始地址决定了变量的位置,数据类型决定了占用内存的大小以及如何操作数据

指针的意义

指针存放了变量的地址,通过指针操作变量效果与变量名一样

通过指针传递变量叫做地址传递,地址传递的意义:

  • 可以在函数中修改实参的值
  • 可以减少内存拷贝,提升性能

常量指针和指针常量

常量指针

语法:const 数据类型* 指针名;

常量指针不能通过解引用来修改内存地址中的值

注意:

  • 指针的指向是可以改变的
  • 一般用于修饰形参,表示禁止修改内存中的值
  • 如果形参不需要改变,建议加上 const

指针常量

语法:数据类型* const 指针名;

指针常量的指向不可以改变

注意:

  • 在定义的同时必须初始化,否则无意义
  • 可以通过解引用修改内存中的值
  • 指针常量是引用的基础

常指针常量

语法:const 数据类型* const 指针名;

常指针常量指向的对象不能改变,解引用的值也不能改变

void*

C++ 中 void 表示无类型,主要有三个用于

  • 表示无返回值
  • 表示无参数
  • 函数的形参使用 void*,表示接受任意数据类型的指针

注意事项:

  • 不能用 void 声明变量,他不能代表一个真实的变量
  • 不能对 void* 指针直接解引用,要先进行类型转换
  • 其他类型指针赋值给 void* 指针不需要类型转换
  • void* 赋值给其他类型的指针需要类型转换

C++ 内存管理

栈和堆的主要区别:

  • 管理方式不同:栈自动管理;堆需要手动释放
  • 空间大小不同:栈很小,一般只有8M;堆的大小仅受物理内存限制
  • 分配效率不同:栈比堆快一些
  • 是否产生碎片:栈不会产生碎片;堆会产生碎片

new 和 delete

使用堆区的内存(动态分配内存)有四个步骤:

  1. 声明一个指针
  2. new 运算符想系统申请一块内存,让指针指向这块内存
  3. 通过指针解引用的方法,像使用变量一样使用这块内存
  4. 如果这块内存不用了,用 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 风格调用

回调函数:通过函数指针调用的函数

指针函数

返回值是地址的函数叫指针函数