如何理解C++中的類和對象,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。
構造函數 是一個 特殊的成員函數,名字與類名相同 , 創建類類型對象時由編譯器自動調用 ,保證每個數據成員都有 一個合適的初始值,并且 在對象的生命周期內只調用一次 。 其實構造函數的作用就是完成成員變量的初始化 ,但不同于c語言的初始化構造函數可以實在創造對象的同時就完成成員變量的初始化。
1. 函數名與類名相同。
2. 無返回值。
3. 對象實例化時編譯器 自動調用 對應的構造函數。
4. 構造函數可以重載。
構造函數的實現主要有三種,
1.當用戶沒有實現構造函數的話系統會默認創造一個,此時系統會將內置類型的成員變量賦予隨機值,而對于自定義類型的成員變量則會調用他們的構造函數。(注:內置類型一般指的是:int char double float等這些定義好的類型,自定義類型指的是:struct這種類型以及class類這種)。
2.當然用戶也可以自己實現構造函數,一種為無參構造
3.類一種為帶參構造,但是在帶參構造中我們使用全缺省構造。我們用代碼展示一下:
我們可以看到當我們沒有在Data類進行函數構造的時系統將會自己默認創建構造函數,對內置類型變量賦予隨機值,自定義類型調用自己的構造函數(若自定義類型也沒有定義構造函數那么此例子中的_a1和_a2也會被賦予隨機值)
這里出一個問題對于代碼風格造成的問題:成員變量year最后的結果是多少呢?
class A{public:A(int year){year = year;}private:int year;};int main(){A a(20);}
答案是:隨機值。那么為什么是隨機值呢?這里主要是變量之間它采用了就近原則,所以等式左邊的year會直接尋找離他最近的變量所以會將等式右邊的year直接賦值給它自己,所以year最后的值就是隨機值。
我們繼續來說帶參的構造函數,我們一般推薦使用的是全缺省的構造函數(注:
無參的構造函數和全缺省的構造函數都稱為默認構造函數,并且默認構造函數只能有一個。無參構造函數、全缺省構造函數、我們沒寫編譯器默認生成的構造函數,三者都可以認為是默認成員函數。
)
構造函數時完成對象的初始化,那么一個對象又是怎么樣被銷毀的呢?
與構造函數功能相反,析構函數不是完成對象的銷毀,局部對象銷毀工作是由編譯器完成的。而 對象在銷毀時會自動調用析構函數,完成類的一些清理工作。
1. 析構函數名是在類名前加上字符 ~ 。
2. 無參數無返回值。
3. 一個類有且只有一個析構函數。若未顯式定義,系統會自動生成默認的析構函數 。
4. 對象生命周期結束時, C++ 編譯系統系統自動調用析構函數。
這里我們用棧的例子來說明析構函數的實現以及作用。
class Stack { public: Stack(int capacity = 4) { _a = (int*)malloc(sizeof(int)*capacity); if (_a == nullptr) { cout << "malloc fail" << endl; exit(-1); } _top = 0; _capacity = capacity; } //析構函數的實現 ~Stack() { // 像Stack這樣的類,對象中的資源需要清理工作,就用析構函數 free(_a); _a = nullptr; _top = _capacity = 0; } private: int* _a; int _top; int _capacity; };
這里是完成構造函數,有自己定義的析構函數的效果。同構造函數一樣對于內置成員變量析構函數會置為隨機值,而自定義類型則會去調用他們的析構函數。
如果某些時候我們需要去復制一個對象,這樣的話我們該怎么樣去解決呢?
這里我們就需要引入拷貝函數。那么什么叫做拷貝函數呢?我們應該去怎么實現呢?有什么注意事項呢?這里我們一一來說道。
構造函數 : 只有單個形參 ,該形參是對本 類類型對象的引用 ( 一般常用 const 修飾 ) ,在用 已存在的類類型對象 創建新對象時由編譯器自動調用 。
1. 拷貝構造函數 是構造函數的一個重載形式 。
2. 拷貝構造函數的參數只有一個且必須使用引用傳參,使用傳值方式會引發無窮遞歸調用 。
3. 若未顯示定義,系統生成默認的拷貝構造函數。 默認的拷貝構造函數對象按內存存儲按字節序完成拷 貝,這種拷貝我們叫做淺拷貝,或者值拷貝。
拷貝函數的實現分為兩種一種是系統默認,一種是自己定義。我們分別來看其效果
class A { public: A() { _a1 = 1; _a2 = 2; } ~A() { cout << "A()" << endl; } private: int _a1; int _a2; }; class Data { public: /*Data() { _year = 2021; _month = 12; _day = 12; }*/ //Data(int year, int month, int day) //{ // _year = year; // _month = month; // _day = day; //} Data(int year = 2022, int month = 12, int day = 12) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; A a; }; int main() { Data s; //拷貝函數的調用 Data s2(s); return 0; }
調用系統默認生成拷貝函數(注:這里拷貝函數的拷貝對自定義類型和內置類型的成員變量處理都是一致的完成字節序的值拷貝)
圖1 調用系統默認生成的拷貝函數
圖2 調用用戶自己定義的拷貝函數
在這里我們順便說一下在自定義拷貝函數的時候一定要使用引用不然會出現無限遞歸例如 Data(Data s){}正確的使用是Data (const Data & s){}其中const是為了保護原數據不被輕易改動。
class A { public: A() { _a1 = 1; _a2 = 2; } ~A() { cout << "A()" << endl; } private: int _a1; int _a2; }; class Data { public: /*Data() { _year = 2021; _month = 12; _day = 12; }*/ //Data(int year, int month, int day) //{ // _year = year; // _month = month; // _day = day; //} Data( const Data &s) { _year = s._year; _month = s._month; _day = s._day; } Data(int year = 2023, int month = 12, int day = 12) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; A a; }; int main() { Data s; //拷貝函數的調用 Data s2(s); return 0; }
我們可以發現s2均完整的賦值了s的內容,但是這里真的就沒有問題了嗎?如果我們使用系統默認生成的拷貝函數成員變量中含有指針那么會出現什么樣的問題呢?
class String { public: String(const char* str = "jack") { _str = (char*)malloc(strlen(str) + 1); strcpy(_str, str); } ~String() { cout << "~String()" << endl; free(_str); } private: char* _str; }; int main() { String s; String s1(s); }
我們可以看到雖然雖然s1拷貝了s的內容但是最后系統還是拋出了錯誤那么這個錯誤來自那里呢?
我們看這幅圖
這里就是我們之前說的系統默認生成的拷貝函數是淺拷貝。
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關注億速云的更多內容!
看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。