C++深入講解類與對象之OOP面向?qū)ο缶幊膛c封裝
面向過程編程也叫結(jié)構(gòu)化編程。雖然結(jié)構(gòu)化編程的理念提高了程序的清晰度,可靠性,并且方便維護(hù)。但它再編寫大型的程序時,仍然面臨這巨大的挑戰(zhàn),OOP(面向?qū)ο缶幊?提供了一種新的方法。與強(qiáng)調(diào)算法的過程性編程不同的是,OOP強(qiáng)調(diào)的是數(shù)據(jù)。--引自《C++ Primer Plus(第六版)》
1.面向?qū)ο缶幊?/h2>
C++ 是 基于面向?qū)ο?的, 關(guān)注 的是 對象 ,將一件事情拆分成不同的對象,靠對象之間的交互完成。
在C++中,類是一種規(guī)范,它描述了這種新型數(shù)據(jù)格式,對象是根據(jù)這種規(guī)范構(gòu)造的特定數(shù)據(jù)結(jié)構(gòu)。這里有小伙伴會問,類是什么?這個問題會在(3.類的引入) 中重點(diǎn)介紹。
2.面向過程性編程和面向?qū)ο缶幊?/h2>
通過下面這個例子,可以更加清晰的揭示OOP的觀點(diǎn)和過程性編程的差別。
此舉例改變自《C++ Primer Plus(第六版)》:
曼聯(lián)足球俱樂部的一名新成員被要求記錄球隊(duì)的統(tǒng)計數(shù)據(jù)。很自然他會借助計算機(jī)來完成這項(xiàng)任務(wù)。
如果這個新成員是過程性程序員,可能會這樣考慮:
我要輸入每名運(yùn)動員的姓名,進(jìn)球數(shù),助攻數(shù),登場數(shù)等其他重要的基本統(tǒng)計數(shù)據(jù)。之所以使用計算機(jī),是為了簡化統(tǒng)計工作,因此讓他來計算某些數(shù)據(jù)。另外,我還希望程序能夠顯示這些結(jié)果。應(yīng)該如何組織呢?我讓main()調(diào)用一個函數(shù)來獲取輸入,調(diào)用另外一個函數(shù)來進(jìn)行計算,然后調(diào)用第三個函數(shù)來顯示結(jié)果。那么,獲得下一場比賽的數(shù)據(jù)后,又改怎么做呢?當(dāng)然不想從頭開始,可以添加一個函數(shù)來更新統(tǒng)計數(shù)據(jù)。可能需要在main函數(shù)中添加一個菜單,選擇是輸入,計算,更新還是顯示數(shù)據(jù)等。則如何表示這些數(shù)據(jù)呢??梢允褂靡粋€字符串來存儲選手的姓名,用另外一個數(shù)組來存儲每位球員的進(jìn)球數(shù),再用一個數(shù)組存儲助攻數(shù)等等。這種方法太不靈活了。因此可以設(shè)計一個結(jié)構(gòu)體來存儲每位球員的所有信息,然后用這種結(jié)構(gòu)組成的數(shù)組來表示整個球隊(duì)。
總之,采用過程性編程時,首先要考慮遵守的步驟,然后考慮如何表示這些數(shù)據(jù)。
如果換成一個OOP程序員,又將如何考慮呢?
首先要考慮數(shù)據(jù)——不僅要考慮如何表示數(shù)據(jù),還要考慮如何使用數(shù)據(jù):
OOP程序員會想,我要跟蹤的是什么?當(dāng)然是球員。因此要有一個對象表示整個球員的各個方面(不僅僅是進(jìn)球數(shù)或助攻數(shù))。因此這將是基本數(shù)據(jù)單元——一個表示球員的姓名和統(tǒng)計數(shù)據(jù)的對象。我需要一些處理該對象的方法。首先需要一種將基本信息加入到該單元中的方法;其次,計算機(jī)應(yīng)計算一些東西,如進(jìn)球率。因此要添加一些執(zhí)行計算的方法。程序應(yīng)自動完成這些計算,而無需用戶的干擾。另外,還需要一些更新和顯示信息的方法。所以,用戶與數(shù)據(jù)交互的方式有三種:初始化,更新和報告——這就是用戶接口。
總之,采用OOP方法時,首先從用戶的角度考慮對象——描述對象所需的數(shù)據(jù)以及描述用戶與數(shù)據(jù)交互所需的操作。完成對接口的描述之后,需要確定如何實(shí)現(xiàn)接口和數(shù)據(jù)存儲。最后,使用尋得設(shè)計方案創(chuàng)建出程序。
3.類的引入
在過程化編程中我們用結(jié)構(gòu)體來描述一個復(fù)雜對象(這里用C語言舉例)。在C語言中,結(jié)構(gòu)體中只能定義變量。結(jié)構(gòu)體關(guān)鍵字是struct。在C++中,結(jié)構(gòu)體內(nèi)不僅可以定義變量,還可以定義函數(shù)
struct Student
{
void SetStudentInfo(const char* name, const char* gender, int age)
{
strcpy(_name, name);
strcpy(_gender, gender);
_age = age;
}
void PrintStudentInfo()
{
cout << _name << " " << _gender << " " << _age << endl;
}
char _name[20];
char _gender[3];
int _age;
};
int main()
{
Student s;
s.SetStudentInfo("Peter", "男", 18);
return 0;
}上面結(jié)構(gòu)體的定義, 在 C++ 中更喜歡用 class 來代替
4.類的定義
class className
{
// 類體:由成員函數(shù)和成員變量組成
}; // 一定要注意后面的分號
class為定義類的關(guān)鍵字,ClassName為類的名字,{}中為類的主體,注意類定義結(jié)束時后面分號。
類中的元素稱為類的成員:類中的數(shù)據(jù)稱為類的屬性或者成員變量; 類中的函數(shù)稱為類的方法或者成員函數(shù)。
4.1類的兩種定義方式
4.1.1聲明和定義全部放在類體中
需要注意:成員函數(shù)如果在類中定義 ,編譯器可能會將其當(dāng)成 內(nèi)聯(lián)函數(shù) 處理。
class Student
{
public:
void SetStudentInfo(const char* name, const char* gender, int age)
{
strcpy(_name, name);
strcpy(_gender, gender);
_age = age;
}
void PrintStudentInfo()
{
cout << _name << " " << _gender << " " << _age << endl;
}
public:
char _name[20];
char _gender[3];
int _age;
};4.2.2.聲明和定義不放在類體中
聲明放在.h文件中,類的定義放在.cpp文件中
//student.h
//學(xué)生
class Student
{
public:
void SetStudentInfo(const char* name, const char* gender, int age);
void PrintStudentInfo();
public:
char _name[20];
char _gender[3];
int _age;
};
//test.cpp
#include "student.h"
void Student::SetStudentInfo(const char* name, const char* gender, int age)
{
strcpy(_name, name);
strcpy(_gender, gender);
_age = age;
}
void Student::PrintStudentInfo()
{
cout << _name << " " << _gender << " " << _age << endl;
}

一般情況下,更期望采用第二種方式。
5.類的訪問限定符及封裝
5.1 訪問限定符
在剛剛的代碼中,細(xì)心的小伙伴可以發(fā)現(xiàn)在類中出現(xiàn)了public這個詞,那這到底有什么用呢?這就是我們現(xiàn)在要說明的訪問限定符。在C++中,除了public(公有)外,還有private(私有),protected(保護(hù))限定符。
那么為什么要引入訪問限定符呢?
C++實(shí)現(xiàn)封裝的方式:用類將對象的屬性與方法結(jié)合在一塊,讓對象更加完善,通過訪問權(quán)限選擇性的將其接口提供給外部的用戶使用。

那么他們都有什么含義呢?
【訪問限定符說明】
1. public修飾的成員在類外可以直接被訪問
2. protected和private修飾的成員在類外不能直接被訪問(此處protected和private是類似的)
3. 訪問權(quán)限作用域從該訪問限定符出現(xiàn)的位置開始直到下一個訪問限定符出現(xiàn)時為止
4. class的默認(rèn)訪問權(quán)限為private,struct為public(因?yàn)閟truct要兼容C)
注意:
1.訪問限定符只在編譯時有用,當(dāng)數(shù)據(jù)映射到內(nèi)存后,沒有任何訪問限定符上的區(qū)別。
2.C++需要兼容C語言,所以C++中struct可以當(dāng)成結(jié)構(gòu)體去使用。另外C++中struct還可以用來定義類。和class是定義類是一樣的,區(qū)別是struct的成員默認(rèn)訪問方式是public,class是的成員默認(rèn)訪問方式是private。
5.2封裝
什么是封裝?
封裝:將數(shù)據(jù)和操作數(shù)據(jù)的方法進(jìn)行有機(jī)結(jié)合,隱藏對象的屬性和實(shí)現(xiàn)細(xì)節(jié),僅對外公開接口來和對象進(jìn)行交互。封裝的本質(zhì)是一種管理。 我們使用類數(shù)據(jù)和方法都封裝到一下。 不想給別人看到的,我們使用 protected/private 把成員 封裝 起來。 開放 一些共有的成員函數(shù)對成員合理的訪 問。所以封裝本質(zhì)是一種管理。
在C語言中,大多數(shù)情況中調(diào)用者和定義結(jié)構(gòu)者不是同一個人,就可能會存在調(diào)用者測出bug的可能。
//C語言中數(shù)據(jù)和方法是分離的
struct Stack
{
int* _a;
int _top;
int _capacity;
};
void StackInit(struct Stack* ps)
{
assert(ps);
ps->_a = NULL;
ps->_capacity = 0;
ps->_top = 0;
}
void StackPush(struct Stack* ps, int x)
{
}
struct Stack StackTop(struct Stack* ps)
{
}
int main()
{
struct Stack st;
StackInit(&st);
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
printf("%d\n", StackTop(&st));
printf("%d\n", st._a[st._top]); //可能就存在誤用
printf("%d\n", st._a[st._top - 1]); //可能就存在誤用
}這是我們在數(shù)據(jù)結(jié)構(gòu)階段用C語言實(shí)現(xiàn)的一個棧,在主函數(shù)中,我們想要訪問棧頂?shù)脑?。在常?guī)情況下,我們調(diào)用StackTop函數(shù)即可訪問到棧頂元素。但是我們也可以使用訪問數(shù)組下標(biāo)的方式拿到棧頂元素,此時如果調(diào)用者不清楚使用者的定義方式,就有可能存在誤用。例如:這段代碼我們定義_top是棧頂元素的下一個元素的下標(biāo),因此棧頂元素的下標(biāo)應(yīng)該是_top-1,而調(diào)用者如果誤以為top就是棧頂元素的下標(biāo),即有可能存在誤用。因此這里太過自由。
為了解決這一問題,在C++中,結(jié)構(gòu)體不僅可以定義變量,還可以定義函數(shù)。我們?nèi)绻押瘮?shù)定義在類中,我們把成員變量封裝在類中,外界函數(shù)無法調(diào)用。因此如果此時我們想調(diào)用棧頂元素,我們只能調(diào)用Top函數(shù)的接口。這就避免了上述問題的發(fā)生。
class Stack
{
private:
void Checkcapacity()
{
}
public:
void Init()
{
}
void Push(int x)
{
}
void Top()
{
}
private:
int* _a;
int _top;
int _capacity;
};6.類的作用域
類定義了一個新的作用域 ,類的所有成員都在類的作用域中 。 在類體外定義成員,需要使用 :: 作用域解析符指明成員屬于哪個類域。 就像在這段代碼中,我們想要在類作用域外定義成員,就要使用::

7.類的實(shí)例化
用類類型創(chuàng)建對象的過程,稱為類的實(shí)例化
1. 類只是 一個 模型 一樣的東西,限定了類有哪些成員,定義出一個類 并沒有分配實(shí)際的內(nèi)存空間 來存儲它
2. 一個類可以實(shí)例化出多個對象, 實(shí)例化出的對象 占用實(shí)際的物理空間,存儲類成員變量
3. 做個比方。 類實(shí)例化出對象就像現(xiàn)實(shí)中使用建筑設(shè)計圖建造出房子,類就像是設(shè)計圖 ,只設(shè)計出需要什么東西,但是并沒有實(shí)體的建筑存在,同樣類也只是一個設(shè)計,實(shí)例化出的對象才能實(shí)際存儲數(shù)據(jù),占用物理空間

我們繼續(xù)引用我們剛剛用C++所寫的棧,其中st就是一個實(shí)例化對象。
class Stack
{
private:
void Checkcapacity()
{
}
public:
void Init()
{
}
void Push(int x)
{
}
void Top()
{
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack st;
st.Init();
st.Push(1);
st.Top();
return 0;
}
8.類對象模型
如何計算類對象的大小
在C語言中,我們在學(xué)習(xí)結(jié)構(gòu)體的時候知道,由于結(jié)構(gòu)體中只定義變量,因此我們是可以計算出結(jié)構(gòu)體的大小的。sizeof計算的是定義類型對象的大小。

那在C++中,由于類中不僅定義變量,還定義函數(shù),那么類的大小是怎么計算的呢?

我們發(fā)現(xiàn)此類的大小還是12。
因此我們猜測:類對象的存儲方式只保存成員變量,成員函數(shù)存放在公共的代碼段。

那我們思考為什么采用這種方式呢?
在上述中說到,類就像是一份建筑圖紙,而所建造的每一個房子中的name,capacity,top應(yīng)當(dāng)是不一樣的。但是所調(diào)用的方法Init(),Top()應(yīng)當(dāng)是同一個方法。因此沒有必要把函數(shù)在對象中存一份。我們也可以通過匯編看看不同的對象是否調(diào)用同一個函數(shù)。

我們能夠發(fā)現(xiàn)st1和st2所調(diào)用得Init()函數(shù)是同一份。因此如果都把函數(shù)存在類中,就會造成浪費(fèi)。因此我們可以把函數(shù)放在一個公共的區(qū)域,這個區(qū)域叫做代碼段。

結(jié)論:一個類的大小,實(shí)際就是該類中”成員變量”之和,當(dāng)然也要進(jìn)行內(nèi)存對齊,注意空類的大小,空類比較特殊,編譯器給了空類一個字節(jié)來唯一標(biāo)識這個類。注意:最小內(nèi)存單元是1.操作系統(tǒng)規(guī)定都要有地址記錄,就像sizeof(void) = 1。
到此這篇關(guān)于C++深入講解類與對象之OOP面向?qū)ο缶幊膛c封裝的文章就介紹到這了,更多相關(guān)C++類與對象內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++類重載函數(shù)的function和bind使用示例
這篇文章主要介紹了C++類重載函數(shù)的function和bind使用示例,幫助大家更好的理解和使用c++,感興趣的朋友可以了解下2021-01-01
C語言中結(jié)構(gòu)體struct編寫的一些要點(diǎn)解析
這篇文章主要介紹了C語言中結(jié)構(gòu)體struct編寫的一些要點(diǎn)解析,談到了結(jié)構(gòu)體的聲明和指針指向等重要知識點(diǎn),需要的朋友可以參考下2016-04-04
一文帶你了解C語言中的0長度數(shù)組(可變數(shù)組/柔性數(shù)組)
眾所周知,?GNU/GCC?在標(biāo)準(zhǔn)的?C/C++?基礎(chǔ)上做了有實(shí)用性的擴(kuò)展,?零長度數(shù)組(Arrays?of?Length?Zero)?就是其中一個知名的擴(kuò)展,本文就來聊聊零長度數(shù)組的相關(guān)知識吧2023-03-03

