文章目錄
- 一:面向物件與面向程序
- 二:類的引入
- 三:類的定義
- (1)C++類的定義
- (2)類的兩種定義方式
- A:宣告和定義全部放在類體中
- B:定義和宣告分開放
- 四:類的訪問限定符及封裝
- (1)訪問限定符
- (2)封裝
- 五:類的實體化
- (1)類的實體化
- (2)面向物件
- 六:類物件模型
- (1)如何計算類物件的大小
- (2)類物件的存盤方式
- 七:this指標
- (1)一個問題
- (2)this指標
- (3)this指標存在哪里?
- 附:視頻
一:面向物件與面向程序
見下文
二:類的引入
學習C語言時,我們知道,結構體的作用是把一些具有不同型別的變陣列合一起
struct Student
{
char _name[20];
char _gender[3];
int _age;
}
而在C++中,其結構體內不止可以定義變數,而且還可以定義函式、
struct Student
{
char _name[20];
char _gender[3];
int _age;
void SetStudentInfo(const char* name,const char* gender,int age)
{
strcpy(_name,name);
strcpy(_gender,gender);
_age=age;
}
};
int main()
{
Student s;
s.SetStudentInfo("Bob","男",18);
return 0;
}
這樣定義后,結構體就可以被稱為類,只不過在C++中,我們更喜歡使用class代替struct
三:類的定義
(1)C++類的定義
根據以上敘述,所以在C++中,類是這樣定義的
class className
{
//成員函式
//成員變數
};//注意分號
所以簡單來說,類就是屬性和方法的集合,屬性就是類中的資料,方法就是呼叫這些資料進行操作的函式
(2)類的兩種定義方式
A:宣告和定義全部放在類體中
要注意,成員函式如果在類中定義,編譯器會將其當作行內函式處理,
class Person//人就是一個類
{
public://成員函式
void showinfo()//顯示這個人的資訊
{
std::cout<<_name<<std::endl;
std::cout<<_sex<<std::endl;
std::cout<<_age<<std::endl;
}
public://成員變數
char* _name;
char* _sex;
int _age;
};
B:定義和宣告分開放
這種方式最常采用

四:類的訪問限定符及封裝
(1)訪問限定符
在前面類的定義中,有一個public,它屬于訪問限定符,除了public(公有)還有protected(保護),private(私有),他們用于修飾成員,比如public修飾的成員可以在類的外面直接被訪問,而是用protected和private修飾的在類的外面不可以被直接訪問

注意以下幾點
- 訪問限定符的作用域是從該訪問限定符出現的位置開始直到下一個訪問限定符為止
- class的默認訪問權限(就是沒有給出)為private,struct則為public
- 訪問限定符限制的是外面,就像鎖子防的是外人
(2)封裝
我們知道面向物件三大特性:封裝,繼承,多型
封裝本質是一種管理手段,將屬性(資料)和方法(介面)有機結合起來,再隱藏他們,只對外公開介面(函式)來和物件進行互動,不想給別人看的,使用protected/private進行修飾,而開放一些公有函式對成員進行合理訪問,合理訪問可以理解為閱讀,不合理訪問可以理解為改寫(不準確),
這里也就能說明,為什么C語言不支持大型專案的撰寫,因為它不支持面向物件,管理能力較弱,不是函式就是資料,就拿struct來說,其默認就是public的,安全性也堪憂,總的來說,用C語言撰寫專案,會感覺很亂,模塊與模塊獨立性很差,
之前使用C語言實作過很多資料結構,比如說堆疊,我們都是新建一個工程然后分別建立它的宣告,實作等檔案,也就是說資料和方法是分離的,是自由的,如果換到C++中,按照面向物件考慮,一個堆疊就可以作為一個物件,這個物件有它的資料(動態增長的陣列,長度,容量),還有它的方法(堆疊的初試線,壓堆疊,出堆疊等等),像這些資料,應該就是私有的,因為要防止外部操作改變資料,一旦資料出錯,那么方法也就會受到相應的波及,然后這些操作按照實際需求讓外部使用,
如:
//stack.h
class Stack//類
{
public:
void Init(int capacity=4);//預設引數,宣告
private: //資料私有
int* _arr;
int _length;
int _capacity;
//stack.cpp
void Stack::Init(int capacity)//該方法的實作
{
_arr=(int*)malloc(sizeof(int)*capacity);
_length=0;
_capactiy=capacity;
}
五:類的實體化
(1)類的實體化
類可以理解為一個房屋的圖紙,這個圖紙規定了一些基本資訊,比如說這個房子朝向是什么,有幾扇窗戶,房子材料是什么等等,但是圖紙終歸就是圖紙,紙上談兵永遠不會成為現實,要把這個圖紙現實化,就要根據這個圖紙的規定,修出相應的房子,當然,不按照圖紙也能修,只不過修出來的房子可能成為四不像,或者不安全,這也就像C語言,沒有圖紙,介面隨意寫,資料任你改,是很自由,但是稍有不慎,房子就可能用不久了,
所以說根據圖紙建造房子的程序,稱為類的實體化,就像前面的堆疊一樣,實體化出一個真正的堆疊,什么叫做真正的堆疊——這個堆疊它占用實際空間,一個圖紙不可能只能造出一個房子,而是可以造出千千萬萬個房子,這個諸多房子本原都是一個,要想讓他們之間有所區別,這取決于使用者,比如你和我都按照這個圖紙了建造了相同的房子,但是我們對房子的裝飾不同,所以看起來也就像兩個不同的房子,回到堆疊,我們可以實體化出許許多多的堆疊,但是有些堆疊用于進行非遞回操作,有些堆疊用于排序操作,他們就不是一樣的了,
比如前面的堆疊可以實體化為
//stack.h
class Stack//類
{
public:
void Init(int capacity=4);//預設引數,宣告
private: //資料私有
int* _arr;
int _length;
int _capacity;
//stack.cpp
void Stack::Init(int capacity)//該方法的實作
{
_arr=(int*)malloc(sizeof(int)*capacity);
_length=0;
_capactiy=capacity;
}
//test.cpp
int main()
{
Stack stack;
stack._arr=4;//操作非法,成員是private的
stack.Init();//初始化這個堆疊
}
(2)面向物件
面向物件編程:其實很多學習編程的人,對于面向程序編程和面向物件編程這兩個概念總是搞不清,具體的專業的定義在這里也不去說了,根據上面的敘述,我們可以這樣去通俗的解釋面向物件,
舉個例子:我去洗澡,如果按照面向程序的角度考慮,那么我先進入浴室,然后打開水龍,然后洗漱,然后把身體擦干,也就是說面向程序關注的是解決問題的步驟;如果用面向物件考慮,只需記住一句話,萬物皆物件,你是物件,水龍頭也是物件,所以我先傳遞力的引數給浴室門,然后門就開了,然后我在傳遞訊息給水龍頭,水龍頭得到訊息,放水,最后傳遞訊息給毛巾,毛巾利用它吸水的特性,呼叫吸水方法擦干身體,也就是說面向物件關注的是物件,將一個問題拆分為不同物件,依靠引數完成物件之間的互動,
**為什么要進行面向物件編程?**這也是一個很值得思考的問題,舉個例子,活字印刷術發明之前使用的是雕版印刷,這種方式弊端太大,如果需要改稿,那么雕版就必須重新雕刻,而活字印刷術則解決了這樣的問題,需要改動,有可能只需改動幾處,面向程序正如雕版印刷一樣,也正如做數學題一樣,中間某個環節一旦出現需求變更,那么整個工程幾乎需要大改,要耗費大量時間精力,面向物件正如活字印刷術一樣,如果需求變化了,可能只是修改其中一部分,也就是一個物件,而且最關鍵的一點是這些物件可以服用,進行活字印刷術一樣,不是說這個物件在這個工程中發揮完之后,它就沒有價值了,它還可能被其他工程所用,
六:類物件模型
(1)如何計算類物件的大小
下面這個類實體化后所占空間大小幾何
class A
{
public:
void printA
{
std::cout<<a<std::endl;
}
private:
char_a;
};
猜測:結合之前C語言學習結構體記憶體對齊,這個類中成員變數和成員函式,其中char占用1個位元組,成員函式實際是一個函式地址,所以在32位平臺下,一個指標占用4個位元組,所以總共是5個,然后根據記憶體,所以占用8個,究竟是不是這樣,可以執行

結果顯示只有一個位元組,不是預想的八個位元組,再次觀察這個類,也能想到類在計算大小時不管成員函式,只管成員變數和記憶體對齊,
那么成員函式究竟在哪?
(2)類物件的存盤方式
根據以上敘述,不禁會提出一個疑問:為什么成員函式不在類內?
其實:一個類可以實體化很多物件,就拿堆疊來說,這些堆疊它的存盤的資料可能會不一樣,但是它們的方法,如進堆疊出堆疊都是一樣的,所以說實體化時,如果為他們再開辟空間,就會造成浪費
所以解決方法是:只保存成員變數,成員函式存放在公共的代碼段(常量區)

- 注意:一般情況下,一個類的大小,實際就是該類中的成員變數和記憶體對齊,但是要注意空類,空類大小不是0,編譯器為空類提供了一個位元組表示它存在

七:this指標
(1)一個問題
定義一個日期類
class Date
{
public:
void SetDate(int year,int month,int day)
{
_year=year;
_month=month;
_day=day;
}
private:
int _year;
int _month;
ibt _day;
}
int main()
{
Date d1;
Date d2;
d1.SetDate(2021,2,20);
d2.SetDate(2022,8,19);
}
上述代碼中看起來沒有問題,但是容易忽略一個細節,類中成員函式是公用的,它并不存在于類中,那么d1.SetDate(2021,2,20),編譯器怎么會知道要給d1這個類去設定,而不會給d2去設定,這里很多有疑問,不是函式前面已經寫了一個d1嗎,這里大家一定要跳出這個誤區,函式只是放在了公用的代碼段,如果函式是放在類里的,那無可厚非,但是當放在公用的代碼段后,不要說用實體化的類去呼叫它,就是我先如果知道函式的地址的話,用匯編都可以去呼叫,那么當無數個類去呼叫這個函式時,編譯器怎么會分的這么清楚,
(2)this指標
這個問題在C++中,是通過引入this指標來解決的,官方定義:C++編譯器給每個非靜態的成員函式增加了一個隱藏的指標引數,讓該指標指向當前函式(函式運行時呼叫該函式的物件),在函式體中所有成員變數的操作,都是通過該指標訪問,
所以如果上述代碼,寫完整應該是下面這樣的
class Date
{
public:
void SetDate(Date* this,int year,int month,int day)
{
//生成一個類指標
this->_year=year;
this->_month=month;
this->_day=day;//通過指標的指向來辨別物件
}
private:
int _year;
int _month;
ibt _day;
}
int main()
{
Date d1;
Date d2;
d1.SetDate(&d1,2021,2,20);
d2.SetDate(&d2,2022,8,19);所以實參里,其實就是類的地址
}
所以這樣就是為什么編譯器可以分清楚的原因
(3)this指標存在哪里?
這是一個極高頻率的面試題
談及C/C++的記憶體開辟,就不得不上這一張圖了

this指標是作為一個函式隱藏的形參,而堆疊是存放區域變數,形參的,所以,this指標存在于堆疊中
還有一個經典的題,如下請問這段程式是否會崩潰
class A
{
public:
void printA()
{
std::cout << _a << std::endl;
}
void show()
{
std::cout << "Show()" << std::endl;
}
private:
int _a;
};
int main()
{
A* p = NULL;
p->printA();
p->show();
}
這段程式的結果是,如果只執行p->printA()就會崩潰,如果只執行p->show()就不會崩潰,
如下

仔細分析,未免讓人覺著有點匪夷所思,A* p= NULL,表明p是一個空的類指標,那么既然是空的就不能對空指標進行操作,但是觀察上述兩種情況,p->printA()報錯了,p->show()并沒有報錯,其實原因就是在于這this指標,首先對于show()和printA()來說,他們不存在與類內,而存在于公共的代碼段內,所以使用指標p進行這樣的操作,并不是在取成員,所以問題不在于空指標,
問題在于:呼叫printA()時,將這個p指標作為this指標傳了過去,那么在函式中就會進行std::cout<<this->_a<<std::endl;的操作,自然而然會出現問題,而另一個函式沒有崩潰是因為它就沒有使用到這個this指標
附:視頻
C++:類和面向物件很懵?其實很簡單
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/261820.html
標籤:其他
上一篇:小馬商城api介面檔案
