?
每文一詩? 💪🏼
? ? ? ?我本將心向明月,奈何明月照溝渠? —— 元/高明《琵琶記》
? ? ? ? 譯文:我本是以真誠的心來對待你,就像明月一樣純潔無瑕;然而,你卻像溝渠里的污水一樣,對這份心意無動于衷,甚至于不屑一顧。
如果本文對你有所幫助,那能否支持一下老弟呢,嘻嘻🥰
??個人主頁 點擊??
封裝
封裝作為C++面向對象的三大特性之一
封裝將數據和操作數據的代碼組合成一個獨立的單元,也就是類。類的成員可以有不同的訪問權限,如公有(public)、私有(private)和受保護(protected),以此來控制外部對這些成員的訪問。
類的結構
class 類名{ 訪問權限: 屬性 / 行為 };
- class類名:指的是類的名稱 這個名稱可以隨意命名例如class hunman
- 訪問權限指的是:如公有(public)、私有(private)和受保護(protected)
- 屬性和行為是指的在這個類當中所定義的變量和方法。
?例如這里定義了一個人類
#include <iostream>
class hunman
{
private:std::string name;
public:hunman();~hunman();
protected:std::string ID_card;
};
struct和class區別
struct默認是公共權限,class默認是私有權限
也就是說在sttruct中定義的變量和方法,可以在外部用該對象直接訪問;
而對于Class中定義的變量和方法,如果不指定其權限,默認是無法在外部通過其對象訪問的。
構造函數和析構函數
????????在C++中,構造函數:是指在這個類當中進行對變量的初始化操作,主要作用在于創建對象時為對象的成員屬性賦值,構造函數由編譯器自動調用,無須手動調用。即在該類的對象被實例化后,構造函數會被立即調用。
????????在C++中,析構函數:主要作用在于對象銷毀前系統自動調用,執行一些清理工作。例如,當程序中有使用動態內存,即使用new操作符,那么可以在析構函數中進行delete,即內存釋放。
構造函數的分類
構造函數的語法是:類名(){}?? 名稱和類名相同,可以重載。
- 無參構造函數:human(){}
- 有參構造函數:human(std::string name){}
- 拷貝構造函數:human(const human& h){}
解釋:
- 無參構造函數:是指這個類當中的構造函數不傳入任何參數
- 有參構造函數:是指這個類當中的構造函數傳入參數,通常時將傳入的參數賦值給類當中的成員變量
- 拷貝構造函數:傳入的參數是和個類的對象,為什么要傳入const +引用的形式呢?,是因為我們不想傳入的對象被修改,并且以引用的方式傳遞,這個可以避免被傳入的對象占用的內存非常大時,對其的拷貝,使用引用本質上是在使用指針,避免內存的拷貝,提高程序的效率。
構造函數調用規則
- 括號法
- 顯示法
- 隱式轉換法
?形式1:類名 變量名? hunman p? 調用函數:無參構造函數,析構函數
代碼
#include <iostream>
class hunman
{
private:std::string name;
public://無參構造函數hunman(){std::cout<<"無參構造函數"<<std::endl;}//有參構造函數hunman(float heightval){height = heightval;std::cout<<"有參構造函數"<<std::endl;}//拷貝構造函數hunman(const hunman& h){height = h.height;std::cout<<"拷貝構造函數"<<std::endl;}//析構函數~hunman(){std::cout<<"析構函數"<<std::endl; }float height;
protected:std::string ID_card;
};int main(int argc, char const *argv[])
{hunman h;/* code */return 0;
}
?輸出
??形式2:類名() 或者 類名 變量名() hunman(1.80) 或者hunman h(1.80);調用函數:有參構造函數,析構函數
代碼
#include <iostream>
class hunman
{
private:std::string name;
public://無參構造函數hunman(){std::cout<<"無參構造函數"<<std::endl;}//有參構造函數hunman(float heightval){height = heightval;std::cout<<"有參構造函數"<<std::endl;}//拷貝構造函數hunman(const hunman& h){height = h.height;std::cout<<"拷貝構造函數"<<std::endl;}//析構函數~hunman(){std::cout<<"析構函數"<<std::endl; }float height;
protected:std::string ID_card;
};int main(int argc, char const *argv[])
{// hunman h;hunman(1.80);/* code */return 0;
}
輸出
??形式2:類名 變量名 = 類名(參數)? hunman h1 = hunman(1.80);? 調用函數:有參構造函數,析構函數
代碼
#include <iostream>
class hunman
{
private:std::string name;
public://無參構造函數hunman(){std::cout<<"無參構造函數"<<std::endl;}//有參構造函數hunman(float heightval){height = heightval;std::cout<<"有參構造函數"<<std::endl;}//拷貝構造函數hunman(const hunman& h){height = h.height;std::cout<<"拷貝構造函數"<<std::endl;}//析構函數~hunman(){std::cout<<"析構函數"<<std::endl; }float height;
protected:std::string ID_card;
};int main(int argc, char const *argv[])
{// hunman h;// hunman(1.80);hunman h1 = hunman(1.80);// hunman h2 = hunman(h1);/* code */return 0;
}
輸出
??形式2:類名 變量名 = 參數 hunman h1 = 1.80;調用函數:有參構造函數,析構函數
代碼
#include <iostream>
class hunman
{
private:std::string name;
public://無參構造函數hunman(){std::cout<<"無參構造函數"<<std::endl;}//有參構造函數hunman(float heightval){height = heightval;std::cout<<"有參構造函數"<<std::endl;}//拷貝構造函數hunman(const hunman& h){height = h.height;std::cout<<"拷貝構造函數"<<std::endl;}//析構函數~hunman(){std::cout<<"析構函數"<<std::endl; }float height;
protected:std::string ID_card;
};int main(int argc, char const *argv[])
{// hunman h;// hunman(1.80);// hunman h1 = hunman(1.80);// hunman h2 = hunman(h1);hunman h1 = 1.80;/* code */return 0;
}
輸出:
默認情況下,c++編譯器至少給一個類添加3個函數
1.默認構造函數(無參,函數體為空)
2.默認析構函數(無參,函數體為空)
3.默認拷貝構造函數,對屬性進行值拷貝
構造函數調用規則如下:
如果用戶定義有參構造函數,c++不在提供默認無參構造,但是會提供默認拷貝構造
如果用戶定義拷貝構造函數,c++不會再提供其他構造函數
拷貝構造函數
拷貝構造函數的調用
當將一個當前類的對象作為參數傳入構造函數中后,就會調用拷貝構造函數
hunman h;hunman h2 = hunman(h);
?
?這里為什么析構函數調用了兩次呢?
很好理解,因為你實例化了兩個對象,分別是h,h2。
深拷貝與淺拷貝
這個問題時對于拷貝構造函數的經典問題
淺拷貝:
????????是指在定義類時,沒有在類中顯式的構建拷貝構造函數,將一個類的對象作為參數傳入類的構造函數中,編譯器會把原對象中棧區的變量的值和堆區指針的值復制,而不復制一個指針指向的值。
導致的問題:
????????在類執行完成,調用析構函數函數時,并且析構函數中有使用delete對指針進行釋放時,會給兩個對象中的指針分別釋放,而兩個對象中指針的值時相同的,就比如說第一個對象中指針的值是0x123,另一個對象中指針的值也是0x123,根據棧區先進后出的原則,后被創建的對象會先進行釋放對象中的指針0x123,釋放之后,另一個對象中的指針0x123也會被釋放,但是這時就會報錯,因為0x123這個指針已經被釋放過了。
代碼演示 (先看一下沒有構建拷貝構造函數時,棧區和堆區變量是否被復制)
#include <iostream>
class hunman
{
private:std::string name;
public://無參構造函數hunman(){std::cout<<"無參構造函數"<<std::endl;}//有參構造函數hunman(float heightval,int ageval){height = heightval;age = new int(ageval);std::cout<<"有參構造函數"<<std::endl;}//析構函數~hunman(){std::cout<<"析構函數"<<std::endl; }float height;int *age;
protected:std::string ID_card;
};int main(int argc, char const *argv[])
{hunman h(1.80,23);hunman h2 = hunman(h);std::cout<<"第一個對象中的height的值:"<<h.height<<" 第二個對象中的height的值:"<<h2.height<<std::endl;std::cout<<"第一個對象中的age的值:"<<h.age<<" 第二個對象中的age的值:"<<h2.age<<std::endl;return 0;
}
輸出
解析:
????????這段代碼中比沒有構建拷貝構造函數,而這時編譯器會默認構建一個,剛才說過棧區和堆區變量會被復制,根據輸出可以直觀的看到,第一個對象中的棧區變量height和堆區中的age變量所存儲的值是相同的。
但是上段代碼有些問題,因為我們既然創建了一個指針變量,即動態分配內存,那么就應該手動釋放這塊內存,即使用delete。
在析構函數中添加
~hunman(){if(age != nullptr){delete age;age = nullptr;}std::cout<<"析構函數"<<std::endl; }
?但是執行后出現問題
????????通過圖中我們可以看到,第一個析構函數已經成功執行,但是第二個析構函數執行時卻發生的報錯free(): double free detected in tcache 2。
????????這個錯誤提示表明你試圖對同一塊已經釋放的內存進行了多次釋放操作,也就是所謂的 “雙重釋放” 問題
這個雙重釋放也很好理解,因為在第二個對象對0x123這個地址釋放后,原對象再次對這個地址釋放是沒有用的,因為他已經被釋放過了。
注:
?hunman h(1.80,23);原對象h(先進后出,后釋放)
?hunman h2 = hunman(h);第二個對象h2(先進后出,先釋放)
?解決方法:
使用深拷貝:
????????深拷貝是指在復制對象時,不僅復制對象的基本數據類型的值,還會為對象中的指針成員分配新的內存空間,并將原指針所指向的內存內容復制到新的內存空間中。這樣,原對象和拷貝對象的指針成員會指向不同的內存地址。
代碼,顯式構建拷貝構造函數
// 拷貝構造函數hunman(const hunman& h){height = h.height;age = new int(*h.age);std::cout<<"拷貝構造函數"<<"地址:"<<std::endl;}
?在拷貝構造函數中對棧區的變量重新復制,并且對堆區的指針重新分配內存,使其和原對象中的指針的值不是一個地址,這樣第二個對象釋放一個地址,而另一個對象釋放里一個地址,這樣互不干涉,程序就不會崩潰了。
由圖可知,兩個對象當中的age的值即地址是不一樣的,完美解決!
注:在顯式的構建拷貝構造函數時,在函數中應該對需要復制的值手動賦值,因為在沒有構建拷貝茍造函數時,編譯器會幫你把所有的變量復制,但是當你顯式構建是,就沒了,所以需要手動復制。
構造函數初始化列表
使用構造函數初始化列表可以幫助我們快速的初始化成員變量
語法:類名(參數1,參數2,...): 成員變量1(參數1),成員變量2(參數2),...
#include <iostream>
class hunman
{
public://構造函數初始化列表hunman(int a,float b,std::string c): age(a),height(b),name(c){std::cout<<"age:"<<age<<std::endl;std::cout<<"height:"<<height<<std::endl;std::cout<<"name:"<<name<<std::endl;}int age;float height;std::string name;
};int main(int argc, char const *argv[])
{hunman(21,1.80,"rqtz");return 0;
}
類對象作為類成員
當有另一個類a的對象作為類h的成員變量時候,構造函數和析構函數的調用順序
構造函數順序 :先a后h
析構函數順序:先h后a
代碼
#include <iostream>
class animal
{public:animal(){std::cout<<"animal無參構造函數"<<std::endl;}~animal(){std::cout<<"animal析構函數"<<std::endl;}};
class hunman
{
public://無參構造函數hunman(){std::cout<<"hunman無參構造函數"<<std::endl;}~hunman(){std::cout<<"hunman析構函數"<<std::endl;}animal a;
};int main(int argc, char const *argv[])
{hunman h;return 0;
}
?
靜態成員
在類中使用 static關鍵字所修飾的變量和函數叫做類的靜態成員
- 靜態成員不屬于任何對象
- 該類的任何對象共用一分數據,共享一塊內存
- 該類的任何對象都可以修改靜態成員的值,并且該值會被更新
- 靜態成員的初始化需要在類外,使用類名加作用域的方式初始化
#include <iostream>class hunman
{
public://無參構造函數hunman(){// std::cout<<"hunman無參構造函數"<<std::endl;}~hunman(){// std::cout<<"hunman析構函數"<<std::endl;}static void func(){std::cout<<"靜態成員函數"<<std::endl;}static int a;
};
//類外初始化
int hunman::a = 1;
int main(int argc, char const *argv[])
{hunman h;std::cout<<h.a<<std::endl;//其他對象改變靜態變量的值班hunman h2;h2.a = 10;//在用原對象訪問時,值已經更新std::cout<<h.a<<std::endl;//通過類名加作用域訪問靜態變量和靜態成員函數std::cout<<hunman::a<<std::endl;hunman::func();//該類的對象靜態變量時統一快內存std::cout<<&h2.a<<std::endl;std::cout<<&hunman::a<<std::endl;return 0;
}
輸出:
?
?
??🔥🔥個人主頁 🔥🔥