C++類和對象補充

一. 再看構造函數

我們之前已經瞭解瞭構造函數的基本內容,那麼這裡我們將深入認識構造函數。

1.函數體內賦初值

class Date
{
public:
 Date(int year, int month, int day)
 {
 _year = year;
 _month = month;
 _day = day;
 //可以進行多次賦值,但一般不這麼做
 _year = 1;
 }
private:
 int _year;
 int _month;
 int _day;
};

首先,對於構造函數體內的賦值我們不能稱之為初始化。首先我們要理解:初始化隻能初始化一次,而構造函數體內可以多次賦值。那麼對象成員變量的初始化是在什麼時候進行的呢?這就要接下來要介紹的初始化列表要做的事瞭。

2.初始化列表

初始化列表是以一個冒號開始,接著是一個以逗號分隔的數據成員列表,每個”成員變量”後面跟一個放在括號中的初始值或表達式。其形式如下:

class Date
{
public:
	Date(int year = 0, int month = 1, int day = 1)
		:_year(year)
		,_month(month)
	{
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

幾點註意

1.每個成員變量在初始化列表中隻能出現一次(初始化隻能初始化一次)

2.類中包含以下成員,必須放在初始化列表位置進行初始化:

(1)const成員變量:由於const變量初始化之後就不能更改,因此需在初始化列表進行初始化。

(2)引用成員變量:引用成員變量隻能作為一個變量的引用,一旦初始化,就不能再作為其他變量的引用,因此引用變量也隻能再初始化列表初始化。

(3)自定義類型成員變量(沒有默認構造函數情況下):由於沒有默認構造函數時,自定義類型變量是不能初始化的,此時程序也無法編譯,因此沒有默認構造函數的自定義類型成員變量必須在初始化列表進行初始化。

class B
{
public:
	B(int i)
		:_i(i)
	{
	}
private:
	int _i;
};
class A
{
public:
	A(int a, int& b, int bb)
		:_a(a)
		,_b(b)
		,_bb(bb)
	{
	}
private:
	const int _a;//const成員變量
	int& _b;//引用成員變量
	B _bb;//自定義成員變量
};

3.盡量使用初始化列表初始化,因為不管你是否使用初始化列表,對於自定義類型成員變量,一定會先使用初始化列表初始化。比如下面代碼的執行結果:

class B
{
public:
	B()
	{
		cout << "B()" << endl;
	}
private:
	int _i;
};
class A
{
public:
	A(int a, int& b)
		:_a(a)
		,_b(b)
	{
	}
private:
	const int _a;//const成員變量
	int& _b;//引用成員變量
	B _bb;//自定義成員變量
};
int main()
{
	int n = 0;
	A a1(0, n);
	return 0;
}

在這裡插入圖片描述

可以看到,初始化列表中並沒有對自定義變量_bb初始化,但程序仍然調用瞭自定義類型的默認構造函數。

4. 成員變量在類中聲明次序就是其在初始化列表中的初始化順序,與其在初始化列表中的先後次序無關,先想想下面的代碼運行結果是什麼:

class A
{
public:
	A(int a)
		:_a1(a)
		, _a2(_a1)
	{}
	void Print() {
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a2;
	int _a1;
};
int main() 
{
	A aa(1);
	aa.Print();
	return 0;
}

在這裡插入圖片描述

可以看到的是,_a1為1,而_a2為隨機值,這是因為在成員列表的聲明中,_a2先被聲明,_a1後被聲明,因此初始化列表中的順序是先_a2,後_a1。而一開始_a1為隨機值,因此最終_a2為隨機值。

3.explicit關鍵字

我們知道,對於構造函數,不僅可以構造和初始化對象,對於單個參數的構造函數,還具有類型轉換的作用。

比如Date類:

class Date
{
public:
	Date(int year)
		:_year(year)
	{}
	explicit Date(int year)
		:_year(year)
	{}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2020);
	// 用一個整形變量給日期類型對象賦值
	// 實際編譯器背後會用2019構造一個無名對象,最後用無名對象給d1對象進行賦值
	Date d2 = 2021;//explict禁止隱式類型轉換,因此該句代碼運行錯誤
}

但是Date d2 = 2021;這樣的代碼可讀性不是很好,因此可以使用explicit關鍵字將這種隱式類型轉換禁止。

二.static成員

C語言中我們就接觸瞭static關鍵字,那麼這個關鍵字修飾成員會怎麼樣呢?

1.概念

聲明為static的類成員稱為類的靜態成員,用static修飾的成員變量,稱之為靜態成員變量;用static修飾的成員函數,稱之為靜態成員函數。像上面初始化列表中說的,靜態的成員變量一定要在類外進行初始化。

2.特性

靜態成員存儲在靜態區,為所有類對象所共享,不屬於某個具體的實例

靜態成員變量必須在類外定義,定義時不添加static關鍵字

類靜態成員即可用類名::靜態成員或者對象.靜態成員來訪問

靜態成員函數沒有隱藏的this指針,不能訪問任何非靜態成員;相對的,非靜態成員函數可以通過this指針訪問靜態成員變量。

靜態成員和類的普通成員一樣,也有public、protected、private 3種訪問級別,也可以具有返回值

接下來我們來看看一道題:

求1+2+3+…+n

題目描述:求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等關鍵字及條件判斷語句(A?B:C)。
這道題我們可以利用構造函數,由於每次實例化對象,都會調用其構造函數,因此我們可以實例化n個對象,每次初始化時計算求和即可;

class Sum
{
public:
    //調用構造函數
    Sum()
    {
        _sum += _i;
        ++_i;
    }
    //static修飾的成員函數,沒有隱含的this指針,隻能訪問靜態成員變量
    static int GetSum()
    {
        return _sum;
    }
private:
	//static修飾的成員變量為所有定義出來的類對象共有
    static int _i;
    static int _sum;
};
//靜態成員變量的定義
int Sum::_i = 1;
int Sum::_sum = 0;
class Solution {
public:
    int Sum_Solution(int n) {
        Sum* p = new Sum[n];
        return Sum::GetSum();
    }
};

【註意】sizeof(類名)不計算靜態成員變量的大小。比如上述代碼中的sizeof(Sum)為1,是一個空類。

三.友元

友元分為友元函數和友元類,其提供瞭一種突破封裝的方式,有時提供瞭便利。但是友元會增加耦合度,破壞瞭封裝,所以友元不宜多用。

1.友元函數

首先如果我們要重載<<(流插入)運算符,我們會發現將其定義成類成員函數將無法實現,這是因為類成員函數的第一個參數為this指針,那麼我們隻能將這個函數定義在類外,但是這樣的話函數又不能訪問類中的成員變量,那麼這個時候要麼在成員函數中實現訪問的方法,要麼就使用友元函數,使其可以訪問類中成員。即:

class Date
{
	//用關鍵字friend在類中聲明函數為Date的友元函數
	friend ostream& operator<<(ostream& out, const Date& d);
public:
	Date(int year, int month, int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};
ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "/" << d._month << "/" << d._day;
	return out;
}
int main()
{
	Date d1(2021, 10, 20);
	cout << d1 << endl;
}

同理,cin也可以如此定義。

【說明】

1.友元函數可訪問類的私有和保護成員,但不是類的成員函數

2.友元函數不能用const修飾

3.友元函數可以在類定義的任何地方聲明,不受類訪問限定符限制

4.一個函數可以是多個類的友元函數

5.友元函數的調用與普通函數的調用和原理相同

2.友元類

和友元函數相似,友元類可以訪問另一個類的私有成員。比如下面代碼中,B作為A的友元類,可以訪問A中的_a和_i。

class A
{
	//聲明B為A的友元類,則在B中可以訪問A中的成員
	friend class B;
public:
	A(int a)
		:_a(a)
	{
	}
private:
	int _a;
	static int _i;
};
class B
{
public:
	B(int b)
		:_b(b)
	{}
	static int Count()
	{
		A::_i++;
		return A::_i;
	}
private:
	int _b;
};
int A::_i = 0;
int main()
{
	A a1(1);
	B b1(1);
	cout << b1.Count() << endl;
	cout << b1.Count() << endl;
	return 0;
}

需要註意,友元關系是單向的,不具有交換性,比如上述代碼中A不能訪問B中的成員;友元關系不能傳遞,即B是A的友元,C是B的友元,但C不是A的友元,C就不能訪問A中的私有成員。

四.內部類

顧名思義,定義在另一個類中的類就是內部類。註意此時這個內部類是一個獨立的類,它不屬於外部類,更不能通過外部類的對象去調用內部類。外部類對內部類沒有任何優越的訪問權限。

內部類就是外部類的友元類。註意友元類的定義,內部類可以通過外部類的對象參數來訪問外部類中的所有成員。但是外部類不是內部類的友元。

class A
{
public:
	class B//內部類,是A的友元類
	{
	public:
	//B可以直接訪問A的成員
		void Print(const A& a)
		{
			cout << a._a << endl;
			cout << _i << endl;
		}
	};
	A(int a)
		:_a(a)
	{}
private:
	int _a;
	static int _i;
};
int main()
{
	A::B b1;//註意B的調用方式
	A a1(1);
	b1.Print(a1);
	//但A的對象不能去訪問B中的成員
	a1.b1;//error
}

特性:

1.內部類可以定義在外部類的public、protected、private都是可以的。

2.註意內部類可以直接訪問外部類中的static、枚舉成員,不需要外部類的對象/類名。

3.sizeof(外部類)=外部類,和內部類沒有任何關系。比如上面的sizeof(A)為4。

總結

本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!

推薦閱讀: