C++(指针和高级指针)-上篇

【在指针中存储地址】

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.类的数据成员可以是指向堆中对象的指针。可以在类的构造函数或其他函数中分配内存,并在析构函数中将其释放。

 

转载于:https://www.cnblogs.com/jianjipan/p/10748945.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值