1. 程式人生 > >【C++基礎】類的預設成員函式的幾種呼叫方式

【C++基礎】類的預設成員函式的幾種呼叫方式

先寫一個日期類,把建構函式,拷貝建構函式,解構函式,賦值運算子的過載都寫上,程式碼如下:

class Date{
public:
	//建構函式
	Date() {
		cout << "Date()" << endl;
	}
	//拷貝建構函式
	Date (const Date & d) {
		cout << "Date(cont Date& d)" << endl;
	}
	//解構函式
	~Date() {
		cout << "~Date" << endl;
	}
	//賦值運算子過載
	Date& operator=(const Date& d) {
		cout << "operator=" << endl;
		return *this;
	}
private:
	int _year;
	int _month;
	int _day;
};

注意,函式裡面都只是輸出了一個字串,用來顯示呼叫情況

建構函式是建立一個物件的時候呼叫的,如果沒有寫,編譯器會自動生成一個預設的建構函式,例:

Date d;

建立一個物件,它會呼叫一次建構函式,如圖:

拷貝建構函式也是建構函式,它是在一個物件被建立的時候呼叫,有以下兩種使用方式:

Date d1;
Date d2 = d1;
Date d3(d1);

接下來看一種特殊的情況:

Date();
這種情況,是產生了一個匿名的物件,會呼叫一次建構函式和解構函式,它的生命週期只有這一行,如圖:

它被構造之後立即就被銷燬了

接下來看這種情況:

Date d = Date();

有了先前的分析,應該是一次建構函式,一次拷貝建構函式,是真正 的結果如圖:

只有一次建構函式,並沒有拷貝建構函式

注意,這裡是編譯器優化的效果,首先這裡直接建立了一個Date(),這是一個匿名的物件,然後要進行拷貝構造的時候編譯器進行了優化

為了測試正確與否,我們先改一下建構函式和拷貝建構函式,程式碼如下:

	//建構函式
	Date(int year = 1800, int month = 1, int day = 1) {
        cout << "Date()" << endl;
        _year = year;
        _month = month;
        _day = day;
    }
	//拷貝建構函式
	Date (const Date & d) {
		cout << "Date(cont Date& d)" << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;
 }

再更改一下測試用例:

Date d3 = Date(1,1,1);



如圖我們發現,這個表示式呼叫 了Date(1,1,1),然後並沒有再呼叫拷貝建構函式,但是,d3還是和這個匿名的物件值一樣, 所以,編譯器的優化是這樣的:直接讓d3成為這個匿名的物件。

什麼意思呢?相當於給匿名物件起了個名字叫d3

這就是編譯器的一種優化

注意,我們說過拷貝建構函式呼叫有兩種方式:

Date d1;
Date d2 = d1;
Date d3(d1);

但是在這裡,雖然Date d = Date();編譯器是優化了的,但是另一種方式是行不通的!

像這樣:

Date d3(Date());

為什麼呢?我也不知道~反正vs2013的編譯器是不認的,建構函式拷貝建構函式都沒有呼叫


d3直接就沒有建立成功

好了~不如我們把這些歸咎於編譯器吧~反正也不知道為啥

但是,這樣是可以建立成功的:


為什麼?別問我,我不知道,誰要是知道了可以評論下

接下來我們定義兩個函式,進一步去測試

void fun1(Date d)
{}

Date func2()
{
    Date ret;
    return ret;
}
場景一:
Date d1;
fun1(d1);
建立d1的時候呼叫一次構造,然後fun1函式傳參的時候呼叫一次拷貝構造,然後fun1函式推出的時候呼叫一次解構函式


場景二:

Date d2 = func2();

函式在返回值的時候,會有一個臨時變數,如圖:

像這樣,所以應該是ret呼叫一次建構函式,臨時變數呼叫一次拷貝構造,然後ret再呼叫一次解構函式,然後臨時變數賦值給d2的時候再呼叫一次拷貝建構函式

但是,結果卻不是這樣的,因為編譯器優化了,如圖:

編譯器優化後,沒有臨時變數的拷貝構造了,如圖:

好了,接下來看場景三:

Date d3;
d3 = func2();

細細一看,這個好像並沒有優化,為什麼呢?

因為d3已經被建立好了,無法進行優化

我們改一下fun2函式,程式碼如下:

Date func2()
{
	return Date();
}

Date()這個是不是似曾相識~

好了我們來測試一下,場景一:

Date d2 = func2();

結果如下:

只有一次建構函式???

如果認真看了上面的例子,這個例子肯定很簡單了,編譯器會優化,沒有臨時變數的拷貝,如圖:

這個不就是Date d2 = Date();

場景二:

Date d3;

d3 = func2();

如圖,d3一次建構函式,Date()一次建構函式,因為Date()是匿名的物件而且剛建立就要返回,所以這裡就直接優化了

呼叫方式基本就講完了,附上一個題:

class AA
{
public:
	AA()
	{
	}
	AA(const AA& a)
	{
		cout<<"AA(const AA& a)"<<endl;
	}
};
AA f (AA a)
{
	return a ;
}
void Test1 ()
{
	AA a1 ;
	a1 = f(a1);
}
void Test2 ()
{
	AA a1 ;
	AA a2 = f(a1);
}

void Test3 ()
{
	AA a1 ;
	AA a2 = f(f(a1));
}

程式碼如上,問:所有的Test函式總共呼叫了幾次建構函式,幾次拷貝建構函式,幾次解構函式

首先來看Test1();

一次建構函式,兩次拷貝構造,一次賦值運算子的過載,過程如下圖:

接下來看Test2();

一次建構函式,一次拷貝建構函式,零次賦值運算子的過載

因為這裡物件a2是新建立的,所以編譯器會進行優化,如下圖:

接下來看最難的Test3();程式碼如下:

void Test3 ()
{
	AA a1 ;
	AA a2 = f(f(a1));
}

可以看到這裡有一個巢狀,我們先一步一步分析

顯示a1拷貝給函式f的形參a,然後返回a,返回a的時候有個臨時變數tmp(假設),然後a拷貝給tmp,tmp再拷貝給外面的函式的形參a,這個a和上面做同樣的動作,思路分析如圖:

執行一下看結果圖:

結果是隻有三次拷貝建構函式,和上面一樣,編譯器肯定進行了優化

優化的情況下肯定不需要臨時變量了,思路如圖:

完!

相關推薦

C++基礎預設成員函式呼叫方式

先寫一個日期類,把建構函式,拷貝建構函式,解構函式,賦值運算子的過載都寫上,程式碼如下:class Date{ public: //建構函式 Date() { cout << "Date()" << endl; } //拷貝建構函式 Da

C++基礎的組合

所謂類的組合是指:類中的成員資料是另一個類的物件或者是另一個類的指標或引用。通過類的組合可以在已有的抽象的基礎上實現更復雜的抽象。 例如: 1、按值組合 #include<iostream.h> #include<math.h> class P

Java基礎-多重For迴圈的兩跳出方式

先來小段Demo,自己跑一下就能看到效果了: public static void main(String[] args) { List<String> listA = new ArrayList<String>(); List<String> lis

C++成員函式聲明後面接 const

const 表示對類中成員函式屬性的宣告; 表示不會修改類中的資料成員; 在編寫const成員函式時,若不慎修改了資料成員,或者呼叫了其他非const成員函式,編譯器將指出錯誤; 以下程式中,類stack的成員函式GetCount僅用於計數,從邏輯上講GetCount應

C++初學和物件定義與建構函式

一.類和物件定義 class 型別名 { public: 公有成員(外部介面) private: 私有成員(只允許類內函式訪問,有後續操作) protected: 保護成員 二.建構函式 def:給物件進行初始化的函式。 gra: 函式名與類名相同; 無返回值(return); 1.委託

C#基礎關於(繼承)

1、繼承:面向物件程式設計中提供的子類可以沿用父類某些行為和特徵的一種方式。       當一個類繼承另一個類時,被繼承的類稱為父類或基類;該類被稱為子類或派生類。       【特點】:        ⑴、傳遞性;(即類B繼承類A,類C繼承類B,故類C同時擁有類A和類

C#基礎之訪問修飾符、與屬性、與結構的簡單介紹

在學方法之前先學習類…… 介紹類之前先介紹下C#中常用的四個訪問修飾符: 我的疑問:結構與類的區別?結構裡不能定義方法,它們都可以定義多個屬性,什麼時候要用結構?什麼時候要用類? 一、C#中的4個常用訪問修飾符: public:可以在任何地方被訪問 internal:只能

Java基礎的例項化、static、父建構函式執行順序

重溫java基礎,以免自己以後犯原則性錯誤,這是最基本,最基礎的東西。 直接上程式碼: A.java public class A { int a1 = 8; int a2 = getA2(); { int a3 = 9; System.out.pr

C#基礎Path、File、Directory

1.Path類(靜態類,用於操作檔案路徑)  Path類提供的常用靜態方法:  string str = @”C:\Users\YF105\Desktop\new.txt”;  Path.GetFileName(str);//獲取檔名,包含副檔名  Path.

c#基礎int 轉換 string,string 轉換 int

方法 res data int 是否 tryparse php convert out 1、int 轉換 string方法:toString() 或者 Convert.toString()舉例: [code]phpcode://toString() int a =1; st

C語言型限定詞

變量 可變 oct 包含 一個數 sta ans eof 方式 ANSI C 的類型限定詞有const、volatile以及restrict三個,以下分別介紹三個限定詞: 1、類型限定詞const (1)、如果變量中帶有const關鍵字,則該變量無法進行賦值、增量及減量運算

1、C++基礎簡介

void指針 通用 if...else 重載 全局變量 但是 區別 模板庫 stl C++ 簡介 C++ 是一種靜態類型的、編譯式的、通用的、大小寫敏感的、不規則的編程語言,支持過程化編程、面向對象編程和泛型編程。 C++ 被認為是一種中級語言,它綜合了高級語言和低級語言

23、C++基礎復制構造函數

amp sna c++基礎 cout span 另一個 include sin str 拷貝構造函數 拷貝構造函數是一種特殊的構造函數,它在創建對象時,是使用同一類中之前創建的對象來初始化新創建的對象。拷貝構造函數通常用於: 通過使用另一個同類型的對象來初始化新創建的對

28、C++基礎線程同步

有一個 handles 返回 由於 離開 hand 應用 數加 out 線程同步的方法主要有四種(《操作系統教程》一書):   1、臨界區:通過對多線程的串行化來訪問公共資源或一段代碼,速度快,適合控制數據訪問。      2、互斥量:為協調一起對一個共享資源的單獨訪問而設

C#基礎輸入一個字元,判定它是什麼型別的字元(大寫字母,小寫字母,數字或者其它字元)

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace _02_判斷使用者輸入字元型別 { c

c++基礎從json檔案提取資料

前言 標註資料匯出檔案是json格式的,也就是python的dict格式,需要讀取標註結果,可以使用c++或者python,本文使用c++實現的。 JsonCpp簡介 JsonCpp是一種輕量級的資料交換格式,是個跨平臺的開源庫,可以從github和sourceforge上下載原始碼。 JsonCpp

java基礎載入機制

類載入機制 1.什麼是類的載入? 類載入機制指的是將.class檔案中的二進位制資料讀入到記憶體中,將其放在執行時資料區的方法區內,然後在堆區建立一個java.lang.Class物件,用來封裝類在方法區內的資料結構。類的載入的最終產品是位於堆區的Class物件,Clas

C#基礎數學運算子自加和自減運算子

1.自加(先加和後加) 無論先加還是後加,值都自加1 ``` int num = 45; int result; //後加,先用num的值運算,num再自加1,result的值為45,num的值為46 result = num++; //先加,num的值先自加1,再用num

C#基礎while迴圈和do-while迴圈

1.While迴圈(先判斷迴圈條件,在執行迴圈體) static void Main(string[] args) { int index = 1; int sum = 0;

C#基礎迴圈的中斷Break , Continue , return , go-to

Break : 跳出迴圈體,繼續執行下面的程式碼 Continue : 跳出本次迴圈,不在執行continue下面程式碼,繼續判斷迴圈條件,進行下一次迴圈 static void Main(string[] args) {