真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

C/C++重點八股文-創(chuàng)新互聯(lián)

1.C/C++關(guān)鍵字 1.1 static(靜態(tài))變量 在C中,關(guān)鍵字static是靜態(tài)變量:
  • 靜態(tài)變量只會初始化一次,然后在這函數(shù)被調(diào)用過程中值不變。
  • 在文件內(nèi)定義靜態(tài)變量(函數(shù)外),作用域是當前文件,該變量可以被文件內(nèi)所有函數(shù)訪問,不能被其他文件函數(shù)訪問。為本地的全局變量,只初始化一次。
在C++中,類內(nèi)數(shù)據(jù)成員可以定義為static
  • 對于非靜態(tài)數(shù)據(jù)成員,每個對象有一個副本。而靜態(tài)數(shù)據(jù)成員是類的成員,只存在一個副本,被所有對象共享。
  • 靜態(tài)成員變量沒有實例化對象也可以使用,“類名:靜態(tài)成員變量”
  • 靜態(tài)成員變量初始化在類外,但是private和protected修飾的靜態(tài)成員不能類外訪問。
class Stu
{public:
	static int age;
private:
	static int height;
};
//初始化靜態(tài)成員變量
int Stu::age = 19;
int Stu::height = 180;

int main()
{cout<
  • 在類中,static修飾的函數(shù)是靜態(tài)成員函數(shù)。靜態(tài)成員函數(shù)一樣屬于類,不屬于對象,被對象共享。靜態(tài)成員函數(shù)沒有this指針,不能訪問非靜態(tài)的函數(shù)和變量,只能訪問靜態(tài)的。
與全局變量相比,靜態(tài)數(shù)據(jù)成員的優(yōu)勢:
  • 全局變量作用域是整個工程,而static作用域是當前文件,避免命名沖突
  • 靜態(tài)數(shù)據(jù)成員可以是private成員,而全局變量不能,實現(xiàn)信息隱藏
為什么靜態(tài)成員變量不能在類內(nèi)初始化?

因為類的聲明可能會在多處引用,每次引用都會初始化一次,分配一次空間。這和靜態(tài)變量只能初始化一次,只有一個副本沖突,因此靜態(tài)成員變量只能類外初始化。

創(chuàng)新互聯(lián)成立于2013年,我們提供高端重慶網(wǎng)站建設公司、成都網(wǎng)站制作、成都網(wǎng)站設計公司、網(wǎng)站定制、成都營銷網(wǎng)站建設微信平臺小程序開發(fā)、微信公眾號開發(fā)、營銷推廣服務,提供專業(yè)營銷思路、內(nèi)容策劃、視覺設計、程序開發(fā)來完成項目落地,為人造霧企業(yè)提供源源不斷的流量和訂單咨詢。為什么static靜態(tài)變量只能初始化一次?

所有變量都只初始化一次。但是靜態(tài)變量在全局區(qū)(靜態(tài)區(qū)),而自動變量在棧區(qū)。靜態(tài)變量生命周期和程序一樣,只創(chuàng)建初始化一次就一直存在,不會銷毀。而自動變量生命周期和函數(shù)一樣,函數(shù)調(diào)用就進行創(chuàng)建初始化,函數(shù)結(jié)束就銷毀,所以每一次調(diào)用函數(shù)就初始化一次。

在頭文件中定義靜態(tài)變量是否可行?

不可行,在頭文件中定義的一個static變量,對于包含該頭文件的所有源文件,實質(zhì)上在每個源文件內(nèi)定義了一個同名的static變量。造成資源浪費,可能引起bug

靜態(tài)變量什么時候初始化
  • 初始化只有一次,但是可以多次賦值,在主程序之前,編譯器已經(jīng)為其分配好了內(nèi)存。

  • 靜態(tài)局部變量和全局變量一樣,數(shù)據(jù)都存放在全局區(qū)域,所以在主程序之前,編譯器已經(jīng)為其分配好了內(nèi)存,但在C和C++中靜態(tài)局部變量的初始化節(jié)點又有點不太一樣。

  • 在C中,初始化發(fā)生在代碼執(zhí)行之前,編譯階段分配好內(nèi)存之后,就會進行初始化,所以我們看到在C語言中無法使用變量對靜態(tài)局部變量進行初始化,在程序運行結(jié)束,變量所處的全局內(nèi)存會被全部回收。

  • 而在C++中,初始化時在執(zhí)行相關(guān)代碼時才會進行初始化,C++標準定為全局或靜態(tài)對象是有首次用到時才會進行構(gòu)造,并通過atexit()來管理。在程序結(jié)束,按照構(gòu)造順序反方向進行逐個析構(gòu)。所以在C++中是可以使用變量對靜態(tài)局部變量進行初始化的。

1.2 const的作用

常量類型也稱為const類型,使用const修飾變量或者對象

在C中,const的作用為:
  • 定義變量(局部或者全局)為常量
const int a = 10; //常量定義時,必須初始化
  • 修飾函數(shù)的參數(shù),函數(shù)體內(nèi)不能修改這個參數(shù)的值
  • 修飾函數(shù)的返回值
    • const修飾的返回值類型為指針,返回的指針不能被修改,而且只能符給被const修飾的指針
      const char* GetString()
      {//...
      }
      
      int main()
      {char *str = GetString();//錯誤,str沒被const修飾
      	const char *str = GetString();//正確
      }
      
    • const修飾的返回值類型為引用,那么函數(shù)調(diào)用表達式不能做左值(函數(shù)不能被賦值)
      const int & add(int &a , int &b)
      {//..
      }
      int main()
      {add(a,b) = 4;//錯誤,const修飾add的返回引用,不能做左值
      }
    • const修飾的返回值類型為普通變量,由于返回是普通臨時變量,const修飾沒意義。
在c++中,const還有作用為:
  • const修飾類內(nèi)的數(shù)據(jù)成員。表示這個數(shù)據(jù)成員在某個對象的生命周期是常量,不同對象的值可以不一樣,因此const成員函數(shù)不能在類內(nèi)初始化。
  • const修飾類內(nèi)的成員函數(shù)。那么這個函數(shù)就不能修改對象的成員變量
const的優(yōu)點?
  1. 進行類型檢查,使編譯器對處理內(nèi)容有更多了解。

  2. 避免意義模糊的數(shù)字出現(xiàn),類似宏定義,方便對參數(shù)進行修改。

  3. 保護被修飾的內(nèi)容,防止被意外修改

  4. 為函數(shù)重載提供參考

    class A
    {void f(int i){...} //非const對象調(diào)用
    	void f(int i) const {...}//const對象調(diào)用
    }

5.節(jié)省內(nèi)存
6.提高程序效率(編譯器不為普通const常量分配存儲空間,而保存在符號表中。稱為一個編譯期間的常量,沒有存儲和讀內(nèi)存的操作)

什么時候使用const?
  • 修飾一般常量

  • 修飾對象

  • 修飾常指針

    const int *p;
    int const *p;
    int *const p;
    const int *const p;
  • 修飾常引用

  • 修飾函數(shù)的參數(shù)

  • 修飾函數(shù)返回值

  • 修飾類的成員函數(shù)

  • 修飾另一文件中引用的變量

    extern const int j;
const和指針(常量指針、指針常量)
  • 常量指針(const 修飾常量,const在*的左邊)

    const int *p = &a; // const修飾int,指針的指向可以修改,但是指針指向的值不能改
    int const *p;//同上
    p = &b;//正確
    *p = 10;//錯誤
  • 指針常量(const修飾指針,const在*的右邊)

    int *const p = &a;//const修飾指針,指針的指向不可以改,但是指針指向的值可以改
    *p = 10;//正確
    p = &b;//錯誤
  • const都修飾指針和常量(指針和常量都不能修改)

    const int *const p;
    int const *const p;
頂層const和底層const
  • 頂層const(常量指針):指的是const修飾的變量本身是一個常量,無法修改,指的是指針,就是 * 號的右邊
  • 底層const(指針常量):指的是const修飾的變量所指向的對象是一個常量,指的是所指變量,就是 * 號的左邊
const和static的作用

static

  • 不考慮類的情況
    • 隱藏。所有不加static的全局變量和函數(shù)具有全局可見性,可以在其他文件中使用,加了之后只能在該文件所在的編譯模塊中使用
    • 默認初始化為0,包括未初始化的全局靜態(tài)變量與局部靜態(tài)變量,都存在全局未初始化區(qū)
    • 靜態(tài)變量在函數(shù)內(nèi)定義,始終存在,且只進行一次初始化,具有記憶性,其作用范圍與局部變量相同,函數(shù)退出后仍然存在,但不能使用
  • 考慮類的情況
    • static成員變量:只與類關(guān)聯(lián),不與類的對象關(guān)聯(lián)。定義時要分配空間,不能在類聲明中初始化,必須在類定義體外部初始化,初始化時不需要標示為static;可以被非static成員函數(shù)任意訪問。
    • static成員函數(shù):不具有this指針,無法訪問類對象的非static成員變量和非static成員函數(shù);不能被聲明為const、虛函數(shù)和volatile;可以被非static成員函數(shù)任意訪問

const

  • 不考慮類的情況

    • const常量在定義時必須初始化,之后無法更改

    • const形參可以接收const和非const類型的實參,例如// i 可以是 int 型或者 const int 型void fun(const int& i){ //…}

  • 考慮類的情況

    • const成員變量:不能在類定義外部初始化,只能通過構(gòu)造函數(shù)初始化列表進行初始化,并且必須有構(gòu)造函數(shù);不同類對其const數(shù)據(jù)成員的值可以不同,所以不能在類中聲明時初始化
    • const成員函數(shù):const對象不可以調(diào)用非const成員函數(shù);非const對象都可以調(diào)用;不可以改變非mutable(用該關(guān)鍵字聲明的變量可以在const成員函數(shù)中被修改)數(shù)據(jù)的值

補充一點const相關(guān):const修飾變量是也與static有一樣的隱藏作用。只能在該文件中使用,其他文件不可以引用聲明使用。 因此在頭文件中聲明const變量是沒問題的,因為即使被多個文件包含,鏈接性都是內(nèi)部的,不會出現(xiàn)符號沖突

1.3 switch語句中case結(jié)尾是否必須加break

**一般必須在case結(jié)尾加break。**因為通過switch確認入口點,一直往下執(zhí)行,直到遇見break。否則會執(zhí)行完這個case后執(zhí)行后面的case,default也會執(zhí)行。 注,switch(c),c可以是int、long、char等,但是不能是float

1.4 volatile 的作用

volatile 關(guān)鍵字是一種類型修飾符,用它聲明的類型變量表示可以被某些編譯器未知的因素更改,比如:操作系統(tǒng)、硬件或者其它線程等。

  • 編譯器不再進行優(yōu)化,從而可以提供對特殊地址的穩(wěn)定訪問。
  • 系統(tǒng)總是重新從它所在的內(nèi)存讀取數(shù)據(jù),不會利用cache中原有的數(shù)值。
  • 用于多線程被多個任務共享的變量,或者并行設備的硬件寄存器
1.5 斷言ASSERT()是什么?

**是一個調(diào)試程序使用的宏。**定義在中,用于判斷是否出現(xiàn)非法數(shù)據(jù)。括號內(nèi)的值 為false(0),程序報錯,終止運行。

ASSERT(n != 0);// n為0的時候程序報錯
k = 10/n;

ASSERT()在Debug中有,在Release中被忽略。 ASSERT()是宏,assert()是ANSCI標準中的函數(shù),但是影響程序性能。

1.6 枚舉變量的值計算
#includeint main()
{enum {a,b=5,c,d=4,e};
	printf("%d %d %d %d %d",a,b,c,d,e); 
	return 0;
}

輸出為 0 5 6 4 5

1.7 字符串存儲方式
  1. 字符串存儲在棧中
char str1[] = "abc";
char str2[] = "abc";
  1. 字符串存儲在常量區(qū)
char *str3 = "abc";
char *str4 = "abc";
  1. 字符串存儲在堆中
char *str5 = (char*)malloc(4);
strcpy(str5,"abc");
char *str6 = (char*)malloc(4);
strcpy(str6,"abc");
  1. 字符串是否相等
  • str1 != str2 ,str1和str2是兩個字符串的首地址。
  • str3 == srt4 , str3和str4是常量的地址,同樣字符串在常量區(qū)只存在一份。
  • str5 != str6 ,str5 和str6是指向堆的地址。
    在這里插入圖片描述
1.8 程序內(nèi)存分區(qū)
內(nèi)存高地址棧區(qū)
堆區(qū)
全局/靜態(tài)區(qū) (.bss段 .date段)
常量區(qū)
內(nèi)存低地址代碼區(qū)

在這里插入圖片描述

  1. 棧區(qū)(stack)
  • 臨時創(chuàng)建的局部變量存放在棧區(qū)。

  • 函數(shù)調(diào)用時,其入口參數(shù)存放在棧區(qū)。

  • 函數(shù)返回時,其返回值存放在棧區(qū)。

  • const定義的局部變量存放在棧區(qū)。

  1. 堆區(qū)(heap)
  • 堆區(qū)用于存放程序運行中被動態(tài)分布的內(nèi)存段,可增可減。

  • malloc函數(shù)分布的內(nèi)存,必須用free進行內(nèi)存釋放,否則會造成內(nèi)存泄漏。

  1. 全局區(qū)(靜態(tài)區(qū))
  • (c語言中)全局區(qū)有.bss段和.data段組成,可讀可寫。
  • C++不分bss和data
  1. .bss段
  • 未初始化的全局變量存放在.bss段。

  • 初始化為0的全局變量和初始化為0的靜態(tài)變量存放在.bss段。

  • .bss段不占用可執(zhí)行文件空間,其內(nèi)容有操作系統(tǒng)初始化。

  1. .data段
  • 已經(jīng)初始化的全局變量存放在.data段。

  • 靜態(tài)變量存放在.data段。

  • .data段占用可執(zhí)行文件空間,其內(nèi)容有程序初始化。

  • const定義的全局變量存放在.rodata段。

  1. 常量區(qū)
  • 字符串存放在常量區(qū)。

  • 常量區(qū)的內(nèi)容不可以被修改。

  1. 代碼區(qū)
  • 程序執(zhí)行代碼(二進制代碼文件)存放在代碼區(qū)。
1.9 *p++ 和 (*p)++ 的區(qū)別
  • *p++ 先完成取地址,然后對指針地址進行++,再取值
  • (*p)++,先完成取值,再對值進行++
1.10 new / delete 與 malloc / free的異同
  • 相同點

    • 都可用于內(nèi)存的動態(tài)申請和釋放
  • 不同點

    • new / delete 是C++運算符,malloc / free是C/C++語言標準庫函數(shù)

    • new自動計算要分配的空間大小,malloc需要手工計算

    • malloc和free返回的是void類型指針(必須進行類型轉(zhuǎn)換),new和delete返回的是具體類型指針。

    • new是類型安全的,malloc不是。例如:

      int *p = new float[2]; //編譯錯誤
      int *p = (int*)malloc(2 * sizeof(double));//編譯無錯誤
    • malloc / free需要庫文件支持,new / delete不用

    • new是封裝了malloc,直接free不會報錯,但是這只是釋放內(nèi)存,而不會析構(gòu)對象

1.11 new和delete是如何實現(xiàn)的?
  • new的實現(xiàn)過程是:首先調(diào)用名為operator new的標準庫函數(shù),分配足夠大的原始為類型化的內(nèi)存,以保存指定類型的一個對象;接下來運行該類型的一個構(gòu)造函數(shù),用指定初始化構(gòu)造對象;最后返回指向新分配并構(gòu)造后的的對象的指針
  • delete的實現(xiàn)過程:對指針指向的對象運行適當?shù)奈鰳?gòu)函數(shù);然后通過調(diào)用名為operator delete的標準庫函數(shù)釋放該對象所用內(nèi)存
1.12 被free回收的內(nèi)存是立即返還給操作系統(tǒng)嗎?

不是的,被free回收的內(nèi)存會首先被ptmalloc使用雙鏈表保存起來,當用戶下一次申請內(nèi)存的時候,會嘗試從這些內(nèi)存中尋找合適的返回。這樣就避免了頻繁的系統(tǒng)調(diào)用,占用過多的系統(tǒng)資源。同時ptmalloc也會嘗試對小塊內(nèi)存進行合并,避免過多的內(nèi)存碎片。

1.13 C++中幾種類型的new
  1. plain new
    言下之意就是普通的new,就是我們常用的new,在C++中定義如下:

    void* operator new(std::size_t) throw(std::bad_alloc);
    void operator delete(void *) throw();

    plain new在空間分配失敗的情況下,拋出異常std::bad_alloc而不是返回NULL

    #include#includeusing namespace std;
    int main()
    {try
    	{char *p = new char[10e11];
    		delete p;
    	}
    	catch (const std::bad_alloc &ex)
    	{cout<< ex.what()<< endl;
    	}
    	return 0;
    }
    //執(zhí)行結(jié)果:bad allocation
  2. nothrow new
    nothrow new在空間分配失敗的情況下是不拋出異常,而是返回NULL,定義如下:

    void * operator new(std::size_t,const std::nothrow_t&) throw();
    void operator delete(void*) throw();
    #include#includeusing namespace std;
    
    int main()
    {char *p = new(nothrow) char[10e11];
    	if (p == NULL) 
    	{cout<< "alloc failed"<< endl;
    	}
    	delete p;
    	return 0;
    }
    //運行結(jié)果:alloc failed
  3. placement new
    這種new允許在一塊已經(jīng)分配成功的內(nèi)存上重新構(gòu)造對象或?qū)ο髷?shù)組。placement new不用擔心內(nèi)存分配失敗,因為它根本不分配內(nèi)存,它做的唯一一件事情就是調(diào)用對象的構(gòu)造函數(shù)。定義如下:

    void* operator new(size_t,void*);
    void operator delete(void*,void*);

使用placement new需要注意兩點:

  • palcement new的主要用途就是反復使用一塊較大的動態(tài)分配的內(nèi)存來構(gòu)造不同類型的對象或者他們的數(shù)組

  • placement new構(gòu)造起來的對象數(shù)組,要顯式的調(diào)用他們的析構(gòu)函數(shù)來銷毀(析構(gòu)函數(shù)并不釋放對象的內(nèi)存),千萬不要使用delete,這是因為placement new構(gòu)造起來的對象或數(shù)組大小并不一定等于原來分配的內(nèi)存大小,使用delete會造成內(nèi)存泄漏或者之后釋放內(nèi)存時出現(xiàn)運行時錯誤。

    #include#includeusing namespace std;
    class ADT{int i;
    	int j;
    public:
    	ADT(){i = 10;
    		j = 100;
    		cout<< "ADT construct i="<< i<< "j="<cout<< "ADT destruct"<< endl;
    	}
    };
    int main()
    {char *p = new(nothrow) char[sizeof ADT + 1];
    	if (p == NULL) {cout<< "alloc failed"<< endl;
    	}
    	ADT *q = new(p) ADT;  //placement new:不必擔心失敗,只要p所指對象的的空間足夠ADT創(chuàng)建即可
    	//delete q;//錯誤!不能在此處調(diào)用delete q;
    	q->ADT::~ADT();//顯示調(diào)用析構(gòu)函數(shù)
    	delete[] p;
    	return 0;
    }
    //輸出結(jié)果:
    //ADT construct i=10j=100
    //ADT destruct
1.14 delete p、delete [] p、allocator都有什么作用?
  • delete p ,為消除一個對象。

  • delete[]時,數(shù)組中的元素按逆序的順序進行銷毀;

  • new在內(nèi)存分配上面有一些局限性,new的機制是將內(nèi)存分配和對象構(gòu)造組合在一起,同樣的,delete也是將對象析構(gòu)和內(nèi)存釋放組合在一起的。allocator將這兩部分分開進行,allocator申請一部分內(nèi)存,不進行初始化對象,只有當需要的時候才進行初始化操作。

1.15 malloc與free的實現(xiàn)原理?

1、 在標準C庫中,提供了malloc/free函數(shù)分配釋放內(nèi)存,這兩個函數(shù)底層是由brk、mmap、munmap這些系統(tǒng)調(diào)用實現(xiàn)的;

2、 brk是將數(shù)據(jù)段(.data)的最高地址指針_edata往高地址推,mmap是在進程的虛擬地址空間中(堆和棧中間,稱為文件映射區(qū)域的地方)找一塊空閑的虛擬內(nèi)存。這兩種方式分配的都是虛擬內(nèi)存,沒有分配物理內(nèi)存。在第一次訪問已分配的虛擬地址空間的時候,發(fā)生缺頁中斷,操作系統(tǒng)負責分配物理內(nèi)存,然后建立虛擬內(nèi)存和物理內(nèi)存之間的映射關(guān)系;

3、 malloc小于128k的內(nèi)存,使用brk分配內(nèi)存,將_edata往高地址推;malloc大于128k的內(nèi)存,使用mmap分配內(nèi)存,在堆和棧之間找一塊空閑內(nèi)存分配;brk分配的內(nèi)存需要等到高地址內(nèi)存釋放以后才能釋放,而mmap分配的內(nèi)存可以單獨釋放。當最高地址空間的空閑內(nèi)存超過128K(可由M_TRIM_THRESHOLD選項調(diào)節(jié))時,執(zhí)行內(nèi)存緊縮操作(trim)。在上一個步驟free的時候,發(fā)現(xiàn)最高地址空閑內(nèi)存超過128K,于是內(nèi)存緊縮。

4、 malloc是從堆里面申請內(nèi)存,也就是說函數(shù)返回的指針是指向堆里面的一塊內(nèi)存。操作系統(tǒng)中有一個記錄空閑內(nèi)存地址的鏈表。當操作系統(tǒng)收到程序的申請時,就會遍歷該鏈表,然后就尋找第一個空間大于所申請空間的堆結(jié)點,然后就將該結(jié)點從空閑結(jié)點鏈表中刪除,并將該結(jié)點的空間分配給程序。

1.16 malloc、realloc、calloc的區(qū)別
  • malloc函數(shù)

    void* malloc(unsigned int num_size);
    int *p = malloc(20*sizeof(int));申請20個int類型的空間;
  • calloc函數(shù)

    void* calloc(size_t n,size_t size);
    int *p = calloc(20, sizeof(int));

    省去了人為空間計算;malloc申請的空間的值是隨機初始化的,calloc申請的空間的值是初始化為0的;

  • realloc函數(shù)

    void realloc(void *p, size_t new_size);

    給動態(tài)分配的空間分配額外的空間,用于擴充容量。

1.17 exit()和return 的區(qū)別
  • return是語言級的,標志調(diào)用堆棧的返回。是從當前函數(shù)的返回,main()中return的退出程序
  • exit()是函數(shù),強行退出程序,并返回值給系統(tǒng)
  • return實現(xiàn)函數(shù)邏輯,函數(shù)的輸出。exit()只用來退出。
1.18 extern和export的作用

變量的聲明有兩種情況:

  1. 一種是需要建立存儲空間的。例如:int a 在定義的時候就已經(jīng)建立了存儲空間。

  2. 另一種是不需要建立存儲空間的。 例如:extern int a 其中變量a是在別的文件中定義的。

  3. 總之就是:把建立空間的聲明成為“定義”,把不需要建立存儲空間的成為“聲明”。

  • extern
    • 普通變量、類。結(jié)構(gòu)體
  • export(C++中新增)
    • 和exturn類似,但是用作模板
    • 使用該關(guān)鍵字可實現(xiàn)模板函數(shù)的外部調(diào)用
    • 模板實現(xiàn)的時候前面加上export,別的文件包含頭文件就可用該模板
extern"C"的用法

在C語言的頭文件中,對其外部函數(shù)只能指定為extern類型,C語言中不支持extern "C"聲明,在.c文件中包含了extern "C"時會出現(xiàn)編譯語法錯誤。**所以使用extern "C"全部都放在于cpp程序相關(guān)文件或其頭文件中。
C++中調(diào)用C代碼:

//xx.h
extern int add(...)

//xx.c
int add(){}

//xx.cpp
extern "C" {#include "xx.h"
}

C調(diào)用C++函數(shù)

//xx.h
extern "C"{int add();
}
//xx.cpp
int add(){}
//xx.c
extern int add();
1.19 C++中,explicit的作用

explicit阻止隱式轉(zhuǎn)換

  • 隱式轉(zhuǎn)換

    String s1 = "hello";
    //進行隱式轉(zhuǎn)換,等價于
    String s1 = String("hello");
  • explicit阻止隱式轉(zhuǎn)換

    class Test1
    {public:
    	Test1(int n){num = n }
    private:
    	int num;
    }
    
    class Test2
    {public:
    	explicit Test2(int n){num = n }
    private:
    	int num;
    }
    
    int main()
    {Test1 t1 = 1; //正確,隱式轉(zhuǎn)換
    	Test2 t2 = 1;//錯誤,禁止隱式轉(zhuǎn)換
    	Test2 t2(1); //正確,可與顯示調(diào)用
    }
1.20 C++的異常處理

C++中的異常處理機制主要使用try、throw和catch三個關(guān)鍵字

#includeusing namespace std;
int main()
{double m = 1, n = 0;
    try {cout<< "before dividing."<< endl;
        if (n == 0)
            throw - 1;  //拋出int型異常
        else if (m == 0)
            throw - 1.0;  //拋出 double 型異常
        else
            cout<< m / n<< endl;
        cout<< "after dividing."<< endl;
    }
    catch (double d) {cout<< "catch (double)"<< d<< endl;
    }
    catch (...) {cout<< "catch (...)"<< endl;
    }
    cout<< "finished"<< endl;
    return 0;
}
//運行結(jié)果
//before dividing.
//catch (...)
//finished

代碼中,對兩個數(shù)進行除法計算,其中除數(shù)為0??梢钥吹揭陨先齻€關(guān)鍵字,

  • 程序的執(zhí)行流程是先執(zhí)行try包裹的語句塊,如果執(zhí)行過程中沒有異常發(fā)生,則不會進入任何catch包裹的語句塊,如果發(fā)生異常,則使用throw進行異常拋出,再由catch進行捕獲,
  • throw可以拋出各種數(shù)據(jù)類型的信息,代碼中使用的是數(shù)字,也可以自定義異常class。catch根據(jù)throw拋出的數(shù)據(jù)類型進行精確捕獲(不會出現(xiàn)類型轉(zhuǎn)換),如果匹配不到就直接報錯,可以使用catch(…)的方式捕獲任何異常(不推薦)。
  • 當然,如果catch了異常,當前函數(shù)如果不進行處理,或者已經(jīng)處理了想通知上一層的調(diào)用者,可以在catch里面再throw異常。
1.21 回調(diào)函數(shù)
  • 把一段可執(zhí)行的代碼像參數(shù)傳遞那樣傳給其他代碼,而這段代碼會在某個時刻被調(diào)用執(zhí)行,這就叫做回調(diào)。

  • 如果代碼立即被執(zhí)行就稱為同步回調(diào),如果過后再執(zhí)行,則稱之為異步回調(diào)。

  • 回調(diào)函數(shù)就是一個通過函數(shù)指針調(diào)用的函數(shù)。如果你把函數(shù)的指針(地址)作為參數(shù)傳遞給另一個函數(shù),當這個指針被用來調(diào)用其所指向的函數(shù)時,我們就說這是回調(diào)函數(shù)。

  • 主函數(shù)和回調(diào)函數(shù)是在同一層的,而庫函數(shù)在另外一層。如果庫函數(shù)對我們不可見,我們修改不了庫函數(shù)的實現(xiàn),也就是說不能通過修改庫函數(shù)讓庫函數(shù)調(diào)用普通函數(shù)那樣實現(xiàn),那我們就只能通過傳入不同的回調(diào)函數(shù)

  • sort(),中自定義的cmp就是回調(diào)函數(shù)

1.22 C++中,mutable的作用

mutable的中文意思是“可變的,易變的”,在C++中,mutable也是為了突破const的限制而設置的。被mutable修飾的變量,將永遠處于可變的狀態(tài),即使在一個const函數(shù)中。

class person
{int m_A;
mutable int m_B;//特殊變量 在常函數(shù)里值也可以被修改
public:
     void add() const//在函數(shù)里不可修改this指針指向的值 常量指針
     {m_A=10;//錯誤  不可修改值,this已經(jīng)被修飾為常量指針
        m_B=20;//正確
     }
}

int main()
{const person p;//修飾常對象 不可修改類成員的值
	p.m_A=10;//錯誤,被修飾了指針常量
	p.m_B=200;//正確,特殊變量,修飾了mutable
}
2. 內(nèi)存分配 2.1 C++內(nèi)存分配

見 1.8

2.2 內(nèi)存泄漏 內(nèi)存泄露的原因

內(nèi)存泄漏是指堆內(nèi)存的泄漏。使用malloc,、realloc、 new等函數(shù)從堆中分配到塊內(nèi)存,使用完后,程序必須負責相應的調(diào)用free或delete釋放該內(nèi)存塊,如果沒有釋放內(nèi)存這塊內(nèi)存就不能被再次使用,我們就說這塊內(nèi)存泄漏了

避免內(nèi)存泄露的幾種方式
  • 計數(shù)法:使用new或者malloc時,讓該數(shù)+1,delete或free時,該數(shù)-1,程序執(zhí)行完打印這個計數(shù),如果不為0則表示存在內(nèi)存泄露
  • 一定要將基類的析構(gòu)函數(shù)聲明為虛函數(shù)(這樣子類的析構(gòu)函數(shù)必須重新實現(xiàn),避免忘記釋放內(nèi)存)
  • 對象數(shù)組的釋放一定要用delete []
  • 有new就有delete,有malloc就有free,保證它們一定成對出現(xiàn)
內(nèi)存泄漏檢測工具
  • 從Linux下可以使用Valgrind工具
  • Windows下可以使用CRT庫
2.3 棧默認的大小
  • Windows 下是2MB
  • Linux下是8MB(ulimit-s 設置)
2.4 sizeof() 和 strlen()的區(qū)別
int a ,b;
a = strlen("\0");
b = sizeof("\0");
// a = 0 , b = 2;
  • sizeof()是c關(guān)鍵字,計算內(nèi)存大小,字節(jié)單位

  • strlen()是函數(shù),計算字符串的長度,到\0結(jié)束

  • sizeof是編譯確定的,strlen是運行確定的

    int a,b,c;
    char str[20] = "0123456789";
    const char *str2 = "0123456789";
    a = strlen(str);
    b = sizeof(str);
    c = sizeof(&str);
    d = strlen(str2);
    e = sizeof(str2);
    // a = 10 , b = 20 , c = 4(指針大小);
    // d = 10 , e = 4(指針大小)
2.5 struct結(jié)構(gòu)體的數(shù)據(jù)對齊 為什么結(jié)構(gòu)體的sizeof返回值一般大于期望?
  • struct的sizeof是所有成員數(shù)據(jù)對其后長度相加
  • union的sizeof是取大的成員長度(所有成員共用一個內(nèi)存)
struct數(shù)據(jù)對其的目的?
  • 是編譯器的一種計算手段,在空間和復雜度上的平衡,在空間浪費可接收的前提下cpu運算最快處理

  • 32位數(shù)據(jù)傳輸是4字節(jié)(數(shù)據(jù)字長),struct進行4的倍數(shù)對其。64位數(shù)據(jù)傳輸是8字節(jié),8的倍數(shù)對其

  • 對齊的目的是要讓數(shù)據(jù)訪問更高效,一般來說,數(shù)據(jù)類型的對齊要求和它的長度是一致的,比如,

    char 是 1
      short 是 2
      int 是 4
      double 是 8
    • 這不是巧合,比如short,2對齊保證了short只可能出現(xiàn)在一個讀取單元的0, 2, 4, 6格,而不會出現(xiàn)在1, 3, 5, 7格;
    • 再比如int,4對齊保證了一個讀取單元可以裝載2個int——在0或者4格。
    • 從根本上杜絕了同一個數(shù)據(jù)橫跨讀取單元的問題。
修改默認的數(shù)據(jù)對齊
  • #pragma pack(n),編譯器按照n字節(jié)對其
  • #pragma pack( ),取消自定義對其
  • __ attribute__((aligned(n))) ,讓結(jié)構(gòu)體成員對其在n字節(jié)自然邊界上,如果成員大于n,按照大成員長度
  • __ attribute__((packed)),取消編譯過程的對齊,按照實際占用字節(jié)對其
C++11中內(nèi)存對其關(guān)鍵字
  • alignof,計算出類型對齊的方式
  • alignas,指定結(jié)構(gòu)體的對齊方式
struct Info {uint8_t a;
  uint16_t b;
  uint8_t c;
};

std::cout<< sizeof(Info)<< std::endl;   // 6  2 + 2 + 2
std::cout<< alignof(Info)<< std::endl;  // 2

//alignas將內(nèi)存對齊調(diào)整為4個字節(jié)。所以sizeof(Info2)的值變?yōu)榱?。
struct alignas(4) Info2 {uint8_t a;
  uint16_t b;
  uint8_t c;
};

std::cout<< sizeof(Info2)<< std::endl;   // 8  4 + 4
std::cout<< alignof(Info2)<< std::endl;  // 4

若alignas小于自然對齊的最小單位,則被忽略。

2.6 堆和棧的區(qū)別
  • 申請方式不同。

    • 棧由系統(tǒng)自動分配。
    • 堆是自己申請和釋放的。
  • 申請大小限制不同。

    • 棧頂和棧底是之前預設好的,棧是向棧底擴展,大小固定,可以通過ulimit -a查看,由ulimit -s修改。

    • 堆向高地址擴展,是不連續(xù)的內(nèi)存區(qū)域,大小可以靈活調(diào)整。

  • 申請效率不同。

    • 棧由系統(tǒng)分配,速度快,不會有碎片。

    • 堆由程序員分配,速度慢,且會有碎片。

  • ??臻g默認是4M, 堆區(qū)一般是 1G - 4G

  • 速度不同

    • 毫無疑問是棧快一點。

    • 因為操作系統(tǒng)會在底層對棧提供支持,會分配專門的寄存器存放棧的地址,棧的入棧出棧操作也十分簡單,并且有專門的指令執(zhí)行,所以棧的效率比較高也比較快。

    • 而堆的操作是由C/C++函數(shù)庫提供的,在分配堆內(nèi)存的時候需要一定的算法尋找合適大小的內(nèi)存。并且獲取堆的內(nèi)容需要兩次訪問,第一次訪問指針,第二次根據(jù)指針保存的地址訪問內(nèi)存,因此堆比較慢。

2.7 形參和實參的區(qū)別
  • 形參變量只有在被調(diào)用時才分配內(nèi)存單元,在調(diào)用結(jié)束時, 即刻釋放所分配的內(nèi)存單元。

  • 實參可以是常量、變量、表達式、函數(shù)等, 無論實參是何種類型的量,在進行函數(shù)調(diào)用時,它們都必須具有確定的值, 以便把這些值傳送給形參。

  • 實參和形參在數(shù)量上,類型上,順序上應嚴格一致, 否則會發(fā)生“類型不匹配”的錯誤。

  • 函數(shù)調(diào)用中發(fā)生的數(shù)據(jù)傳送是單向的。 即只能把實參的值傳送給形參,而不能把形參的值反向地傳送給實參。

  • 當形參和實參不是指針類型時,在該函數(shù)運行時,形參和實參是不同的變量,他們在內(nèi)存中位于不同的位置,形參將實參的內(nèi)容復制一份,在該函數(shù)運行結(jié)束的時候形參被釋放,而實參內(nèi)容不會改變。

3. 指針 3.1 指針的優(yōu)點?

指針變量和一般變量區(qū)別,一般變量是包含的是數(shù)據(jù),而指針變量包含的是地址

  • 動態(tài)分配內(nèi)存,直接操作內(nèi)存,效率高
  • 實現(xiàn)動態(tài)數(shù)據(jù)結(jié)構(gòu)(樹、鏈表)
  • 高效的“復制”數(shù)據(jù)
3.2 引用和指針
  • 指針是一個變量,存儲的是一個地址,引用跟原來的變量實質(zhì)上是同一個東西,是原變量的別名
  • 指針可以有多級,引用只有一級
  • 指針可以為空,引用不能為NULL且在定義時必須初始化
  • 指針在初始化后可以改變指向,而引用在初始化之后不可再改變
  • sizeof指針得到的是本指針的大?。?字節(jié)),sizeof引用得到的是引用所指向變量的大小
  • 當把指針作為參數(shù)進行傳遞時,也是將實參的一個拷貝傳遞給形參,兩者指向的地址相同,但不是同一個變量,在函數(shù)中改變這個變量的指向不影響實參,而引用卻可以。
  • 引用本質(zhì)是一個指針,同樣會占4字節(jié)內(nèi)存;指針是具體變量,需要占用存儲空間(具體情況還要具體分析)。
  • 引用在聲明時必須初始化為另一變量;指針聲明和定義可以分開,可以先只聲明指針變量而不初始化,等用到時再指向具體變量。
3.3 數(shù)組和指針
  • 數(shù)組在內(nèi)存中是連續(xù)存放的,開辟一塊連續(xù)的內(nèi)存空間;數(shù)組所占存儲空間:sizeof(數(shù)組名);數(shù)組大小:sizeof(數(shù)組名)/sizeof(數(shù)組元素數(shù)據(jù)類型);

  • 用運算符sizeof 可以計算出數(shù)組的容量(字節(jié)數(shù))。sizeof( p ),p 為指針得到的是一個指針變量的字節(jié)數(shù)(4),而不是p 所指的內(nèi)存容量。

  • 編譯器為了簡化對數(shù)組的支持,實際上是利用指針實現(xiàn)了對數(shù)組的支持。具體來說,就是將表達式中的數(shù)組元素引用轉(zhuǎn)換為指針加偏移量的引用。

  • 在向函數(shù)傳遞參數(shù)的時候,如果實參是一個數(shù)組,那用于接受的形參為對應的指針。也就是傳遞過去是數(shù)組的首地址而不是整個數(shù)組,能夠提高效率;

  • 在使用下標的時候,兩者的用法相同,都是原地址加上下標值,不過數(shù)組的原地址就是數(shù)組首元素的地址是固定的,指針的原地址就不是固定的。

數(shù)組名和指針
  • 二者均可通過增減偏移量來訪問數(shù)組中的元素。

  • 數(shù)組名不是真正意義上的指針,可以理解為常指針,所以數(shù)組名沒有自增、自減等操作。

  • 當數(shù)組名當做形參傳遞給調(diào)用函數(shù)后,就失去了原有特性,退化成一般指針,多了自增、自減操作,但sizeof運算符不能再得到原數(shù)組的大小了。

3.4 指針的加法

指針加上n,為加上n個指針類型的長度

unsigned char*p1 = 0x801000;
unsigned int *p2 = 0x810000;
p1+=5;//p1 = 0x801000 + 5*1 = 0x801005;
p2+=5;//p2 = 0x810000 + 5*4 = 0x810000;
3.5 空指針、野指針和懸空指針
  • 空指針
    空指針不會指向任何地方,它不是任何對象或函數(shù)的地址

    int *p = NULL;
    int *p2 = nullptr;
  • 野指針
    指的是沒有被初始化過的指針

    int main(void) {
        int* p;     // 未初始化
        std::cout<< *p<< std::endl; // 未初始化就被使用
        
        return 0;
    }
  • 懸空指針
    最初指向的內(nèi)存已經(jīng)被釋放了的一種指針

    int main(void) {  int * p = nullptr;
      int* p2 = new int;
      p = p2;
      delete p2;
    }

    此時 p和p2就是懸空指針,指向的內(nèi)存已經(jīng)被釋放。繼續(xù)使用這兩個指針,行為不可預料

    野指針和懸空指針的產(chǎn)生和解決
  • 野指針:指針變量未及時初始化 =>定義指針變量及時初始化,要么置空。

  • 懸空指針:指針free或delete之后沒有及時置空 =>釋放操作后立即置空。
    或使用智能指針(避免懸空指針產(chǎn)生)

3.6 指針函數(shù)和函數(shù)指針的區(qū)別 指針函數(shù)
  • 返回值為指針類型的函數(shù)

    #includeint* fun(int* x)    //傳入指針  
    {int* tmp = x;	  //指針tmp指向x
        return tmp;       //返回tmp指向的地址
    }
    int main()
    {int b = 2;      
        int* p = &b;   //p指向b的地址
        printf("%d",*fun(p));//輸出p指向的地址的值
        return 0;
    }
函數(shù)指針
  • 函數(shù)指針是 指向函數(shù)的指針 。主體是指針,指向的是一個函數(shù)的地址

  • 兩種方法賦值:指針名 = 函數(shù)名; 指針名 = &函數(shù)名

    #includeint add(int x,int y)
    {return x + y;
    }
    
    int main()
    {int (*fun) (int,int);//聲明函數(shù)指針
        fun = &add;		//fun函數(shù)指針指向add函數(shù)
        //fun = add;   //同上,等價fun = &add;
        printf("%d ",fun(3,5));		
        printf("%d",(*fun)(4,2));
        return 0;
    }
3.7 傳遞函數(shù)參數(shù)的時候,什么時候使用指針,什么時候使用引用
  • 需要返回函數(shù)內(nèi)局部變量的內(nèi)存的時候用指針。使用指針傳參需要開辟內(nèi)存,用完要記得釋放指針,不然會內(nèi)存泄漏。而返回局部變量的引用是沒有意義的

  • 對??臻g大小比較敏感(比如遞歸)的時候使用引用。使用引用傳遞不需要創(chuàng)建臨時變量,開銷要更小

  • 類對象作為參數(shù)傳遞的時候使用引用,這是C++類對象傳遞的標準方式

3.8 區(qū)別指針類型
int *p[10]
int (*p)[10]
int *p(int)
int (*p)(int)
  • int *p[10]表示指針數(shù)組,強調(diào)數(shù)組概念,是一個數(shù)組變量,數(shù)組大小為10,數(shù)組內(nèi)每個元素都是指向int類型的指針變量。

  • int (*p)[10]表示數(shù)組指針,強調(diào)是指針,只有一個變量,是指針類型,不過指向的是一個int類型的數(shù)組,這個數(shù)組大小是10。

  • int *p(int)是函數(shù)聲明,函數(shù)名是p,參數(shù)是int類型的,返回值是int *類型的。

  • int (*p)(int)是函數(shù)指針,強調(diào)是指針,該指針指向的函數(shù)具有int類型參數(shù),并且返回值是int類型的。

3.9 int a[10]; int (*p)[10] = &a中a和&a有什么區(qū)別?
  • a是數(shù)組名,是數(shù)組首元素地址,+1表示地址值加上一個int類型的大小,如果a的值是0x00000001,加1操作后變?yōu)?x00000005。*(a + 1) = a[1]。
  • &a是數(shù)組的指針,其類型為int (*)[10](就是前面提到的數(shù)組指針),其加1時,系統(tǒng)會認為是數(shù)組首地址加上整個數(shù)組的偏移(10個int型變量),值為數(shù)組a尾元素后一個元素的地址。
  • 若(int *)p ,此時輸出 *p時,其值為a[0]的值,因為被轉(zhuǎn)為int *類型,解引用時按照int類型大小來讀取。
3.10 值傳遞、指針傳遞、引用傳遞的區(qū)別和效率
  • 值傳遞:有一個形參向函數(shù)所屬的??截悢?shù)據(jù)的過程,如果值傳遞的對象是類對象 或是大的結(jié)構(gòu)體對象,將耗費一定的時間和空間。(傳值)

  • 指針傳遞:同樣有一個形參向函數(shù)所屬的??截悢?shù)據(jù)的過程,但拷貝的數(shù)據(jù)是一個固定為4字節(jié)的地址。(傳值,傳遞的是地址值)

  • 引用傳遞:同樣有上述的數(shù)據(jù)拷貝過程,但其是針對地址的,相當于為該數(shù)據(jù)所在的地址起了一個別名。(傳地址)

效率上講,指針傳遞和引用傳遞比值傳遞效率高。一般主張使用引用傳遞,代碼邏輯上更加緊湊、清晰。

4. 預處理

為編譯做準備工作,處理#開頭的指令
在這里插入圖片描述

4.1 ifndef/define/endif的作用

防止頭文件被重復包含和編譯。頭文件重復包含會增大程序大小,重復編譯增加編譯時間

4.2 #include< >和 #include“ ”的區(qū)別
  • <>和" "表示編譯器在搜索頭文件時的順序不同,
  • <>表示從系統(tǒng)目錄下開始搜索,然后再搜索PATH環(huán)境變量所列出的目錄,不搜索當前目錄,
  • ""是表示從當前目錄開始搜索,然后是系統(tǒng)目錄和PATH環(huán)境變量所列出的目錄。
    所以,系統(tǒng)頭文件一般用<>,用戶自己定義的則可以使用"",加快搜索速度。
4.3 #define 的缺點

#define只能進行字符替換

  • 無法類型檢查

  • 由于優(yōu)先級的不同,會產(chǎn)生潛在問題

    #define MAX_NUM 100+1
    int a = MAX_NUM * 10;//a=110
    //等價于
    int a = 100 + 1 * 10;
    
    //正確定義為
    #define MAX_NUM (100+1)
    int a = MAX_NUM * 10;//a=1010
  • 無法單步調(diào)試

  • 導致代碼膨脹

4.4 寫一個標準宏MIN
#define MIN(A,B) ( (A)<=(B)?(A):(B) )

每個括號都是必須的,如果沒有結(jié)果無法預測

4.5 #define和typdef的區(qū)別
  • define主要用于定義常量及書寫復雜的內(nèi)容;typedef主要用于定義類型別名。

  • define替換發(fā)生在編譯階段之前,屬于文本插入替換;typedef是編譯的一部分。

  • define不檢查類型;typedef會檢查數(shù)據(jù)類型。

  • define不是語句,不在在最后加分號;typedef是語句,要加分號標識結(jié)束。

  • 對指針的操作不同

    #define INTPTR1 int*
    typedef int* INTPTR2;
    INTPTR1 p1, p2;//聲明一個指針變量p1和一個整型變量p2
    INTPTR2 p3, p4;//聲明兩個指針變量p3、p4
    #define INTPTR1 int*
    typedef int* INTPTR2;
    int a = 1;
    int b = 2;
    int c = 3;
    const INTPTR1 p1 = &a;//const INTPTR1 p1是一個常量指針
    const INTPTR2 p2 = &b;//const INTPTR2 p2是一個指針常量
    INTPTR2 const p3 = &c;//INTPTR2 const p3是一個指針常量
4.6 宏定義和內(nèi)聯(lián)函數(shù)的區(qū)別
  • 宏定義是在預處理階段進行代碼替換,內(nèi)聯(lián)函數(shù)是編譯階段插入代碼
  • 宏定義沒有類型檢查,內(nèi)斂函數(shù)有類型檢查
內(nèi)聯(lián)函數(shù)和普通函數(shù)的區(qū)別
  • 編譯器將內(nèi)聯(lián)函數(shù)的位置進行函數(shù)展開,避免函數(shù)調(diào)用的開銷,提高效率
  • 普通函數(shù)被調(diào)用,跳躍到函數(shù)入口地址,執(zhí)行結(jié)束后跳轉(zhuǎn)回調(diào)用地方
  • 內(nèi)斂函數(shù)不需要尋址,執(zhí)行N次內(nèi)聯(lián)函數(shù),代碼就復制N次
  • 函數(shù)體過大,編譯器放棄內(nèi)聯(lián),變的和普通函數(shù)一樣
  • 內(nèi)聯(lián)函數(shù)不能遞歸,編譯器無法預知深度,變成普通函數(shù)
4.7 #define和const區(qū)別?
  • #define只能單純文本替換,不分配周期,寸在代碼段
  • const常量在程序數(shù)據(jù)段,分配內(nèi)存
  • #define沒有數(shù)據(jù)類型,const有數(shù)據(jù)類型
  • #define沒法調(diào)試,const可以調(diào)試
5.結(jié)構(gòu)體與類 5.1 struct和union的區(qū)別
  • 聯(lián)合體所有成員共用一塊內(nèi)存,結(jié)構(gòu)體成員占用空間累加
  • 對聯(lián)合體的不用成員賦值,對其他成員重寫;結(jié)構(gòu)體成員互相不影響
union
{int i;
	char x[2];
}
int main()
{a.x[0] = 10;
	a.x[1] = 1;
	printf("%d",a.i);//輸出為266
}

其中 a.x[0]=10=00001010 ;a.x[1] = 1 = 00000001。
輸出 i 的時候,將a.x[0] a.x[1] 看作一個整數(shù),為00000001 00001010,為256+8+2 = 266

在這里插入圖片描述

5.2 C++中struct和class的區(qū)別

相同點

  • 兩者都擁有成員函數(shù)、公有和私有部分

  • 任何可以使用class完成的工作,同樣可以使用struct完成
    不同點

  • 兩者中如果不對成員不指定公私有,struct默認是公有的,class則默認是私有的

  • class默認是private繼承, 而struct默認是public繼承

5.3 C++和C的struct區(qū)別
  • C語言中:struct是用戶自定義數(shù)據(jù)類型(UDT);C++中struct是抽象數(shù)據(jù)類型(ADT),支持成員函數(shù)的定義,(C++中的struct能繼承,能實現(xiàn)多態(tài))

  • C中struct是沒有權(quán)限的設置的,且struct中只能是一些變量的集合體,可以封裝數(shù)據(jù)卻不可以隱藏數(shù)據(jù),而且成員不可以是函數(shù)

  • C++中,struct增加了訪問權(quán)限,且可以和類一樣有成員函數(shù),成員默認訪問說明符為public(為了與C兼容)

  • struct作為類的一種特例是用來自定義數(shù)據(jù)結(jié)構(gòu)的。一個結(jié)構(gòu)標記聲明后,在C中必須在結(jié)構(gòu)標記前加上struct,才能做結(jié)構(gòu)類型名(除:typedef struct class{};);C++中結(jié)構(gòu)體標記(結(jié)構(gòu)體名)可以直接作為結(jié)構(gòu)體類型名使用,此外結(jié)構(gòu)體struct在C++中被當作類的一種特例

6. 位操作 6.1 最有效的計算2乘8的方法
int a = 2 ;
a = a<<3;//a乘上2的三次方
計算乘7倍
int a = 2 ;
a = (a<<3)-a;
6.2 位操作求兩個數(shù)的平均值
int a = 2 .b =3;
int c ;
c = (a&b) + ((a^b)>>1);
  • 對于表達式(x&y)+(xy)>>1), x&y表示的是取出x與y二進制位數(shù)中都為1的所有位, xy表示的是x與y中有一個為1’的所有位,右移1位相當于執(zhí)行除以2運算。
  • 整個表達式實際上可以分為兩部分,第一部分是都為1的部分,求平均數(shù)后這部分的值保持不變;而第二部分是x為1、y為0的部分,以及y為1、x為0的部分,兩部分加起來再除以2,然后跟前面的相加就可以表示兩者的平均數(shù)了
6.3 什么是大端和小端,如何判斷?
  • 小端存儲:字數(shù)據(jù)的低字節(jié)存儲在低地址中(數(shù)據(jù)存儲從低字節(jié)到高字節(jié))
  • 大端存儲:字數(shù)據(jù)的高字節(jié)存儲在低地址中(數(shù)據(jù)存儲從高字節(jié)到低字節(jié))
    例如:32bit的數(shù)字0x12345678

在這里插入圖片描述

代碼判斷
  • 方式一:使用強制類型轉(zhuǎn)換-這種法子不錯
#includeusing namespace std;
int main()
{int a = 0x1234;
    //由于int和char的長度不同,借助int型轉(zhuǎn)換成char型,只會留下低地址的部分
    char c = (char)(a);
    if (c == 0x12)
        cout<< "big endian"<< endl;
    else if(c == 0x34)
        cout<< "little endian"<< endl;
}
  • 方式二:巧用union聯(lián)合體
#includeusing namespace std;
//union聯(lián)合體的重疊式存儲,endian聯(lián)合體占用內(nèi)存的空間為每個成員字節(jié)長度的大值
union endian
{int a;
    char ch;
};
int main()
{endian value;
    value.a = 0x1234;
    //a和ch共用4字節(jié)的內(nèi)存空間
    if (value.ch == 0x12)
        cout<< "big endian"<
7. 編譯 7.1 main函數(shù)執(zhí)行前和執(zhí)行后的代碼 main函數(shù)執(zhí)行前(初始化系統(tǒng)相關(guān)資源)
  • 設置棧指針
  • 初始化靜態(tài)static變量和global全局變量,即.data段的內(nèi)容
  • 將未初始化部分的全局變量賦初值:數(shù)值型short,int,long等為0,bool為FALSE,指針為NULL等等,即.bss段的內(nèi)容
  • 全局對象初始化,在main之前調(diào)用全局對象的構(gòu)造函數(shù),這是可能會執(zhí)行前的一些代碼
  • 將main函數(shù)的參數(shù)argc,argv等傳遞給main函數(shù),然后才真正運行main函數(shù)
  • __ attribute __((constructor))
main函數(shù)執(zhí)行后
  • 全局對象的析構(gòu)函數(shù)會在main函數(shù)之后執(zhí)行;
  • 可以用 atexit 注冊一個函數(shù),它會在main 之后執(zhí)行;
  • __ attribute__((destructor))
8.面向?qū)ο? 8.1 final和override關(guān)鍵字 override

override指定了子類的這個虛函數(shù)是重寫的父類的,如果你名字不小心打錯了的話,編譯器是不會編譯通過的。

class A
{virtual void foo();
};
class B : public A
{virtual void f00(); //OK,這個函數(shù)是B新增的,不是繼承的
    virtual void f0o() override; //Error, 加了override之后,這個函數(shù)一定是繼承自A的,A找不到就報錯
    //virtual void foo() override; //ok,是繼承父類的虛函數(shù)
};
final

不希望某個類被繼承,或不希望某個虛函數(shù)被重寫,可以在類名和虛函數(shù)后添加final關(guān)鍵字,添加final關(guān)鍵字后被繼承或重寫,編譯器會報錯。

class Base
{virtual void foo();
};
 
class A : public Base
{void foo() final; // foo 被override并且是最后一個override,在其子類中不可以重寫
};

class B final : A // 指明B是不可以被繼承的
{void foo() override; // Error: 在A中已經(jīng)被final了
};
 
class C : B // Error: B is final
{};
8.2 拷貝初始化和直接初始化

當用于類類型對象時,初始化的拷貝形式和直接形式有所不同:

  • 直接初始化直接調(diào)用與實參匹配的構(gòu)造函數(shù)
  • 拷貝初始化首先使用指定構(gòu)造函數(shù)創(chuàng)建一個臨時對象,然后用拷貝構(gòu)造函數(shù)將那個臨時對象拷貝到正在創(chuàng)建的對象。
//語句1 直接初始化
string str1("I am a string");
//語句2 直接初始化,str1是已經(jīng)存在的對象,直接調(diào)用拷貝構(gòu)造函數(shù)對str2進行初始化
string str2(str1);
//語句3 拷貝初始化,先為字符串”I am a string“創(chuàng)建臨時對象,再把臨時對象作為參數(shù),使用拷貝構(gòu)造函數(shù)構(gòu)造str3
string str3 = "I am a string";
//語句4 拷貝初始化,這里相當于隱式調(diào)用拷貝構(gòu)造函數(shù),而不是調(diào)用賦值運算符函數(shù)
string str4 = str1;
  • 為了提高效率,允許編譯器跳過創(chuàng)建臨時對象這一步,直接調(diào)用構(gòu)造函數(shù)構(gòu)造要創(chuàng)建的對象,這樣就完全等價于直接初始化了(語句1和語句3等價),但是需要辨別兩種情況。
    • 當拷貝構(gòu)造函數(shù)為private時:語句3和語句4在編譯時會報錯
    • 使用explicit修飾構(gòu)造函數(shù)時:如果構(gòu)造函數(shù)存在隱式轉(zhuǎn)換,編譯時會報錯
8.3 C和C++的類型安全

類型安全很大程度上可以等價于內(nèi)存安全,類型安全的代碼不會試圖訪問自己沒被授權(quán)的內(nèi)存區(qū)域。有的時候也用“類型安全”形容某個程序,判別的標準在于該程序是否隱含類型錯誤。

  • 想保證程序的類型安全性,
    • 應盡量避免使用空類型指針void*,
    • 盡量不對兩種類型指針做強制轉(zhuǎn)換。
8.4 C++中的重載、重寫(覆蓋)和隱藏的區(qū)別 重載(overload)

重載是指在同一范圍定義中的同名成員函數(shù)才存在重載關(guān)系。主要特點是函數(shù)名相同,參數(shù)類型和數(shù)目有所不同,不能出現(xiàn)參數(shù)個數(shù)和類型均相同,僅僅依靠返回值不同來區(qū)分的函數(shù)。重載和函數(shù)成員是否是虛函數(shù)無關(guān)。

class A{...
    virtual int fun();
    void fun(int);
    void fun(double, double);
    static int fun(char);
    ...
}
重寫(覆蓋)(override)

重寫指的是在派生類中覆蓋基類中的同名函數(shù),重寫就是重寫函數(shù)體,要求基類函數(shù)必須是虛函數(shù)且:

  • 與基類的虛函數(shù)有相同的參數(shù)個數(shù)
  • 與基類的虛函數(shù)有相同的參數(shù)類型
  • 與基類的虛函數(shù)有相同的返回值類型
//父類
class A{public:
    virtual int fun(int a){}
}
//子類
class B : public A{public:
    //重寫,一般加override可以確保是重寫父類的函數(shù)
    virtual int fun(int a) override{}
}

重載與重寫的區(qū)別:

  • 重寫是父類和子類之間的垂直關(guān)系,重載是不同函數(shù)之間的水平關(guān)系
  • 重寫要求參數(shù)列表相同,重載則要求參數(shù)列表不同,返回值不要求
隱藏(hide)

隱藏指的是某些情況下,派生類中的函數(shù)屏蔽了基類中的同名函數(shù),包括以下情況:

  • 兩個函數(shù)參數(shù)相同,但是基類函數(shù)不是虛函數(shù)。和重寫的區(qū)別在于基類函數(shù)是否是虛函數(shù)。

    //父類
    class A{public:
        void fun(int a){cout<< "A中的fun函數(shù)"<< endl;
    	}
    };
    //子類
    class B : public A{public:
        //隱藏父類的fun函數(shù)
        void fun(int a){cout<< "B中的fun函數(shù)"<< endl;
    	}
    };
    int main(){B b;
        b.fun(2); //調(diào)用的是B中的fun函數(shù)
        b.A::fun(2); //調(diào)用A中fun函數(shù)
        return 0;
    }
  • 兩個函數(shù)參數(shù)不同,無論基類函數(shù)是不是虛函數(shù),都會被隱藏。和重載的區(qū)別在于兩個函數(shù)不在同一個類中。

    //父類
    class A{public:
        virtual void fun(int a){cout<< "A中的fun函數(shù)"<< endl;
    	}
    };
    //子類
    class B : public A{public:
        //隱藏父類的fun函數(shù)
       virtual void fun(char* a){   cout<< "A中的fun函數(shù)"<< endl;
       }
    };
    int main(){B b;
        b.fun(2); //報錯,調(diào)用的是B中的fun函數(shù),參數(shù)類型不對
        b.A::fun(2); //調(diào)用A中fun函數(shù)
        return 0;
    }
  • 基類指針指向派生類對象時,基類指針可以直接調(diào)用到派生類的覆蓋(重寫)函數(shù),也可以通過 :: 調(diào)用到基類被覆蓋
    的虛函數(shù);

  • 而基類指針只能調(diào)用基類的被隱藏函數(shù),無法識別派生類中的隱藏函數(shù)。

    // 父類
    class A {public:
        virtual void fun(int a) {// 虛函數(shù)
            cout<< "This is A fun "<< a<< endl;
        }  
        void add(int a, int b) {cout<< "This is A add "<< a + b<< endl;
        }
    };
    
    // 子類
    class B: public A {public:
        void fun(int a) override {// 覆蓋(重寫)
            cout<< "this is B fun "<< a<< endl;
        }
        void add(int a) {// 隱藏
            cout<< "This is B add "<< a + a<< endl;
        }
    };
    
    int main() {A *p = new B();
        p->fun(1);      // 調(diào)用子類 fun 覆蓋函數(shù)
        p->A::fun(1);   // 調(diào)用父類 fun
        p->add(1, 2);
        // p->add(1);      // 錯誤,識別的是 A 類中的 add 函數(shù),參數(shù)不匹配
        // p->B::add(1);   // 錯誤,無法識別子類 add 函數(shù)
        return 0;
    }
8.5 淺拷貝和深拷貝的區(qū)別

淺拷貝

淺拷貝只是拷貝一個指針,并沒有新開辟一個地址,拷貝的指針和原來的指針指向同一塊地址,如果原來的指針所指向的資源釋放了,那么再釋放淺拷貝的指針的資源就會出現(xiàn)錯誤。

深拷貝

深拷貝不僅拷貝值,還開辟出一塊新的空間用來存放新的值,即使原先的對象被析構(gòu)掉,釋放內(nèi)存了也不會影響到深拷貝得到的值。在自己實現(xiàn)拷貝賦值的時候,如果有指針變量的話是需要自己實現(xiàn)深拷貝的。

8.6 public,protected和private訪問和繼承權(quán)限 訪問權(quán)限

在這里插入圖片描述

public、protected、private 的訪問權(quán)限范圍關(guān)系:public >protected >private

繼承權(quán)限
  1. public繼承
    公有繼承的特點是,基類的公有和保護,變派生類的公有和保護,基類私有不可訪問

  2. protected繼承
    保護繼承的特點是,基類的公有和保護,變派生類的保護,基類私有派生類不可訪問

  3. private繼承
    私有繼承的特點是,基類的公有和保護,變派生類的私有,基類私有派生類不可訪問

在這里插入圖片描述

你是否還在尋找穩(wěn)定的海外服務器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準確流量調(diào)度確保服務器高可用性,企業(yè)級服務器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧


本文標題:C/C++重點八股文-創(chuàng)新互聯(lián)
文章URL:http://weahome.cn/article/dgjpos.html

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部