【C++複習】易錯的小問題

1. 數組初始化

數組初始化有很多坑,比如

int a [10]; 

這樣的話,10個元素的值都是沒有初始化的,在32位Windows上是-858993460,16進製表示是0xcccccccc之類的垃圾數據,棧中變量如果沒有初始化,值就是這個。如果要初始化數組,可以用

int a[10] = {}; 

或者

int a[10] = {0};

兩種都是把值初始化爲0。需要注意的是,這種方法只能把值初始化爲0,如果

int a[10] = {123}; 

數組中也只有第0個是123,其餘還是0.

2. 使用指針還是引用作爲參數的一個重要區分:
引用必須被初始化爲指向一個對象,一旦初始化了,它就不能再指向其他對象,而指針可以指向一系列不同的對象也可以什麼都不指向
所以,如果一個參數可能在函數中指向不同的對象,或者這個參數可能不指向任何對象,則必須使用指針參數

3.函數模板調用時不需要顯式指定類型,系統自動匹配參數類型,若沒有合適的,會進行報錯。而類模板使用需要顯式指定類型。

template<typename T> 

T add(T a, T b)

{

retrun a + b ; 

}

int main ()

{

cout << add(1, 2) ; // 可以,不需要add<int>(1,2)

}

4. 注意函數調用時,參數的入棧順序是從右到左。

函數func的定義如下:
1
2
3
4
5
voidfunc(constint& v1, cont int& v2)
{
    std::cout << v1 << ' ';
    std::cout << v2 << ' ';
}
 以下代碼輸出結果爲____。
1
2
3
4
5
6
intmain (intargc, char* argv[])
{
    inti=0;
    func(++i,i++);
    return0;
}
答案是 2, 0

5. 要注意類型的表示範圍。

chara=101;
intsum=200;
a+=27;sum+=a;
printf("%d\n",sum);
答案:72
6. 靜態變量(包含全局變量,類的靜態成員等)在main之前調用構造函數,在main之後調用析構函數。
7. 如何重載一元運算符

因爲++和--有前綴和後綴兩種形式,爲了區分,要求在後綴形式加一個int參數。  const Fraction operator ++(int)   中 int不過是個啞元(dummy),是永遠用不上的,它只是用來判斷++是prefix  還是  postfix  。如果有啞元,則是postfix,否則,就是prefix 。 
(1) 雙目運算符重載爲類的成員函數時,函數只顯式說明一個參數,該形參是運算符的右操作數。
(2) 前置單目運算符重載爲類的成員函數時,不需要顯式說明參數,即函數沒有形參。
(3) 後置單目運算符重載爲類的成員函數時,函數要帶有一個整型形參。
8. 時刻注意指針大小和系統位數有關,64位是8字節,32位是4字節。

9. 賦值語句做表達式時,返回賦值後的值。如int a = (b = 10); 結果 a = 10 ;

10. 申請的內存一定要釋放,打開的文件也要關閉

malloc -- free

fopen -- fclose

以下代碼段有問題的是()

正確答案: A B C   你的答案: A B C (正確)

<pre>void func1(char *e){
  char *p1;
  p1=malloc(100);
  sprintf(p1,error:"%s'.",e);
  local_log(p1);
  }
</pre>
<pre>
int func2(char *filename)
  {
    FILE *fp;
    int key;
    fp=fopen(filename,"r");
    fscanf(fp,"%d",&key);
    return key;
   }
</pre>
<pre>
void func3(char *info){
  char *p,*pp;
  p=malloc(100);
  pp=p;
  free(p);
  sprintf(pp,*info:"%s'.",info);
  free(pp);
  }
</pre>
  答案是A 申請了空間沒有釋放,答案B 打開流沒有關閉,答案C free了二次,但是其實指針都是一塊內存空間

11. C++裏,重寫就是覆蓋,如果沒有virtual就是隱藏。這些術語和C#有點不一樣。

12. C++ 實現接口是通過只有純虛函數的抽象基類。

13. 二維數組初始化有兩種方式:一種順序初始化, int array1[3][2]={4,2,5,6};//順序初始化;另一種按行初始化,如 int array2[3][2]={{4,2},{5},{6}};//按行初始化
本題是順序初始化,初始化的元素個數不超過一行規定的個數,所以只有一行。

14. 全局變量只聲明就會定義,而局部變量只聲明,值會是一個0xcccccccc之類的值(VC++),比如:http://blog.csdn.net/u012861978/article/details/46560651

int a ;

int main()

{

int a2; 
}

//結果是

a 爲 0; a2爲0xcccccccc

15. http://blog.csdn.net/dawn_sf/article/details/68078882

以下敘述中正確的是(    )

正確答案: B D   你的答案: A B (錯誤)

在C++中數據封裝是通過各種類型來實現的
在C++中,數據封裝可以由struct關鍵字提供
數據封裝就是使用結構類型將數據代碼連接在一起
數據封裝以後,仍然可以不通過使用函數就能直接存取數據


A  C++通過類來實現封裝性,把數據和與這些數據有關的操作封裝在一個類中,或者說,類的作用是把數據和算法封裝在用戶聲明的抽象數據類型中。

B 正確,C++中雖然struct的默認封裝類型爲public,但是你也可以設置爲private的形式,都可以.

所有的 C++ 程序都有以下兩個基本要素:
  • 程序語句(代碼):這是程序中執行動作的部分,它們被稱爲函數。
  • 程序數據:數據是程序的信息,會受到程序函數的影響。
封裝是面向對象編程中的把數據和操作數據的函數綁定在一起的一個概念(並不是單純將數據代碼連接起來,是數據和操作數據的函數.),這樣能避免受到外界的干擾和誤用,從而確保了安全。

D 正確  一般情況好像是不允許的. 但是如果你用一些比較騷的方法. 前提要你知道一個類的結構分佈,比如讓你訪問類中虛函數表的內容,你就可以先取到類的首地址,把指針強轉成int*,通過你瞭解到的結構定位到虛函數表的地址,這樣你就能訪問到虛函數表裏面的元素,當然這些的前提都是你對這個類的結構相當瞭解. 一般不推薦使用,因爲做法比較bug. 舉個例子吧,下面是打印一個虛函數表的實例.
代碼:
class Base
{

public:
virtual void func1()
{
cout << "Base::func1" << endl;
}

virtual void func2()
{
cout << "Base::func2" << endl;
}

private:
int a;

};

class Derive :public Base
{

public:
virtual void func1()
{
cout << "Derive::func1" << endl;
}

virtual void func3()
{
cout << "Derive::func3" << endl;
}

virtual void func4()
{
cout << "Derive::func4" << endl;
}

private:
int b;

};


typedef void(*FUNC)(void);
void PrintVTable(int* VTable)
{
cout << " 虛表地址" << VTable << endl;

for (inti = 0; VTable[i] != 0; ++i)
{
printf(" 第%d個虛函數地址 :0X%x,->", i, VTable[i]);
FUNC f = (FUNC)VTable[i];
f();
}

cout << endl;
}
int main()
{
Derive d1;
PrintVTable((int*)(*(int*)(&d1)));
system("pause");
return0;
}

這裏重要的就是這個傳參,上面那個打印虛函數表的函數很容易理解的.
PrintVTable((int*)(*(int*)(&d1)));
首先我們肯定要拿到d1的首地址,把它強轉成int*,讓他讀取到前4個字節的內容(也就是指向虛表的地址),再然後對那個地址解引用,我們已經拿到虛表的首地址的內容(虛表裏面存儲的第一個函數的地址)了,但是此時這個變量的類型解引用後是int,不能夠傳入函數,所以我們再對他進行一個int*的強制類型轉換,這樣我們就傳入參數了,開始函數執行了,我們一切都是在可控的情況下使用強轉,使用強轉你必須要特別清楚的知道內存的分佈結構。最後我們來看看輸出結果:



當我們能打印出它的時候,修改也就變得容易許多.最後解釋一下打印虛函數表的函數.


16. 正數原碼、反碼、補碼形式一致。 負數反碼,爲其原碼的符號位不變,其他位取反; 負數補碼,是其反碼加1。

原碼是值,

補碼是內部存儲格式,

反碼是原碼取反,其中正數的反碼等於原碼

https://www.nowcoder.com/test/question/done?tid=10736459&qid=26145#summary

Pandora的答案

不管是在32還是在64位編譯器處理下,int都是4字節32位,所以整數範圍是-2147483648~2147483647,數值以補碼形式存儲,但要求值還是得自己換成原碼正數的原碼、補碼、反碼相同,原碼即補碼,補碼即原碼。

-i 是對 i 求補碼,(含符號位)按位取反再加1.

~i 是對 i 求反碼

原碼 -> 補碼 是 按位取反加1

補碼 -> 原碼 是 先減一在(除符號位)按位取反

17. 二元操作符+定義爲類成員函數數時因爲有隱藏的this指針,故只需用一個形參;如果定義爲普通非成員函數,則需要使用兩個形參,此時應該選A,c++primer中提到最好將算術運算符定義爲非成員,所以如果沒有類成員的限制的話,最好的答案爲A

18. 引用不能是void的,比如void& n,這是錯的。“不允許使用對void的引用”

19. 對於0xcccccccc和0xcdcdcdcd,在 Debug 模式下,VC 會把未初始化的棧內存上的指針全部填成 0xcccccccc ,當字符串看就是 “燙燙燙燙……”;會把未初始化的堆內存上的指針全部填成 0xcdcdcdcd,當字符串看就是 “屯屯屯屯……”。那麼調試器爲什麼要這麼做呢?VC的DEBUG版會把未初始化的指針自動初始化爲0xcccccccc或0xcdcdcdcd,而不是就讓取隨機值,那是爲了方便我們調試程序,如果野指針的初值不確定,那麼每次調試同一個程序就可能出現不一樣的結果,比如這次程序崩掉,下次卻能正常運行,這樣顯然對我們解bug是非常不利的,所以自動初始化的目的是爲了讓我們一眼就能確定我們使用了未初始化的野指針了。

對於0xfeeefeee,是用來標記堆上已經釋放掉的內存。注意,如果指針指向的內存被釋放了,變量變量本身的地址如未做改動,還是之前指向的內存的地址。如果該指針是一個類的指針,並且類中包含有指針變量,則內存被釋放後(對於C++類,通常是執行delete操作),類中的指針變量就會被賦值爲0xfeeefeee。如果早調試代碼過程中,發現有值爲0xfeeefeee的指針,就說明對應的內存被釋放掉了,我們的代碼已經出問題了。

20. 虛函數可以內聯inline https://www.zhihu.com/question/45894112

21. STL中的容器,只要支持隨機訪問就支持[](重寫了operator[])

22.  虛函數不能聲明爲靜態的、全局的、友元的。

23. 

};
ClassC aObject;
ClassA* pA = &aObject;
ClassB* pB = &aObject;
ClassC* pC = &aObject;

下面那一個語句是不安全的

正確答案: A B C   你的答案: B (錯誤)

delete pA
delete pB
delete pC
因爲三個指針不是動態建立的,不用刪除,只有用new建立的對象纔會用到delete刪除

24. 以下程序輸出是____。

1
2
3
4
5
6
7
8
9
10
#include <iostream> 
using namespace std; 
intmain(void
    constinta = 10
    int* p = (int*)(&a); 
    *p = 20
    cout<<"a = "<<a<<", *p = "<<*p<<endl; 
    return0

正確答案: D   你的答案: E (錯誤)

編譯階段報錯運行階段報錯
a = 10, *p = 10
a = 20, *p = 20
a = 10, *p = 20
a = 20, *p = 10

因爲a 和p都指向相同的內存地址,所以輸出的前兩個結果是相同的,但爲啥相同的內存裏的結果不相同麼?--這就是常量摺疊.

這個"常量摺疊"是 就是在編譯器進行語法分析的時候,將常量表達式計算求值,並用求得的值來替換表達式,放入常量表。可以算作一種編譯優化。

因爲編譯器在優化的過程中,會把碰見的const全部以內容替換掉(跟宏似的: #define pi 3.1415,用到pi時就用3.1415代替),這個出現在預編譯階段;但是在運行階段,它的內存裏存的東西確實改變了!!!
簡單的說就是,當編譯器處理const的時候,編譯器會將其變成一個立即數。 

25. 函數是構成C語言的基本單位

26. 逗號表達式的計算順序是從左往右

27. fopen() 函數中,

       r 打開只讀文件,該文件必須存在。

  r+ 打開可讀寫的文件,該文件必須存在。

  rb+ 讀寫打開一個二進制文件,只允許讀寫數據。

  rt+ 讀寫打開一個文本文件,允許讀和寫。

  w 打開只寫文件,若文件存在則文件長度清爲0,即該文件內容會消失。若文件不存在則建立該文件。

  w+ 打開可讀寫文件,若文件存在則文件長度清爲零,即該文件內容會消失。若文件不存在則建立該文件。

  a 以附加的方式打開只寫文件。若文件不存在,則會建立該文件,如果文件存在,寫入的數據會被加到文件尾,即文件原先的內容會被保留。(EOF符保留)

  a+ 以附加方式打開可讀寫的文件。若文件不存在,則會建立該文件,如果文件存在,寫入的數據會被加到文件尾後,即文件原先的內容會被保留。 (原來的EOF符不保留)

  wb 只寫打開或新建一個二進制文件;只允許寫數據。

  wb+ 讀寫打開或建立一個二進制文件,允許讀和寫。

  wt+ 讀寫打開或着建立一個文本文件;允許讀寫。

  at+ 讀寫打開一個文本文件,允許讀或在文本末追加數據。

  ab+ 讀寫打開一個二進制文件,允許讀或在文件末追加數據。

  上述的形態字符串都可以再加一個b字符,如rb、w+b或ab+等組合,加入b 字符用來告訴函數庫打開的文件爲二進制文件,而非純文字文件。不過在POSIX系統,包含Linux都會忽略該字符。由fopen()所建立的新文件會具有S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH(0666)權限,此文件權限也會參考umask 值。

28. 使用 inline 關鍵字的函數只是用戶希望它成爲內聯函數,但編譯器有權忽略這個請求,比如:若此函數體太大,則不會把它作爲內聯函數展開的。類內的成員函數,默認都是 inline 的。【定義在類聲明之中的成員函數將自動地成爲內聯函數】

https://www.nowcoder.com/test/question/done?tid=10748635&qid=44774#summary

29. C語言中只有按值傳遞。傳遞方式只有兩種1. 按值,2. 按引用,沒有按指針傳遞的說法。傳指針也是按值傳指針變量。

30. switch 語句的執行過程, 先去查有沒有符合的case標籤,如果沒有則跳到default後運行,case和default的相對位置不會影響這個過程。

31. case 語句的條件必須是 常量。

32. free釋放的內存不一定直接還給操作系統,可能要到進程結束才釋放。

33. 可以直到malloc不能直接申請物理內存,它申請的是虛擬內存

34. int (*a[10])(int); //函數指針的數組的聲明方式,指向有一個參數並且返回類型均爲int的函數的數組。https://cdecl.org/ 這個網站可以自動分析聲明

https://www.nowcoder.com/test/question/done?tid=10749995&qid=36725#summary

========== 1. 由變量描述到表達式 ==========
本題
用變量a給出下面的定義:一個有10個指針的數組,該指針指向一個函數,該函數有一個整形參數並返回一個整型數:

1. 定義一個數組
a[10]
2. 該數組元素是指針
*a[10]
3. 該指針指向一個函數,
(* a[10]) ()
4. 有一個整形參數
(* a[10]) (int)
5. 並返回一個整形數
int (* a[10]) (int)

另外一個例子:
定義一個函數指針,指向的函數有兩個int形參並且返回一個函數指針,返回的指針指向一個有一個int形參且返回int的函數?
來自 <http://www.nowcoder.com/questionTerminal/960f8047a9ee4a6f8227768f3bc2734d>

1. 定義一個函數指針
(*p)
2. 指向的函數有兩個int參數
(*p) (int,int)
3. 返回值是一個函數指針
* (*p) (int,int) (* (*p) (int,int))
4. 返回的指針指向一個int參數
(* (*p) (int,int))(int)
5. 並返回 int
int (* (*p) (int,int))(int)
所以:int (* (*p) (int,int))(int)

相似的一個例子
聲明一個指向含有10個元素的數組的指針,其中每個元素是一個函數指針,該函數的返回值是int,參數是int*,正確的是()

來自 <http://www.nowcoder.com/questionTerminal/242d747044bd417e95fe37d69884dff8>
1. 聲明一個指針
(*p)
2. 該指針指向一個數組
(*p)[10]
3. 每個元素是一個函數指針
(*(*p)[10])()
4. 參數是int*
(*(*p)[10])(int*)
5. 返回值是int
int (*(*p)[10])(int*)

========== 2. 由表達式到變量描述 ==========

經典例子
注意: 指向數組的指針和指針數組
int *a[10]: a是一個數組,該數組的元素是指針,每個指針都指向一個int型
int (*a)[10]:a是一個指針,該指針指向一個數組,數組元素是int

From http://cdecl.org/
int (*a)[10]: declare a as pointer to array 10 of int
int *a[10]:declare a as array 10 of pointer to int

例子1
int (*a[10])(int)
1. a是一個數組
2. a這個數組的元素是一個指針
3. 指針是一個函數指針
4. 這個函數參數是int,返回值是int

a是一個包含10個元素的數組,每個元素指向一個參數是int,返回值是int的函數。

From http://cdecl.org/
declare a as array 10 of pointer to function (int) returning int
-----------
例子2
int (*(*p)[10])(int*)
1. p是一個指針
2. p這個指針指向一個數組
3. 數組元素是指針
4. 數組元素的指針指向一個函數
5. 這個函數參數是int*,返回值是int

p是一個指向包含10個元素的數組的指針,每個元素是一個函數指針,指向一個參數是int*,返回值是int的函數。
declare p as pointer to array 10 of pointer to function (pointer to int) returning int

35. 運算符優先級以及計算方法


優先級 操作符 描述 例子 結合性
1 ()
[]
->
.
::
++
--
調節優先級的括號操作符
數組下標訪問操作符
通過指向對象的指針訪問成員的操作符
通過對象本身訪問成員的操作符
作用域操作符
後置自增操作符
後置自減操作符
(a + b) / 4;
array[4] = 2;
ptr->age = 34;
obj.age = 34;
Class::age = 2;
for( i = 0; i < 10; i++ ) ...
for( i = 10; i > 0; i-- ) ...
從左到右
2 !
~
++
--
-
+
*
&
(type)
sizeof
邏輯取反操作符
按位取反(按位取補) 
前置自增操作符
前置自減操作符
一元取負操作符
一元取正操作符
解引用操作符
取地址操作符
類型轉換操作符
返回對象佔用的字節數操作符
if( !done ) ...
flags = ~flags;
for( i = 0; i < 10; ++i ) ...
for( i = 10; i > 0; --i ) ...
int i = -1;
int i = +1;
data = *ptr;
address = &obj;
int i = (int) floatNum;
int size = sizeof(floatNum);
從右到左
3 ->*
.*
在指針上通過指向成員的指針訪問成員的操作符
在對象上通過指向成員的指針訪問成員的操作符
ptr->*var = 24;
obj.*var = 24;
從左到右
4 *
/
%
乘法操作符
除法操作符
取餘數操作符
int i = 2 * 4;
float f = 10 / 3;
int rem = 4 % 3;
從左到右
5 +
-
加法操作符
減法操作符
int i = 2 + 3;
int i = 5 - 1;
從左到右
6 <<
>>
按位左移操作符
按位右移操作符
int flags = 33 << 1;
int flags = 33 >> 1;
從左到右
7 <
<=
>
>=
小於比較操作符
小於或等於比較操作符
大於比較操作符
大於或等於比較操作符
if( i < 42 ) ...
if( i <= 42 ) ...
if( i > 42 ) ...
if( i >= 42 ) ...
從左到右
8 ==
!=
等於比較操作符
不等於比較操作符
if( i == 42 ) ...
if( i != 42 ) ...
從左到右
9 & 按位與操作符 flags = flags & 42; 從左到右
10 ^ 按位異或操作符 flags = flags ^ 42; 從左到右
11 | 按位或操作符 flags = flags | 42; 從左到右
12 && 邏輯與操作符 if( conditionA && conditionB ) ... 從左到右
13 || 邏輯或操作符 if( conditionA || conditionB ) ... 從左到右
14 ? : 三元條件操作符 int i = (a > b) ? a : b; 從右到左
15 =
+=
-=
*=
/=
%=
&=
^=
|=
<<=
>>=
賦值操作符
複合賦值操作符(加法)
複合賦值操作符(減法)
複合賦值操作符(乘法)
複合賦值操作符(除法)
複合賦值操作符(取餘)
複合賦值操作符(按位與)
複合賦值操作符(按位異或)
複合賦值操作符(按位或)
複合賦值操作符(按位左移)
複合賦值操作符(按位右移)
int a = b;
a += 3;
b -= 4;
a *= 5;
a /= 2;
a %= 3;
flags &= new_flags;
flags ^= new_flags;
flags |= new_flags;
flags <<= 2;
flags >>= 2;
從右到左
16 , 逗號操作符 for( i = 0, j = 0; i < 10; i++, j++ ) ... 從左到右

記憶方法:
--摘自《C語言程序設計實用問答》       
    問題:如何記住運算符的15種優先級和結合性?    
    解答:C語言中運算符種類比較繁多,優先級有15種,結合性有兩種。    
    如何記憶兩種結合性和15種優先級?下面講述一種記憶方法。    
    結合性有兩種,一種是自左至右,另一種是自右至左,大部分運算符的結合性是自左至右,只有單目運算符、三目運算符的賦值運算符的結合性自右至左。    
    優先級有15種。記憶方法如下:    
    記住一個最高的:構造類型的元素或成員以及小括號。    
    記住一個最低的:逗號運算符。    
    剩餘的是一、二、三、賦值。    
    意思是單目、雙目、三目和賦值運算符。    
    在諸多運算符中,又分爲:    
    算術、關係、邏輯。    
    兩種位操作運算符中,移位運算符在算術運算符後邊,邏輯位運算符在邏輯運算符的前面。再細分如下:    
    算術運算符分     *,/,%高於+,-。    
    關係運算符中,〉,〉=,<,<=高於==,!=。    
    邏輯運算符中,除了邏輯求反(!)是單目外,邏輯與(&&)高於邏輯或(||)。    
    邏輯位運算符中,除了邏輯按位求反(~)外,按位與(&)高於按位半加(^),高於按位或(|)。    
    這樣就將15種優先級都記住了,再將記憶方法總結如下:    
    去掉一個最高的,去掉一個最低的,剩下的是一、二、三、賦值。雙目運算符中,順序爲算術、關係和邏輯,移位和邏輯位插入其中。

36.

C++中哪些函數不能聲明爲inline?

  1. 包含了遞歸、循環等結構的函數一般不會被內聯。
  2. 虛擬函數一般不會內聯,但是如果編譯器能在編譯時確定具體的調用函數,那麼仍然會就地展開該函數。
  3. 如果通過函數指針調用內聯函數,那麼該函數將不會內聯而是通過call進行調用。
  4. 構造和析構函數一般會生成大量代碼,因此一般也不適合內聯。
  5. 如果內聯函數調用了其他函數也不會被內聯。

如果想要阻止某函數被內聯,可以在函數體前加上 __attribute__((noinline)) 。

37. 儘量不要再構造函數和析構函數中調用虛函數。
所謂虛函數就是多態情況下只執行一個,而從繼承的概念來講,總是要先構造父類對象,然後才能是子類對象,如果構造函數設爲虛函數,那麼當你在構造父類的構造函數時就不得不顯示的調用構造,還有一個原因就是爲了防錯,試想如果你在子類中一不小心重寫了個跟父類構造函數一樣的函數,那麼你的父類的構造函數將被覆蓋,也即不能完成父類的構造.就會出錯.

在構造函數不要調用虛函數。在基類構造的時候,虛函數是非虛,不會走到派生類中,既是採用的靜態綁定。顯然的是:當我們構造一個子類的對象時,先調用基類的構造函數,構造子類中基類部分,子類還沒有構造,還沒有初始化,如果在基類的構造中調用虛函數,如果可以的話就是調用一個還沒有被初始化的對象,那是很危險的,所以C++中是不可以在構造父類對象部分的時候調用子類的虛函數實現。但是不是說你不可以那麼寫程序,你這麼寫,編譯器也不會報錯。只是你如果這麼寫的話編譯器不會給你調用子類的實現,而是還是調用基類的實現。
在析構函數中也不要調用虛函數。在析構的時候會首先調用子類的析構函數,析構掉對象中的子類部分,然後在調用基類的析構函數析構基類部分,如果在基類的析構函數裏面調用虛函數,會導致其調用已經析構了的子類對象裏面的函數,這是非常危險的。
構造函數不能聲明爲虛函數的原因是:
1 構造一個對象的時候,必須知道對象的實際類型,而虛函數行爲是在運行期間確定實際類型的。而在構造一個對象時,由於對象還未構造成功。編譯器無法知道對象 的實際類型,是該類本身,還是該類的一個派生類,或是更深層次的派生類。無法確定。。。
2 虛函數的執行依賴於虛函數表。而虛函數表在構造函數中進行初始化工作,即初始化vptr,讓他指向正確的虛函數表。而在構造對象期間,虛函數表還沒有被初 始化,將無法進行。

38. 

當參數*x=1, *y=1, *z=1時,下列不可能是函數add的返回值的( )?
1
2
3
4
5
6
intadd(int*x, int*y, int*z){
    *x += *x;
    *y += *x;
    *z += *y;
    return*z;
 }

正確答案: D   你的答案: 空 (錯誤)

4
5
6
7
開始不知道啥意思,後經牛客網的大神指點才知道這題要考慮的是,x,y,z三個參數是否指向同一地址(或者說調用該函數時是否實參相同),如:當a=b=c=1時,add(&a,&a,&a),add(&a,&b,&c)。
通過寫程序測試得出結果,不可能得到答案7。
39. 

結構體中每一個成員的起始地址要是 該成員大小的整數倍。

40. 

scanf :當遇到回車,空格和tab鍵會自動在字符串後面添加'\0',但是回車,空格和tab鍵仍會留在輸入的緩衝區中。

gets(): 以回車結束讀取,使用'\0'結尾.回車符'\n'被捨棄沒有遺留在緩衝區。可以用來輸入帶空格的字符串。

41. calloc() 申請連續內存並初始化。

42. 友元函數包括3種:設爲友元的普通的非成員函數;設爲友元的其他類的成員函數;設爲友元類中的所有成員函數。

43. const volatile int i = 0; 表示:
任何對i的直接修改都是錯誤的,但是i可能被意外情況修改掉,不要做無意義的優化。所以,const 和 volatile可以同時出現

44. 靜態變量在沒有初始化的時候存在BSS段,初始化後存在 data section 段,都在靜態存儲區。局部靜態變量在第一次使用時初始化。

BSS段通常是指用來存放程序中未初始化的全局變量和靜態變量的一塊內存區域。特點是可讀寫的,在程序執行之前BSS段會自動清0。
可執行程序包括BSS段、數據段、代碼段(也稱文本段)。

45. 野指針是指沒有初始化的指針,使用野指針(取內容、加減法)都是錯誤的行爲。

46.  C++ 的冒號

表示機構內位域的定義(即該變量佔幾個bit空間)

typedef struct _XXX{

unsigned char a:4;

unsigned char c;

} ; XXX

47. c++中規定,重載運算符必須和用戶定義的自定義類型的對象一起使用。也就是說,不能重載C++默認的運算符,比如double * double

48.  後置加法返回右值,前置加法返回左值

i++:
1
2
3
4
inttemp;
temp = i;
i = i + 1;
returntemp;
++i:
1
2
3
4
inttemp;
temp = i;
i = i + 1;
returni;
49. memcpy 與 memmove的區別

區別:

memcpy和memmove()都是C語言中的庫函數,在頭文件string.h中,作用是拷貝一定長度的內存的內容,原型分別如下:

[cpp] view plain copy
  1. void *memcpy(void *dst, const void *src, size_t count);  
  2. void *memmove(void *dst, const void *src, size_t count);  

他們的作用是一樣的,唯一的區別是,當內存發生局部重疊的時候,memmove保證拷貝的結果是正確的,memcpy不保證拷貝的結果的正確。

一、memcpy函數

Memcpy原型:     

[cpp] view plain copy
  1. void *memcpy(void *dest, const void *src, size_t n);  

描述:
        memcpy()函數從src內存中拷貝n個字節到dest內存區域,但是源和目的的內存區域不能重疊。
返回值:
        memcpy()函數返回指向dest的指針。
二、memmove函數

memmovey原型:

[cpp] view plain copy
  1. void *memmove(void *dest, const void *src, size_t n);  

描述:
       memmove() 函數從src內存中拷貝n個字節到dest內存區域,但是源和目的的內存可以重疊。
返回值:
        memmove函數返回一個指向dest的指針。

從上面的描述中可以看出兩者的唯一區別就是在對待重疊區域的時候,memmove可以正確的完成對應的拷貝,而memcpy不能。

內存覆蓋的情形有以下兩種,

先看memcpy()和memmove()這兩個函數的實現:

[cpp] view plain copy
  1. void* my_memcpy(void* dst, const void* src, size_t n)  
  2. {  
  3.     char *tmp = (char*)dst;  
  4.     char *s_src = (char*)src;  
  5.   
  6.     while(n--) {  
  7.         *tmp++ = *s_src++;  
  8.     }  
  9.     return dst;  
  10. }  

從實現中可以看出memcpy()是從內存左側一個字節一個字節地將src中的內容拷貝到dest的內存中,這種實現方式導致了對於圖中第二種內存重疊情形下,最後兩個字節的拷貝值明顯不是原先的值了,新的值是變成了src的最開始的2個字節了。

而對於第一種內存覆蓋情況,memcpy的這種拷貝方式是可以的。

而memmove就是針對第二種內存覆蓋情形,對memcpy進行了改進,改進代碼如下:

[cpp] view plain copy
  1. void* my_memmove(void* dst, const void* src, size_t n)  
  2. {  
  3.     char* s_dst;  
  4.     char* s_src;  
  5.     s_dst = (char*)dst;  
  6.     s_src = (char*)src;  
  7.     if(s_dst>s_src && (s_src+n>s_dst)) {      //-------------------------第二種內存覆蓋的情形。  
  8.         s_dst = s_dst+n-1;  
  9.         s_src = s_src+n-1;  
  10.         while(n--) {  
  11.             *s_dst-- = *s_src--;  
  12.         }  
  13.     }else {  
  14.         while(n--) {  
  15.             *s_dst++ = *s_src++;  
  16.         }  
  17.     }  
  18.     return dst;  
  19. }  

在第二種內存覆蓋的情形下面,memcpy會出錯,但是memmove是能正常工作的。


發佈了84 篇原創文章 · 獲贊 145 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章