C++中棧和堆上創建對象的區別

在C++中類的對象創建分爲兩種,一種是靜態創建,如A a;另外一種是動態創建,如A* p=new A(),A*p=(A*)malloc();靜態創建一個類對象,是由編譯器爲對象在棧空間中分配內存,經過直接移動棧頂指針挪出適當的空間,而後在這片內存空間上調用構造函數造成一個棧對象。動態創建類對象,是使用new運算符將對象創建在堆空間中,在棧中只保留了指向該對象的指針。棧是由編譯器自動分配釋放 ,存放函數的參數值,局部變量的值,對象的引用地址等。其操做方式相似於數據結構中的棧,一般都是被調用時處於存儲空間中,調用完畢當即釋放。堆中一般保存程序運行時動態建立的對象,C++堆中存放的對象須要由程序員分配釋放,它存在程序運行的整個生命期,直到程序結束由OS釋放。而在java中一般類的對象都分配在堆中,對象的回收由虛擬機的GC垃圾回收機制決定。java

1.下面的程序來看看靜態創建和動態創建對象的區別ios

 1 #include<iostream>
 2 #include<string>
 3 using namespace std;
 4 class  student
 5 {
 6 public:
 7     string name;
 8     int age;
 9     void sayhello();
10 };
11 void student::sayhello()
12 {
13     cout<<"my name is: "+this->name+" I am: "<<this->age;
14     cout<<"\n";
15 }
16 student setname(string name)
17 {
18     student stu;
19     stu.age=12;
20     stu.name=name;
21     return stu;
22 }
23 int main()
24 {
25     student stu=setname("jim");
26     stu.sayhello();
27     return 0;
28 }

程序運行結果:my name is: jim I am: 12;c++

程序定義了一個student類,在setname函數中定義一個局部對象做爲返回值。程序第18行靜態構建了一個student對象stu,它在棧上分配空間,在函數調用結束後就銷燬了,函數返回的類對應在內存中的值應該不存在啊?其實原來C++在用類做爲函數的返回值時調用了類的拷貝構造函數,並且該拷貝構造函數是在堆上分配存儲空間,後面再討論這個問題。程序員

在setname函數內的stu在函數調用結束後就銷燬了,能夠添加一個析構函數來證實:數組

在student類中加入析構函數:數據結構

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

程序運行結果:ide

在sayhello()前,輸出jim:gameover,即爲setname()裏的stu對象執行了析構函數。函數

如將setname函數改成:this

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

main函數的調用改成:spa

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函數改成以下:

1  student* setname(string name)
2  {
3 student* stu=(student*)malloc(sizeof(student));
4 stu->age=12;
5 stu->name=name;
6 return  stu;
7  }

爲在student中加入構造函數:

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

上面的程序執行到第5行會出錯,緣由是沒有調用構造函數,stu->name根本就沒有被初始化(string的構造函數沒有被調用),因此不能賦值 。具體的解釋是: 由於malloc只是分配堆內存(不會調用構造函數)它並不知道內存裏要存的是什麼。爲此用new便可,將第3行代碼改成:student* stu=new student;程序運行結果爲下:

即程序調用了構造函數。若非要用malloc來申請內存能夠將setname函數改成以下:

1  student* setname(string name)
2  {
3 student* stu=(student*)malloc(sizeof(student));
4 new(stu) student;
5 stu->age=12;
6 stu->name=name;
7 return  stu;
8  }

即加入了第4行程序正常運行,調用了構造函數。第4行大概能夠理解爲new了一個student對象,賦值轉換爲student的指針stu。
既然這樣能夠把第3行直接改成:student* stu;即

1  student* setname(string name)
2  {
3 //student*stu= new student;
4 student* stu;
5 new(stu) student;
6 stu->age=12;
7 stu->name=name;
8 return  stu;
9  }

讓說第4,5行的效果應該和第3行相同,編譯程序提示:warning C4700: local variable 'stu' used without having been initialized,即stu沒有初始化。 new(stu) student;和stu= new student;並不等價。第5行並非初始化,這裏能夠看作第5行這種寫法(以前還真沒見過)是C++爲兼容malloc內存申請的用法,通常狀況下推薦確定是用new關鍵字。
到此其實這裏要說明的主題已經基本說明了,關於malloc的用法當申請的類的成員變量只包含基本的數據類型(數值型int,double等)(string等引用類除外)時是不會出錯的。下面的列子能夠證實;

 1 #include<iostream>
 2 #include<string>
 3 #include <malloc.h>
 4 using namespace std;
 5 class course
 6 {
 7 public:
 8     int id;
 9     float score;
10     void printscore()
11     {
12     cout<<"id:"<<this->id;
13         cout<<" score:"<<this->score<<endl;
14     }
15 };
16 
17  course* setscore(int id,float score)
18  {
19      course* co= (course*)malloc(sizeof(course));
20      co->id=id;
21      co->score=score;
22      return co;
23  }
24 int main()
25 {
26 course* cou=setscore(999,188.9);
27 cou->printscore();
28     return 0;
29 }

程序運行結果以下:

 程序第19行這樣的用法沒有問題,而以前的string類卻有問題。這樣看來,自定義類型做爲類的成員時也應該會有問題,來看下面的代碼:

 1 #include<iostream>
 2 #include<string>
 3 #include <malloc.h>
 4 using namespace std;
 5 class course;
 6 class  student
 7 {
 8 public:
 9     string  name;
10     int age;
11         course cou;
12     void sayhello();
13     ~student();
14     student();
15 };
16 student::student()
17 {
18     cout<<"constructor"<<endl;
19 }
20 student::~student()
21 {
22     cout<<this->name<<":gameover"<<endl;
23 }
24 void student::sayhello()
25 {
26     cout<<"my name is: "+this->name+" I am: "<<this->age;
27     cout<<"\n";
28 }
29 class course
30 {
31 public:
32     int id;
33     float score;
34     void printscore()
35     {
36     cout<<"id:"<<this->id;
37         cout<<" score:"<<this->score<<endl;
38     }
39 };
40  course* setscore(int id,float score)
41  {
42      course* co= (course*)malloc(sizeof(course));
43      co->id=id;
44      co->score=score;
45      return co;
46  }
47  student* setname_score(string name ,course* cou)
48  {
49 student* stu= (student*)malloc(sizeof(student));
50 stu->age=12;
51 new(stu)student;
52 stu->cou.id=cou->id;
53 stu->cou.score=cou->score;
54 stu->name= name;
55 return  stu;
56  }
57 int main()
58 {
59 course* cou=setscore(999,188.9);
60 student* stu=setname_score("jimm",cou);
61 stu->cou.printscore();
62 stu->sayhello();
63 return 0;
64 }

這段代碼中把course類對象做爲student的類成員。程序編譯出錯: error C2079: 'cou' uses undefined class 'course'。把course類的定義放在前面則沒有錯即:

 1 #include<iostream>
 2 #include<string>
 3 #include <malloc.h>
 4 using namespace std;
 5 class course
 6 {
 7 public:
 8     int id;
 9     float score;
10     void printscore()
11     {
12     cout<<"id:"<<this->id;
13         cout<<" score:"<<this->score<<endl;
14     }
15 };
16 class  student
17 {
18 public:
19     string  name;
20     int age;
21         course cou;
22     void sayhello();
23     ~student();
24     student();
25 };
26 student::student()
27 {
28     cout<<"constructor"<<endl;
29 }
30 student::~student()
31 {
32     cout<<this->name<<":gameover"<<endl;
33 }
34 void student::sayhello()
35 {
36     cout<<"my name is: "+this->name+" I am: "<<this->age;
37     cout<<"\n";
38 }
39 course* setscore(int id,float score)
40  {
41      course* co= (course*)malloc(sizeof(course));
42      co->id=id;
43      co->score=score;
44      return co;
45  }
46 student* setname_score(string name ,course* cou)
47  {
48 student* stu= (student*)malloc(sizeof(student));
49 stu->age=12;
50 new(stu)student;
51 stu->cou.id=cou->id;
52 stu->cou.score=cou->score;
53 stu->name= name;
54 return  stu;
55  }
56 int main()
57 {
58 course* cou=setscore(999,188.9);
59 student* stu=setname_score("jimm",cou);
60 stu->cou.printscore();
61 stu->sayhello();
62 return 0;
63 }
View Code

運行結果爲:

上面程序setname_score函數中若不用new(stu)student;這種寫法,則會出現未初始化的錯誤。這裏徹底能夠將類的成員改成指針的形式,在初始化時用new在堆上分配存儲。改寫的代碼以下:

 1 #include<iostream>
 2 #include<string>
 3 #include <malloc.h>
 4 using namespace std;
 5 class course;
 6 class  student
 7 {
 8 public:
 9     string*  name;
10     int age;
11         course* cou;
12     void sayhello();
13     ~student();
14     student();
15 };
16 student::student()
17 {
18     cout<<"constructor"<<endl;
19 }
20 student::~student()
21 {
22     cout<<this->name<<":gameover"<<endl;
23 }
24 void student::sayhello()
25 {
26     cout<<"my name is: "+*(this->name)+" I am: "<<this->age;
27     cout<<"\n";
28 }
29 class course
30 {
31 public:
32     int id;
33     float score;
34     void printscore()
35     {
36     cout<<"id:"<<this->id;
37         cout<<" score:"<<this->score<<endl;
38     }
39 };
40 course* setscore(int id,float score)
41 {
42      course* co= (course*)malloc(sizeof(course));
43      co->id=id;
44      co->score=score;
45      return co;
46 }
47 student* setname_score(string name ,course* cou)
48 {
49 student* stu= (student*)malloc(sizeof(student));//student*stu=new student;也同樣,只是一個調用構造函數,一個不調用
50 stu->age=12;
51 stu->cou=new course();//這裏用new創建對象
52 stu->cou->id=cou->id;
53 stu->cou->score=cou->score;
54 stu->name=new string(name);//new
55 return  stu;
56  }
57 int main()
58 {
59 course* cou=setscore(999,188.9);
60 student* stu=setname_score("jimm",cou);
61 stu->cou->printscore();
62 stu->sayhello();
63 return 0;
64 
65 }
View Code

上面程序運行結果爲:

綜上所述,C++中對象的創建能夠在堆和棧上。分別爲動態創建和動態創建的方式,構建堆上的對象時通常使用new關鍵字,而對象的指針在棧上。使用new在堆上構建的對象須要主動的delete銷燬。C++對象能夠在堆或棧中,函數的傳參能夠是對象(對象的拷貝),或是對象的指針。而在java中對象通常分配在堆上,對象的傳值只有值類型,即對象的引用(地址),這樣看來C++要靈活的多。關於c++數組的內存分配還有這裏提到的拷貝構造函數,下次再討論啊。上面的程序在VC++6.0編寫經過。

相關文章
相關標籤/搜索