第一篇:拷貝構造函數剖析
拷貝構造函數剖析
在講課過程中,我發現大部分學生對拷貝構造函數的理解不夠深入,不明白自定義拷貝構造函數的必要性。因此,我將這部分內容,進行了總結。
拷貝構造函數是一種特殊的構造函數,其形參為本類的對象引用。功能:使用一個已經存在的對象始初化同類的一個新對象。這樣得到對象和原來的對象具有完全相同的數據成員,即相同的屬性。
拷貝構造函數的函數原型:
A(const A& other){ … … }
拷貝構造函數的應用場合:
當用類的一個對象去初始化該類的另一個對象時;若函數的形參為類對象,調用函數時,實參賦值給形參;當函數的返回值是類對象時。比如:
A a1(10);
A a2 = a1;
A a3(a1);// 構造函數 // 拷貝構造函數 // 拷貝構造函數
默認拷貝構造函數:成員變量之間的“值”拷貝
編寫拷貝構造函數的必要性
class A
{
public:
A(const char* data)
{
name = new char[strlen(data)+ 1];
strcpy(name, data);
}
A(const A& other)
{
name = new char[strlen(other.name)+ 1];
strcpy(name, other.name);
}
private:
char* name;
};
考察:char* data = “abcd”;A a1(data);A a2 = a1;
如果未定義拷貝構造函數,會有何種后果?
現將a1賦給a2,缺省拷貝構造函數的“位拷貝”意味著執行a2.name = a1.name。這將造成二個錯誤:一是a2.name和a1.name指向同一塊內存,任何一方變動都會影響另一方;二是在對象被析構時,name被釋放了兩次。
第二篇:拷貝構造函數和賦值函數的區別
拷貝構造函數和賦值函數的區別
一個類會默認生成它的string()//默認普通構造函數
void string(const string &a)//默認拷貝構造函數,如果自己不實現,會用這個默認的//采用“位拷貝”的方式,對有成員指針的情況,一定有
//問 題, 因為“位拷貝”,指向同一地址空間,自己//實現,改成“值拷貝”
~stirng()//默認析構函數
const string& operation=(const string &a)//默認賦值函數,如果自己不實現,//默認的也是采用“位拷貝”的方式
“位拷貝”,string a(b);
除了a,b對象的地址不一樣,但a,b成員對象都指向的同一地址空間。如果delete a, 就會刪掉b的內容,所以位拷貝,對有指針成員變量的類,非常危險
拷貝構造函數發生的例子:
Aa(1);//調用構造函數,Ab(a);//調用拷貝構造函數
Ac=a;//第一次賦值,因為對象還沒初始化,還是調用拷貝構造函數,c=b;//已經初始化的對象才能調用賦值函數
voidf(Aa);//函數聲明
f(c);//實參傳遞時調用拷貝構造函數,但是編譯器會根據具體情況把這個過程優化掉
總結:
1.拷貝構造函數只有在定義一個新的類對象并且用已有的對象進行初始化時調用.2.賦值函數只有在已經初始化(對象已定義)的情況下被調用
例如有一個類叫做‘myclass',并有一個實例:b
那么,myclassa=b;//拷貝構造函數
myclassa;
a=b;//賦值
兩者調用的時間不一樣,第一種情況下,a此時還沒有被分配空間,在扶植的同時還要生成資源;
第二種情況下,a一構造完成,已經有了資源,所以此時等號只進行賦值。
第三篇:不要輕視拷貝構造函數與賦值函數
不要輕視拷貝構造函數與賦值函數
由于并非所有的對象都會使用拷貝構造函數和賦值函數,程序員可能對這兩個函數有些輕視。請先記住以下的警告,在閱讀正文時就會多心:
? 本章開頭講過,如果不主動編寫拷貝構造函數和賦值函數,編譯器將以“位拷貝”的方式自動生成缺省的函數。倘若類中含有指針變量,那么這兩個缺省的函數就隱含了錯誤。以類String的兩個對象a,b為例,假設a.m_data的內容為“hello”,b.m_data的內容為“world”。
現將a賦給b,缺省賦值函數的“位拷貝”意味著執行b.m_data = a.m_data。這將造成三個錯誤:一是b.m_data原有的內存沒被釋放,造成內存泄露;二是b.m_data和a.m_data指向同一塊內存,a或b任何一方變動都會影響另一方;三是在對象被析構時,m_data被釋放了兩次。
? 拷貝構造函數和賦值函數非常容易混淆,常導致錯寫 錯用。拷貝構造函數是在對象被創建時調用的,而賦值函數只能被已經存在了的對象調用。以下程序中,第三個語句和第四個語句很相似,你分得清楚哪個調用了拷貝構造函數,哪個調用了賦值函數嗎? Stringa(“hello”);
Stringb(“world”);
Stringc = a;// 調用了拷貝構造函數,最好寫成 c(a);
c = b;// 調用了賦值函數
本例中第三個語句的風格較差,宜改寫成String c(a)以區別于第四個語句。
第四篇:拷貝構造函數的參數類型必須是引用
拷貝構造函數的參數類型必須是引用
在C++中,構造函數,拷貝構造函數,析構函數和賦值函數(賦值運算符重載)是最基本不過的需要掌握的知識。但是如果我問你“拷貝構造函數的參數為什么必須使用引用類型?”這個問題,你會怎么回答? 或許你會回答為了減少一次內存拷貝? 很慚愧的是,我的第一感覺也是這么回答。不過還好,我思索一下以后,發現這個答案是不對的。
原因:
如果拷貝構造函數中的參數不是一個引用,即形如CClass(const CClass c_class),那么就相當于采用了傳值的方式(pass-by-value),而傳值的方式會調用該類的拷貝構造函數,從而造成無窮遞歸地調用拷貝構造函數。因此拷貝構造函數的參數必須是一個引用。
需要澄清的是,傳指針其實也是傳值,如果上面的拷貝構造函數寫成CClass(const CClass* c_class),也是不行的。事實上,只有傳引用不是傳值外,其他所有的傳遞方式都是傳值。
先從一個小例子開始:(自己測試一下自己看看這個程序的輸出是什么?)
[cpp] view plain copy #include
using namespace std;
class CExample
{
private:
int m_nTest;
public:
CExample(int x): m_nTest(x)
//帶參數構造函數
{
cout << “constructor with argument”< } // 拷貝構造函數,參數中的const不是嚴格必須的,但引用符號是必須的 CExample(const CExample & ex) //拷貝構造函數 { m_nTest = ex.m_nTest; cout << “copy constructor”< } CExample& operator =(const CExample &ex) //賦值函數(賦值運算符重載) { cout << “assignment operator”< m_nTest = ex.m_nTest; return *this; } void myTestFunc(CExample ex) { } }; int main(void) { CExample aaa(2); CExample bbb(3); bbb = aaa; CExample ccc = aaa; bbb.myTestFunc(aaa); return 0; } 這個例子的輸出結果是: [cpp] view plain copy constructor with argument // CExample aaa(2); constructor with argument // CExample bbb(3); assignment operator // bbb = aaa; copy constructor // CExample ccc = aaa; copy constructor // bbb.myTestFunc(aaa); 如果你能一眼看出就是這個結果的話,恭喜你,可以站起來扭扭屁股,不用再往下看了。 如果你的結果和輸出結果有誤差,那拜托你謙虛的看完。 第一個輸出: constructor with argument // CExample aaa(2); 如果你不理解的話,找個人把你拖出去痛打一頓,然后嘴里還喊著“我是二師兄,我是二師兄.......” 第二個輸出:constructor with argument // CExample bbb(3); 分析同第一個 第三個輸出: assignment operator // bbb = aaa; 第四個輸出: copy constructor // CExample ccc = aaa; 這兩個得放到一塊說。肯定會有人問為什么兩個不一致。原因是,bbb對象已經實例化了,不需要構造,此時只是將aaa賦值給bbb,只會調用賦值函數,就這么簡單,還不懂的話,撞墻去!但是ccc還沒有實例化,因此調用的是拷貝構造函數,構造出ccc,而不是賦值函數,還不懂的話,我撞墻去! 第五個輸出: copy constructor // bbb.myTestFunc(aaa); 實際上是aaa作為參數傳遞給bbb.myTestFunc(CExample ex),即CExample ex = aaa;和第四個一致的,所以還是拷貝構造函數,而不是賦值函數,如果仍然不懂,我的頭剛才已經流血了,不要再讓我撞了,你就自己使勁的再裝一次吧。 通過這個例子,我們來分析一下為什么拷貝構造函數的參數只能使用引用類型。 看第四個輸出: copy constructor // CExample ccc = aaa; 構造ccc,實質上是ccc.CExample(aaa);我們假如拷貝構造函數參數不是引用類型的話,那么將使得 ccc.CExample(aaa)變成aaa傳值給ccc.CExample(CExample ex),即CExample ex = aaa,因為 ex 沒有被初始化,所以 CExample ex = aaa 繼續調用拷貝構造函數,接下來的是構造ex,也就是 ex.CExample(aaa),必然又會有aaa傳給CExample(CExample ex), 即 CExample ex = aaa;那么又會觸發拷貝構造函數,就這下永遠的遞歸下去。 所以繞了那么大的彎子,就是想說明拷貝構造函數的參數使用引用類型不是為了減少一次內存拷貝,而是避免拷貝構造函數無限制的遞歸下去。 附帶說明,在下面幾種情況下會調用拷貝構造函數: a、顯式或隱式地用同類型的一個對象來初始化另外一個對象。如上例中,用對象c初始化d; b、作為實參(argument)傳遞給一個函數。如CClass(const CClass c_class)中,就會調用CClass的拷貝構造函數; c、在函數體內返回一個對象時,也會調用返回值類型的拷貝構造函數; d、初始化序列容器中的元素時。比如 vector e、用列表的方式初始化數組元素時。string a[] = {string(“hello”), string(“world”)};會調用string的拷貝構造函數。 如果在沒有顯式聲明構造函數的情況下,編譯器都會為一個類合成一個缺省的構造函數。如果在一個類中聲明了一個構造函數,那么就會阻止編譯器為該類合成缺省的構造函數。和構造函數不同的是,即便定義了其他構造函數(但沒有定義拷貝構造函數),編譯器總是會為我們合成一個拷貝構造函數。 另外函數的返回值是不是引用也有很大的區別,返回的不是引用的時候,只是一個簡單的對象,此時需要調用拷貝構造函數,否則,如果是引用的話就不需要調用拷貝構造函數。[cpp] view plain copy #include using namespace std; class A { private: int m_nTest; public: A() { } A(const A& other) //構造函數重載 { m_nTest = other.m_nTest; cout << “copy constructor”< } A & operator =(const A& other) { if(this!= &other) { m_nTest = other.m_nTest; cout<<“Copy Assign”< } return *this; } }; A fun(A &x) { return x; //返回的不是引用的時候,需要調用拷貝構造函數 } int main(void) { A test; fun(test); system(“pause”); return 0; } 分享一道筆試題目,編譯運行下圖中的C++代碼,結果是什么?(A)編譯錯誤;(B)編譯成功,運行時程序崩潰;(C)編譯運行正常,輸出10。請選擇正確答案并分析原因。 [cpp] view plain copy class A { private: int value; public: A(int n) { value = n; } A(A other) { value = other.value; } void Print() { cout< } }; int main(void) { A a = 10; A b = a; b.Print(); return 0; } 答案:編譯錯誤。在復制構造函數中傳入的參數是A的一個實例。由于是傳值,把形參拷貝到實參會調用復制構造函數。因此如果允許復制構造函數傳值,那么會形成永無休止的遞歸并造成棧溢出。因此C++的標準不允許復制構造函數傳值參數,而必須是傳引用或者常量引用。在Visual Studio和GCC中,都將編譯出錯。 構造函數 1.設 f(x),g(x)分別為定義在R上的奇函數和偶函數,當x?0時,f?(x)g(x)?f(x)g?(x)?0,且g(?3)?0,則不等式f(x)g(x)?0的解集為______.2.設f(x)是定義在R上的奇函數,且f(2)?0,當x?0時,有x? f?(x)?f(x)?0 恒成立,則不等式x2f(x)?0的解集為__________.3.已知函數f(x)是定義在R上的奇函數,且當x?(??,0)時,有x?<0成立,若a?30.3? b f?(x)+f(x)1 3f(3 0.3),b??log?3?? f(log ? 3),c?(log 9)?f(log 9),則a、、c的大小關系為__________.f(x),則當a?0 4.已知可導函數f(x)滿足f?(x)?系為__________.時,f(a)與ea? f(0)的大小關 5.若函數f(x)對任意的x?R都有f?(x)? A.3f(ln2)?2f(ln3) f(x) 成立,則__________.B.3f(ln2)?2f(ln3) C.3f(ln2)?2f(ln3)D.3f(ln2)與2f(ln3)的大小關系不確定 6.設f(x)是R上的奇函數,且f(?1)?0,當x?0時,(x2 ?1)?f?(x)?2x?f(x)?0,則不等式f(x)?0的解集為__________.7.已知函數f(x)是定義在(0,??)的非負可導函數,且滿足x?對任意正數a、b,若a f?(x)+f(x)?0,B.af(b)?bf(a)C.af(a)?f(b) D.bf(b)?f(a),8.已知f(x)與g(x)都是定義在R上的函數,g(x)?0,f?(x)g(x)? f(x)?a?g(x),x f(x)g?(x)?0 f(1)g(1) ? f(?1)g(?1) ? .在有窮數列? ?f(n)? ?(n?1,2,?,10)中,前kg(n)?? 項和 為 1516,則k=__________.第五篇:構造函數