C++詳細(xì)講解對象的構(gòu)造
一、對象的構(gòu)造(上)
1.1 對象的初始值
問題:對象中成員變量的初始值是多少?
下面的類定義中成員變量 i 和 j 的初始值為多少?
下面看一段成員變量初始值的代碼:
#include<stdio.h> class Test { private: int i; int j; public: int getI() {return i;} int getJ() {return j;} }; Test gt; int main() { printf("gt.i = %d\n", gt.getI()); printf("gt.j = %d\n", gt.getJ()); Test t1; printf("t1.i = %d\n", t1.getI()); printf("t1.j = %d\n", t1.getJ()); Test* pt = new Test; printf("pt->i = %d\n", pt->getI()); printf("pt->j = %d\n", pt->getJ()); delete pt; return 0; }
下面為輸出結(jié)果:
對象t1 所占用的存儲空間在棧上面,而且成員變量 i 和 j 也沒有明確的初始值,所以初始值就不定。對象 gt 所占用的存儲空間在全局?jǐn)?shù)據(jù)區(qū),所以初始值統(tǒng)一為 0。
Test* pt = new Test;意味著在堆空間中生成一個 Test 對象,雖然 pt->i 和 pt->j 均為 0,這只是巧合罷了,因為在堆上創(chuàng)建對象時,成員變量初始為隨機(jī)值。
注:類得到的其實是數(shù)據(jù)類型,所以說通過這種數(shù)據(jù)類型在全局?jǐn)?shù)據(jù)區(qū)、棧和堆上面都能夠生成對象。
1.2 對象的初始化
從程序設(shè)計的角度,對象只是變量,因此:
- 在棧上創(chuàng)建對象時,成員變量初始為隨機(jī)值
- 在堆上創(chuàng)建對象時,成員變量初始為隨機(jī)值
- 在靜態(tài)存儲區(qū)創(chuàng)建對象時,成員變量初始為 0 值
生活中的對象都是在初始化后上市的
初始狀態(tài)(出廠設(shè)置)是對象普遍存在的一個狀態(tài)
—股而言,對象都需要—個確定的初始狀態(tài)
解決方案
- 在類中提供一個 public 的 initialize 函數(shù)
- 對象創(chuàng)建后立即調(diào)用 initialize 函數(shù)進(jìn)行初始化
如下:
下面看一段初始化函數(shù)的代碼:
#include<stdio.h> class Test { private: int i; int j; public: int getI() {return i;} int getJ() {return j;} void initialize() { i = 1; j = 2; } }; Test gt; int main() { gt.initialize(); printf("gt.i = %d\n", gt.getI()); printf("gt.j = %d\n", gt.getJ()); Test t1; t1.initialize(); printf("t1.i = %d\n", t1.getI()); printf("t1.j = %d\n", t1.getJ()); Test* pt = new Test; pt->initialize(); printf("pt->i = %d\n", pt->getI()); printf("pt->j = %d\n", pt->getJ()); delete pt; return 0; }
下面為輸出結(jié)果:
存在的問題
- initialize 只是一個普通函數(shù),必須顯示調(diào)用
- 如果未調(diào)用 initialize 函數(shù),運(yùn)行結(jié)果是不確定的
下面為解決辦法:
C++中可以定義與類名相同的特殊成員函數(shù)
這種特殊的成員函數(shù)叫做構(gòu)造函數(shù)
- 構(gòu)造沒有任何返回類型的聲明
- 構(gòu)造函數(shù)在對象定義時自動被調(diào)用
下面來體驗一下構(gòu)造函數(shù):
#include<stdio.h> class Test { private: int i; int j; public: int getI() {return i;} int getJ() {return j;} Test() { printf("Test() Begin\n"); i = 1; j = 2; printf("Test() End\n"); } }; Test gt; int main() { printf("gt.i = %d\n", gt.getI()); printf("gt.j = %d\n", gt.getJ()); Test t1; printf("t1.i = %d\n", t1.getI()); printf("t1.j = %d\n", t1.getJ()); Test* pt = new Test; printf("pt->i = %d\n", pt->getI()); printf("pt->j = %d\n", pt->getJ()); delete pt; return 0; }
下面為輸出結(jié)果:
可以看到,Test() Begin 和 Test() End 出現(xiàn)了三次,也就是說,Test() 這個構(gòu)造函數(shù)被調(diào)用了三次,這是因為創(chuàng)建了三個對象。
1.3 小結(jié)
- 每個對象在使用之前都應(yīng)該初始化
- 類的構(gòu)造函數(shù)用于對象的初始化
- 構(gòu)造函數(shù)與類同名并且沒有返回值
- 構(gòu)造函數(shù)在對象定義時自動被調(diào)用
二、對象的構(gòu)造(中)
2.1 構(gòu)造函數(shù)
帶有參數(shù)的構(gòu)造函數(shù)
- 構(gòu)造函數(shù)可以根據(jù)需要定義參數(shù)
- 一個類中可以存在多個重載的構(gòu)造函數(shù)
- 構(gòu)造函數(shù)的重載遵循 C++ 重載的規(guī)則
如下:
友情提醒
對象定義和對象聲明不同
- 對象定義--申請對象的空間并調(diào)用構(gòu)造函數(shù)
- 對象聲明--告訴編譯器存在這樣一個對象
如下:
構(gòu)造函數(shù)的自動調(diào)用
如下:
下面看一段帶參數(shù)的構(gòu)造函數(shù)的代碼:
#include <stdio.h> class Test { public: Test() { printf("Test()\n"); } Test(int v) { printf("Test(int v), v = %d\n", v); } }; int main() { Test t; // 調(diào)用 Test() Test t1(1); // 調(diào)用 Test(int v) Test t2 = 2; // 調(diào)用 Test(int v) return 0; }
下面為輸出結(jié)果,和預(yù)想中的一致。
這里需要明確一個問題,int i = 1;與 int i; i = 1;的不同。前者是初始化,后者是先定義,再賦值。后者由于定義 i 時沒有初始化,所以 i 的值時隨機(jī)的。C語言中這兩者差別很小,但是在 C++ 中兩者差異很大。差別在于在 C++ 中初始化會調(diào)用構(gòu)造函數(shù)。下面看一個例子,在上述代碼的基礎(chǔ)上加一行代碼 t = t2;
#include <stdio.h> class Test { public: Test() { printf("Test()\n"); } Test(int v) { printf("Test(int v), v = %d\n", v); } }; int main() { Test t; // 調(diào)用 Test() Test t1(1); // 調(diào)用 Test(int v) Test t2 = 2; // 調(diào)用 Test(int v) t = t2; return 0; }
下面為輸出結(jié)果,可以看到與上面的代碼輸出結(jié)果一模一樣。這就因為 C++ 中初始化和賦值不同,初始化會調(diào)用構(gòu)造函數(shù),賦值的時候則不用。
下面再看一個例子:
#include <stdio.h> class Test { public: Test() { printf("Test()\n"); } Test(int v) { printf("Test(int v), v = %d\n", v); } }; int main() { Test t; // 調(diào)用 Test() Test t1(1); // 調(diào)用 Test(int v) Test t2 = 2; // 調(diào)用 Test(int v) int i(100); printf("i = %d\n", i); return 0; }
下面為輸出結(jié)果:
構(gòu)造函數(shù)的調(diào)用
- 一般情況下,構(gòu)造函數(shù)在對象定義時被自動調(diào)用
- —些特殊情況下,需要手工調(diào)用構(gòu)造函數(shù)
下面看一段構(gòu)造函數(shù)手動調(diào)用的代碼:
#include <stdio.h> class Test { private: int m_value; public: Test() { printf("Test()\n"); m_value = 0; } Test(int v) { printf("Test(int v), v = %d\n", v); m_value = v; } int getValue() { return m_value; } }; int main() { Test ta[3] = {Test(), Test(1), Test(2)}; for (int i = 0; i < 3; i++) { printf("ta[%d].getValue() = %d\n", i, ta[i].getValue()); } Test t = Test(100); printf("t.getValue() = %d\n", t.getValue()); return 0; }
下面為輸出結(jié)果,可以看到,Test(1)、Test(2) 和 Test(100) 均為手動調(diào)用構(gòu)造函數(shù)。
2.2小實例
需求:開發(fā)一個數(shù)組類解決原生數(shù)組的安全性問題
- 提供函數(shù)獲取數(shù)組長度
- 提供函數(shù)獲取數(shù)組元素
- 提供函數(shù)設(shè)置數(shù)組元素
IntArray.h:
#ifndef _INTARRAY_H_ #define _INTARRAY_H_ class IntArray { private: int m_length; int* m_pointer; public: IntArray(int len); int length(); bool get(int index, int& value); bool set(int index ,int value); void free(); }; #endif
IntArray.cpp:
#include "IntArray.h" IntArray::IntArray(int len) { m_pointer = new int[len]; for (int i = 0; i < len; i++) { m_pointer[i] = 0; } m_length = len; } int IntArray::length() { return m_length; } bool IntArray::get(int index, int& value) { bool ret = (0 <= index) && (index < length()); if( ret ) { value = m_pointer[index]; } return ret; } bool IntArray::set(int index, int value) { bool ret = (0 <= index) && (index < length()); if( ret ) { m_pointer[index] = value; } return ret; } void IntArray::free() { delete[]m_pointer; }
main.cpp:
#include <stdio.h> #include "IntArray.h" int main() { IntArray a(5); for (int i = 0; i < a.length(); i++) { a.set(i, i + 1); } for (int i = 0; i < a .length(); i++) { int value = 0; if( a.get(i, value) ) { printf("a[%d] = %d\n", i, value); } } a.free(); return 0; }
下面為輸出結(jié)果:
這樣寫出來的數(shù)組很安全,沒有數(shù)組越界問題。
2.3 小結(jié)
- 構(gòu)造函數(shù)可以根據(jù)需要定義參數(shù)
- 構(gòu)造函數(shù)之間可以存在重載關(guān)系
- 構(gòu)造函數(shù)遵循 C++ 中重載函數(shù)的規(guī)則
- 對象定義時會觸發(fā)構(gòu)造函數(shù)的調(diào)用
- 在一些情況下可以手動調(diào)用構(gòu)造函數(shù)
三、對象的構(gòu)造(下)
3.1 特殊的構(gòu)造函數(shù)
兩個特殊的構(gòu)造函數(shù)
無參構(gòu)造函數(shù)
- 沒有參數(shù)的構(gòu)造函數(shù)
- 當(dāng)類中沒有定義構(gòu)造函數(shù)時,編譯器默認(rèn)提供一個無參構(gòu)造函數(shù),并且其函數(shù)體為空
拷貝構(gòu)造函數(shù)
- 參數(shù)為 const class_name& 的構(gòu)造函數(shù)
- 當(dāng)類中沒有定義拷貝構(gòu)造函數(shù)時,編譯器默認(rèn)提供一個拷貝構(gòu)造函數(shù),簡單的進(jìn)行成員變量的值復(fù)制
下面看一段無參數(shù)構(gòu)造函數(shù)的代碼(代碼3-1):
#include <stdio.h> class Test { private: int i; int j; public: int getI() { return i; } int getJ() { return j; } }; int main() { Test t; return 0; }
可以看到,編譯通過:
創(chuàng)建一個類的對象必須要調(diào)用構(gòu)造函數(shù),為什么能夠編譯通過呢?這是因為編譯器在發(fā)現(xiàn)我們沒有定義構(gòu)造函數(shù)時,會默認(rèn)提供一個無參構(gòu)造函數(shù),等效如(代碼3-2):
#include <stdio.h> class Test { private: int i; int j; public: int getI() { return i; } int getJ() { return j; } Test() { } }; int main() { Test t; return 0; }
小貼士:所以說,class T { }; 里面不是什么都沒有,里面至少有一個無參構(gòu)造函數(shù)。
下面再來看一段代碼(代碼3-3):
#include <stdio.h> class Test { private: int i; int j; public: int getI() { return i; } int getJ() { return j; } }; int main() { Test t1; Test t2 = t1; printf("t1.i = %d, t1.j = %d\n", t1.getI(), t1.getJ()); printf("t2.i = %d, t2.j = %d\n", t2.getI(), t2.getJ()); return 0; }
下面為輸出結(jié)果:
這里的 i 和 j 打印出來的都是隨機(jī)值,這是因為類里面沒有手工編寫的構(gòu)造函數(shù),所以 t1 和 t2 所采用的就是編譯器提供的默認(rèn)無參構(gòu)造函數(shù)構(gòu)造的,編譯器提供的無參構(gòu)造函數(shù)為空,所以 i 和 j 的值就是隨機(jī)的。
上述代碼就相當(dāng)于(代碼3-4):
#include <stdio.h> class Test { private: int i; int j; public: int getI() { return i; } int getJ() { return j; } Test(const Test& t) { i = t.i; j = t.j; } }; int main() { Test t1; Test t2 = t1; printf("t1.i = %d, t1.j = %d\n", t1.getI(), t1.getJ()); printf("t2.i = %d, t2.j = %d\n", t2.getI(), t2.getJ()); return 0; }
但是編譯的時候會報錯:
這是因為在類里面沒有編寫任何構(gòu)造函數(shù)時,編譯器才提供默認(rèn)的無參構(gòu)造函數(shù)。這里手工編寫了一個拷貝構(gòu)造函數(shù),編譯器就不會提供默認(rèn)的無參構(gòu)造函數(shù),需要自己把無參構(gòu)造函數(shù)加上。
如下,自己加上無參構(gòu)造函數(shù)(代碼3-5):
#include <stdio.h> class Test { private: int i; int j; public: int getI() { return i; } int getJ() { return j; } Test(const Test& t) { i = t.i; j = t.j; } Test() { } }; int main() { Test t1; Test t2 = t1; printf("t1.i = %d, t1.j = %d\n", t1.getI(), t1.getJ()); printf("t2.i = %d, t2.j = %d\n", t2.getI(), t2.getJ()); return 0; }
這樣就能編譯通過了,而且效果跟代碼3-3的相同:
3.2 拷貝構(gòu)造函數(shù)
拷貝構(gòu)造函數(shù)的意義
兼容C語言的初始化方式
初始化行為能夠符合預(yù)期的邏輯
淺拷貝
- 拷貝后對象的物理狀態(tài)相同(物理狀態(tài)指的是對象占據(jù)的內(nèi)存當(dāng)中每個字節(jié)是否相等,如代碼3-6)
深拷貝
- 拷貝后對象的邏輯狀態(tài)相同(邏輯狀態(tài)指的是指針?biāo)赶虻膬?nèi)存空間的值是否相同,如代碼3-9)
注:編譯器提供的拷貝構(gòu)造函數(shù)只進(jìn)行淺拷貝!
下面看一段代碼(代碼3-6):
#include <stdio.h> class Test { private: int i; int j; int* p; public: int getI() { return i; } int getJ() { return j; } int* getP() { return p; } Test(int v) { i = 1; j = 2; p = new int; *p = v; } }; int main() { Test t1(3); Test t2 = t1; printf("t1.i = %d, t1.j = %d, t1.p = %p\n", t1.getI(), t1.getJ(), t1.getP()); printf("t2.i = %d, t2.j = %d, t2.p = %p\n", t2.getI(), t2.getJ(), t2.getP()); return 0; }
下面為輸出結(jié)果:
這段程序的第一個問題就是 t1 和 t2 的 p 指針都指向同一個堆空間中的地址,第二個問題就是申請了內(nèi)存并沒有釋放,會造成內(nèi)存泄漏。
下面加上釋放內(nèi)存的代碼(代碼3-7):
#include <stdio.h> class Test { private: int i; int j; int* p; public: int getI() { return i; } int getJ() { return j; } int* getP() { return p; } Test(int v) { i = 1; j = 2; p = new int; *p = v; } void free() { delete p; } }; int main() { Test t1(3); Test t2 = t1; printf("t1.i = %d, t1.j = %d, t1.p = %p\n", t1.getI(), t1.getJ(), t1.getP()); printf("t2.i = %d, t2.j = %d, t2.p = %p\n", t2.getI(), t2.getJ(), t2.getP()); t1.free(); t2.free(); return 0; }
下面為輸出結(jié)果,編譯能通過,但是運(yùn)行時發(fā)生了錯誤,釋放了兩次堆空間的內(nèi)存:
下面為解決方法(代碼3-8):
#include <stdio.h> class Test { private: int i; int j; int* p; public: int getI() { return i; } int getJ() { return j; } int* getP() { return p; } Test(const Test& t) { i = t.i; j = t.j; p = new int; *p = *t.p; } Test(int v) { i = 1; j = 2; p = new int; *p = v; } void free() { delete p; } }; int main() { Test t1(3); Test t2(t1); printf("t1.i = %d, t1.j = %d, t1.p = %p\n", t1.getI(), t1.getJ(), t1.getP()); printf("t2.i = %d, t2.j = %d, t2.p = %p\n", t2.getI(), t2.getJ(), t2.getP()); t1.free(); t2.free(); return 0; }
下面為輸出結(jié)果,可以到 t1 和 t2 的 p 指針分別指向不同的堆空間地址:
如果我們看一下邏輯狀態(tài),也就是 *t1.p 和 *t2.p 的值,代碼如下(代碼3-9):
#include <stdio.h> class Test { private: int i; int j; int* p; public: int getI() { return i; } int getJ() { return j; } int* getP() { return p; } Test(const Test& t) { i = t.i; j = t.j; p = new int; *p = *t.p; } Test(int v) { i = 1; j = 2; p = new int; *p = v; } void free() { delete p; } }; int main() { Test t1(3); Test t2(t1); printf("t1.i = %d, t1.j = %d, t1.p = %p\n", t1.getI(), t1.getJ(), t1.getP()); printf("t2.i = %d, t2.j = %d, t2.p = %p\n", t2.getI(), t2.getJ(), t2.getP()); t1.free(); t2.free(); return 0; }
下面為輸出結(jié)果,可以看到 *t1.p 和 *t2.p 的值相同,也就是說邏輯狀態(tài)相同,這就叫做深拷貝。
什么時候需要進(jìn)行深拷貝?
對象中有成員指代了系統(tǒng)中的資源
- 成員指向了動態(tài)內(nèi)存空間
- 成員打開了外存中的文件
- 成員使用了系統(tǒng)中的網(wǎng)絡(luò)端口
- ......
問題分析
下面就是淺拷貝:
一般性原則
自定義拷貝構(gòu)造函數(shù),必然需要實現(xiàn)深拷貝!??!
下面看一個使用深拷貝,對前面數(shù)組的代碼進(jìn)行改造。
IntArray.h:
#ifndef _INTARRAY_H_ #define _INTARRAY_H_ class IntArray { private: int m_length; int* m_pointer; public: IntArray(int len); IntArray(const IntArray& obj); int length(); bool get(int index, int& value); bool set(int index ,int value); void free(); }; #endif
IntArray.cpp:
#include "IntArray.h" IntArray::IntArray(int len) { m_pointer = new int[len]; for (int i = 0; i < len; i++) { m_pointer[i] = 0; } m_length = len; } IntArray::IntArray(const IntArray& obj) { m_length = obj.m_length; m_pointer = new int[obj.m_length]; for (int i = 0; i < obj.m_length; i++) { m_pointer[i] = obj.m_pointer[i]; } } int IntArray::length() { return m_length; } bool IntArray::get(int index, int& value) { bool ret = (0 <= index) && (index < length()); if( ret ) { value = m_pointer[index]; } return ret; } bool IntArray::set(int index, int value) { bool ret = (0 <= index) && (index < length()); if( ret ) { m_pointer[index] = value; } return ret; } void IntArray::free() { delete[]m_pointer; }
main.cpp:
#include <stdio.h> #include "IntArray.h" int main() { IntArray a(5); for (int i = 0; i < a.length(); i++) { a.set(i, i + 1); } for (int i = 0; i < a.length(); i++) { int value = 0; if( a.get(i, value) ) { printf("a[%d] = %d\n", i, value); } } IntArray b = a; for (int i = 0; i < b.length(); i++) { int value = 0; if( b.get(i, value) ) { printf("b[%d] = %d\n", i, value); } } a.free(); b.free(); return 0; }
下面為輸出結(jié)果:
可以看到 b 數(shù)組里面的元素與 a 數(shù)組里面的元素相同,這就是深拷貝構(gòu)造函數(shù)的結(jié)果。
3.3 小結(jié)
C++ 編譯器會默認(rèn)提供構(gòu)造函數(shù)
無參構(gòu)造函數(shù)用于定義對象的默認(rèn)初始狀態(tài)
拷貝構(gòu)造函數(shù)在創(chuàng)建對象時拷貝對象的狀態(tài)
對象的拷貝有淺拷貝和深拷貝兩種方式
- 淺拷貝使得對象的物理狀態(tài)相同
- 深拷貝使得對象的邏輯狀態(tài)相同
到此這篇關(guān)于C++詳細(xì)講解對象的構(gòu)造的文章就介紹到這了,更多相關(guān)C++ 對象的構(gòu)造內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++17 使用 std::string_view避免字符串拷貝優(yōu)化程序性能
這篇文章主要介紹了C++17 使用 std::string_view避免字符串拷貝優(yōu)化程序性能,幫助大家提高程序運(yùn)行速度,感興趣的朋友可以了解下2020-10-10C/C++實現(xiàn)經(jīng)典象棋游戲的示例代碼
中國象棋是起源于中國的一種棋,屬于二人對抗性游戲的一種,在中國有著悠久的歷史。本文將利用C++實現(xiàn)這一經(jīng)典游戲,快跟隨小編一起學(xué)習(xí)一下吧2022-06-06c語言網(wǎng)絡(luò)編程-標(biāo)準(zhǔn)步驟(改進(jìn)版)
這篇文章主要介紹了c語言網(wǎng)絡(luò)編程-標(biāo)準(zhǔn)步驟的改進(jìn)說明,需要的朋友可以參考下2014-01-01GCC 編譯使用動態(tài)鏈接庫和靜態(tài)鏈接庫的方法
根據(jù)鏈接時期的不同,庫又有靜態(tài)庫和動態(tài)庫之分,有別于靜態(tài)庫,動態(tài)庫的鏈接是在程序執(zhí)行的時候被鏈接的2013-03-03C語言數(shù)據(jù)結(jié)構(gòu)進(jìn)階之棧和隊列的實現(xiàn)
棧和隊列,嚴(yán)格意義上來說,也屬于線性表,因為它們也都用于存儲邏輯關(guān)系為 "一對一" 的數(shù)據(jù),但由于它們比較特殊,因此將其單獨(dú)作為一章,做重點(diǎn)講解2021-11-11C++實現(xiàn)LeetCode(124.求二叉樹的最大路徑和)
這篇文章主要介紹了C++實現(xiàn)LeetCode(124.求二叉樹的最大路徑和),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07