C/C++知識點彙總

1.

C++中類對象的內存佈局和佔用空間:
a.非靜態成員變量總合。

b.編譯器爲了CPU計算,作出的數據對齊處理(可用#pragma pack(n)來設定變量的對齊方式)。

c.爲了支持虛函數,產生的額外負擔。

//例子:
#pragma pack(2)
class BU
{
    int number;			//4
    union UBffer
    {
        char buffer[13];
        int number;
    }ubuf;			//13+1
    void foo(){}		//0
    typedef char*(*f)(void*);	//0
    enum{hdd,ssd,blueray}disk;	//4
}bu;
//sizeof(bu)的值爲22。

重點:
空類在C語言中佔0字節,在C++中佔1字節。
sizeof(void):編譯錯誤或者爲1。sizeof(void*):指針大小。

2.
純虛函數可以有函數體:

出處:《C++Primer》中文版第五版541頁第22行。

我們可以爲純虛函數提供定義,不過函數體必須定義在類的外部。若定義在類的內部,會出現錯誤:pure-specifier on function-definition

class Dummy
{
	//error:pure_specifier on function-definition.
	virtual void process()=0{};
};


class Dummy
{
	virtual void process()=0;
}
void Dummy::process()
{}

3.
C++命名的強制類型轉換(static_cast dynamic_cast const_cast reinterpret_cast):

cast_name<type>(expression)

A.static_cast:任何具有明確定義的類型轉換,只要不包含底層const(比如常量指針,而不是指針常量),都可以使用。常用於窄化轉換(告訴程序的讀者和編譯器:我們知道並且不在乎潛在的精度損失),編譯器無法自動執行的類型轉換(找回存在於void*指針中的值)。

a.用於類層次結構中基類和派生類之間指針或者引用的轉換(up-casting把派生類的指針或引用轉換成基類的指針或引用是安全的,down-casting把基類的指針或引用轉換成派生類的指針或引用是不安全的)。

b.基本類型之間的轉換。

c.把空指針轉換成目標類型。

d.不能提供數字到指針的轉換,不能提供不同類型指針之間的轉換。


B.const_cast:只能改變運算對象的底層const(一旦我們去掉了某個對象的const性質,編譯器就不再阻止我們對該對象進行寫操作了。如果對象本身不是一個常量,使用強制類型轉換獲得寫權限是合法的行爲。然而如果對象是一個常量,再使用const_cast執行寫操作就會產生未定義的後果)。只有const_cast能改變表達式的常量屬性,使用其他形式的命名強制類型轉換改變表達式的常量屬性都將引發編譯器錯誤。同樣的,也不能用const_cast改變表達式的類型。常用於函數重載中

//比較兩個string對象的長度,返回較短的那個引用.
const string &shortString(const string &s1,const string &s2)
{
	return s1.size()<=s2.size()?s1:s2;
}
/*當我們對兩個非常量的string實參調用這個函數,但返回的結果仍然是const string的引用。因此我們需要一種新的shortString函數,當它的實參不是常量時,得到的結果是一個普通的引用,使用const_cast可以做到這一點:*/
string &shortString(string &s1,string &s2)
{
	//調用這個函數的目的:我們只要比較大小,這樣調用是聲明我們不能修改參數!!
	auto &r=shortString(const_cast<const string&>(s1),const_cast<const string&>(s2));
	return const_cast<string&>(r);
}


C.reinterpret_cast:通常爲運算對象的位模式提供較低層次上的重新解釋(數字到指針之間的轉換,不同類型指針之間的轉換)。操作結果只是簡單的從一個指針到別的指針的值的二進制拷貝,在類型之間指向的內容不做任何類型的檢查和轉換。慎用!!


D.dynamic_cast:該轉換符用於將一個指向派生類的基類指針或引用轉換爲派生類的指針或引用,注意dynamic_cast轉換符只能用於含有虛函數的類。比如含有虛函數的基類B和從基類B派生出的派生類D,則B *pb; D *pd, d; pb=&d; pd=dynamic_cast<D*>(pb); 最後一條語句表示把指向派生類D的基類指針pb轉換爲派生類D的指針,然後將這個指針賦給派生類D的指針pd,有人可能會覺得這樣做沒有意義,既然指針pd要指向派生類爲什麼不pd=&d;這樣做更直接呢?因爲虛函數的基類版本和派生類版本必須具有相同的形參類型。就是當在派生類中覆蓋虛函數時,若參數有基類指針或引用,需在函數裏將參數強制轉換爲派生類指針或引用。也即有些時候我們需要強制轉換,比如如果指向派生類的基類指針B想訪問派生類D中的除虛函數之外的成員時就需要把該指針轉換爲指向派生類D的指針,以達到訪問派生類D中特有的成員的目的,比如派生類D中含有特有的成員函數g(),這時可以這樣來訪問該成員dynamic_cast<D*>(pb)->g();因爲dynamic_cast轉換後的結果是一個指向派生類的指針,所以可以這樣訪問派生類中特有的成員。但是該語句不影響原來的指針的類型,即基類指針pb仍然是指向基類B的。如果單獨使用該指針仍然不能訪問派生類中特有的成員。


4.

a.text:已編譯程序的機器代碼。

b.data:已初始化的全局變量。

c.bss:未初始化的全局變量。在目標文件中這個節不佔據實際的空間,它僅僅是一個佔位符。目標文件格式區分初始化和未初始化變量是爲了空間效率:在目標文件中,未初始化變量不需要佔據任何實際的磁盤空間。

d.heap:

e.stack:


5.

A.通用多態

a.參數多態:模板。

b.包含多態:virtual。


B.特定多態

a.重載多態:

b.強制多態:強制類型轉換。


6.

面向對象的三大基本特徵:封裝(數據抽象)、繼承和多態(動態綁定)。

面向對象的五大基本原則:單一職責原則、開放封閉原則(擴展性開放,更改性封閉)、里氏替換原則、接口隔離原則和依賴倒置原則。


7.

對於free(p)這條語句,如果p是NULL指針,那麼free對p無論操作多少次都不會出問題。如果p不是NULL指針,那麼free對p連續操作兩次就會導致程序運行錯誤。
fclose也類似。

8.

構造函數不能聲明爲虛函數,析構函數可以聲明爲虛函數,而且有時是必須聲明爲虛函數。不建議在構造函數和析構函數裏面調用虛函數。


9.

val.operator++();		//前置++。
val.operator++(0);		//後置++。


10.

enum中:首元素不賦值的話,默認爲0。後一個元素不賦值的話比前一個元素大1。


11.

所有的虛函數都必須有定義。


12.

數字前加0表示八進制數,加0x表示16進制。

-:左對齊。

:::作用域操作符。當作用域操作符的左側爲空時,向全局作用域發出請求。


13.

C語言:char a='a'; sizeof(char)=1;sizeof(a)=1;sizeof('a')=4;

C++語言:char a='a';sizeof(char)=1; sizeof(a)=1;sizeof('a')=1;
字符型變量是1字節這個沒錯,奇怪就奇怪在C語言認爲'a'是4字節,而C++語言認爲'a'是1字節。
原因如下:
C99標準的規定,'a'叫做整型字符常量(integer character constant),被看成是int型,所以在32位機器上佔4字節。
ISO C++標準規定,'a'叫做字符字面量(character literal),被看成是char型,所以佔1字節。

14.
在C++源文件中的語句前面加上extern "C",表明它按照類C的編譯和連接規約來編譯和連接,而不是C++的編譯的連接規約。主要是解決在C++代碼中調用C代碼。

15.
關於浮點數相等問題:通過比較兩數的差的絕對值是否小於FLT_EPSILON/DBL_EPSILON/LDBL_EPSILON。

16.
說使用引用有節省存儲空間的作用是錯誤的。網上說並未規定要不要爲引用分配空間,但自己通過結構體發現佔用了空間!
int a=3;
class A
{
public:
    A():ra(a){}
private:
    int &ra;
};
//在64位系統中,sizeof(A)爲8!

17.
volatile:易變的,不穩定的。volatile用來聲明那些可能在你的程序本身不知道的情況下會發生改變的變量。

一個定義爲volatile的變量是說這變量可能會被意想不到地改變,這樣,編譯器就不會去假設這個變量的值了。精確地說就是,優化器在用到這個變量時必須每次都小心地重新讀取這個變量的值,而不是使用保存在寄存器裏的備份。
對於一般變量:爲提高存取速度,編譯器優化時有時會先把變量讀取到一個寄存器中。以後再取變量值時,就直接從寄存器中取值。

一個參數既可以是const也可以是volatile:一個例子是隻讀的狀態寄存器。它是volatile因爲它可能被意想不到地改變。它是const因爲程序不應該試圖去修改它。(簡單點就是該程序代碼不能試圖去修改它,但不排除硬件方面修改了它,我們每次都得重新讀取它的值。)

幾個應用例子:
a. 並行設備的硬件寄存器(如:狀態寄存器)。
b. 一箇中斷服務子程序中會訪問到的非自動變量。(個人理解:中斷服務子程序不能用緩存在寄存器中的值來判斷事件,因爲那個值可能被修改了,需要重新讀取。所以一般需要把這種變量聲明爲volatile。)
c. 多線程應用中被幾個任務共享的變量。

18.
構造函數的執行順序:先執行基類的(如果基類當中有虛基類,要先執行虛基類的,都是按照繼承時的順序依次執行),再執行成員對象的(聲明的順序),最後執行自己的。(切記都不是初始化列表的順序)

19.
默認情況下,stack和queue是基於deque實現的,priority_queue是在vector之上實現的。

20.
聲明多維數組只有最靠近數組名的那一維的大小可以省略。

21.
explicit???

22.
不能被重載的運算符:    .    ::    ?:    .*


補充(錯題):

1.

對於一個運算符函數來說,它或者是類的成員(擁有this,即有一個類類型的參數),或者至少含有一個類類型的參數。

<pre class="cpp" name="code">int operator+(int,int);			////錯誤,不能爲int重定義內置的運算符。
MyClass operator+(int,int);		//不行。
MyClass operator+(int,MyClass);		//可以。
MyClass operator+(MyClass,int);		//可以。
MyClass operator+(MyClass,MyClass);	//可以。

2.

當我們把運算符定義成成員函數時,它的左側運算對象必須是運算符所屬類的一個對象。(出處:《C++Primer》中文版第五版493頁第13行。)

如果我們想提供含有類對象的混合類型表達式,則運算符必須定義成非成員函數。唯一的要求是至少有一個運算對象是類類型。

//有如下類模板定義:
template<class T>
class BigNumber
{
	int n;
public:
	BigNumber(T i):n(i){}
	BigNumber operator+(BigNumber b)
	{
		return BigNumber(n+b.n);
	}
}; 
//已知b1,b2是BigNumber的兩個對象,則下列表達式中錯誤的是?
//3+3
//b1+3
//b1+b2
//3+b2(錯誤!)


3.

//賦值運算符優先級比逗號運算符優先級高。注意下面兩個表達式的區別:
a=b,c;
a=(b,c);


4.

//請問下面的程序一共輸出多少個“-”?8個。
int main()
{
	int i;
	for(i = 0;i<2;i++)
	{
		fork();
		printf("-");
	}
	return 0;
}
//一共調用了6次printf,但是會輸出8個-。因爲父進程的輸出緩衝也會被子進程複製。
//因爲標準輸出是行緩衝,程序遇到"\n"、EOF、文件描述符關閉、主動flush或程序退出等,纔會把數據刷出緩衝區。


5.

int fun(char str[14])
{
	return sizeof(str);		//考點:此時str是一個char指針。
}


6.

define a 10
void foo();
int main()
{
	printf("%d ",a);
	foo();
	printf("%d",a);
	return 0;
}
void foo()
{
	#undef a		//在這之前a都被替換成10
	#define a 50		//從這開始a被替換成50
}
//輸出10 10


#define a 10
void foo();
void prin();
int main()
{
	prin();
	printf("%d ",a);
	foo();
	printf("%d\n",a);
	return 0;
}
void foo()
{
	#undef a
	#define a 50
}
void prin()
{
	printf("%d ",a);
}
//輸出50 10 10,可以看出define只是在預處理階段將a替換爲相應數值,具體替換的值只與define在文件中的位置有關,與是否在函數內無關。


7.

關於浮點數相等問題:通過比較兩數相減的絕對值是否小於FLT_EPSILON/DBL_EPSILON/LDBL_EPSILON。


8.

int main()
{
	int a=-3;
	unsigned int b=2;
	long c=a+b;
	printf("%ld\n",c);
}
//c的結果爲-1或4294967295。(跟long是32位還是64位有關)

9.

class A
{
public:
	virtual void func(int val=1)
	{
		std::cout<<"A->"<<val<<std:endl;
	}
	virtual void test()
	{
		func();
	}
};
class B : public A
{
public:
	void func(int val=0)
	{
		std::cout<<"B->"<<val<<std::endl;
	}
};
int main()
{
	A *p1=new B;
	B *p2=new B;
	p1->func();	//B->1
	p1->text();	//B->1
	p2->func();	//B->0
	<strong>p2->text();	//B->1(雖然p2的靜態類型是B*,但是調用text()後,用A*調用func())</strong>
	return 0;
}


重點:缺省參數值是靜態綁定的,絕不重新定義繼承而來的缺省參數值。







發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章