摘要:中默認(rèn)的對(duì)齊數(shù)為結(jié)構(gòu)體總大小為最大對(duì)齊數(shù)所有變量類型最大者與默認(rèn)對(duì)齊參數(shù)取最小的整數(shù)倍。性能原因數(shù)據(jù)結(jié)構(gòu)尤其是棧應(yīng)該盡可能地在自然邊界上對(duì)齊。
C語(yǔ)言中,結(jié)構(gòu)體中只能定義變量,在C++中,結(jié)構(gòu)體內(nèi)不僅可以定義變量,也可以定義函數(shù)。
struct Student{ //在類里面定義函數(shù) void SetStudentInfo(const char* name, const char* gender, int age) { strcpy(_name, name); strcpy(_gender, gender); _age = age; } //在類里面定義函數(shù) 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來(lái)代替
class className{// 類體:由成員函數(shù)和成員變量組成}; // 一定要注意后面的分號(hào)
class為定義類的關(guān)鍵字,ClassName為類的名字,{}中為類的主體,注意類定義結(jié)束時(shí)后面分號(hào)。
類中的元素稱為類的成員:類中的數(shù)據(jù)稱為類的屬性或者成員變量; 類中的函數(shù)稱為類的方法或者成員函數(shù)。
聲明和定義全部放在類體中,需要注意:成員函數(shù)如果在類中定義,編譯器可能會(huì)將其當(dāng)成內(nèi)聯(lián)函數(shù)處
理
聲明放在.h文件中,類的定義放在.cpp文件中
一般情況下,更期望采用第二種方式。
C++實(shí)現(xiàn)封裝的方式:用類將對(duì)象的屬性與方法結(jié)合在一塊,讓對(duì)象更加完善,通過(guò)訪問(wèn)權(quán)限選擇性的將其
接口提供給外部的用戶使用
【訪問(wèn)限定符說(shuō)明】
public修飾的成員在類外可以直接被訪問(wèn)
protected和private修飾的成員在類外不能直接被訪問(wèn)(此處protected和private是類似的)
訪問(wèn)權(quán)限作用域從該訪問(wèn)限定符出現(xiàn)的位置開(kāi)始直到下一個(gè)訪問(wèn)限定符出現(xiàn)時(shí)為止
class的默認(rèn)訪問(wèn)權(quán)限為private,struct為public(因?yàn)閟truct要兼容C)
注意:訪問(wèn)限定符只在編譯時(shí)有用,當(dāng)數(shù)據(jù)映射到內(nèi)存后,沒(méi)有任何訪問(wèn)限定符上的區(qū)別
【面試題】
問(wèn)題:C++中struct和class的區(qū)別是什么?
解答:C++需要兼容C語(yǔ)言,所以C++中struct可以當(dāng)成結(jié)構(gòu)體去使用。另外C++中struct還可以用來(lái)定義類。
和class是定義類是一樣的,區(qū)別是struct的成員默認(rèn)訪問(wèn)方式是public,class是struct的成員默認(rèn)訪問(wèn)方式
是private。
【面試題】 面向?qū)ο蟮娜筇匦裕悍庋b、繼承、多態(tài)。
在類和對(duì)象階段,我們只研究類的封裝特性,那什么是封裝呢?
封裝:將數(shù)據(jù)和操作數(shù)據(jù)的方法進(jìn)行有機(jī)結(jié)合,隱藏對(duì)象的屬性和實(shí)現(xiàn)細(xì)節(jié),僅對(duì)外公開(kāi)接口來(lái)和對(duì)象進(jìn)行交互。
封裝本質(zhì)上是一種管理:我們?nèi)绾喂芾肀R俑呢?比如如果什么都不管,兵馬俑就被隨意破壞了。那么我們首先建了一座房子把兵馬俑給封裝起來(lái)。但是我們目的全封裝起來(lái),不讓別人看。所以我們開(kāi)放了售票通
道,可以買票突破封裝在合理的監(jiān)管機(jī)制下進(jìn)去參觀。類也是一樣,我們使類數(shù)據(jù)和方法都封裝到一下。
不想給別人看到的,我們使用protected/private把成員封裝起來(lái)。開(kāi)放一些共有的成員函數(shù)對(duì)成員合理的訪問(wèn)。所以封裝本質(zhì)是一種管理
類定義了一個(gè)新的作用域,類的所有成員都在類的作用域中。在類體外定義成員,需要使用 :: 作用域解析符指明成員屬于哪個(gè)類域
class Person{public: void PrintPersonInfo();private:char _name[20];char _gender[3];int _age;};// 這里需要指定PrintPersonInfo是屬于Person這個(gè)類域void Person::PrintPersonInfo(){ cout<<_name<<" "_gender<<" "<<_age<<endl;}
短小的的成員函數(shù),直接在類里面定義,就默認(rèn)是內(nèi)聯(lián)函數(shù)
代碼長(zhǎng)的成員函數(shù)建議聲明和定義分離
私有的數(shù)據(jù)可以通過(guò)共有的函數(shù)來(lái)操作,比如棧,棧的成員變量可以私有,在外部就無(wú)法訪問(wèn)成員變量,要想對(duì)成員變量進(jìn)行操作可以使用公有的成員函數(shù)。
class A{public:void PrintA(){ cout<<_a<<endl;} private:char _a;};
問(wèn)題:類中既可以有成員變量,又可以有成員函數(shù),那么一個(gè)類的對(duì)象中包含了什么?如何計(jì)算一個(gè)類的大小?
答案:一個(gè)類的對(duì)象中只包含了成員變量,一個(gè)類的大小只包括成員變量的大小,根據(jù)內(nèi)存對(duì)齊方式來(lái)確定類的大小。
對(duì)象中包含類的各個(gè)成員
缺陷:每個(gè)對(duì)象中成員變量是不同的,但是調(diào)用同一份函數(shù),如果按照此種方式存儲(chǔ),當(dāng)一個(gè)類創(chuàng)建多個(gè)對(duì)象時(shí),每個(gè)對(duì)象中都會(huì)保存一份代碼,相同代碼保存多次,浪費(fèi)空間。那么如何解決呢
只保存成員變量,成員函數(shù)存放在公共的代碼段
一個(gè)類實(shí)例化出N個(gè)對(duì)象,每個(gè)對(duì)象的成員變量可以存儲(chǔ)不同的值,但是調(diào)用的函數(shù)卻是同一個(gè),如果每個(gè)對(duì)象都放成員函數(shù),而這些成員函數(shù)卻是一樣的,就會(huì)浪費(fèi)空間。
我們?cè)偻ㄟ^(guò)對(duì)下面的不同對(duì)象分別獲取大小來(lái)分析看下
// 類中既有成員變量,又有成員函數(shù)class A1 {public:void f1(){}private:int _a;};// 類中僅有成員函數(shù)class A2 {public:void f2() {}};// 類中什么都沒(méi)有---空類class A3{};
sizeof(A1) : 4
sizeof(A2) : 1
sizeof(A3) : 1
結(jié)論:一個(gè)類的大小,實(shí)際就是該類中”成員變量”之和,當(dāng)然也要進(jìn)行內(nèi)存對(duì)齊,注意空類的大小,空類比
較特殊,編譯器給了空類1個(gè)字節(jié)來(lái)唯一標(biāo)識(shí)這個(gè)類。
為什么給了1個(gè)字節(jié)而不是0個(gè)字節(jié)呢?
開(kāi)1個(gè)字節(jié)不是為了存數(shù)據(jù),是為了占位,表示其存在。
如果一個(gè)類沒(méi)有成員,那么他的對(duì)象需要給1byte進(jìn)行占位來(lái)標(biāo)識(shí)對(duì)象存在,這1byte不存儲(chǔ)有效數(shù)據(jù)
第一個(gè)成員在與結(jié)構(gòu)體偏移量為0的地址處。
其他成員變量要對(duì)齊到某個(gè)數(shù)字(對(duì)齊數(shù))的整數(shù)倍的地址處
注意:對(duì)齊數(shù) = 編譯器默認(rèn)的一個(gè)對(duì)齊數(shù) 與 該成員大小的較小值。
VS中默認(rèn)的對(duì)齊數(shù)為8
結(jié)構(gòu)體總大小為:最大對(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ù)倍。
【面試題】
結(jié)構(gòu)體怎么對(duì)齊? 為什么要進(jìn)行內(nèi)存對(duì)齊
平臺(tái)原因(移植原因): 不是所有的硬件平臺(tái)都能訪問(wèn)任意地址上的任意數(shù)據(jù)的;某些硬件平臺(tái)只能在某些地址處取某些特定類型的數(shù)據(jù),否則拋出硬件異常。
性能原因: 數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能地在自然邊界上對(duì)齊。 原因在于,為了訪問(wèn)未對(duì)齊的內(nèi)存,處理需要作兩次內(nèi)存訪問(wèn);而對(duì)齊的內(nèi)存訪問(wèn)僅需要一次訪問(wèn)
如何讓結(jié)構(gòu)體按照指定的對(duì)齊參數(shù)進(jìn)行對(duì)齊
使用#pragma pack
如何知道結(jié)構(gòu)體中某個(gè)成員相對(duì)于結(jié)構(gòu)體起始位置的偏移量
C語(yǔ)言中的宏offsetof可以計(jì)算,本質(zhì)上是:#define my_offsetof(s, m) (size_t)&(((s*)(0))->m)
(s*)(0)意思是將0地址變成一個(gè)s類型的結(jié)構(gòu)體的起始地址,再對(duì)引用某個(gè)成員(s *)(0)->m,對(duì)該成員取地址
&((s *)(0)->m),得到的是該成員的地址,而因?yàn)槠鹗嫉刂窞?,所以該成員的地址就是相對(duì)起始位置的偏移量。
什么是大小端?如何測(cè)試某臺(tái)機(jī)器是大端還是小端,有沒(méi)有遇到過(guò)要考慮大小端的場(chǎng)景
大小端在計(jì)算機(jī)業(yè)界,Endian表示數(shù)據(jù)在存儲(chǔ)器中的存放順序。
大端模式,是指數(shù)據(jù)的高字節(jié)保存在內(nèi)存的低地址中,而數(shù)據(jù)的低字節(jié)保存在內(nèi)存的高地址中,這樣的存儲(chǔ)模式有點(diǎn)兒類似于把數(shù)據(jù)當(dāng)作字符串順序處理:地址由小向大增加,而數(shù)據(jù)從高位往低位放;
小端模式,是指數(shù)據(jù)的高字節(jié)保存在內(nèi)存的高地址中,而數(shù)據(jù)的低字節(jié)保存在內(nèi)存的低地址中,這種存儲(chǔ)模式將地址的高低和數(shù)據(jù)位權(quán)有效地結(jié)合起來(lái),高地址部分權(quán)值高,低地址部分權(quán)值低,和我們的邏輯方法一致。
比如一個(gè)整型10,將它的32位按十六進(jìn)制從高位到低位表示:00 00 00 0A
在大端中的存儲(chǔ)方式是:
小端中的存儲(chǔ)方式是:
測(cè)試大小端(使用共用體Union):
union U { char a; int b;};int main(){ U u1; u1.b = 1; if (u1.a == 1) { printf("小端/n"); } else printf("大端/n"); return 0;}
原理:U中的a和b是共用一塊空間的,a占一個(gè)字節(jié),能使用的只有b的一個(gè)低地址處的字節(jié)
如果當(dāng)前機(jī)器是小端,則b=1,按照地址從低到高,存儲(chǔ)方式就是:01 00 00 00
a能夠訪問(wèn)的就是低地址處的01
如果當(dāng)前機(jī)器是大端,則b=1,按照地址從低到高,存儲(chǔ)方式就是:0 00 00 01
a能夠訪問(wèn)的就是低地址處的00
我們先來(lái)定義一個(gè)日期類Date
class Date{public :void Print (){cout <<_year<< "-" <<_month << "-"<< _day <<endl;} void SetDate(int year , int month , int day){_year = year;_month = month;_day = day;} private :int _year ; // 年int _month ; // 月int _day ; // 日};int main(){ Date d1, d2; d1.SetDate(2018,5,1); d2.SetDate(2018,7,1); d1.Print(); d2.Print(); return 0;}
對(duì)于上述類,有這樣的一個(gè)問(wèn)題:
Date類中有SetDate與Print兩個(gè)成員函數(shù),函數(shù)體中沒(méi)有關(guān)于不同對(duì)象的區(qū)分,那當(dāng)s1調(diào)用SetDate函數(shù)
時(shí),該函數(shù)是如何知道應(yīng)該設(shè)置s1對(duì)象,而不是設(shè)置s2對(duì)象呢?
C++中通過(guò)引入this指針解決該問(wèn)題,即:C++編譯器給每個(gè)“非靜態(tài)的成員函數(shù)“增加了一個(gè)隱藏的指針參
數(shù),讓該指針指向當(dāng)前對(duì)象(函數(shù)運(yùn)行時(shí)調(diào)用該函數(shù)的對(duì)象),在函數(shù)體中所有成員變量的操作,都是通過(guò)該
指針去訪問(wèn)。只不過(guò)所有的操作對(duì)用戶是透明的,即用戶不需要來(lái)傳遞,編譯器自動(dòng)完成。
所以上述函數(shù)調(diào)用時(shí),實(shí)際上是這樣的:
//規(guī)定this指針默認(rèn)放在第一個(gè)參數(shù)位置d1.SetDate(&d1,2018,5,1);d2.SetDate(&d2,2018,7,1);d1.Print(&d1);d2.Print(&d2);
但我們調(diào)用時(shí)不得私自加上這個(gè)指針參數(shù)
并且在定義類成員函數(shù)時(shí),實(shí)際上也是這樣的
//在定義成員函數(shù)時(shí)也可以這么寫void Display() { cout << this->_year << "-" << this->_month << "-" << this->_day << endl; }void SetDate(int year, int month, int day) { this->_year = year; this->_month = month; this->_day = day; }
我們只是看上去沒(méi)有傳參數(shù), 實(shí)際上是傳了調(diào)用Print函數(shù)的類的地址。
【面試題】
this指針存在哪里?
this指針可以為空嗎?
我們先看下面的代碼,再回答這兩個(gè)問(wèn)題
// 1.下面程序能編譯通過(guò)嗎?// 2.下面程序會(huì)崩潰嗎?在哪里崩潰class A{public:void PrintA(){ cout<<_a<<endl;}void Show(){ cout<<"Show()"<<endl;}private:int _a;};int main(){ Date* p = NULL; p->PrintA(); p->Show();}
答案:p->PrintA()崩潰,p->Show()通過(guò),因?yàn)镻rintA中需要用到this指針,并對(duì)this指針指向的類的成員變量進(jìn)行訪問(wèn),而p是一個(gè)空指針,沒(méi)有指向任何類,所以函數(shù)內(nèi)部對(duì)_a(實(shí)際上是this-> _a)的引用失敗
而對(duì)于p->Show(),Show函數(shù)在公共代碼段,所以p對(duì)其不用解引用,并且Show()函數(shù)中,并沒(méi)有對(duì)p進(jìn)行解引用,所以不會(huì)引發(fā)錯(cuò)誤。
最開(kāi)始兩個(gè)問(wèn)題的答案:
1.this指針是存放在棧上的,因?yàn)樗且粋€(gè)形參。
注:vs下是在ecx這個(gè)寄存器下存放的,因?yàn)閠his指針要被多次使用,所以把它放在寄存器中,方便快速訪問(wèn)。
2.如上面代碼所示,this指針是可以為空的,只要在類成員函數(shù)內(nèi)不使用this指針。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/122190.html
摘要:在類內(nèi)部的方法中使用時(shí)。類的私有方法兩個(gè)下劃線開(kāi)頭,聲明該方法為私有方法,不能在類地外部調(diào)用。先在本類中查找調(diào)用的方法,找不到才去基類中找。如果在繼承元組中列了一個(gè)以上的類,那么它就被稱作多重繼承。 類定義 類對(duì)象:創(chuàng)建一個(gè)類之后,可以通過(guò)類名訪問(wèn)、改變其屬性、方法 實(shí)例對(duì)象:類實(shí)例化后,可以使用其屬性,可以動(dòng)態(tài)的為實(shí)例對(duì)象添加屬性(類似javascript)而不影響類對(duì)象。 類...
摘要:下面定義一個(gè)構(gòu)造函數(shù)不過(guò)這樣寫這個(gè)函數(shù),每個(gè)對(duì)象都會(huì)包括一部分,太浪費(fèi)內(nèi)存。原型鏈與繼承在學(xué)習(xí)原型鏈之前我們一定要區(qū)分清楚是構(gòu)造函數(shù)的屬性,而是對(duì)象的屬性。但對(duì)象形式不等于基本類型。用來(lái)判斷對(duì)象是否某個(gè)構(gòu)造函數(shù)的實(shí)例。 js是一個(gè)基于對(duì)象的語(yǔ)言,所以本文研究一下js對(duì)象和類實(shí)現(xiàn)的過(guò)程和原理。 對(duì)象的屬性及屬性特性 下面是一個(gè)對(duì)象的各個(gè)部分: var person = { name:...
摘要:基類中的構(gòu)造函數(shù)和析構(gòu)函數(shù)不能被繼承,在派生類中需要定義新的構(gòu)造函數(shù)和析構(gòu)函數(shù),私有成員不能被繼承。對(duì)象訪問(wèn)在派生類外部,通過(guò)派生類的對(duì)象對(duì)從基類繼承來(lái)的成員的訪問(wèn)。 ...
摘要:類的方法相當(dāng)于之前我們定義在構(gòu)造函數(shù)的原型上。的構(gòu)造函數(shù)中調(diào)用其目的就是調(diào)用父類的構(gòu)造函數(shù)。是先創(chuàng)建子類的實(shí)例,然后在子類實(shí)例的基礎(chǔ)上創(chuàng)建父類的屬性。 前言 首先歡迎大家關(guān)注我的Github博客,也算是對(duì)我的一點(diǎn)鼓勵(lì),畢竟寫東西沒(méi)法獲得變現(xiàn),能堅(jiān)持下去也是靠的是自己的熱情和大家的鼓勵(lì)。 許久已經(jīng)沒(méi)有寫東西了,因?yàn)殡s七雜八的原因最近一直沒(méi)有抽出時(shí)間來(lái)把寫作堅(jiān)持下來(lái),感覺(jué)和跑步一...
閱讀 1819·2021-10-11 10:59
閱讀 2494·2021-09-30 09:53
閱讀 1866·2021-09-22 15:28
閱讀 2867·2019-08-29 15:29
閱讀 1624·2019-08-29 13:53
閱讀 3284·2019-08-29 12:34
閱讀 2920·2019-08-26 10:16
閱讀 2711·2019-08-23 15:16