天天看點

C++知識點——類和對象一、面向對象三大特性之一——封裝二、類和對象的大小以及在記憶體中類的對齊規則三、this 指針四、類中的預設成員函數五、 explicit 關鍵字六、 static 靜态成員。七、 C++11 類中成員初始化新方式八、 友元九、 内部類十、const成員函數

一、面向對象三大特性之一——封裝

封裝:将資料和操作資料的方法進行有機結合,隐藏對象的屬性和實作細節,僅對外公開接口來和對象進行互動。

1. C++中struct與class的差別:struct 中預設成員為公有 public ;而 class 中預設成員為私有 private 。在C語言中 struct 中隻能聲明成員變量,不能聲明成員函數函數,但是C++中 struct 可以聲明成員函數。

2. 類隻是一個像模型一樣的東西,限定了類有哪些成員,定義出一個類并沒有配置設定實際的記憶體空間來存儲它。隻有當建立類的對象時(執行個體化),才會為成員變量配置設定記憶體空間來存儲它。

二、類和對象的大小以及在記憶體中類的對齊規則

類和對象的大小:對象中隻儲存成員變量,成員函數存放在公共的代碼段。如果一個類中具有虛函數,則對象中還會有一個虛表指針,存放在對象的前四個位元組中。靜态成員變量也不計入類和對象的大小。綜上所述,類和對象的大小就是 成員變量+虛表指針。

需要注意的是,空類的大小為 1 。

我們來看一段代碼:

class A
{
public:
	A(int _a,double _b)
	{
		a = _a;
		b = _b;
	}
private:
	int a;
	double b;
	static int c;
};
int A::c = 30;
int main()
{
	A obj(10, 20.5);
	cout << sizeof(A) << endl;
	cout << sizeof(obj) << endl;
	return 0;
}
           

我們可看到,類中有三個成員變量,其中靜态成員變量不計入類和對象的大小,也就是說,隻有一個 int 型的變量和一個 double 類型的變量,那麼大小是多少?是 4+8=12 嗎?答案是 16。為什麼呢? 在記憶體中并不是緊挨着存放的,而是有對齊規則的。

類(結構體)在記憶體對齊規則:

1. 第一個成員在結構體變量偏移量為0的位址處。

2. 其他成員要對齊到對齊數的整數倍位址處。 對齊數=編譯器預設的對齊數與該成員變量大小的較小值。

    vs 下對齊數預設為8 ,Linux 中的對齊數預設為 4 。

3. 結構體總大小為最大對齊數的整數倍。 最大對齊數為所有成員變量的對齊數的最大值。

4. 如果嵌套了結構體,那麼嵌套的結構體的對齊數就是自己的最大對齊數的整數倍處。

是以我們來看上面代碼,先在記憶體中存放變量 a 四個位元組,變量 b 對齊時需要對齊到偏移量為 8 處,是以類和對象的大小為:4+4+8=16 。

我們再來看另一段代碼:

class A
{
public:
	A()
	{

	}
private:
	int a;
	char arr[9];
	double b;
};

int main()
{
	A obj;
	cout << sizeof(A) << endl;
	cout << sizeof(obj) << endl;

	return 0;
}}
           

上面的代碼結果又是多少呢? 是 4+(5)+9+(6)+8=32 嗎?(括号中數字代表,為了記憶體對齊而空出來的位元組數)

答案是 4+9+(3)+8=24 。這是因為 arr[9] 的對齊數不是 9 而是 1。一個 char 類型的大小。

如果類中有虛函數,則對象中會有一個虛表指針,存放于首位址中 同樣遵循記憶體對齊規則。

我們已經知道結構體在記憶體中需要對齊,那麼為什麼需要對齊呢?

1、平台原因(移植原因):

不是所有的硬體平台都能通路任意位址上的任意資料的;某些硬體平台隻能在某些位址處取某些特定類型的資料,否則抛出硬體異常。

2、性能原因:資料結構(尤其是棧)應該盡可能地在自然邊界上對齊。

原因在于,為了通路未對齊的記憶體,處理器需要作兩次記憶體通路;而對齊的記憶體通路僅需要一次通路。(提高CPU處理速度)

三、this 指針

C++編譯器給每個“成員函數“增加了一個隐藏的指針參數,讓該指針指向目前對象(函數運作時調用該函數的對象),在函數體中所有成員變量的操作,都是通過該指針去通路。

當我們使用對象來調用類中的成員函數時,會将對象的位址傳給 this 指針,也就是說 this 指針指向的就是調用成員函數的對象。

我們來看一段代碼:

class A
{
public:
	A(int _a,int _b)
	{
		a = _a; 
		b = _b;
	}
	void show()
	{
		cout << a << endl;
		cout << b << endl;
	}
private:
	int a;
	int b;
};

int main()
{
	A obj(10, 20);
	obj.show();
	return 0;
}
           

執行函數 show 的時候編譯器會處理成 show(A* this); this 存放的就是對象 obj 的位址。

也就是說 cout<<a<<endl; 相當于 cout<<this->a<<endl;  這樣函數就知道是哪個對象調用了自己。

1. this指針的類型:類類型* const

2. 隻能在“成員函數”的内部使用

3. this指針本質上其實是一個成員函數的形參,是對象調用成員函數時,将對象位址作為實參傳遞給this形參。是以對象中不存儲this指針。

4. this指針是成員函數第一個隐含的指針形參。

5. this 指針存放在 ecx 寄存器中。

6. this 指針可以為空,為空時不能通過 this 指針來調用類中成員變量。

四、類中的預設成員函數

一個空類中并不是什麼都沒有的,它有六個預設的成員函數:構造函數、析構函數、拷貝構造函數、指派運算符重載函數、取位址操作符重載、const 取位址操作符重載。

1. 無參構造函數和全預設構造函數都是預設構造函數,類中隻能存在一個預設構造函數,沒有構造函數時,會自動生成一個無參構造函數。

2. 拷貝構造函數的參數隻有一個且必須使用引用傳參,使用傳值方式會引發無窮遞歸調用。

3. 有五個運算符不能被重載    . 點運算符     .* 成員指針通路運算符     :: 域運算符     sizeof 長度運算符   ?: 條件運算符。

4. 以下三種成員變量必須用初始化清單初始化: const 成員變量 ,引用成員變量,類類型成員(該類中沒有預設構造函數)

5. 成員變量在類中聲明次序就是其在初始化清單中的初始化順序,與其在初始化清單中的先後次序無關。

class A
{
public:
 A(int a)
 :_a(a)
 {}
private:
 int _a;
};
class B
{
public:
 B(int a, int ref)
 :_aobj(a)
 ,_ref(ref)
 ,_n(10)
 {}
private:
 A _aobj; // 沒有預設構造函數
 int& _ref; // 引用
 const int _n; // const 
}
           

五、 explicit 關鍵字

在 C++ 中,單參數構造函數(或者是除了第一個參數,其餘參數都有預設值的多參構造函數)有兩個作用:一是初始化對象,二是隐式類型轉換。

class A
{
public:
	A(int _a)
	{
		a = _a;
	}
	void print()
	{
		cout << a << endl;
	}
private:
	int a;

};

int main()
{
	A obj(5);
	obj = 10;
	obj.print();

	return 0;
}
           

上述代碼相當于用一個整型 10 給obj 指派。編譯器會用 10 建構一個無名對象,然後用這個無名對象給 obj 指派。

但是這樣的代碼不具有可讀性,所有引入了 explicit 關鍵字。用 explicit 修飾構造函數,将會禁止單參構造函數的隐式轉換。

class A
{
public:
	explicit A(int _a)
	{
		a = _a;
	}
	void print()
	{
		cout << a << endl;
	}
private:
	int a;

};

int main()
{
	A obj(5);
	obj = 10;
	obj.print();

	return 0;
}
           

這個時候,編譯器就會報錯:沒有與這些操作數比對的 “=” 運算符。 

六、 static 靜态成員。

static的類成員稱為類的靜态成員,用static修飾的成員變量,稱之為靜态成員變量;用static修飾的成員函數,稱之為靜态成員函數。靜态的成員變量一定要在類外進行初始化。在類外初始化時不需要加上 static。

1. 靜态成員為所有類對象所共享,不屬于某個具體的執行個體。

2. 類靜态成員即可用類名::靜态成員或者對象.靜态成員來通路

3. 靜态成員函數沒有隐藏的this指針,不能通路任何非靜态成員

4. 靜态成員和類的普通成員一樣,也有public、protected、private 3種通路級别,也可以具有傳回值,const修飾符等參數。

七、 C++11 類中成員初始化新方式

C++11 允許在類中聲明非靜态成員變量時直接初始化。

class B
{
public:
 B(int b = 0)
 :_b(b)
 {}
 int _b;
};
class A
{
public:
 void Print()
 {
 cout << a << endl;
 cout << b._b<< endl;
 cout << p << endl;
 }
private:
 // 非靜态成員變量,可以在成員聲明時,直接初始化。
 int a = 10;
 B b = 20;
 int* p = (int*)malloc(4);
 static int n;
};
int A::n = 10;
int main()
{
 A a;
 a.Print();
 return 0;
}
           

八、 友元

友元函數可以直接通路類的私有成員,它是定義在類外部的普通函數,不屬于任何類,但需要在類的内部聲

明,聲明時需要加friend關鍵字。

1. 友元函數可通路類的私有成員,但不是類的成員函數

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

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

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

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

友元類的所有成員函數都可以是另一個類的友元函數,都可以通路另一個類中的非公有成員。

class Date; // 前置聲明
class Time
{
 friend class Date; // 聲明日期類為時間類的友元類,則在日期類中就直接通路Time類中的私有成員變
量
public:
 Time(int hour, int minute, int second)
 : _hour(hour)
 , _minute(minute)
 , _second(second)
 {}
 private:
 int _hour;
 int _minute;
 int _second;
};
class Date
{
public:
 Date(int year = 1900, int month = 1, int day = 1)
 : _year(year)
 , _month(month)
 , _day(day)
 {}
 
void SetTimeOfDate(int hour, int minute, int second)
 {
 // 直接通路時間類私有的成員變量
 _t._hour = hour;
 _t._minute = minute;
 _t.second = second;
 }
 
private:
 int _year;
 int _month;
 int _day;
 Time _t;
};
           

在一個類中聲明另一類為該類的友元類時,必須要前置聲明該友元類。

1. 友元關系是單向的,不具有交換性。

    比如上述Time類和Date類,在Time類中聲明Date類為其友元類,那麼可以在Date類中直接通路Time類的私有成員變量,但想在Time類中通路Date類中私有的成員變量則不行。

2. 友元關系不能傳遞

    如果B是A的友元,C是B的友元,則不能說明C時A的友元。

九、 内部類

如果一個類定義在另一個類的内部,這個内部類就叫做内部類。注意此時這個内部類是一個獨立的類,它不屬于外部類,更不能通過外部類的對象去調用内部類。外部類對内部類沒有任何優越的通路權限。

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

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

2. 注意内部類可以直接通路外部類中的static、枚舉成員,不需要外部類的對象/類名。

3. sizeof(外部類)=外部類,和内部類沒有任何關系。

class A
{
public:
	A(int _a) :a(_a)
	{
	}
	void print()
	{
		cout << a << endl;
	}
	class B
	{
	public:
		B(int _b) :b(_b)
		{}
		void print(const A& obj)
		{
			cout << obj.a << endl;  // a 為普通成員變量,需要通過類A 的對象來通路
			cout << b << endl;
			cout << aa << endl; // aa 為A類的靜态成員變量,可以不通過類A的對象或類名來通路
		}
	private:
		int b;
	};
private:
	int a;
	static int aa;
};
int A::aa = 100;
int main()
{
	A obj1(10);
	A::B obj2(20);
	obj2.print(obj1);
	cout << sizeof(A) << endl;
	cout << sizeof(A::B) << endl;

	return 0;
}
           

程式運作結果為:

10

20

100

4

4

十、const成員函數

用const修飾的成員函數稱為const成員函數。const修飾成員函數實際上是修飾成員函數隐含的this指針,表明在該成員函數中,不能對調用該成員函數的對象做任何修改。

C++知識點——類和對象一、面向對象三大特性之一——封裝二、類和對象的大小以及在記憶體中類的對齊規則三、this 指針四、類中的預設成員函數五、 explicit 關鍵字六、 static 靜态成員。七、 C++11 類中成員初始化新方式八、 友元九、 内部類十、const成員函數

 const對象不可以調用非const成員函數,非const對象可以調用const成員函數;const成員函數内部不可以調用非const成員函數,非const成員函數内部可以調用const成員函數。

繼續閱讀