前言
??面向過程
C語言是面向過程的,關(guān)注的是過程,分析出求解問題的步驟,通過函數(shù)調(diào)用逐步解決問題。以洗衣服這件事為例,下圖是C語言完成洗衣服這件事的過程。??面向?qū)ο?/strong>
C++是基于面向?qū)ο蟮?/strong>,關(guān)注的是對(duì)象,將一件事情拆分成不同的對(duì)象,靠對(duì)象之間的交互完成。針對(duì)洗衣服這件事,C++會(huì)設(shè)置四個(gè)對(duì)象:人、衣服、洗衣粉、洗衣機(jī)。整個(gè)洗衣服的過程就變成了:人將衣服放進(jìn)洗衣機(jī)、倒入洗衣粉、啟動(dòng)洗衣機(jī),洗衣機(jī)就會(huì)完成洗衣過程并甩干。整個(gè)洗衣服的過程,是人、衣服、洗衣粉、洗衣機(jī)四個(gè)對(duì)象之間交互完成的,人不需要關(guān)心洗衣機(jī)具體是如何洗衣服的。
一、類的引入
C語言結(jié)構(gòu)體中只能定義變量,在C++中,結(jié)構(gòu)體不僅可以定義變量,也可以定義函數(shù)。比如:之前用C語言方法實(shí)現(xiàn)的棧,結(jié)構(gòu)體中只能定義變量,而現(xiàn)在C++方式實(shí)現(xiàn),struct中也可以定義函數(shù)。
??C語言版
typedef int DataType;
struct Stack
{
DataType* _array;
size_t _capacity;
size_t _size;
};
struct Stack s;//聲明一個(gè)結(jié)構(gòu)體變量
結(jié)構(gòu)體中只能定義變量,并且聲明棧類型的變量時(shí),必須寫全struct Stack
。
??C++版
typedef int DataType;
struct Stack
{
//函數(shù)
void Init(size_t capacity)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (nullptr == _array)
{
perror("malloc申請(qǐng)空間失敗");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(const DataType& data)
{
// 擴(kuò)容
_array[_size] = data;
++_size;
}
DataType Top()
{
return _array[_size - 1];
}
void Destroy()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
//變量
DataType* _array;
size_t _capacity;
size_t _size;
};
Stack s;//聲明一個(gè)棧類型對(duì)象
s.Init();//調(diào)用類里面的函數(shù)
s.Push(10);
s.Push(20);
s.Push(30);
s.Push(40);
int ret = s.Top();
s.Destroy();
C++中,struct
結(jié)構(gòu)體升級(jí)成了類,它里面不僅可定義變量,還可以定義函數(shù),并且聲明棧類型變量的時(shí)候,可以不加struct
。聲明的變量可以通過.
去調(diào)用類里面的函數(shù)。
二、類的定義
class className
{
//類體:由成員變量和成員函數(shù)組成
};//不要忘了分號(hào)
C++中可以用struct
來定義一個(gè)類(把C語言中的結(jié)構(gòu)體升級(jí)了),但更多的是使用class
關(guān)鍵字來定義類。class后面跟類名,{}
中的是類的主體。注意:類定義結(jié)束時(shí)后面分號(hào)不能省略。
類體中的內(nèi)容稱為類的成員:類中的變量稱為,類的屬性或成員變量;類中的函數(shù)稱為類的方法或者成員函數(shù)。
??類的兩種定義方法:
- 聲明和定義全部放在類體中。
//定義一個(gè)人的類
class Person
{
//成員函數(shù)——顯式基本信息
void showInfo()
{
cout << _name << "-" << _sex << "-" << _age << "-" << endl;
}
//成員變量
char* _name;//姓名
char* _sex;//性別
int _age;//年齡
};
注意:成員函數(shù)如果在類中定義,編譯器可能會(huì)將其當(dāng)成內(nèi)聯(lián)函數(shù)處理,最終是否真的是內(nèi)聯(lián),還是由編譯器說了算。
- 類的聲明放在
.h
文件中,成員函數(shù)的定義放在.cpp
文件中。
Person.h文件
//定義一個(gè)人的類
class Person
{
//成員函數(shù)——顯式基本信息
void showInfo();
//成員變量
char* _name;//姓名
char* _sex;//性別
int _age;//年齡
};
Person.cpp文件
#include "Person.h"
void Person::showInfo()
{
cout << _name << "-" << _sex << "-" << _age << "-" << endl;
}
注意:成員函數(shù)名前需要加類名::
,告訴編譯器這個(gè)函數(shù)屬于哪個(gè)類域,否則編譯器不知道成員函數(shù)里面的成員變量是哪里的。
小Tips:一般情況下,更期望采用第二種方法來定義類。
三、類的訪問限定符及封裝
??訪問限定符
C++實(shí)現(xiàn)封裝的方法:用類將對(duì)象的屬性與方法結(jié)合在一塊,讓對(duì)象更加完善,通過訪問權(quán)限,選擇性的將其接口(成員函數(shù))提供給外部的用戶使用。C++提供了三種限制權(quán)限的訪問限定符:public
、protected
、private
。
??訪問限定符說明
- 訪問限定符可以修飾類中的成員變量、成員函數(shù)、內(nèi)部類、重命名類型。友元函數(shù)的聲明不受訪問限定符的限制。
-
public
修飾的成員,在類外面可以直接被訪問(通過對(duì)象.
訪問)。 -
protected
和private
修飾的成員在類外面不能直接被訪問,目前我們認(rèn)為它們兩個(gè)一樣,只有在繼承的時(shí)候,這倆才有區(qū)別。 -
訪問權(quán)限的作用域:從該訪問限定符出現(xiàn)的位置開始,直到下一個(gè)訪問限定符出現(xiàn)為止。如果后面沒有訪問限定符,作用域就到
}
即類結(jié)束。 - class的默認(rèn)訪問權(quán)限是private,struct為了兼容C語言,默認(rèn)訪問權(quán)限是public。
- 訪問限定符只在編譯時(shí)有用,當(dāng)數(shù)據(jù)映射到內(nèi)存后,沒任何訪問限定符上的區(qū)別。
??封裝
封裝就是:把數(shù)據(jù)和操作數(shù)據(jù)的方法進(jìn)行有機(jī)的結(jié)合,隱藏對(duì)象的屬性和實(shí)現(xiàn)細(xì)節(jié),僅對(duì)外公開接口來和對(duì)現(xiàn)象進(jìn)行交互。
封裝本質(zhì)上是一種管理,讓用戶更方便使用類。比如:對(duì)于電腦這樣一個(gè)復(fù)雜的設(shè)備,提供給用戶的只有開關(guān)機(jī)鍵、通過鍵盤輸入,顯示器、USB插孔等,讓用戶和計(jì)算機(jī)進(jìn)行交互,完成日常事務(wù)。但實(shí)際上,電腦真正工作的卻是CPU、顯卡、內(nèi)存等一些硬件原件。
對(duì)于計(jì)算機(jī)的使用者而言,不用關(guān)心內(nèi)部核心部件,比如主板上線路是如何布局的,CPU內(nèi)部是如何設(shè)計(jì)的等等,用戶只需要知道,怎么開機(jī)、怎么通過鍵盤和鼠標(biāo)與計(jì)算機(jī)進(jìn)行交互即可。因此計(jì)算機(jī)廠商在出廠時(shí),在外部套上殼子,將內(nèi)部實(shí)現(xiàn)細(xì)節(jié)隱藏起來,僅僅對(duì)外提供開關(guān)機(jī)、鼠標(biāo)以及鍵盤插孔等,讓用戶可以與計(jì)算機(jī)進(jìn)行交互即可。
四、類的作用域
類定義了一個(gè)新的作用域,類的所有成員都在類的作用域中。在類體外定義成員時(shí),需要::
作用域限定符指明成員屬于哪個(gè)類域。不同的類域里面可以定義同名變量,
Person.h文件
//定義一個(gè)人的類
class Person
{
//成員函數(shù)——顯式基本信息
void showInfo();
//成員變量
char* _name;//姓名
char* _sex;//性別
int _age;//年齡
};
Person.cpp文件
#include "Person.h"
void Person::showInfo()
{
cout << _name << "-" << _sex << "-" << _age << "-" << endl;
}
如上面的成員函數(shù)showInfo
,對(duì)于函數(shù)體中出現(xiàn)的變量_name
等,編譯器會(huì)先在當(dāng)前函數(shù)的局部域中搜索,如果沒有找到,接下來會(huì)到對(duì)應(yīng)的類域里面去搜索,當(dāng)類域里面也沒有的時(shí)候,最后回到全局區(qū)搜索,如果全局也沒有,編譯就會(huì)報(bào)錯(cuò)。
注意:所有的域都會(huì)影響訪問,但是只有全局域和局部域會(huì)影響生命周期,而類域和命名空間域不會(huì)影響聲明周期。
五、類的實(shí)例化
用類類型創(chuàng)建對(duì)象的過程,稱為類的實(shí)例化。
類是對(duì)對(duì)象進(jìn)行描述的,是一個(gè)模型一樣的東西,限定了類有哪些成員,定義出一個(gè)類并沒有分配實(shí)際的內(nèi)存空間來存儲(chǔ)它。類和對(duì)象的關(guān)系可以看成,拿圖紙建房子的過程,圖紙就是類,建出來的一棟棟房子,就是一個(gè)個(gè)的對(duì)象。一個(gè)類可以實(shí)例化出多個(gè)對(duì)象,就像一個(gè)圖紙,可以建成很多房子一樣,實(shí)例化出的對(duì)象占用實(shí)際的物理空間,存儲(chǔ)類成員變量。類里面不能存數(shù)據(jù),就像圖紙里面不能住人一樣。
//定義一個(gè)人的類
class Person
{
public:
//成員函數(shù)——顯式基本信息
void showInfo()
{
cout << _name << "-" << _sex << "-" << _age << "-" << endl;
}
public:
//成員變量
const char* _name;//姓名
const char* _sex;//性別
int _age;//年齡
};
int main()
{
Person p1;//用類實(shí)例化一個(gè)對(duì)象p1
p1._name = "王華";
p1._sex = "男";
p1._age = 10;
//Person._name = "黎明";//這是錯(cuò)的,這就相當(dāng)于圖紙里住人
return 0;
}
六、類對(duì)象模型
??類對(duì)象的大小
// 類中既有成員變量,又有成員函數(shù)
class A1
{
public:
void f1() {}
private:
int _a;
};
// 類中僅有成員函數(shù)
class A2 {
public:
void f2() {}
};
// 類中什么都沒有---空類
class A3
{};
int main()
{
cout << "A1的大小" << sizeof(A1) << endl;
cout << "A2的大小" << sizeof(A2) << endl;
cout << "A3的大小" << sizeof(A3) << endl;
return 0;
}
一個(gè)類的大小,實(shí)際就是該類中“成員變量”之和,要遵守內(nèi)存對(duì)齊規(guī)則,對(duì)于一個(gè)沒有成員變量的類來說,編譯器會(huì)給這種類一個(gè)字節(jié),來唯一標(biāo)識(shí)這個(gè)類對(duì)象。
還是以建房子為例,類就相當(dāng)于圖紙,對(duì)象就是用圖紙建造出來的房子,成員變量就相當(dāng)于臥室、廚房、衛(wèi)生間等,是每個(gè)房子都應(yīng)該有的屬性。而成員函數(shù),就相當(dāng)于健身房、籃球場(chǎng)等,不需要建在每一個(gè)房子里,建在一個(gè)公共場(chǎng)所,大家都可以來使用。
小Tips:sizeof(類)
和sizeof(對(duì)象)
計(jì)算出來的結(jié)果是一樣,前者相當(dāng)于拿著圖紙,計(jì)算房子的面積,后者相當(dāng)于直接拿尺子去測(cè)量實(shí)際房子的面積。
??結(jié)構(gòu)體內(nèi)存對(duì)齊規(guī)則
- 第一個(gè)成員在與結(jié)構(gòu)體偏移量為0的地址處。
- 其他成員變量要對(duì)齊到某個(gè)數(shù)字(對(duì)齊數(shù))的整數(shù)倍的地址處。
- 對(duì)齊數(shù)=編譯器默認(rèn)的對(duì)齊數(shù)與該成員大小的較小值。(vs的默認(rèn)對(duì)齊數(shù)是8)
- 結(jié)構(gòu)體的總大?。鹤畲髮?duì)齊數(shù)(所有變量類型中的最大對(duì)齊數(shù)和默認(rèn)對(duì)齊數(shù)取最?。┑恼麛?shù)倍。
- 如果嵌套了結(jié)構(gòu)體,嵌套的結(jié)構(gòu)體對(duì)齊到自己最大的對(duì)齊數(shù)的整數(shù)倍處,結(jié)構(gòu)體的整體大小就是所有最大對(duì)齊數(shù)(含嵌套結(jié)構(gòu)體的對(duì)齊數(shù))的整數(shù)倍。
??為什么會(huì)有內(nèi)存對(duì)齊規(guī)則?
計(jì)算機(jī)在訪問數(shù)據(jù)的時(shí)候,并不是想訪問哪個(gè)字節(jié)就訪問哪個(gè)字節(jié)。而是按倍數(shù)進(jìn)行訪問,固定一次訪問多少,具體和硬件電路有關(guān)。內(nèi)存對(duì)齊會(huì)減少計(jì)算機(jī)讀取數(shù)據(jù)的訪問次數(shù),提高數(shù)據(jù)的讀取效率。修改默認(rèn)對(duì)齊數(shù),本質(zhì)上是用時(shí)間換空間,因?yàn)橛布娐芬淮卧L問多少個(gè)字節(jié)是固定的。修改默認(rèn)對(duì)齊數(shù)只是改變了數(shù)據(jù)的存儲(chǔ)方式。
class A1
{
public:
void f1() {}
private:
char _ch;
int _a;
};
如上圖,在沒有內(nèi)存對(duì)齊的情況下,雖然整體占用的空間變小了,但是讀取成員變量
_a
的時(shí)候,就非常麻煩,由于硬件電路的原因,不能直接從_a
的起始地址開始讀取,這就導(dǎo)致需要讀取兩次,并且還要把第一個(gè)讀取到的第一個(gè)字節(jié)給去掉,和第二次讀取得到的第一個(gè)字節(jié)進(jìn)行拼接,這個(gè)過程是十分麻煩的。而左邊,在內(nèi)存對(duì)齊的情況下,讀取_a
只需要讀一次即可。
??結(jié)論
內(nèi)存對(duì)齊,浪費(fèi)了空間,提高了訪問效率;沒有內(nèi)存對(duì)齊,節(jié)省了空間,降低了訪問效率。修改對(duì)齊數(shù),改變的是數(shù)據(jù)在內(nèi)存中的存儲(chǔ)方式,機(jī)器一次讀取多少字節(jié),是由硬件電路決定的。
七、this指針
??先看一個(gè)神奇的現(xiàn)象
//定義一個(gè)日期類
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
int main()
{
Date d1, d2;//定義兩個(gè)日期類
d1.Init(2022, 1, 11);//給d1初始化化
d2.Init(2022, 1, 12);//給d2初始化
d1.Print();//調(diào)用Print函數(shù)
d2.Print();//調(diào)用Print函數(shù)
return 0;
}
上面的代碼定義了一個(gè)日期類Date
,接著又定義了兩個(gè)日期類對(duì)象d1
和d2
,然后用這兩個(gè)對(duì)象分別去調(diào)用成員函數(shù)Print
,上面說過,成員函數(shù)是所有對(duì)象公有的,只有一份,存放在公共代碼區(qū)。那就意味著d1
和d2
調(diào)用的是同一個(gè)Print
函數(shù),既然調(diào)用的是同一個(gè),那為什么d1
調(diào)的時(shí)候,打印出來的是d1
的日期,d2
調(diào)的時(shí)候,打印的是d2
的日期,編譯器是如何做到的???
C++中通過引入this指針解決該問題,即:C++編譯器給每個(gè)“非靜態(tài)成員函數(shù)”增加了一個(gè)隱藏的指針參數(shù),讓該指針指向當(dāng)前對(duì)象(函數(shù)運(yùn)行時(shí)調(diào)用該函數(shù)的對(duì)象),在函數(shù)體中所有“成員變量”的操作,都是通過該指針去訪問。只不過所有的操作對(duì)用戶都是透明的,即用戶不需要來傳遞,編譯器自動(dòng)完成。
??結(jié)論
雖然d1
和d2
調(diào)用的是同一個(gè)函數(shù),但是調(diào)用時(shí)傳遞的參數(shù)不同,所以最終誰調(diào)用,打印的就是誰的結(jié)果。我們可以在成員函數(shù)中去顯式使用this
,但是不能在形參或者實(shí)參中自己去顯式傳遞this。
??this指針的特性
- this指針的類型:類類型* const,即在成員函數(shù)中,不能修改this指針的指向。(可以對(duì)this指向的內(nèi)容進(jìn)行修改)
- 只能在成員函數(shù)中使用。
- this指針本質(zhì)上是成員函數(shù)的形參,當(dāng)對(duì)象調(diào)用成員函數(shù)時(shí),將對(duì)象地址作為實(shí)參傳遞給this形參。所以對(duì)象中不存儲(chǔ)this指針。
- this指針是成員函數(shù)第一個(gè)隱藏的指針形參,一般情況下由編譯器通過ecx寄存器自動(dòng)傳遞,不需要用戶傳遞。
??this指針存在哪里?
this指針是函數(shù)的形參,所以this指針和普通的函數(shù)形參一樣,在函數(shù)調(diào)用的時(shí)候壓棧,存在函數(shù)調(diào)用的棧幀里面。
??this指針可以為空嘛?
// 1.下面程序編譯運(yùn)行結(jié)果是? A、編譯報(bào)錯(cuò) B、運(yùn)行崩潰 C、正常運(yùn)行
class A
{
public:
void Print()
{
cout << "Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
上面這段代碼,定義了一個(gè)A
類型的指針p
,并把它置為空,然后用這個(gè)指針p
去調(diào)用成員函數(shù),不會(huì)發(fā)生解引用,因?yàn)镻rint函數(shù)的地址不在對(duì)象中(要看轉(zhuǎn)換成匯編指令,都干了些啥,這里直接去call成員函數(shù)的地址)。p會(huì)作為實(shí)參傳遞給this指針。傳遞空指針不會(huì)報(bào)錯(cuò),所以此時(shí)成員函數(shù)中的隱藏參數(shù)this
指針,是拷貝的p
指針的值,所以此時(shí)的形參this
指針是nullptr
。針對(duì)這個(gè)題目,首先可以排除掉A選項(xiàng),因?yàn)榭罩羔樀膯栴}是屬于運(yùn)行時(shí)錯(cuò)誤,不可能是編譯時(shí)錯(cuò)誤。這道題目選C,代碼可以正常運(yùn)行,因?yàn)?,雖然this
指針是空,但是在Print
成員函數(shù)中,我們并沒有去訪問任何類中的其他成員,這就意味著,我們根本就沒有使用這個(gè)this指針,所以代碼可以正常運(yùn)行。
// 1.下面程序編譯運(yùn)行結(jié)果是? A、編譯報(bào)錯(cuò) B、運(yùn)行崩潰 C、正常運(yùn)行
class A
{
public:
void PrintA()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->PrintA();
return 0;
}
和上面的代碼一樣,這段代碼的this
指針也是nullptr
,但是這段代碼會(huì)運(yùn)行崩潰,因?yàn)樵诔蓡T函數(shù)Print
中使用了類中的其他成員_a
,這就相當(dāng)于this->_a
,而this是一個(gè)空指針,這就成了解引用空指針,所以會(huì)運(yùn)行崩潰。
??總結(jié)
this指針能否為空,要根據(jù)具體的情況來定,如果成員函數(shù)里使用了類中的其他成員,此時(shí)this指針就不能為空。
小Tips:由于非靜態(tài)成員函數(shù),會(huì)有一個(gè)隱藏的形參this指針,且該指針不能顯式傳遞,這就導(dǎo)致了不能通過域作用限定符去訪問非靜態(tài)成員函數(shù),即A::Print()
是錯(cuò)誤的(其中A
是一個(gè)類名,Print
是該類中的一個(gè)非靜態(tài)成員函數(shù)),因?yàn)闊o法傳遞一個(gè)合理的值,讓形參this去接收。如果Print
是靜態(tài)成員函數(shù),這樣寫是可以的。(后面的文章會(huì)提到)文章來源:http://www.zghlxwxcb.cn/news/detail-576917.html
??結(jié)語:
?今天的分享到這里就結(jié)束啦!今天我們初步認(rèn)識(shí)了一下類和對(duì)象,C++中通過類可以將數(shù)據(jù)以及操作數(shù)據(jù)的方法進(jìn)行完美的結(jié)合,通過訪問權(quán)限可以控制那些方法在類外面可以調(diào)用,即封裝,在使用的時(shí)候就像使用自己的成員一樣,更符合人們對(duì)事物的認(rèn)知。如果覺得文章還不錯(cuò)的話,可以三連支持一下,您的支持就是春人前進(jìn)的動(dòng)力!文章來源地址http://www.zghlxwxcb.cn/news/detail-576917.html
到了這里,關(guān)于【C++初階】類和對(duì)象(上)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!