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