const 修饰指针
只写一种:常量指针const 数据类型 *变量名;
不能通过解引用的方法修改内存地址中的值(可以用原始的变量名修改)。
这种写法,指针指向的对象是可以改变的。
void* 指针
void*
表示可以接收任意数据类型的指针。
- 不能用void* 指针声明变量。
- 不能对void* 指针直接解引用(需要转换成其他类型的指针)。
- 其他类型变量赋值给void* 指针不需要转换。
- void* 指针赋值给其他类型的指针需要转换。
C++内存模型
重点关心 栈 and 堆 区。
管理方式:
栈是系统自动管理,在出作用域时,将被自动释放;堆需要手动释放,若程序中不释放,程序结束时由操作系统回收。
空间大小:
堆内存的大小受限于物理内存空间;
栈内存,一般就只有8M(可以修改系统参数,但没必要)。
不是很重要:
1.栈是系统提供的数据结构,计算机在底层提供了堆栈的支持,进栈和出栈都有专门的指令,效率比较高;堆是由C++函数库提供的。
2.栈不会产生碎片,堆频繁的分配和释放,会造成内存空间不连续,容易产生碎片,太多碎片会导致性能下降。
3.栈,以降序分配内存地址;堆,以升序分配内存地址。
动态分配内存new 和 delete
int *p = new int(5);
delete p;
- 动态分配出来的内存没有变量名,只能通过指向它的指针来操作内存中的数据。
- 如果动态分配的内存不用了,必须用delete释放它,否则有可能用尽系统的内存。
- 动态分配的内存生命周期与程序相同,程序退出时,如果没有释放,系统将自动回收。
- 就算指针的作用域已经失效,所指向的内存也不会自动释放。
- 用指针跟踪已经分配的内存时,不能跟丢。
二级指针
二级指针用于存放指针变量的地址。
声明二级指针的语法:数据类型** 指针名;
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。
- 函数不要返回局部变量的地址。