C++中的類與對象深度解析

初始化列表

引論

//初始化列表的引出
class B
{
public:
	B()
	{
		cout << "我是B的構造函數" << endl;
	}
private:
	int _b;
};
class A
{
public:
	A()
	{
		cout << "我是A的構造函數" << endl;
	}
private:
	int _a;
	B b;
};
int main()
{
	A a;
	return 0;
}

image-20220203215753999

匯編驗證:

image-20220203220240209

初始化列表

c++推薦使用初始化列表進行初始化

什麼是初始化列表?

一個冒號開始,以逗號分隔的數據成員列表,每個成員變量後面放一個括號,括號中放初始值或者表達式

:_a(1)

,_aa(2)

這個就是初始化列表

//初始化列表使用demo
class A
{
public:
	A()
		:_a(1)//初始化列表
		, _aa(2)
	{
		cout << _a << " " << _aa << endl;
	}
private:
	int _a;
	int _aa;
};
int main()
{
	A a;
	return 0;
}

image-20220203221620952

  • 存在自定義類型時,初始化列表,不寫也認為有

隻是認為啊,不寫初始化列表會在函數體之前調用自定義類型的構造函數,寫瞭初始化列表是會進入函數體(花括號)然後調用,具體的可以觀察匯編

  • 成員變量隻能在初始化列表裡出現一次
  • 成員變量包含引用和const變量時,必須在初始化列表裡初始化

引用和const變量必須初始化

  • 成員變量的聲明次序就是初始化順序,與在初始化列表裡先後順序無關

image-20220203222641177

初始化列表是推薦使用的,如果初始化列表不能解決問題,混著用(在構造函數體內初始化)就行瞭

explicit關鍵字

引論

//int賦給對象demo
class A
{
public:
	A(int){}
};
int main()
{
	A a = 1;
	return 0;
}

image-20220203223944155

這麼寫是合法的,賦值的過程發生瞭隱式類型轉換,前提是必須有A(int這樣的構造函數)

//多個int賦給對象demo
class A
{
public:
	A(int,int){}
};
int main()
{
	A a = {1,2};
	return 0;
}

image-20220203224101796

C++11支持多參數轉換,C++98不支持

explicit關鍵字使用

前面提到,int可以賦給對象是隱式類型轉換,如果要禁止這種用法,則用explicit修飾對應的構造函數

//explicit使用demo
class A
{
public:
	explicit A(int){}
};
int main()
{
	A a = 1;//error
	return 0;
}

image-20220203224344113

static成員 

靜態成員變量:static修飾的成員變量

靜態成員函數:static修飾的成員函數

靜態成員變量不是單單屬於某一個對象的,一個類創建的多個對象使用這個靜態成員變量時使用的也是同一塊內存,即所有對象共有該靜態成員變量

一份內存,多對象使用

靜態成員函數一般用來訪問靜態成員,沒有this指針

由於沒有this指針,所以無法訪問非靜態的成員

計算類的大小時不包括靜態成員

image-20220203225355745

計算類的大小可以認為是計算對象的大小,因為每個對象共有靜態成員變量,所以不能認為該變量特定屬於某一個對象

調用靜態成員函數,初始化靜態成員變量

//調用static函數和初始化static變量的democlass A{public:static int Print(){cout << "static int Print()" << endl;return _aa;}private:int _a;static int _aa;};int A::_aa = 1;int main(){A::Print();return 0;}//調用static函數和初始化static變量的demo
class A
{
public:
	static int Print()
	{
		cout << "static int Print()" << endl;
		return _aa;
	}
private:
	int _a;
	static int _aa;
};
int A::_aa = 1;
int main()
{
	A::Print();
	return 0;
}

image-20220203225932607

靜態成員變量不能給缺省值,必須在類外初始化,因為在類外初始化時才分配空間,所以不在類外初始化就不能用,用瞭可能會導致鏈接錯誤

靜態成員函數不能調用非靜態成員函數,非靜態成員函數可以調用靜態成員函數

普通靜態函數需要通過this指針調用,而靜態成員函數沒有this指針–百度

待瞭解:鏈接屬性

友元

引論

image-20220203232439932

友元

什麼是友元?

友元是一種定義在類外部的普通函數或類,但它需要在類體內進行說明,為瞭與該類的成員函數加以區別,在說明時前面加以關鍵字friend。–百度百科

友元的作用

突破封裝

class Date
{
	friend bool operator==(Date d1, Date d2);

private:
	int _year;
	int _month;
	int _day;
};
bool operator==(Date d1,Date d2)
{
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}
int main()
{
	return 0;
}

image-20220203232555062

隨筆記錄:編譯器找一些聲明隻會往上找

類的聲明:class A;

如果我們在一個類內想訪問另一個類的私有成員,就需要友元類

class Time
{
public:
	void GetData()
	{
		cout << d._year << d._month << d._day << endl;
	}
private:
	Date d;//借助這個對象
};
class Date
{
	friend class Time;
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	return 0;
}

image-20220203233823260

友元==突破類域

內部類

基礎概念

什麼是內部類?

類裡面定義一個類,這就叫內部類

內部類就是外部類的友元類,所以內部類可以訪問外部類的成員,用法也和友元類很像

計算類的大小時不算內部類

內部類內可以直接訪問外部類的靜態成員,不需要通過類名

內部類受到類域影響和訪問限定符限制

內部類的使用

//內部類使用demo
class A
{
public:
	class B
	{
	public:
		B(const A& a)
		{
			cout << "我是內部類B" << endl;
			cout << "我可以訪問外部類A的變量_a:" <<a._a << endl;
		}
	private:
		int _b;
	};
	A(){}
	A(const B& b)
	{
		cout << b._b << endl;//error
	}
private:
	int _a=1;
};
int main()
{
	A a;
	A::B b(a);
	return 0;
}	

image-20220204001232793

其實C++不咋用內部類,Java喜歡用內部類

補充

析構順序例題

類A、B、C、D,問下面程序中析構函數的調用順序?

C c;
int main()
{
	A a;
	B b;
	static D d;
	return 0;
}

答案:析構順序 B A D C

構造順序:C A B D

析構順序是D B C A嗎?不是

  • 析構函數的調用時期:對象聲明周期結束後
  • 靜態的變量存儲在全局區,main函數結束後會銷毀棧幀

①因為a,b都是局部對象,先構造則後析構,構造時是AB,則析構肯定是BA

換個角度理解,棧的特點是先進後出,那a先入棧就應該後銷毀,所以b先調用析構函數

②剩下C D,C是全局對象,D是靜態局部對象,這兩個誰先析構?

靜態局部變量先析構,全局變量C再析構

D先析構,C後析構,即DC

全局對象和靜態局部對象的釋放優先級在網上沒有找到很好的解釋

個人理解:CD都存在全局區,所以CD的構造順序和析構順序應該是相反的,即構造是CD,則析構是DC

組合①②,得到BADC

這種題的技巧:把局部變量作為一組,把全局和靜態變量作為一組,寫出兩個相應的構造順序,再逆置一下就得到相應的析構順序,又因為局部變量先析構,再拼接兩組的析構順序得到答案

可以把上面那段代碼拷到編譯器上,然後自己寫代碼驗證答案

總結

  • 初始化列表提供瞭一種更好的初始化的方式,如果初始化列表不能單獨完成任務,就結合構造函數體完成初始化任務
  • explicit可以禁止內置類型和自定義類型的轉換,具體操作就是修飾對應的構造函數
  • static成員可以牽扯出很多東西,比如靜態成員函數可不可以非靜態變量等等,抓住關鍵點:靜態成員函數沒有this指針,static成員變量始終是一塊內存,類外初始化才會分配空間
  • 友元主要解決瞭我們在類外不能訪問私有成員變量的問題,本質上破壞瞭封裝,不建議大量使用
  • 內部類,C++一般不怎麼用,內部類理解為外部類的友元,同時受到訪問限定符的限制,類外使用內部類得用::突破類域限制
  • 面向對象是在模擬是在抽象模擬我們的現實世界!

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

推薦閱讀: