哈嘍堆生,大家好专缠。最近幾天,我把去年秋招總結(jié)的筆試面試的一些內(nèi)容淑仆,又進行了重新規(guī)劃分類涝婉。詳細分成了簡歷書寫,面試技巧蔗怠,面經(jīng)總結(jié)墩弯,筆試面試八股文總結(jié)等四個部分。
其中寞射,八股文又分成了C/C++渔工,數(shù)據(jù)結(jié)構(gòu)與算法分析,Arm體系與架構(gòu)桥温,Linux驅(qū)動開發(fā)引矩,操作系統(tǒng),網(wǎng)絡(luò)編程侵浸,名企筆試真題等七個部分旺韭。本次八股文更新,對于部分不合適的內(nèi)容進行了刪減掏觉,新增了C++相關(guān)內(nèi)容区端。
以上七個部分的內(nèi)容,會同步更新在github澳腹,鏈接https://github.com/ZhongYi-LinuxDriverDev/EmbeddedSoftwareEngineerInterview 织盼。希望大家能給個star支持下杨何,讓我有繼續(xù)更新下去的動力。所有內(nèi)容更新完成后悔政,會將這些內(nèi)容整合成PDF晚吞。話不多說,看下目錄谋国。
預(yù)警:本文內(nèi)容很長槽地,很長,很長芦瘾!沒耐心看完的建議直接跳轉(zhuǎn)github捌蚊,獲取PDF下載方式。
C/C++
關(guān)鍵字
C語言宏中"#“和”##"的用法
- (#)字符串化操作符
作用:將宏定義中的傳入?yún)?shù)名轉(zhuǎn)換成用一對雙引號括起來參數(shù)名字符串近弟。其只能用于有傳入?yún)?shù)的宏定義中缅糟,且必須置于宏定義體中的參數(shù)名前。
如:
#define example( instr ) printf( "the input string is:\t%s\n", #instr )
#define example1( instr ) #instr當使用該宏定義時:
example( abc ); // 在編譯時將會展開成:printf("the input string is:\t%s\n","abc")
string str = example1( abc ); // 將會展成:string str="abc"
- (##)符號連接操作符
作用:將宏定義的多個形參轉(zhuǎn)換成一個實際參數(shù)名祷愉。
如:
#define exampleNum( n ) num##n
使用:
int num9 = 9;
int num = exampleNum( 9 ); // 將會擴展成 int num = num9
注意:
a. 當用##連接形參時窗宦,##前后的空格可有可無。
如:
#define exampleNum( n ) num ## n
// 相當于 #define exampleNum( n ) num##n
b. 連接后的實際參數(shù)名二鳄,必須為實際存在的參數(shù)名或是編譯器已知的宏定義赴涵。
c. 如果##后的參數(shù)本身也是一個宏的話,##會阻止這個宏的展開订讼。
#include <stdio.h>
#include <string.h>
#define STRCPY(a, b) strcpy(a ## _p, #b)
int main()
{
char var1_p[20];
char var2_p[30];
strcpy(var1_p, "aaaa");
strcpy(var2_p, "bbbb");
STRCPY(var1, var2);
STRCPY(var2, var1);
printf("var1 = %s\n", var1_p);
printf("var2 = %s\n", var2_p);
//STRCPY(STRCPY(var1,var2),var2);
//這里是否會展開為: strcpy(strcpy(var1_p,"var2")_p,"var2“)髓窜?答案是否定的:
//展開結(jié)果將是: strcpy(STRCPY(var1,var2)_p,"var2")
//## 阻止了參數(shù)的宏展開!如果宏定義里沒有用到 # 和 ##, 宏將會完全展開
// 把注釋打開的話,會報錯:implicit declaration of function 'STRCPY'
return 0;
}
結(jié)果:
var1 = var2
var2 = var1
關(guān)鍵字volatile有什么含意欺殿?并舉出三個不同的例子寄纵?
并行設(shè)備的硬件寄存器。存儲器映射的硬件寄存器通常加volatile脖苏,因為寄存器隨時可以被外設(shè)硬件修改程拭。當聲明指向設(shè)備寄存器的指針時一定要用volatile,它會告訴編譯器不要對存儲在這個地址的數(shù)據(jù)進行假設(shè)棍潘。
一個中斷服務(wù)程序中修改的供其他程序檢測的變量哺壶。volatile提醒編譯器,它后面所定義的變量隨時都有可能改變蜒谤。因此編譯后的程序每次需要存儲或讀取這個變量的時候,都會直接從變量地址中讀取數(shù)據(jù)至扰。如果沒有volatile關(guān)鍵字鳍徽,則編譯器可能優(yōu)化讀取和存儲,可能暫時使用寄存器中的值敢课,如果這個變量由別的程序更新了的話阶祭,將出現(xiàn)不一致的現(xiàn)象绷杜。
多線程應(yīng)用中被幾個任務(wù)共享的變量。單地說就是防止編譯器對代碼進行優(yōu)化.比如如下程序:
XBYTE[2]=0x55;
XBYTE[2]=0x56;
XBYTE[2]=0x57;
XBYTE[2]=0x58;
對外部硬件而言濒募,上述四條語句分別表示不同的操作鞭盟,會產(chǎn)生四種不同的動作,但是編譯器卻會對上述四條語句進行優(yōu)化瑰剃,認為只有XBYTE[2]=0x58(即忽略前三條語句齿诉,只產(chǎn)生一條機器代碼)。如果鍵入volatile晌姚,編譯器會逐一的進行編譯并產(chǎn)生相應(yīng)的機器代碼(產(chǎn)生四條代碼)粤剧。
關(guān)鍵字static的作用是什么?
在函數(shù)體挥唠,只會被初始化一次抵恋,一個被聲明為靜態(tài)的變量在這一函數(shù)被調(diào)用過程中維持其值不變。
在模塊內(nèi)(但在函數(shù)體外)宝磨,一個被聲明為靜態(tài)的變量可以被模塊內(nèi)所用函數(shù)訪問弧关,但不能被模塊外其它函數(shù)訪問。它是一個本地的全局變量(只能被當前文件使用)唤锉。
在模塊內(nèi)世囊,一個被聲明為靜態(tài)的函數(shù)只可被這一模塊內(nèi)的其它函數(shù)調(diào)用。那就是腌紧,這個函數(shù)被限制在聲明它的模塊的本地范圍內(nèi)使用(只能被當前文件使用)茸习。
在C語言中,為什么 static變量只初始化一次壁肋?
對于所有的對象(不僅僅是靜態(tài)對象)号胚,初始化都只有一次,而由于靜態(tài)變量具有“記憶”功能浸遗,初始化后猫胁,一直都沒有被銷毀,都會保存在內(nèi)存區(qū)域中跛锌,所以不會再次初始化弃秆。存放在靜態(tài)區(qū)的變量的生命周期一般比較長,它與整個程序“同生死髓帽、共存亡”菠赚,所以它只需初始化一次。而auto變量郑藏,即自動變量衡查,由于它存放在棧區(qū),一旦函數(shù)調(diào)用結(jié)束必盖,就會立刻被銷毀拌牲。
extern”C” 的作用是什么俱饿?
extern "C"的主要作用就是為了能夠正確實現(xiàn)C++代碼調(diào)用其他C語言代碼。加上extern "C"后塌忽,會指示編譯器這部分代碼按C語言的進行編譯拍埠,而不是C++的。
const有什么作用土居?
- 定義變量(局部變量或全局變量)為常量枣购,例如:
const int N=100;//定義一個常量N
N=50; //錯誤,常量的值不能被修改
const int n; //錯誤装盯,常量在定義的時候必須初始化
修飾函數(shù)的參數(shù)坷虑,表示在函數(shù)體內(nèi)不能修改這個參數(shù)的值。
-
修飾函數(shù)的返回值埂奈。
a.如果給用 const修飾返回值的類型為指針迄损,那么函數(shù)返回值(即指針)的內(nèi)容是不能被修改的,而且這個返回值只能賦給被 const修飾的指針账磺。例如:
const char GetString() //定義一個函數(shù) char *str= GetString() //錯誤芹敌,因為str沒有被 const修飾 const char *str=GetString() //正確
b.如果用 const修飾普通的返回值,如返回int變量垮抗,由于這個返回值是一個臨時變量氏捞,在函數(shù)調(diào)用結(jié)束后這個臨時變量的生命周期也就結(jié)束了,因此把這些返回值修飾為 const是沒有意義的冒版。
-
節(jié)省空間液茎,避免不必要的內(nèi)存分配。例如:
#define PI 3.14159//該宏用來定義常量 const doulbe Pi=3.14159//此時并未將P放入只讀存儲器中 double i=Pi//此時為Pi分配內(nèi)存辞嗡,以后不再分配 double I=PI//編譯期間進行宏替換捆等,分配內(nèi)存 double j=Pi//沒有內(nèi)存分配再次進行宏替換,又一次分配內(nèi)存
什么情況下使用const關(guān)鍵字续室?
- 修飾一般常量栋烤。一般常量是指簡單類型的常量。這種常量在定義時挺狰,修飾符const可以用在類型說明符前明郭,也可以用在類型說明符后。例如:
int const x=2丰泊;const int x=2
- 修飾常數(shù)組薯定。定義或說明一個常數(shù)組可以采用如下格式:
int const a[8]={1,2,3,4,5,6,7,8}
const int a[8]={1,2,3,4,5,6,7,8}
- 修飾常對象。常對象是指對象常量瞳购,定義格式如下:
class A:
const A a:
A const a:
定義常對象時沉唠,同樣要進行初始化,并且該對象不能再被更新苛败。修飾符 const可以放在類名后面激况,也可以放在類名前面髓考。
- 修飾常指針
const int*p; //指向常量的指針,p值可以變闷堡,p指向的數(shù)值內(nèi)容不可變
int const*p; //常量指針 p指向的內(nèi)存不可以變睬罗,但是p指向的數(shù)值可以變
int*const p苍姜;//同2
const int* const p;//指向常量的常量指針曼月。即p指向的內(nèi)存和數(shù)值都不可變
修飾常引用谊却。被 const修飾的引用變量為常引用,一旦被初始化哑芹,就不能再指向其他對象了炎辨。
修飾函數(shù)的常參數(shù)。 const修飾符也可以修飾函數(shù)的傳遞參數(shù)聪姿,格式如下:
void Fun(const int Var)
??告訴編譯器Var在函數(shù)體中不能被改變碴萧,從而防止了使用者一些無意的或錯誤的修改。
- 修飾函數(shù)的返回值末购。 const修飾符也可以修飾函數(shù)的返回值破喻,表明該返回值不可被改變,格式如下:
const int FunI()盟榴;
const MyClass Fun2()曹质;
- 在另一連接文件中引用 const常量。使用方式有
extern const int 1:
extern const int j=10擎场;
new/delete與malloc/free的區(qū)別是什么羽德?
new、delete是C++中的操作符顶籽,而malloc和free是標準庫函數(shù)玩般。
對于非內(nèi)部數(shù)據(jù)對象來說,只使用malloc是無法完成動態(tài)對象要求的礼饱,一般在創(chuàng)建對象時需要調(diào)用構(gòu)造函數(shù)坏为,對象消亡時,自動的調(diào)用析構(gòu)函數(shù)镊绪。而malloc free是庫函數(shù)而不是運算符匀伏,不在編譯器控制范圍之內(nèi),不能夠自動調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)蝴韭。而NEW在為對象申請分配內(nèi)存空間時够颠,可以自動調(diào)用構(gòu)造函數(shù),同時也可以完成對對象的初始化榄鉴。同理履磨,delete也可以自動調(diào)用析構(gòu)函數(shù)蛉抓。而mallloc只是做一件事,只是為變量分配了內(nèi)存剃诅,同理巷送,free也只是釋放變量的內(nèi)存。
new返回的是指定類型的指針矛辕,并且可以自動計算所申請內(nèi)存的大小笑跛。而 malloc需要我們計算申請內(nèi)存的大小,并且在返回時強行轉(zhuǎn)換為實際類型的指針聊品。
strlen("\0") =飞蹂? sizeof("\0")=?
strlen("\0") =0,sizeof("\0")=2翻屈。
strlen用來計算字符串的長度(在C/C++中陈哑,字符串是以"\0"作為結(jié)束符的),它從內(nèi)存的某個位置(可以是字符串開頭妖胀,中間某個位置芥颈,甚至是某個不確定的內(nèi)存區(qū)域)開始掃描直到碰到第一個字符串結(jié)束符\0為止,然后返回計數(shù)器值sizeof是C語言的關(guān)鍵字赚抡,它以字節(jié)的形式給出了其操作數(shù)的存儲大小爬坑,操作數(shù)可以是一個表達式或括在括號內(nèi)的類型名,操作數(shù)的存儲大小由操作數(shù)的類型決定涂臣。
sizeof和strlen有什么區(qū)別盾计?
strlen與 sizeof的差別表現(xiàn)在以下5個方面。
sizeof是運算符(是不是被弄糊涂了赁遗?事實上署辉, sizeof既是關(guān)鍵字,也是運算符岩四,但不是函數(shù))哭尝,而strlen是函數(shù)。 sizeof后如果是類型剖煌,則必須加括弧材鹦,如果是變量名,則可以不加括弧耕姊。
sizeof運算符的結(jié)果類型是 size_t桶唐,它在頭文件中 typedef為 unsigned int類型。該類型保證能夠容納實現(xiàn)所建立的最大對象的字節(jié)大小
sizeof可以用類型作為參數(shù)茉兰, strlen只能用char*作參數(shù)尤泽,而且必須是以“0結(jié)尾的。 sizeof還可以以函數(shù)作為參數(shù),如int g()坯约,則 sizeof(g())的值等于 sizeof( int的值熊咽,在32位計算機下,該值為4闹丐。
大部分編譯程序的 sizeof都是在編譯的時候計算的网棍,所以可以通過 sizeof(x)來定義數(shù)組維數(shù)。而 strlen則是在運行期計算的妇智,用來計算字符串的實際長度,不是類型占內(nèi)存的大小氏身。例如巍棱, char str[20] = "0123456789”,字符數(shù)組str是編譯期大小已經(jīng)固定的數(shù)組蛋欣,在32位機器下航徙,為 sizeof(char)20=20,而其 strlen大小則是在運行期*確定的陷虎,所以其值為字符串的實際長度10到踏。
當數(shù)組作為參數(shù)傳給函數(shù)時,傳遞的是指針尚猿,而不是數(shù)組窝稿,即傳遞的是數(shù)組的首地址。
不使用 sizeof凿掂,如何求int占用的字節(jié)數(shù)伴榔?
#include <stdio.h>
#define MySizeof(Value) (char *)(&value+1)-(char*)&value
int main()
{
int i ;
double f;
double *q;
printf("%d\r\n",MySizeof(i));
printf("%d\r\n",MySizeof(f));
printf("%d\r\n",MySizeof(a));
printf("%d\r\n",MySizeof(q));
return 0;
}
輸出為:
4 8 32 4
上例中,(char*)& Value
返回 Value的地址的第一個字節(jié)庄萎,(char*)(& Value+1)
返回value的地址的下一個地址的第一個字節(jié)踪少,所以它們之差為它所占的字節(jié)數(shù)。
C語言中 struct與 union的區(qū)別是什么?
struct(結(jié)構(gòu)體)與 union(聯(lián)合體)是C語言中兩種不同的數(shù)據(jù)結(jié)構(gòu)糠涛,兩者都是常見的復(fù)合結(jié)構(gòu)援奢,其區(qū)別主要表現(xiàn)在以下兩個方面。
結(jié)構(gòu)體與聯(lián)合體雖然都是由多個不同的數(shù)據(jù)類型成員組成的忍捡,但不同之處在于聯(lián)合體中所有成員共用一塊地址空間集漾,即聯(lián)合體只存放了一個被選中的成員,而結(jié)構(gòu)體中所有成員占用空間是累加的锉罐,其所有成員都存在帆竹,不同成員會存放在不同的地址。在計算一個結(jié)構(gòu)型變量的總長度時脓规,其內(nèi)存空間大小等于所有成員長度之和(需要考慮字節(jié)對齊)栽连,而在聯(lián)合體中,所有成員不能同時占用內(nèi)存空間,它們不能同時存在秒紧,所以一個聯(lián)合型變量的長度等于其最長的成員的長度绢陌。
對于聯(lián)合體的不同成員賦值,將會對它的其他成員重寫熔恢,原來成員的值就不存在了脐湾,而對結(jié)構(gòu)體的不同成員賦值是互不影響的。
舉個例子叙淌。下列代碼執(zhí)行結(jié)果是多少秤掌?
typedef union {double i; int k[5]; char c;}DATE;
typedef struct data( int cat; DATE cow;double dog;)too;
DATE max;
printf ("%d", sizeof(too)+sizeof(max));
假設(shè)為32位機器,int型占4個字節(jié)鹰霍, double型占8個字節(jié)闻鉴,char型占1個字節(jié),而DATE是一個聯(lián)合型變量茂洒,聯(lián)合型變量共用空間孟岛,uion里面最大的變量類型是int[5],所以占用20個字節(jié)督勺,它的大小是20渠羞,而由于 union中 double占了8個字節(jié),因此 union是要8個字節(jié)對齊智哀,所占內(nèi)存空間為8的倍數(shù)次询。為了實現(xiàn)8個字節(jié)對齊,所占空間為24.而data是一個結(jié)構(gòu)體變量盏触,每個變量分開占用空間渗蟹,依次為 sizeof(int)+ sizeof(DATE)+ sizeof( double)=4+24+8=36按照8字節(jié)對齊,占用空間為40赞辩,所以結(jié)果為40+24=64雌芽。
左值和右值是什么?
左值是指可以出現(xiàn)在等號左邊的變量或表達式辨嗽,它最重要的特點就是可寫(可尋址)世落。也就是說,它的值可以被修改糟需,如果一個變量或表達式的值不能被修改屉佳,那么它就不能作為左值。
右值是指只可以出現(xiàn)在等號右邊的變量或表達式洲押。它最重要的特點是可讀武花。一般的使用場景都是把一個右值賦值給一個左值。
通常杈帐,左值可以作為右值体箕,但是右值不一定是左值专钉。
什么是短路求值?
#include <stdio.h>
int main()
{
int i = 6;
int j = 1;
if(i>0||(j++)>0);
printf("%D\r\n",j);
return 0;
}
輸出結(jié)果為1累铅。
輸出為什么不是2跃须,而是1呢?其實娃兽,這里就涉及一個短路計算的問題菇民。由于i語句是個條件判斷語句,里面是有兩個簡單語句進行或運算組合的復(fù)合語句投储,因為或運算中第练,只要參與或運算的兩個表達式的值都為真,則整個運算結(jié)果為真玛荞,而由于變量i的值為6复旬,已經(jīng)大于0了,而該語句已經(jīng)為true冲泥,則不需要執(zhí)行后續(xù)的j+操作來判斷真假,所以后續(xù)的j++操作不需要執(zhí)行壁涎,j的值仍然為1凡恍。
因為短路計算的問題,對于&&操作怔球,由于在兩個表達式的返回值中嚼酝,如果有一個為假則整個表達式的值都為假,如果前一個語句的返回值為 false竟坛,則無論后一個語句的返回值是真是假闽巩,整個條件判斷都為假,不用執(zhí)行后一個語句担汤,而a>b的返回值為 false涎跨,程序不執(zhí)行表達式n=c>d,所以崭歧,n的值保持為初值2隅很。
++a和a++有什么區(qū)別?兩者是如何實現(xiàn)的率碾?
a++的具體運算過程為
int temp = a;
a=a+1;
return temp;
++a的具體運算過程為
a=a+1;
return a;
后置自增運算符需要把原來變量的值復(fù)制到一個臨時的存儲空間叔营,等運算結(jié)束后才會返回這個臨時變量的值。所以前置自增運算符效率比后置自增要高
內(nèi)存
C語言中內(nèi)存分配的方式有幾種所宰?
- 靜態(tài)存儲區(qū)分配
內(nèi)存分配在程序編譯之前完成绒尊,且在程序的整個運行期間都存在,例如全局變量仔粥、靜態(tài)變量等婴谱。
- 棧上分配
在函數(shù)執(zhí)行時,函數(shù)內(nèi)的局部變量的存儲單元在棧上創(chuàng)建,函數(shù)執(zhí)行結(jié)束時這些存儲單元自動釋放勘究。
- 堆上分配
堆與棧有什么區(qū)別伯顶?
-
申請方式
棧的空間由操作系統(tǒng)自動分配/釋放,堆上的空間手動分配/釋放丽惶。
-
申請大小的限制
棿伲空間有限。在Windows下,棧是向低地址擴展的數(shù)據(jù)結(jié) 構(gòu)景描,是一塊連續(xù)的內(nèi)存的區(qū)域十办。這句話的意思是棧頂?shù)牡刂泛蜅5淖畲笕萘渴窍到y(tǒng)預(yù)先規(guī)定好的,在WINDOWS下超棺,棧的大小是2M(也有的說是1M向族,總之是 一個編譯時就確定的常數(shù)),如果申請的空間超過棧的剩余空間時棠绘,將提示overflow件相。因此,能從棧獲得的空間較小堆是很大的自由存儲區(qū)氧苍。堆是向高地址擴展的數(shù)據(jù)結(jié)構(gòu)夜矗,是不連續(xù)的內(nèi)存區(qū)域。這是由于系統(tǒng)是用鏈表來存儲的空閑內(nèi)存地址的让虐,自然是不連續(xù)的紊撕,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計算機系統(tǒng)中有效的虛擬內(nèi)存赡突。由此可見对扶,堆獲得的空間比較靈活,也比較大惭缰。
-
申請效率
棧由系統(tǒng)自動分配浪南,速度較快。但程序員是無法控制的漱受。堆是由new分配的內(nèi)存逞泄,一般速度比較慢,而且容易產(chǎn)生內(nèi)存碎片,不過用起來最方便.
棧在C語言中有什么作用拜效?
C語言中棧用來存儲臨時變量喷众,臨時變量包括函數(shù)參數(shù)和函數(shù)內(nèi)部定義的臨時變量。函數(shù)調(diào)用中和函數(shù)調(diào)用相關(guān)的函數(shù)返回地址紧憾,函數(shù)中的臨時變量到千,寄存器等均保存在棧中,函數(shù)調(diào)動返回后從棧中恢復(fù)寄存器和臨時變量等函數(shù)運行場景赴穗。
多線程編程的基礎(chǔ)是棧憔四,棧是多線程編程的基石膀息,每一個線程都最少有一個自己專屬的棧,用來存儲本線程運行時各個函數(shù)的臨時變量和維系函數(shù)調(diào)用和函數(shù)返回時的函數(shù)調(diào)用關(guān)系和函數(shù)運行場景了赵。 操作系統(tǒng)最基本的功能是支持多線程編程潜支,支持中斷和異常處理,每個線程都有專屬的棧柿汛,中斷和異常處理也具有專屬的棧冗酿,棧是操作系統(tǒng)多線程管理的基石。
C語言函數(shù)參數(shù)壓棧順序是怎樣的络断?
從右至左裁替。
C語言參數(shù)入棧順序的好處就是可以動態(tài)變化參數(shù)個數(shù)。自左向右的入棧方式貌笨,最前面的參數(shù)被壓在棧底弱判。除非知道參數(shù)個數(shù),否則是無法通過棧指針的相對位移求得最左邊的參數(shù)锥惋。這樣就變成了左邊參數(shù)的個數(shù)不確定昌腰,正好和動態(tài)參數(shù)個數(shù)的方向相反。因此膀跌,C語言函數(shù)參數(shù)采用自右向左的入棧順序剥哑,主要原因是為了支持可變長參數(shù)形式。
C++如何處理返回值淹父?
C++函數(shù)返回可以按值返回和按常量引用返回,偶爾也可以按引址返回怎虫。多數(shù)情況下不要使用引址返回暑认。
C++中拷貝賦值函數(shù)的形參能否進行值傳遞?
不能大审。如果是這種情況下蘸际,調(diào)用拷貝構(gòu)造函數(shù)的時候,首先要將實參傳遞給形參徒扶,這個傳遞的時候又要調(diào)用拷貝構(gòu)造函數(shù)(aa = ex.aa; //此處調(diào)用拷貝構(gòu)造函數(shù))粮彤。如此循環(huán),無法完成拷貝姜骡,棧也會滿导坟。
class Example
{
public:
Example(int a):aa(a) {} //構(gòu)造函數(shù)
Example(Example &ex) //拷貝構(gòu)造函數(shù)(引用傳遞參數(shù))
{
aa = ex.aa; //此處調(diào)用拷貝構(gòu)造函數(shù)
}
private:
int aa;
};
int main()
{
Example e1(10);
Example e2 = e1;
return 0;
}
C++的內(nèi)存管理是怎樣的?
在C++中圈澈,虛擬內(nèi)存分為代碼段惫周、數(shù)據(jù)段、BSS段康栈、堆區(qū)递递、文件映射區(qū)以及棧區(qū)六部分喷橙。
代碼段:包括只讀存儲區(qū)和文本區(qū),其中只讀存儲區(qū)存儲字符串常量登舞,文本區(qū)存儲程序的機器代碼贰逾。
數(shù)據(jù)段:存儲程序中已初始化的全局變量和靜態(tài)變量
BSS 段:存儲未初始化的全局變量和靜態(tài)變量(局部+全局),以及所有被初始化為0的全局變量和靜態(tài)變量菠秒。
堆區(qū):調(diào)用new/malloc函數(shù)時在堆區(qū)動態(tài)分配內(nèi)存疙剑,同時需要調(diào)用delete/free來手動釋放申請的內(nèi)存。
映射區(qū):存儲動態(tài)鏈接庫以及調(diào)用mmap函數(shù)進行的文件映射
棧:使用椈海空間存儲函數(shù)的返回地址核芽、參數(shù)、局部變量酵熙、返回值
什么是內(nèi)存泄漏轧简?
簡單地說就是申請了一塊內(nèi)存空間,使用完畢后沒有釋放掉匾二。
它的一般表現(xiàn)方式是程序運行時間越長哮独,占用內(nèi)存越多,最終用盡全部內(nèi)存察藐,整個系統(tǒng)崩潰皮璧。由程序申請的一塊內(nèi)存,且沒有任何一個指針指向它分飞,那么這塊內(nèi)存就泄露了悴务。
如何判斷內(nèi)存泄漏?
良好的編碼習(xí)慣譬猫,盡量在涉及內(nèi)存的程序段讯檐,檢測出內(nèi)存泄露。當程式穩(wěn)定之后染服,在來檢測內(nèi)存泄露時别洪,無疑增加了排除的困難和復(fù)雜度。使用了內(nèi)存分配的函數(shù)柳刮,一旦使用完畢,要記得要使用其相應(yīng)的函數(shù)釋放掉挖垛。
將分配的內(nèi)存的指針以鏈表的形式自行管理,使用完畢之后從鏈表中刪除秉颗,程序結(jié)束時可檢查改鏈表痢毒。
Boost 中的smart pointer。
一些常見的工具插件蚕甥,如ccmalloc闸准、Dmalloc、Leaky等等梢灭。
指針
數(shù)組指針和指針數(shù)組有什么區(qū)別夷家?
數(shù)組指針就是指向數(shù)組的指針蒸其,它表示的是一個指針,這個指針指向的是一個數(shù)組库快,它的重點是指針摸袁。例如,int(*pa)[8]
聲明了一個指針义屏,該指針指向了一個有8個int型元素的數(shù)組靠汁。下面給出一個數(shù)組指針的示例。
#include <stdio. h>
#include <stdlib. h>
void main()
{
int b[12]={1,2,3,4,5,6,7,8,9,10,11,12};
int (*p)[4];
p = b;
printf("%d\n"闽铐, **(++p);
}
程序的輸出結(jié)果為 5蝶怔。
上例中,p是一個數(shù)組指針兄墅,它指向一個包含有4個int類型數(shù)組的指針踢星,剛開始p被初始化為指向數(shù)組b的首地址,++p相當于把p所指向的地址向后移動4個int所占用的空間隙咸,此時p指向數(shù)組{5,6,7,8}沐悦,語句*(++p);
表示的是這個數(shù)組中第一個元素的地址(可以理解p為指向二維數(shù)組的指針五督,{1,2,3,4}藏否,{5,6,7,8},{9,10,11,12}充包。p指向的就是{1,2,3,4}的地址副签,*p
就是指向元素,{1,2,3,4}基矮,**p
指向的就是1)淆储,語句**(++p)會輸出這個數(shù)組的第一個元素5。
指針數(shù)組表示的是一個數(shù)組愈捅,而數(shù)組中的元素是指針。下面給出另外一個指針數(shù)組的示例
#include <stdio.h>
int main()
{
int i;
int *p[4];
int a[4]={1,2,3,4};
p[0] = &a[0];
p[1] = &a[1];
p[2] = &a[2];
p[3] = &a[3];
for(i=0;i<4;i++)
printf("%d",*p[i]);
printf("\n");
return 0;
}
程序的輸出結(jié)果為1234慈鸠。
函數(shù)指針和指針函數(shù)有什么區(qū)別蓝谨?
- 函數(shù)指針
如果在程序中定義了一個函數(shù),那么在編譯時系統(tǒng)就會為這個函數(shù)代碼分配一段存儲空間青团,這段存儲空間的首地址稱為這個函數(shù)的地址譬巫。而且函數(shù)名表示的就是這個地址。既然是地址我們就可以定義一個指針變量來存放督笆,這個指針變量就叫作函數(shù)指針變量芦昔,簡稱函數(shù)指針。
int(*p)(int, int);
這個語句就定義了一個指向函數(shù)的指針變量 p娃肿。首先它是一個指針變量咕缎,所以要有一個“*”珠十,即(*p)
;其次前面的 int 表示這個指針變量可以指向返回值類型為 int 型的函數(shù)凭豪;后面括號中的兩個 int 表示這個指針變量可以指向有兩個參數(shù)且都是 int 型的函數(shù)焙蹭。所以合起來這個語句的意思就是:定義了一個指針變量 p,該指針變量可以指向返回值類型為 int 型嫂伞,且有兩個整型參數(shù)的函數(shù)孔厉。p 的類型為 int(*)(int,int)
帖努。
我們看到撰豺,函數(shù)指針的定義就是將“函數(shù)聲明”中的“函數(shù)名”改成“(指針變量名)”。但是這里需要注意的是:“(指針變量名)”兩端的括號不能省略拼余,括號改變了運算符的優(yōu)先級污桦。如果省略了括號,就不是定義函數(shù)指針而是一個函數(shù)聲明了姿搜,即聲明了一個返回值類型為指針型的函數(shù)寡润。
最后需要注意的是,指向函數(shù)的指針變量沒有 ++ 和 -- 運算舅柜。
# include <stdio.h>
int Max(int, int); //函數(shù)聲明
int main(void)
{
int(*p)(int, int); //定義一個函數(shù)指針
int a, b, c;
p = Max; //把函數(shù)Max賦給指針變量p, 使p指向Max函數(shù)
printf("please enter a and b:");
scanf("%d%d", &a, &b);
c = (*p)(a, b); //通過函數(shù)指針調(diào)用Max函數(shù)
printf("a = %d\nb = %d\nmax = %d\n", a, b, c);
return 0;
}
int Max(int x, int y) //定義Max函數(shù)
{
int z;
if (x > y)
{
z = x;
}
else
{
z = y;
}
return z;
}
- 指針函數(shù)
首先它是一個函數(shù)梭纹,只不過這個函數(shù)的返回值是一個地址值。函數(shù)返回值必須用同類型的指針變量來接受致份,也就是說变抽,指針函數(shù)一定有“函數(shù)返回值”,而且氮块,在主調(diào)函數(shù)中绍载,函數(shù)返回值必須賦給同類型的指針變量。
類型名 *函數(shù)名(函數(shù)參數(shù)列表);
其中滔蝉,后綴運算符括號“()”表示這是一個函數(shù)击儡,其前綴運算符星號“*”表示此函數(shù)為指針型函數(shù),其函數(shù)值為指針蝠引,即它帶回來的值的類型為指針阳谍,當調(diào)用這個函數(shù)后,將得到一個“指向返回值為…的指針(地址)螃概,“類型名”表示函數(shù)返回的指針指向的類型”矫夯。
“(函數(shù)參數(shù)列表)”中的括號為函數(shù)調(diào)用運算符,在調(diào)用語句中吊洼,即使函數(shù)不帶參數(shù)训貌,其參數(shù)表的一對括號也不能省略。其示例如下:
int *pfun(int, int);
由于“*”的優(yōu)先級低于“()”的優(yōu)先級冒窍,因而pfun首先和后面的“()”結(jié)合递沪,也就意味著豺鼻,pfun是一個函數(shù)。即:
int *(pfun(int, int));
接著再和前面的“*”結(jié)合区拳,說明這個函數(shù)的返回值是一個指針拘领。由于前面還有一個int,也就是說樱调,pfun是一個返回值為整型指針的函數(shù)约素。
#include <stdio.h>
float *find(float(*pionter)[4],int n);//函數(shù)聲明
int main(void)
{
static float score[][4]={{60,70,80,90},{56,89,34,45},{34,23,56,45}};
float *p;
int i,m;
printf("Enter the number to be found:");
scanf("%d",&m);
printf("the score of NO.%d are:\n",m);
p=find(score,m-1);
for(i=0;i<4;i++)
printf("%5.2f\t",*(p+i));
return 0;
}
float *find(float(*pionter)[4],int n)/*定義指針函數(shù)*/
{
float *pt;
pt=*(pionter+n);
return(pt);
}
共有三個學(xué)生的成績,函數(shù)find()被定義為指針函數(shù)笆凌,其形參pointer是指針指向包含4個元素的一維數(shù)組的指針變量渐苏。pointer+n指向score的第n+1行饱普。*(pointer+1)指向第一行的第0個元素。pt是一個指針變量,它指向浮點型變量算途。main()函數(shù)中調(diào)用find()函數(shù)芍耘,將score數(shù)組的首地址傳給pointer沃琅。
數(shù)組名和指針的區(qū)別與聯(lián)系是什么刀诬?
- 數(shù)據(jù)保存方面
指針保存的是地址(保存目標數(shù)據(jù)地址,自身地址由編譯器分配)屋灌,內(nèi)存訪問偏移量為4個字節(jié)洁段,無論其中保存的是何種數(shù)據(jù)均已地址類型進行解析。
數(shù)組保存的數(shù)據(jù)共郭。數(shù)組名表示的是第一個元素的地址祠丝,內(nèi)存偏移量是保存數(shù)據(jù)類型的內(nèi)存偏移量;只有對數(shù)組名取地址(&數(shù)組名)時數(shù)組名才表示整個數(shù)組除嘹,內(nèi)存偏移量是整個數(shù)組的大行窗搿(sizeof(數(shù)組名))。
- 數(shù)據(jù)訪問方面
指針對數(shù)據(jù)的訪問方式是間接訪問尉咕,需要用到解引用符號(*數(shù)組名)叠蝇。
數(shù)組對數(shù)據(jù)的訪問則是直接訪問,可通過下標訪問或數(shù)組名+元素偏移量的方式
- 使用環(huán)境
指針多用于動態(tài)數(shù)據(jù)結(jié)構(gòu)(如鏈表年缎,等等)和動態(tài)內(nèi)存開辟悔捶。
數(shù)組多用于存儲固定個數(shù)且類型統(tǒng)一的數(shù)據(jù)結(jié)構(gòu)(如線性表等等)和隱式分配。
指針進行強制類型轉(zhuǎn)換后與地址進行加法運算晦款,結(jié)果是什么炎功?
假設(shè)在32位機器上枚冗,在對齊為4的情況下,sizeof(long)的結(jié)果為4字節(jié)缓溅,sizeof(char*)的結(jié)果為4字節(jié),sizeof(short int)的結(jié)果與 sizeof(short)的結(jié)果都為2字節(jié)赁温, sizeof(char)的結(jié)果為1字節(jié)坛怪, sizeof(int)的結(jié)果為4字節(jié)淤齐,由于32位機器上是4字節(jié)對齊,以如下結(jié)構(gòu)體為例:
struct BBB
{
long num;
char *name;
short int data;
char ha;
short ba[5];
}*p;
當p=0x100000;
則p+0×200=? (ulong)p+0x200=? (char*)p+0x200=?
其實,在32位機器下,sizeof(struct BBB)=sizeof(*p)=4+4+2+2+1+3/*補齊*/+2*5+2/*補齊*/=24字節(jié)
袜匿,而p=0x100000
,那么p+0x200=0x1000000+0x200*24
指針加法更啄,加出來的是指針所指類型的字節(jié)長度的整倍數(shù),就是p偏移sizeof(p)*0x200居灯。
(ulong)p+0x200=0x10000010+0x200
經(jīng)過ulong后,已經(jīng)不再是指針加法祭务,而變成一個數(shù)值加法了。
(char*)p+0x200=0x1000000+0×200*sizeof(char)
結(jié)果類型是char*怪嫌。
常量指針义锥,指向常量的指針,指向常量的常量指針有什么區(qū)別岩灭?
- 常量指針
int * const p
先看const再看 * 拌倍,是p是一個常量類型的指針,不能修改這個指針的指向噪径,但是這個指針所指向的地址上存儲的值可以修改柱恤。
- 指向常量的指針
const int *p
先看再看const,定義一個指針指向一個常量找爱,不能通過指針來修改這個指針指向的值*
- 指向常量的常量指針
const int *const p
對于“指向常量的常量指針”梗顺,就必須同時滿足上述1和2中的內(nèi)容,既不可以修改指針的值缴允,也不可以修改指針指向的值荚守。
指針和引用的異同是什么?如何相互轉(zhuǎn)換练般?
相同
都是地址的概念矗漾,指針指向某一內(nèi)存、它的內(nèi)容是所指內(nèi)存的地址薄料;引用則是某塊內(nèi)存的別名敞贡。
從內(nèi)存分配上看:兩者都占內(nèi)存,程序為指針會分配內(nèi)存摄职,一般是4個字節(jié)誊役;而引用的本質(zhì)是指針常量,指向?qū)ο蟛荒茏児仁校赶驅(qū)ο蟮闹悼梢宰兓坠浮烧叨际堑刂犯拍睿员旧矶紩加脙?nèi)存迫悠。
區(qū)別
指針是實體鹏漆,而引用是別名。
指針和引用的自增(++)運算符意義不同,指針是對內(nèi)存地址自增艺玲,而引用是對值的自增括蝠。
引用使用時無需解引用(*),指針需要解引用饭聚;(關(guān)于解引用大家可以看看這篇博客忌警,傳送門)。
引用只能在定義時被初始化一次秒梳,之后不可變法绵;指針可變。
引用不能為空酪碘,指針可以為空礼烈。
“sizeof 引用”得到的是所指向的變量(對象)的大小,而“sizeof 指針”得到的是指針本身的大小婆跑,在32位系統(tǒng)指針變量一般占用4字節(jié)內(nèi)存此熬。
#include "stdio.h"
int main(){
int x = 5;
int *p = &x;
int &q = x;
printf("%d %d\n",*p,sizeof(p));
printf("%d %d\n",q,sizeof(q));
}
//結(jié)果
5 8
5 4
由結(jié)果可知,引用使用時無需解引用(*)滑进,指針需要解引用犀忱;我用的是64位操作系統(tǒng),“sizeof 指針”得到的是指針本身的大小扶关,及8個字節(jié)阴汇。而“sizeof 引用”得到的是的對象本身的大小及int的大小,4個字節(jié)节槐。
轉(zhuǎn)換
指針轉(zhuǎn)引用:把指針用*就可以轉(zhuǎn)換成對象搀庶,可以用在引用參數(shù)當中。
引用轉(zhuǎn)指針:把引用類型的對象用&取地址就獲得指針了铜异。
int a = 5;
int *p = &a;
void fun(int &x){}//此時調(diào)用fun可使用 : fun(*p);
//p是指針哥倔,加個*號后可以轉(zhuǎn)換成該指針指向的對象,此時fun的形參是一個引用值,
//p指針指向的對象會轉(zhuǎn)換成引用X揍庄。
野指針是什么咆蒿?
野指針是指向不可用內(nèi)存的指針,當指針被創(chuàng)建時蚂子,指針不可能自動指向NULL沃测,這時,默認值是隨機的食茎,此時的指針成為野指針蒂破。
當指針被free或delete釋放掉時,如果沒有把指針設(shè)置為NULL别渔,則會產(chǎn)生野指針附迷,因為釋放掉的僅僅是指針指向的內(nèi)存田巴,并沒有把指針本身釋放掉。
第三個造成野指針的原因是指針操作超越了變量的作用范圍挟秤。
如何避免野指針?
- 對指針進行初始化抄伍。
//將指針初始化為NULL艘刚。
char * p = NULL;
//用malloc分配內(nèi)存
char * p = (char * )malloc(sizeof(char));
//用已有合法的可訪問的內(nèi)存地址對指針初始化
char num[ 30] = {0};
char *p = num;
- 指針用完后釋放內(nèi)存,將指針賦NULL截珍。
delete(p);
p = NULL;
注:malloc函數(shù)分配完內(nèi)存后需注意:
a. 檢查是否分配成功(若分配成功攀甚,返回內(nèi)存的首地址;分配不成功岗喉,返回NULL秋度。可以通過if語句來判斷)
b. 清空內(nèi)存中的數(shù)據(jù)(malloc分配的空間里可能存在垃圾值钱床,用memset或bzero 函數(shù)清空內(nèi)存)
//s是 需要置零的空間的起始地址荚斯; n是 要置零的數(shù)據(jù)字節(jié)個數(shù)。
void bzero(void *s, int n);
// 如果要清空空間的首地址為p查牌,value為值事期,size為字節(jié)數(shù)。
void memset(void *start, int value, int size);
C++中的智能指針是什么纸颜?
智能指針是一個類兽泣,用來存儲指針(指向動態(tài)分配對象的指針)。
C++程序設(shè)計中使用堆內(nèi)存是非常頻繁的操作胁孙,堆內(nèi)存的申請和釋放都由程序員自己管理唠倦。程序員自己管理堆內(nèi)存可以提高了程序的效率,但是整體來說堆內(nèi)存的管理是麻煩的涮较,C++11中引入了智能指針的概念稠鼻,方便管理堆內(nèi)存。使用普通指針狂票,容易造成堆內(nèi)存泄露(忘記釋放)枷餐,二次釋放,程序發(fā)生異常時內(nèi)存泄露等問題等苫亦,使用智能指針能更好的管理堆內(nèi)存毛肋。
智能指針的內(nèi)存泄漏如何解決?
為了解決循環(huán)引用導(dǎo)致的內(nèi)存泄漏屋剑,引入了弱指針weak_ptr
润匙,weak_ptr
的構(gòu)造函數(shù)不會修改引用計數(shù)的值,從而不會對對象的內(nèi)存進行管理唉匾,其類似一個普通指針孕讳,但是不會指向引用計數(shù)的共享內(nèi)存匠楚,但是可以檢測到所管理的對象是否已經(jīng)被釋放,從而避免非法訪問厂财。
預(yù)處理
預(yù)處理器標識#error的目的是什么芋簿?
#error預(yù)處理指令的作用是,編譯程序時璃饱,只要遇到#error就會生成一個編譯錯誤提示消息与斤,并停止編譯。其語法格式為:#error error-message荚恶。
下面舉個例子:
程序中往往有很多的預(yù)處理指令
#ifdef XXX
...
#else
#endif
當程序比較大時撩穿,往往有些宏定義是在外部指定的(如makefile),或是在系統(tǒng)頭文件中指定的谒撼,當你不太確定當前是否定義了 XXX 時食寡,就可以改成如下這樣進行編譯:
#ifdef XXX
...
#error "XXX has been defined"
#else
#endif
這樣,如果編譯時出現(xiàn)錯誤,輸出了XXX has been defined,表明宏XXX已經(jīng)被定義了。
定義常量誰更好廓潜?# define還是 const抵皱?
尺有所短,寸有所長辩蛋, define與 const都能定義常量叨叙,效果雖然一樣,但是各有側(cè)重堪澎。
define既可以替代常數(shù)值擂错,又可以替代表達式,甚至是代碼段樱蛤,但是容易出錯钮呀,而 const的引入可以增強程序的可讀性,它使程序的維護與調(diào)試變得更加方便昨凡。具體而言爽醋,它們的差異主要表現(xiàn)在以下3個方面。
define只是用來進行單純的文本替換便脊, define常量的生命周期止于編譯期蚂四,不分配內(nèi)存空間,它存在于程序的代碼段哪痰,在實際程序中遂赠,它只是一個常數(shù);而 const常量存在于程序的數(shù)據(jù)段晌杰,并在堆棧中分配了空間跷睦, const常量在程序中確確實實存在,并且可以被調(diào)用肋演、傳遞
const常量有數(shù)據(jù)類型抑诸,而 define常量沒有數(shù)據(jù)類型烂琴。編譯器可以對 const常量進行類型安全檢査,如類型蜕乡、語句結(jié)構(gòu)等奸绷,而 define不行。
很多IDE支持調(diào)試 const定義的常量层玲,而不支持 define定義的常量由于 const修飾的變量可以排除程序之間的不安全性因素号醉,保護程序中的常量不被修改,而且對數(shù)據(jù)類型也會進行相應(yīng)的檢查称簿,極大地提高了程序的健壯性,所以一般更加傾向于用const來定義常量類型惰帽。
typedef和 define有什么區(qū)別憨降?
typedef與 define都是替一個對象取一個別名,以此來增強程序的可讀性该酗,但是它們在使用和作用上也存在著以下4個方面的不同授药。
- 原理不同
define是C語言中定義的語法,它是預(yù)處理指令呜魄,在預(yù)處理時進行簡單而機械的字符串替換悔叽,不做正確性檢査,不管含義是否正確照樣代入爵嗅,只有在編譯已被展開的源程序時娇澎,才會發(fā)現(xiàn)可能的錯誤并報錯。
例如睹晒,# define Pl3.1415926
趟庄,當程序執(zhí)行area=Pr*r
語句時,PI會被替換為3.1415926伪很。于是該語句被替換為area=3.1415926*r*r
戚啥。如果把# define語句中的數(shù)字9寫成了g,預(yù)處理也照樣代入锉试,而不去檢查其是否合理猫十、合法佃扼。
typedef是關(guān)鍵字搔涝,它在編譯時處理,所以 typedef具有類型檢查的功能踏施。它在自己的作用域內(nèi)給一個已經(jīng)存在的類型一個別名应又,但是不能在一個函數(shù)定義里面使用標識符 typedef江兢。例如, typedef int INTEGER
丁频,這以后就可用 INTEGER來代替int作整型變量的類型說明了杉允,例如:INTEGER a,b;
用 typedef定義數(shù)組邑贴、指針、結(jié)構(gòu)等類型將帶來很大的方便叔磷,不僅使程序書寫簡單而且使意義更為明確拢驾,因而增強了可讀性。例如:typedef int a[10];
表示a是整型數(shù)組類型改基,數(shù)組長度為10繁疤。然后就可用a說明變量,例如:語句a s1,s2秕狰;完全等效于語句 int s1[10],s2[10].同理稠腊, typedef void(*p)(void)表示p是一種指向void型的指針類型。
- 功能不同
typedef用來定義類型的別名鸣哀,這些類型不僅包含內(nèi)部類型(int架忌、char等),還包括自定義類型(如 struct)我衬,可以起到使類型易于記憶的功能叹放。
例如:typedef int (*PF)(const char *, const char*)
定義一個指向函數(shù)的指針的數(shù)據(jù)類型PF挠羔,其中函數(shù)返回值為int井仰,參數(shù)為 const char*。typedef還有另外一個重要的用途破加,那就是定義機器無關(guān)的類型俱恶。例如,可以定義一個叫REAL的浮點類型范舀,在目標機器上它可以獲得最高的精度:typedef long double REAL
速那,在不支持 long double的機器上,該 typedef看起來會是下面這樣:typedef double real
尿背,在 double都不支持的機器上端仰,該 typedef看起來會是這樣:typedef float REAL
。
define不只是可以為類型取別名田藐,還可以定義常量荔烧、變量、編譯開關(guān)等汽久。
- 作用域不同
define沒有作用域的限制鹤竭,只要是之前預(yù)定義過的宏,在以后的程序中都可以使用景醇,而 typedef有自己的作用域臀稚。
程序示例如下:
void fun()
{
#define A int
}
void gun()
{
//這里也可以使用A,因為宏替換沒有作用域三痰,但如果上面用的是 typedef吧寺,那這里就不能用
//A窜管,不過,一般不在函數(shù)內(nèi)使用 typedef
}
- 對指針的操作不同
兩者修飾指針類型時稚机,作用不同幕帆。
#define INTPTR1 int*
typedef int* INTPTR2;
INTPTR1 pl, p2;
INTPTR2 p3, p4;
INTPTR1 pl, p2和INTPTR2 p3赖条, p4的效果截然不同失乾。 INTPTR1 pl, p2進行字符串替換后變成int*p1,p2
,要表達的意義是聲明一個指針變量p1和一個整型變量p2.而 INTPTR2 p3纬乍, p4碱茁,由于 INTPTR2是具有含義的,告訴我們是一個指向整型數(shù)據(jù)的指針仿贬,那么p3和p4都為指針變量纽竣,這句相當于int*pl,*p2
.從這里可以看出诅蝶,進行宏替換是不含任何意義的替換退个,僅僅為字符串替換募壕;而用 typedef為一種數(shù)據(jù)類型起的別名是帶有一定含義的调炬。
程序示例如下
#define INTPTR1 int*
typedef int* INTPTR2
int a=1;
int b=2;
int c=3;
const INTPTR1 p1=&a;
const INTPTR2 p2=&b;
INTPTR2 const p3=&c;
上述代碼中, const INTPTR1 p1表示p1是一個常量指針舱馅,即不可以通過p1去修改p1指向的內(nèi)容缰泡,但是p1可以指向其他內(nèi)容。而對于 const INTPTR2 p2代嗤,由于 INTPTR2表示的是個指針類型棘钞,因此用 const去限定,表示封鎖了這個指針類型干毅,因此p2是一個指針常量宜猜,不可使p2再指向其他內(nèi)容,但可以通過p2修改其當前指向的內(nèi)容硝逢。 INTPTR2 const p3同樣聲明的是一個指針常量姨拥。
如何使用 define聲明個常數(shù),用以表明1年中有多少秒(忽略閏年問題)
#define SECOND_PER_YEAR (60*60*24*365)UL
# include< filename. h>和# nclude" filename. h"有什么區(qū)別渠鸽?
對于 include< filename. h>叫乌,編譯器先從標準庫路徑開始搜索 filename.h,使得系統(tǒng)文件調(diào)用較快徽缚。而對于# include“ filename.h"”憨奸,編譯器先從用戶的工作路徑開始搜索 filename.h,然后去尋找系統(tǒng)路徑凿试,使得自定義文件較快排宰。
頭文件的作用有哪些似芝?
頭文件的作用主要表現(xiàn)為以下兩個方面:
通過頭文件來調(diào)用庫功能。出于對源代碼保密的考慮额各,源代碼不便(或不準)向用戶公布国觉,只要向用戶提供頭文件和二進制的庫即可。用戶只需要按照頭文件中的接口聲明來調(diào)用庫功能虾啦,而不必關(guān)心接口是怎么實現(xiàn)的麻诀。編譯器會從庫中提取相應(yīng)的代碼。
頭文件能加強類型安全檢查傲醉。當某個接口被實現(xiàn)或被使用時蝇闭,其方式與頭文件中的聲明不一致,編譯器就會指出錯誤硬毕,大大減輕程序員調(diào)試呻引、改錯的負擔(dān)。
在頭文件中定義靜態(tài)變量是否可行吐咳,為什么?
不可行逻悠,如果在頭文件中定義靜態(tài)變量,會造成資源浪費的問題韭脊,同時也可能引起程序錯誤童谒。因為如果在使用了該頭文件的每個C語言文件中定義靜態(tài)變量,按照編譯的步驟沪羔,在每個頭文件中都會單獨存在一個靜態(tài)變量饥伊,從而會引起空間浪費或者程序錯誤所以,不推薦在頭文件中定義任何變量蔫饰,當然也包括靜態(tài)變量琅豆。
寫一個"標準"宏MIN ,這個宏輸入兩個參數(shù)并返回較小的一個篓吁?
#define MIN(A,B) ((A) <= (B) ? (A) : (B))
不使用流程控制語句茫因,如何打印出1~1000的整數(shù)?
宏定義多層嵌套(10 * 10 * 10)杖剪,printf多次輸出冻押。
#include <stdio. h>
#define B P,P,P,P,P,P,P,P,P,P
#define P L,L,L,L,L,L,L,L,L,L
#define L I,I,I,I,I,I,I,I,I,I,N
#define I printf("%3d",i++)
#define N printf("n")
int main()
{
int i = 1;
B;
return 0;
}
簡便寫法,同樣使用多層嵌套
#include<stdio. h>
#define A(x)x;x;x;x;x;x;x;x;x;
int main ()
{
int n=1;
A(A(A(printf("%d"摘盆, n++);
return 0;
}
變量
全局變量和靜態(tài)變量的區(qū)別是什么?
全局變量的作用域為程序塊翼雀,而局部變量的作用域為當前函數(shù)。
內(nèi)存存儲方式不同孩擂,全局變量(靜態(tài)全局變量狼渊,靜態(tài)局部變量)分配在全局數(shù)據(jù)區(qū)(靜態(tài)存儲空間),后者分配在棧區(qū)。
生命周期不同狈邑。全局變量隨主程序創(chuàng)建而創(chuàng)建城须,隨主程序銷毀而銷毀,局部變量在局部函數(shù)內(nèi)部米苹,甚至局部循環(huán)體等內(nèi)部存在糕伐,退出就不存在了。
使用方式不同蘸嘶。通過聲明為全局變量良瞧,程序的各個部分都可以用到,而局部變量只能在局部使用训唱。
全局變量可不可以定義在可被多個.C文件包含的頭文件中褥蚯?為什么?
可以况增,在不同的C文件中以static形式來聲明同名全局變量赞庶。
可以在不同的C文件中聲明同名的全局變量,前提是其中只能有一個C文件中對此變量賦初值澳骤,此時連接不會出錯歧强。
局部變量能否和全局變量重名?
能为肮,局部會屏蔽全局摊册。
局部變量可以與全局變量同名,在函數(shù)內(nèi)引用這個變量時弥锄,會用到同名的局部變量丧靡,而不會用到全局變量蟆沫。
對于有些編譯器而言籽暇,在同一個函數(shù)內(nèi)可以定義多個同名的局部變量,比如在兩個循環(huán)體內(nèi)都定義一個同名的局部變量饭庞,而那個局部變量的作用域就在那個循環(huán)體內(nèi)戒悠。
函數(shù)
請寫個函數(shù)在main函數(shù)執(zhí)行前先運行
attribute可以設(shè)置函數(shù)屬性(Function Attribute)、變量屬性(Variable Attribute)和類型屬性(Type Attribute)舟山。
gnu對于函數(shù)屬性主要設(shè)置的關(guān)鍵字如下:
alias: 設(shè)置函數(shù)別名绸狐。
aligned: 設(shè)置函數(shù)對齊方式。
always_inline/gnu_inline:
函數(shù)是否是內(nèi)聯(lián)函數(shù)累盗。
constructor/destructor:
主函數(shù)執(zhí)行之前寒矿、之后執(zhí)行的函數(shù)。
format:
指定變參函數(shù)的格式輸入字符串所在函數(shù)位置以及對應(yīng)格式輸出的位置若债。
noreturn:
指定這個函數(shù)沒有返回值符相。
請注意,這里的沒有返回值,并不是返回值是void啊终。而是像_exit/exit/abord那樣
執(zhí)行完函數(shù)之后進程就結(jié)束的函數(shù)镜豹。
weak:指定函數(shù)屬性為弱屬性,而不是全局屬性蓝牲,一旦全局函數(shù)名稱和指定的函數(shù)名稱
命名有沖突趟脂,使用全局函數(shù)名稱。
完整示例代碼如下:
#include <stdio.h>
void before() __attribute__((constructor));
void after() __attribute__((destructor));
void before() {
printf("this is function %s\n",__func__);
return;
}
void after(){
printf("this is function %s\n",__func__);
return;
}
int main(){
printf("this is function %s\n",__func__);
return 0;
}
// 輸出結(jié)果
// this is function before
// this is function main
// this is function after
為什么析構(gòu)函數(shù)必須是虛函數(shù)例衍?
將可能會被繼承的父類的析構(gòu)函數(shù)設(shè)置為虛函數(shù)昔期,可以保證當我們new一個子類,然后使用基類指針指向該子類對象佛玄,釋放基類指針時可以釋放掉子類的空間镇眷,防止內(nèi)存泄漏。
為什么C++默認的析構(gòu)函數(shù)不是虛函數(shù)翎嫡?
C++默認的析構(gòu)函數(shù)不是虛函數(shù)是因為虛函數(shù)需要額外的虛函數(shù)表和虛表指針欠动,占用額外的內(nèi)存。而對于不會被繼承的類來說惑申,其析構(gòu)函數(shù)如果是虛函數(shù)具伍,就會浪費內(nèi)存。因此C++默認的析構(gòu)函數(shù)不是虛函數(shù)圈驼,而是只有當需要當作父類時人芽,設(shè)置為虛函數(shù)。
C++中析構(gòu)函數(shù)的作用绩脆?
如果構(gòu)造函數(shù)打開了一個文件萤厅,最后不需要使用時文件就要被關(guān)閉。析構(gòu)函數(shù)允許類自動完成類似清理工作靴迫,不必調(diào)用其他成員函數(shù)惕味。
析構(gòu)函數(shù)也是特殊的類成員函數(shù)。簡單來說玉锌,析構(gòu)函數(shù)與構(gòu)造函數(shù)的作用正好相反名挥,它用來完成對象被刪除前的一些清理工作,也就是專門的掃尾工作主守。
靜態(tài)函數(shù)和虛函數(shù)的區(qū)別禀倔?
靜態(tài)函數(shù)在編譯的時候就已經(jīng)確定運行時機,虛函數(shù)在運行的時候動態(tài)綁定参淫。虛函數(shù)因為用了虛函數(shù)表機制救湖,調(diào)用的時候會增加一次內(nèi)存開銷。
重載和覆蓋有什么區(qū)別涎才?
- 覆蓋是子類和父類之間的關(guān)系鞋既,垂直關(guān)系;重載同一個類之間方法之間的關(guān)系,是水平關(guān)系涛救。
- 覆蓋只能由一個方法或者只能由一對方法產(chǎn)生關(guān)系畏邢;重載是多個方法之間的關(guān)系。
- 覆蓋是根據(jù)對象類型(對象對應(yīng)存儲空間類型)來決定的检吆;而重載關(guān)系是根據(jù)調(diào)用的實參表和形參表來選擇方法體的舒萎。
虛函數(shù)表具體是怎樣實現(xiàn)運行時多態(tài)的?
原理:
虛函數(shù)表是一個類的虛函數(shù)的地址表,每個對象在創(chuàng)建時蹭沛,都會有一個指針指向該類虛函數(shù)表臂寝,每一個類的虛函數(shù)表,按照函數(shù)聲明的順序摊灭,會將函數(shù)地址存在虛函數(shù)表中咆贬,當子類對象重寫父類的虛函數(shù)的時候,父類的虛函數(shù)表中對應(yīng)的位置會被子類的虛函數(shù)地址覆蓋帚呼。
作用:
在用父類的指針調(diào)用子類對象成員函數(shù)時掏缎,虛函數(shù)表會指明要調(diào)用的具體函數(shù)是哪個。
C語言是怎么進行函數(shù)調(diào)用的煤杀?
大多數(shù)CPU上的程序?qū)崿F(xiàn)使用棧來支持函數(shù)調(diào)用操作眷蜈,棧被用來傳遞函數(shù)參數(shù)、存儲返回信息沈自、臨時保存寄存器原有的值以備恢復(fù)以及用來存儲局部變量酌儒。
函數(shù)調(diào)用操作所使用的棧部分叫做棧幀結(jié)構(gòu),每個函數(shù)調(diào)用都有屬于自己的棧幀結(jié)構(gòu)枯途,棧幀結(jié)構(gòu)由兩個指針指定僻肖,幀指針(指向起始)察郁,棧指針(指向棧頂)胁附,函數(shù)對大多數(shù)數(shù)據(jù)的訪問都是基于幀指針呜象。下面是結(jié)構(gòu)圖:
棧指針和幀指針一般都有專門的寄存器,通常使用ebp寄存器作為幀指針捶索,使用esp寄存器做棧指針插掂。
幀指針指向棧幀結(jié)構(gòu)的頭灰瞻,存放著上一個棧幀的頭部地址腥例,棧指針指向棧頂。
請你說一說select
- select函數(shù)原型
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
- 文件描述符的數(shù)量
單個進程能夠監(jiān)視的文件描述符的數(shù)量存在最大限制酝润,通常是1024燎竖,當然可以更改數(shù)量;(在linux內(nèi)核頭文件中定義:#define __FD_SETSIZE 1024)
- 就緒fd采用輪詢的方式掃描
select返回的是int要销,可以理解為返回的是ready(準備好的)一個或者多個文件描述符构回,應(yīng)用程序需要遍歷整個文件描述符數(shù)組才能發(fā)現(xiàn)哪些fd句柄發(fā)生了事件,由于select采用輪詢的方式掃描文件描述符(不知道那個文件描述符讀寫數(shù)據(jù),所以需要把所有的fd都遍歷)纤掸,文件描述符數(shù)量越多脐供,性能越差
- 內(nèi)核 /用戶空間內(nèi)存拷貝
select每次都會改變內(nèi)核中的句柄數(shù)據(jù)結(jié)構(gòu)集(fd集合),因而每次調(diào)用select都需要從用戶空間向內(nèi)核空間復(fù)制所有的句柄數(shù)據(jù)結(jié)構(gòu)(fd集合)借跪,產(chǎn)生巨大的開銷
- select的觸發(fā)方式
select的觸發(fā)方式是水平觸發(fā)政己,應(yīng)用程序如果沒有完成對一個已經(jīng)就緒的文件描述符進行IO操作,那么之后每次調(diào)用select還是會將這些文件描述符通知進程掏愁。
- 優(yōu)點
a. select的可移植性較好歇由,可以跨平臺;
b. select可設(shè)置的監(jiān)聽時間timeout精度更好果港,可精確到微秒沦泌,而poll為毫秒。
- 缺點:
a. select支持的文件描述符數(shù)量上限為1024辛掠,不能根據(jù)用戶需求進行更改谢谦;
b. select每次調(diào)用時都要將文件描述符集合從用戶態(tài)拷貝到內(nèi)核態(tài),開銷較大萝衩;
c. select返回的就緒文件描述符集合他宛,需要用戶循環(huán)遍歷所監(jiān)聽的所有文件描述符是否在該集合中,當監(jiān)聽描述符數(shù)量很大時效率較低欠气。
請你說說fork,wait,exec函數(shù)
父進程產(chǎn)生子進程使用fork拷貝出來一個父進程的副本厅各,此時只拷貝了父進程的頁表,兩個進程都讀同一塊內(nèi)存预柒,當有進程寫的時候使用寫實拷貝機制分配內(nèi)存队塘,exec函數(shù)可以加載一個elf文件去替換父進程,從此父進程和子進程就可以運行不同的程序了宜鸯。fork從父進程返回子進程的pid憔古,從子進程返回0.調(diào)用了wait的父進程將會發(fā)生阻塞,直到有子進程狀態(tài)改變,執(zhí)行成功返回0淋袖,錯誤返回-1鸿市。exec執(zhí)行成功則子進程從新的程序開始運行,無返回值即碗,執(zhí)行失敗返回-1焰情。
數(shù)組
以下代碼表示什么意思?
*(a[1]+1)、*(&a[1][1])剥懒、(*(a+1))[1]
第一個: 因為a[1]是第2行的地址内舟,a[1]+1偏移一個單位(得到第2行第2列的地址),然后解引用取值初橘,得到a[1][1]
验游;
第二個:[]優(yōu)先級高充岛,a[1][1]取地址再取值。
第三個:a+1相當于&a[1]耕蝉,所以* (a+1)=a[1]崔梗,因此*(a+1)[1]=a[1][1]
數(shù)組下標可以為負數(shù)嗎?
可以,因為下標只是給出了一個與當前地址的偏移量而已垒在,只要根據(jù)這個偏移量能定位得到目標地址即可炒俱。下面給出一個下標為負數(shù)的示例:
數(shù)組下標取負值的情況:
#include <stdio.h>
int main()
{
int i:
int a[5]={0,1,2,3,4};
int *p=&a[4]
for(i=-4;i<=0爪膊;i++)
printf("%d %x\n", p[i], &p[i]);
return O.
}
//輸出結(jié)果為
//0 b3ecf480
//1 b3ecf484
//2 b3ecf488
//3 b3ecf48c
//4 b3ecf490
從上例可以發(fā)現(xiàn)权悟,在C語言中,數(shù)組的下標并非不可以為負數(shù)推盛,當數(shù)組下標為負數(shù)時峦阁,編譯可以通過,而且也可以得到正確的結(jié)果耘成,只是它表示的意思卻是從當前地址向前尋址.
位操作
如何求解整型數(shù)的二進制表示中1的個數(shù)榔昔?
程序代碼如下:
#include <stdio.h>
int func(int x)
{
int countx = 0;
while(x)
{
countx++;
x = x&(x-1);
}
return countx;
}
int main()
{
printf("%d\n",func(9999));
return 0;
}
程序輸出的結(jié)果為8。
在上例中瘪菌,函數(shù)func()的功能是將x轉(zhuǎn)化為二進制數(shù)撒会,然后計算該二進制數(shù)中含有的1的個數(shù)。首先以9為例來分析师妙,9的二進制表示為1001,8的二進制表示為1000诵肛,兩者執(zhí)行&操作之后結(jié)果為1000,此時1000再與0111(7的二進制位)執(zhí)行&操作之后結(jié)果為0默穴。
為了理解這個算法的核心怔檩,需要理解以下兩個操作:
1)當一個數(shù)被減1時,它最右邊的那個值為1的bit將變?yōu)?蓄诽,同時其右邊的所有的bit都會變成1薛训。
2)每次執(zhí)行x&(x-1)的作用是把ⅹ對應(yīng)的二進制數(shù)中的最后一位1去掉。因此仑氛,循環(huán)執(zhí)行這個操作直到ⅹ等于0的時候乙埃,循環(huán)的次數(shù)就是x對應(yīng)的二進制數(shù)中1的個數(shù)。
如何求解二進制中0的個數(shù)
int CountZeroBit(int num)
{
int count = 0;
while (num + 1)
{
count++;
num |= (num + 1); //算法轉(zhuǎn)換
}
return count;
}
int main()
{
int value = 25;
int ret = CountZeroBit(value);
printf("%d的二進制位中0的個數(shù)為%d\n",value, ret);
system("pause");
return 0;
}
交換兩個變量的值锯岖,不使用第三個變量介袜。即a=3,b=5,交換之后a=5,b=3;
有兩種解法, 一種用算術(shù)算法, 一種用^(異或)。
a = a + b;
b = a - b;
a = a - b;
a = a^b;// 只能對int,char..
b = a^b;
a = a^b;
or
a ^= b ^= a;
給定一個整型變量a嚎莉,寫兩段代碼米酬,第一個設(shè)置a的bit 3,第二個清除a 的bit 3趋箩。在以上兩個操作中赃额,要保持其它位不變。
#define BIT3 (0x1<<3)
static int a;
void set_bit3(void)
{
a |= BIT3;
}
void clear_bit3(void)
{
a &= ~BIT3;
}
容器和算法
map和set有什么區(qū)別叫确?分別又是怎么實現(xiàn)的跳芳?
map和set都是C++的關(guān)聯(lián)容器,其底層實現(xiàn)都是紅黑樹(RB-Tree)竹勉。
由于 map 和set所開放的各種操作接口飞盆,RB-tree 也都提供了,所以幾乎所有的 map 和set的操作行為次乓,都只是轉(zhuǎn)調(diào) RB-tree 的操作行為吓歇。
map和set的區(qū)別在于:
map中的元素是key-value(鍵值對)對:關(guān)鍵字起到索引的作用,值則表示與索引相關(guān)聯(lián)的數(shù)據(jù)票腰;Set與之相對就是關(guān)鍵字的簡單集合城看,set中每個元素只包含一個關(guān)鍵字。
set的迭代器是const的杏慰,不允許修改元素的值测柠;map允許修改value,但不允許修改key缘滥。
其原因是因為map和set是根據(jù)關(guān)鍵字排序來保證其有序性的轰胁,如果允許修改key的話,那么首先需要刪除該鍵朝扼,然后調(diào)節(jié)平衡赃阀,再插入修改后的鍵值,調(diào)節(jié)平衡擎颖,如此一來凹耙,嚴重破壞了map和set的結(jié)構(gòu),導(dǎo)致iterator失效肠仪,不知道應(yīng)該指向改變前的位置肖抱,還是指向改變后的位置。所以STL中將set的迭代器設(shè)置成const异旧,不允許修改迭代器的值意述;而map的迭代器則不允許修改key值,允許修改value值吮蛹。
map支持下標操作荤崇,set不支持下標操作。
map可以用key做下標潮针,map的下標運算符[ ]將關(guān)鍵碼作為下標去執(zhí)行查找术荤,如果關(guān)鍵碼不存在,則插入一個具有該關(guān)鍵碼和mapped_type類型默認值的元素至map中每篷,因此下標運算符[ ]在map應(yīng)用中需要慎用瓣戚,const_map不能用端圈,只希望確定某一個關(guān)鍵值是否存在而不希望插入元素時也不應(yīng)該使用,mapped_type類型沒有默認值也不應(yīng)該使用子库。如果find能解決需要舱权,盡可能用find。
STL的allocator有什么作用仑嗅?
STL的分配器用于封裝STL容器在內(nèi)存管理上的底層細節(jié)宴倍。在C++中,其內(nèi)存配置和釋放如下:
new運算分兩個階段:(1)調(diào)用::operator new配置內(nèi)存;(2)調(diào)用對象構(gòu)造函數(shù)構(gòu)造對象內(nèi)容
delete運算分兩個階段:(1)調(diào)用對象希構(gòu)函數(shù)仓技;(2)掉員工::operator delete釋放內(nèi)存
為了精密分工鸵贬,STL allocator將兩個階段操作區(qū)分開來:內(nèi)存配置有alloc::allocate()負責(zé),內(nèi)存釋放由alloc::deallocate()負責(zé)脖捻;對象構(gòu)造由::construct()負責(zé)阔逼,對象析構(gòu)由::destroy()負責(zé)。
同時為了提升內(nèi)存管理的效率郭变,減少申請小內(nèi)存造成的內(nèi)存碎片問題颜价,SGI STL采用了兩級配置器,當分配的空間大小超過128B時诉濒,會使用第一級空間配置器周伦;當分配的空間大小小于128B時,將使用第二級空間配置器未荒。第一級空間配置器直接使用malloc()专挪、realloc()、free()函數(shù)進行內(nèi)存空間的分配和釋放片排,而第二級空間配置器采用了內(nèi)存池技術(shù)寨腔,通過空閑鏈表來管理內(nèi)存。
STL迭代器如何刪除元素率寡?
對于序列容器vector,deque來說迫卢,使用erase(itertor)后,后邊的每個元素的迭代器都會失效冶共,但是后邊每個元素都會往前移動一個位置乾蛤,但是erase會返回下一個有效的迭代器;
對于關(guān)聯(lián)容器map set來說捅僵,使用了erase(iterator)后家卖,當前元素的迭代器失效,但是其結(jié)構(gòu)是紅黑樹庙楚,刪除當前元素的上荡,不會影響到下一個元素的迭代器,所以在調(diào)用erase之前馒闷,記錄下一個元素的迭代器即可酪捡。
對于list來說叁征,它使用了不連續(xù)分配的內(nèi)存,并且它的erase方法也會返回下一個有效的iterator沛善,因此上面兩種正確的方法都可以使用航揉。
STL中MAP數(shù)據(jù)如何存放的塞祈?
紅黑樹金刁。unordered map底層結(jié)構(gòu)是哈希表
STL中map與unordered_map有什么區(qū)別?
map在底層使用了紅黑樹來實現(xiàn)议薪,unordered_map是C++11標準中新加入的容器尤蛮,它的底層是使用hash表的形式來完成映射的功能,map是按照operator<比較判斷元素是否相同斯议,以及比較元素的大小产捞,然后選擇合適的位置插入到樹中。所以哼御,如果對map進行遍歷(中序遍歷)的話坯临,輸出的結(jié)果是有序的。順序就是按照operator< 定義的大小排序恋昼。
而unordered_map是計算元素的Hash值看靠,根據(jù)Hash值判斷元素是否相同。所以液肌,對unordered_map進行遍歷挟炬,結(jié)果是無序的。
使用map時嗦哆,需要為key定義operator< 谤祖。 而unordered_map的使用需要定義hash_value函數(shù)并且重載operator==。對于內(nèi)置類型老速,如string粥喜,這些都不用操心,可以使用默認的橘券。對于自定義的類型做key额湘,就需要自己重載operator< 或者hash_value()了。
所以說约郁,當不需要結(jié)果排好序時缩挑,最好用unordered_map,插入刪除和查詢的效率要高于map鬓梅。
vector和list的區(qū)別是什么供置?
vector底層實現(xiàn)是數(shù)組;list是雙向 鏈表绽快。
vector支持隨機訪問芥丧,list不支持紧阔。
vector是順序內(nèi)存,list不是续担。
vector在中間節(jié)點進行插入刪除會導(dǎo)致內(nèi)存拷貝擅耽,list不會。
vector一次性分配好內(nèi)存物遇,不夠時才進行2倍擴容乖仇;list每次插入新節(jié)點都會進行內(nèi)存申請。
vector隨機訪問性能好询兴,插入刪除性能差乃沙;list隨機訪問性能差,插入刪除性能好诗舰。
STL中迭代器有什么作用警儒?有指針為何還要迭代器?
1眶根、迭代器
Iterator(迭代器)模式又稱Cursor(游標)模式蜀铲,用于提供一種方法順序訪問一個聚合對象中各個元素, 而又不需暴露該對象的內(nèi)部表示∈舭伲或者這樣說可能更容易理解:Iterator模式是運用于聚合對象的一種模式记劝,通過運用該模式,使得我們可以在不知道對象內(nèi)部表示的情況下诸老,按照一定順序(由iterator提供的方法)訪問聚合對象中的各個元素隆夯。
由于Iterator模式的以上特性:與聚合對象耦合,在一定程度上限制了它的廣泛運用别伏,一般僅用于底層聚合支持類蹄衷,如STL的list、vector厘肮、stack等容器類及ostream_iterator等擴展iterator愧口。
2旭寿、迭代器和指針的區(qū)別
迭代器不是指針期贫,是類模板屹电,表現(xiàn)的像指針判沟。他只是模擬了指針的一些功能,通過重載了指針的一些操作符姓赤,->另萤、*浑槽、++兢哭、--等领舰。迭代器封裝了指針,是一個“可遍歷STL( Standard Template Library)容器內(nèi)全部或部分元素”的對象, 本質(zhì)是封裝了原生指針冲秽,是指針概念的一種提升(lift)舍咖,提供了比指針更高級的行為,相當于一種智能指針锉桑,他可以根據(jù)不同類型的數(shù)據(jù)結(jié)構(gòu)來實現(xiàn)不同的++排霉,--等操作。
迭代器返回的是對象引用而不是對象的值民轴,所以cout只能輸出迭代器使用*取值后的值而不能直接輸出其自身攻柠。
3、迭代器產(chǎn)生原因
Iterator類的訪問方式就是把不同集合類的訪問邏輯抽象出來杉武,使得不用暴露集合內(nèi)部的結(jié)構(gòu)而達到循環(huán)遍歷集合的效果辙诞。
epoll的原理是什么辙售?
調(diào)用順序:
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
首先創(chuàng)建一個epoll對象轻抱,然后使用epoll_ctl對這個對象進行操作,把需要監(jiān)控的描述添加進去旦部,這些描述如將會以epoll_event結(jié)構(gòu)體的形式組成一顆紅黑樹祈搜,接著阻塞在epoll_wait,進入大循環(huán)士八,當某個fd上有事件發(fā)生時容燕,內(nèi)核將會把其對應(yīng)的結(jié)構(gòu)體放入到一個鏈表中,返回有事件發(fā)生的鏈表婚度。
STL里resize和reserve的區(qū)別是什么蘸秘?
改變當前容器內(nèi)含有元素的數(shù)量(size()),eg: vectorv; v.resize(len);v的 size 變?yōu)?len,如果原來 v 的 size 小于 len蝗茁,那么容器新增(len-size)個元素醋虏,元素的值為
默認為 0.當 v.push_back(3);之后,則是 3 是放在了 v 的末尾哮翘,即下標為 len颈嚼,此時容器是 size為 len+1;
改變當前容器的最大容量(capacity),它不會生成元素饭寺,只是確定這個容器允許放入多少對象阻课,如果 reserve(len)的值大于當前的 capacity(),那么會重新分配一塊能存 len 個對象的空間艰匙,然后把之前 v.size()個對象通過 copy construtor 復(fù)制過來限煞,銷毀之前的內(nèi)存;
類和數(shù)據(jù)抽象
C++中類成員的訪問權(quán)限员凝?
C++通過 public署驻、protected、private 三個關(guān)鍵字來控制成員變量和成員函數(shù)的訪問權(quán)限,它們分別表示公有的硕舆、受保護的秽荞、私有的,被稱為成員訪問限定符抚官。在類的內(nèi)部(定義類的代碼內(nèi)部)扬跋,無論成員被聲明為 public、protected 還是 private凌节,都是可以互相訪問的钦听,沒有訪問權(quán)限的限制。在類的外部(定義類的代碼之外)倍奢,只能通過對象訪問成員朴上,并且通過對象只能訪問 public 屬性的成員,不能訪問 private卒煞、protected 屬性的成員
C++中struct和class的區(qū)別是什么?
在C++中痪宰,可以用struct和class定義類,都可以繼承畔裕。區(qū)別在于:structural的默認繼承權(quán)限和默認訪問權(quán)限是public衣撬,而class的默認繼承權(quán)限和默認訪問權(quán)限是private。另外扮饶,class還可以定義模板類形參具练,比如template。
C++類內(nèi)可以定義引用數(shù)據(jù)成員嗎甜无?
可以扛点,必須通過成員函數(shù)初始化列表初始化。
面向?qū)ο笈c泛型編程是什么岂丘?
- 面向?qū)ο缶幊毯喎QOOP,是一種程序設(shè)計思想陵究。OOP把對象作為程序的基本單元,一個對象包含了數(shù)據(jù)和操作數(shù)據(jù)的函數(shù)。
- 面向過程的程序設(shè)計把計算機程序視為一系列的命令集合元潘,即一組函數(shù)的順序執(zhí)行畔乙。為了簡化程序設(shè)計,面向過程把函數(shù)繼續(xù)切分為子函數(shù)翩概,即把大塊函數(shù)通過切割成小塊函數(shù)來降低系統(tǒng)的復(fù)雜度牲距。
- 泛型編程: 讓類型參數(shù)化,方便程序員編碼。
類型參數(shù)化: 使的程序(算法)可以從邏輯功能上抽象,把被處理對象(數(shù)據(jù))的類型作為參數(shù)傳遞钥庇。
什么是右值引用牍鞠,跟左值又有什么區(qū)別?
左值和右值的概念:
左值:能對表達式取地址评姨、或具名對象/變量难述。一般指表達式結(jié)束后依然存在的持久對象萤晴。
右值:不能對表達式取地址,或匿名對象胁后。一般指表達式結(jié)束就不再存在的臨時對象店读。
右值引用和左值引用的區(qū)別:
- 左值可以尋址,而右值不可以攀芯。
- 左值可以被賦值屯断,右值不可以被賦值,可以用來給左值賦值侣诺。
- 左值可變,右值不可變(僅對基礎(chǔ)類型適用殖演,用戶自定義類型右值引用可以通過成員函數(shù)改變)。
析構(gòu)函數(shù)可以為 virtual 型年鸳,構(gòu)造函數(shù)則不能趴久,為什么?
構(gòu)造函數(shù)不能聲明為虛函數(shù)搔确,析構(gòu)函數(shù)可以聲明為虛函數(shù)彼棍,而且有時是必須聲明為虛函數(shù)。不建議在構(gòu)造函數(shù)和析構(gòu)函數(shù)里面調(diào)用虛函數(shù)妥箕。
構(gòu)造函數(shù)不能聲明為虛函數(shù)的原因是:
虛函數(shù)的主要意義在于被派生類繼承從而產(chǎn)生多態(tài)滥酥。派生類的構(gòu)造函數(shù)中,編譯器會加入構(gòu)造基類的代碼畦幢,如果基類的構(gòu)造函數(shù)用到參數(shù),則派生類在其構(gòu)造函
數(shù)的初始化列表中必須為基類給出參數(shù)缆蝉,就是這個原因宇葱。虛函數(shù)的意思就是開啟動態(tài)綁定,程序會根據(jù)對象的動態(tài)類型來選擇要調(diào)用的方法刊头。然而在構(gòu)造函數(shù)運行的時候黍瞧,這個對象的動態(tài)類型還不完整,沒有辦法確定它到底是什么類型原杂,故構(gòu)造函數(shù)不能動態(tài)綁定印颤。(動態(tài)綁定是根據(jù)對象的動態(tài)類型而不是函數(shù)名,在調(diào)用構(gòu)造函數(shù)之前穿肄,這個對象根本就不存在年局,它怎么動態(tài)綁定?)
C++中空類默認產(chǎn)生哪些類成員函數(shù)咸产?
C++中空類默認會產(chǎn)生以下6個函數(shù):默認構(gòu)造函數(shù)矢否、復(fù)制構(gòu)造函數(shù)、析構(gòu)函數(shù)脑溢、賦值運算符重載函數(shù)僵朗、取址運算法重載函數(shù)、const取址運算符重載函數(shù)等。
class Empty
{
public:
Empty(); // 缺省構(gòu)造函數(shù)
Empty( const Empty& ); // 拷貝構(gòu)造函數(shù)
~Empty(); // 析構(gòu)函數(shù)
Empty& operator=( const Empty& ); // 賦值運算符
Empty* operator&(); // 取址運算符
const Empty* operator&() const; // 取址運算符 const
};
面向?qū)ο?/h2>
面向?qū)ο蠛兔嫦蜻^程有什么區(qū)別验庙?
面向?qū)ο笈c面向過程有以下四個方面的不同:
- 出發(fā)點不同
面向?qū)ο笫褂梅铣R?guī)思維的方式來處理客觀世界的問題顶吮,強調(diào)把解決問題領(lǐng)域的“動作”直接映射到對象之間的接口上。而面向過程則強調(diào)的是過程的抽象化與模塊化粪薛,是以過程為中心構(gòu)造或處理客觀世界問題云矫。
- 層次邏輯關(guān)系不同
面向?qū)ο笫褂糜嬎銠C邏輯來模擬客觀世界中的物理存在,以對象的集合類作為處理問題的單位汗菜,盡可能地使計算機世界向客觀世界靠攏让禀,以使處理問題的方式更清晰直接,面向?qū)ο笫褂妙惖膶哟谓Y(jié)構(gòu)來體現(xiàn)類之間的繼承與發(fā)展陨界。面向過程處理問題的基本單位是能清晰準確地表達過程的模塊巡揍,用模塊的層次結(jié)構(gòu)概括模塊或模塊間的關(guān)系與功能,把客觀世界的問題抽象成計算機可以處理的過程菌瘪。
- 數(shù)據(jù)處理方式與控制程序方式不同
面向?qū)ο髮?shù)據(jù)與對應(yīng)的代碼封裝成一個整體腮敌,原則上其他對象不能直接修改其數(shù)據(jù),即對象的修改只能由自身的成員函數(shù)完成俏扩,控制程序方式上是通過“事件驅(qū)動”來激活和運行程序的糜工。而面向過程是直接通過程序來處理數(shù)據(jù),處理完畢后即可顯示處理的結(jié)果录淡,在控制方式上是按照設(shè)計調(diào)用或返回程序捌木,不能自由導(dǎo)航,各模塊之間存在著控制與被控制嫉戚,調(diào)動與被調(diào)用的關(guān)系刨裆。
- 分析設(shè)計與編碼轉(zhuǎn)換方式不同
面向?qū)ο筘灤┯谲浖芷诘姆治觥⒃O(shè)計及編碼中彬檀,是一種平滑的過程帆啃,從分析到設(shè)計再到編碼是采用一致性的模型表示,實現(xiàn)的是一種無縫連接窍帝。而面向過程強調(diào)分析努潘、設(shè)計及編碼之間按規(guī)則進行轉(zhuǎn)換貫穿于軟件生命周期的分析、設(shè)計及編碼中坤学,實現(xiàn)的是一種有縫的連接疯坤。
面向?qū)ο蟮幕咎卣饔心男?/h3>
面向?qū)ο蟮木幊谭椒ㄓ兴膫€基本特性:
- 抽象:就是忽略一個主題中與當前目標無關(guān)的方面,以便更充分地注意與當前目標有關(guān)的方面拥峦。抽象并不打算了解全部問題贴膘,而只是選擇其中的一部分,暫時不用部分細節(jié)略号。抽象包括兩個方面刑峡,一是過程抽象洋闽,二是數(shù)據(jù)抽象。
過程抽象是指任何一個明確定義功能的操作都可被使用者看作單個的實體看待突梦,盡管這個操作實際上可能由一系列更低級的操作來完成诫舅。數(shù)據(jù)抽象定義了數(shù)據(jù)類型和施加于該類型對象上的操作,并限定了對象的值宫患,只能通過使用這些操作修改和觀察刊懈。
- 繼承:這是一種聯(lián)結(jié)類的層次模型,并且允許和鼓勵類的重用娃闲,它提供了一種明確表述共性的方法虚汛。對象的一個新類可以從現(xiàn)有的類中派生,這個過程稱為類繼承皇帮。新類繼承了原始類的特性卷哩,新類稱為原始類的派生類(子類),而原始類稱為新類的基類(父類)属拾。
派生類可以從它的基類那里繼承方法和實例變量将谊,并且類可以修改或增加新的方法使之更適合特殊的需要。這也體現(xiàn)了大自然中一般與特殊的關(guān)系渐白。繼承性很好地解決了軟件的可重用性問題尊浓。
- 封裝:就是把過程和數(shù)據(jù)包圍起來,對數(shù)據(jù)的訪問只能通過已定義的接口纯衍。面向?qū)ο蟮挠嬎闶加谶@個基本概念栋齿,即現(xiàn)實世界可以被描繪成一系列完全自治、封裝的對象托酸,這些對象通過一個受保護的接口訪問其他對象褒颈。一旦定義了一個對象的特性,則有必要決定這些特性的可見性励堡,即哪些特性對外部世界是可見的,哪些特性用于表示內(nèi)部狀態(tài)堡掏。
在這個階段定義對象的接口应结。通常,應(yīng)禁止直接訪問一個對象的實際表示泉唁,而應(yīng)通過操作接口訪問對象鹅龄,這稱為信息隱藏。封裝保證了模塊具有較好的獨立性亭畜,使得程序維護修改較為容易扮休。對應(yīng)用程序的修改僅限于類的內(nèi)部,因而可以將應(yīng)用程序修改帶來的影響減少到最低限度拴鸵。
- 多態(tài):是指允許不同類的對象對同一消息做出響應(yīng)玷坠。比如同樣的復(fù)制-粘貼操作蜗搔,在字處理程序和繪圖程序中有不同的效果。多態(tài)性包括參數(shù)化多態(tài)性和包含多態(tài)性八堡。多態(tài)性語言具有靈活樟凄、抽象、行為共享兄渺、代碼共享的優(yōu)勢缝龄,很好地解決了應(yīng)用程序函數(shù)同名問題。
什么是深拷貝挂谍?什么是淺拷貝叔壤?
深拷貝是徹底的拷貝,兩對象中所有的成員都是獨立的一份口叙,而且炼绘,成員對象中的成員對象也是獨立一份。
淺拷貝中的某些成員變量可能是共享的庐扫,深拷貝如果不夠徹底饭望,就是淺拷貝。
什么是友元形庭?
有成員只能在類的成員函數(shù)內(nèi)部訪問铅辞,如果想在別處訪問對象的私有成員,只能通過類提供的接口(成員函數(shù))間接地進行萨醒。這固然能夠帶來數(shù)據(jù)隱藏的好處斟珊,利于將來程序的擴充,但也會增加程序書寫的麻煩富纸。
C++是從結(jié)構(gòu)化的C語言發(fā)展而來的囤踩,需要照顧結(jié)構(gòu)化設(shè)計程序員的習(xí)慣,所以在對私有成員可訪問范圍的問題上不可限制太死晓褪。
C++ 設(shè)計者認為堵漱, 如果有的程序員真的非常怕麻煩,就是想在類的成員函數(shù)外部直接訪問對象的私有成員涣仿,那還是做一點妥協(xié)以滿足他們的愿望為好勤庐,這也算是眼前利益和長遠利益的折中。因此好港,C++ 就有了友元(friend)的概念愉镰。打個比方,這相當于是說:朋友是值得信任的钧汹,所以可以對他們公開一些自己的隱私丈探。
友元提供了一種 普通函數(shù)或者類成員函數(shù) 訪問另一個類中的私有或保護成員 的機制。也就是說有兩種形式的友元:
(1)友元函數(shù):普通函數(shù)對一個訪問某個類中的私有或保護成員拔莱。
(2)友元類:類A中的成員函數(shù)訪問類B中的私有或保護成員碗降。
基類的構(gòu)造函數(shù)/析構(gòu)函數(shù)是否能被派生類繼承隘竭?
基類的構(gòu)造函數(shù)析構(gòu)函數(shù)不能被派生類繼承。
基類的構(gòu)造函數(shù)不能被派生類繼承遗锣,派生類中需要聲明自己的構(gòu)造函數(shù)货裹。設(shè)計派生類的構(gòu)造函數(shù)時,不僅要考慮派生類所增加的數(shù)據(jù)成員初始化精偿,也要考慮基類的數(shù)據(jù)成員的初始化弧圆。聲明構(gòu)造函數(shù)時,只需要對本類中新增成員進行初始化笔咽,對繼承來的基類成員的初始化闸溃,需要調(diào)用基類構(gòu)造函數(shù)完成燃观。
基類的析構(gòu)函數(shù)也不能被派生類繼承寡喝,派生類需要自行聲明析構(gòu)函數(shù)圆仔。聲明方法與一般(無繼承關(guān)系時)類的析構(gòu)函數(shù)相同,不需要顯式地調(diào)用基類的析構(gòu)函數(shù)甩十,系統(tǒng)會自動隱式調(diào)用船庇。需要注意的是,析構(gòu)函數(shù)的調(diào)用次序與構(gòu)造函數(shù)相反侣监。
初始化列表和構(gòu)造函數(shù)初始化的區(qū)別鸭轮?
構(gòu)造函數(shù)初始化列表以一個冒號開始,接著是以逗號分隔的數(shù)據(jù)成員列表橄霉,每個數(shù)據(jù)成員后面跟一個放在括號中的初始化式窃爷。例如:
Example::Example() : ival(0), dval(0.0) {} //ival 和dval是類的兩個數(shù)據(jù)成員
上面的例子和下面不用初始化列表的構(gòu)造函數(shù)看似沒什么區(qū)別:
Example::Example()
{
ival = 0;
dval = 0.0;
}
的確,這兩個構(gòu)造函數(shù)的結(jié)果是一樣的姓蜂。但區(qū)別在于:上面的構(gòu)造函數(shù)(使用初始化列表的構(gòu)造函數(shù))顯示的初始化類的成員按厘;而沒使用初始化列表的構(gòu)造函數(shù)是對類的成員賦值,并沒有進行顯示的初始化钱慢。
初始化和賦值對內(nèi)置類型的成員沒有什么大的區(qū)別逮京,像上面的任一個構(gòu)造函數(shù)都可以。但有的時候必須用帶有初始化列表的構(gòu)造函數(shù):
成員類型是沒有默認構(gòu)造函數(shù)的類束莫。若沒有提供顯示初始化式造虏,則編譯器隱式使用成員類型的默認構(gòu)造函數(shù),若類沒有默認構(gòu)造函數(shù)麦箍,則編譯器嘗試使用默認構(gòu)造函數(shù)將會失敗。
const成員或引用類型的員陶珠。因為const對象或引用類型只能初始化挟裂,不能對他們賦值。
C++中有那些情況只能用初始化列表揍诽,而不能用賦值诀蓉?
構(gòu)造函數(shù)初始化列表以一個冒號開始栗竖,接著是以逗號分隔的數(shù)據(jù)成員列表,每個數(shù)據(jù)成員后面都跟一個放在括號中的初始化式渠啤。例如狐肢, Example:Example ival(o,dva(0.0){},其中ival與dva是類的兩個數(shù)據(jù)成員沥曹。
在C++語言中份名,賦值與初始化列表的原理不一樣,賦值是刪除原值妓美,賦予新值僵腺,初始化列表開辟空間和初始化是同時完成的,直接給予一個值
所以壶栋,在C++中辰如,賦值與初始化列表的使用情況也不一樣,只能用初始化列表贵试,而不能用賦值的情況一般有以下3種:
- 當類中含有 const(常量)琉兜、 reference(引用)成員變量時,只能初始化毙玻,不能對它們進行賦值豌蟋。常量不能被賦值,只能被初始化淆珊,所以必須在初始化列表中完成夺饲,C++的引用也一定要初始化,所以必須在初始化列表中完成施符。
- 派生類在構(gòu)造函數(shù)中要對自身成員初始化往声,也要對繼承過來的基類成員進行初始化當基類沒有默認構(gòu)造函數(shù)的時候,通過在派生類的構(gòu)造函數(shù)初始化列表中調(diào)用基類的構(gòu)造函數(shù)實現(xiàn)戳吝。
- 如果成員類型是沒有默認構(gòu)造函數(shù)的類浩销,也只能使用初始化列表。若沒有提供顯式初始化時听哭,則編譯器隱式使用成員類型的默認構(gòu)造函數(shù)慢洋,此時編譯器嘗試使用默認構(gòu)造函數(shù)將會失敗
類的成員變量的初始化順序是什么?
成員變量在使用初始化列表初始化時陆盘,與構(gòu)造函數(shù)中初始化成員列表的順序無關(guān)普筹,只與定義成員變量的順序有關(guān)。因為成員變量的初始化次序是根據(jù)變量在內(nèi)存中次序有關(guān)隘马,而內(nèi)存中的排列順序早在編譯期就根據(jù)變量的定義次序決定了太防。這點在EffectiveC++中有詳細介紹。
如果不使用初始化列表初始化酸员,在構(gòu)造函數(shù)內(nèi)初始化時蜒车,此時與成員變量在構(gòu)造函數(shù)中的位置有關(guān)讳嘱。
注意:類成員在定義時,是不能初始化的
注意:類中const成員常量必須在構(gòu)造函數(shù)初始化列表中初始化酿愧。
注意:類中static成員變量沥潭,必須在類外初始化。
靜態(tài)變量進行初始化順序是基類的靜態(tài)變量先初始化嬉挡,然后是它的派生類钝鸽。直到所有的靜態(tài)變量都被初始化。這里需要注意全局變量和靜態(tài)變量的初始化是不分次序的棘伴。這也不難理解寞埠,其實靜態(tài)變量和全局變量都被放在公共內(nèi)存區(qū)『缚洌可以把靜態(tài)變量理解為帶有“作用域”的全局變量仁连。在一切初始化工作結(jié)束后,main函數(shù)會被調(diào)用阱穗,如果某個類的構(gòu)造函數(shù)被執(zhí)行饭冬,那么首先基類的成員變量會被初始化。
當一個類為另一個類的成員變量時揪阶,如何對其進行初始化昌抠?
示例程序如下:
class ABC
{
public:
ABC(int x, int y, int z);
private :
int a;
int b;
int c;
};
class MyClass?
{
public:
MyClass():abc(1,2,3)
{
}
private:
ABC abc;
};
上例中,因為ABC有了顯式的帶參數(shù)的構(gòu)造函數(shù)鲁僚,那么它是無法依靠編譯器生成無參構(gòu)造函數(shù)的炊苫,所以必須使用初始化列表:abc(1,2,3),才能構(gòu)造ABC的對象冰沙。
C++能設(shè)計實現(xiàn)一個不能被繼承的類嗎侨艾?
在Java 中定義了關(guān)鍵字final ,被final 修飾的類不能被繼承拓挥。但在C++ 中沒有final 這個關(guān)鍵字唠梨,要實現(xiàn)這個要求還是需要花費一些精力。
首先想到的是在C++ 中侥啤,子類的構(gòu)造函數(shù)會自動調(diào)用父類的構(gòu)造函數(shù)当叭。同樣,子類的析構(gòu)函數(shù)也會自動調(diào)用父類的析構(gòu)函數(shù)盖灸。要想一個類不能被繼承蚁鳖,我們只要把它的構(gòu)造函數(shù)和析構(gòu)函 數(shù)都定義為私有函數(shù)。那么當一個類試圖從它那繼承的時候赁炎,必然會由于試圖調(diào)用構(gòu)造函數(shù)、析構(gòu)函數(shù)而導(dǎo)致編譯錯誤。
可是這個類的構(gòu)造函數(shù)和析構(gòu)函數(shù)都是私有函數(shù)了琅攘,我們怎樣才能得到該類的實例呢?這難不倒我們松邪,我們可以通過定義靜態(tài)來創(chuàng)建和釋放類的實例坞琴。
基于這個思路,我們可以寫出如下的代碼:
/// // Define a class which can't be derived from /// class FinalClass1
{
public :
static FinalClass1* GetInstance()
{
return new FinalClass1;
}
static void DeleteInstance( FinalClass1* pInstance)
{
delete pInstance;
pInstance = 0;
}
private :
FinalClass1() {}
~FinalClass1() {}
};
這個類是不能被繼承逗抑,但在總覺得它和一般的類有些不一樣剧辐,使用起來也有點不方便。比如邮府,我們只能得到位于堆上的實例荧关,而得不到位于棧上實例。能不能實現(xiàn)一個和一般類除了不能被繼承之外其他用法都一樣的類呢褂傀?辦法總是有的忍啤,不過需要一些技巧。請看如下代碼:
/// // Define a class which can't be derived from /// template <typename T> class MakeFinal
{
friend T;
private :
MakeFinal() {}
~MakeFinal() {}
};
class FinalClass2 :
virtual public MakeFinal<FinalClass2>
{
public :
FinalClass2() {}
~FinalClass2() {}
};
這個類使用起來和一般的類沒有區(qū)別仙辟,可以在棧上同波、也可以在堆上創(chuàng)建實例。盡管類 MakeFinal <FinalClass2>
的構(gòu)造函數(shù)和析構(gòu)函數(shù)都是私有的叠国,但由于類 FinalClass2 是它的友元函數(shù)未檩,因此在 FinalClass2 中調(diào)用 MakeFinal <FinalClass2>
的構(gòu)造函數(shù)和析構(gòu)函數(shù)都不會造成編譯錯誤。但當我們試圖從 FinalClass2 繼承一個類并創(chuàng)建它的實例時粟焊,卻不同通過編譯冤狡。
class Try : public FinalClass2
{
public :
Try() {}
~Try() {}
}; Try temp;
由于類 FinalClass2 是從類 MakeFinal <FinalClass2>
虛繼承過來的,在調(diào)用 Try 的構(gòu)造函數(shù)的時候项棠,會直接跳過 FinalClass2 而直接調(diào)用 MakeFinal <FinalClass2>
的構(gòu)造函數(shù)悲雳。非常遺憾的是Try 不是 MakeFinal <FinalClass2>
的友元,因此不能調(diào)用其私有的構(gòu)造函數(shù)沾乘。
基于上面的分析怜奖,試圖從 FinalClass2 繼承的類,一旦實例化翅阵,都會導(dǎo)致編譯錯誤歪玲,因此是 FinalClass2 不能被繼承。這就滿足了我們設(shè)計要求掷匠。
構(gòu)造函數(shù)沒有返回值滥崩,那么如何得知對象是否構(gòu)造成功?
這里的“構(gòu)造”不單指分配對象本身的內(nèi)存讹语,而是指在建立對象時做的初始化操作(如打開文件钙皮、連接數(shù)據(jù)庫等)。
因為構(gòu)造函數(shù)沒有返回值,所以通知對象的構(gòu)造失敗的唯一方法就是在構(gòu)造函數(shù)中拋出異常短条。構(gòu)造函數(shù)中拋出異常將導(dǎo)致對象的析構(gòu)函數(shù)不被執(zhí)行导匣,當對象發(fā)生部分構(gòu)造時,已經(jīng)構(gòu)造完畢的子對象將會逆序地被析構(gòu)茸时。
Public繼承贡定、protected繼承、private繼承的區(qū)別可都?
public(公有)繼承缓待、 protected(保護)繼承和 private(私有)繼承是常見的3種繼承方式。
- 公有繼承
對于子類的對象而言渠牲,采用公有繼承時旋炒,基類成員對子類對象的可見性與一般類成員對對象的可見性相同,公有成員可見签杈,其他成員不可見瘫镇。
對于子類而言,基類的公有成員和保護成員可見芹壕;基類的公有成員和保護成員作為派生類的成員時汇四,它們都維持原有的可見性(基類 public成員在子類中還是public,基類 protected成員在子類中還是 protected)踢涌;基類的私有成員不可見通孽,基類的私有成員依然是私有的,子類不可訪問睁壁。
- 保護繼承
保護繼承的特點是:基類的所有公有成員和保護成員都成為派生類的保護成員背苦,并且只能被它的派生類成員函數(shù)或友元訪問∨嗣鳎基類的私有成員仍然是私有的行剂。由此可以看出,基類的所有成員對子類的對象都是不可見的钳降。
- 私有繼承
私有繼承的特點是厚宰,基類的公有成員和保護成員都作為派生類的私有成員,并且不能被這個派生類的子類所訪問遂填。
C++提供默認參數(shù)的函數(shù)嗎铲觉?
C++可以給函數(shù)定義默認參數(shù)值。在函數(shù)調(diào)用時沒有指定與形參相對應(yīng)的實參時吓坚,就自動使用默認參數(shù)撵幽。
默認參數(shù)的語法與使用:
(1) 在函數(shù)聲明或定義時,直接對參數(shù)賦值礁击,這就是默認參數(shù)盐杂。
(2) 在函數(shù)調(diào)用時逗载,省略部分或全部參數(shù)。這時可以用默認參數(shù)來代替链烈。
通常調(diào)用函數(shù)時厉斟,要為函數(shù)的每個參數(shù)給定對應(yīng)的實參。例如:
void delay(int loops=1000);//函數(shù)聲明
void delay(int loops) //函數(shù)定義
{
if(loops==0)
{
return;
}
for(int i=0;i<loops;i++)
;
}
在上例中测垛,如果將delay()函數(shù)中的loops定義成默認值1000捏膨,這樣,以后無論何時調(diào)用delay()函數(shù)食侮,都不用給loops賦值,程序都會自動將它當做值 1000進行處理目胡。例如锯七,當執(zhí)行delay(2500)調(diào)用時,loops的參數(shù)值為顯性化的誉己,被設(shè)置為 2500;當執(zhí)行delay()時眉尸,loops將采用默認值1000。
默認參數(shù)在函數(shù)聲明中提供巨双,當有聲明又有定義時噪猾,定義中不允許默認參數(shù)。如果函數(shù)只有定義筑累,則默認參數(shù)才可出現(xiàn)在函數(shù)定義中袱蜡。例如:
oid point(int=3,int=4);//聲明中給出默認值
void point(int x慢宗,int y) //定義中不允許再給出默認值
{
cout<<x<<endl;
cout<<y<<endl;
}
如果一組重載函數(shù)(可能帶有默認參數(shù))都允許相同實參個數(shù)的調(diào)用坪蚁,將會引起調(diào)用的二義性。例如:
void func(int);//重載函數(shù)之一
void func(int镜沽,int=4);//重載函數(shù)之二敏晤,帶有默認參數(shù)
void func(int=3,int=4);//重載函數(shù)三缅茉,帶有默認參數(shù)
func(7);//錯誤:到底調(diào)用3個重載函數(shù)中的哪個嘴脾?
func(20,30);//錯誤:到底調(diào)用后面兩個重載函數(shù)的哪個?
虛函數(shù)
什么是虛函數(shù)蔬墩?
指向基類的指針在操作它的多態(tài)類對象時译打,可以根據(jù)指向的不同類對象調(diào)用其相應(yīng)的函數(shù),這個函數(shù)就是虛函數(shù)筹我。
虛函數(shù)的作用:在基類定義了虛函數(shù)后扶平,可以在派生類中對虛函數(shù)進行重新定義,并且可以通過基類指針或引用蔬蕊,在程序的運行階段動態(tài)地選擇調(diào)用基類和不同派生類中的同名函數(shù)结澄。(如果在派生類中沒有對虛函數(shù)重新定義哥谷,則它繼承其基類的虛函數(shù)。)
下面是一個虛函數(shù)的實例程序:
#include "stdafx.h"
#include<iostream>
using namespace std;
class Base
{
public:
virtual void Print()//父類虛函數(shù)
{
printf("This is Class Base!\n");
}
};
class Derived1 :public Base
{
public:
void Print()//子類1虛函數(shù)
{
printf("This is Class Derived1!\n");
}
};
class Derived2 :public Base
{
public:
void Print()//子類2虛函數(shù)
{
printf("This is Class Derived2!\n");
}
};
int main()
{
Base Cbase;
Derived1 Cderived1;
Derived2 Cderived2;
Cbase.Print();
Cderived1.Print();
Cderived2.Print();
cout << "---------------" << endl;
Base *p1 = &Cbase;
Base *p2 = &Cderived1;
Base *p3 = &Cderived2;
p1->Print();
p2->Print();
p3->Print();
}
/*
輸出結(jié)果:
This is Class Base!
This is Class Derived1!
This is Class Derived2!
---------------
This is Class Base!
This is Class Derived1!
This is Class Derived2!
*/
需要注意的是麻献,虛函數(shù)雖然非常好用们妥,但是在使用虛函數(shù)時,并非所有的函數(shù)都需要定義成虛函數(shù)勉吻,因為實現(xiàn)虛函數(shù)是有代價的监婶。在使用虛函數(shù)時,需要注意以下幾個方面的內(nèi)容:
(1) 只需要在聲明函數(shù)的類體中使用關(guān)鍵字virtual將函數(shù)聲明為虛函數(shù)齿桃,而定義函數(shù)時不需要使用關(guān)鍵字virtual惑惶。
(2) 當將基類中的某一成員函數(shù)聲明為虛函數(shù)后,派生類中的同名函數(shù)自動成為虛函數(shù)短纵。
(3) 非類的成員函數(shù)不能定義為虛函數(shù)带污,全局函數(shù)以及類的成員函數(shù)中靜態(tài)成員函數(shù)和構(gòu)造函數(shù)也不能定義為虛函數(shù),但可以將析構(gòu)函數(shù)定義為虛函數(shù)香到。
(4) 基類的析構(gòu)函數(shù)應(yīng)該定義為虛函數(shù)鱼冀,否則會造成內(nèi)存泄漏∮凭停基類析構(gòu)函數(shù)未聲明virtual千绪,基類指針指向派生類時,delete指針不調(diào)用派生類析構(gòu)函數(shù)梗脾。有 virtual荸型,則先調(diào)用派生類析構(gòu)再調(diào)用基類析構(gòu)。
C++如何實現(xiàn)多態(tài)藐唠?
C++中通過虛函數(shù)實現(xiàn)多態(tài)帆疟。虛函數(shù)的本質(zhì)就是通過基類指針訪問派生類定義的函數(shù)。每個含有虛函數(shù)的類宇立,其實例對象內(nèi)部都有一個虛函數(shù)表指針踪宠。該虛函數(shù)表指針被初始化為本類的虛函數(shù)表的內(nèi)存地址。所以妈嘹,在程序中柳琢,不管對象類型如何轉(zhuǎn)換,該對象內(nèi)部的虛函數(shù)表指針都是固定的润脸,這樣才能實現(xiàn)動態(tài)地對對象函數(shù)進行調(diào)用柬脸,這就是C++多態(tài)性的原理。
純虛函數(shù)指的是什么毙驯?
純虛函數(shù)是一種特殊的虛函數(shù)倒堕,格式一般如下
class <類名>
{
virtual()函數(shù)返回值類型 虛函數(shù)名(形參表)=0;
...
};
class <類名>
由于在很多情況下,基類中不能對虛函數(shù)給出有意義的實現(xiàn)爆价,只能把函數(shù)的實現(xiàn)留給派生類垦巴。例如媳搪,動物作為一個基類可以派生出老虎、孔雀等子類骤宣,但是動物本身生成對象不合情理秦爆,此時就可以將動物類中的函數(shù)定義為純虛函數(shù),如果基類中有純虛函數(shù)憔披,那么在子類中必須實現(xiàn)這個純虛函數(shù)等限,否則子類將無法被實例化,也無法實現(xiàn)多態(tài)芬膝。
含有純虛函數(shù)的類稱為抽象類望门,抽象類不能生成對象。純虛函數(shù)永遠不會被調(diào)用锰霜,它們主要用來統(tǒng)一管理子類對象怒允。
什么函數(shù)不能聲明為虛函數(shù)?
常見的不能聲明為虛函數(shù)的有:普通函數(shù)(非成員函數(shù))锈遥;靜態(tài)成員函數(shù);內(nèi)聯(lián)成員函數(shù)勘畔;構(gòu)造函數(shù)所灸;友元函數(shù)。
1.為什么C++不支持普通函數(shù)為虛函數(shù)炫七?
普通函數(shù)(非成員函數(shù))只能被overload爬立,不能被override,聲明為虛函數(shù)也沒有什么意思万哪,因此編譯器會在編譯時邦定函數(shù)侠驯。
2.為什么C++不支持構(gòu)造函數(shù)為虛函數(shù)?
這個原因很簡單奕巍,主要是從語義上考慮吟策,所以不支持。因為構(gòu)造函數(shù)本來就是為了明確初始化對象成員才產(chǎn)生的请琳,然而virtual function主要是為了再不完全了解細節(jié)的情況下也能正確處理對象花鹅。另外肌括,virtual函數(shù)是在不同類型的對象產(chǎn)生不同的動作,現(xiàn)在對象還沒有產(chǎn)生匾委,如何使用virtual函數(shù)來完成你想完成的動作。(這不就是典型的悖論)
3.為什么C++不支持內(nèi)聯(lián)成員函數(shù)為虛函數(shù)氓润?
其實很簡單赂乐,那內(nèi)聯(lián)函數(shù)就是為了在代碼中直接展開,減少函數(shù)調(diào)用花費的代價咖气,虛函數(shù)是為了在繼承后對象能夠準確的執(zhí)行自己的動作挨措,這是不可能統(tǒng)一的挖滤。(再說了,inline函數(shù)在編譯時被展開运嗜,虛函數(shù)在運行時才能動態(tài)的邦定函數(shù))
4.為什么C++不支持靜態(tài)成員函數(shù)為虛函數(shù)壶辜?
這也很簡單,靜態(tài)成員函數(shù)對于每個類來說只有一份代碼担租,所有的對象都共享這一份代碼砸民,他也沒有要動態(tài)邦定的必要性。
5.為什么C++不支持友元函數(shù)為虛函數(shù)奋救?
因為C++不支持友元函數(shù)的繼承岭参,對于沒有繼承特性的函數(shù)沒有虛函數(shù)的說法。
C++中如何阻止一個類被實例化尝艘?
C++中可以通過使用抽象類演侯,或者將構(gòu)造函數(shù)聲明為private阻止一個類被實例化。抽象類之所以不能被實例化背亥,是因為抽象類不能代表一類具體的事物秒际,它是對多種具有相似性的具體事物的共同特征的一種抽象。例如狡汉,動物作為一個基類可以派生出老虎娄徊、孔雀等子類,但是動物本身生成對象不合情理盾戴。
結(jié)語
如果大家在網(wǎng)上看到了不錯的資料寄锐,或者在筆試面試中遇到了資料中沒有的知識點,大家可以聯(lián)系我尖啡,我替大家整理橄仆。資料如有錯誤或者不合適的地方,請及時聯(lián)系作者衅斩。
這些內(nèi)容都是我熬夜整理的盆顾,最近還在修改大論文,事情也挺多的矛渴。創(chuàng)作不易椎扬,大家不要忘了點擊「贊」支持下,也算沒有白白熬夜具温,對得起我掉的一根根頭發(fā)蚕涤。
最后,再放下github鏈接(https://github.com/ZhongYi-LinuxDriverDev/EmbeddedSoftwareEngineerInterview9)铣猩,不要忘了點個star揖铜!