1. 指针是什么?
1.1 理解指针及其用途
-
变量
是可存储一个值的对象:整型变量
存储一个数字;字符变量
存储一个字母; -
指针
是存储内存地址
的变量 -
计算机内存是存储变量值的地方。根据约定,计算机内存被划分成按顺序编号的内存单元,每个内存单元都有地址(内存编址方案随计算机而异,不必要知道每个变量的地址对应的数字,重点是关心每个变量都有地址且编译器依据声明的变量类型给它分配了正确的内存量)
1.2 指针、地址和变量
int a = 5; int* p = &a; cout << "a= " << a << endl; //变量, a = 5 cout << "p= " << p << endl; //指针p, p = 000000F4DEDCFC44 cout << "*p= " << *p << endl; //解引用,*p = 5
1.3 为何使用指针
指针常用于完成以下三项任务:
① 管理堆中数据;
② 访问类的成员数据和成员函数;
③ 按引用将变量传递给函数;
2. 如何声明和使用指针?
2.1 在指针中存储地址
-
每个变量都有地址,即使不知道变量的具体地址,也可以将地址存储在指针中
-
声明指针:
int *pAge = nullptr; //注意,pAge 也是变量(指针是一种特殊的变量),用于存储对象在内存中的地址。 //此处 pAge 存储了一个int变量的地址,初始化为 nullptr(空指针 = NULL = 0)
-
声明指针时,指定它将指向的变量类型(这告诉编译器如何处理指针指向的内存单元,指针自己本身包含了一个地址)
-
声明指针时,必须将其初始化(如果不知道指什么值,就指向空)
-
未被初始化的指针称为
野指针
,野指针十分危险 -
如果将指针初始化为 nullptr,后面必须将变量的地址赋给它:
int HowOld = 50; int *pAge = nullptr; pAge = &HowOld; //① 创建 int变量 HowOld,并初始化为50; //② 将 pAge 声明为 int*指针,并初始化为 nullptr(空指针); //③ 将 HowOld 的地址赋值给 pAge (取址符&) //上述代码可优化: int HowOld = 50; int *pAge = &HowOld; //使用 pAge 来访问 HowOld 的值,被称为 间接访问
-
间接访问
指的是访问指针存储的地址处的值,指针提供了一种间接获取值的方式
2.2 间接运算符(间接地使用指针)
-
间接运算符 / 解除引用运算符
*
int HowOld = 50; int* pAge = &HowOld; int YourAge; YourAge = *pAge; //从 pAge 指向的地址处获取值,并将其赋给 YourAge cout << HowOld << endl; cout << YourAge << endl; //50 //50
-
注意区分:
间接运算符
*
以两种不同的方式作用于指针,即声明
和解除引用
(1)声明指针时(
int* p = &a;
),*
表示这是指针,而非其他变量(2)解除引用时(
*p = 5;
),*
表示访问指针指向的内存单元中的值,而非地址本身(3)此外,
*
还表示乘法运算符,编译器会根据上下文来判定int a = 50; int* p = &a; *p = 5; cout << p << endl; // 等价于cout << &a << endl; cout << *p << endl; //000000392176FC74 //5
2.3 使用指针操作数据
(改)
int myAge; // 声明一个变量 int* pAge = NULL; // 声明一个空指针 myAge = 5; // 变量赋值5 pAge = &myAge; // 指针 pAge 获取变量的地址 cout << "myAge: " << myAge << endl; // myAge: 5 cout << "*pAge: " << *pAge << endl; // *pAge: 5 *pAge = 7; // 解引用 *pAge = 7(指针指向的内存单元中的值,修改为7),等价于 myAge = 7; cout << "*pAge: " << *pAge << "\n"; // myAge: 7 cout << "myAge: " << myAge << "\n\n"; // *pAge: 7 myAge = 9; // 变量赋值9 cout << "myAge: " << myAge << "\n"; // myAge: 9 cout << "*pAge: " << *pAge << "\n"; // *pAge: 9
(查)
没啥好说的,就是注意一下,指针自己本身也有相应的地址,该地址可存储在指针中(也可以赋给其他的指针)
3. 什么是堆,如何操作内存?
3.1 堆和栈
-
程序员通常需要处理以下5个内存区域:
-
全局名称空间
-
堆
-
寄存器
-
代码空间
-
栈
-
-
栈
中存储了局部变量
和函数参数
;代码空间
放了代码
;全局名称空间
存储了全局变量
;寄存器
用于内部管理(如跟踪栈顶和指令指针)
;余下所有的内存都分给了
堆
,堆有时也被称为自由存储区
。
-
① 局部变量的局限性是不会持久化,函数返回时,局部变量将丢弃;
② 全局变量解决了上述问题,但代价是在整个程序中都能访问它,这导致代码容易出bug;
而将数据放在
堆
中可以解决上述2个问题。
-
堆的优点在于:在显示释放前,预留的内存始终可用。如果在函数中预留堆中的内存,在函数返回后,该内存仍可用。
-
以这种方式(而不是全局变量)访问内存的优点是:只有有权访问指针的函数才能访问它指向的数据。这提供了控制严密的数据接口,消除了函数意外修改数据的问题。
3.2 使用关键字 new
-
使用关键字
new
分配堆中内存,并在它后面指定要为之分配内存的对象的类型int *p; p = new int; //等价于: int *p = new int; //关键字new返回一个内存地址,必须将其赋给指针(分配内存时,注意指针不要为空)
3.3 使用关键字 delete
-
在使用完分配的内存区域后,必须对指针调用
delete
,将内存归还给堆 -
使用 new 运算符分配的内存不会自动释放,这些内存将不可用,这被称为
内存泄露
,因为在程序结束前,内存不会归还给堆,就像内存从计算机中漏掉了delete p; //删除指针时,实际是释放了其地址存储在指针中的内存,,这被称为将指针指向的内存归还给堆 //指针还是指针,可重新给它赋值 //对指针调用 delete 时,将释放它指向的内存。如果再次对该指针调用 delete,就将导致程序崩溃 p = nullptr; delete p; //删除指针时,应将其置空,再对空指针调用 delete,则是安全的
3.4 避免内存泄露
-
除了 new 完不释放,还有一种无意间导致内存泄露的情况:没有释放指针指向的内存就给它重新赋值。
int *p = new int; *p = 72; delete p; //程序中每个 new 调用都必须有对应的 delete 调用,使用完后及时将内存释放出来 p = new int; *p = 82;
3.5 空指针常量
-
使用指针时,需要将指针初始化(未初始化的指针比较野)
int *p = 0; int *p = NULL; //这2条语句等效,因为 NULL 是一个预处理器宏,将被转换为0
-
而又因为 0 既可以表示指针常量,又可表示整形常量,带来了二义性
int *p = nullptr; //出于向后兼容的考虑,使用关键字 nullptr 来给指针置空
//(nullptr 不会被隐式地转换为整数,在需要布尔值的地方,它将被转换为False)