<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
問題:物件中成員變數的初始值是多少?
下面的類定義中成員變數 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 = %dn", gt.getI()); printf("gt.j = %dn", gt.getJ()); Test t1; printf("t1.i = %dn", t1.getI()); printf("t1.j = %dn", t1.getJ()); Test* pt = new Test; printf("pt->i = %dn", pt->getI()); printf("pt->j = %dn", pt->getJ()); delete pt; return 0; }
下面為輸出結果:
物件t1 所佔用的儲存空間在棧上面,而且成員變數 i 和 j 也沒有明確的初始值,所以初始值就不定。物件 gt 所佔用的儲存空間在全域性資料區,所以初始值統一為 0。
Test* pt = new Test;意味著在堆空間中生成一個 Test 物件,雖然 pt->i 和 pt->j 均為 0,這只是巧合罷了,因為在堆上建立物件時,成員變數初始為隨機值。
注:類得到的其實是資料型別,所以說通過這種資料型別在全域性資料區、棧和堆上面都能夠生成物件。
從程式設計的角度,物件只是變數,因此:
生活中的物件都是在初始化後上市的
初始狀態(出廠設定)是物件普遍存在的一個狀態
—股而言,物件都需要—個確定的初始狀態
解決方案
如下:
下面看一段初始化函數的程式碼:
#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 = %dn", gt.getI()); printf("gt.j = %dn", gt.getJ()); Test t1; t1.initialize(); printf("t1.i = %dn", t1.getI()); printf("t1.j = %dn", t1.getJ()); Test* pt = new Test; pt->initialize(); printf("pt->i = %dn", pt->getI()); printf("pt->j = %dn", pt->getJ()); delete pt; return 0; }
下面為輸出結果:
存在的問題
下面為解決辦法:
C++中可以定義與類名相同的特殊成員函數
這種特殊的成員函數叫做建構函式
下面來體驗一下建構函式:
#include<stdio.h> class Test { private: int i; int j; public: int getI() {return i;} int getJ() {return j;} Test() { printf("Test() Beginn"); i = 1; j = 2; printf("Test() Endn"); } }; Test gt; int main() { printf("gt.i = %dn", gt.getI()); printf("gt.j = %dn", gt.getJ()); Test t1; printf("t1.i = %dn", t1.getI()); printf("t1.j = %dn", t1.getJ()); Test* pt = new Test; printf("pt->i = %dn", pt->getI()); printf("pt->j = %dn", pt->getJ()); delete pt; return 0; }
下面為輸出結果:
可以看到,Test() Begin 和 Test() End 出現了三次,也就是說,Test() 這個建構函式被呼叫了三次,這是因為建立了三個物件。
帶有引數的建構函式
如下:
友情提醒
物件定義和物件宣告不同
如下:
建構函式的自動呼叫
如下:
下面看一段帶引數的建構函式的程式碼:
#include <stdio.h> class Test { public: Test() { printf("Test()n"); } Test(int v) { printf("Test(int v), v = %dn", v); } }; int main() { Test t; // 呼叫 Test() Test t1(1); // 呼叫 Test(int v) Test t2 = 2; // 呼叫 Test(int v) return 0; }
下面為輸出結果,和預想中的一致。
這裡需要明確一個問題,int i = 1;與 int i; i = 1;的不同。前者是初始化,後者是先定義,再賦值。後者由於定義 i 時沒有初始化,所以 i 的值時隨機的。C語言中這兩者差別很小,但是在 C++ 中兩者差異很大。差別在於在 C++ 中初始化會呼叫建構函式。下面看一個例子,在上述程式碼的基礎上加一行程式碼 t = t2;
#include <stdio.h> class Test { public: Test() { printf("Test()n"); } Test(int v) { printf("Test(int v), v = %dn", v); } }; int main() { Test t; // 呼叫 Test() Test t1(1); // 呼叫 Test(int v) Test t2 = 2; // 呼叫 Test(int v) t = t2; return 0; }
下面為輸出結果,可以看到與上面的程式碼輸出結果一模一樣。這就因為 C++ 中初始化和賦值不同,初始化會呼叫建構函式,賦值的時候則不用。
下面再看一個例子:
#include <stdio.h> class Test { public: Test() { printf("Test()n"); } Test(int v) { printf("Test(int v), v = %dn", v); } }; int main() { Test t; // 呼叫 Test() Test t1(1); // 呼叫 Test(int v) Test t2 = 2; // 呼叫 Test(int v) int i(100); printf("i = %dn", i); return 0; }
下面為輸出結果:
建構函式的呼叫
下面看一段建構函式手動呼叫的程式碼:
#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 = %dn", 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() = %dn", i, ta[i].getValue()); } Test t = Test(100); printf("t.getValue() = %dn", t.getValue()); return 0; }
下面為輸出結果,可以看到,Test(1)、Test(2) 和 Test(100) 均為手動呼叫建構函式。
需求:開發一個陣列類解決原生陣列的安全性問題
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] = %dn", i, value); } } a.free(); return 0; }
下面為輸出結果:
這樣寫出來的陣列很安全,沒有陣列越界問題。
兩個特殊的建構函式
無參建構函式
拷貝建構函式
下面看一段無引數建構函式的程式碼(程式碼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; }
可以看到,編譯通過:
建立一個類的物件必須要呼叫建構函式,為什麼能夠編譯通過呢?這是因為編譯器在發現我們沒有定義建構函式時,會預設提供一個無參建構函式,等效如(程式碼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 { }; 裡面不是什麼都沒有,裡面至少有一個無參建構函式。
下面再來看一段程式碼(程式碼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 = %dn", t1.getI(), t1.getJ()); printf("t2.i = %d, t2.j = %dn", t2.getI(), t2.getJ()); return 0; }
下面為輸出結果:
這裡的 i 和 j 列印出來的都是隨機值,這是因為類裡面沒有手工編寫的建構函式,所以 t1 和 t2 所採用的就是編譯器提供的預設無參建構函式構造的,編譯器提供的無參建構函式為空,所以 i 和 j 的值就是隨機的。
上述程式碼就相當於(程式碼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 = %dn", t1.getI(), t1.getJ()); printf("t2.i = %d, t2.j = %dn", t2.getI(), t2.getJ()); return 0; }
但是編譯的時候會報錯:
這是因為在類裡面沒有編寫任何建構函式時,編譯器才提供預設的無參建構函式。這裡手工編寫了一個拷貝建構函式,編譯器就不會提供預設的無參建構函式,需要自己把無參建構函式加上。
如下,自己加上無參建構函式(程式碼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 = %dn", t1.getI(), t1.getJ()); printf("t2.i = %d, t2.j = %dn", t2.getI(), t2.getJ()); return 0; }
這樣就能編譯通過了,而且效果跟程式碼3-3的相同:
拷貝建構函式的意義
相容C語言的初始化方式
初始化行為能夠符合預期的邏輯
淺拷貝
深拷貝
注:編譯器提供的拷貝建構函式只進行淺拷貝!
下面看一段程式碼(程式碼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 = %pn", t1.getI(), t1.getJ(), t1.getP()); printf("t2.i = %d, t2.j = %d, t2.p = %pn", t2.getI(), t2.getJ(), t2.getP()); return 0; }
下面為輸出結果:
這段程式的第一個問題就是 t1 和 t2 的 p 指標都指向同一個堆空間中的地址,第二個問題就是申請了記憶體並沒有釋放,會造成記憶體漏失。
下面加上釋放記憶體的程式碼(程式碼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 = %pn", t1.getI(), t1.getJ(), t1.getP()); printf("t2.i = %d, t2.j = %d, t2.p = %pn", t2.getI(), t2.getJ(), t2.getP()); t1.free(); t2.free(); return 0; }
下面為輸出結果,編譯能通過,但是執行時發生了錯誤,釋放了兩次堆空間的記憶體:
下面為解決方法(程式碼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 = %pn", t1.getI(), t1.getJ(), t1.getP()); printf("t2.i = %d, t2.j = %d, t2.p = %pn", t2.getI(), t2.getJ(), t2.getP()); t1.free(); t2.free(); return 0; }
下面為輸出結果,可以到 t1 和 t2 的 p 指標分別指向不同的堆空間地址:
如果我們看一下邏輯狀態,也就是 *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 = %pn", t1.getI(), t1.getJ(), t1.getP()); printf("t2.i = %d, t2.j = %d, t2.p = %pn", t2.getI(), t2.getJ(), t2.getP()); t1.free(); t2.free(); return 0; }
下面為輸出結果,可以看到 *t1.p 和 *t2.p 的值相同,也就是說邏輯狀態相同,這就叫做深拷貝。
什麼時候需要進行深拷貝?
物件中有成員指代了系統中的資源
問題分析
下面就是淺拷貝:
一般性原則
自定義拷貝建構函式,必然需要實現深拷貝!!!
下面看一個使用深拷貝,對前面陣列的程式碼進行改造。
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] = %dn", i, value); } } IntArray b = a; for (int i = 0; i < b.length(); i++) { int value = 0; if( b.get(i, value) ) { printf("b[%d] = %dn", i, value); } } a.free(); b.free(); return 0; }
下面為輸出結果:
可以看到 b 陣列裡面的元素與 a 陣列裡面的元素相同,這就是深拷貝建構函式的結果。
C++ 編譯器會預設提供建構函式
無參建構函式用於定義物件的預設初始狀態
拷貝建構函式在建立物件時拷貝物件的狀態
物件的拷貝有淺拷貝和深拷貝兩種方式
到此這篇關於C++詳細講解物件的構造的文章就介紹到這了,更多相關C++ 物件的構造內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45