【在指针中存储地址】
int *pAge=nullptr; //将PAge声明为int指针,即用于存储int变量的地址
如果将指针初始化为0或者NUll,以后必须将变量的地址赋给它,如下例代码:
int howOld=50; int *pAge=nullptr; pAge=&howOld;
【间接运算符(解除引用运算符)】
int howOld=50; int *pAge=nullptr; pAge=&howOld; int yourAge; yourAge=*pAge; //*表示存储在.....处的值。
【通过指针访问指针中存储的内容,指针存储的内容(地址)指向的值,和指针本身的地址】
int howOld=50; int *pAge=nullptr; pAge=&howOld; std::cout<<"the address of howOld"<<pAge<<std::endl; //howOld的地址 std::cout<<"the value of howOld"<<*pAge<<std::endl; //howOld的值 std::cout<<"the address of pAge"<<&pAge<<std::endl; //pAge本身的地址
【为何使用指针】
指针最常用于完成如下三项任务。
1.管理堆中的数据
2.访问类的成员和成员函数。
3.按引用将变量传递给函数
【栈和堆】
程序员通常需要处理下述的5个内存区域
1.全局名称空间
2.堆
3.寄存器
4.代码空间
5.栈
局部变量和函数参数存储在栈中,
代码存储在代码空间中
全局变量在全局名称空间中。
寄存器用于内部管理,如跟踪栈顶和指令指针。
余下的几乎所有内存都分配给了堆。堆也被称为自由存储区
(每当函数返回时,都会清理栈,此时,所有局部变量都不在作用域内,从而从栈中删除,。
只有到程序结束后才会清理堆,因此使用完预留的内存后,你需要负责将其释放。让不再需要的信息留在堆中称为内存泄漏)
堆的优点在于,在显示释放前,你预留的内存始终可用。如果在函数中预留堆中的内存,在函数返回后,该内存仍可用。
【使用关键字new】
在C++中,要分配堆中的内存,可使用关键字new,并在它后面指定要为之分配内存对象的类型,让编译器知道需要多少个内存,例如,new unsigned short int从堆中分配2个字节内存,而new long 分配4字节内存。
关键字new 返回一个内存地址,必须将其赋给指针。要在堆中创建一个unsigned short ,可编写如下代码:
unsigned short int *pPointer; pPointer=new unsigned short int;
当然也可在声明指针的时候初始化它:
unsigned short int *pPointer=new unsigned short int;
无论采取哪种方式,pPointer 都将指向堆中的一个unsigned short int.可像使用指向变量的指针那样使用它,将一个值存储到该内存区域:
*pPointer=72;
如果不能从堆中分配内存(因为内存资源有限),将引发异常。
【使用关键字delete】
使用完分配的内存区域后,必须对指针调用delete,将内存归还给堆。别忘了,指针本身为局部变量,这不同于它指向的内存;当申明指针的函数返回时,指针将不再在作用域中,因此被丢弃。然而,使用new关键字分配的内存并不会自动释放,这些内存将不可用,这被称为内存泄露,因此程序结束前,内存不会归还给堆,就像内存从计算机中漏掉了。
要将内存归还给堆,可使用关键字delete,例如
delete pPointer
删除指针时,实际释放了其地址存储在指针中的内存,这被称为将指针指向的内存归还给堆。指针还是指针,可重新给它复制
对指针调用delete时,将释放它指向的内存。如果再次对该指针调用delete,这将导致程序崩溃!删除指针时,应将其设置为NULL,而对空指针调用delete是安全的。
Animal *pDog=new Animal; delete pDog; //free the memory pDog=nullptr; //sets pointer to null delete pDog; //harmless
以下程序Heap演示了如何给一个变量分配堆内存,然后使用并删除它,并再次使用指针
//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; if(pHeap==nullptr) { std::cout<<"Error!No memory for pHeap!"; return 1; } *pHeap=9; std::cout<<"pHeap: "<<pHeap<<std::endl; delete pHeap; return 0; }
程序输出如下:
localVariable:5
*pLocal:5
*pHeap:7
*pHeap:9
虽然最后调用delete pHeap是多余的(程序结束时,将自动归还内存),但是显式的释放内存是一个不错的主意,修改或扩展该程序时,这种操作将带来明显的好处
【避免内存泄漏】
第一种内存泄漏是由于:在函数返回值之前没有delete pPointer,当申明指针的函数返回时,指针将不再在作用域中,因此被丢弃。然而,使用new关键字分配的内存并不会自动释放,这些内存将不可用,这被称为内存泄露
另一种内存泄漏是因为:没有释放指针指向的内存就给他重新复制。例如下面的代码:
unsigned short int *pPointer=new unsigned short int; *pPointer=72; pPointer=new unsigned short int; *pPointer=85;
这样会导致第一个内存区域(值为72)不可用.导致程序在结束之前都无法将其释放
正确的代码如下:
unsigned short int *pPointer=new unsigned short int; *pPointer=72; delete pPointer pPointer=new unsigned short int; *pPointer=85;
注意:程序中每个new 调用都必须有对应的delete调用,应跟踪哪个指针指向了内存区域,并使用完后将内存释放,这很重要。
【开发高级指针】
【在堆中创建对象】
定义了类型Cat后,便可以声明一个指向这种对象的指针,并在堆中实例化一个Cat对象,就像在栈中实例化一样,其语法与在堆中分配int相同
Cat *pCat=new Cat;
这将调用默认构造函数-不接受任何参数的构造函数。每当在堆中或栈中创建对象时,都将调用构造函数
【删除对象】
对指向堆中对象的指针调用delete时,将调用对象的析构函数,然后释放内存。这让类有机会执行清理工作,就像销毁栈中的对象一样。
【使用指针访问数据成员】
可以这样访问: (*pCat).GetAge();
也可以这样访问:pCat->GetAge();
【堆中的数据成员】
类可能有一个或多个数据成员为指针,指向堆中的对象。可在构造函数或成员函数中分配内存,并在析构函数中释放内存,例如以下程序
1 #include<iostream> 2 3 class SimpleCat 4 { 5 public: 6 SimpleCat(); 7 ~SimpleCat(); 8 int GetAge() const {return *itsAge;} 9 void SetAge(int age){*itsAge=age;} 10 11 int GetWeight() const {return *itsWeight;} 12 void SetWeight(int weight){*itsWeight=weight;} 13 14 private: 15 int *itsAge; 16 int *itsWeight; 17 }; 18 19 SimpleCat::SimpleCat() 20 { 21 itsAge=new int(2); 22 itsWeight=new int(5); 23 } 24 25 SimpleCat::~SimpleCat() 26 { 27 delete itsAge; 28 delete itsWeight; 29 } 30 int main() 31 { 32 SimpleCat *Frisky=new SimpleCat; 33 std::cout<<"Frisky is "<<Frisky->GetAge()<<" years old\n"; 34 Frisky->SetAge(23); 35 std::cout<<"Frisky is "<<Frisky->GetAge()<<" years old\n"; 36 delete Frisky; 37 system("pause"); 38 return 0; 39 }
第25~29行的析构函数释放分配的内存。在析构函数中,将nullptr赋给这些指针没有任何意义,因为他们将不可访问。对于已经删除的指针,应该将nullptr赋给它,但这是可违反这种规则的唯一地方,虽然遵守这种规则没有任何坏处
(调用方(这里指main())不知道itsAge和itsWeight是指向堆内存的指针,它不断的调用GetAge()和SetAge(),而内存管理的细节隐藏在类中实现)。
this指针:
每个类成员函数都有一个隐藏的参数:this指针,它指向相应的对象。因此,每次调用GetAge()或者SetAge()时,都通过隐藏参数包含指向相应对象的this指针。
this指针指向其函数被调用的对象,通常不需要它,而只是调用函数并设置成员变量,但偶尔需要访问对象本身(可能旨在返回一个指向当前对象的指针),在这种情况下,this指针将很有用。(该指针将在后续讲到)
悬摆指针:
悬摆指针是导致讨厌且难以发现的bug的罪魁祸首之一!!!!
对指针调用delete(从而释放他指向的内存)后,如果没有重新赋值就使用它,将导致悬摆指针。
注意!!!:::对指针调用delete后,千万不要使用它。改指针仍指向原来的内存区域。但编译器可能在这里存储了其他数据;使用该指针会导致程序崩溃。更糟糕的是,程序可能继续运行,但几分钟后崩溃了。所以出于安全考虑。删除指针后,应将其设置为nullptr,这便解除了它的武装。
const指针:
声明指针时,可在类型前,类型后或者两个使用const 。例如下面声明都合法
const int *pOne; int * const pTwo; const int * const pThree;
pOne是指向整型常量的指针,即使用该指针不能修改它指向的值。 例如 *pOne=5是非法的
pTwo是指向整型的常量指针,可修改指向的整型变量,但pTwo不能指向其他变量。不能给常量指针重新赋值,例如: pTwo=&x; 是非法的
pThree 是指向整型常量的常量指针,不能修改它指向的值,也不能让它指向其他的变量。
const指针和const成员函数
如果声明了一个指向常量对象的指针,那么使用该指针只能调用常量函数
#include<iostream> class Rectangle { public: Rectangle(); ~Rectangle(); void SetLength(int length){itsLength=length;} int GetLength() const {return itsLength;} void SetWidth(int width){itsWidth=width;} int GetWidth() const {return itsWidth;} private: int itsLength; int itsWidth; }; Rectangle::Rectangle():itsLength(10),itsWidth(5) { } Rectangle::~Rectangle() { } int main() { Rectangle *pRect=new Rectangle; const Rectangle *pConstRect=new Rectangle; Rectangle * const pConstPtr=new Rectangle; std::cout<<"pRect width: "<<pRect->GetWidth()<<" feet\n"; std::cout<<"pConstRect width: "<<pConstRect->GetWidth()<<" feet\n"; std::cout<<"pConstPtr width: "<<pConstPtr->GetWidth()<<" feet\n"; pRect->SetWidth(10); //pConstRect->SetWidth(10); pConstPtr->SetWidth(10); std::cout<<"pRect width: "<<pRect->GetWidth()<<" feet\n"; std::cout<<"pConstRect width: "<<pConstRect->GetWidth()<<" feet\n"; std::cout<<"pConstPtr width: "<<pConstPtr->GetWidth()<<" feet\n"; system("pause"); return 0; }
可以看到由于编译器不允许pConstRect调用SetWidth()函数,只能注释掉才能运行。
运行结果如下所示:
pRect width: 5 feet
pConstRect width: 5 feet
pConstPtr width: 5 feet
pRect width: 10 feet
pConstRect width: 5 feet
pConstPtr width: 10 feet
总结:
1.指针可指向整型等简单数据类型,也可指向对象。
2.可在堆中创建对象,并将其删除。如果你声明了一个类,可声明指向其对象的指针,并在堆中实例化对象。
3.类的数据成员可以是指向堆中对象的指针。可以在类的构造函数或其他函数中分配内存,并在析构函数中将其释放。