C++指针详解

指针

目录

内存变量

变量的全称是内存变量

在这里插入图片描述

以上例子中,cout会将地址转换成字符串进行输出,就有可能出现乱码的的情况

void*类型返回的是16进制的地址,longlong类型返回的是10进制的地址

指针变量

指针变量简称指针,是一种特殊的变量,专门用来存放变量在内存中的起始地址

对指针进行赋值

地址都是16进制的数,但是指针是有类型的标识符,指针的名字也是标识符,不能与其他变量重名

int *pa = &a;

对指针赋值的操作可以叫做指针指向某个变量,被指向的类型叫做基类型

指针占用的内存

在64位操作系统中,都是8字节

可以把int*当成一种数据类型

使用指针

声明指针变量后,不初始化的话,其中是乱七八糟的值,这时候不能使用指针

*运算符是解引用运算符,用于指针,可以得到该地址的内存中存储的值。

变量和指向变量的指针就像同一枚硬币的两面

在这里插入图片描述

每次运行程序的时候,变量的地址都是随机分配的,不一样

指针只是记录了变量在内存中的起始地址,类型决定了数据占用内存的大小以及如何操作这个数据

在这里插入图片描述

指针用于函数参数

指针作为参数的时候,在函数中通过解引用的方法可以修改实参的值

地址传递

  • 可以在函数中修改实参的值,作为传出参数
  • 减少内存拷贝,提升性能,每个指针都是8字节,但是要拷贝的内容可能很大,像字符串,数组什么的
const修饰指针

常量指针

const 数据类型* 变量名

不能通过解引用的方法修改内存地址中的值,用原始的变量名是可以修改的

  • 可以修改指针的指向,但是没有什么意义
  • 一般用于函数形参,表示在函数中不能用解引用的方式修改常量的值

指针常量

数据类型* const 变量名

指向的对象不能改变,所以在声明的时候必须初始化

  • 可以通过解引用的方式修改内存地址中的值
  • C++编译把指针常量做了一些特别的处理,改头换面之后,新名字叫做引用

常指针常量

指向的对象不能改变,不能通过解引用的方法修改内存地址中的值

常引用

void关键字

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

注意

  • 不能用void 来声明变量,不能表示一个真实的变量
  • 不能对void*指针直接解引用(需要转换成其他类型的指针)
  • 把其他类型的指针赋值给void*指针不需要转换
  • 把void*指针赋值给其他类型的指针需要转换

在c++中返回字符类型的时候会有乱码,可以先转成void*类型再输出

C++内存模型

在这里插入图片描述

程序运行后,代码段中的内容是不会改变的

  • 栈:存储局部变量、函数参数和返回值
  • 堆:存储动态开辟内存的变量
  • 数据段:存储全局变量和静态变量
  • 代码段:存储可执行程序的代码和常量

堆和栈的主要区别

在这里插入图片描述

动态分配内存new 和 delete

使用堆区内存有四个步骤

  1. 声明一个指针
  2. 使用new运算符向系统中申请一块内存,让指针指向这块内存
  3. 通过对指针解引用的方法,像使用变量一样使用这块内存
  4. 如果这块内存不存在了,使用delete运算符来释放

内存只分配不释放,后果是非常严重的,可能会用尽系统的内存

动态分配出来的内存没有变量名,只能通过指向它的指针来操作内存中的数据

动态分配的内存生命周期跟程序相同,程序退出时,如果没有释放,系统将自动回收

就算指针的作用域已经失效,所指向的内存也不会释放

用指针跟踪已分配的内存时,不能跟丢

二级指针

二级指针的变量中存放的是指针的地址

使用指针的目的

  • 传递地址
  • 存放动态分配的内存的地址

在函数中,如果传递普通变量的地址,形参使用指针,传递指针变量的地址,形参使用二级指针为了修改指针的地址

在这里插入图片描述

多级指针除了套娃没有任何意义

空指针

  • 如果对空指针解引用,程序会崩溃。
  • 如果对空指针使用delete运算符,系统将忽略该操作,不会出现异常。所以,内存被释放后,也应该把指针指向空。
  • 在函数中,应该有判断形参是否为空指针的代码,目的是保证程序的健壮性。

为什么空指针访问会出现异常

NULL指针分配的分区:其范围是从 0x000000000x0000FFFF。这段空间是空闲的,对于空闲的空间而言,没有相应的物理存储器与之相对应,所以对这段空间来说,任何读写操作都是会引起异常的。空指针是程序无论在何时都没有物理存储器与之对应的地址。为了保障“无论何时”这个条件,需要人为划分一个空指针的区域,固有上面NULL指针分区。

简而言之,系统中有专门的空指针分区,这段空间是空闲的,没有相应的物理存储对应,所以读写操作都会出错。说白了就是NULL指针没有真实的内存数据对应

nullptr等价于(void *)0

野指针

指针指向的不是一个有效或者说合法的地址

在程序中,如果访问野指针,可能会造成程序的崩溃

为什么是可能?

打人犯法吗,犯法;一定会近派出所吗,不一定;如果野指针指向的内存是内核或者其他非空闲的内存,就会导致程序的崩溃,如果是空闲的内存,就不会崩溃,但是没有人能保证那块内存是不是空闲的

出现的情况

  1. 指针在定义的时候,如果没有进行初始化,它的值是不确定的
  2. 如果用指针指向了动态分配的内存内存被释放后,指针不会置空,但是指向的地址已经失效
  3. 指针指向的变量已经超越变量的作用域,让指针指向了函数的局部变量,或者把函数的局部变量的地址作为返回值赋值给了指针

规避方法

针对上面三种情况

  1. 初始化,没有地方指的话就初始化为nullptr
  2. 动态分配的内存被释放后,指针指向nullptr
  3. 函数不要返回局部变量的地址

一位数组和指针

对地址加1操作往后移动的长度跟数据类型有关,地址往后移动的长度是 1 * sizeof(数据类型)

数组的地址
double a[5];
cout << a << &a << &a[0] << endl;

以上三个值是相同的,都是数组a的地址

C++编译器把数组名[下标]解释为*(数组首地址 + 下标)

C++编译器把**地址[下标]解释为*(地址 + 下标)**

数组的本质

数组是占用连续空间的一块内存,数据名被解释成数组第0个元素的地址,C++操作这块内存有两种方法:数组解释法和指针表示法,它们是等价的。

数组名不一定会被解释为地址

在**sizeof**运算符运用于数组名的时候,返回的是整个数组占用内存空间的字节数

可以修改指针的值,数组名是常量,不可以修改

可以p++,但是不能a++
一维数组作为函数参数

只能传数组的地址,并且把数组长度也传递进去

书写方法有以下两种

void func(int* arr, int len);
void func(int arr[], int len);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wd3yuzEn-1683617233351)(image/image_R_EISMeJ1S.png)]

指针作为参数传入函数的时候,进行**sizeof**运算,返回就永远是8了,因为被当作是指针,而不再是数组了

用new动态创建一维数组

创建语法:数据类型* 指针 = new 数据类型[数组长度]

释放语法:delete[] 指针

注意

  • 动态创建的数组没有数组名,不能使用sizeof运算符,得到的永远是8

在这里插入图片描述

// 如果内存不够,程序不会崩溃,而是会返回空地址
int *a = new (std::nothrow) int[10000001];

二维数组用于函数参数

行指针(数组指针)

在这里插入图片描述

对一维数组取地址,得到的是行地址

在这里插入图片描述

这里有一个比较有意思的现象,a和&a的值是相同的,但是a+1与&a+1的值是不同的,原因在于&a是一个行地址。在方阵中,一个人的下一个位置是她旁边的那个,下一行的位置是下一行中正后面的那个人。

二维数组名是行地址
int bh[2][3] = {{11, 12, 13}, {21, 22, 23}};

其中,bh是二维数组名,该数组有两个元素,每个元素本身又是一个数组长度为3的整型数组。

bh被解释为数组长度为3的整形数组的行指针。

如果存放bh的值,要用数组长度为3的整型数组类型的行指针

int (*p)[3] = bh;
把二维数组作为参数

声明如下

void func(int (*p)[3], int len);
void func(int p[][3], int len);
使用new动态创建二维数组
int **mat = new int*[n];
for (int i = 0; i < n; i ++)
    mat[i] = new int[n];

多维数组

在实际开发中应用场景很少

**memset**函数

memset(数组名, 0, sizeof(数组名));

函数指针和回调函数

函数指针的主要用途是函数的回调

函数的二进制代码存放在内存四区中的代码段,函数的地址是它在内存中的起始地址,如果把函数的地址作为参数传递给函数,就可以在函数中灵活的调用其他函数

声明函数指针的时候也必须提供函数的类型,函数的类型指的是返回值和参数列表,函数名和形参名不是

// 原函数
int func1(int bh, string str);
bool func2(int id, string info);

// 函数指针
int (*pf1)(int, string);
bool (*pf2)(int, string);

// 对函数指针赋值
pf1 = func1;
// 使用函数指针调用函数
int no = 1;
string message = "测试";
pf1(no, message);

应用场景,回调函数

回调函数

需要在封装好的函数中调用一个自定义的个性化函数

如视频中的表白问题

表白公司可以提供一个封装好的表白流程,处理准备工作和收尾工作,但是中间你自己要操作的部分是你自己需要定义好的,所以表白流程的函数用一个函数指针作为参数,在其函数体内部调用自定义的表白函数。

这种情况下只能使用函数指针,没有别的方法

回调函数在多线程和网络通讯中很常用

指针函数

返回值是地址的函数是指针函数,感觉这个概念是一个伪命题。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值