《c++入门经典》笔记10
《c++入门经典》笔记10
第十章 创建指针
10.1理解指针及其用途
变量是可存储一个值的对象:整型变量存储一个数字,字符变量存储一个字母,而指针是存储内存地址的变量。
计算机内存是存储变量值的地方。根据约定,计算机内存被划分成按顺序编号的内存单元,每个内存单元都有对应的地址。内存中,不管其类型是什么,每个变量都位于特定的地址处。
内存编址方案随计算机而异。通常,程序员无须知道变量地址,如果想要获取地址信息,可使用地址运算符&
程序清单10.1 Address.cpp
#include<iostream>
int main()
{
unsigned short shortVar = 5;
unsigned long longVar = 65535;
long sVar = -65535;
std::cout<<"shortVar: "<<shortVar;
std::cout<<" Address of shortVar: "<<&shortVar<<"
";
std::cout<<"longVar: "<<longVar;
std::cout<<" Address of longVar: "<<&longVar<<"
";
std::cout<<"sVar: "<<sVar;
std::cout<<" Address of sVar: "<<&sVar<<"
";
}
(地址默认以16进制表示法输出的)
您运行该程序时,变量的地址将不同,因为这取决于内存中存储的其他内容以及可用的内存有多少。
在指针中存储地址
每个变量都有地址,即使不知道变量的具体地址,也可将该地址存储在指针变量中。
int howOld = 50;
int* pAge = nullptr;//初始化一个int型空指针变量,这样能更明显看出来pAge类型是int*,但c/c++的标准写法是int *pAge
pAge = &howOld;//将howOld的地址取出来放入指针变量pAge中
间接运算符
间接运算符(*)又被称为解引用运算符。对指针解除引用时,将获取指针存储的地址处的值。
int howOld = 50; int* pAge = &howOld; int yourAge; yourAge = *pAge;//yourAge的值变成了50 *pAge = 10;//howOld的值变成了10,而yourAge的值还是50
指针pAge前面的间接运算符(*)表示“存储在……处的值”。这条赋值语句的意思是,从pAge指向的地址处获取值,并将其赋给yourAge。看待这条语句的另一种方式是,不影响指针,而是影响指针指向的内容(比如上面最后一条语句)。
使用指针操作数据(其实上面那个例子就是)
程序清单10.2 Pointer.cpp
#include <iostream>
int main()
{
int myAge;
int *pAge = nullptr;
myAge = 5;
pAge = &myAge;
std::cout << "myAge: " << myAge << "
";
std::cout << "*pAge: " << *pAge << "
";
std::cout << "*pAge = 7
";
*pAge = 7;
std::cout << "myAge: " << myAge << "
";
std::cout << "*pAge: " << *pAge << "
";
std::cout << "myAge = 9
";
myAge = 9;
std::cout << "myAge: " << myAge << "
";
std::cout << "*pAge: " << *pAge << "
";
}
查看存储在指针中的地址:
程序清单10.3 PointerCheck.cpp
#include <iostream>
int main()
{
unsigned short int myAge = 5, yourAge = 10;
unsigned short int *pAge = &myAge;
std::cout << "pAge: " << pAge << "
";
std::cout << "*pAge: " << *pAge << "
";
pAge = &yourAge;
std::cout << "after reassign the pAge point to yourAge : "
<< "
";
std::cout << "pAge: " << pAge << "
";
std::cout << "*pAge: " << *pAge << "
";
return 0;
}
为何使用指针
熟悉指针的语法后,便可将其用于其他用途了,指针最长用于完成如下三项任务:
- 管理堆中的数据;
- 访问类的成员数据和成员函数;
- 按引用将变量传递给函数
10.2堆和栈
(这部分其实如果有一点数据结构或者操作系统基础更好)
程序员通常需要处理下述五个内存区域
- 全局名称空间
- 堆
- 寄存器
- 代码空间
- 栈
局部变量和函数参数存储在栈中,代码当然在代码空间中,而全局变量在全局名称空间中。寄存器用于内存管理,如跟踪栈顶和指令指针,几乎余下的所有内存都分配给了堆,堆有时也被称为自由存储区。
局部变量的局限性是不会持久化,函数返回时,局部变量将被丢弃。全局变量解决了这种问题,但代价是在整个程序中都能访问它,这导致代码容易出现bug,难以理解与维护。将数据放在堆中可解决这两个问题。
每当函数返回时,都会清理栈(实际上,开始调用函数时,栈空间进行压栈操作;函数调用完时,栈空间进行出栈操作)。此时,所有的局部变量都不在作用域内,从而从栈中删除。只有到程序结束后才会清理堆,因此使用完预留的内存后,您需要负责将其释放(手动GC)。让不再需要的信息留在堆中称为内存泄露(垃圾滞留)。
堆的优点在于,在显示释放前,您预留的内存始终可用。如果在函数中预留堆中的内存,在函数返回后,该内存仍可用。
以这种方式(而不是全局变量)访问内存的优点是,只有有权访问指针的函数才能访问它指向的数据。这提供了控制严密的数据接口,消除了函数意外修改数据的问题。
关键字new
在c++中,使用new关键字分配堆中的内存,并在其后指定要为之分配内存的对象的类型,让编译器知道需要多少内存。比如
new int
分配4字节内存。关键字new返回一个内存地址,必须将其赋给指针。
int *pPointer = new int;//指针pPointer将指向堆中的一个int变量 *pPointer = 72;//将72赋值给pPointer指向的堆内存变量
关键字delete
使用好了分配的内存区域后,必须对指针调用delete,将内存归还给堆空间。
delete pPointer;
对指针调用delete时,将释放它指向的内存。如果再次对该指针调用delete,就会导致程序崩溃(delete野指针)。删除指针时,应将其设置为nullptr,对空指针调用delete是安全的。
Animal *pDog = new Animal; delete pDog;//释放内存 pDog = nullptr;//设置空指针 delete pDog;//安全行为
程序清单10.4 Heap.cpp
#include <iostream>
int main()
{
int localVariable = 5;
int *pLocal = &localVariable;
int *pHeap = new int;
if (pHeap == nullptr)
{
std::cout << "Error! No memory for pHeap!!";
return 1;
}
*pHeap = 7;
std::cout << "localVariable: " << localVariable << std::endl;
std::cout << "*pLocal: " << *pLocal << std::endl;
std::cout << "*pHeap: " << *pHeap << std::endl;
delete pHeap; //此处只是释放了堆中new分配的内存,并没有删除指针,所以下面可以接着用。
pHeap = new int;
if (pHeap == nullptr)
{
std::cout << "Error! No memory for pHeap!!";
return 1;
}
*pHeap = 9;
std::cout << "*pHeap: " << *pHeap << std::endl;
delete pHeap; //再次释放new出来的内存
return 0;
}
另一种可能无意间导致内存泄露的情形是,没有释放指针指向的内存就给它重新赋值。
int *pPointer = new int; *pPointer = 72; pPointer = new int; *pPointer = 50;//指针变量指向了一个新new出来的存有50的int型变量,但是之前那个存有72的堆内存变量还没被释放,也就造成了内存泄露
上述代码应该修改成这样:
int *pPointer = new int; *pPointer = 72; delete pPointer; pPointer = new int; *pPointer = 50;
也就是说一个不存在内存泄露的c++程序至少其new与delete是成对的,或者说是数量相等的。(这可苦了c艹程序员咯!)
空指针常量:
在较早的c++版本中,使用0或者NULL来设置指针为空值。
int *pBuffer = 0;
int *pBuffer = NULL;
但是其实因为定义NULL的语句是一个预处理宏:
#define NULL 0
所以其实NULL和0一个意思,上面两句也是一个意思。
但是当某个函数进行了重载,参数有指针类型也有int型的时候,传了一个值为0的空值指针进去,这个时候会出现二义性:
void displayBuffer(char*);//这个函数的参数是char型指针变量
void displayBuffer(int);
如果将空指针作为参数去调用,那么会调用displayBuffer(int),这个时候就会造成运行与预期不同。
所以才应该使用关键字nullptr
int *pBuffer = nullptr;
程序清单10.5 Swapper.cpp
#include <iostream>
int main()
{
int value1 = 12500;
int value2 = 1700;
int *pointer2 = nullptr;
pointer2 = &value2;
value1 = *pointer2;
pointer2 = 0;
std::cout << "value = " << value1 << "
";
return 0;
}