天天看點

【C++從青銅到王者】第二篇:C++類和對象(上篇)系列文章目錄前言一、面向過程和面向對象初步認識二、類的引入三、類的定義四、類的通路限定符及封裝五、類的作用域六、類的執行個體化七、類對象模型八、this指針總結

【C++從青銅到王者】第二篇:C++類和對象(上篇)系列文章目錄前言一、面向過程和面向對象初步認識二、類的引入三、類的定義四、類的通路限定符及封裝五、類的作用域六、類的執行個體化七、類對象模型八、this指針總結

系列文章目錄

文章目錄

  • 系列文章目錄
  • 前言
  • 一、面向過程和面向對象初步認識
  • 二、類的引入
  • 三、類的定義
  • 四、類的通路限定符及封裝
      • 1.類的通路限定符
      • 2.類的封裝
  • 五、類的作用域
  • 六、類的執行個體化
  • 七、類對象模型
      • 1.如何計算類對象的大小
      • 2 類對象的存儲方式猜測
          • 1.對象中包含類的各個成員
          • 2.隻儲存成員變量,成員函數存放在公共的代碼段
      • 3.結構體記憶體對齊規則
  • 八、this指針
      • 1.this指針的引出
      • 2.this指針的特性
  • 總結

前言

【C++從青銅到王者】第二篇:C++類和對象(上篇)系列文章目錄前言一、面向過程和面向對象初步認識二、類的引入三、類的定義四、類的通路限定符及封裝五、類的作用域六、類的執行個體化七、類對象模型八、this指針總結

一、面向過程和面向對象初步認識

【C++從青銅到王者】第二篇:C++類和對象(上篇)系列文章目錄前言一、面向過程和面向對象初步認識二、類的引入三、類的定義四、類的通路限定符及封裝五、類的作用域六、類的執行個體化七、類對象模型八、this指針總結

程式設計思想不同:

面向過程:是一種以過程為中心的程式設計思想。都是以什麼正在發生為主要目标進行程式設計。C語言是面向過程的,關注的是過程,分析出求解問題的步驟,通過函數調用逐漸解決問題。

面向對象:是一類以對象作為基本程式結構機關的程式設計語言,指用于描述的設計是以對象為核心,而對象是程式運作時刻的基本成分。C++語言是基于面向對象的,關注的是對象,将一件事情拆分成不同的對象,靠對象之間的互動完成。

【C++從青銅到王者】第二篇:C++類和對象(上篇)系列文章目錄前言一、面向過程和面向對象初步認識二、類的引入三、類的定義四、類的通路限定符及封裝五、類的作用域六、類的執行個體化七、類對象模型八、this指針總結

二、類的引入

C語言中,結構體中隻能定義變量,在C++中,結構體内不僅可以定義變量,也可以定義函數

代碼如下:

typedef int STDateType;
struct Stack
{
	void StackInit(int initSize = 4)
	{
		a = (STDateType*)malloc(sizeof(STDateType)*initSize);
		size = 0;
		capacity = initSize;
	}
	void StackPush(STDateType x)
	{
		a[size] = x;
		size++;
	}
	STDateType* a;
	int size;
	int capacity;
};
           

上面結構體棧的定義,在C++中更喜歡用class來代替

三、類的定義

  1. class為定義類的關鍵字,ClassName為類的名字,{}中為類的主體,注意類定義結束時後面分号。
  2. 類中的元素稱為類的成員:類中的資料稱為類的屬性或者成員變量; 類中的函數稱為類的方法或者成員函數。
    【C++從青銅到王者】第二篇:C++類和對象(上篇)系列文章目錄前言一、面向過程和面向對象初步認識二、類的引入三、類的定義四、類的通路限定符及封裝五、類的作用域六、類的執行個體化七、類對象模型八、this指針總結

類的兩種定義方式:

第一種聲明和定義都放在類體中:

【C++從青銅到王者】第二篇:C++類和對象(上篇)系列文章目錄前言一、面向過程和面向對象初步認識二、類的引入三、類的定義四、類的通路限定符及封裝五、類的作用域六、類的執行個體化七、類對象模型八、this指針總結

代碼如下:

#define _CRT_SECURE_NO_WARNINGS   1
#include<iostream>
typedef int STDateType;
class Stack
{
public:
	void StackInit(int initSize = 4)
	{
	  a = (STDateType*)malloc(sizeof(STDateType)*initSize);
	  size = 0;
	  capacity = initSize;
    }
	void StackPush(STDateType x)
	{
	  a[size] = x;
	  size++;
    } 
public:
	STDateType* a;
	int size;
	int capacity;
};
           

第二種聲明和定義分開放:

【C++從青銅到王者】第二篇:C++類和對象(上篇)系列文章目錄前言一、面向過程和面向對象初步認識二、類的引入三、類的定義四、類的通路限定符及封裝五、類的作用域六、類的執行個體化七、類對象模型八、this指針總結

代碼如下:

typedef int STDateType;
class Stack
{
public:
	//成員函數的聲明
	void StackInit(int initSize = 4);
	void StackPush(STDateType x);

	//成員變量的聲明
	STDateType* a;
	int size;
	int capacity;
};

//成員函數的定義
void Stack::StackInit(int initSize)
{
	a = (STDateType*)malloc(sizeof(STDateType)*initSize);
	size = 0;
	capacity = initSize;
}
void Stack::StackPush(STDateType x)	
{
	a[size] = x;
	size++;
}
           

四、類的通路限定符及封裝

1.類的通路限定符

C++實作封裝的方式:用類将對象的屬性與方法結合在一塊,讓對象更加完善,通過通路權限選擇性的将其接口提供給外部的使用者使用。

【通路限定符說明】

  1. public修飾的成員在類外可以直接被通路
  2. protected和private修飾的成員在類外不能直接被通路(此處protected和private是類似的)
  3. 通路權限作用域從該通路限定符出現的位置開始直到下一個通路限定符出現時為止
  4. class的預設通路權限為private,struct為public(因為struct要相容C)

    注意:通路限定符隻在編譯時有用,當資料映射到記憶體後,沒有任何通路限定符上的差別

    【C++從青銅到王者】第二篇:C++類和對象(上篇)系列文章目錄前言一、面向過程和面向對象初步認識二、類的引入三、類的定義四、類的通路限定符及封裝五、類的作用域六、類的執行個體化七、類對象模型八、this指針總結

    問題:C++中struct和class的差別是什麼?

    解答:C++需要相容C語言,是以C++中struct可以當成結構體去使用。另外C++中struct還可以用來定義類。和class是定義類是一樣的,差別是struct的成員預設通路方式是public,class是的成員預設通路方式是private

2.類的封裝

【面試題】 面向對象的三大特性:封裝、繼承、多态。

在類和對象階段,我們隻研究類的封裝特性,那什麼是封裝呢?

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

封裝本質上是一種管理:我們如何管理兵馬俑呢?比如如果什麼都不管,兵馬俑就被随意破壞了。那麼我們首先建了一座房子把兵馬俑給封裝起來。但是我們目的全封裝起來,不讓别人看。是以我們開放了售票通道,可以買票突破封裝在合理的監管機制下進去參觀。類也是一樣,我們使用類資料和方法都封裝到一下。不想給别人看到的,我們使用protected/private把成員封裝起來。開放一些共有的成員函數對成員合理的通路。是以封裝本質是一種管理。

【C++從青銅到王者】第二篇:C++類和對象(上篇)系列文章目錄前言一、面向過程和面向對象初步認識二、類的引入三、類的定義四、類的通路限定符及封裝五、類的作用域六、類的執行個體化七、類對象模型八、this指針總結

五、類的作用域

類定義了一個新的作用域,類的所有成員都在類的作用域中。在類體外定義成員,需要使用 :: 作用域解析符,指明成員屬于哪個類域。

代碼如下:

#define _CRT_SECURE_NO_WARNINGS   1
#include<iostream>
typedef int STDateType;
class Stack
{
public:
	void StackInit(int initSize = 4);
	void StackPush(STDateType x);
private:
	STDateType* a;
	int size;
	int capacity;
};
void Stack::StackInit(int initSize) //作用域符号::表示StackInit成員函數屬于Stack域
{
	a = (STDateType*)malloc(sizeof(STDateType)*initSize);
	size = 0;
	capacity = initSize;
}
void Stack::StackPush(STDateType x)//作用域符号::表示StackPush成員函數屬于Stack域
{
	a[size] = x;
	size++;
}
           

六、類的執行個體化

用類類型建立對象的過程,稱為類的執行個體化

  1. 類隻是一個模型一樣的東西,限定了類有哪些成員,定義出一個類并沒有配置設定實際的記憶體空間來存儲它。
  2. 一個類可以執行個體化出多個對象,執行個體化出的對象 占用實際的實體空間,存儲類成員變量。
  3. 做個比方。類執行個體化出對象就像現實中使用建築設計圖建造出房子,類就像是設計圖,隻設計出需要什麼東西,但是并沒有實體的建築存在,同樣類也隻是一個設計,執行個體化出的對象才能實際存儲資料,占用實體空間。
    【C++從青銅到王者】第二篇:C++類和對象(上篇)系列文章目錄前言一、面向過程和面向對象初步認識二、類的引入三、類的定義四、類的通路限定符及封裝五、類的作用域六、類的執行個體化七、類對象模型八、this指針總結

代碼如下:

#define _CRT_SECURE_NO_WARNINGS   1
#include<iostream>
typedef int STDateType;
class Stack
{
public:
	void StackInit(int initSize = 4);
	void StackPush(STDateType x);
private:
	STDateType* a;
	int size;
	int capacity;
};
class  A1
{

};
class A2
{
	void fun()
	{

	}
};

int main()
{
	Stack st; //類的執行個體化,建立一個對象st
	std::cout << sizeof(st) << std::endl;
	std::cout << sizeof(A1) << std::endl;
	std::cout << sizeof(A2) << std::endl;
	return 0;
}
           

七、類對象模型

1.如何計算類對象的大小

代碼如下:

class Stack
{
public:
	void StackInit(int initSize = 4);
	void StackPush(STDateType x);
private:
	STDateType* a;
	int size;
	int capacity;
};
class  A1
{

};
class A2
{
	void fun()
	{

	}
};
           

問題:類中既可以有成員變量,又可以有成員函數,那麼一個類的對象中包含了什麼?如何計算一個類的大小?

2 類對象的存儲方式猜測

1.對象中包含類的各個成員
【C++從青銅到王者】第二篇:C++類和對象(上篇)系列文章目錄前言一、面向過程和面向對象初步認識二、類的引入三、類的定義四、類的通路限定符及封裝五、類的作用域六、類的執行個體化七、類對象模型八、this指針總結
2.隻儲存成員變量,成員函數存放在公共的代碼段
【C++從青銅到王者】第二篇:C++類和對象(上篇)系列文章目錄前言一、面向過程和面向對象初步認識二、類的引入三、類的定義四、類的通路限定符及封裝五、類的作用域六、類的執行個體化七、類對象模型八、this指針總結

問題:對于上述兩種存儲方式,那計算機到底是按照那種方式來存儲的?

我們再通過對下面的不同對象分别擷取大小來分析看下:

【C++從青銅到王者】第二篇:C++類和對象(上篇)系列文章目錄前言一、面向過程和面向對象初步認識二、類的引入三、類的定義四、類的通路限定符及封裝五、類的作用域六、類的執行個體化七、類對象模型八、this指針總結

結論:一個類的大小,實際就是該類中”成員變量”之和,當然也要進行記憶體對齊,注意空類的大小,空類比較特殊,編譯器給了空類一個位元組來唯一辨別這個類。

3.結構體記憶體對齊規則

  1. 第一個成員在與結構體偏移量為0的位址處。
  2. 其他成員變量要對齊到某個數字(對齊數)的整數倍的位址處。

    注意:對齊數 = 編譯器預設的一個對齊數 與 該成員大小的較小值。VS中預設的對齊數為8

  3. 結構體總大小為:最大對齊數(所有變量類型最大者與預設對齊參數取最小)的整數倍。
  4. 如果嵌套了結構體的情況,嵌套的結構體對齊到自己的最大對齊數的整數倍處,結構體的整體大小就是所有最大對齊數(含嵌套結構體的對齊數)的整數倍。

面試題

  1. 結構體怎麼對齊? 為什麼要進行記憶體對齊?

    平台原因(移植原因):不是所有的硬體平台都能通路任意位址上的任意資料,某些硬 件平台隻能在某些位址處取某些特定類型的資料,否則抛出硬體異常

    硬體原因:經過記憶體對齊之後,CPU的記憶體通路速度大大提升。

  2. 如何讓結構體按照指定的對齊參數進行對齊?

    #pragma pack(n)//強制使對齊模數為n(不管VS和Linux)

  3. 什麼是大小端?如何測試某台機器是大端還是小端,有沒有遇到過要考慮大小端的場景?

    大端存儲模式:就是記憶體的低位址上存着資料的高位,高位址上存着資料的低位。

    小端存儲模式:就是記憶體的低位址上存資料的低位,而高位址上存資料的高位。

    大小端場景:代碼移植和網絡通信。

八、this指針

1.this指針的引出

我們先來定義一個日期類Date:

代碼如下:

class Date
{
public:
	void InitDate(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		std::cout << _year << ":" << _month << ":" << _day << std::endl;
	}
private:
	int _year;   //年
	int _month;  //月
	int _day;    //天
};
int main()
{
	Date d1;
	Date d2;
	d1._year = 2021;
	//d1._month = 5;
	//d1._day = 27;
	d1.InitDate(2021, 5, 27);
	d2.InitDate(2021, 5, 28);
	d1.Print();
	d2.Print();
	return 0;
}
           

我們不能通過執行個體化出來的對象d1或者d2直接去通路私有成員變量,而是通過公有成員函數取通路。

【C++從青銅到王者】第二篇:C++類和對象(上篇)系列文章目錄前言一、面向過程和面向對象初步認識二、類的引入三、類的定義四、類的通路限定符及封裝五、類的作用域六、類的執行個體化七、類對象模型八、this指針總結

對于上述類,還有這樣的一個問題:

Date類中有SetDate與Display兩個成員函數,函數體中沒有關于不同對象的區分,那當s1調用SetDate函數時,該函數是如何知道應該設定s1對象,而不是設定s2對象呢?

C++中通過引入this指針解決該問題,即:C++編譯器給每個“非靜态的成員函數“增加了一個隐藏的指針參數,讓該指針指向目前對象(函數運作時調用該函數的對象),在函數體中所有成員變量的操作,都是通過該指針去通路。隻不過所有的操作對使用者是透明的,即使用者不需要來傳遞,編譯器自動完成。

2.this指針的特性

  1. this指針的類型:類類型* const。
  2. 隻能在“成員函數”的内部使用。
  3. this指針本質上其實是一個成員函數的形參,是對象調用成員函數時,将對象位址作為實參傳遞給this形參。是以對象中不存儲this指針。
  4. this指針是成員函數第一個隐含的指針形參,一般情況由編譯器通過ecx寄存器自動傳遞,不需要使用者傳遞。
    【C++從青銅到王者】第二篇:C++類和對象(上篇)系列文章目錄前言一、面向過程和面向對象初步認識二、類的引入三、類的定義四、類的通路限定符及封裝五、類的作用域六、類的執行個體化七、類對象模型八、this指針總結

【面試題】

  1. this指針存在哪裡?

    其實編譯器在生成程式時加入了擷取對象首位址的相關代碼。并把擷取的首位址存放在了寄存器ECX中(VC++編譯器是放在ECX中,其它編譯器有可能不同)。也就是成員函數的其它參數正常都是存放在棧中。而this指針參數則是存放在寄存器中。類的靜态成員函數因為沒有this指針這個參數,是以類的靜态成員函數也就無法調用類的非靜态成員變量。

  2. this指針可以為空嗎?

    可以為空,當我們調用函數時,如果函數内部不需要使用到this,也就是不需要通過this指向目前對象并對其進行操作時才可以為空(當我們在其中什麼都不放或者在裡面随便列印一個字元串),如果調用的函數需要指向目前對象,并進行操作,則會發生錯誤(空指針引用)就跟C中一樣不能進行空指針的引用。

下面程式編譯能通過嗎?

代碼如下:

#define _CRT_SECURE_NO_WARNINGS   1
#include<iostream>
// 1.下面程式能編譯通過嗎?
// 2.下面程式會崩潰嗎?在哪裡崩潰
class A
{
public:
	void PrintA()
	{
		std::cout << _a << std::endl;
	}
	void Show()
	{
		std::cout << "Show()" << std::endl;
	}
private:
	int _a;
};
int main()
{
	A* p = nullptr;
	p->PrintA();
	p->Show();
}
           
  1. PrintA函數運作時會崩潰,因為要通路類A裡面的成員變量要通過this指針去通路,但是指針P是一個空指針,是以解引用去通路成員變量_a會發生崩潰。
  2. 能輸出Show(),因為Show成員函數雖然有this指針,但是并沒有用this指針去通路類A裡面的成員變量,是以能正常列印Show()。

總結

以上就是今天要講的内容,本文僅僅簡單介紹了類和對象(上)的使用,而類提供了大量能使我們快速便捷地處理資料的成員函數和成員變量,我們務必掌握。另外如果上述有任何問題,請懂哥指教,不過沒關系,主要是自己能堅持,更希望有一起學習的同學可以幫我指正,但是如果可以請溫柔一點跟我講,愛與和平是永遠的主題,愛各位了。

【C++從青銅到王者】第二篇:C++類和對象(上篇)系列文章目錄前言一、面向過程和面向對象初步認識二、類的引入三、類的定義四、類的通路限定符及封裝五、類的作用域六、類的執行個體化七、類對象模型八、this指針總結

繼續閱讀