1. 程式人生 > >棧與堆、作用域與生存期

棧與堆、作用域與生存期

原文地址:https://www.jianshu.com/p/29e8f2930cf5

問題引入

// correct
#include<iostream>
#include<math.h>
#include<stdlib.h>
using namespace std;

/* 幾何形體處理程式: 
 * 輸入若干個幾何形體的引數,要求按面積排序輸出。
 * 輸出時要指明形狀。
 * Input:
 * 第一行是幾何形體數目n(不超過100)
 * 下面有n行,每行以一個字母c開頭
 * 若 c 是 ‘R’ ,則代表一個矩形,本行後面跟著兩個整數,分別是矩形的寬和高
 * 若 c 是 ‘C’ ,則代表一個圓,本行後面跟著一個整數代表其半徑
 * 若 c 是 ‘T’ ,則代表一個三角形,本行後面跟著三個整數,代表三條邊的長度
 * Output:
 * 按面積從小到大依次輸出每個幾何形體的種類及面積。
 * 每行一個幾何形體,輸 出格式為:
 * 形體名稱:面積*/

class CShape{
    public:
    virtual double Area()=0;
    virtual void PrintInfo()=0;
};

class CRectangle:public CShape {
    int w,h;
    public:
    CRectangle(int x,int y):w(x),h(y){}
    double Area(){return w*h;}
    void PrintInfo(){cout<<"Rectangle:"<<Area()<<endl;}
};

class CCircle:public CShape{
    int r;
    public:
    CCircle(int x):r(x){}
    double Area(){return r*r*3.14;}
    void PrintInfo(){cout<<"Circle:"<<Area()<<endl;}
};

class CTriangle:public CShape{
    int a,b,c;
    public:
    CTriangle(int x,int y,int z):a(x),b(y),c(z){}
    double Area(){
        //海倫公式
        double p=double(a+b+c)/2;
        return sqrt(p*(p-a)*(p-b)*(p-c));
    }
    void PrintInfo(){cout<<"Triangle:"<<Area()<<endl;}
};

int MyCompare(const void* p1,const void* p2){
        CShape** pc1;
        CShape** pc2;
        pc1=(CShape**)p1;
        pc2=(CShape**)p2;
        double a1,a2;
        a1=(*pc1)->Area();
        a2=(*pc2)->Area();
        if(a1>a2) return 1;
        else if(a1<a2) return -1;
        else return 0; 
}

int main(){
    CShape* p[100];
    int n;
    int i;
    cin>>n;
    for(i=0;i<n;i++){
        char ch;
        cin>>ch;
        switch(ch){
            case 'R':{
                int w,h;
                cin>>w>>h;
                CRectangle* pr=new CRectangle(w,h);
                p[i]=pr;
                cout<<p[i]<<endl;
                break;}
            case 'C':{
                int r;
                cin>>r;
                CCircle* pc=new CCircle(r);
                p[i]=pc;
                cout<<p[i]<<endl;
                break;}
            case 'T':{
                int a,b,c;
                cin>>a>>b>>c;
                CTriangle* pt=new CTriangle(a,b,c);
                p[i]=pt;
                cout<<p[i]<<endl;
                break;}
            default:{break;}
        }
    p[0]->PrintInfo();
    }
    qsort(p,n,sizeof(CShape*),MyCompare);
    for(i=0;i<n;i++)
    {
        p[i]->PrintInfo();
    }
    return 0;
}

若將case里程序實現改為新建派生類物件,然後將指標指向該物件方式,會發現p[i]裡存放的地址相同,結果會出錯。

// wrong answer
int main(){
    CShape* p[100];
    int n;
    int i;
    cin>>n;
    for(i=0;i<n;i++){
        char ch;
        cin>>ch;
        switch(ch){
            case 'R':{
                int w,h;
                cin>>w>>h;
                //CRectangle* pr=new CRectangle(w,h);
                CRectangle CR(w,h);
                p[i]=&CR;
                cout<<p[i]<<endl;
                break;}
            case 'C':{
                int r;
                cin>>r;
                //CCircle* pc=new CCircle(r);
                CCircle CC(r);
                p[i]=&CC;
                cout<<p[i]<<endl;
                break;}
            case 'T':{
                int a,b,c;
                cin>>a>>b>>c;
                //CTriangle* pt=new CTriangle(a,b,c);
                CTriangle CT(a,b,c);
                p[i]=&CT;
        cout<<p[i]<<endl;
                break;}
            default:{break;}
        }
    p[0]->PrintInfo();
    }
    qsort(p,n,sizeof(CShape*),MyCompare);
    for(i=0;i<n;i++)
    {
        p[i]->PrintInfo();
    }
    return 0;
}

原因分析

新建的派生類物件為棧物件,case結束後會自動呼叫解構函式析構;
而new出來的為堆物件,需要delete析構。

知識點總結

C++記憶體分配方式

在C++中,記憶體分成5個區,分別是堆、棧、自由儲存區、全域性/靜態區和常量儲存區。


  • 存放函式引數以及區域性變數,在出作用域時,將自動被釋放。棧記憶體分配運算內置於處理器的指令集中,效率很高,但分配的記憶體容量有限。

  • new分配的記憶體塊(包括陣列、類例項等),需delete手動釋放。如果未釋放,在整個程式結束後,OS會幫你回收掉。
  • 自由儲存區
    malloc分配的記憶體塊,需free手動釋放。它和堆有些相似。
  • 全域性/靜態區
    全域性變數(global)和靜態變數(static)存於此處。在以前的C語言中,全域性變數又分為初始化的和未初始化的,C++中不區分。
  • 常量儲存區
    常量(const)存於此處,此儲存區不可修改。

棧與堆的區別

主要區別:

  • 管理方式不同
    棧是編譯器自動管理的,堆需手動釋放。
  • 空間大小不同
    在32位OS下,堆記憶體可達到4GB的的空間;而棧就小得可憐。VC6中棧預設大小是1M,當然可以對其進行修改。
  • 能否產生碎片不同
    對於棧來說,進棧/出棧都有著嚴格的順序(先進後出),不會產生碎片;而堆頻繁的new/delete,會造成記憶體空間的不連續,容易產生碎片。
  • 生長方向不同
    棧向下生長,以降序分配記憶體地址;堆向上生長,以升序分配內在地址。
  • 分配方式不同
    堆動態分配,無靜態分配;棧分為靜態分配和動態分配。
  • 分配效率不同
    棧是系統提供的資料結構,計算機會在底層對棧提供支援,進棧/出棧都有專門的指令,這就決定了棧的效率比較高;堆則不然,它由C/C++函式庫提供,機制複雜,堆的效率要比棧低得多。

可以看出,棧的效率要比堆高很多,所以,儘量用棧。不過,雖然棧有如此多的好處,但遠沒有堆使用靈活。

作用域和生存期

作用域,顧名思義,就是作用的區域,分為函式原型作用域,區域性作用域,類作用域和名稱空間作用域。它們的作用範圍按此順序變大。
生存期,顧名思義,就是生存的時間,分為靜態生存期和動態生存期。靜態生存期有兩種情況:1.名稱空間作用域中的變數具有靜態生存期;2.在區域性作用域中用static宣告的變數也具有靜態生存期。除了這兩種情況之外的變數都具有動態生存期。
在區域性作用域中宣告的變數如果用static修飾,則具有靜態生存期,注意,雖然在整個程式中這個變數都存在,但它的作用域還是原來的作用域,而且這個變數叫做靜態區域性變數,也就是說生存期和作用域沒有關係。