C++:构造函数作用及用法
PS:寫在前面
?
就是構(gòu)造函數(shù)的作用可以這樣理解,如果沒有構(gòu)造函數(shù)就是類里邊只是聲明了成員變量,成員函數(shù),還有最后的對象,這樣你在對該對象進行初始化賦值時就比較麻煩就得先調(diào)用成員函數(shù)對成員變量賦值,成員變量進而作用到對象上,之后有了構(gòu)造函數(shù),在構(gòu)建構(gòu)造函數(shù)時直接可以帶參數(shù)對對象進行初始化,相當于省略了步驟,可以這樣簡單的理解。
PS:但是構(gòu)造函數(shù)遠遠不止只有賦值這一條作用(此處不要陷入誤區(qū)以為他就是給成員變量賦值的這一個作用,不是這樣的或者說不完全是這樣,給成員變量賦值只是構(gòu)造函數(shù)的作用之一,他還有其他別的作用比如說打開文件再比如說分配內(nèi)存,再比如說預先做一些計算,比如加減乘除之類的,所以沒有參數(shù)的構(gòu)造函數(shù)就不對成員變量進行賦值,他還可以在函數(shù)體內(nèi)執(zhí)行分配內(nèi)存或者打開文件操作還可以提前做一些計算,所以無參的構(gòu)造函數(shù)沒有參數(shù)也無所謂它可以進行別的操作啊,再說了沒有參數(shù)我也可以對成員變量賦值把它賦值為0嘛,這個時候就不需要參數(shù)我就是固定的寫死的就是要給他賦值為0,所以沒有參數(shù)的構(gòu)造函數(shù)照樣具有很巨大的意義。)
所以看完這個博客不要就記住了構(gòu)造函數(shù)的賦值作用,他還有其他很多的作用。
首先從本質(zhì)上理解構(gòu)造函數(shù):
在 C++ 程序中,變量在定義時可以初始化。如果不進行初始化,變量的初始值會是什么呢?對全局變量和局部變量來說,這個答案是不一樣的。
未初始化的全部變量
全局變量在程序裝入內(nèi)存時就已經(jīng)分配好了存儲空間,程序運行期間其地址不變。對于程序員沒有初始化的全局變量,程序啟動時自動將其全部初始化為 0(即變量的每個比特都是 0)。
在大多數(shù)情況下,這是一種穩(wěn)妥的做法。而且,將全局變量自動初始化為 0,是程序啟動時的一次性工作,不會花費多少時間,所以大多數(shù) C++ 編譯器生成的程序,未初始化的全局變量的初始值都是全 0。
未初始化的局部變量
對于局部變量,如果不進行初始化,那么它的初始值是隨機的。局部變量定義在函數(shù)內(nèi)部,其存儲空間是動態(tài)分配在棧中的。
函數(shù)被調(diào)用時,棧會分配一部分空間存放該函數(shù)中的局部變量(包括參數(shù)),這片新分配的存儲空間中原來的內(nèi)容是什么,局部變量的初始內(nèi)容也就是什么,因此局部變量的初始值是不可預測的。
函數(shù)調(diào)用結(jié)束后,局部變量占用的存儲空間就被回收,以便分配給下一次函數(shù)調(diào)用中涉及的局部變量。
為什么不將局部變量自動初始化為全 0 呢?因為一個函數(shù)的局部變量在內(nèi)存中的地址,在每次函數(shù)被調(diào)用時都可能不同,因此自動初始化的工作就不是一次性的,而是每次函數(shù)被調(diào)用時都耍做,這會帶來無謂的時間開銷。
當然,如果程序員在定義局部變量時將其初始化了,那么這個初始化的工作也是每次函數(shù)被調(diào)用時都要做的,但這是編程者要求做的,因而不會是無謂的。
對象的初始化
對象和基本類型的變量一樣,定義時也可以進行初始化。一個對象,其行為和內(nèi)部結(jié)構(gòu)可能比較復雜,如果不通過初始化為其某些成員變量賦予一個合理的值,使用時就會產(chǎn)生錯誤。例如,有些以指針為成員變量的類可能會要求其對象生成時,指針就已經(jīng)指向一片動態(tài)分配的存儲空間。
對象的初始化往往不只是對成員變量賦值這么簡單,也可能還要進行一些動態(tài)內(nèi)存分配、打開文件等復雜的操作,在這種情況下,就不可能用初始化基本類型變量的方法來對其初始化。
雖然可以為類設(shè)汁一個初始化函數(shù),對象定義后就立即調(diào)用它,但這樣做的話,初始化就不具有強制性,難保程序員在定義對象后不會忘記對其進行初始化。面向?qū)ο蟮某绦蛟O(shè)計語言傾向于對象一定要經(jīng)過初始化后,使用起來才比較安全。因此,引入了構(gòu)造函數(shù)(constructor)的概念,用于對對象進行自動初始化。
在C++中,有一種特殊的成員函數(shù),它的名字和類名相同,沒有返回值,不需要用戶顯式調(diào)用(用戶也不能調(diào)用),而是在創(chuàng)建對象時自動執(zhí)行。這種特殊的成員函數(shù)就是構(gòu)造函數(shù)(Constructor)。
在C++語言中,“構(gòu)造函數(shù)”就是一類特殊的成員函數(shù),其名字和類的名字一樣,并且不寫返回值類型(void 也不寫)。
構(gòu)造函數(shù)可以被重載,即一個類可以有多個構(gòu)造函數(shù)。
如果類的設(shè)計者沒有寫構(gòu)造函數(shù),那么編譯器會自動生成一個沒有參數(shù)的構(gòu)造函數(shù),雖然該無參構(gòu)造函數(shù)什么都不做。
無參構(gòu)造函數(shù),不論是編譯器自動生成的,還是程序員寫的,都稱為默認構(gòu)造函數(shù)(default constructor)。如果編寫了構(gòu)造函數(shù),那么編譯器就不會自動生成默認構(gòu)造閑數(shù)。
對象在生成時,一定會自動調(diào)用某個構(gòu)造函數(shù)進行初始化,對象一旦生成,就再也不會在其上執(zhí)行構(gòu)造函數(shù)。
初學者常因“構(gòu)造函數(shù)”這個名稱而認為構(gòu)造函數(shù)負責為對象分配內(nèi)存空間,其實并非如此。構(gòu)造函數(shù)執(zhí)行時,對象的內(nèi)存空間已經(jīng)分配好了,構(gòu)造函數(shù)的作用是初始化這片空間。
為類編寫構(gòu)造函數(shù)是好的習慣,能夠保證對象生成時總是有合理的值。例如,一個“雇員”對象的年齡不會是負的。
來看下面的程序片段:
//設(shè)計一個表示復數(shù)的類
class Complex{
private:double real, imag; //實部和虛部
public:void Set(double r, double i); //設(shè)置實部和虛部
};
上面這個 Complex 類代表復數(shù),沒有編寫構(gòu)造函數(shù),因此編譯器會為 Complex 類自動生成一個無參的構(gòu)造函數(shù)。
下面兩條定義或動態(tài)生成 Complex 對象的語句,都會導致該無參構(gòu)造函數(shù)被調(diào)用,以對 Complex 對象進行初始化。
Complex c; //類對象c用無參構(gòu)造函數(shù)初始化
Complex *p = new Complex; //類對象 *p 用無參構(gòu)造函數(shù)初始化
如果為 Complex 類編寫了構(gòu)造閑數(shù),如下所示:
class Complex
{
private:double real, imag;
public:Complex(double r, double i = 0); //聲明構(gòu)造函數(shù)//第二個參數(shù)的默認值為0
};
Complex::Complex(double r,double i)//定義構(gòu)造函數(shù)
{real = r;imag = i;
}
那么以下語句有的能夠編譯通過,有的則不行:
Complex cl; //錯,Complex 類沒有聲明無參數(shù)的構(gòu)造函數(shù)(默認構(gòu)造函數(shù))
Complex* pc = new Complex; //錯,Complex 類沒有默認構(gòu)造函數(shù)(因為已經(jīng)有了一個構(gòu)造函數(shù),編譯器就不會自動生成默認構(gòu)造函數(shù),于是 Complex 類就不存在默認構(gòu)造函數(shù))
Complex c2(2); //正確,相當于 Complex c2(2, 0)
Complex c3(2, 4), c4(3, 5); //正確
Complex* pc2 = new Complex(3, 4); //正確
C++ 規(guī)定,任何對象生成時都一定會調(diào)用構(gòu)造閑數(shù)進行初始化。第 1 行通過變量定義的方式生成了 c1 對象,第 2 行通過動態(tài)內(nèi)存分配生成了一個 Complex 對象,這兩條語句均沒有涉及任何關(guān)于構(gòu)造函數(shù)參數(shù)的信息,因此編譯器會認為這兩個對象應(yīng)該用默認構(gòu)造函數(shù)初始化。可是 Complex 類已經(jīng)有了一個構(gòu)造函數(shù),編譯器就不會自動生成默認構(gòu)造函數(shù),于是 Complex 類就不存在默認構(gòu)造函數(shù),所以上述兩條語句就無法完成對象的初始化,導致編譯時報錯。
構(gòu)造函數(shù)是可以重載的,即可以寫多個構(gòu)造函數(shù),它們的參數(shù)表不同。當編譯到能生成對象的語句時,編譯器會根據(jù)這條語句所提供的參數(shù)信息決定該調(diào)用哪個構(gòu)造函數(shù)。如果沒有提供參數(shù)信息,編譯器就認為應(yīng)該調(diào)用無參構(gòu)造函數(shù)。
下面是一個有多個構(gòu)造函數(shù)的 Complex 類的例子程序。
class Complex{
private:double real, imag;
public:Complex(double r);Complex(double r, double i);Complex(Complex cl, Complex c2);
};Complex::Complex(double r) //構(gòu)造函數(shù) 1
{real = r;imag = 0;
}
Complex :: Complex(double r, double i) //構(gòu)造數(shù) 2
{real = r;imag = i;
}
Complex :: Complex(Complex cl, Complex c2) //構(gòu)造函數(shù) 3
{real = cl.real + c2.real;imag = cl.imag + c2.imag;
}
int main()
{Complex cl(3), c2(1,2), c3(cl,c2), c4(7);return 0;
}
根據(jù)參數(shù)個數(shù)和類型要匹配的原則,c1、c2、c3、c4 分別用構(gòu)造函數(shù) 1、構(gòu)造函數(shù) 2、構(gòu)造函數(shù) 3 和構(gòu)造函數(shù) 1 進行初始化。初始化的結(jié)果是:c1.real = 3,c1.imag = 0 (不妨表示為 c1 = {3, 0}),c2 = {1, 2},c3 = {4, 2}, c4 = {7, 0}。
從上訴表明可以看出用構(gòu)造函數(shù)完成了對象c1、c2、c3、c4 的初始化。
下面從兩個類的定義方式來說明使用構(gòu)造函數(shù)來對類的對象進行初始化的便利性(對比于類中聲明定義的普通成員函數(shù))。
在沒有構(gòu)造函數(shù)時,我們在類中通過成員函數(shù) setname()、setage()、setscore() 分別為成員變量 name、age、score 賦值,這樣做雖然有效,但顯得有點麻煩。有了構(gòu)造函數(shù),我們就可以簡化這項工作,在創(chuàng)建對象的同時為成員變量賦值,請看下面的代碼:
第一個代碼(通過先調(diào)用對象的成員函數(shù)對成員變量進行初始化賦值):
#include <iostream>
using namespace std;//類的聲明
class Student{
//因為三個成員變量都是私有的,不能通過對象直接訪問,必須借助三個 public 屬性的成員函數(shù)來修改它們的值。即不能在外部直接對他們賦值做修改
private: //私有的char *m_name;int m_age;float m_score;public: //共有的void setname(char *name);void setage(int age);void setscore(float score);void show();
};//成員函數(shù)的定義
void Student::setname(char *name){m_name = name;
}
void Student::setage(int age){m_age = age;
}
void Student::setscore(float score){m_score = score;
}
void Student::show(){cout<<m_name<<"的年齡是"<<m_age<<",成績是"<<m_score<<endl;
}int main(){//在棧上創(chuàng)建對象Student stu;stu.setname("小明");stu.setage(15);stu.setscore(92.5f);stu.show();//在堆上創(chuàng)建對象Student *pstu = new Student;pstu -> setname("李華");pstu -> setage(16);pstu -> setscore(96);pstu -> show();return 0;
}
運行結(jié)果:
小明的年齡是15,成績是92.5
李華的年齡是16,成績是96
第二種改變上述代碼(使用構(gòu)造函數(shù)在創(chuàng)建對象的同時可以直接為成員變量賦值)
#include <iostream>
using namespace std;class Student{
private:char *m_name;int m_age;float m_score;
public://聲明構(gòu)造函數(shù)Student(char *name, int age, float score);//聲明普通成員函數(shù)void show();
};//定義構(gòu)造函數(shù)
Student::Student(char *name, int age, float score){m_name = name;m_age = age;m_score = score;
}
//定義普通成員函數(shù)
void Student::show(){cout<<m_name<<"的年齡是"<<m_age<<",成績是"<<m_score<<endl;
}int main(){//創(chuàng)建對象時向構(gòu)造函數(shù)傳參Student stu("小明", 15, 92.5f);stu.show();//創(chuàng)建對象時向構(gòu)造函數(shù)傳參Student *pstu = new Student("李華", 16, 96);pstu -> show();return 0;
}
運行結(jié)果:
小明的年齡是15,成績是92.5
李華的年齡是16,成績是96
該例在 Student 類中定義了一個構(gòu)造函數(shù)Student(char *, int, float),它的作用是給三個 private 屬性的成員變量賦值。要想調(diào)用該構(gòu)造函數(shù),就得在創(chuàng)建對象的同時傳遞實參,并且實參由( )包圍,和普通的函數(shù)調(diào)用非常類似。
在棧上創(chuàng)建對象時,實參位于對象名后面,例如Student stu("小明", 15, 92.5f);在堆上創(chuàng)建對象時,實參位于類名后面,例如new Student("李華", 16, 96)。
構(gòu)造函數(shù)必須是 public 屬性的,否則創(chuàng)建對象時無法調(diào)用。當然,設(shè)置為 private、protected 屬性也不會報錯,但是沒有意義。
構(gòu)造函數(shù)沒有返回值,因為沒有變量來接收返回值,即使有也毫無用處,這意味著:
- 不管是聲明還是定義,函數(shù)名前面都不能出現(xiàn)返回值類型,即使是 void 也不允許;
- 函數(shù)體中不能有 return 語句。
構(gòu)造函數(shù)的重載
和普通成員函數(shù)一樣,構(gòu)造函數(shù)是允許重載的。一個類可以有多個重載的構(gòu)造函數(shù),創(chuàng)建對象時根據(jù)傳遞的實參來判斷調(diào)用哪一個構(gòu)造函數(shù)。
構(gòu)造函數(shù)的調(diào)用是強制性的,一旦在類中定義了構(gòu)造函數(shù),那么創(chuàng)建對象時就一定要調(diào)用,不調(diào)用是錯誤的。如果有多個重載的構(gòu)造函數(shù),那么創(chuàng)建對象時提供的實參必須和其中的一個構(gòu)造函數(shù)匹配;反過來說,創(chuàng)建對象時只有一個構(gòu)造函數(shù)會被調(diào)用。
對示例1中的代碼,如果寫作Student stu或者new Student就是錯誤的,因為類中包含了構(gòu)造函數(shù),而創(chuàng)建對象時卻沒有調(diào)用。
更改示例1的代碼,再添加一個構(gòu)造函數(shù)(示例2):
#include <iostream>
using namespace std;class Student{
private:char *m_name;int m_age;float m_score;
public:Student();Student(char *name, int age, float score);void setname(char *name);void setage(int age);void setscore(float score);void show();
};Student::Student(){m_name = NULL;m_age = 0;m_score = 0.0;
}
Student::Student(char *name, int age, float score){m_name = name;m_age = age;m_score = score;
}
void Student::setname(char *name){m_name = name;
}
void Student::setage(int age){m_age = age;
}
void Student::setscore(float score){m_score = score;
}
void Student::show(){if(m_name == NULL || m_age <= 0){cout<<"成員變量還未初始化"<<endl;}else{cout<<m_name<<"的年齡是"<<m_age<<",成績是"<<m_score<<endl;}
}int main(){//調(diào)用構(gòu)造函數(shù) Student(char *, int, float)Student stu("小明", 15, 92.5f);stu.show();//調(diào)用構(gòu)造函數(shù) Student()Student *pstu = new Student();pstu -> show();pstu -> setname("李華");pstu -> setage(16);pstu -> setscore(96);pstu -> show();return 0;
}
運行結(jié)果:
小明的年齡是15,成績是92.5
成員變量還未初始化
李華的年齡是16,成績是96
構(gòu)造函數(shù)Student(char *, int, float)為各個成員變量賦值,構(gòu)造函數(shù)Student()將各個成員變量的值設(shè)置為空,它們是重載關(guān)系。根據(jù)Student()創(chuàng)建對象時不會賦予成員變量有效值,所以還要調(diào)用成員函數(shù) setname()、setage()、setscore() 來給它們重新賦值。
構(gòu)造函數(shù)在實際開發(fā)中會大量使用,它往往用來做一些初始化工作,例如對成員變量賦值、預先打開文件等。
默認構(gòu)造函數(shù)
如果用戶自己沒有定義構(gòu)造函數(shù),那么編譯器會自動生成一個默認的構(gòu)造函數(shù),只是這個構(gòu)造函數(shù)的函數(shù)體是空的,也沒有形參,也不執(zhí)行任何操作。比如上面的 Student 類,默認生成的構(gòu)造函數(shù)如下:
Student(){}
一個類必須有構(gòu)造函數(shù),要么用戶自己定義,要么編譯器自動生成。一旦用戶自己定義了構(gòu)造函數(shù),不管有幾個,也不管形參如何,編譯器都不再自動生成。在示例1中,Student 類已經(jīng)有了一個構(gòu)造函數(shù)Student(char *, int, float),也就是我們自己定義的,編譯器不會再額外添加構(gòu)造函數(shù)Student(),在示例2中我們才手動添加了該構(gòu)造函數(shù)。
實際上編譯器只有在必要的時候才會生成默認構(gòu)造函數(shù),而且它的函數(shù)體一般不為空。默認構(gòu)造函數(shù)的目的是幫助編譯器做初始化工作,而不是幫助程序員。這是C++的內(nèi)部實現(xiàn)機制,這里不再深究,初學者可以按照上面說的“一定有一個空函數(shù)體的默認構(gòu)造函數(shù)”來理解。
最后需要注意的一點是,調(diào)用沒有參數(shù)的構(gòu)造函數(shù)也可以省略括號。對于示例2的代碼,在棧上創(chuàng)建對象可以寫作Student stu()或Student stu,在堆上創(chuàng)建對象可以寫作Student *pstu = new Student()或Student *pstu = new Student,它們都會調(diào)用構(gòu)造函數(shù) Student()。
以前我們就是這樣做的,創(chuàng)建對象時都沒有寫括號,其實是調(diào)用了默認的構(gòu)造函數(shù)。
總結(jié)
以上是生活随笔為你收集整理的C++:构造函数作用及用法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算点云之间的平均距离,方差,标准差
- 下一篇: PCL:拟合平面直线和曲线以及空间曲线的