請注重,這一節內容是c++的重點,要非凡注重!
我們先說一下什麼是構造函數?
<!-- frame contents -->
<!-- /frame contents -->
上一個教程我們簡單說了關於類的一些基本內容,對於類對象成員的初始化我們始終是建立成員函數然後手工調用該函數對成員進行賦值的,那麼在c++中對於類來說有沒有更方便的方式能夠在對象創建的時候就自動初始化成員變量呢,這一點對操作保護成員是至關重要的,答案是肯定的關於c++類成員的初始化,有專門的構造函數來進行自動操作而無需要手工調用,在正式講解之前先看看c++對構造函數的一個基本定義。
1.C++規定,每個類必須有默認的構造函數,沒有構造函數就不能創建對象。
2.若沒有提供任何構造函數,那麼c++提供自動提供一個默認的構造函數,該默認構造函數是一個沒有參數的構造函數,它僅僅負責創建對象而不做任何賦值操作。
3.只要類中提供了任意一個構造函數,那麼c++就不在自動提供默認構造函數。
4.類對象的定義和變量的定義類似,使用默認構造函數創建對象的時候,假如創建的是靜態或者是全局對象,則對象的位模式全部為0,否則將會是隨即的。
我們來看下面的代碼:
//程序作者:管寧
//站點:www.cndev-lab.com
//所有稿件均有版權,如要轉載,請務必聞名出處和作者
#include <iostream>
using namespace std;
class Student
{
public:
Student()//無參數構造函數
{
number = 1;
score = 100;
}
void show();
protected:
int number;
int score;
};
void Student::show()
{
cout<<number<<endl<<score<<endl;
}
void main()
{
Student a;
a.show();
cin.get();
}
在類中的定義的和類名相同,並且沒有任何返回類型的Student()就是構造函數,這是一個無參數的構造函數,他在對象創建的時候自動調用,假如去掉Student()函數體內的代碼那麼它和c++的默認提供的構造函數等價的。
更多內容請看C/C++技術學堂專題,或
構造函數可以帶任意多個的形式參數,這一點和普通函數的特性是一樣的!
下面我們來看一個帶參數的構造函數是如何進行對象的始化操作的。
代碼如下:
<!-- frame contents -->
<!-- /frame contents -->
//程序作者:管寧
//站點:www.cndev-lab.com
//所有稿件均有版權,如要轉載,請務必聞名出處和作者
#include <iostream>
using namespace std;
class Teacher
{
public:
Teacher(char *input_name)//有參數的構造函數
{
name=new char[10];
//name=input_name;//這樣賦值是錯誤的
strcpy(name,input_name);
}
void show();
protected:
char *name;
};
void Teacher::show()
{
cout<<name<<endl;
}
void main()
{
//Teacher a;//這裡是錯誤的,因為沒有無參數的構造函數
Teacher a("test");
a.show();
cin.get();
}
我們創建了一個帶有字符指針的帶有形參的Teacher(char *input_name)的構造函數,調用它創建對象的使用類名加對象名稱加擴號和擴號內參數的方式調用,這和調用函數有點類似,但意義也有所不同,因為構造函數是為創建對象而設立的,這裡的意義不單純是調用函數,而是創建一個類對象。
一旦類中有了一個帶參數的構造函數而又沒無參數構造函數的時候系統將無法創建不帶參數的對象,所以上面的代碼
Teacher a;
就是錯誤的!!!
這裡還有一處也要注重:
//name=input_name;//這樣賦值是錯誤的
因為name指是指向內存堆區的,假如使用name=input_name;會造成指針指向改變不是指向堆區而是指向棧區,導致在後面調用析構函數delete釋放堆空間出錯!(析構函數的內容我們後面將要介紹)
假如需要調用能夠執行就需要再添加一個沒有參數的構造函數
對上面的代碼改造如下:
//程序作者:管寧
//站點:www.cndev-lab.com
//所有稿件均有版權,如要轉載,請務必聞名出處和作者
#include <iostream>
using namespace std;
class Teacher
{
public:
Teacher(char *input_name)
{
name=new char[10];
//name=input_name;//這樣賦值是錯誤的
strcpy(name,input_name);
}
Teacher()//無參數構造函數,進行函數重載
{
}
void show();
protected:
char *name;
};
void Teacher::show()
{
cout<<name<<endl;
}
void main()
{
Teacher test;
Teacher a("test");
a.show();
cin.get();
}
創建一個無闡述的同名的Teacher()無參數函數,一重載方式區分調用,由於構造函數和普通函數一樣具有重載特性所以編寫程序的人可以給一個類添加任意多個構造函數,來使用不同的參數來進行初始話對象!
更多內容請看C/C++技術學堂專題,或
現在我們來說一下,一個類對象是另外一類的數據成員的情況,假如有點覺得饒人那麼可以簡單理解成:類成員的定義可以相互嵌套定義,一個類的成員可以用另一個類進行定義聲明。
<!-- frame contents -->
<!-- /frame contents -->
c++規定假如一個類對象是另外一類的數據成員,那麼在創建對象的時候系統將自動調用那個類的構造函數。
下面我們看一個例子。
代碼如下:
//程序作者:管寧
//站點:www.cndev-lab.com
//所有稿件均有版權,如要轉載,請務必聞名出處和作者
#include <iostream>
using namespace std;
class Teacher
{
public:
Teacher()
{
Director = new char[10];
strcpy(director,"王大力");
}
char *show();
protected:
char *director;
};
char *Teacher::show()
{
return director;
}
class Student
{
public:
Student()
{
number = 1;
score = 100;
}
void show();
protected:
int number;
int score;
Teacher teacher;//這個類的成員teacher是用Teacher類進行創建並初始化的
};
void Student::show()
{
cout<<teacher.show()<<endl<<number<<endl<<score<<endl;
}
void main()
{
Student a;
a.show();
Student b[5];
for(int i=0; i<sizeof(b)/sizeof(Student); i++)
{
b[i].show();
}
cin.get();
}
上面代碼中的Student類成員中teacher成員是的定義是用類Teacher進行定義創建的,那麼系統碰到創建代碼的時候就會自動調用Teacher類中的Teacher()構造函數對對象進行初始化工作!
這個例子說明類的分工很明確,只有碰到自己的對象的創建的時候才自己調用自己的構造函數!
更多內容請看C/C++技術學堂專題,或
一個類可能需要在構造函數內動態分配資源,那麼這些動態開辟的資源就需要在對象不復存在之前被銷毀掉,那麼c++類的析構函數就提供了這個方便。
<!-- frame contents -->
<!-- /frame contents -->
析構函數的定義:析構函數也是非凡的類成員函數,它沒有返回類型,沒有參數,不能隨意調用,也沒有重載,只有在類對象的生命期結束的時候,由系統自動調用。
析構函數與構造函數最主要大不同就是在於調用期不同,構造函數可以有參數可以重載!
我們前面例子中的Teacher類中就使用new操作符進行了動態堆內存的開辟,由於上面的代碼缺少析構函數,所以在程序結束後,動態開辟的內存空間並沒有隨著程序的結束而小時,假如沒有析構函數在程序結束的時候逐一清除被占用的動態堆空間那麼就會造成內存洩露,使系統內存不斷減少系統效率將大大降低!
那麼我們將如何編寫類的析構函數呢?
析構函數可以的特性是在程序結束的時候逐一調用,那麼正好與構造函數的情況是相反,屬於互逆特性,所以定義析構函數因使用"~"符號(邏輯非運算符),表示它為膩構造函數,加上類名稱來定義。
看如下代碼:
//程序作者:管寧
//站點:www.cndev-lab.com
//所有稿件均有版權,如要轉載,請務必聞名出處和作者
#include <iostream>
#include <string>
using namespace std;
class Teacher
{
public:
Teacher()
{
director = new char[10];
strcpy(director,"王大力");
//director = new string;
// *director="王大力";//string情況賦值
}
~Teacher()
{
cout<<"釋放堆區director內存空間1次";
delete[] director;
cin.get();
}
char *show();
protected:
char *director;
//string *director;
};
char *Teacher::show()
{
return director;
}
class Student
{
public:
Student()
{
number = 1;
score = 100;
}
void show();
protected:
int number;
int score;
Teacher teacher;
};
void Student::show()
{
cout<<teacher.show()<<endl<<number<<endl<<score<<endl;
}
void main()
{
Student a;
a.show();
Student b[5];
for(int i=0; i<sizeof(b)/sizeof(Student); i++)
{
b[i].show();
}
cin.get();
}
上面的代碼中我們為Teacher類添加了一個名為~Teacher()的析構函數用於清空堆內存。
建議大家編譯運行代碼觀察調用情況,程序將在結束前也就是對象生命周期結束的時候自動調用~Teacher()
~Teache()中的delete[] director;就是清除堆內存的代碼,這與我們前面一開始提到的。
name=input_name;//這樣賦值是錯誤的
有直接的關系,因為delete操作符只能清空堆空間而不能清楚桟空間,假如強行清除棧空間內存的話將導致程序崩潰!!
更多內容請看C/C++技術學堂專題,或
前面我們已經簡單的說了類的構造函數和析構函數,我們知道一個類的成員可以是另外一個類的對象,構造函數答應帶參數,那麼我們可能會想到上面的程序我們可以在類中把Student類中的teacher成員用帶參數的形式調用Student類的構造函數,不必要再在Teacher類中進行操作,
<!-- frame contents -->
<!-- /frame contents -->
由於這一點構想我們把程序修改成如下形式:
//程序作者:管寧
//站點:www.cndev-lab.com
//所有稿件均有版權,如要轉載,請務必聞名出處和作者
#include <iostream>
#include <string>
using namespace std;
class Teacher
{
public:
Teacher(char *temp)
{
director = new char[10];
strcpy(director,temp);
}
~Teacher()
{
cout<<"釋放堆區director內存空間1次";
delete[] director;
cin.get();
}
char *show();
protected:
char *director;
};
char *Teacher::show()
{
return director;
}
class Student
{
public:
Student()
{
number = 1;
score = 100;
}
void show();
protected:
int number;
int score;
Teacher teacher("王大力");//錯誤,一個類的成員假如是另外一個類的對象的話,不能在類中使用帶參數的構造函數進行初始化
};
void Student::show()
{
cout<<teacher.show()<<endl<<number<<endl<<score<<endl;
}
void main()
{
Student a;
a.show();
Student b[5];
for(int i=0; i<sizeof(b)/sizeof(Student); i++)
{
b[i].show();
}
cin.get();
}
可是很遺憾,程序不能夠被編譯成功,為什麼呢?
因為:類是一個抽象的概念,並不是一個實體,並不能包含屬性值(這裡來說也就是構造函數的參數了),只有對象才占有一定的內存空間,含有明確的屬性值!
這一個問題是類成員初始化比較尴尬的一個問題,是不是就沒有辦法解決了呢?呵呵。。。。。。
c++為了解決此問題,有一個很獨特的方法,下一小節我們將介紹。
更多內容請看C/C++技術學堂專題,或
對於上面的那個"尴尬"問題,我們可以在構造函數頭的後面加上:號並指定調用哪那個類成員的構造函數來解決!
<!-- frame contents -->
<!-- /frame contents -->
教程寫到這裡的時候對比了很多書籍,發現幾乎所有的書都把這一章節叫做構造類成員,筆者在此覺得有所不妥,因為從讀音上輕易混淆概念,所以把這一小節的名稱改為構造類的成員比較合適!
代碼如下:
//程序作者:管寧
//站點:www.cndev-lab.com
//所有稿件均有版權,如要轉載,請務必聞名出處和作者
#include <iostream>
using namespace std;
class Teacher
{
public:
Teacher(char *temp)
{
director = new char[10];
strcpy(director,temp);
}
~Teacher()
{
cout<<"釋放堆區director內存空間1次";
delete[] director;
cin.get();
}
char *show();
protected:
char *director;
};
char *Teacher::show()
{
return director;
}
class Student
{
public:
Student(char *temp):teacher(temp)
{
number = 1;
score = 100;
}
void show();
protected:
int number;
int score;
Teacher teacher;
};
void Student::show()
{
cout<<teacher.show()<<endl<<number<<endl<<score<<endl;
}
void main()
{
Student a("王大力");
a.show();
//Student b[5]("王大力"); //這裡這麼用是不對的,數組不能夠使用帶參數的構造函數,以後我們將具體介紹vector類型
// for(int i=0; i<sizeof(b)/sizeof(Student); i++)
//{
// b[i].show();
/