国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

C++ 類與對象(上)

alanoddsoff / 1763人閱讀

摘要:中默認的對齊數為結構體總大小為最大對齊數所有變量類型最大者與默認對齊參數取最小的整數倍。性能原因數據結構尤其是棧應該盡可能地在自然邊界上對齊。

類的引入

C語言中,結構體中只能定義變量,在C++中,結構體內不僅可以定義變量,也可以定義函數。

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;}

上面結構體的定義,在C++中更喜歡用class來代替

類的定義

class className{// 類體:由成員函數和成員變量組成}; // 一定要注意后面的分號

class為定義類的關鍵字,ClassName為類的名字,{}中為類的主體,注意類定義結束時后面分號。

類中的元素稱為類的成員:類中的數據稱為類的屬性或者成員變量; 類中的函數稱為類的方法或者成員函數。

類的兩種定義方式:

  1. 聲明和定義全部放在類體中,需要注意:成員函數如果在類中定義,編譯器可能會將其當成內聯函數處

  2. 聲明放在.h文件中,類的定義放在.cpp文件中

一般情況下,更期望采用第二種方式。

類的訪問限定符及封裝

訪問限定符

C++實現封裝的方式:用類將對象的屬性與方法結合在一塊,讓對象更加完善,通過訪問權限選擇性的將其
接口提供給外部的用戶使用

【訪問限定符說明】

  1. public修飾的成員在類外可以直接被訪問

  2. protected和private修飾的成員在類外不能直接被訪問(此處protected和private是類似的)

  3. 訪問權限作用域從該訪問限定符出現的位置開始直到下一個訪問限定符出現時為止

  4. class的默認訪問權限為private,struct為public(因為struct要兼容C)
    注意:訪問限定符只在編譯時有用,當數據映射到內存后,沒有任何訪問限定符上的區別

【面試題】
問題:C++中struct和class的區別是什么?
解答:C++需要兼容C語言,所以C++中struct可以當成結構體去使用。另外C++中struct還可以用來定義類。
和class是定義類是一樣的,區別是struct的成員默認訪問方式是public,class是struct的成員默認訪問方式
是private。

封裝

【面試題】 面向對象的三大特性:封裝、繼承、多態。
在類和對象階段,我們只研究類的封裝特性,那什么是封裝呢?
封裝:將數據和操作數據的方法進行有機結合,隱藏對象的屬性和實現細節,僅對外公開接口來和對象進行交互。
封裝本質上是一種管理:我們如何管理兵馬俑呢?比如如果什么都不管,兵馬俑就被隨意破壞了。那么我們首先建了一座房子把兵馬俑給封裝起來。但是我們目的全封裝起來,不讓別人看。所以我們開放了售票通
道,可以買票突破封裝在合理的監管機制下進去參觀。類也是一樣,我們使類數據和方法都封裝到一下。
不想給別人看到的,我們使用protected/private把成員封裝起來。開放一些共有的成員函數對成員合理的訪問。所以封裝本質是一種管理

類的作用域

類定義了一個新的作用域,類的所有成員都在類的作用域中。在類體外定義成員,需要使用 :: 作用域解析符指明成員屬于哪個類域

class Person{public:	void PrintPersonInfo();private:char _name[20];char _gender[3];int _age;};// 這里需要指定PrintPersonInfo是屬于Person這個類域void Person::PrintPersonInfo(){	cout<<_name<<" "_gender<<" "<<_age<<endl;}

短小的的成員函數,直接在類里面定義,就默認是內聯函數

代碼長的成員函數建議聲明和定義分離

私有的數據可以通過共有的函數來操作,比如棧,棧的成員變量可以私有,在外部就無法訪問成員變量,要想對成員變量進行操作可以使用公有的成員函數。

類的實例化

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

類對象模型

如何計算類對象的大小

class A{public:void PrintA(){	cout<<_a<<endl;}    private:char _a;};

問題:類中既可以有成員變量,又可以有成員函數,那么一個類的對象中包含了什么?如何計算一個類的大小?

答案:一個類的對象中只包含了成員變量,一個類的大小只包括成員變量的大小,根據內存對齊方式來確定類的大小。

類對象的存儲方式猜測

  • 對象中包含類的各個成員

    缺陷:每個對象中成員變量是不同的,但是調用同一份函數,如果按照此種方式存儲,當一個類創建多個對象時,每個對象中都會保存一份代碼,相同代碼保存多次,浪費空間。那么如何解決呢

  • 只保存成員變量,成員函數存放在公共的代碼段

一個類實例化出N個對象,每個對象的成員變量可以存儲不同的值,但是調用的函數卻是同一個,如果每個對象都放成員函數,而這些成員函數卻是一樣的,就會浪費空間。

我們再通過對下面的不同對象分別獲取大小來分析看下

// 類中既有成員變量,又有成員函數class A1 {public:void f1(){}private:int _a;};// 類中僅有成員函數class A2 {public:void f2() {}};// 類中什么都沒有---空類class A3{};

sizeof(A1) : 4

sizeof(A2) : 1

sizeof(A3) : 1

結論:一個類的大小,實際就是該類中”成員變量”之和,當然也要進行內存對齊,注意空類的大小,空類比
較特殊,編譯器給了空類1個字節來唯一標識這個類。

為什么給了1個字節而不是0個字節呢?

開1個字節不是為了存數據,是為了占位,表示其存在。

如果一個類沒有成員,那么他的對象需要給1byte進行占位來標識對象存在,這1byte不存儲有效數據

結構體內存對齊規則

  1. 第一個成員在與結構體偏移量為0的地址處。

  2. 其他成員變量要對齊到某個數字(對齊數)的整數倍的地址處

    注意:對齊數 = 編譯器默認的一個對齊數 與 該成員大小的較小值。
    VS中默認的對齊數為8

  3. 結構體總大小為:最大對齊數(所有變量類型最大者與默認對齊參數取最小)的整數倍。

  4. 如果嵌套了結構體的情況,嵌套的結構體對齊到自己的最大對齊數的整數倍處,結構體的整體大小就是
    所有最大對齊數(含嵌套結構體的對齊數)的整數倍。

【面試題】

  1. 結構體怎么對齊? 為什么要進行內存對齊

    1. 平臺原因(移植原因): 不是所有的硬件平臺都能訪問任意地址上的任意數據的;某些硬件平臺只能在某些地址處取某些特定類型的數據,否則拋出硬件異常。

    2. 性能原因: 數據結構(尤其是棧)應該盡可能地在自然邊界上對齊。 原因在于,為了訪問未對齊的內存,處理需要作兩次內存訪問;而對齊的內存訪問僅需要一次訪問

  2. 如何讓結構體按照指定的對齊參數進行對齊

    使用#pragma pack

  3. 如何知道結構體中某個成員相對于結構體起始位置的偏移量

    C語言中的宏offsetof可以計算,本質上是:#define my_offsetof(s, m) (size_t)&(((s*)(0))->m)

    (s*)(0)意思是將0地址變成一個s類型的結構體的起始地址,再對引用某個成員(s *)(0)->m,對該成員取地址

    &((s *)(0)->m),得到的是該成員的地址,而因為起始地址為0,所以該成員的地址就是相對起始位置的偏移量。

  4. 什么是大小端?如何測試某臺機器是大端還是小端,有沒有遇到過要考慮大小端的場景

    大小端在計算機業界,Endian表示數據在存儲器中的存放順序

    大端模式,是指數據的高字節保存在內存的低地址中,而數據的低字節保存在內存的高地址中,這樣的存儲模式有點兒類似于把數據當作字符串順序處理:地址由小向大增加,而數據從高位往低位放;

    小端模式,是指數據的高字節保存在內存的高地址中,而數據的低字節保存在內存的低地址中,這種存儲模式將地址的高低和數據位權有效地結合起來,高地址部分權值高,低地址部分權值低,和我們的邏輯方法一致。

    比如一個整型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占一個字節,能使用的只有b的一個低地址處的字節

    如果當前機器是小端,則b=1,按照地址從低到高,存儲方式就是:01 00 00 00

    a能夠訪問的就是低地址處的01

    如果當前機器是大端,則b=1,按照地址從低到高,存儲方式就是:0 00 00 01

    a能夠訪問的就是低地址處的00

this指針

this指針的引出

我們先來定義一個日期類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兩個成員函數,函數體中沒有關于不同對象的區分,那當s1調用SetDate函數
時,該函數是如何知道應該設置s1對象,而不是設置s2對象呢?

C++中通過引入this指針解決該問題,即:C++編譯器給每個“非靜態的成員函數“增加了一個隱藏的指針參
數,讓該指針指向當前對象(函數運行時調用該函數的對象),在函數體中所有成員變量的操作,都是通過該
指針去訪問。只不過所有的操作對用戶是透明的,即用戶不需要來傳遞,編譯器自動完成。

所以上述函數調用時,實際上是這樣的:

//規定this指針默認放在第一個參數位置d1.SetDate(&d1,2018,5,1);d2.SetDate(&d2,2018,7,1);d1.Print(&d1);d2.Print(&d2);

但我們調用時不得私自加上這個指針參數

并且在定義類成員函數時,實際上也是這樣的

//在定義成員函數時也可以這么寫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;	}

我們只是看上去沒有傳參數, 實際上是傳了調用Print函數的類的地址。

this指針的特性

  1. this指針的類型:類類型* const
  2. 只能在“成員函數”的內部使用
  3. this指針本質上其實是一個成員函數的形參,是對象調用成員函數時,將對象地址作為實參傳遞給this形參。所以對象中不存儲this指針。
  4. this指針是成員函數第一個隱含的指針形參,一般情況由編譯器通過ecx寄存器自動傳遞,不需要用戶傳遞

【面試題】

  1. this指針存在哪里?

  2. 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是一個空指針,沒有指向任何類,所以函數內部對_a(實際上是this-> _a)的引用失敗

而對于p->Show(),Show函數在公共代碼段,所以p對其不用解引用,并且Show()函數中,并沒有對p進行解引用,所以不會引發錯誤。

最開始兩個問題的答案:

1.this指針是存放在棧上的,因為它是一個形參。

注:vs下是在ecx這個寄存器下存放的,因為this指針要被多次使用,所以把它放在寄存器中,方便快速訪問。

2.如上面代碼所示,this指針是可以為空的,只要在類成員函數內不使用this指針。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/122190.html

相關文章

  • Python方法(二) - 類與繼承

    摘要:在類內部的方法中使用時。類的私有方法兩個下劃線開頭,聲明該方法為私有方法,不能在類地外部調用。先在本類中查找調用的方法,找不到才去基類中找。如果在繼承元組中列了一個以上的類,那么它就被稱作多重繼承。 類定義 類對象:創建一個類之后,可以通過類名訪問、改變其屬性、方法 實例對象:類實例化后,可以使用其屬性,可以動態的為實例對象添加屬性(類似javascript)而不影響類對象。 類...

    DevWiki 評論0 收藏0
  • javascript對象類與原型鏈

    摘要:下面定義一個構造函數不過這樣寫這個函數,每個對象都會包括一部分,太浪費內存。原型鏈與繼承在學習原型鏈之前我們一定要區分清楚是構造函數的屬性,而是對象的屬性。但對象形式不等于基本類型。用來判斷對象是否某個構造函數的實例。 js是一個基于對象的語言,所以本文研究一下js對象和類實現的過程和原理。 對象的屬性及屬性特性 下面是一個對象的各個部分: var person = { name:...

    adam1q84 評論0 收藏0
  • C++之繼承

    摘要:基類中的構造函數和析構函數不能被繼承,在派生類中需要定義新的構造函數和析構函數,私有成員不能被繼承。對象訪問在派生類外部,通過派生類的對象對從基類繼承來的成員的訪問。 ...

    不知名網友 評論0 收藏0
  • JavaScript基礎: 類與繼承

    摘要:類的方法相當于之前我們定義在構造函數的原型上。的構造函數中調用其目的就是調用父類的構造函數。是先創建子類的實例,然后在子類實例的基礎上創建父類的屬性。 前言   首先歡迎大家關注我的Github博客,也算是對我的一點鼓勵,畢竟寫東西沒法獲得變現,能堅持下去也是靠的是自己的熱情和大家的鼓勵。    許久已經沒有寫東西了,因為雜七雜八的原因最近一直沒有抽出時間來把寫作堅持下來,感覺和跑步一...

    liuchengxu 評論0 收藏0

發表評論

0條評論

alanoddsoff

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<