1. 程式人生 > >C/C++容易混淆的小知識點

C/C++容易混淆的小知識點

1、函式傳指標和傳引用的區別?
1>指標定義時可以不初始化,但引用不行;
2>引用只能和一個實體結合,而指標可和多個實體結合;
3>自加減意義不同。指標的++表示指標向後偏移型別個位元組,而引用則是+1,–類似;
4>sizeof求值的意義不同。Sizeof(指標)是指標所佔的位元組數,32位平臺下為4,64位平臺下為8(陣列名除外),sizeof(引用)是指引用所指實體型別的大小;
5>沒有空引用,但是有空指標;
6>有多級指標,但沒有多級引用;
7>引用比指標用起來更安全,不用判空。
注意:指標也可以進行引用

2、C++中的封裝:


隱藏物件的屬性和實現細節,僅僅對外提供公開介面和物件進行互動,將資料和操作資料的方法進行有機結合。

3、必須在建構函式的成員初始化列表中進行初始化的資料成員:
引用資料成員、const資料成員、類型別成員(該類沒有預設的建構函式)。

4、面向物件與面向過程的區別?
面向過程程式設計方法採用函式(或過程)來描述對資料的操作,但又將函式與其操作的資料分離開來。
面向物件程式設計方法是將資料和物件的操作封裝在一起,作為一個整體來處理。
面向過程程式設計以過程為中心,難於維護。
面向物件程式設計以資料為中心,資料相對功能而言,有較強的穩定性,因此更易於維護。

5、static關鍵字


static可修飾區域性變數、全域性變數和函式。
在修飾全域性的變數和函式時:在檔案作用域中使用可改變連結屬性,僅在當前檔案中可以使用。
修飾區域性變數時:
1.在靜態區開闢;
2.生命週期為整個函式的執行時間;
3.預設初始化為0;
4.變數具有記憶作用,即每次儲存上一次呼叫後的值;
5.在函式第一次呼叫時建立,建構函式只調用一次。
修飾類的成員變數和函式:
靜態成員為所有物件所共享,不屬於某個例項
類靜態成員即可用類名::靜態成員或者物件(.靜態成員函式)來訪問
類靜態成員變數必須在類內宣告類外定義,定義時不新增static關鍵字
類的靜態成員函式沒有預設的this指標,因此在它裡面不能使用任何非靜態成員
靜態成員和類的普通成員一樣,也有public、protected、private3種訪問級別,也可以具有返回值,const修飾符等引數。

6、malloc、free和new、delete的區別:
malloc、free為函式,new、delete為操作符;
malloc的引數為要申請的位元組數,new直接跟型別;
malloc的返回值為void*,並且返回時需要判空,new不需要判空,申請空間失敗時會直接拋異常;
new申請單個型別的空間時可對其進行初始化,同時還可以陣列空間;
malloc、free呼叫時不會呼叫建構函式和解構函式,但new、delete會;
new的底層會呼叫malloc,delete的底層會呼叫free。
1.operator new/operator delete、 operator new[]/operator delete[] 和 malloc/free用法一樣。
2. 他們只負責分配空間/釋放空間,不會呼叫物件建構函式/解構函式來初始化/清理物件。
3. 實際operator new和operator delete只是malloc和free的一層封裝

7、定位new表示式
是在已分配的原始記憶體空間中呼叫建構函式初始化一個物件。
new (place_address) type
new (place_address) type(initializer-list)
place_address必須是一個指標,initializer-list是型別的初始化列表。

8、sizeof 和 strlen的區別?
Sizeof用來求取型別或變數所佔的位元組數,strlen則用來求取字串的長度,不包括’\0’;
Sizeof 求字元個數時會包含‘\0’,strlen 不會;
Sizeof(陣列名)陣列名代表整個陣列的大小,strlen(陣列名)代表陣列首元素的地址;
Sizeof(型別),sizeof(變數),sizeof 變數,strlen()必須加括號;
Sizeof在編譯時求位元組數,strlen在執行時才計算;
Sizeof+(型別或變數),strlen(字串地址)。

9、debug和release的區別?
Debug通常稱為除錯版本,它包含除錯資訊,並且不做任何優化,便於程式設計師進行除錯。
Release稱為釋出版本,它往往進行了各種優化,使得程式在程式碼大小和執行速度上都是最優的,以便使用者很好的使用。

10、區域性性原理
時間區域性性原理:一個值正在被訪問,那麼近期它有可能再次被訪問;
空間區域性性原理:一個值正在被訪問,那麼地址與它臨近的值有可能近期再次被訪問。

11、C語言和C++的對比
1>檔案字尾不同。C語言通常以.c結尾,而C++通常以.cpp結尾
2>預設返回值不同。如果一個函式沒有指定返回值,則C語言預設返回int型別,C++預設返回void型別
3>預設引數列表不同。在沒有指定引數列表時,C語言預設可接收任意多個引數,C++預設為void,不接收任何型別的引數
4>預設引數。C語言的預設引數為int型別,C++為void型別。
最重要的是解決問題的思想和方法不一樣,C語言是面向過程的,C++是面向物件的。

12、巨集和函式的比較
這裡寫圖片描述

11、建構函式的特性
1>建構函式沒有返回值
2>建構函式有初始化列表但可以不用
3>建構函式的名字必須與類名相同
4>在物件被建立時,建構函式被編譯器自動呼叫,且在物件的生命週期內智慧調一次
5>建構函式可以過載,實參決定了呼叫哪個建構函式
7>無參建構函式和帶有預設值的建構函式都認為是預設建構函式,且智慧有一個

12、賦值相容規則
子類物件可以直接賦值給父類物件;
父類物件不能直接賦值給子類物件;
父類的指標或引用可以直接指向子類的物件;
子類的指標或引用不可以直接指向給父類的物件(強轉可以)。

13、多型
概念:所謂多型性就是指達能夠不同的物件受到不同的訊息時產生不同的動作
引入:基類的指標或引用指向子類的物件時,它只能訪問派生類中從基類繼承來的成員,而不能訪問派生類的新增成員,引用虛擬函式可解決此問題
動態繫結條件:基類中的成員函式給成虛擬函式;呼叫虛擬函式時一定要通過基類的指標或引用呼叫;虛擬函式必須在派生類中重寫(同時滿足)。

14、函式過載、同名隱藏、重寫(覆蓋/覆寫)
函式過載:函式名相同、同一作用域、引數列表不同(型別、個數、順序)
同名隱藏:一個處於基類,一個處於派生類、函式名相同
重寫(覆蓋/覆寫):一個處於基類一個處於派生類、都是虛擬函式、函式名相同、引數列表相同、函式的返回值相同(協變除外)
協變:返回值一個是基類的指標或引用,一個是派生類的指標或引用

15、什麼時候解構函式需要設定為虛擬函式
在多型的場景下,當一個基類的指標或引用指向一個派生類物件時,由於是基類的指標所以會呼叫基類的解構函式,此時屬於派生類的那部分空間並沒有釋放,因此造成記憶體洩漏,所以這種場景下必須基類的解構函式必須設定為虛擬函式,此時呼叫解構函式的時候就會呼叫派生類的解構函式。

16、編譯器什麼時候會為我們合成預設的建構函式
1>有一個A類定義有自己的預設建構函式,B類沒有顯示定義自己的建構函式但類中包含A類的物件,此時編譯器會為B類合成預設的建構函式來呼叫A類已有的建構函式。
2>如果B類和D類處於繼承關係(class D:public B)B類包含自己的預設建構函式,D類無建構函式,此時編譯器會為D類合成預設的建構函式來呼叫B類已有的預設建構函式。
3>虛擬繼承中,如果派生類沒有顯示的定義自己的建構函式,則編譯器將會給它合成預設的建構函式,並且在構造物件的時候在物件的前4個位元組放入指向虛基表的指標。
4>多型中,如果基類包含虛擬函式,如果派生類沒有顯示定義建構函式,則編譯器會為其合成預設的建構函式,並且將指向虛表(虛擬函式表)的指標存放在物件的前4個位元組。

17、虛表的建立
基類:按照虛擬函式的宣告順序一次將虛擬函式的地址填入虛表中,最後位置放0
派生類:先拷貝一份基類的虛表
如果派生類對基類的某些函式進行重寫,則派生類會將虛表相同便宜位置的函式修改為派生類自己的虛擬函式地址;
如果派生類新增了不同於基類的虛擬函式,則將其緊放在虛表末尾,最後位置放0

18、STL的6大元件
容器、迭代器、演算法、介面卡、空間配置器、仿函式(函式物件)

19、程式的執行過程
預處理—-編譯—-彙編—-連結—-執行
預處理:刪除註釋、巨集替換、處理條件編譯、包含標頭檔案、新增行號、檔名等
編譯 :詞法分析、語法分析、語義分析、優化之後產生相應的彙編程式碼
彙編:將彙編程式碼程式設計計算機可以執行的二進位制指令
連結:匯出符號表、地址重定向表、位解決的符號表
執行:執行程式碼的具體邏輯

20、異常處理
C語言:
1>終止程式
2>返回一個表示錯誤的值,附加錯誤碼
3>返回一個合法值,讓程式處於某種非法的狀態
4>呼叫一個預先準備好出現錯誤的情況下呼叫的函式
5>暴力解決:abort exit
6>使用goto語句(只能在函式內部跳轉)
7>setjmp和longjmp組合使用
C++:
throw(丟擲異常) try(檢查是否發生異常) catch(處理捕獲的異常)

21、型別轉換
C語言:隱式型別轉換(如整形提升)和顯示型別轉換(強轉)
C++:static_cast:用於非靜態型別,類似於隱式型別轉換
const_cast:去除變數的常屬性,方便賦值
reinterpret_cast:適用於兩個不同型別之間的轉換(不安全)
dynamic_cast:用於將父類物件/指標轉換為子類物件/指標,類中必須包含虛擬函式
Expilicit:防止單引數的建構函式進行飲食型別的轉換
Volatile:防止編譯器進行優化,保證記憶體的可見性

22、呼叫約定
這裡寫圖片描述

23、const關鍵字
(1)可以定義const常量,具有不可變性。
例如:const int Max=100; Max++會產生錯誤;
(2)便於進行型別檢查,使編譯器對處理內容有更多瞭解,消除了一些隱患。
例如: void f(const int i) { ………} 編譯器就會知道i是一個常量,不允許修改;
(3)可以避免意義模糊的數字出現,同樣可以很方便地進行引數的調整和修改。 同巨集定義一樣,可以做到不變則已,一變都變!
如(1)中,如果想修改Max的內容,只需要:const int Max=you want;即可!
(4)可以保護被修飾的東西,防止意外的修改,增強程式的健壯性。 還是上面的例子,如果在函式體內修改了i,編譯器就會報錯;
例如: void f(const int i) { i=10;//error! }
(5) 可以節省空間,避免不必要的記憶體分配。 例如:

#define PI 3.14159 //常量巨集
const double Pi=3.14159; //此時並未將Pi放入RAM中 ......
double i=Pi; //此時為Pi分配記憶體,以後不再分配!
double I=PI; //編譯期間進行巨集替換,分配記憶體
double j=Pi; //沒有記憶體分配
double J=PI; //再進行巨集替換,又一次分配記憶體!

const定義常量從彙編的角度來看,只是給出了對應的記憶體地址,而不是像#define一樣給出的是立即數,所以,const定義的常量在程式執行過程中只有一份拷貝,而#define定義的常量在記憶體中有若干份拷貝。
(6)提高了效率。
編譯器通常不為普通const常量分配儲存空間,而是將它們儲存在符號表中,這使得它成為一個編譯期間的常量,沒有了儲存與讀記憶體的操作,使得它的效率也很高。
C語言和C++ 中const的區別:
C:const定義變數時變數還是變數,只是它具有了常屬性,不可修改。
C++:const定義的變數,編譯器認為它時常量。
驗證:arr[n]在C++中可以,在C語言中不行。

24、struct和class
struct成員的預設訪問許可權是public,class成員的預設訪問許可權是private。
預設的繼承訪問許可權:struct是public的,class是private的。

25、模板為什麼不支援分離編譯
C++編譯器的工作簡介:
在 C++ 標準中提到,一個編譯單元 [ Translation Unit ] 是指一個.cpp檔案以及它所include的所有.h檔案。.h檔案裡的程式碼將會被擴充套件到包含它的.cpp檔案裡,然後編譯器編譯該.cpp 檔案為一個.obj檔案,後者擁有PE [ Portable Executable,即 Windows 可執行檔案 ] 檔案格式,並且本身包含的就已經是二進位制碼。但是,不一定能夠執行,因為並不保證其中一定有main 函式。當編譯器將一個工程裡的所有.cpp檔案以分離的方式編譯完畢後,再由聯結器 [ linker ] 進行連線成為一個.exe檔案。
下面給出一個例子說明原因: [將模板和它的實現分離]

//-------------test.h----------------//
template<class T>
class A
{
public:
void f(); //這裡只是個宣告
};
//---------------test.cpp-------------//
#include”test.h”
template<class T>
void A<T>::f() //模板的實現
{
…//do something
}

//---------------main.cpp---------------//
#include”test.h”
int main()
{
A<int> a;//模板的呼叫過程
a. f();//f具有外部屬性
}

在main.cpp中由於編譯時test.h裡的程式碼被展開,所以找到了f的宣告,並同時呼叫它找它的實現程式碼,本來f函式的實現程式碼在test.cpp的檔案裡實現,但由於在test.cpp裡沒有對f函式進行例項化,所以模板並不會為其生成相關程式碼,這就導致了main函式連結時並沒有找到f函式的實現程式碼,因而報出連結錯誤(無法解析的外部符號)。

26、C語言為什麼不支援過載
因為c++有名命修飾,他會把每一個引數的型別的用一個字串來表示加到函式名上。所以過載的兩個同名函式編譯出來的函式名不同,所以不衝突支援過載。
c沒有命名修飾,函式叫啥編譯出來只是給函式名前加了下劃線而已,同名函式當然會衝突的,因此不能過載。

若有問題,歡迎指出…