啥是指针
指针变量简称指针,是一种特殊类型的变量,专用于存放变量在内存中的起始地址。
指针也是一种类型。比如
int *p = &a;
其中指针变量p中存储的是变量a的地址,也可以说指针p指向变量a。
在32位编译器,指针占4个字节,64位编译器占8个字节。不管什么类型的指针,大小都是一样的。
*运算符被称为解引用,通过指针访问变量,*p取得是p指向的变量a的值。
#include <iostream>
using namespace std;
int main()
{
int a = 3;
int *pa = &a;
cout << "a:" << a << endl;
cout << "*pa" << *pa << endl;
}
并且,可以通过指针解引用的方法对变量进行赋值,效果和用变量名一样。
#include <iostream>
using namespace std;
int main()
{
int a = 3;
int *pa = &a;
cout << "a:" << a << endl;
cout << "*pa" << *pa << endl;
*pa = 10;
cout << "a:" << a << endl;
cout << "*pa" << *pa << endl;
}
其中 *pa=10 等价于 a=10。
多个指针可以指向同一个变量。
const修饰指针
*表示指针,指针在前先读指针
常量指针
语法:const 数据类型 *变量名
例:const int *pa;
其中, const修饰int,不能通过解引用的方法修改内存中的值,但是用原始的变量名可以修改。
注意:
- 指向的变量可以改变,指针指向a,可以修改为指向b
- 一般用于修饰函数的形参,表示不能在函数中修改内存地址中的值
- 如果用于形参,虽然指向的对象可以改变,但是这么做没有意义
- 如果形参的值不需要改变,建议加上const,可读性好
指针常量
语法:数据类型 * const 变量名
例:int * const pa
其中,const修饰*,表示指向的变量不可改变,开始指向a,不能修改为指向b
注意:
- 定义的时候必须初始化,否则没有意义
- 可以通过解引用的方法修改所指向变量的值
- 指针常量也叫引用
常指针常量
语法:const 数据类型 * const 变量名
例:int const * const 或 const int * const
指向的变量不可改变,不能通过解引用的方式修改内存地址中的值。
- 常指针常量也叫常引用
小结
常量指针:指针指向可以改,指针指向的值不可修改
指针常量:指针指向不可以改,指针指向的值可以修改
常指针常量:指针指向不可以改,指针指向的值不可以修改
指针的使用
用作函数参数
如果把函数的形参声明为指针,调用的时候把实参的地址传递进去,形参中存放的是实参的地址,在函数中通过解引用的方式可以修改内存中的值。
- 可以在函数中修改实参的值
- 减少内存拷贝
- 可以用指针参数作为函数的返回值
void
void有三种用法
- 用作函数的返回值,表示函数没有返回值 例如 void func(int a);
- 函数的参数填void,表示函数不需要参数,例如 int func(void),等同于int func()
- 函数的形参用void *,表示接受任意数据类型的指针
C++内存模型
堆
- 需要手动释放
- 堆内存大小受限于物理内存大小
- 堆是动态分配的
栈
- 系统自动管理,在出作用域后自动释放
- 栈内存很小,一般只有8M,可以修改
- 栈有静态分配和动态分配
C动态内存分配
申请内存
malloc函数,参数是申请的字节大小,返回值是申请得到的地址指针。
#include <stdlib.h>
#include <iostream>
using namespace std;
int main() {
void * p = malloc(sizeof(int) * 10);
cout << "p:" << p <<endl;
return 0;
}
释放内存
free函数,参数是申请得到的指针
#include <stdlib.h>
#include <iostream>
using namespace std;
int main() {
void * p = malloc(sizeof(int) * 10);
cout << "p:" << p <<endl;
free(p);
return 0;
}
动态内存使用建议
- 避免修改指向已分配的内存的指针
- 对于free之后的指针主动置为null
- 避免将过多的指针指向动态分配的内存
- 动态内存应遵循谁分配谁释放的原则
C++动态内存分配
使用堆区内存的四个步骤
- 声明一个指针
- 用new运算符向系统申请一块儿内存,并用指针指向它
- 通过对指针解引用,像使用普通变量一样使用这块儿内存
- 用完后,用delete运算符释放内存
new运算符
语法:new 数据类型(初始值);
申请成功后返回一个地址,申请失败返回空指针(同C)
#include <iostream>
using namespace std;
int main(){
int *p;
p = new int(10);
cout << "*p=" << *p << endl;
return 0;
}
结果:
*p=10
delete运算符
#include <iostream>
using namespace std;
int main(){
int *p;
p = new int(10);
cout << "*p=" << *p << endl;
delete(p);
return 0;
}
注意事项
- 动态分配出来的内存没有变量名,只能通过指针来操作内存中的数据
- 如果动态分配的内存不用了,需要用delete释放调
- 动态分配的内存生命周期与程序相同,如果不释放则程序退出时释放
- 如果指针的作用域失效,所指向的内存不会释放,所以避免修改指向已分配的内存的指针
空指针
声明指针后,在赋值之前,让它指向空,表示没有指向任何地址,用0或NULL都可以表示空指针。
使用空指针后果
- 对空指针解引用,程序会崩溃
- 对空指针delete,系统会忽略该操作,不会出现异常,所以内存释放后把指针指向空
C11中的nullptr
用0或NULL表示空指针会产生歧义,C11建议使用nullptr表示空指针,也就是(void *)0
野指针
指针指向的不是一个有效的地址,访问野指针,可能会导致程序崩溃。
出现野指针的三种情况
- 指针在定义的时候没有初始化,它的值是不确定的
- 如果用指针指向了动态分配的内存,内存释放后,指针没有置空
- 指针指向的变量已经超出了变量作用域
函数指针
函数指针作为参数传递给函数,一般用于回调。
使用步骤
- 声明函数指针
- 让函数指针指向函数的首地址
- 通过函数指针调用函数
函数指针的声明
函数类型指返回值和参数列表,不包含函数名和参数名,如果两个函数的返回值和参数列表一样,那么它们就是相同类型的函数。
例:
int (*pfa)(int,string)
bool (*pfb)(int,double)
#include <iostream>
using namespace std;
int run(){
cout << "run" << endl;
return 1;
}
int fly(){
cout << "fly" << endl;
return 2;
}
int move(int(*pf)()){
cout << "before move" << endl;
int res = pf();
cout << "after move" << endl;
return res;
}
int main()
{
cout << move(run) << endl;
cout << move(fly) << endl;
return 0;
}