C++指针进阶

const 修饰指针

只写一种:常量指针const 数据类型 *变量名;
不能通过解引用的方法修改内存地址中的值(可以用原始的变量名修改)。
这种写法,指针指向的对象是可以改变的。

void* 指针

void* 表示可以接收任意数据类型的指针。

  • 不能用void* 指针声明变量。
  • 不能对void* 指针直接解引用(需要转换成其他类型的指针)。
  • 其他类型变量赋值给void* 指针不需要转换。
  • void* 指针赋值给其他类型的指针需要转换。
C++内存模型

在这里插入图片描述
重点关心 and 区。
管理方式:
栈是系统自动管理,在出作用域时,将被自动释放;堆需要手动释放,若程序中不释放,程序结束时由操作系统回收。
空间大小:
堆内存的大小受限于物理内存空间;
栈内存,一般就只有8M(可以修改系统参数,但没必要)。

不是很重要:
1.栈是系统提供的数据结构,计算机在底层提供了堆栈的支持,进栈和出栈都有专门的指令,效率比较高;堆是由C++函数库提供的。
2.栈不会产生碎片,堆频繁的分配和释放,会造成内存空间不连续,容易产生碎片,太多碎片会导致性能下降。
3.栈,以降序分配内存地址;堆,以升序分配内存地址。

动态分配内存new 和 delete

int *p = new int(5);
delete p;
  1. 动态分配出来的内存没有变量名,只能通过指向它的指针来操作内存中的数据。
  2. 如果动态分配的内存不用了,必须用delete释放它,否则有可能用尽系统的内存。
  3. 动态分配的内存生命周期与程序相同,程序退出时,如果没有释放,系统将自动回收。
  4. 就算指针的作用域已经失效,所指向的内存也不会自动释放。
  5. 用指针跟踪已经分配的内存时,不能跟丢。

二级指针

二级指针用于存放指针变量的地址。
声明二级指针的语法:数据类型** 指针名;

int a = 8;
int *p = &a;
int **pp = &p;
cout << **pp << endl;//8
int *p = 0;
{
	int *pp = p;
	pp = new int(3);
	cout << pp << *pp << endl; // add 3
}
cout << p << *p << endl; // add 0

如果想在函数中修改p的值,需要把p的地址传给pp。
使用指针的目的:1) 传递地址;2) 存放动态分配的内存的地址。
在函数中,如果传递普通变量的地址,形参用指针;传递指针的地址,形参使用二级指针。
把普通变量的地址传入函数后可以在函数中修改这些变量的值;把指针的地址传入函数后可以在函数中修改指针的值。

空指针

在C/C++中,用0或者NULL都可以表示空指针。
声明指针后,在赋值之前,让它指向空,表示没有指向任何地址。

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

为什么使用空指针访问会出现异常?
NULL指针分配的分区:范围是0x00000000到0x0000FFFF。这段空间是空闲的,对于空闲的空间而言,没有相应的物理存储器与之相对应,所以对这段空间来说,任何读写操作都是会引起异常的。空指针是程序无论在何时都没有物理存储器与之相对应的地址。为了保障“无论何时”这个条件,需要人为划分一个空指针的区域,固有上面NULL指针分区。
C++11的nullptr
用0和NULL表示空指针会产生歧义,C++11建议用nullptr表示空指针,也就是(void*)0。
NULL在C++中就是0,这是因为在C++中void*类型是不允许隐式转换成其他类型的,所以之前C++中使用0来表示空指针,但是在重载整形的情况下,会出现上述的问题。所以,C++11加入了nullptr,可以保证在任何情况下都表示空指针。因此建议nullptr代替NULL,NULL当作0使用。
在Linux平台,如果使用nullptr,编译需要加-std=c++11参数。

野指针

野指针就算指针指向的不是一个有效(合法)的地址。
在程序中,如果访问野指针,可能会造成程序的崩溃。
出现野指针的三种情况:

  • 指针在定义的时候,没有初始化,值是乱指的。
  • 如果用指针指向了动态分配的内存,内存被释放后,指针不会置空,但是,指向的地址已经失效。
  • 指针指向的变量已经超越变量的作用域(变量的内存空间已经被系统回收)比如指向了函数中的返回值
    规避方法:
  • 指针在定义的时候,如果没地方指,就初始化为nullptr。
  • 动态分配的内存被释放后,将其置为nullptr。
  • 函数不要返回局部变量的地址。

一维数组和指针

sizeof(char)   //1
sizeof(short)  //2
sizeof(int)    //4
sizeof(double) //8

地址+1并不是字节+1,而是地址+sizeof(type)

  • 数组在内存中占用的空间是连续的。
  • C++将数组名解释为数组第0个元素的地址。
  • 数组第0个元素的地址和数组首地址的取值是相同的。
  • 数组第n个元素的地址是:数组首地址+n
  • C++编译器把数组名[下标] 解释为 *(数组首地址+下标)

多数情况下,C++将数组名解释为数组的第0个元素的地址,但是,将sizeof运算符用于数据名时,将返回整个数组占用内存空间的字节数。
可以修改指针的值,但是数组名是常量,不可修改。

一维数组用于函数的参数

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

一维数组用于函数的内存时,只能传数组的地址,并且必须把数组长度也传进去,除非数组中有最后一个元素的标志。
void func(int* arr, int len);
void func(int arr[], int len);
在函数中,可以用数组表示法,也可以用指针表示法。
在函数中,不要对指针名用sizeof运算符,它不是数组名。

其中用int* arr 替换了 int arr[]。这证明这两个函数头都是正确的,因为在C++中,当(且仅当)用于函数头或函数原型中,int *arr 和 int arr[]的含义才是相同的。他们都意味着arr是一个int指针。然而,数组表示法(int arr[])提醒用户,arr不仅指向int,还指向int数组的第一个int。当指针指向数组的第一个元素时,本书使用数组表示法;而当指针指向一个独立的值时,使用指针表示法。别忘了,在其他的上下文中,int *arr 和 int arr[]的含义并不相同。

for(int i=0;i<len;++i)
{
	//等价
	cout << i << arr[i] << endl;
	cout << i << *(arr + i) << endl;
}

用new动态创建一维数组

普通数组在栈上分配内存,栈很小;如果需要存放更多的元素,必须在堆上分配内存。
动态创建一维数组数据类型 *指针 = new 数据类型[数组长度];
释放一维数组delete [] 指针;
注意:

  • 动态创建的数组没有数组名,不能用sizeof运算符。
  • 可以用数组表示法和指针表示法两种方式使用动态创建的数组。
  • 不要用delete [] 来释放不是new [] 分配的内存。
  • 不要用delete [] 来释放同一个内存块两次(否则等同于操作野指针)。
  • 对空指针用delete []是安全的,释放内存后,应该把指针置为nullptr。
  • 如果内存不足,调用new会产生异常,导致程序中止,如果在new关键字后面加上(std::nothrow) 选项,则返回nullptr,不会产生异常。
int* a = new (std::nothrow) int[1000000000001];
if(a == nullptr) cout<<"error"<<endl;
  • delete [] 释放数组的时候,不需要指定数组大小,系统会自动跟踪已经分配数组的内存。

二维数组用于函数的参数

 int* p; //整型指针
 int* p[3]; //一维整型指针数组
 int* p();  //函数p的返回值类型是整型地址
 int (*p)(int,int); //p是指针函数,函数的返回值是整型

行指针(数组指针)
数据类型 (*行指针名)[行的大小,即数组的长度];

int (*p1)[3];  //p1是行指针,用于指向数组长度为3的int型数组
int (*p2)[5];  //p2是行指针,用于指向数组长度为5的int型数组
double (*p3)[5];  //p3是行指针,用于指向数组长度为5的double型数组
int a[10] = {1,2,3,4,5,6,7,8,9,0};
cout << a << endl;        //0x61fde0
cout << &a << endl;       //0x61fde0
cout << a+1 << endl;      //0x61fde4
cout << &a+1 << endl;     //0x61fe08
int *p1 = a;
int (*p2)[10] = &a;
cout << (*p1)  << endl;   //1
cout << (*p2)[0] << endl; //1

int bh[2][3] = {{11,12,13}, {21,22,23}};
int (*p)[3] = bh;
cout << bh << endl;     //0x61fe00
cout << &bh << endl;    //0x61fe00
cout << (*p) << endl;   //0x61fe00
cout << (*p)[0] << endl;//11
cout << (*p)[3] << endl;//21

可以按照方块队来解释,a+1就是+4,访问下一个int;&a+1就是+40,访问下一列的第一个int,因为一行有十个元素,所以+40。

函数指针和回调函数

函数的二进制代码存放在内存四区中的代码段,函数的地址是它在内存中的起始地址。如果把函数的地址作为参数传递给函数,就可以在函数中灵活的调用其它函数。
使用函数指针的三个步骤:
a) 声明函数指针;
b) 让函数指针指向函数的地址;
c) 通过函数指针调用函数。

理解:
我们不可能在当前函数中直接调用另一个我们需要的函数(没准需要哪个函数,我也就不可能写一个确定的函数名,但是通过函数指针,只需要不同的参数,可以个性化需求,不需要提前知道函数名),因为在写函数的时候,根本不知道这个函数会被谁调用。
回调函数是把一个函数的代码嵌入到另一个函数中,调用者函数提供了主体的流程和框架,具体的功能可以由回调函数来实现。我们在写调用者函数的时候,只确定回调函数的种 类,不关系回调函数的功能。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值