C++ 笔记(17)— 类和对象(构造函数、析构函数、拷贝构造函数)
1. 構造函數
構造函數是一種特殊的函數(方法),在根據類創建對象時被調用。構造函數是一種隨著對象創建而自動被調用的函數,它的主要用途是為對象作初始化。
構造函數的名稱與類的名稱是完全相同的,并且不會返回任何類型,也不會返回 void。構造函數可用于為某些成員變量設置初始值。類似于 Python 類中的 __init__() 函數。
1.1 聲明和實現構造函數
通過下面示例理解構造函數,Human 類的構造函數的聲明類似于下面這樣:
class Human
{public:Human(); //構造函數聲明
};
這個構造函數可在類聲明中實現,也可在類聲明外實現。在類聲明中實現(定義)構造函數的代碼類似于下面這樣:
class Human
{public:Human(){// 代碼實現}
};
在類聲明外定義構造函數的代碼類似于下面這樣:
class Human
{public:Human(); //構造函數聲明
};Human::Human()
{// 代碼實現
}
注意: :: 被稱為作用域解析運算符。例如, Human::dateOfBirth 指的是在 Human 類中聲明的變量 dateOfBirth ,而 ::dateOfBirth 表示全局作用域中的變量 dateOfBirth 。
1.2 默認構造函數
#include <iostream>
using namespace std;class Line
{public:void setLength(double len);double getLength(void);Line(); // 這是構造函數private:double length;
};// 成員函數定義,包括構造函數
Line::Line(void)
{cout << "Object is being created" << endl;
}void Line::setLength(double len)
{length = len;
}double Line::getLength( void )
{return length;
}
// 程序的主函數
int main( )
{Line line;// 設置長度line.setLength(6.0); cout << "Length of line : " << line.getLength() <<endl;return 0;
}
輸出結果:
Object is being created
Length of line : 6
1.3 帶參數的構造函數
默認的構造函數沒有任何參數,但如果需要,構造函數也可以帶有參數。這樣在創建對象時就會給對象賦初始值,如下面的例子所示:
#include <iostream>
using namespace std;class Line
{public:void setLength(double len);double getLength(void);Line(double len); // 這是構造函數private:double length;
};// 成員函數定義,包括構造函數
Line::Line(double len)
{cout << "Object is being created, length = " << len << endl;length = len;
}void Line::setLength( double len )
{length = len;
}double Line::getLength( void )
{return length;
}
// 程序的主函數
int main( )
{Line line(10.0);// 獲取默認設置的長度cout << "Length of line : " << line.getLength() <<endl;// 再次設置長度line.setLength(6.0); cout << "Length of line : " << line.getLength() <<endl;return 0;
}
輸出結果:
Object is being created, length = 10
Length of line : 10
Length of line : 6
1.4 帶默認參數的構造函數
就像函數可以有帶默認值的參數一樣,構造函數也可以。可以對上面的代碼進行修改
...
Line::Line(double len=20.0)
{cout << "Object is being created, length = " << len << endl;length = len;
}...
int main( )
{Line line(10.0);Line line2;...
}
輸出結果:
Object is being created, length = 10
Object is being created, length = 20
Length of line : 10
Length of line : 6
1.5 使用初始化列表的構造函數
構造函數對初始化成員變化很有用,另一種初始化成員的方式是使用初始化列表。
初始化列表由包含在括號中的參數聲明后面的冒號 : 標識,冒號 : 后面列出了各個成員變量及其初始值。
Line::Line(double len): length(len)
{cout << "Object is being created, length = " << len << endl;
}
上面的語法等同于如下語法:
Line::Line(double len)
{length = len;cout << "Object is being created, length = " << len << endl;
}
假設有一個類 C,具有多個字段 X、Y、Z 等需要進行初始化,同理地,您可以使用上面的語法,只需要在不同的字段使用逗號進行分隔,如下所示:
C::C(double a, double b, double c): X(a), Y(b), Z(c)
{....
}
2. 析構函數
類的析構函數是類的一種特殊的成員函數,它會在每次刪除所創建的對象時執行。
析構函數的名稱與類的名稱是完全相同的,只是在前面加了個波浪號 ~ 作為前綴,它不會返回任何值,也不能帶有任何參數。析構函數有助于在跳出程序(比如關閉文件、釋放內存等)前釋放資源。
析構函數是一種隨著對象消亡而自動被調用的函數,它的主要用途是釋放動態申請的資源。它沒有返回類型,沒有參數,也沒有重載。析構函數的函數名也是指定的,是在構造函數名之前加一個 ~ 符號。
2.1 聲明和定義析構函數
析構函數的聲明類似于下面這樣:
class Line
{~Line(); // 析構函數聲明
};
這個析構函數可在類聲明中實現,也可在類聲明外實現。在類聲明中實現(定義)析構函數的代碼類似于下面這樣:
class Line
{public:~Line(){// code}
};
在類聲明外定義析構函數的代碼類似于下面這樣:
class Line
{public:~Line(); // 析構函數聲明
};// 析構函數實現
Line::~Line()
{// code
};
2.2 析構函數示例
示例代碼 1
#include <iostream>
using namespace std;class Line
{public:void setLength(double len);double getLength(void);Line(); // 這是構造函數聲明~Line(); // 這是析構函數聲明private:double length;
};// 成員函數定義,包括構造函數
Line::Line(void)
{cout << "Object is being created" << endl;
}
Line::~Line(void)
{cout << "Object is being deleted" << endl;
}void Line::setLength(double len)
{length = len;
}double Line::getLength(void)
{return length;
}// 程序的主函數
int main( )
{Line line;// 設置長度line.setLength(6.0); cout << "Length of line : " << line.getLength() <<endl;return 0;
}
輸出結果:
Object is being created
Length of line : 6
Object is being deleted
3. 拷貝構造函數
拷貝構造函數是一種特殊的構造函數,它在創建對象時,是使用同一類中之前創建的對象來初始化新創建的對象。拷貝構造函數通常用于:
- 一個對象需要通過另外一個對象進行初始化(使用另一個同類型的對象來初始化新創建的對象);
- 一個對象以值傳遞的方式傳入函數體;
- 一個對象以值傳遞的方式從函數返回;
?
- 如果在類中沒有定義拷貝構造函數,編譯器會自行定義一個;
- 如果類帶有指針變量,并有動態內存分配,則它必須有一個拷貝構造函數;
拷貝構造函數的最常見形式如下:
classname (const classname &obj) {// 拷貝構造函數的主體
}
在這里,**obj** 是一個對象引用,該對象是用于初始化另一個對象的。
?
3.1 一個對象需要通過另外一個對象進行初始化
直接看代碼
#include <iostream>
using namespace std;class Student
{public:string getID();Student(string i, string n, int a); // 簡單的構造函數Student(const Student &stu); // 拷貝構造函數~Student(); // 析構函數private:string id;string name;int age;
};Student::Student(string i="", string n="", int a=0)
{cout << "constructor func run" << endl;id = i;name = n;age = a;
}Student::Student(const Student &stu)
{cout << "copy constructor func run" << endl;id = stu.id;name = stu.name;age = stu.age;
}Student::~Student()
{cout << "destructor func run" << endl;
}string Student::getID()
{return id;
}int main()
{// 創建對象Student stu = Student("0001", "Jack", 18);// 使用另一個同類型的對象 stu 來初始化新創建的對象 stStudent st = stu;cout << "st id is " << st.getID() << endl; return 0;
}
輸出結果:
constructor func run
copy constructor func run
st id is 0001
destructor func run
destructor func run
3.2 一個對象以值傳遞的方式傳入函數體
直接看代碼
#include <iostream>
using namespace std;class Student
{public:string getID();Student(string i, string n, int a); // 簡單的構造函數Student(const Student &stu); // 拷貝構造函數~Student(); // 析構函數private:string id;string name;int age;
};Student::Student(string i="", string n="", int a=0)
{cout << "constructor func run" << endl;id = i;name = n;age = a;
}Student::Student(const Student &stu)
{cout << "copy constructor func run" << endl;id = stu.id;name = stu.name;age = stu.age;
}Student::~Student()
{cout << "destructor func run" << endl;
}string Student::getID()
{return id;
}void display(Student stu)
{cout << "st id is " << stu.getID() << endl;
}int main()
{// 創建對象Student stu = Student("0001", "Jack", 18);// 將對象 stu 以值傳遞的方式傳遞給函數 displaydisplay(stu);return 0;
}
輸出結果:
constructor func run
copy constructor func run
st id is 0001
destructor func run
destructor func run
?
3.3 一個對象以值傳遞的方式從函數返回
如果函數的返冋值是類 A 的對象,則函數返回時,類 A 的拷貝構造函數被調用。
#include <iostream>
using namespace std;class Student
{public:string getID();Student(string i, string n, int a); // 簡單的構造函數Student(const Student &stu); // 拷貝構造函數~Student(); // 析構函數private:string id;string name;int age;
};Student::Student(string i="", string n="", int a=0)
{cout << "constructor func run" << endl;id = i;name = n;age = a;
}Student::Student(const Student &stu)
{cout << "copy constructor func run" << endl;id = stu.id;name = stu.name;age = stu.age;
}Student::~Student()
{cout << "destructor func run" << endl;
}string Student::getID()
{return id;
}Student stu ("0001", "Jack", 18);Student retStudent()
{// 在函數內部創建對象時,由于編譯器優化原因,所以不會調用拷貝構造函數// Student stu ("0001", "Jack", 18);// Student stu = Student("0001", "Jack", 18);return stu;
}int main()
{retStudent();return 0;
}
輸出結果:
constructor func run
copy constructor func run
destructor func run
destructor func run
需要說明的是,有些編譯器出于程序執行效率的考慮,編譯的時候進行了優化,函數返回值對象就不用復制構造函數初始化了,這并不符合 C++的標準。
?
C++拷貝構造函數(復制構造函數)詳解
?
3.4 當類成員中含有指針類型成員且需要對其分配內存時,一定要重定義拷貝構造函數
默認的拷貝構造函數實現的只能是淺拷貝,即直接將原對象的數據成員值依次復制給新對象中對應的數據成員,并沒有為新對象另外分配內存資源。這樣,如果對象的數據成員是指針,兩個指針對象實際上指向的是同一塊內存空間。在某些情況下,淺拷貝會帶來數據安全方面的隱患。
?
當類的數據成員中有指針類型時,我們就必須定義一個特定的拷貝構造函數,該拷貝構造函數不僅可以實現原對象和新對象之間數據成員的拷貝,而且可以為新的對象分配單獨的內存資源,這就是深拷貝構造函數。
?
如何防止默認拷貝發生
聲明一個私有的拷貝構造函數,這樣因為拷貝構造函數是私有的,如果用戶試圖按值傳遞或函數返回該類的對象,編譯器會報告錯誤,從而可以避免按值傳遞或返回對象。
?
總結:
當出現類的等號賦值時,會調用拷貝函數,在未定義顯示拷貝構造函數的情況下,系統會調用默認的拷貝函數——即淺拷貝,它能夠完成成員的一一復制。
?
當數據成員中沒有指針時,淺拷貝是可行的。但當數據成員中有指針時,如果采用簡單的淺拷貝,則兩類中的兩個指針將指向同一個地址,當對象快結束時,會調用兩次析構函數,而導致指針懸掛現象。所以,這時,必須采用深拷貝。
?
深拷貝與淺拷貝的區別就在于深拷貝會在堆內存中另外申請空間來儲存數據,從而也就解決了指針懸掛的問題。簡而言之,當數據成員中有指針時,必須要用深拷貝。
?
類帶有指針變量,并有動態內存分配,則它必須有一個拷貝構造函數。
?
示例代碼:
#include <iostream>
using namespace std;class Student
{public:Student(string i, string n, int a); // 簡單的構造函數Student(const Student &stu); // 拷貝構造函數~Student(); // 析構函數string getID();private:string *id;string name;int age;
};Student::Student(string i="", string n="", int a=0)
{cout << "constructor func run" << endl;id = new string;*id = i;name = n;age = a;
}Student::Student(const Student &stu)
{cout << "copy constructor func run" << endl;id = new string;*id = *stu.id; // 拷貝值name = stu.name;age = stu.age;
}Student::~Student()
{cout << "destructor func run" << endl;delete id;
}string Student::getID()
{return *id;
}void display(Student stu)
{cout << "st id is " << stu.getID() << endl;
}int main()
{Student stu ("0001", "Jack", 18);display(stu);return 0;
}
輸出結果:
constructor func run
copy constructor func run
st id is 0001
destructor func run
destructor func run
總結
以上是生活随笔為你收集整理的C++ 笔记(17)— 类和对象(构造函数、析构函数、拷贝构造函数)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 工字开头的成语有哪些啊?
- 下一篇: qq个性签名酷炫霸气