1 new/delete 與 malloc/free的區別
運算符是語言自身的特性,有固定的語義,編譯器知道意味着什么,由編譯器解釋語義,生成相應的代碼。
庫函數是依賴於庫的,一定程度上獨立於語言的。編譯器不關心庫函數的作用,只保證編譯,調用函數參數和返回值符合語法,生成call函數的代碼。
實際中,一些高級點的編譯器,都會對庫函數進行特別處理。
malloc/free是庫函數,new/delete是C++運算符。對於非內部數據類型而言,光用malloc/free無法滿足動態對象都要求。new/delete是運算符,編譯器保證調用構造和析構函數對對象進行初始化/析構。但是庫函數malloc/free是庫函數,不會執行構造/析構。
2 delete與delete[ ] 區別
delete只會調用一次析構函數,而delete[] 會調用沒一個成員的析構函數。
delete 與 new 配套使用; delete[] 與new[]配套使用。
對於內建的簡單數據類型,delete和delete[] 功能相同。
對於復雜數據類型,delete和delete[]不同,前者刪除單個對象,后者刪除數組。
3 子類析構時,要調用父類的析構函數嗎?
析構函數調用的次序時先派生類后基類的。和構造函數的執行順序相反。並且析構函數要是virtual的,否則如果用父類的指針指向子類對象的時候,析構函數靜態綁定,不會調用子類的析構。
不用顯示調用,自動調用。
4 多態, 虛函數, 純虛函數
多態:不同對象接收相同的消息產生不同的動作。多態包括 編譯時多態和 運行時多態
運行時多態是:通過繼承和虛函數來體現的。
編譯時多態:運算符重載上。
虛函數: 在基類中用virtual的成員函數。允許在派生類中對基類的虛函數重新定義。
基類的虛函數可以有函數體,基類也可以實例化。
虛函數要有函數體,否則編譯過不去。
虛函數在子類中可以不覆蓋。
構造函數不能是虛函數。
純虛函數:基類中為其派生類保留一個名字,以便派生類根據需要進行定義。
包含一個純虛函數的類是抽象類。
純虛函數后面有 = 0;
抽象類不可以實例化。但可以定義指針。
如果派生類如果不是先基類的純虛函數,則仍然是抽象類。
抽象類可以包含虛函數。
5 抽象類和接口的區別
在C++里面抽象類就是接口
抽象類:定義了純虛函數的類是抽象類,不能實例化。
抽象類包括抽象方法(純虛方法),也可以包含普通方法。
抽象類可以派生自一個抽象類,可以覆蓋基類的抽象方法也可以不覆蓋。
雖不能定義抽象類的實例,但是可以定義抽象類的指針。
6 什么是“引用”?聲明和使用“引用”要注意哪些問題?
引用的特性:
引用是目標變量的別名,對引用的操作與對變量的操作效果一樣。聲明引用的時候要必須對其初始化。引用聲明完后,相當於目標變量有兩個名稱,不能 再把引用作為其他變量的別名。
引用不是新定義一個變量,它只是表示該引用是目標變量名的一個別名,它本身不是一種數據類型,因此引用不占用存儲單元。
無法建立數組的引用。因為數組是一個由若干元素組成的集合,無法建立數組的別名。
引用的作用:
作為函數的參數,以前用值傳遞,現在用指針或引用。
傳引用和傳指針給函數效果一樣的。
傳遞引用,內存中沒有生成實參副本,是直接對實參操作。如果傳遞的是值類型,需要在棧上生成副本,如果是對象,還要調用構造函數。
指針調用的時候,其實也會形參分配存儲單元,且需要用“指針變量名”的形式運算,容易產生錯誤並且可讀性差;調用的時候,需要用變量的地址作為實 參,調用形式不好看。引用沒有這些 問題。
引用作為返回值最大的好處是:內存中不會產生副本。
但是,引用作為返回值注意事項:
A:不能返回局部變量的引用。
B:不能返回函數內部new的變量。因為引用僅僅是別名,無法釋放內存。
C: 可以返回類成員的引用,但是最好是const
D : 引用和指針一樣,可以產生多態的效果。
總結:
A: 引用的使用主要用於函數傳參,解決大塊數據或對象的問題。
B: 用引用傳遞函數參數,不產生副本,通過const,保證引用傳遞的安全性。
C:比指針的可讀性好,
7 將引用作為函數參數有哪些特點
(1)與指針調用效果一樣。
(2)引用傳參,內存中並沒有產生副本。
(3)用指針傳遞,也要給形參分配存儲單元;並且需要使用"*變量的"的形式,可讀性差;另外,調用的地方還得用地址作為實參。
8 什么時候用常引用
const int &ra = a; // 不能通過引用對目標變量的值進行修改,從而使引用的目標成為const的,安全。
void bar(String &ra)
bar("AA") // 這個會報錯,因為 ”AA“相當於 const char[], 不能傳遞給bar函數。
可以把函數聲明為Void bar(Const String &ra), 上述語句就不會報錯。
9 引用作為函數返回值類型的格式,好處和規則?
int &fun(int a) {}
好處:不會生成副本。
規則: 不能返回局部變量的引用;不能返回函數內部new分配的內存引用; 如果返回成員的話,返回const'
10 結構與聯合的區別
聯合是公用存儲單元的,任何一個時刻只有一個被選中的成員。一旦賦值后,其他成員也覆蓋了。
11 重載(overload)和重寫(override)?
重載:多個同名函數,參數不同(個數不同,參數類型不同);是同一層級的函數;靜態綁定;編譯期綁定。
重寫:子類重新定義父類函數的方法;是動態綁定。
12 有幾種情況用intialization list(初始化列表)而不是assignment(賦值)?
當類中含有const成員變量; reference成員變量; 基類構造函數需要初始化列表。
13 C++是不是類型按群的?
不是; 兩個不同類型的指針之間可以強制轉換。
14 main函數之前會執行什么代碼?
全局變量的初始化。
15 內存分配方式和區別
(1)靜態存儲區:在編譯時就分配好,在整個運行期間都存在。比如全局變量,static變量。
(2)常量區: 存放常量的,比如字符串常量。
(3)堆
(4)棧
16 BOOL,int,float,指針類型,於”零“的比較語句。
BOOL: if(!a) or if(a)
int: if(a == 0)
float: const EXPRESSION exp = 0.000001
if (a < EXP && a > -EXP)
指針: if(a != NULL)
17 const 與 #define相比,優點?
const: 定義常量; 修飾函數參數; 修飾函數返回值; 修飾類成員函數。
好處: const 修飾的有數據類型,而宏沒有,所以可以做類型檢查;而宏僅作字符替換,無安全檢查。
const常量可以調試
宏有副作用。不加括號的話有副作用。
18 數組和指針的區別
數組要么在靜態存儲區創建,要么在棧上創建。指針可以隨時指向任意類型的內存塊。
char a[] = "khell"; // 棧中分配內存,所以可以修改。
a[0] = 'x'; // 可以 沒有問題
char *p = "khell";//常量字符串,存儲在字符常量區,不可以修改
p[0] = 'x'; // 編譯可以,運行時錯誤。
sizeof(a) //是數組的大小;
sizeof(p) // 是指針的大小4.
當數組作為函數參數進行傳遞時,該數組退化成指針
void Func(char a[100])
{
cout<< sizeof(a) << endl; // 4 字節而不是100 字節
}
數組名不能自加自減,但是指針可以。
int a[ ] = {1, 2, 3, 4, 5};
int *ptr = (int *) (&a + 1) // a的地址加1后,其實是加了 4*5 = 20那么多。每次把一個地址加1,都是走數據結構那么大的步長。
19 int(*s[10])(int)
函數指針數組,S[10]里面每個元素都是函數指針,指向函數的類型是 int fun(int a)
void add(int a, int b)
{
cout << a + b << endl;
}
void (*p1)(int a, int b);
p1 = add;
20 為什么基類的析構函數是虛函數?
動態綁定,不會造成潛在的內存泄漏
21 全局變量和局部變量的區別?如何實現的?操作系統和編譯器是怎么知道的?
全局變量分配在全局數據段(靜態存儲區),在程序開始運行時候加載。局部變量則分配在堆棧里面。
22 內存分配方式
堆:有內存碎片的問題。一定的算法去找合適的內存。效率低。OS有記錄空閑內存地址的鏈表
棧:專門的寄存器存放棧地址。效率高。有大小限制。
自由存儲區:用malloc /free分配釋放。 和堆類似。
全局/靜態存儲區:全局變量,靜態變量。
常量存儲區:放常量,不允許修改。
int a=0; 全局/靜態存儲區
char *p1; 全局/靜態存儲區
int main()
{
int b; //棧
char s[]="abc"; //棧
char *p2; //棧
char *p3="123456"; //123456在常量區,p3在棧上。
static int c =0;//全局(靜態)初始化區
p1 = (char *)malloc(10); //分配得來得10和20字節的區域就在堆區
p2 = (char *)malloc(20);
strcpy(p3,"123456"); //123456/0放在常量區,編譯器可能會將它與p3所指向的"123456" 優化成一個地方。
}
23 void *(*(*fp1)(int)[10] / float(*(*fp2)(int, int, int))(int) / int (*(*fp3())[10]()
24 引用與指針區別:
引用必須初始化,指針不用。
引用初始化后不能改變,指針可以改變所指的內容。
不存在指向空值的引用,但是存在指向空值的指針。
指針可以有多級;引用就一級。
指正要解引用,引用不用。
引用沒有const, 但是指針有。
sizeof結果不同。
自增的語義不同。
25 int id[sizeof(unsigned long)] 合法嗎?
可以。數組的大小在編譯的時候就要確認。
26 棧內存與文字常量區域
char str1[] = "abc";
char str2[] = "abc";
const char str3[] = "abc";
const char str4[] = "abc";
const char *str5 = "abc";
const char *str6 = "abc";
char *str7 = "abc";
char *str8 = "abc";
cout << ( str1 == str2 ) << endl;//0 分別指向各自的棧內存
cout << ( str3 == str4 ) << endl;//0 分別指向各自的棧內存
cout << ( str5 == str6 ) << endl;//1指向文字常量區地址相同
cout << ( str7 == str8 ) << endl;//1指向文字常量區地址相同
結果是:0 0 1 1
解答:str1,str2,str3,str4是數組變量,它們有各自的內存空間;而str5,str6,str7,str8是指針,它們指向相同的常量區域。
27 虛函數 VS 純虛函數
虛函數為了重載和多態,在基類中是有定義的,即便定義為空。在子類中可以重寫。
純虛函數在基類中沒有定義,必須在子類中實現。
多態的基礎是繼承,需要虛函數的支持。
28 子類不能繼承父類的函數
子類繼承父類大部分的資源,不能繼承的有構造函數,析構函數,拷貝構造函數,operator=函數,友元函數。
29 開發中常用的數據結構:
A:數組和鏈表:
數組大小不能動態定義。鏈表和動態分配大小的。
數組不適應動態增/減的情況,因為大小固定,一旦定義就不能改變。
鏈表適合動態的增加、刪除數據。
數組的隨機訪問快。
數組棧中分配; 鏈表在堆中。
B:二叉樹遍歷:
先序、中序、后序。
30 const與static的用法
const:
修飾類成員變量,成員不可以改。
修飾函數參數;
修飾返回值;
修飾函數,函數不會修改類內的數據成員。不會調用非const成員函數。(在函數末尾,默認是const this指針,不能修改成員)
const函數只能調用const函數,非const函數可以調用const函數。
static:
局部static變量:局部靜態變量,處於內存中的靜態存儲區;只能初始化一次;作用域是局部。
全局static變量:全局靜態變量,靜態存儲區;全局靜態變量的作用局是聲明它的文件,在文件之外是不可見的。其實是從
定義的地方到文件結尾。
類的static成員:類的全局變量,被類的所有獨享共享,包括派生類的對象。按照這種方式int base::var = 10;進行
初始化,不能在構造函數內初始化,但是可以用const修飾的static數據成員在類內初始化。
static修飾成員函數,類只有一份,不含this指針。
static成員變量定義放在cpp文件中。 const static 可以就地初始化。
31 類的static變量在什么時候初始化,函數的static變量什么時候初始化?
類的靜態成員在類實例化之前就存在了; 函數的static變量在執行此函數時進行實例化(第一次調用的時候,只初始化一次)
32 棧溢出的原因:
棧大小有限制:分過多的數組;
遞歸調用層太深;
33 switch參數類型
可以是:byte short int long bool
不能是: float double(這種浮點型的不能精確的比較,所以不能) string
但是在c++ 11里面, string可以作為switch的條件了。
35 頻繁出現的短小的函數,在c/C++中分別如何實現
c中用宏定義; C++ 內聯
36 C++函數傳參數方式
值傳遞、指針、引用
37 定義宏注意什么?
定義部分的每個形參和整個表達式都必須用括號括起來。
38 .h頭文件中的ifndef/define/endif作用
防止頭文件重復包含
39 struct VS class
struct的成員默認是共有的,而類的成員默認是私有的。
繼承的的時候,class默認是私有繼承;結構體是共有繼承;
class還用於定義模板參數,就像typename
40 系統會自動打開和關閉的三個標准文件是?
在C語言中,在程序開始運行時,系統自動打開3個標准文件:標准輸入、標准輸出、標准出錯輸出。通常這3個文件都與終端相聯系。因此,以前我們所用到的從終端輸入或輸出都不需要打開終端文件。系統自定義了3個文件指針 stdin、stdout、stderr,分別指向終端輸入、終端輸出和標准出錯輸出(也從終端輸出)。
標准輸入流:stdin
標准輸出流:stdout
標准錯誤輸出流:stderr
41 如何判斷浮點數是否是0.5
fabs(x - 0.5)< DBL_DEPSILON
42 內存泄漏? 指針越界和內存泄漏,有哪些方法?
new/delete, new[]/delete, malloc/free 配套使用
對指針賦值的時候,一定要看原來指向的東西是否需要釋放
指針指向的東西釋放后,一定要將指針設置為null。
43 TCP、UDP差別
TCP: 面向連接, 有保障, 效率低, 基於流的,重要數據
udp: 無連接 無保障 效率高 基於數據報文 不重要的數據
44 #include<file.h> #include "file.h"
前者是從標准庫路徑尋找
后者是從當前工作路徑
45 sizeof
sizeof計算的是棧中分配的內存大小
A: 類中static的變量,計算static的時候不算在內
B: 指針大小是4個字節。
C: char = 1; int = 4; short in = 2; long int = 4; float = 4; double=8,
string = 4, 空類=1(對象在內存中都有獨一無二的地址,空類會隱含的加一個字節)), 單一繼承的空類占一個字節;虛繼承涉及的虛指針占4個字節
D:數組: 如果指定數組長度,則總字節數=數組長度 * sizeof(元素類型),如果沒有指定長度,則按照實際元素個數;如果是字符數組,則應考慮末尾空字符。
E: unsigned影響的只是最高位的意義,數據長度不變,sizeof(unsigned int) = 4
F:對函數取sizeof,在編譯階段會被函數返回值的類型代替。
G:sizeof不能返回動態數組的大小。
H:sizeof不能返回外部數組的大小,因為sizeof是編譯時常量(函數調用的時候,數組退化成指針)
38 sizeof VS strlen
sizeof() 返回值類型為size_t(unsigned int)
sizeof是運算符,strlen是函數
sizeof的參數可以是類型,變量或函數。而strlen只能用char*做參數,必須以'\0'結尾
數組指針作為sizeof參數會退化為指針,但是傳遞給strlen的無論是數組還是指針不會退化為指針。
char *p = "chinaaaa";
char q[] = "chinaaaa";
cout << sizeof(p) << endl; 4 // 就是指針
cout << sizeof(q) << endl; 9
cout << strlen(p) << endl; 8
cout << strlen(q) << endl; 8
sizeof是編譯時常量,而strlen運行的時才會計算處理,而且是字符個數,不算最后的結尾字符。
39 const用法
const常量:定義的時候必須初始化。const int a 和 int const a 是一個意思。
const指針: 常量指針 和 指針常量
int a;
const int *p = &a; 常量指針,不能通P改變所值對象的值。但是可以其他方式修改。並且指針
還可以指向其他的int變量。const int *p 和 int const *p一樣。
int * const p = &i; 指針常量,p中存放的地址不可以變化,可以通過P改變變量的值,但是指針不能
再指向其他的變量。
注意const int *p 和 int const *p一樣
const引用: 可以綁定常量,也可以綁定變量。不能通過這個const引用來改變綁定對象的值。但是變量本身可以改。
非const 引用不能與const 對象綁定;但是const 引用可以綁定非const 變量。
40 空指針和懸掛指針
空指針是等於null的指針; 懸掛指針是delete后沒有置空的野指針。
A: 空指針可以被delete多次,而野指針多次delete會很不穩定。
B: 二者都容易導致程序崩潰。
41 C++空類,有哪些成員函數?
默認構造函數, 析構函數, 賦值構造函數, 賦值函數。
class Empty
{ Empty(); // 缺省構造函數,如果用戶定義構造函數,就沒有這個缺省的了。無this指針。
// 兩種辦法初始化:
初始化列表:效率高。常量成員變量/引用類型/無缺省構造函數的類成員,必須用初始化列表,函數體內賦值
Empty(const Empty&) // 拷貝構造函數,直接用對象賦值。必須是引用。注意深拷貝/淺拷貝的問題。
// 3種情況調用拷貝構造函數 : 一個對象初始化另一個對象;
函數形參是類對象,調用函數的時候;
函數返回值是對象
~Empty();
// 析構函數,無參,無返回值,所以不能重載。
Empty& operator=(const Empty&);
}
42 所有的函數都設置為虛函數?
不行。 每個虛函數的對象要維護虛函數表。代價高。
43 共有繼承 受保護繼承 私有繼承
共有繼承:可以訪問基類中的公有成員,派生類可以訪問公有的和受保護的成員;
受保護繼承:
私有繼承:
44 阻止類實例化
抽象基類 或者 構造函數是private
46 main函數執行之前會執行什么?執行之后還能執行代碼嗎?
全局對象的構造函數在main函數之前執行。
用_onexit注冊一個函數,在main執行之后就會調用這個函數.
47 函數參數入棧的順?
從右端往左進入棧的。為了支持可變參數(原理要懂得)。
48 類的static變量的定義和初始化
static int Sum;//在頭文件中聲明靜態數據成員
int Myclass::Sum=0;//定義並初始化靜態數據成員,在類的外部。
49 多態類中虛函數表是compilie-Time 還是 Run-time時建立的
虛函數表是在編譯時就建立了,各個虛擬函數這時被組織成了一個虛函數的入口地址的數組。
而對象的隱藏成員--虛函數表指針是在運行期-也就是構造函數被調用時進行初始化的,這是實現多態的的關鍵。、
50 父類寫了一個virtual函數,如果子類覆蓋它函數不加virtual,可以多態嗎?
可以; 子類可寫,可不寫。
51 子類的空間中,有無父類的virtual函數,或者私有變量?
答:除了static的,其他的都有。
52 sprintf/strcpy/memcpy
sprintf: 其他字符串或基本類型向字符串的轉換功能。是一種格式化。
strcpy: 操作的是字符串,從源字符到目的字符串拷貝功能。
memcpy:內存拷貝。內存塊內容復制。
53 內聯函數在編譯時是否做類型檢查
內聯函數要做類型檢查,這是內聯函數比宏的優勢
54 c/C++的結構體區別:
c++的結構體和class幾乎一樣。結構體可以繼承,可以有函數,可以實現多態。
區別是:結構體成員默認是public的,而類是private的;
另外class可以作為模板中的一個關鍵字 template<Class T>
默認的繼承訪問權限不同,struct是public的繼承;class 是private的繼承。
c 的結構體不具備面向對象的特征,有變量,沒有函數,但是可以有函數指針。
55 如何判斷一段程序是C編譯程序還是C++編譯程序編譯的?
#ifdef __cplusplus
cout << "c++" << endl;
#else
cout << "c";
#endif
56 C ++ 在c基礎上加了什么?
A:包含全部的C語言部分。
B:面向對象部分,封裝,繼承,多態。
C:泛型編程部分,模板,方便使用。
D:STL庫。
57 全局變量和局部變量
分配的區域不同: 全局數據區 vs 棧
聲明周期不同: 主程序 vs 函數內部
可見性不同: 全局 VS 局部
58 有N個大小不等的自然數(1–N),請將它們由小到大排序.要求程序算法:時間復雜度為O(n),空間復雜度為O(1)。
#include <iostream> #include <algorithm> #include <iterator> void sort(int*, int); int main(){ int arr[] = {5,9,3,7,4,2,8,6,1,10}; int n = sizeof(arr)/sizeof(int); sort(arr, n); std::copy(arr,arr+n,std::ostream_iterator<int>(std::cout, ",")); std::cout << std::endl; return 0; } void sort(int *arr, int n) { int count = 0;//此數據不是算法必須,用來計算算法循環次數。 int i; int tmp; for(i=0;i<n;++i){ while( (i + 1) != arr[i]){ tmp = arr[i]; arr[i] = arr[tmp-1]; arr[tmp-1] = tmp; ++count; //算法每循環一次加一。 } } std::cout << "count = " << count << std::endl; //最后得出的循環次數小於等於n。 }
59 宏,內聯函數,函數 區別:
宏效率高,編譯時替換,沒有類型檢查,可能有副作用。
內聯函數有類型檢查,效率高,替換,當然也有可能不替換。一般函數短可以用內聯,長的話編譯器可以優化不內聯。
函數,調用過程入棧、出棧,效率低。
60 棧, 堆區別
申請方式:自動 vs 手動
響應方式: 只要棧沒有超過最大,不statck overflow, 就能分配成功 ; 堆,遍歷空閑內存地址鏈表,找到第一個大於申請空間的節點,從空閑鏈表刪除,並 且在這個塊的首地址處記錄分配的大小,以便delete語句正確執行,並且,堆的大小如果大於申請的大小,多余的部分還會記錄到空閑鏈表。
申請大小限制:棧的大小有限制; 堆的話比較大。
效率:棧快, 自動的; 堆慢,容易產生內存碎片。
存儲的內容:在函數調用時,先入棧的是函數調用的下一條語句的地址,然后從左到右函數入棧,然后是局部變量
靜態局部變量不入棧; 堆的頭部用一個字節大小存堆的大小。堆中的具體內容程序員安排。
61 常引用的作用
傳遞給函數的數據在函數中不被改變
62 引用與多態的關系?
引用和指針可以實現多態。
63 多態作用
代碼模塊化,擴展代碼模塊,實現代碼重用。
64 隱藏
隱藏:派生類的函數屏蔽了同名的基類函數:
派生類函數與基類函數同名,參數不同,無論有無virtual關鍵字,基類函數被隱藏(不是重載)
派生類函數與基類函數同名,參數相同,基類無virtual, 基類被隱藏。
65 a,b兩個變量,不用 if,else, 不用switch,不用三目表達式。找到最大的那個?
找最大的: (a + b + abs(a-b) ) / 2
找最小的: ( a + b - abs(a-b) ) / 2
66 打印文件名行號
cout << __FILE__ << " " << __LINE__ << endl;
67 程序在結束的時候,系統會自動析構所有的全局變量。
事實上,系統也會析構所有的類的靜態成員變量,就像這些靜態成員也是全局變量一樣
68:pragma once , ifdefine
#pragma once是編譯器相關的,有的編譯器支持,有的編譯器不支持,具體情況請查看編譯器API文檔,不過現在大部分編譯器都有這個雜注了。
#ifndef,#define,#endif是C/C++語言中的宏定義,通過宏定義避免文件多次編譯。所以在所有支持C++語言的編譯器上都是有效的,如果寫的程序要跨平台,最好使用這種方式
69: 函數的const參數構成重載函數
如果是值傳遞的函數,值的const 和非const 不構成重載。
如果是引用和指針的可以構成重載。
非const 可以調用const。
const 不可調用非const的。
調用重載函數的時候 會選擇最匹配的那個
70:C++ 全局變量/常量解析
編譯單元:編譯成obj文件,然后link成.exe,編譯主要是看語法錯誤;鏈接主要是重復定義或者沒有定義。
聲明與定義:函數或變量在聲明的時候,沒有給實際的物理內存空間,它有時候可以保證編譯能通過;
當函數或變量定義的時候,它就在內存中有了實際的物理空間。聲明可以多次,但是定義只能一次。
extern 作用:
A: extern "C" void fun(int a, int b),在編譯fun這個函數時按照C的規則區翻譯相應的函數名,而不是c++.
B: extern作用:你現在編譯的文件中,有一個變量或函數雖然在本文件中沒有定義,但是在別的文件中定義的全局變量。
在頭文件中: extern int g_int 作用是聲明函數或全局變量的作用范圍的關鍵字,其聲明的函數或變量
可以在本模塊或其他模塊中使用,記住,這是聲明不是定義!也就是說b模塊引用a 中定義的全局變量或
函數,它包含a的頭文件,就可以在編譯階段通過,b模塊在鏈接的時候從模塊a生成的目標代碼中找到
此函數或變量。
錯誤的做法:在stdafx.h中定義int globalINt = 0; 然后在其他.cpp文件中extern int globalINt ;總是提示重復定義。 原因是:.h文件會被包含多次,相當於
定義多次。
正確的做法: 不在.h文件中定義, 而是在.cpp文件中定義。在.h文件中聲明。這樣就是多次聲明。 鏈接的
時候會找到這個變量的物理地址。
注意: int a ; // 這個也是定義,雖然沒有給賦值。extern int a 才是聲明
extern const int globalINt = 1; //當這個給它賦值了,也可以看做是定義。
只有當extern聲明位於函數外部時,才可以含有初始化式。
問題1: 一個源文件定義了 char a[6]; 另外一個文件用下列語句進行了聲明: extern char *a, 這樣可以嗎?
答案:不可以。因為指向類型T的指針並不等價於類型T數組。提示我們:聲明和定義要嚴格一樣的格式。
問題2: 如果用extern函數的方式引用全局函數,當函數原型修改后,比如加了個參數,編譯居然不報告錯。
解決方案:通常提供函數放在自己的XX.h文件中聲明和這個函數,其他的調用方include該頭文件,從而省去
extern這一步,可以避免上述錯。
71: extern "C "
#ifndef XXXXXXX
#define XXXXXXX //避免重復包含頭文件, #pragma once 可以實現同樣的功能
#ifdef __CPLUSCPLUS
extern "C"{
#endif
#ifdef __cplusplus
}
#endif
# endif
72 C和 c++ 互相調用
因為C++ 重載,而C不重載,函數名編譯的結果都不一樣。
如果C++ 直接調用C的函數,因為二者編譯的不同,就會失敗。
C++ 調用C: 比在一個.h文件中有個 foo(int),其實現是在 .c中,
當C++ 包含這個.h文件的時候就要用extern "C", 否則編譯器編譯的不一樣,根本調用不到。
c++ 調用一個C語言編寫的DLL時,當包括.DLL的頭文件或聲明接口函數時,應加入extern "C"
c調用C++ : 非類成員函數的話,就用extern “C”;
如果要調用成員函數(虛函數,重載函數),可以提供封裝函數,封裝函數內部調用實際的東西。
73 字符數組和字符串
注意最后一個'\0'.
char str[10] = {'a','b','c'} // 不是以 '\0'結尾
char *p = "abc"; // 是以'\0'結尾
74 static 文件作用域的問題
當同時編譯多個文件時,所有未加static的全局變量和函數都是全局可見的(其他文件加上的extern就行)。
用static修飾全局變量,可見性就是本文件,這樣可以在不同的源文件中定義同名的函數和變量,不擔心沖突。
static函數: 主要就是為了隱藏(只在本文件中可以看到)。
static變量: 一是隱藏; 另外是保持變量內容的持久。存儲在靜態區域的變量會在程序剛剛運行時就完成初始化,
也是唯一的一次初始化(初始化只是一次,但是可以改變值)
static 還有一個作用:默認初始化為0,其實全局變量也是這樣的。
75 字節對齊,類對象占據內存
字節對齊好處:為了提高存取效率,讀取int類型的時候,一次讀取就OK。否則要高低字節拼接才行。
字節對齊:有助於加快計算機的取數速度,否則就得多花指令周期了。寬度為2的基本數據類型都位於能被2整除的地址上,
4的位於能被4整除的地址上。
struct S2
{
int i;
char c;
};
規律:i 的地址低, C的地址高,結構體是往高地址擴展的。
A:結構體變量首地址能被其最寬基本類型成員的大小整除。(首地址能整除)
B:結構體每個成員相對於結構體首地址的偏移都是成員大小的整數倍,如有需要,會在成員之間加上填充字節。(偏移量能整除)
C: 結構體總大小為結構體最寬基本類型成員的整數倍,如有需要,會在最后一個成員之后加上填充字節。(結尾填充)
D:如果成員是復合類型,比如另外一個結構體,應該考慮子成員。
但是:還有一個影響sizeof的重要參數還沒有提及:pack指令。
#paragma pack(n) // n是字節對齊數,取值是1,2,4,8,16; 默認是8
如果這個值比結構體成員的sizeof小, 那么該成員的偏移量應該以此為准: 也就是結構體成員的偏移量取二者最小值。
下面代碼演示了:單獨對一個結構體的字節對齊方式進行設置。
#pragma pack(push) // 將當前pack設置壓棧保存
#pragma pack(2)// 必須在結構體定義之前使用
struct S1
{
char c;
int i;
};
struct S3
{
char c1;
S1 s;
char c2
};
pack影響的的是偏移量。
注意:空結構體,空對象的占據空間是1個字節。
對於聯合體: int從首地址開始占據4個自己; char從首地址開始占據2個字節,有重合。
#include <stdio.h>
union
{
int i;
char x[2];
}a;
void main()
{
a.x[0] =10;
a.x[1] =1;
printf("%d",a.i);
}
76 進程之間通信
消息隊列:存放在內核中,是鏈表的形式。
匿名管道:CreatePipe(); 只能本地使用。管道是半雙工的。只能是父子進程之間通信
命名管道:也是半雙工,但是可在無親緣關系的進程之間通信。可用於網絡通信,可以通過名稱引用;支持多客戶端鏈接,雙向通信;
共享內存(內存映射文件):CreateFileMapping .創建內存映射文件,是多個進程對同一塊物理內存的映射。(因為是共享內存的方式,讀寫之間有沖突)
共享內存的效率是最高的,因為共享一塊都能看見的內存,不用多份拷貝,而且訪問共享內存相當於內存中區域一樣,
不需要通過系統調用或者切入內核來完成。但是需要字節提供同步措施。一般用信號量解決讀寫沖突。
socket: 可以跨越機器
77 類型轉換
隱式類型轉換:int 類型 和float類型相加,會轉成float
顯式類型轉換:static_cast / dynamic_cast / const_cast / reinterpret_cast
static_cast: static_cast <type-id> (expression)主要用於非多態類型之間的轉化:
用於類層次結構中,基類和子類之間指針和引用的轉換;
當進行上行轉換,也就是把子類的指針或引用轉換成父類表示,這種轉換是安全的;
當進行下行轉換,也就是把父類的指針或引用轉換成子類表示,這種轉換是不安全的,也需要程序員來保證;
基本數據類型之間的轉換,如把int轉換成char,把int轉換成enum等等,這種轉換的安全性需要程序員來保證;
把void指針轉換成目標類型的指針,是極其不安全的;
dynamic_cast: dynamic_cast <type-id> (expression),因為是動態,主要是考慮多態的問題。
type-id必須是類的指針,類的引用或者是void*, 如果是指針,expression也是指針;如果是引用,expression也是
引用。主要用於類層次之間的上行/下行轉換,以及類之間的交叉轉。在類上行轉換的時候和static_cast一樣;下行
轉換的時候,比static 安全。 多態類型之間轉換,主要使用dynamic_cast, 因為類型提供了運行時信息。
class b: Public A
{};
B *pb = new B;
A *Pa = dynamic_cast<A*>(pb); //安全的、
下面是轉換成Void*,A和B沒有關系,但是都有虛函數
void *pV = dynamic_cast<void *>(pA); // pV points to an object of A
pV = dynamic_cast<void *>(pB); // pV points to an object of B
// 因為向下轉換是不安全的,所以dynimac做檢查。這就是動態比靜態好的原因。
如果expression是type-id的基類,使用dynamic_cast進行轉換時,在運行時就會檢查expression是否真正的指向一個type-id類型的對象,如
果是,則能進行正確的轉換,獲得對應的值;否則返回NULL,如果是引用,則在運行時就會拋出異常
const_cast: const_cast <type-id> (expression)
const_cast用來將類型的const、volatile和__unaligned屬性移除。常量指針被轉換成非常量指針,並且仍然指向原來的對象;
常量引用被轉換成非常量引用,並且仍然引用原來的對象
reinterpret_cast: reinterpret_cast <type-id> (expression):
允許將任何指針類型轉換為其它的指針類型;聽起來很強大,但是也很不靠譜。它主要用於將一種數據類型從一種類型
轉換為另一種類型。它可以將一個指針轉換成一個整數,也可以將一個整數轉換成一個指針,在實際開發中,
先把一個指針轉換成一個整數,在把該整數轉換成原類型的指針,還可以得到原來的指針值;特別是開辟了系統全局
的內存空間,需要在多個應用程序之間使用時,需要彼此共享,傳遞這個內存空間的指針時,
就可以將指針轉換成整數值,得到以后,再將整數值轉換成指針,進行對應的操作。
static_cast和reinterpret_cast的區別主要在於多重繼承,比如:
class A {
public:
int m_a;
};
class B {
public:
int m_b;
};
class C : public A, public B {};
那么對於以下代碼:
C c;
printf("%p, %p, %p", &c, reinterpret_cast<B*>(&c), static_cast <B*>(&c));
前兩個的輸出值是相同的,最后一個則會在原基礎上偏移4個字節,這是因為static_cast計算了父子類指針轉換的偏移量,並將之轉換到正確的地址(c里面有m_a,m_b,轉換為B*指針后指到m_b處),而reinterpret_cast卻不會做這一層轉換。
因此, 你需要謹慎使用 reinterpret_cast.
注意:reinterpret_cast, 操作符修改了操作數類型,但僅僅是重新解釋了給出的對象的比特模型而沒有進行二進制轉換。
78 虛函數表
注意: 如果沒有虛函數,那么就沒有這個虛函數表的指針。虛函數表的指針(占4字節大小)影響sizeof的結果。
v-Table: 虛函數的地址表。在有虛函數的類實例中,這個表被分配在了這個實例的內存中,當用父類型指針操作
一個子類的時候,這張虛函數表像一個地圖一樣,指明了實際調用的函數。
C++ 編譯器保證:虛函數表的指針存在於對象實例中最前面的位置。
class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
};
typedef void(*Fun)(void);
Base base;
Fun pFun = NULL;
cout << "虛函數表地址" << (int*)(&base)<< endl;
cout << "虛函數表第一個函數地址:" << (int*)*(int*)(&base) << endl;
pFun = (Fun)*((int*)*(int*)(&base));
pFun();
虛函數表最后有一個結尾標志。
一般繼承(無虛函數覆蓋):
總結:A: 虛函數表按照其聲明的順序放在表中。
B: 父類的虛函數在子類的虛函數前面。
一般繼承(有虛函數覆蓋):
總結:子類的覆蓋的函數放在原來虛函數的位置。
沒有被覆蓋的函數依舊。
多重繼承(無函數覆蓋):情況比較復雜(多張虛函數表,所以也有多個指向不同函數表的指針)
總結: 每個父類都有自己的虛表;子類的成員函數放到了第一個父類的虛表中。(所謂的第一個父類是按照聲明順序來判斷的。)
多重繼承(有虛函數覆蓋) :
多個父類虛函數表中的被覆蓋的函數都會替換成子類的函數指針。這樣我們就可以任一靜態類型的父類來指向子類。
安全線: 用父類的指針訪問子類對象的非覆蓋函數,會報錯。
虛函數如果是private的,但是可以通過虛函數表來訪問的到的。
79 多重繼承的二義性
多個父類中有相同名稱的變量或者函數。子類中要指明是哪個父類的。
子類中同名的函數會覆蓋父類的。
子類如果和父類函數同名但是參數不同,子類會覆蓋父類,但是用using (using Base::fun;)是可以實現父類和子類重載的
80 菱形繼承
N是基類(包含a成員和函數display),A和B分別繼承N,C繼承A和B。
A 和B 中都有a的存儲空間。可以通過A和B做限定: c.A::a 和 c.B::display();
81 為什么用 exit()函數
是歷史原因,雖然現在大多數平台下,直接在 main() 函數里面 return 可以退出程序。但是在某些平台下,在 main() 函數里面 return 會導致程序永遠不退出(因為代碼已經執行完畢,程序卻還沒有收到要退出的指令)。換句話說,為了兼容性考慮,在特定的平台下,程序最后一行必須使用 exit() 才能正常退出,這是 exit() 存在的重要價值。
exit(1)表示進程非正常退出. 返回 1;
exit(0)表示進程正常退出. 返回 0.
在unix下的多進程中,n是該進程返回給父進程的值
在main函數中exit(0)等價於return 0.
82 廣義表
非線性的數據結構,是線性表的一種推廣。廣義表中放松對表元素的原子限制,容許它們
具有自身的結構。人工智能領域的表處理語言LISP語言中,廣義表是一種基本的數據結構,
廣義表是n(n≥0)個元素a1,a2,…,ai,…,an的有限序列。
其中:
①ai 或者是原子或者是一個廣義表。
②廣義表通常記作:
Ls=( a1,a2,…,ai,…,an)。
③Ls是廣義表的名字,n為它的長度。
④若ai是廣義表,則稱它為Ls的子表。
注意:
①廣義表通常用圓括號括起來,用逗號分隔其中的元素。
②為了區分原子和廣義表,書寫時用大寫字母表示廣義表,用小寫字母表示原子。
③若廣義表Ls非空(n≥1),則al是LS的表頭,其余元素組成的表(a1,a2,…,an)稱為Ls的表尾。
④廣義表是遞歸定義的[1]
① E=()
E是一個空表,其長度為0。
② L=(a,b)
L是長度為2的廣義表,它的兩個元素都是原子,因此它是一個線性表
③ A=(x,L)=(x,(a,b))
A是長度為2的廣義表,第一個元素是原子x,第二個元素是子表L。
一個表的"深度"是指表展開后所含括號的層數。
((a,b,(c,(d,e),f)),g) 的深度是4。
廣義表的存儲結構:
頭尾表示法: 表中的數據可能是列表,也可能是單元素,所以節點的結構有兩種:一種是表節點,表示列表;另外一種
是元素節點,用來表示單元素。
A:表節點:包括一個指向表頭的指針和指向表尾的指針。
B:元素節點:
C:還需要一個標志位,0表示元素;1表示表節點。
孩子兄弟表示法:兩種節點形式,一種是有孩子節點,表示列表;另外一種是無孩子節點,用來表示單元素。
在有孩子節點中包括一個指向第一個孩子的指針,和一個指向兄弟節點的指針
無孩子節點中包括一個指向兄弟的指針和該元素的元素值。
為了能區分這兩類節點,在節點中還要設置一個標志域:標志1表示有孩子節點,標志0,則
表示無孩子節點。
83 廣義表((a,b),c,d)表頭和表尾分別是?
頭(a,b) // 第一個
表尾(c,d) // 除了第一個剩下的加上括號就是表尾。
84 堆和棧區別
A 管理方式: 棧:編譯器管理; 堆:程序釋放,容易泄露。
B 空間大小: 棧:默認是1M, 堆:可以看做沒有限制。
C 是否產生碎片:棧:沒有碎片。 堆:產生碎片。
D 生長方向:棧:向內存地址減小的方向; 堆: 地址增大的方向。
E 分配方式: 棧:有靜態分配 堆:都是動態分配的。
F 分配效率: 棧:寄存器存了棧的地址,壓棧/出棧有專門的指令,棧的效率很高。
堆:分配、管理內存的算法復雜,空閑鏈塊查找,合並,用了后,要更新
空閑鏈塊的記錄。效率低。 如果碎片太多,可能還要像OS申請更多內存。