C++全面精通類與對象

運算符重載

C++語法設計很巧妙,比如運算符重載一個 >

bool operator>(const Date& d)
{
     return !(*this <= d);
}

這裡可以結合前面的內聯函數來進一步提高代碼的效率,而內聯函數不支持 .h 和 .cpp 分開寫,所以成員函數要成為內聯函數最好的辦法就是把定義放在類裡面,類裡面定義的會被默認為是 inline 內聯函數。

我們計算日期類的加法時:

	Date Date::operator+(int d)
	{
		Date ret(*this);
		ret.day += d;
		while (ret.day > Getmonth(ret.year, ret.month))
		{
			ret.day -= Getmonth(ret.year,ret.month);
			ret.month++;
			if (ret.month == 13)
			{
				ret.year += 1;
				ret.month = 1;
			}
		}
		return ret;
	}

運算符復用

我們可能會有這樣的問題,這裡面 += 和 + 兩個運算符其實是一樣的,實現原理上沒什麼差別,那你可能會封裝個函數來解決他們的關系,但是其實直接讓他倆互相附庸,分為瞭 += 復用+ 和 + 復用 += 兩種辦法:

1.+= 復用 +:

2.+ 復用 +=(更優):

兩種乍一看其實沒什麼區別,但是其實有優越和劣勢可以分的,很 += 是不需要構造的,因為它是傳引用調用(返回值為域外的 this 指針的內容,必須要傳引用),但是 + 是必須要構造的,拷貝局部對象的 ret 和 最後的 return , 一共需要構造兩次。

讓 += 復用 +,+在先就會讓整個過程構造 4 次,而讓 + 來復用 += 的話,+ 還是構造 2 次沒得說,但是 += 就不需要拷貝構造瞭,整個過程就隻需要構造 2 次,消耗就會小很多。咱就應該多摳摳細節,寫出正確的代碼固然重要,但是追求更優秀更高效的代碼是每一個程序員的基本素養。

前置後置運算符

既然 +,- 能造,那 ++ 和 – 自然也不在話下,但是這就不好玩瞭啊, num++ 和 ++num 功能上都是 +1,寫成運算符重載格式都是

Date Date::operator++();

我們該怎麼區分呢?要知道函數名相同而參數不同就應該敏感使用函數重載,C++這個大聰明是不會考慮不到這些的,因此就有瞭對應的語法:前置不帶參數而後置帶參數

Date operator++();//前置++

Date operator++(int d);//後置++

Date& operator++()//前置
{
   *this += 1;
   return *this;
}
Date operator++(int)//後置
{
    Date tmp(*this);
    *this += 1;
    return tem;
}

其實括號裡面這個參數並沒有任何意義,單純隻是用來區分前置與後置的寫法,所以這裡不寫形參也是可以的,我這裡就隻給瞭一個類型。還有這裡千萬不要想著去加一個缺省值,顯式傳參還好,要是不傳參編譯器就沒辦法區分開來,屬於是沒事找事瞭。

const

給一個場景:

void Func(const Date& d)

{

d1.Print();

}

void test()

{

Date d1(2022,5,19);

d1.Print();

Func(d1);

}

這個場景下就會報錯:

說實在的,這個報錯我自己也看的雲裡霧裡,為什麼 Print 那裡不報錯到瞭 Func 裡面 Print 就要報錯?Print 傳的過去 Func 就傳不過去瞭?究其為什麼會報錯,其實涉及到一個權限問題。

void Print(Date* const this)
{
     cout<<year<<"-"<<month<<"-"<<day<<endl;
}

我們知道 Print() 的參數其實是 Date& const this ,在上面場景中去調用 Print 時其實參數是 &d1,傳對象的地址。在 Print 定義時 const 修飾的是 this 指針,const 修飾的變量可以初始化,此時指針不能被改變但是他指向的內容可以被初始化和修改;而 Func 的 const 修飾 Date*,他指向的內容不能被修改,所以這是一個經典的權限放大問題。

const Date* 要傳給 Date* ,所以我們需要一個 const 進行修飾保護,但是 this 本質是一個隱含形參,我們沒辦法顯式調用,也就是說 const 沒辦法進行修飾。那麼C++也提供瞭一種修飾方法打破這個僵局,就是在函數尾巴加上 const。

void Print() const
{}

尾巴上的 const 編譯器就會默認你是加在瞭函數原本定義的前面,這樣就完美瞭。

C++ 的IO流

我們在代碼中使用的 << , >> 為流輸入和流提取操作符,隻要涉及輸入或者輸出數據,我們立馬想到的就是 cin 和 cout,這倆貨其實是全局的對象, cin 對應 istream 類,cout 對應 ostream 類,它們都聲明在 頭文件中,這也解釋瞭“為什麼在 C++ 程序中引入 就可以使用 cin 和 cout”。

我們之所以可以在 <<, >> 之後接上任何類型,是因為強大的語法對每種類型進行瞭重載,能自動識別類型的本質就是函數重載,所以如果一個 int 類型的流插入 cin<<1 其實是 cin . operator <<(1)。

初始化列表

與其他函數不同,構造函數除瞭有名字,參數列表和函數體之外,還可以有初始化列表,初始化列表以冒號開頭,後跟一系列以逗號分隔的初始化字段,初始化列表可以看成是對象的成員變量定義的地方:

class Func
{
public:
    Func(int a):
    _a(a){} // 初始化列表
private:
    int _a;
};

註意:每個成員變量在初始化列表中隻能出現一次因為初始化隻能初始化一次,還要明確哪些成員必須放在初始化列表進行初始化:

  1. 引用成員變量
  2. const 成員變量
  3. 自義定類型成員(該類沒有默認構造函數)

其他變量即可以在初始化列表初始化也可以在函數體內初始化,內置類型成員不處理時,會調用默認構造函數即隨機值,如果我給出缺省值,那麼之這個值就是給初始化列表用的,如果在初始化列表也同時給出這個內置類型的初始化值,就會采用初始化列表的值。我們應該盡量在初始化列表就初始化完,這樣能盡可能的減少很多毛病效率也高。

再來看看這個題目:

這個程序的結果是啥?

答案是 1 和隨機值,因為成員變量在類中的聲明次序就是他在初始化列表中的初始化順序,與他在初始化列表中的先後次序無關。_a2 先聲明 _a2 = _a1,此時 _a1 為隨機值,所以 _a2 為隨機值,_a1 為 1。

explicit 關鍵字

構造函數不僅可以構造和初始化對象,對於單個參數的構造函數,還具有類型轉換的作用。在C語言裡面我們就知道有隱式類型轉換,其實在 C++ 裡面也是一樣的,比如針對我定義的一個 Date(int year):

Date d1(2022);
Date d2 = 2022;

顯然, 這裡 d2 需要的是 Date 類型的參數, 而我們傳入的是一個int, 這個程序卻能成功運行, 就是因為這隱式調用,另外說一句, 在對象剛剛定義時, 即使使用的是賦值操作符 = , 也是會調用構造函數, 而不是重載的 operator= 運算符。這兩個語句對應前者是構造,而後者是構造+拷貝構造,相當於發生瞭隱式類型轉換, 如果我們寫成:

explicit Date(int year)

這個關鍵字會阻止這種轉換的發生。

到此這篇關於C++全面精通類與對象的文章就介紹到這瞭,更多相關C++類與對象內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: