1. 程式人生 > 其它 >C++棧上和堆上建立物件的區別

C++棧上和堆上建立物件的區別

技術標籤:C/C++c++

在C++中類的物件建立分為兩種,一種是靜態建立,如A a;另一種是動態建立,如A* p=new A(),Ap=(A)malloc();靜態建立一個類物件,是由編譯器為物件在棧空間中分配記憶體,通過直接移動棧頂指標挪出適當的空間,然後在這片記憶體空間上呼叫建構函式形成一個棧物件。動態建立類物件,是使用new運算子將物件建立在堆空間中,在棧中只保留了指向該物件的指標。棧是由編譯器自動分配釋放 ,存放函式的引數值,區域性變數的值,物件的引用地址等。其操作方式類似於資料結構中的棧,通常都是被呼叫時處於儲存空間中,呼叫完畢立即釋放。堆中通常儲存程式執行時動態建立的物件,C++堆中存放的物件需要由程式設計師分配釋放,它存在程式執行的整個生命期,直到程式結束由OS釋放。而在java中通常類的物件都分配在堆中,物件的回收由虛擬機器的GC垃圾回收機制決定。

1.下面的程式來看看靜態建立和動態建立物件的區別

#include<iostream>
#include<string>
using namespace std;
class  student
{
public:
    string name;
    int age;
    void sayhello();
};
void student::sayhello()
{
    cout<<"my name is: "+this->name+" I am: "<<this->age;
    cout<<
"\n"; } student setname(string name) { student stu; stu.age=12; stu.name=name; return stu; } int main() { student stu=setname("jim"); stu.sayhello(); return 0; }

程式執行結果:my name is: jim I am: 12;

程式定義了一個student類,在setname函式中定義一個區域性物件作為返回值。程式第18行靜態構建了一個student物件stu,它在棧上分配空間,在函式呼叫結束後就銷燬了,函式返回的類對應在記憶體中的值應該不存在啊?其實原來C++在用類作為函式的返回值時呼叫了類的拷貝建構函式,而且該拷貝建構函式是在堆上分配儲存空間,後面再討論這個問題。

在setname函式內的stu在函式呼叫結束後就銷燬了,可以新增一個解構函式來證明:

在student類中加入解構函式:

student::~student()
{
    cout<<this->name<<":gameover"<<endl;
}

程式執行結果:
在這裡插入圖片描述
在sayhello()前,輸出jim:gameover,即為setname()裡的stu物件執行了解構函式。

如將setname函式改為:

student* setname(string name)
{
    student stu;
    stu.age=12;
    stu.name=name;
    return &stu;
}

main函式的呼叫改為:

int main()
{
    student* p=setname("tom");
    p->sayhello();
    return 0;
}

顯然這裡會出現問題,物件指標返回的是棧上的物件,在函式呼叫結束後已經銷燬了,物件指標即為野指標,故程式在編譯時會提示:warning C4172: returning address of local variable or temporary。解決這個問題我們自然想到把該物件構建在堆上即可。修改setname函式為下:

student* setname(string name)
 {
student* stu= new student();
stu->age=12;
stu->name=name;
return  stu;
 }

main函式的呼叫不變;程式正常執行輸出:
在這裡插入圖片描述
上面輸出結果並沒有呼叫解構函式,在setname呼叫後,在main函式結束後也沒有呼叫。在對上的物件需要程式設計師自己delete釋放,將main改為如下:

int main()
{
    student* p=setname("tom");
    p->sayhello();
    delete p;
    return 0;
}

即加入delete p;執行結果:
在這裡插入圖片描述

C中用malloc函式來動態申請空間,該記憶體分配在堆上。這裡可以驗證,加入#include <malloc.h>,將setname函式改為如下:

student* setname(string name)
 {
student* stu=(student*)malloc(sizeof(student));
stu->age=12;
stu->name=name;
return  stu;
 }

為在student中加入建構函式:

student::student()
{
    cout<<"constructor"<<endl;
}

上面的程式執行會出錯,原因是沒有呼叫建構函式,stu->name根本就沒有被初始化(string的建構函式沒有被呼叫),所以不能賦值 。具體的解釋是: 因為malloc只是分配堆記憶體(不會呼叫建構函式)它並不知道記憶體裡要存的是什麼。為此用new即可,將程式碼改為:student* stu=new student;程式執行結果為下:
在這裡插入圖片描述
即程式呼叫了建構函式。若非要用malloc來申請記憶體可以將setname函式改為如下:

student* setname(string name)
 {
student* stu=(student*)malloc(sizeof(student));
new(stu) student;
stu->age=12;
stu->name=name;
return  stu;
 }

即加入了new(stu) student程式正常執行,呼叫了建構函式。大概可以理解為new了一個student物件,賦值轉換為student的指標stu。
既然這樣可以把程式碼直接改為:student* stu;即

student* setname(string name)
 {
//student*stu= new student;
student* stu;
new(stu) student;
stu->age=12;
stu->name=name;
return  stu;
 }

編譯程式提示:warning C4700: local variable ‘stu’ used without having been initialized,即stu沒有初始化。 new(stu) student;和stu= new student;並不等價。new(stu) student並不是初始化,這裡可以看做這種寫法(之前還真沒見過)是C++為相容malloc記憶體申請的用法,一般情況下推薦肯定是用new關鍵字。到此其實這裡要說明的主題已經基本說明了,關於malloc的用法當申請的類的成員變數只包含基本的資料型別(數值型int,double等)(string等引用類除外)時是不會出錯的。下面的列子可以證明;

#include<iostream>
#include<string>
#include <malloc.h>
using namespace std;
class course
{
public:
    int id;
    float score;
    void printscore()
    {
    cout<<"id:"<<this->id;
        cout<<" score:"<<this->score<<endl;
    }
};

 course* setscore(int id,float score)
 {
     course* co= (course*)malloc(sizeof(course));
     co->id=id;
     co->score=score;
     return co;
 }
int main()
{
course* cou=setscore(999,188.9);
cou->printscore();
    return 0;
}

程式執行結果如下:
在這裡插入圖片描述
程式這樣的用法沒有問題,而之前的string類卻有問題。這樣看來,自定義型別作為類的成員時也應該會有問題,來看下面的程式碼:

#include<iostream>
#include<string>
#include <malloc.h>
using namespace std;
class course;
class  student
{
public:
    string  name;
    int age;
        course cou;
    void sayhello();
    ~student();
    student();
};
student::student()
{
    cout<<"constructor"<<endl;
}
student::~student()
{
    cout<<this->name<<":gameover"<<endl;
}
void student::sayhello()
{
    cout<<"my name is: "+this->name+" I am: "<<this->age;
    cout<<"\n";
}
class course
{
public:
    int id;
    float score;
    void printscore()
    {
    cout<<"id:"<<this->id;
        cout<<" score:"<<this->score<<endl;
    }
};
 course* setscore(int id,float score)
 {
     course* co= (course*)malloc(sizeof(course));
     co->id=id;
     co->score=score;
     return co;
 }
 student* setname_score(string name ,course* cou)
 {
student* stu= (student*)malloc(sizeof(student));
stu->age=12;
new(stu)student;
stu->cou.id=cou->id;
stu->cou.score=cou->score;
stu->name= name;
return  stu;
 }
int main()
{
course* cou=setscore(999,188.9);
student* stu=setname_score("jimm",cou);
stu->cou.printscore();
stu->sayhello();
return 0;
}

這段程式碼中把course類物件作為student的類成員。程式編譯出錯: error C2079: ‘cou’ uses undefined class ‘course’。把course類的定義放在前面則沒有錯即:

#include<iostream>
#include<string>
#include <malloc.h>
using namespace std;
class course
{
public:
    int id;
    float score;
    void printscore()
    {
    cout<<"id:"<<this->id;
        cout<<" score:"<<this->score<<endl;
    }
};
class  student
{
public:
    string  name;
    int age;
        course cou;
    void sayhello();
    ~student();
    student();
};
student::student()
{
    cout<<"constructor"<<endl;
}
student::~student()
{
    cout<<this->name<<":gameover"<<endl;
}
void student::sayhello()
{
    cout<<"my name is: "+this->name+" I am: "<<this->age;
    cout<<"\n";
}
course* setscore(int id,float score)
 {
     course* co= (course*)malloc(sizeof(course));
     co->id=id;
     co->score=score;
     return co;
 }
student* setname_score(string name ,course* cou)
 {
student* stu= (student*)malloc(sizeof(student));
stu->age=12;
new(stu)student;
stu->cou.id=cou->id;
stu->cou.score=cou->score;
stu->name= name;
return  stu;
 }
int main()
{
course* cou=setscore(999,188.9);
student* stu=setname_score("jimm",cou);
stu->cou.printscore();
stu->sayhello();
return 0;
}

執行結果為:
在這裡插入圖片描述
上面程式setname_score函式中若不用new(stu)student;這種寫法,則會出現未初始化的錯誤。這裡完全可以將類的成員改為指標的形式,在初始化時用new在堆上分配儲存。改寫的程式碼如下:

#include<iostream>
#include<string>
#include <malloc.h>
using namespace std;
class course;
class  student
{
public:
    string*  name;
    int age;
        course* cou;
    void sayhello();
    ~student();
    student();
};
student::student()
{
    cout<<"constructor"<<endl;
}
student::~student()
{
    cout<<this->name<<":gameover"<<endl;
}
void student::sayhello()
{
    cout<<"my name is: "+*(this->name)+" I am: "<<this->age;
    cout<<"\n";
}
class course
{
public:
    int id;
    float score;
    void printscore()
    {
    cout<<"id:"<<this->id;
        cout<<" score:"<<this->score<<endl;
    }
};
course* setscore(int id,float score)
{
     course* co= (course*)malloc(sizeof(course));
     co->id=id;
     co->score=score;
     return co;
}
student* setname_score(string name ,course* cou)
{
student* stu= (student*)malloc(sizeof(student));//student*stu=new student;也一樣,只是一個呼叫建構函式,一個不呼叫
stu->age=12;
stu->cou=new course();//這裡用new建立物件
stu->cou->id=cou->id;
stu->cou->score=cou->score;
stu->name=new string(name);//new
return  stu;
 }
int main()
{
course* cou=setscore(999,188.9);
student* stu=setname_score("jimm",cou);
stu->cou->printscore();
stu->sayhello();
return 0;

}

上面程式執行結果為:
在這裡插入圖片描述
綜上所述,C++中物件的建立可以在堆和棧上。分別為動態建立和動態建立的方式,構建堆上的物件時一般使用new關鍵字,而物件的指標在棧上。使用new在堆上構建的物件需要主動的delete銷燬。C++物件可以在堆或棧中,函式的傳參可以是物件(物件的拷貝),或是物件的指標。而在java中物件一般分配在堆上,物件的傳值只有值型別,即物件的引用(地址),這樣看來C++要靈活的多。關於c++陣列的記憶體分配還有這裡提到的拷貝建構函式,下次再討論啊。上面的程式在VC++6.0編寫通過。