1. 程式人生 > >初學C++——鄭莉老師的課 第四章 建構函式/委託建構函式/拷貝建構函式(深拷貝/淺拷貝)

初學C++——鄭莉老師的課 第四章 建構函式/委託建構函式/拷貝建構函式(深拷貝/淺拷貝)

建構函式——用於初始化物件

函式名與類名相同,不能有返回值型別,可以有形式引數,也可以沒有形式引數,可以是inline函式,可以過載,可以帶預設引數值。

在物件建立時自動呼叫。如: Clock myClock(0,0,0);

預設建構函式(default constructor):呼叫時可以不需要實參的建構函式。有以下兩種:

1. 引數表為空的建構函式

2. 全部引數都有預設值的建構函式。

clcok();
clock(int newH=0, int newM=0, int newS=0);
//這兩個都是預設建構函式,如果在類內同時出現,將會產生編譯錯誤

如果程式中已經定義了建構函式,預設情況下編譯器不再隱含生成建構函式,如果此時希望那個編譯器隱含生成建構函式可以使用“=default”

class Clock
{
public:
	Clock() = default;//指示編譯器依然提供預設建構函式
	Clock(int newH, int newM, int newS); //建構函式
private:
	int hour,minute,second;
};

建構函式的例項:

#include<iostream>
using namespace std;

class Clock
{
public:
	Clock(int newH, int newM, int newS);//建構函式
	void setTime(int newH, int newM, int newS);//當呼叫這個函式時,如果給了實參
	                                                 //就用實參值,如果沒給,就用預設引數值(0,0,0)    
	void showTime();

private:
	int hour,minute,second;
};

//建構函式的實現,優先選初始化列表的方式初始化
Clock::Clock(int newH,int newM,int newS):hour(newH),minute(newM),second(newS){}

int main()
{
	Clock c(0, 0, 0);//自動呼叫建構函式
	c.showTime();
	return 0;
}

委託建構函式:在一個類中過載多個建構函式時,這些函式只是形參不同,初始化列表不同,而初始化演算法和函式體都是相同的。這個時候,為了避免重複,C++11新標準提供了委託建構函式。更重要的是,可以保持程式碼的一致性,如果以後要修改建構函式的程式碼,只需要在一處修改即可。

Clock::Clock(int newH, int newM, int newS)::hour(newH), minute(newM), second(newS) {}//建構函式
Clock::Clock():hour(0),minute(0),second(0){}//預設建構函式

改為:

Clock::Clock(int newH, int newM, int newS)::hour(newH), minute(newM), second(newS) {}//建構函式
Clock::Clock(0, 0, 0) {}//預設建構函式

拷貝建構函式

  • 拷貝建構函式是一種特殊的建構函式,其形參為本類物件的引用。
  • 作用:用一個已經存在的物件去初始化同類型的新物件。
class 類名
{

public:

	類名(形參);//建構函式

		類名(const 類名 &物件名); //拷貝建構函式

//引數前用const限定,因為傳遞引用作為引數時,實際上是可以雙向傳遞資料,
//也就是說接受這個引用引數的函式,如果在函式體中對這個引用做了任何修改,那麼實參也會被同步修改,
//顯然不是拷貝建構函式的目的,
//所以,加一個const關鍵字,說明這個引用是一個常引用,只能用這個引用讀取它裡面的資料,不能用這個引用對它指向的物件進行修改。
//這樣既能夠傳引數進來,又能保證實參的安全性

};

類名::類(const 類名 &物件名)//拷貝建構函式的實現,不允許定義返回值
{ 函式體 } // 函式體中不允許有return語句。

三種典型情況需要呼叫拷貝建構函式:

  1. 定義一個物件時,用本類的另一個物件作為初始值,發生拷貝構造;
  2. 如果函式的形參是類的物件,呼叫函式時,將使用實參物件初始化形參物件,發生拷貝構造;
  3. 如果函式的返回值是類的物件時,函式執行完成返回主函式時,將使用return語句中的物件初始化一個臨時無名物件,傳遞給主調函式,此時發生拷貝構造。

隱含的拷貝建構函式:(寫簡單程式時,預設的拷貝建構函式就夠用挺好用的)

  1. 當程式設計師餓沒有定義拷貝建構函式,編譯器會為我們生成一個預設的拷貝建構函式。
  2. 它執行的功能:用初始值物件的每個資料成員,初始化將要建立的物件的對應資料成員。

(當類的成員有指標時,淺拷貝就不夠用了,就要用到深拷貝了。)

   當物件不想被複制構造時,用“=delete"只是編譯器不生成預設的拷貝建構函式。

class Point //Point類定義 
{
public: //外部介面
	Point(int x = 0, int y = 0) :x(x), y(y) {} //建構函式
	Point(const Point &p)=delete;//指示編譯器不生成預設拷貝建構函式
	int getX() { return x; }
	int getY() { return y; }
private:
}

例子:

#include<iostream>
#include<cmath>
using namespace std;


class Point //Point類定義 
{
public: //外部介面
	Point(int x = 0, int y = 0) :x(x), y(y) {} //建構函式
	Point(const Point &p);
	int getX() { return x; }
	int getY() { return y; }
private: //私有資料成員
	int x, y;
};

//成員函式的實現
Point::Point(const Point &p)
{
	x = p.x;
	y = p.y;
	cout << "calling the copy constructor" << endl; //作為例題,看到流程經過了哪些地方,也叫除錯資訊。
}
//形參為Point類物件的函式
void fun1(Point p)
{
	cout << p.getX() << endl;

}

//返回值為Point類物件的函式
Point fun2()
{
	Point a;
	return a;

}

int main()
{
	
	Point a;
	Point b(a);//情況一:用物件a初始化物件b,第一次呼叫拷貝建構函式
	cout << b.getX() << endl;
	fun1(b);//情況二:物件b作為fun1的實參,第二次呼叫拷貝建構函式。先呼叫Point類的拷貝建構函式,再呼叫fun1()
	b = fun2(); //情況三,函式返回值為類物件,函式返回時,即在呼叫fun2(),執行到返回a這條語句時,
	         //第三次呼叫拷貝建構函式。而給b賦值時,不會呼叫拷貝建構函式
	         //賦值實際上隱含一個賦值運算子函式來完成的。
	cout << b.getX() << endl;
	while (1);
	return 0;
}

深拷貝/淺拷貝

當類的資料成員有指標或者為有動態成員存在時,而指標指向的空間是在構造物件時通過動態分配記憶體獲得的空間。這時,要對物件進行拷貝構造,還能用淺拷貝嗎?(不可以)

#include<iostream>
using namespace std;

class Rect
{
public:
    Rect()
    {
     count++;
    }
    ~Rect()
    {
     count--;
    }
    static int getCount()
    {
     return count;
    }
private:
    int width;
    int height;
    static int count;
};

int Rect::count=0;
int main()
{
    Rect rect1;
    cout<<"The count of Rect:"<<Rect::getCount()<<endl;
    Rect rect2(rect1);
    cout<<"The count of Rect:"<<Rect::getCount()<<endl;
    return 0;
}

按道理來說,此時應該有兩個物件,但是結果顯示只有一個物件,其實,就是預設拷貝建構函式沒有處理靜態資料成員

#include<iostream>
using namespace std;

class Rect
{
public:
    Rect()
    {
        count++;
    }
    Rect(const Rect& r) //自己寫的拷貝建構函式
   {
        width=r.width;
        height=r.height;
        count++;
    }
    ~Rect()
    {
        count--;
    }
    static int getCount()
    {
        return count;
    }
private:
    int width;
    int height;
    static int count;
};

int Rect::count=0;
int main()
{
    Rect rect1;
    cout<<"The count of Rect:"<<Rect::getCount()<<endl;
    Rect rect2(rect1);
    cout<<"The count of Rect:"<<Rect::getCount()<<endl;
    return 0;
}

      所謂淺拷貝,指的是在物件複製時,只對物件中的資料成員進行簡單的複製,預設拷貝函式執行的就是淺拷貝,多數情況,淺拷貝可以很好完成工作了,但一旦物件中存在動態成員,那麼就得用深拷貝了。比如以下:

  • 淺拷貝(預設拷貝建構函式)
#include<iostream>
#include<assert.h>
using namespace std;

class Rect
{
public:
    Rect()
    {
     p=new int(100); //動態獲取記憶體
    }
   
    ~Rect()
    {
     assert(p!=NULL);
        delete p;
    }

private:
    int width;
    int height;
    int *p;
};


int main()
{
    Rect rect1;
    Rect rect2(rect1);
    return 0;
}

程式執行時,報錯:


出錯的原因是在進行復制時候,對於動態分配的內容沒有進行正確的操作。


      即在淺拷貝時,將兩個指標指向同一個空間,但是在delete銷燬物件時,該空間會被釋放兩次,所以程式就報錯了。在此,我們需要的不是兩個指標有相同的值,而是兩個指向不同地址空間相同的值,也就是要用到“深拷貝”。

  • 深拷貝

   對於物件中的動態成員,就不能簡單賦值了,而是要重新動態分配空間,改一下上面的程式程式碼。

#include<iostream>
#include<assert.h>
using namespace std;

class Rect
{
public:
    Rect()
    {
     p=new int(100);
    }
    
    Rect(const Rect& r)//新增的拷貝建構函式
    {
     width=r.width;
        height=r.height;
     p=new int(100);
        *p=*(r.p);
    }
     
    ~Rect()
    {
     assert(p!=NULL);
        delete p;
    }

private:
    int width;
    int height;
    int *p;
};


int main()
{
    Rect rect1;
    Rect rect2(rect1);
    return 0;
}

成功執行。


深拷貝和淺拷貝的區別在於:深拷貝會在堆記憶體中另外


相關推薦

初學C++——老師 建構函式/委託建構函式/拷貝建構函式拷貝/拷貝

建構函式——用於初始化物件函式名與類名相同,不能有返回值型別,可以有形式引數,也可以沒有形式引數,可以是inline函式,可以過載,可以帶預設引數值。在物件建立時自動呼叫。如: Clock myClock(0,0,0);預設建構函式(default constructor):

the c programming language second edition 函式與程式結構筆記及練習題中

the c programming language second edition 第四章函式與程式結構筆記 4.3外部變數 C語言程式可以看成由一系列的外部物件構成,這些外部物件可能是變數或函式 外部變數和函式具有以下性質:通過同一個名字對外部變數的所有引

the c programming language second edition 函式與程式結構筆記及練習題上

the c programming language second edition 第四章函式與程式結構筆記 4.1函式的基本認識 編寫一個程式它將輸入中包含特定模式或字串的各行打印出來。 該任務可以明確地劃分成下列3部分: while(未處理的行) if

C++ primer 學習筆記(到)

1、計算機內部實現過程有別,詳細如下:<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /> i=i+1的過程相當:   temp=i+1; i=temp; i++的過程相當:   temp=i; 

C語言程式設計入門之--C語言基本資料型別

    導讀:C語言程式中經常涉及一些數學計算,所以要熟悉其基本的資料型別。資料型別學習起來比較枯燥,不過結合之前的記憶體概念,以及本節的位元組概念,相信資料型別也就不難理解了。本章從二進位制的基本概念開始,然後介紹機器語言通用的計算單位位元組,最後再介紹C語言中基本的資料型別及其基本概念。 &

程式設計師程式設計藝術----------現場編寫類似strstr/strcpy/strpbrk的函式

               第四章、現場編寫類似strstr/strcpy/strpbrk的函式   作者:July。    說明: 如果在部落格中程式碼使用了\n,csdn blog系統將會自動回給我變成/n。據後續驗證,可能是原來舊blog版本的bug,新版已不存在此問題。至於,本文程式碼,日後統一修正

Elixir超程式設計- 能力越大,責任也越大樂趣也越大

Elixir超程式設計-第六章 能力越大,責任也越大(樂趣也越大) 我們已經揭開了 Elixir 超程式設計的神祕面紗。我們從基礎

C++對象模型——Inline Functions()

優化 tor tracking 改善 pan c++ col ria 表達式 4.5 Inline Functions 以下是Point class 的一個加法運算符的可能實現內容: class Point { friend Point operato

OO先導——次上課

style 結果 內容 blog alt 測試性能 img log ges 上課內容:測試正確率;測試性能 結果: 教訓:能用類庫的就用類庫!自己寫的很難快起來!OO先導課——第四次上課

c++primer 編程練習答案

float enter put rand out har lin score ring 4.13.1 #include<iostream> struct students { char firstname[20]; char lastname

C++ 個人銀行賬戶管理程序案例】

with count acc cpp name c++ money 建立 esc 【第四章】 個人銀行賬戶管理程序 案例實現 #include<iostream> #include<cmath> using namespace std; clas

C++程序設計語言(英文版)》【PDF】下載

files com gpo spa stat per const read 編程 《C++程序設計語言(英文第四版)》【PDF】下載鏈接: https://u253469.pipipan.com/fs/253469-230382177 內容簡介 本書是C++領域經

c語言-條件結構

case 數值 運算符和 替代 替代品 OS 運算符 結構 div 表達式:由運算符和操作數組成賦值運算符:=多分枝選擇結構if else if else if else嵌套結構if(){ if(){ }}switch case 結構defaul

-講05_04_bash腳本編程之三 條件判斷及算術運算

ash 如果 寫一個腳本 字符 命令引用 是否 練習 bash腳本 [] 第五課-第四講05_04_bash腳本編程之三 條件判斷及算術運算 練習:寫一個腳本,判斷當前系統上是否有用戶的默認shell為bash:如果有,就顯示有多少個這類用戶,否則,就顯示沒有這類用戶 bc

-講 07_04_特殊權限SUID等詳解

人的 大寫 backup 就是 取值 特殊權限 詳解 -- 執行 第七課-第四講 07_04_特殊權限SUID等詳解 一. 特殊權限 SUID: 運行某程序時,相應進程的屬主是該程序文件自身的屬主,而不是啟動者chmod u+s filechmod u-s file文件本

C語言每日一練——

圖片 數據文件 () inf fop dat文件 print 數組a open 一、題目要求 已知數據文件in.dat中有300個四位數,並調用readDat()函數把這些數存儲數組a中,編寫函數jsValue(),其功能是:求出所有這些四位數是素數的個數cnt,再把所有滿

CLR via C#學習筆記--類型基礎-命名空間和程序集

程序集 microsoft 集中 歧義 str 可能 ring 需要 idg 4.3 命名空間和程序集 使用using指令簡化命名空間 C#編譯器通過using指令提供這個機制,例如 using System.IO; using System.Text; 只需要在代碼中

CLR via C#學習筆記--類型基礎-所有類型都從System.Object派生

回收 spa 操作 哈希 包括 生成 自動生成 返回 equals 4.1 所有類型都從System.Object派生 System.Object類型 運行時要求每個類型最終都從System.Object類型派生 也就是說,以下兩個類型定義完全一樣 //隱式派生自Syst

CLR via C#學習筆記--類型基礎-運行時的相互關系

分配內存 ring type類 實現 語句 初始化 sem strong 允許 4.4 運行時的相互關系 已加載CLR的一個Windows進程,該進程可能有多個線程。線程創建時會分到1MB的棧。棧空間用於向方法傳遞實參,方法內部定義的局部變量也在棧上。 以下是方法M1和M2

C#本質論6.0:方法和參數

有助於 異常 名稱 別名 不同的 元素 寫入 轉換 參數順序 方法和參數 方法組合一系列語句以執行特定操作或計算特定結果,它能夠為構成程序的語句提供更好的結構和組織。 方法總是和類型——通常是類關聯。 方法通過實參接收數據,實參由方法的參數或形參定義,參數是調用者用於向被