構造函數的種類:
-
默認構造函數
-
無參數構造函數
-
一般構造函數(重載構造函數)
-
複製(拷貝)構造函數
class Student
{
private:
int num;
int age;
string name;
public:
//當一個類沒有定義任何構造函數時,系統會自動生成默認的無參構造函數。
//系統合成的默認構造函數自己定義的默認構造函數(無參數構造函數)的區別以及爲什麼在自己提供了其他構造函數的同時再提供一個默認構造函數(無參數構造函數)也總是對的
//原因:合成的默認構造函數無法初始化內置和複合類型的成員,如指針和數組。即默認構造函數非我們所期望,應該由自己明確定義構造函數,而非編譯器完成。
// 一旦實現了的任何一種構造函數,系統就不會再自動生成這樣一個默認構造函數。
//無參數構造函數
Student()
{
num = 0;
age = 0;
name = "zero";
}
// 一般構造函數(也稱重載構造函數)
// 一般構造函數可以有各種參數形式,一個類可以有多個一般構造函數,前提是參數的個數或者類型不同(基於c++的重載函數原理)
// 創建對象時根據傳入的參數不同調用不同的構造函數
Student(int num_temp)
{
num= num_temp;
age= 20;
name = "zero";
}
Student(int num_temp, int age_temp, string name_temp)
{
num = num_temp;
age = age_temp;
name = name_temp;
}
//帶有默認參數的構造函數(與上面三個構造函數不能共存,切記,切記)
Student(int num_temp = 0, int age_temp = 0, string name_temp = "zero")
{
num = num_temp;
age = age_temp;
name = name_temp;
}
//複製構造函數(也稱爲拷貝構造函數)
//複製構造函數的形式爲X(X&) 函數名與類名必須一致,參數爲類對象本身的引用
//若沒有顯示的複製構造函數,則系統會默認創建一個複製構造函數,但當類中有指針成員時,由系統默認創建該複製構造函數會存在風險,具體原因看下文。
//複製構造函數參數爲類對象本身的引用,用於根據一個已存在的對象複製出一個新的該類的對象,一般在函數中會將已存在對象的數據成員的值複製一份到新創建的對象中
Student(const Student & s)
{
num = s.num;
age = s.age;
name = s.name;
}
virtual ~Student(){}
};
int main()
{
// 調用了無參構造函數
Student s1,s2;
// 調用一般構造函數(當沒有默認參數的構造函數)
Student s3(1);
Student s4(1,20,"張三");
//當有默認參數的構造函數,可傳遞任意個數(包括0)的參數去調用該構造函數。
// 調用拷貝構造函數( 有下面兩種調用方式)
//複製初始化
Student s5(s4);
//賦值初始化
Student s6 = s4;
}
深拷貝和淺拷貝
- 淺拷貝,指的是在對象複製時,只對對象中的數據成員進行簡單的賦值,默認拷貝構造函數執行的也是淺拷貝。大多情況下“淺拷貝”已經能很好地工作了,但是一旦對象存在了動態成員,那麼淺拷貝就會出問題了。即淺拷貝只拷貝指針
- 兩個指針指向同一塊內存,任何一方的變動都會影響到另一方。
- 兩個指針指向同一塊內存,被析構2次,即delete同一塊內存2次,造成程序崩潰。
- 深拷貝,相對於淺拷貝,不僅對象中的數據成員進行了賦值,同時還爲動態成員開闢了新的空間。
class Student
{
private:
int num;
int age;
string name;
int gradenumber;
int *grade
public:
Student(int num_temp, int age_temp, string name_temp, int gradenumber)
{
num = num_temp;
age = age_temp;
name = name_temp;
grade - new int[gradenumber];
}
//淺拷貝,默認拷貝構造函數是淺拷貝
Student(const Student & s)
{
num = s.num;
age = s.age;
name = s.name;
grade = s.grade;
}
//深拷貝
Student(const Student & s)
{
num = s.num;
age = s.age;
name = s.name;
grade - new int[s.gradenumber];
for(int i = 0; i < gradenumber; i++)
{
grade[i] = s.grade[i];
}
}
virtual ~Student()
{
delete []grade ;
grade = NULL;
}
};
int main()
{
Student s4(01,20,"張三",5);
Student s5(s4);
Student s6 = s4;
return 0;
}
在什麼情況下系統會調用拷貝構造函數:
- 一個對象以值傳遞的方式傳入函數體
- 一個對象以值傳遞的方式從函數返回
- 一個對象需要通過另外一個對象進行初始化。
class Student
{
private:
int num;
int age;
string name;
int gradenumber;
int *grade
public:
Student(int num_temp, int age_temp, string name_temp, int gradenumber)
{
num = num_temp;
age = age_temp;
name = name_temp;
grade - new int[gradenumber];
}
//淺拷貝,默認拷貝構造函數是淺拷貝
// Student(const Student & s)
// {
// num = s.num;
// age = s.age;
// name = s.name;
// grade = s.grade;
// }
//深拷貝
Student(const Student & s)
{
num = s.num;
age = s.age;
name = s.name;
grade - new int[s.gradenumber];
for(int i = 0; i < gradenumber; i++)
{
grade[i] = s.grade[i];
}
}
virtual ~Student()
{
delete []grade ;
grade = NULL;
}
};
//參數是對象,是值傳遞,會調用拷貝構造函數
int get_student(Student s) const
{
return s.num;
}
//返回值是對象類型,會調用拷貝構造函數。
Student get_student()
{
Student s(1, 20, "張三", 5);
return s;
}
int main()
{
//調用構造函數
Student s1(1, 20, "張三", 5);
//調用拷貝構造函數
Student s2 s2 (s1);
Student s3 s3 = s1;
//函數形參是類的對象,調用拷貝構造函數
int num = get_student(s1);
//函數返回值是類的對象,調用拷貝構造函數
Student s4 = get_student();
return 0;
}
結論:
- 對象不存在,且沒用別的對象來初始化,就是調用了構造函數;
- 對象不存在,且用別的對象來初始化,就是拷貝構造函數(上面說了三種用它的情況!)
- 對象存在,用別的對象來給它賦值,就是賦值函數。
爲什麼用成員初始化列表會快一些
- 定義:初始化列表是一種C++初始化列表,有初始化階段和計算階段兩個階段。初始化列表以冒號開頭,後跟一系列以逗號分隔的初始化字段。
- 初始化類的成員有兩種方式,一是使用初始化列表,二是在構造函數體內進行賦值操作。例如:
class Student
{
private:
int num;
int age;
public:
//構造函數初始化列表
Student():num(1),age(20)
{}
//構造函數內部賦值
Student(int num_temp, int age_temp)
{
num = num_temp;
age = age_temp;
}
};
- 用成員初始化列表會快一些的原因是:
- 對內置類型成員變量(指針,引用),兩者性能差別不大。
- 對非內置類型成員變量(類類型),構造函數體內進行會使用兩次構造函數,而成員初始化列表則只需要一次。
- 必須使用成員初始化列表的情況
- const成員或者引用類型的成員。因爲const對象或者引用類型只能初始化,不能賦值。
- 需要初始化的數據成員是對象的情況,並且這個對象只有含參數的構造函數,沒有無參數的構造函數。
- 派生類初始化基類的私有成員。構造函數只能在初始化列表中被顯示調用,不能在構造函數內部被顯示調用。
explicit關鍵字的作用
- 可以阻止不應該允許的經過轉化構造函數進行的隱式轉換的發生。
class Test1
{
public:
Test1(int n)
{
num=n;
}//普通構造函數
private:
int num;
};
class Test2
{
public:
explicit Test2(int n)
{
num=n;
}//explicit(顯式)構造函數
private:
int num;
};
int main()
{
Test1 t1=12;//隱式調用其構造函數,成功
Test2 t2=12;//編譯錯誤,不能隱式調用其構造函數
Test2 t2(12);//顯式調用成功
return 0;
}
- effective c++中說:被聲明爲explicit的構造函數通常比其non-explicit兄弟更受歡迎。因爲它們禁止編譯器執行非預期(往往也不被期望)的類型轉換。除非我有一個好理由允許構造函數被用於隱式類型轉換,否則我會把它聲明爲explicit。我鼓勵你遵循相同的政策。
本系列文章目的爲個人準備面試的簡單總結,文中多有不足,敬請批評指正!
參考:
https://blog.csdn.net/feitianxuxue/article/details/9275979
https://blog.csdn.net/zxc024000/article/details/51153743
https://blog.csdn.net/zzwdkxx/article/details/53409803
https://blog.csdn.net/sinat_20265495/article/details/53670644