1.
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++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()
{}
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.
8.
構造函數不能聲明爲虛函數,析構函數可以聲明爲虛函數,而且有時是必須聲明爲虛函數。不建議在構造函數和析構函數裏面調用虛函數。
9.
val.operator++(); //前置++。
val.operator++(0); //後置++。
10.
enum中:首元素不賦值的話,默認爲0。後一個元素不賦值的話比前一個元素大1。
11.
所有的虛函數都必須有定義。
12.
-:左對齊。
:::作用域操作符。當作用域操作符的左側爲空時,向全局作用域發出請求。
13.
C語言:char a='a'; sizeof(char)=1;sizeof(a)=1;sizeof('a')=4;
int a=3;
class A
{
public:
A():ra(a){}
private:
int &ra;
};
//在64位系統中,sizeof(A)爲8!
17.
補充(錯題):
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;
}
重點:缺省參數值是靜態綁定的,絕不重新定義繼承而來的缺省參數值。