本文是筆者在C++學習過程中,對類和對象的總結(jié)記錄,如有不對以及不足的地方,歡迎大家指正!
文中代碼學于黑馬。
1. 構(gòu)造函數(shù)與析構(gòu)函數(shù)
??在C++當中,提供了一種特殊的公有成員函數(shù):構(gòu)造函數(shù)和析構(gòu)函數(shù),用于對對象進行初始化和銷毀。
-
構(gòu)造函數(shù):主要用于在創(chuàng)建對象時為對象的成員屬性賦值,即給對象分配資源。
語法:類名(){}
構(gòu)造函數(shù)可以有參數(shù),也就是說,可以發(fā)生重載。 -
析構(gòu)函數(shù):主要用于在對象銷毀時釋放相應的資源。
語法:~類名(){}
析構(gòu)函數(shù)不可以有參數(shù),所以不能發(fā)生重載。
??一個類,可以有多個構(gòu)造函數(shù),但只能有一個析構(gòu)函數(shù),不同構(gòu)造函數(shù)之間通過參數(shù)的個數(shù)和類型來進行區(qū)分。
??對于C++來說,對象的初始化與銷毀是必須的,所以即使我們不提供構(gòu)造函數(shù)與析構(gòu)函數(shù),編譯器也會提供默認的構(gòu)造函數(shù)與析構(gòu)函數(shù),并在對象初始化和銷毀時由編譯器自動調(diào)用,不需要手動調(diào)用,且只會調(diào)用一次。編譯器提供的構(gòu)造函數(shù)與析構(gòu)函數(shù)是空實現(xiàn)。
#include <iostream>
#include <string>
using namespace std;
// 1. 構(gòu)造函數(shù)與析構(gòu)函數(shù)
class Person_1{
public:
// 構(gòu)造函數(shù)
Person_1(){
cout<<"Person_1 構(gòu)造函數(shù)"<<endl;
}
// 析構(gòu)函數(shù)
~Person_1(){
cout<<"Person_1 析構(gòu)函數(shù)"<<endl;
}
};
void test_1(){
// 棧區(qū)的數(shù)據(jù),會在函數(shù)調(diào)用結(jié)束后立即釋放
Person_1 p1;
}
int main() {
// 1. 構(gòu)造函數(shù)與析構(gòu)函數(shù)
test_1();
return 0;
}
運行結(jié)果:

2.構(gòu)造函數(shù)的分類與調(diào)用
2.1 分類
??構(gòu)造函數(shù)可以按參數(shù)來分類,也可以按類型來進行分類。
- 按參數(shù)
- 有參構(gòu)造
- 無參構(gòu)造(默認構(gòu)造)
- 按類型
- 普通構(gòu)造
-
拷貝構(gòu)造
在創(chuàng)建對象時,是通過同一個類的已創(chuàng)建的對象來對自己進行初始化,即對原對象進行復制,來初始化本對象。
語法示例:Object(const Object &o){...}
#include <iostream>
using namespace std;
//2.構(gòu)造函數(shù)的分類與調(diào)用
class Person_2{
public:
//無參構(gòu)造函數(shù)(默認)
Person_2(){
cout<<"Person_2 無參構(gòu)造函數(shù)(默認)"<<endl;
}
//有參構(gòu)造函數(shù)
Person_2(int age){
this->age=age;
cout<<"Person_2 有參構(gòu)造函數(shù)"<<endl;
}
//拷貝構(gòu)造函數(shù)
Person_2(const Person_2 &p){
age=p.age;
cout<<"Person_2 拷貝構(gòu)造函數(shù)"<<endl;
}
//析構(gòu)函數(shù)
~Person_2(){
cout<<"Person_2 析構(gòu)函數(shù)"<<endl;
}
private:
int age;
};
void test_2_1(){
Person_2 p1;
Person_2 p2(10);
Person_2 p3(p2);
}
int main() {
//2.構(gòu)造函數(shù)的分類與調(diào)用
test_2_1();
return 0;
}
運行結(jié)果:

2.2 調(diào)用
??構(gòu)造函數(shù)的調(diào)用方式,可以分為以下幾類:
-
括號法
??調(diào)用默認構(gòu)造(無參構(gòu)造)時,不可以加括號,否則編譯器會認為這是函數(shù)聲明。 -
顯示法
??匿名對象(只調(diào)用構(gòu)造方法而沒有給對象命名),會在當前行執(zhí)行結(jié)束后,立即被系統(tǒng)回收,即調(diào)用析構(gòu)函數(shù)。
??同時,不要用拷貝構(gòu)造函數(shù)來初始化匿名函數(shù),否則,在編譯器看來這是一個對象聲明:Object(o1); == Object o1; - 隱式轉(zhuǎn)換法
#include <iostream>
using namespace std;
//調(diào)用
void test_2_2(){
// 括號法
Person_2 p1(10);
// 調(diào)用默認構(gòu)造(無參構(gòu)造)時,不可以加括號,否則編譯器會認為這是函數(shù)聲明。
// Person_2 p2(); // Function 'p2' is never used
// 顯示法
Person_2 p2=Person_2(10); // 有參
Person_2 p3=Person_2(p2); // 拷貝
// Person_2(10) 單獨寫即為匿名對象,在當前行執(zhí)行結(jié)束后,立即被釋放(析構(gòu))
// 不要用拷貝構(gòu)造函數(shù)來初始化匿名函數(shù),否則,在編譯器看來這是一個對象聲明
// Person_2(p4); // 等價于 Person_2 p4
// 隱式轉(zhuǎn)換法
Person_2 p4=10; // 等價于 Person_2 p2=Person_2(10);
Person_2 p5=p4; // 等價于 Person_2 p3=Person_2(p4);
}
3. 拷貝函數(shù)的調(diào)用時機
??在C++中什么時候會調(diào)用拷貝構(gòu)造函數(shù)呢?有以下三種情況:
- 一個對象需要通過一個已經(jīng)創(chuàng)建的對象來初始化
- 一個對象以值傳遞的方式傳入函數(shù)體
- 一個對象以值傳遞的方式從函數(shù)返回
#include <iostream>
using namespace std;
class Person_2{
public:
//無參構(gòu)造函數(shù)(默認)
Person_2(){
cout<<"Person_2 無參構(gòu)造函數(shù)(默認)"<<endl;
}
//有參構(gòu)造函數(shù)
Person_2(int age){
this->age=age;
cout<<"Person_2 有參構(gòu)造函數(shù)"<<endl;
}
//拷貝構(gòu)造函數(shù)
Person_2(const Person_2 &p){
age=p.age;
cout<<"Person_2 拷貝構(gòu)造函數(shù)"<<endl;
}
//析構(gòu)函數(shù)
~Person_2(){
cout<<"Person_2 析構(gòu)函數(shù)"<<endl;
}
private:
int age;
};
// 3.拷貝函數(shù)的調(diào)用時機
// 3.1 一個對象需要通過一個已經(jīng)創(chuàng)建的對象來初始化
void test_3_1(){
Person_2 p(10); // 無參構(gòu)造函數(shù)
Person_2 p1(p); // 調(diào)用拷貝構(gòu)造函數(shù)
Person_2 p2=p; // 調(diào)用拷貝構(gòu)造函數(shù)
Person_2 p3;
p3=p; // 不調(diào)用拷貝構(gòu)造函數(shù),是賦值操作
}
// 3.2 一個對象以值傳遞的方式傳入函數(shù)體
void value(Person_2 p){}
void test_3_2(){
Person_2 p; // 無參構(gòu)造函數(shù)
value(p);
}
// 3.3 一個對象以值傳遞的方式從函數(shù)返回
Person_2 result(){
Person_2 p;
cout<<"result()函數(shù)內(nèi)變量p的地址為:\t\t"<<(int *)&p<<endl;
return p;
}
void test_3_3(){
Person_2 p=result();
cout<<"result()函數(shù)返回的變量p的地址為:\t"<<(int *)&p<<endl;
}
int main() {
// 3.拷貝函數(shù)的調(diào)用時機
// 3.1 一個對象需要通過一個已經(jīng)創(chuàng)建的對象來初始化
test_3_1();
cout<<"===================="<<endl;
// 3.2 一個對象以值傳遞的方式傳入函數(shù)體
test_3_2();
cout<<"===================="<<endl;
// 3.3 一個對象以值傳遞的方式從函數(shù)返回
test_3_3();
cout<<"===================="<<endl;
return 0;
}
運行結(jié)果:

4. 構(gòu)造函數(shù)的調(diào)用規(guī)則
??在默認的情況下,C++編譯器會至少給類添加以下三個函數(shù):
- 默認構(gòu)造函數(shù):無參,函數(shù)體為空
- 默認析構(gòu)函數(shù):無參,函數(shù)體為空
- 默認拷貝構(gòu)造函數(shù):對對象的屬性進行簡單的值拷貝
??而構(gòu)造函數(shù)的調(diào)用規(guī)則如下所示:
- 若用戶自定義了有參構(gòu)造函數(shù),那么C++就不會再提供默認無參構(gòu)造函數(shù),而僅提供默認拷貝構(gòu)造函數(shù)。
- 若用戶自定義了拷貝構(gòu)造函數(shù),那么C++就不會再提供其他的構(gòu)造函數(shù)。
5. 深拷貝與淺拷貝
??深拷貝與淺拷貝,一直是C++面試的一個熱點與難點,那么這兩者的區(qū)別是什么呢?
- 淺拷貝:在對象復制時,僅進行簡單的賦值拷貝
- 深拷貝:在對象復制時,會在堆區(qū)重新申請空間,再進行拷貝操作。
??在默認拷貝構(gòu)造函數(shù)中,進行的是淺拷貝,而淺拷貝會將新舊對象的指針型成員變量指向同一塊內(nèi)存。因此在一個對象被銷毀(調(diào)用析構(gòu)函數(shù))后,這一塊內(nèi)存就已經(jīng)被釋放了,所以當另一個對象調(diào)用析構(gòu)函數(shù)時,就會造成對同一內(nèi)存的重復釋放,從而引發(fā)錯誤。所以如果有在堆區(qū)開辟的屬性,就一定要自定義拷貝構(gòu)造函數(shù),通過深拷貝來進行拷貝。
#include <iostream>
using namespace std;
// 5. 深拷貝與淺拷貝
class Person_3{
public:
//無參構(gòu)造函數(shù)(默認)
Person_3(){
cout<<"Person_3 無參構(gòu)造函數(shù)(默認)"<<endl;
}
//有參構(gòu)造函數(shù)
Person_3(int age,int height){
this->age=age;
this->height=new int(height);
cout<<"Person_3 有參構(gòu)造函數(shù)"<<endl;
}
//拷貝構(gòu)造函數(shù)
Person_3(const Person_3 &p){
age=p.age;
//利用深拷貝來在堆區(qū)開辟空間,避免利用淺拷貝帶來的堆區(qū)重復釋放問題
height=new int(*p.height);
cout<<"Person_3 拷貝構(gòu)造函數(shù)"<<endl;
}
//析構(gòu)函數(shù)
~Person_3(){
//釋放在堆區(qū)開辟的空間
if(height!=NULL){
delete height;
}
cout<<"Person_3 析構(gòu)函數(shù)"<<endl;
}
void show(){
cout<<"age:"<<age<<",height:"<<*height<<endl;
cout<<"height變量的地址:"<<height<<endl;
}
private:
int age;
int *height;
};
void test_5(){
Person_3 p1(22,180);
Person_3 p2(p1);
cout<<"p1:"<<endl;
p1.show();
cout<<"p2:"<<endl;
p2.show();
}
int main() {
// 5. 深拷貝與淺拷貝
test_5();
return 0;
}
運行結(jié)果:

6. 初始化列表
??C++除了提供傳統(tǒng)的構(gòu)造函數(shù)之外,還提供了初始化列表語法,用來初始化屬性。
- 語法:構(gòu)造函數(shù)():屬性1(值1),屬性2(值2)...{...}
#include <iostream>
using namespace std;
// 6. 初始化列表
class Person_4{
public:
// 初始化列表方式初始化
Person_4(int a,int b,int c):A(a),B(b),C(c){}
void show(){
cout<<"A:"<<A<<",B:"<<B<<",C:"<<C<<endl;
}
private:
int A;
int B;
int C;
};
void test_6(){
Person_4 p(1,2,3);
p.show();
}
int main() {
// 6. 初始化列表
test_6();
return 0;
}
7. 類對象作為類成員
??在C++中,一個類的成員可以是另一個類的對象,我們稱這種成員為對象成員。
??eg:
class A{}
class B{
A a; // 對象成員
}
??帶有對象成員的類,在初始化時,先構(gòu)造對象成員,再構(gòu)造自己;在銷毀時,先析構(gòu)自己,再析構(gòu)對象成員,兩者順序相反。
#include <iostream>
using namespace std;
// 7. 類對象作為類成員
class Pat{
public:
Pat(string name){
this->name=name;
cout<<"Pat 有參構(gòu)造函數(shù)"<<endl;
}
~Pat(){
cout<<"Pat 析構(gòu)函數(shù)"<<endl;
}
private:
string name;
};
class Person{
public:
string name;
Pat pat;
// 初始化列表傳參
Person(string person,string patname):name(person), pat(patname){
cout<<"Person 有參構(gòu)造函數(shù)"<<endl;
}
~Person(){
cout<<"Person 析構(gòu)函數(shù)"<<endl;
}
};
void test_7(){
Person p("Alice","cat");
}
int main() {
// 7. 類對象作為類成員
test_7();
return 0;
}
運行結(jié)果:

8. 靜態(tài)成員
??類的成員變量與成員方法,在前面加上關(guān)鍵字static后。就被稱為靜態(tài)成員,所以靜態(tài)成員可以分為靜態(tài)成員變量與靜態(tài)成員函數(shù)。
??這兩者的訪問,都可以通過以下兩種方式達成:
- 通過對象:對象名.靜態(tài)成員變量名/靜態(tài)成員函數(shù)名
- 通過類名:類名::靜態(tài)成員變量名/靜態(tài)成員函數(shù)名
8.1 靜態(tài)成員變量
- 所有對象共享同一份數(shù)據(jù)
- 在編譯階段分配內(nèi)存
- 類內(nèi)聲明,類外初始化
#include <iostream>
using namespace std;
// 8. 靜態(tài)成員
// 靜態(tài)成員變量
class Person_5{
public:
static int sA;
private:
static int sB;
};
//類內(nèi)聲明,類外初始化
int Person_5::sA=10;
int Person_5::sB=20;
void test_8_1(){
Person_5 p1;
//1. 通過對象訪問
p1.sA=100;
cout<<"p1.sA="<<p1.sA<<endl;
Person_5 p2;
p2.sA=200; //所有對象共享同一份數(shù)據(jù)
cout<<"p1.sA="<<p1.sA<<endl;
cout<<"p2.sA="<<p2.sA<<endl;
//2. 通過類名訪問
cout<<"Person_5::sA="<<Person_5::sA<<endl;
}
int main() {
// 8. 靜態(tài)成員
// 靜態(tài)成員變量
test_8_1();
return 0;
}
運行結(jié)果:

8.2 靜態(tài)成員函數(shù)
- 所有對象共享同一個函數(shù)
- 靜態(tài)成員方法僅能訪問靜態(tài)成員變量。如若不然,某個對象在調(diào)用靜態(tài)成員方法時,不能確認方法中的普通變量屬于哪個對象。
#include <iostream>
using namespace std;
class Person_6{
public:
static int sA;
static void func(){
cout<<"static void func()"<<endl;
sA=200;
}
};
//類內(nèi)聲明,類外初始化
int Person_6::sA=10;
void test_8_2(){
Person_6 p;
//1. 通過對象訪問
p.func();
//2. 通過類名訪問
Person_6::func();
}
int main() {
// 靜態(tài)成員函數(shù)
test_8_2();
return 0;
}