1. 結(jié)構(gòu)體和共同體的區(qū)別。
定義:
結(jié)構(gòu)體struct:把不同類型的數(shù)據(jù)組合成一個(gè)整體,自定義類型。
共同體union:使幾個(gè)不同類型的變量共同占用一段內(nèi)存。
地址:
struct和union都有內(nèi)存對(duì)齊,結(jié)構(gòu)體的內(nèi)存布局依賴于CPU、操作系統(tǒng)、編譯器及編譯時(shí)的對(duì)齊選項(xiàng)。
關(guān)于內(nèi)存對(duì)齊,先讓我們看四個(gè)重要的基本概念:1.數(shù)據(jù)類型自身的對(duì)齊值:對(duì)于char型數(shù)據(jù),其自身對(duì)齊值為1,對(duì)于short型為2,對(duì)于int,float,double類型,其自身對(duì)齊值為4,單位字節(jié)。2.結(jié)構(gòu)體或者類的自身對(duì)齊值:其成員中自身對(duì)齊值最大的那個(gè)值。3.指定對(duì)齊值:#pragma pack(n),n=1,2,4,8,16改變系統(tǒng)的對(duì)齊系數(shù)4.數(shù)據(jù)成員、結(jié)構(gòu)體和類的有效對(duì)齊值:自身對(duì)齊值和指定對(duì)齊值中小的那個(gè)值。
常見數(shù)據(jù)類型及其長(zhǎng)度:
注意long int和int一樣是4byte,long double和double一樣是8byte。
在標(biāo)準(zhǔn)c++中,int的定義長(zhǎng)度要依靠你的機(jī)器的字長(zhǎng),也就是說,如果你的機(jī)器是32位的,int的長(zhǎng)度為32位,如果你的機(jī)器是64位的,那么int的標(biāo)準(zhǔn)長(zhǎng)度就是64位。
從上面的一段文字中,我們可以看出,首先根據(jù)結(jié)構(gòu)體內(nèi)部成員的自身對(duì)齊值得到結(jié)構(gòu)體的自身對(duì)齊值(內(nèi)部成員最大的長(zhǎng)度),如果沒有修改系統(tǒng)設(shè)定的默認(rèn)補(bǔ)齊長(zhǎng)度4的話,取較小的進(jìn)行內(nèi)存補(bǔ)齊。
結(jié)構(gòu)體struct:不同之處,stuct里每個(gè)成員都有自己獨(dú)立的地址。sizeof(struct)是內(nèi)存對(duì)齊后所有成員長(zhǎng)度的加和。
共同體union:當(dāng)共同體中存入新的數(shù)據(jù)后,原有的成員就失去了作用,新的數(shù)據(jù)被寫到union的地址中。sizeof(union)是最長(zhǎng)的數(shù)據(jù)成員的長(zhǎng)度。
總結(jié): struct和union都是由多個(gè)不同的數(shù)據(jù)類型成員組成, 但在任何同一時(shí)刻, union中只存放了一個(gè)被選中的成員, 而struct的所有成員都存在。在struct中,各成員都占有自己的內(nèi)存空間,它們是同時(shí)存在的。一個(gè)struct變量的總長(zhǎng)度等于所有成員長(zhǎng)度之和。在Union中,所有成員不能同時(shí)占用它的內(nèi)存空間,它們不能同時(shí)存在。Union變量的長(zhǎng)度等于最長(zhǎng)的成員的長(zhǎng)度。對(duì)于union的不同成員賦值, 將會(huì)對(duì)其它成員重寫, 原來成員的值就不存在了, 而對(duì)于struct的不同成員賦值是互不影響的。
** 2.static 和const分別怎么用,類里面static和const可以同時(shí)修飾成員函數(shù)嗎。**
static的作用:
對(duì)變量:
1.局部變量:
在局部變量之前加上關(guān)鍵字static,局部變量就被定義成為一個(gè)局部靜態(tài)變量。
1)內(nèi)存中的位置:靜態(tài)存儲(chǔ)區(qū)
2)初始化:未經(jīng)初始化的全局靜態(tài)變量會(huì)被程序自動(dòng)初始化為0(自動(dòng)對(duì)象的值是任意的,除非他被顯示初始化)
3)作用域:作用域仍為局部作用域,當(dāng)定義它的函數(shù)或者語句塊結(jié)束的時(shí)候,作用域隨之結(jié)束。
注:當(dāng)static用來修飾局部變量的時(shí)候,它就改變了局部變量的存儲(chǔ)位置(從原來的棧中存放改為靜態(tài)存儲(chǔ)區(qū))及其生命周期(局部靜態(tài)變量在離開作用域之后,并沒有被銷毀,而是仍然駐留在內(nèi)存當(dāng)中,直到程序結(jié)束,只不過我們不能再對(duì)他進(jìn)行訪問),但未改變其作用域。
2.全局變量
在全局變量之前加上關(guān)鍵字static,全局變量就被定義成為一個(gè)全局靜態(tài)變量。
1)內(nèi)存中的位置:靜態(tài)存儲(chǔ)區(qū)(靜態(tài)存儲(chǔ)區(qū)在整個(gè)程序運(yùn)行期間都存在)
2)初始化:未經(jīng)初始化的全局靜態(tài)變量會(huì)被程序自動(dòng)初始化為0(自動(dòng)對(duì)象的值是任意的,除非他被顯示初始化)
3)作用域:全局靜態(tài)變量在聲明他的文件之外是不可見的。準(zhǔn)確地講從定義之處開始到文件結(jié)尾。
注:static修飾全局變量,并為改變其存儲(chǔ)位置及生命周期,而是改變了其作用域,使當(dāng)前文件外的源文件無法訪問該變量,好處如下:(1)不會(huì)被其他文件所訪問,修改(2)其他文件中可以使用相同名字的變量,不會(huì)發(fā)生沖突。對(duì)全局函數(shù)也是有隱藏作用。
對(duì)類中的
1.成員變量
用static修飾類的數(shù)據(jù)成員實(shí)際使其成為類的全局變量,會(huì)被類的所有對(duì)象共享,包括派生類的對(duì)象。因此,static成員必須在類外進(jìn)行初始化(初始化格式: int base::var=10;),而不能在構(gòu)造函數(shù)內(nèi)進(jìn)行初始化,不過也可以用const修飾static數(shù)據(jù)成員在類內(nèi)初始化 。
特點(diǎn):
不要試圖在頭文件中定義(初始化)靜態(tài)數(shù)據(jù)成員。在大多數(shù)的情況下,這樣做會(huì)引起重復(fù)定義這樣的錯(cuò)誤。即使加上#ifndef #define #endif或者#pragma once也不行。
靜態(tài)數(shù)據(jù)成員可以成為成員函數(shù)的可選參數(shù),而普通數(shù)據(jù)成員則不可以。
靜態(tài)數(shù)據(jù)成員的類型可以是所屬類的類型,而普通數(shù)據(jù)成員則不可以。普通數(shù)據(jù)成員的只能聲明為 所屬類類型的指針或引用。
2.成員函數(shù)
用static修飾成員函數(shù),使這個(gè)類只存在這一份函數(shù),所有對(duì)象共享該函數(shù),不含this指針。
靜態(tài)成員是可以獨(dú)立訪問的,也就是說,無須創(chuàng)建任何對(duì)象實(shí)例就可以訪問。base::func(5,3);當(dāng)static成員函數(shù)在類外定義時(shí)不需要加static修飾符。
在靜態(tài)成員函數(shù)的實(shí)現(xiàn)中不能直接引用類中說明的非靜態(tài)成員,可以引用類中說明的靜態(tài)成員。因?yàn)殪o態(tài)成員函數(shù)不含this指針。
不可以同時(shí)用const和static修飾成員函數(shù)。
C++編譯器在實(shí)現(xiàn)const的成員函數(shù)的時(shí)候?yàn)榱舜_保該函數(shù)不能修改類的實(shí)例的狀態(tài),會(huì)在函數(shù)中添加一個(gè)隱式的參數(shù)const this。但當(dāng)一個(gè)成員為static的時(shí)候,該函數(shù)是沒有this指針的。也就是說此時(shí)const的用法和static是沖突的。
我們也可以這樣理解:兩者的語意是矛盾的。static的作用是表示該函數(shù)只作用在類型的靜態(tài)變量上,與類的實(shí)例沒有關(guān)系;而const的作用是確保函數(shù)不能修改類的實(shí)例的狀態(tài)*,與類型的靜態(tài)變量沒有關(guān)系。因此不能同時(shí)用它們。
const的作用:
1.限定變量為不可修改。
2.限定成員函數(shù)不可以修改任何數(shù)據(jù)成員。
3.const與指針:
const char *p 表示 指向的內(nèi)容不能改變。
char * const p,就是將P聲明為常指針,它的地址不能改變,是固定的,但是它的內(nèi)容可以改變。
** 3.指針和引用的區(qū)別,引用可以用常指針實(shí)現(xiàn)嗎。**
本質(zhì)上的區(qū)別是,指針是一個(gè)新的變量,只是這個(gè)變量存儲(chǔ)的是另一個(gè)變量的地址,我們通過訪問這個(gè)地址來修改變量。
而引用只是一個(gè)別名,還是變量本身。對(duì)引用進(jìn)行的任何操作就是對(duì)變量本身進(jìn)行操作,因此以達(dá)到修改變量的目的。
[圖片上傳中。。。(2)]
(1)指針:指針是一個(gè)變量,只不過這個(gè)變量存儲(chǔ)的是一個(gè)地址,指向內(nèi)存的一個(gè)存儲(chǔ)單元;而引用跟原來的變量實(shí)質(zhì)上是同一個(gè)東西,只不過是原變量的一個(gè)別名而已。如:int a=1;int p=&a;int a=1;int &b=a;上面定義了一個(gè)整形變量和一個(gè)指針變量p,該指針變量指向a的存儲(chǔ)單元,即p的值是a存儲(chǔ)單元的地址。而下面2句定義了一個(gè)整形變量a和這個(gè)整形a的引用b,事實(shí)上a和b是同一個(gè)東西,在內(nèi)存占有同一個(gè)存儲(chǔ)單元。(2)可以有const指針,但是沒有const引用;(3)指針可以有多級(jí),但是引用只能是一級(jí)(int p;合法 而 int &&a是不合法的)(4)指針的值可以為空,但是引用的值不能為NULL,并且引用在定義的時(shí)候必須初始化*;(5)指針的值在初始化后可以改變,即指向其它的存儲(chǔ)單元,而引用在進(jìn)行初始化后就不會(huì)再改變了。(6)"sizeof引用"得到的是所指向的變量(對(duì)象)的大小,而"sizeof指針"得到的是指針本身的大小;(7)指針和引用的自增(++)運(yùn)算意義不一樣;
[圖片上傳中。。。(3)]
指針傳參的時(shí)候,還是值傳遞,試圖修改傳進(jìn)來的指針的值是不可以的。只能修改地址所保存變量的值。引用傳參的時(shí)候,傳進(jìn)來的就是變量本身,因此可以被修改。
4.什么是多態(tài),多態(tài)有什么用途。
定義:“一個(gè)接口,多種方法”,程序在運(yùn)行時(shí)才決定調(diào)用的函數(shù)。
實(shí)現(xiàn):C++多態(tài)性主要是通過虛函數(shù)實(shí)現(xiàn)的,虛函數(shù)允許子類重寫override(注意和overload的區(qū)別,overload是重載,是允許同名函數(shù)的表現(xiàn),這些函數(shù)參數(shù)列表/類型不同)。
多態(tài)與非多態(tài)的實(shí)質(zhì)區(qū)別就是函數(shù)地址是早綁定還是晚綁定。如果函數(shù)的調(diào)用,在編譯器編譯期間就可以確定函數(shù)的調(diào)用地址,并生產(chǎn)代碼,是靜態(tài)的,就是說地址是早綁定的。而如果函數(shù)調(diào)用的地址不能在編譯器期間確定,需要在運(yùn)行時(shí)才確定,這就屬于晚綁定。
3.目的:接口重用。封裝可以使得代碼模塊化,繼承可以擴(kuò)展已存在的代碼,他們的目的都是為了代碼重用。而多態(tài)的目的則是為了接口重用。
4.用法:聲明基類的指針,利用該指針指向任意一個(gè)子類對(duì)象,調(diào)用相應(yīng)的虛函數(shù),可以根據(jù)指向的子類的不同而實(shí)現(xiàn)不同的方法。
補(bǔ)充一下關(guān)于重載、重寫、隱藏(總是不記得)的區(qū)別:
[圖片上傳中。。。(4)]
Overload(重載):在C++程序中,可以將語義、功能相似的幾個(gè)函數(shù)用同一個(gè)名字表示,但參數(shù)或返回值不同(包括類型、順序不同),即函數(shù)重載。(1)相同的范圍(在同一個(gè)類中);(2)函數(shù)名字相同;(3)參數(shù)不同;(4)virtual 關(guān)鍵字可有可無。Override(覆蓋):是指派生類函數(shù)覆蓋基類函數(shù),特征是:(1)不同的范圍(分別位于派生類與基類);(2)函數(shù)名字相同;(3)參數(shù)相同;(4)基類函數(shù)必須有virtual 關(guān)鍵字。注:重寫基類虛函數(shù)的時(shí)候,會(huì)自動(dòng)轉(zhuǎn)換這個(gè)函數(shù)為virtual函數(shù),不管有沒有加virtual,因此重寫的時(shí)候不加virtual也是可以的,不過為了易讀性,還是加上比較好。Overwrite(重寫):隱藏,是指派生類的函數(shù)屏蔽了與其同名的基類函數(shù),規(guī)則如下:(1)如果派生類的函數(shù)與基類的函數(shù)同名,但是參數(shù)不同。此時(shí),不論有無virtual關(guān)鍵字,基類的函數(shù)將被隱藏(注意別與重載混淆)。(2)如果派生類的函數(shù)與基類的函數(shù)同名,并且參數(shù)也相同,但是基類函數(shù)沒有virtual關(guān)鍵字。此時(shí),基類的函數(shù)被隱藏(注意別與覆蓋混淆)。
[圖片上傳中。。。(5)]
補(bǔ)充一下虛函數(shù)表:
多態(tài)是由虛函數(shù)實(shí)現(xiàn)的,而虛函數(shù)主要是通過虛函數(shù)表(V-Table)來實(shí)現(xiàn)的。
如果一個(gè)類中包含虛函數(shù)(virtual修飾的函數(shù)),那么這個(gè)類就會(huì)包含一張?zhí)摵瘮?shù)表,虛函數(shù)表存儲(chǔ)的每一項(xiàng)是一個(gè)虛函數(shù)的地址。如下圖:
這個(gè)類的每一個(gè)對(duì)象都會(huì)包含一個(gè)虛指針(虛指針存在于對(duì)象實(shí)例地址的最前面,保證虛函數(shù)表有最高的性能),這個(gè)虛指針指向虛函數(shù)表。
注:對(duì)象不包含虛函數(shù)表,只有虛指針,類才包含虛函數(shù)表,派生類會(huì)生成一個(gè)兼容基類的虛函數(shù)表。
原始基類的虛函數(shù)表
下圖是原始基類的對(duì)象,可以看到虛指針在地址的最前面,指向基類的虛函數(shù)表(假設(shè)基類定義了3個(gè)虛函數(shù))
單繼承時(shí)的虛函數(shù)(無重寫基類虛函數(shù))
假設(shè)現(xiàn)在派生類繼承基類,并且重新定義了3個(gè)虛函數(shù),派生類會(huì)自己產(chǎn)生一個(gè)兼容基類虛函數(shù)表的屬于自己的虛函數(shù)表。
Derive class 繼承了 Base class 中的三個(gè)虛函數(shù),準(zhǔn)確的說,是該函數(shù)實(shí)體的地址被拷貝到 Derive類的虛函數(shù)表,派生類新增的虛函數(shù)置于虛函數(shù)表的后面,并按聲明順序存放。
單繼承時(shí)的虛函數(shù)(重寫基類虛函數(shù))
現(xiàn)在派生類重寫基類的x函數(shù),可以看到這個(gè)派生類構(gòu)建自己的虛函數(shù)表的時(shí)候,修改了base::x()這一項(xiàng),指向了自己的虛函數(shù)。
多重繼承時(shí)的虛函數(shù)(Derived ::public Base1,public Base2)
這個(gè)派生類多重繼承了兩個(gè)基類base1,base2,因此它有兩個(gè)虛函數(shù)表。
[圖片上傳中。。。(10)]
它的對(duì)象會(huì)有多個(gè)虛指針(據(jù)說和編譯器相關(guān)),指向不同的虛函數(shù)表。
多重繼承時(shí)指針的調(diào)整:
Derive b;Base1* ptr1 = &b; // 指向 b 的初始地址Base2* ptr2 = &b; // 指向 b 的第二個(gè)子對(duì)象
因?yàn)?Base1 是第一個(gè)基類,所以 ptr1 指向的是 Derive 對(duì)象的起始地址,不需要調(diào)整指針(偏移)。
因?yàn)?Base2 是第二個(gè)基類,所以必須對(duì)指針進(jìn)行調(diào)整,即加上一個(gè) offset,讓 ptr2 指向 Base2 子對(duì)象。
當(dāng)然,上述過程是由編譯器完成的。
Base1* b1 = (Base1)ptr2;b1->y(); // 輸出 Base2::y()Base2 b2 = (Base2*)ptr1;b2->y(); // 輸出 Base1::y()
其實(shí),通過某個(gè)類型的指針訪問某個(gè)成員時(shí),編譯器只是根據(jù)類型的定義查找這個(gè)成員所在偏移量,用這個(gè)偏移量獲取成員。由于 ptr2 本來就指向 Base2 子對(duì)象的起始地址,所以b1->y()
調(diào)用到的是Base2::y()
,而 ptr1 本來就指向 Base1 子對(duì)象的起始地址(即 Derive對(duì)象的起始地址),所以b2->y()
調(diào)用到的是Base1::y()
。
虛繼承時(shí)的虛函數(shù)表
虛繼承的引入把對(duì)象的模型變得十分復(fù)雜,除了每個(gè)基類(MyClassA和MyClassB)和公共基類(MyClass)的虛函數(shù)表指針需要記錄外,每個(gè)虛擬繼承了MyClass的父類還需要記錄一個(gè)虛基類表vbtable的指針vbptr。MyClassC的對(duì)象模型如圖4所示。
[圖片上傳中。。。(11)]
虛基類表每項(xiàng)記錄了被繼承的虛基類子對(duì)象相對(duì)于虛基類表指針的偏移量。比如MyClassA的虛基類表第二項(xiàng)記錄值為24,正是MyClass::vfptr相對(duì)于MyClassA::vbptr的偏移量,同理MyClassB的虛基類表第二項(xiàng)記錄值12也正是MyClass::vfptr相對(duì)于MyClassA::vbptr的偏移量。(虛函數(shù)與虛繼承深入探討)
對(duì)象模型探討:
1.沒有繼承情況,vptr存放在對(duì)象的開始位置,以下是Base1的內(nèi)存布局
m_iData :100
2.單繼承的情況下,對(duì)象只有一個(gè)vptr,它存放在對(duì)象的開始位置,派生類子對(duì)象在父類子對(duì)象的最后面,以下是D1的內(nèi)存布局
B1:: m_iData : 100
B1::vptr : 4294800
B2::vptr : 4294776
D::m_iData :300
- 虛擬繼承情況下,虛父類子對(duì)象會(huì)放在派生類子對(duì)象之后,派生類子對(duì)象的第一個(gè)位置存放著一個(gè)vptr,虛擬子類子對(duì)象也會(huì)保存一個(gè)vptr,以下是VD1的內(nèi)存布局
Unknown : 4294888
B1::vptr :4294864
VD1::vptr : 4294944
VD1::m_iData : 200
VD2::Unknown : 4294952
VD::m_iData : 500
B1::m_iData : 100
- 棱形繼承的情況下,非虛基類子對(duì)象在派生類子對(duì)象前面,并按照聲明順序排列,虛基類子對(duì)象在派生類子對(duì)象后面
VD1::Unknown : 4294968
VD2::vptr : 4 294932
VD2::m_iData : 300
B1::vptr : 4294920
B1::m_iData : 100
補(bǔ)充一下純虛函數(shù):
定義: 在很多情況下,基類本身生成對(duì)象是不合情理的。為了解決這個(gè)問題,方便使用類的多態(tài)性,引入了純虛函數(shù)的概念,將函數(shù)定義為純虛函數(shù)(方法:virtual ReturnType Function()=** 0**;)純虛函數(shù)不能再在基類中實(shí)現(xiàn),編譯器要求在派生類中必須予以重寫以實(shí)現(xiàn)多態(tài)性。同時(shí)含有純虛擬函數(shù)的類稱為抽象類,它不能生成對(duì)象。
特點(diǎn):
1,當(dāng)想在基類中抽象出一個(gè)方法,且該基類只做能被繼承,而不能被實(shí)例化;(避免類被實(shí)例化且在編譯時(shí)候被發(fā)現(xiàn),可以采用此方法)
2,這個(gè)方法必須在派生類(derived class)中被實(shí)現(xiàn);
目的:使派生類僅僅只是繼承函數(shù)的接口。
補(bǔ)充一下純虛函數(shù):
定義:稱帶有純虛函數(shù)的類為抽象類。
作用:為一個(gè)繼承體系提供一個(gè)公共的根,為派生類提供操作接口的通用語義。
特點(diǎn):1.抽象類只能作為基類來使用,而繼承了抽象類的派生類如果沒有實(shí)現(xiàn)純虛函數(shù),而只是繼承純虛函數(shù),那么該類仍舊是一個(gè)抽象類,如果實(shí)現(xiàn)了純虛函數(shù),就不再是抽象類。
2.抽象類不可以定義對(duì)象。
補(bǔ)充一下多重繼承和虛繼承:
多重繼承:
定義:派生類繼承多個(gè)基類,派生類為每個(gè)基類(顯式或隱式地)指定了訪問級(jí)別——public
、protected
或 private
。
class Panda : public Bear, public Endangered { }
構(gòu)造:
派生類的對(duì)象包含每個(gè)基類的基類子對(duì)象。
派生類構(gòu)造函數(shù)初始化所有基類(多重繼承中若沒有顯式調(diào)用某個(gè)基類的構(gòu)造函數(shù),則編譯器會(huì)調(diào)用該基類默認(rèn)構(gòu)造函數(shù)),派生類只能初始化自己的基類,并不需要考慮基類的基類怎么初始化。
多重繼承時(shí),基類構(gòu)造函數(shù)按照基類構(gòu)造函數(shù)在類派生列表中的出現(xiàn)次序調(diào)用。
析構(gòu):總是按構(gòu)造函數(shù)運(yùn)行的逆序調(diào)用析構(gòu)函數(shù)。(基類的析構(gòu)函數(shù)最好寫成virtual,否則再子類對(duì)象銷毀的時(shí)候,無法銷毀子類對(duì)象部分資源。)假定所有根基類都將它們的析構(gòu)函數(shù)適當(dāng)定義為虛函數(shù),那么,無論通過哪種指針類型刪除對(duì)象,虛析構(gòu)函數(shù)的處理都是一致的。
拷貝構(gòu)造/賦值:如果要為派生類編寫拷貝構(gòu)造函數(shù),則需要為調(diào)用基類相應(yīng)拷貝構(gòu)造函數(shù)并為其傳遞參數(shù),否則只會(huì)拷貝派生類部分。
深拷貝與淺拷貝:淺拷貝:默認(rèn)的復(fù)制構(gòu)造函數(shù)只是完成了對(duì)象之間的位拷貝,也就是把對(duì)象里的值完全復(fù)制給另一個(gè)對(duì)象,如A=B。這時(shí),如果B中有一個(gè)成員變量指針已經(jīng)申請(qǐng)了內(nèi)存,那A中的那個(gè)成員變量也指向同一塊內(nèi)存。 這就出現(xiàn)了問題:當(dāng)B把內(nèi)存釋放了(如:析構(gòu)),這時(shí)A內(nèi)的指針就是野指針了,出現(xiàn)運(yùn)行錯(cuò)誤。深拷貝:自定義復(fù)制構(gòu)造函數(shù)需要注意,對(duì)象之間發(fā)生復(fù)制,資源重新分配,即A有5個(gè)空間,B也應(yīng)該有5個(gè)空間,而不是指向A的5個(gè)空間。
虛繼承與虛基類:
定義:在多重繼承下,一個(gè)基類可以在派生層次中出現(xiàn)多次。(派生類對(duì)象中可能出現(xiàn)多個(gè)基類對(duì)象)在 C++ 中,通過使用虛繼承解決這類問題。虛繼承是一種機(jī)制,類通過虛繼承指出它希望共享其虛基類的狀態(tài)。在虛繼承下,對(duì)給定虛基類,無論該類在派生層次中作為虛基類出現(xiàn)多少次,只繼承一個(gè)共享的基類子對(duì)象。共享的基類子對(duì)象稱為虛基類。
用法:istream
和 ostream
類對(duì)它們的基類進(jìn)行虛繼承。通過使基類成為虛基類,istream
和 ostream
指定,如果其他類(如 iostream
同時(shí)繼承它們兩個(gè),則派生類中只出現(xiàn)它們的公共基類ios的一個(gè)副本。通過在派生列表中包含關(guān)鍵字 virtual
設(shè)置虛基類:** class istream : public virtual ios { ... }; class ostream : virtual public ios { ... }; // iostream inherits only one copy of its ios base class class iostream: public istream, public ostream { ... };**
5.各個(gè)排序算法的時(shí)間復(fù)雜度和穩(wěn)定性,快排的原理。
[圖片上傳中。。。(12)]排序深入探討
插入排序
每次將一個(gè)待排序的數(shù)據(jù),跟前面已經(jīng)有序的序列的數(shù)字一一比較找到自己合適的位置,插入到序列中,直到全部數(shù)據(jù)插入完成。
希爾排序
先將整個(gè)待排元素序列分割成若干個(gè)子序列(由相隔某個(gè)“增量”的元素組成的)分別進(jìn)行直接插入排序,然后依次縮減增量再進(jìn)行排序,待整個(gè)序列中的元素基本有序(增量足夠小)時(shí),再對(duì)全體元素進(jìn)行一次直接插入排序。由于希爾排序是對(duì)相隔若干距離的數(shù)據(jù)進(jìn)行直接插入排序,因此可以形象的稱希爾排序?yàn)椤?strong>跳著插”
冒泡排序
通過交換使相鄰的兩個(gè)數(shù)變成小數(shù)在前大數(shù)在后,這樣每次遍歷后,最大的數(shù)就“沉”到最后面了。重復(fù)N次即可以使數(shù)組有序。
冒泡排序改進(jìn)1:在某次遍歷中如果沒有數(shù)據(jù)交換,說明整個(gè)數(shù)組已經(jīng)有序。因此通過設(shè)置標(biāo)志位來記錄此次遍歷有無數(shù)據(jù)交換就可以判斷是否要繼續(xù)循環(huán)。
冒泡排序改進(jìn)2:記錄某次遍歷時(shí)最后發(fā)生數(shù)據(jù)交換的位置,這個(gè)位置之后的數(shù)據(jù)顯然已經(jīng)有序了。因此通過記錄最后發(fā)生數(shù)據(jù)交換的位置就可以確定下次循環(huán)的范圍了。
快速排序
“挖坑填數(shù)+分治法”,首先令i =L; j = R; 將a[i]挖出形成第一個(gè)坑,稱a[i]為基準(zhǔn)數(shù)。然后j--由后向前找比基準(zhǔn)數(shù)小的數(shù),找到后挖出此數(shù)填入前一個(gè)坑a[i]中,再i++由前向后找比基準(zhǔn)數(shù)大的數(shù),找到后也挖出此數(shù)填到前一個(gè)坑a[j]中。重復(fù)進(jìn)行這種“挖坑填數(shù)”直到i==j。再將基準(zhǔn)數(shù)填入a[i]中,這樣i之前的數(shù)都比基準(zhǔn)數(shù)小,i之后的數(shù)都比基準(zhǔn)數(shù)大。因此將數(shù)組分成二部分再分別重復(fù)上述步驟就完成了排序。
選擇排序
數(shù)組分成有序區(qū)和無序區(qū),初始時(shí)整個(gè)數(shù)組都是無序區(qū),然后每次從無序區(qū)選一個(gè)最小的元素直接放到有序區(qū)的最后,直到整個(gè)數(shù)組變有序區(qū)。
堆排序
[圖片上傳中。。。(13)]
堆的插入就是——每次插入都是將新數(shù)據(jù)放在數(shù)組最后,而從這個(gè)新數(shù)據(jù)的父結(jié)點(diǎn)到根結(jié)點(diǎn)必定是一個(gè)有序的數(shù)列,因此只要將這個(gè)新數(shù)據(jù)插入到這個(gè)有序數(shù)列中即可。
堆的刪除就是——堆的刪除就是將最后一個(gè)數(shù)據(jù)的值賦給根結(jié)點(diǎn),然后再從根結(jié)點(diǎn)開始進(jìn)行一次從上向下的調(diào)整。調(diào)整時(shí)先在左右兒子結(jié)點(diǎn)中找最小的,如果父結(jié)點(diǎn)比這個(gè)最小的子結(jié)點(diǎn)還小說明不需要調(diào)整了,反之將父結(jié)點(diǎn)和它交換后再考慮后面的結(jié)點(diǎn)。相當(dāng)于從根結(jié)點(diǎn)開始將一個(gè)數(shù)據(jù)在有序數(shù)列中進(jìn)行“下沉”。
因此,堆的插入和刪除非常類似直接插入排序,只不是在二叉樹上進(jìn)行插入過程。所以可以將堆排序形容為“樹上插”
歸并排序
歸并排序主要分為兩步:分?jǐn)?shù)列(divide),每次把數(shù)列一分為二,然后分到只有兩個(gè)元素的小數(shù)列;合數(shù)列(Merge),合并兩個(gè)已經(jīng)內(nèi)部有序的子序列,直至所有數(shù)字有序。用遞歸可以實(shí)現(xiàn)。
基數(shù)排序(桶排序)
基數(shù)排序,第一步根據(jù)數(shù)字的個(gè)位分配到每個(gè)桶里,在桶內(nèi)部排序,然后將數(shù)字再輸出(串起來);然后根據(jù)十位分桶,繼續(xù)排序,再串起來。直至所有位被比較完,所有數(shù)字已經(jīng)有序。
6.vector中size()和capacity()的區(qū)別。
size()指容器當(dāng)前擁有的元素個(gè)數(shù)(對(duì)應(yīng)的resize(size_type)會(huì)在容器尾添加或刪除一些元素,來調(diào)整容器中實(shí)際的內(nèi)容,使容器達(dá)到指定的大小。);capacity()指容器在必須分配存儲(chǔ)空間之前可以存儲(chǔ)的元素總數(shù)。
size表示的這個(gè)vector里容納了多少個(gè)元素,capacity表示vector能夠容納多少元素,它們的不同是在于vector的size是2倍增長(zhǎng)的。如果vector的大小不夠了,比如現(xiàn)在的capacity是4,插入到第五個(gè)元素的時(shí)候,發(fā)現(xiàn)不夠了,此時(shí)會(huì)給他重新分配8個(gè)空間,把原來的數(shù)據(jù)及新的數(shù)據(jù)復(fù)制到這個(gè)新分配的空間里。(會(huì)有迭代器失效的問題)
各容器的特點(diǎn):
[圖片上傳中。。。(15)]
7.map和set的原理。
(map和set的四個(gè)問題)
map和set的底層實(shí)現(xiàn)主要是由紅黑樹實(shí)現(xiàn)的。
紅黑樹:
性質(zhì)1 節(jié)點(diǎn)是紅色或黑色。
性質(zhì)2 根節(jié)點(diǎn)是黑色。
性質(zhì)3 每個(gè)葉節(jié)點(diǎn)(NIL節(jié)點(diǎn),空節(jié)點(diǎn))是黑色的。
性質(zhì)4 每個(gè)紅色節(jié)點(diǎn)的兩個(gè)子節(jié)點(diǎn)都是黑色。(從每個(gè)葉子到根的所有路徑上不能有兩個(gè)連續(xù)的紅色節(jié)點(diǎn))
性質(zhì)5 從任一節(jié)點(diǎn)到其每個(gè)葉子的所有路徑都包含相同數(shù)目的黑色節(jié)點(diǎn)。
這些約束的好處是:保持了樹的相對(duì)平衡,同時(shí)又比AVL的插入刪除操作的復(fù)雜性要低許多。
(深入探討紅黑樹)
8.tcp為什么要三次握手,tcp為什么可靠。
為什么不能兩次握手:(防止已失效的連接請(qǐng)求又傳送到服務(wù)器端,因而產(chǎn)生錯(cuò)誤)
假設(shè)改為兩次握手,client端發(fā)送的一個(gè)連接請(qǐng)求在服務(wù)器滯留了,這個(gè)連接請(qǐng)求是無效的,client已經(jīng)是closed的狀態(tài)了,而服務(wù)器認(rèn)為client想要建立
一個(gè)新的連接,于是向client發(fā)送確認(rèn)報(bào)文段,而client端是closed狀態(tài),無論收到什么報(bào)文都會(huì)丟棄。而如果是兩次握手的話,此時(shí)就已經(jīng)建立連接了。
服務(wù)器此時(shí)會(huì)一直等到client端發(fā)來數(shù)據(jù),這樣就浪費(fèi)掉很多server端的資源。
(校注:此時(shí)因?yàn)閏lient沒有發(fā)起建立連接請(qǐng)求,所以client處于CLOSED狀態(tài),接受到任何包都會(huì)丟棄,謝希仁舉的例子就是這種場(chǎng)景。但是如果服務(wù)器發(fā)送對(duì)這個(gè)延誤的舊連接報(bào)文的確認(rèn)的同時(shí),客戶端調(diào)用connect函數(shù)發(fā)起了連接,就會(huì)使客戶端進(jìn)入SYN_SEND狀態(tài),當(dāng)服務(wù)器那個(gè)對(duì)延誤舊連接報(bào)文的確認(rèn)傳到客戶端時(shí),因?yàn)榭蛻舳艘呀?jīng)處于SYN_SEND狀態(tài),所以就會(huì)使客戶端進(jìn)入ESTABLISHED狀態(tài),此時(shí)服務(wù)器端反而丟棄了這個(gè)重復(fù)的通過connect函數(shù)發(fā)送的SYN包,見第三個(gè)圖。而連接建立之后,發(fā)送包由于SEQ是以被丟棄的SYN包的序號(hào)為準(zhǔn),而服務(wù)器接收序號(hào)是以那個(gè)延誤舊連接SYN報(bào)文序號(hào)為準(zhǔn),導(dǎo)致服務(wù)器丟棄后續(xù)發(fā)送的數(shù)據(jù)包)
三次握手的最主要目的是保證連接是雙工的,可靠更多的是通過重傳機(jī)制來保證的。
TCP可靠傳輸?shù)膶?shí)現(xiàn):
TCP 連接的每一端都必須設(shè)有兩個(gè)窗口——一個(gè)發(fā)送窗口和一個(gè)接收窗口。TCP 的可靠傳輸機(jī)制用字節(jié)的序號(hào)進(jìn)行控制。TCP 所有的確認(rèn)都是基于序號(hào)而不是基于報(bào)文段。發(fā)送過的數(shù)據(jù)未收到確認(rèn)之前必須保留,以便超時(shí)重傳時(shí)使用。發(fā)送窗口沒收到確認(rèn)不動(dòng),和收到新的確認(rèn)后前移。
發(fā)送緩存用來暫時(shí)存放: 發(fā)送應(yīng)用程序傳送給發(fā)送方 TCP 準(zhǔn)備發(fā)送的數(shù)據(jù);TCP 已發(fā)送出但尚未收到確認(rèn)的數(shù)據(jù)。
接收緩存用來暫時(shí)存放:按序到達(dá)的、但尚未被接收應(yīng)用程序讀取的數(shù)據(jù); 不按序到達(dá)的數(shù)據(jù)。
必須強(qiáng)調(diào)三點(diǎn): 1> A 的發(fā)送窗口并不總是和 B 的接收窗口一樣大(因?yàn)橛幸欢ǖ臅r(shí)間滯后)。 2> TCP 標(biāo)準(zhǔn)沒有規(guī)定對(duì)不按序到達(dá)的數(shù)據(jù)應(yīng)如何處理。通常是先臨時(shí)存放在接收窗口中,等到字節(jié)流中所缺少的字節(jié)收到后,再按序交付上層的應(yīng)用進(jìn)程。 3> TCP 要求接收方必須有累積確認(rèn)的功能,這樣可以減小傳輸開銷
TCP報(bào)文格式
[圖片上傳中。。。(16)]
(1)序號(hào):Seq序號(hào),占32位,用來標(biāo)識(shí)從TCP源端向目的端發(fā)送的字節(jié)流,發(fā)起方發(fā)送數(shù)據(jù)時(shí)對(duì)此進(jìn)行標(biāo)記。 (2)確認(rèn)序號(hào):Ack序號(hào),占32位,只有ACK標(biāo)志位為1時(shí),確認(rèn)序號(hào)字段才有效,Ack=Seq+1。 (3)標(biāo)志位:共6個(gè),即URG、ACK、PSH、RST、SYN、FIN等,具體含義如下: (A)URG:緊急指針(urgent pointer)有效。 (B)ACK:確認(rèn)序號(hào)有效。 (C)PSH:接收方應(yīng)該盡快將這個(gè)報(bào)文交給應(yīng)用層。 (D)RST:重置連接。 (E)SYN:發(fā)起一個(gè)新連接。 (F)FIN:釋放一個(gè)連接。
需要注意的是: (A)不要將確認(rèn)序號(hào)Ack與標(biāo)志位中的ACK搞混了。 (B)確認(rèn)方Ack=發(fā)起方Req+1,兩端配對(duì)。
三次握手
TCP三次即建立TCP連接,指建立一個(gè)TCP連接時(shí),需要客戶端服務(wù)端總共發(fā)送3 個(gè)包以確認(rèn)連接的建立。在socket編程中,這一過程中由客戶端執(zhí)行connect來觸發(fā),流程如下:
[圖片上傳中。。。(17)]
(1)第一次握手:Client將標(biāo)志位SYN置為1(表示要發(fā)起一個(gè)連接),隨機(jī)產(chǎn)生一個(gè)值seq=J,并將該數(shù)據(jù)包發(fā)送給Server,Client進(jìn)入SYN_SENT狀態(tài),等待Server確認(rèn)。(2)第二次握手:Server收到數(shù)據(jù)包后由標(biāo)志位SYN=1知道Client請(qǐng)求建立連接,Server將標(biāo)志位SYN和ACK都置為1,ack=J+1,隨機(jī)產(chǎn)生一個(gè)值seq=K,并將該數(shù)據(jù)包發(fā)送給Client以確認(rèn)連接請(qǐng)求,Server進(jìn)入SYN_RCVD狀態(tài)。(3)第三次握手:Client收到確認(rèn)后,檢查ack是否為J+1,ACK是否為1,如果正確則將標(biāo)志位ACK置為1,ack=K+1,并將該數(shù)據(jù)包發(fā)送給Server,Server檢查ack是否為K+1,ACK是否為1,如果正確則連接建立成功,Client和Server進(jìn)入ESTABLISHED狀態(tài),完成三次握手,隨后Client與Server之間可以開始傳輸數(shù)據(jù)了。
[圖片上傳中。。。(18)]
SYN攻擊: 在三次握手過程中,Server發(fā)送SYN-ACK之后,收到Client的ACK之前的TCP連接稱為半連接(half-open connect),此時(shí)Server處于SYN_RCVD狀態(tài),當(dāng)收到ACK后,Server轉(zhuǎn)入ESTABLISHED狀態(tài)。SYN攻擊就是Client在短時(shí)間內(nèi)偽造大量不存在的IP地址,并向Server不斷地發(fā)送SYN包,Server回復(fù)確認(rèn)包,并等待Client的確認(rèn),由于源地址是不存在的,因此,Server需要不斷重發(fā)直至超時(shí),這些偽造的SYN包將產(chǎn)時(shí)間占用未連接隊(duì)列,導(dǎo)致正常的SYN請(qǐng)求因?yàn)殛?duì)列滿而被丟棄,從而引起網(wǎng)絡(luò)堵塞甚至系統(tǒng)癱瘓。SYN攻擊時(shí)一種典型的DDOS攻擊,檢測(cè)SYN攻擊的方式非常簡(jiǎn)單,即當(dāng)Server上有大量半連接狀態(tài)且源IP地址是隨機(jī)的,則可以斷定遭到SYN攻擊了,使用如下命令可以讓之現(xiàn)行: #netstat -nap | grep SYN_RECV
[圖片上傳中。。。(19)]
ddos攻擊:分布式拒絕服務(wù)(DDoS:Distributed Denial of Service)攻擊指借助于客戶/服務(wù)器技術(shù),將多個(gè)計(jì)算機(jī)聯(lián)合起來作為攻擊平臺(tái),對(duì)一個(gè)或多個(gè)目標(biāo)發(fā)動(dòng)DDoS攻擊,從而成倍地提高拒絕服務(wù)攻擊的威力。通常,攻擊者使用一個(gè)偷竊帳號(hào)將DDoS主控程序安裝在一個(gè)計(jì)算機(jī)上,在一個(gè)設(shè)定的時(shí)間主控程序?qū)⑴c大量代理程序通訊,代理程序已經(jīng)被安裝在網(wǎng)絡(luò)上的許多計(jì)算機(jī)上。代理程序收到指令時(shí)就發(fā)動(dòng)攻擊。利用客戶/服務(wù)器技術(shù),主控程序能在幾秒鐘內(nèi)激活成百上千次代理程序的運(yùn)行。
四次揮手
所謂四次揮手(Four-Way Wavehand)即終止TCP連接,就是指斷開一個(gè)TCP連接時(shí),需要客戶端和服務(wù)端總共發(fā)送4個(gè)包以確認(rèn)連接的斷開。在socket編程中,這一過程由客戶端或服務(wù)端任一方執(zhí)行close來觸發(fā),整個(gè)流程如下圖所示:
[圖片上傳中。。。(20)]
由于TCP連接時(shí)全雙工的,因此,每個(gè)方向都必須要單獨(dú)進(jìn)行關(guān)閉,這一原則是當(dāng)一方完成數(shù)據(jù)發(fā)送任務(wù)后,發(fā)送一個(gè)FIN來終止這一方向的連接,收到一個(gè)FIN只是意味著這一方向上沒有數(shù)據(jù)流動(dòng)了,即不會(huì)再收到數(shù)據(jù)了,但是在這個(gè)TCP連接上仍然能夠發(fā)送數(shù)據(jù),直到這一方向也發(fā)送了FIN。首先進(jìn)行關(guān)閉的一方將執(zhí)行主動(dòng)關(guān)閉,而另一方則執(zhí)行被動(dòng)關(guān)閉,上圖描述的即是如此。 (1)第一次揮手:Client發(fā)送一個(gè)FIN,用來關(guān)閉Client到Server的數(shù)據(jù)傳送,Client進(jìn)入FIN_WAIT_1狀態(tài)。 (2)第二次揮手:Server收到FIN后,發(fā)送一個(gè)ACK給Client,確認(rèn)序號(hào)為收到序號(hào)+1(與SYN相同,一個(gè)FIN占用一個(gè)序號(hào)),Server進(jìn)入CLOSE_WAIT狀態(tài)。 (3)第三次揮手:Server發(fā)送一個(gè)FIN,用來關(guān)閉Server到Client的數(shù)據(jù)傳送,Server進(jìn)入LAST_ACK狀態(tài)。 (4)第四次揮手:Client收到FIN后,Client進(jìn)入TIME_WAIT狀態(tài),接著發(fā)送一個(gè)ACK給Server,確認(rèn)序號(hào)為收到序號(hào)+1,Server進(jìn)入CLOSED狀態(tài),完成四次揮手。
為什么需要TIME_WAIT
TIMEWAIT狀態(tài)也稱為2MSL等待狀態(tài)。
1)為實(shí)現(xiàn)TCP這種全雙工(full-duplex)連接的可靠釋放
這樣可讓TCP再次發(fā)送最后的ACK以防這個(gè)ACK丟失(另一端超時(shí)并重發(fā)最后的FIN)。這種2MSL等待的另一個(gè)結(jié)果是這個(gè)TCP連接在2MSL等待期間,定義這個(gè)連接的插口(客戶的IP地址和端口號(hào),服務(wù)器的IP地址和端口號(hào))不能再被使用。這個(gè)連接只能在2MSL結(jié)束后才能再被使用。
2)為使舊的數(shù)據(jù)包在網(wǎng)絡(luò)因過期而消失
每個(gè)具體TCP實(shí)現(xiàn)必須選擇一個(gè)報(bào)文段最大生存時(shí)間MSL(Maximum Segment Lifetime)。它是任何報(bào)文段被丟棄前在網(wǎng)絡(luò)內(nèi)的最長(zhǎng)時(shí)間。
為什么建立連接是三次握手,而關(guān)閉連接卻是四次揮手呢?
這是因?yàn)榉?wù)端在LISTEN狀態(tài)下,收到建立連接請(qǐng)求的SYN報(bào)文后,把ACK和SYN放在一個(gè)報(bào)文里發(fā)送給客戶端。而關(guān)閉連接時(shí),當(dāng)收到對(duì)方的FIN報(bào)文時(shí),僅僅表示對(duì)方不再發(fā)送數(shù)據(jù)了但是還能接收數(shù)據(jù),我們也未必全部數(shù)據(jù)都發(fā)送給對(duì)方了,所以我們不可以立即close,也可以發(fā)送一些數(shù)據(jù)給對(duì)方后,再發(fā)送FIN報(bào)文給對(duì)方來表示同意現(xiàn)在關(guān)閉連接,因此,我們的ACK和FIN一般都會(huì)分開發(fā)送。
9.函數(shù)調(diào)用和系統(tǒng)調(diào)用的區(qū)別。
什么是系統(tǒng)調(diào)用?(常見Linux及其分類表)
所謂系統(tǒng)調(diào)用就是用戶在程序中調(diào)用操作系統(tǒng)所提供的一個(gè)子功能,也就是系統(tǒng)API,系統(tǒng)調(diào)用可以被看做特殊的公共子程序。系統(tǒng)中的各種共享資源都由操作系統(tǒng)統(tǒng)一掌管,因此在用戶程序中,凡是與資源有關(guān)的操作(如存儲(chǔ)分配、進(jìn)行I/O傳輸及管理文件等),都必須通過系統(tǒng)調(diào)用方式向操作系統(tǒng)提出服務(wù)請(qǐng)求,并由操作系統(tǒng)代為完成。通常,一個(gè)操作系統(tǒng)提供的系統(tǒng)調(diào)用命令有幾十個(gè)乃至上百個(gè)之多。這些系統(tǒng)調(diào)用按照功能大致可以分為以下幾類:
設(shè)備管理:完成設(shè)備的請(qǐng)求或釋放,以及設(shè)備啟動(dòng)等功能。
文件管理:完成文件的讀、寫、創(chuàng)建及刪除等功能
進(jìn)程控制:完成進(jìn)程的創(chuàng)建、撤銷、阻塞、及喚醒的功能
進(jìn)程通信:完成進(jìn)程之間的消息傳遞或信號(hào)的傳遞
內(nèi)存管理:完成內(nèi)存的分配、回收以及獲取作業(yè)占用內(nèi)存區(qū)大小及始址等功能。
顯然,系統(tǒng)調(diào)用運(yùn)行在系統(tǒng)的核心態(tài)。通過系統(tǒng)調(diào)用的方式來使用系統(tǒng)功能,可以保證系統(tǒng)的穩(wěn)定性和安全性,防止用戶隨意更改或訪問系統(tǒng)的數(shù)據(jù)或命令。系統(tǒng)調(diào)用命令式由操作系統(tǒng)提供的一個(gè)或多個(gè)子程序模塊來實(shí)現(xiàn)的。
下圖詳細(xì)闡述了,Linux系統(tǒng)中系統(tǒng)調(diào)用的過程:(int 0x80中斷向量是dos系統(tǒng)返回,int 3中斷向量是斷點(diǎn)指令——可以查中斷向量表)
[圖片上傳中。。。(21)]
庫是可重用的模塊,處于用戶態(tài)。系統(tǒng)調(diào)用是操作系統(tǒng)提供的服務(wù),處于內(nèi)核態(tài),不能直接調(diào)用,而要使用類似int 0x80的軟中斷陷入內(nèi)核,所以庫函數(shù)中有很大部分是對(duì)系統(tǒng)調(diào)用的封裝。
既然如此,如何調(diào)用系統(tǒng)調(diào)用?
用戶是處于用戶態(tài),具有的權(quán)限是非常有限,肯定是不能直接使用內(nèi)核態(tài)的服務(wù),只能間接通過有訪問權(quán)限的API函數(shù)內(nèi)嵌的系統(tǒng)調(diào)用函數(shù)來調(diào)用。
介紹下系統(tǒng)調(diào)用的過程:首先將API函數(shù)參數(shù)壓到棧上,然后將函數(shù)內(nèi)調(diào)用系統(tǒng)調(diào)用的代碼放入寄存器,通過陷入中斷,進(jìn)入內(nèi)核將控制權(quán)交給操作系統(tǒng),操作系統(tǒng)獲得控制后,將系統(tǒng)調(diào)用代碼拿出來,跟操作系統(tǒng)一直維護(hù)的一張系統(tǒng)調(diào)用表做比較,已找到該系統(tǒng)調(diào)用程序體的內(nèi)存地址,接著訪問該地址,執(zhí)行系統(tǒng)調(diào)用。執(zhí)行完畢后,返回用戶程序
[圖片上傳中。。。(22)]
例子:
int main(){ int fd = create("filename",0666); exit(0);}
在執(zhí)行main函數(shù)時(shí),是在user mode下執(zhí)行,當(dāng)遇到create函數(shù)時(shí),繼續(xù)在user mode下執(zhí)行,然后將filename和0666兩個(gè)參數(shù)壓入棧中寄存器,接著調(diào)用庫函數(shù)create,系統(tǒng)仍然處于user mode。這里的庫函數(shù)create實(shí)際上調(diào)用了內(nèi)核的系統(tǒng)調(diào)用create,執(zhí)行到這里后,系統(tǒng)將create系統(tǒng)調(diào)用的unique number壓入寄存器,然后執(zhí)行指令trap使系統(tǒng)進(jìn)入kernel mode(執(zhí)行int $0x80產(chǎn)生中斷)。這時(shí)系統(tǒng)意識(shí)到要進(jìn)行系統(tǒng)調(diào)用的invoke,于是從剛才的寄存器中取出create系統(tǒng)調(diào)用的unique number,從系統(tǒng)調(diào)用表中得知要invoke的系統(tǒng)調(diào)用是create,然后執(zhí)行。執(zhí)行完畢返回庫函數(shù)create的調(diào)用,庫函數(shù)負(fù)責(zé)檢查系統(tǒng)調(diào)用的執(zhí)行情況(檢查某些寄存器的值),然后庫函數(shù)create根據(jù)檢查的結(jié)果返回響應(yīng)的值。
這里trap指令類似于一個(gè)系統(tǒng)中斷并且是軟中斷,而系統(tǒng)調(diào)用create類似于一個(gè)中斷處理函數(shù)所有的系統(tǒng)調(diào)用都與上邊的情況類似,靠中斷機(jī)制切換到內(nèi)核模式實(shí)現(xiàn)。
系統(tǒng)調(diào)用通常比庫函數(shù)要慢,因?yàn)橐焉舷挛沫h(huán)境切換到內(nèi)核模式。
補(bǔ)充一下系統(tǒng)調(diào)用和庫函數(shù)的區(qū)別:
[圖片上傳中。。。(23)]
系統(tǒng)調(diào)用:是操作系統(tǒng)為用戶態(tài)運(yùn)行的進(jìn)程和硬件設(shè)備(如CPU、磁盤、打印機(jī)等)進(jìn)行交互提供的一組接口,即就是設(shè)置在應(yīng)用程序和硬件設(shè)備之間的一個(gè)接口層。可以說是操作系統(tǒng)留給用戶程序的一個(gè)接口。再來說一下,linux內(nèi)核是單內(nèi)核,結(jié)構(gòu)緊湊,執(zhí)行速度快,各個(gè)模塊之間是直接調(diào)用的關(guān)系。放眼望整個(gè)linux系統(tǒng),從上到下依次是用戶進(jìn)程->linux內(nèi)核->硬件。其中系統(tǒng)調(diào)用接口是位于Linux內(nèi)核中的,如果再稍微細(xì)分一下的話,整個(gè)linux系統(tǒng)從上到下可以是:用戶進(jìn)程->系統(tǒng)調(diào)用接口->linux內(nèi)核子系統(tǒng)->硬件,也就是說Linux內(nèi)核包括了系統(tǒng)調(diào)用接口和內(nèi)核子系統(tǒng)兩部分;或者從下到上可以是:物理硬件->OS內(nèi)核->OS服務(wù)->應(yīng)用程序,其中操作系統(tǒng)起到“承上啟下”的關(guān)鍵作用,向下管理物理硬件,向上為操作系服務(wù)和應(yīng)用程序提供接口,這里的接口就是系統(tǒng)調(diào)用了。 一般地,操作系統(tǒng)為了考慮實(shí)現(xiàn)的難度和管理的方便,它只提供一少部分的系統(tǒng)調(diào)用,這些系統(tǒng)調(diào)用一般都是由C和匯編混合編寫實(shí)現(xiàn)的,其接口用C來定義,而具體的實(shí)現(xiàn)則是匯編,這樣的好處就是執(zhí)行效率高,而且,極大的方便了上層調(diào)用。庫函數(shù):顧名思義是把函數(shù)放到庫里。是把一些常用到的函數(shù)編完放到一個(gè)文件里,供別人用。別人用的時(shí)候把它所在的文件名用#include<>加到里面就可以了。一般是放到lib文件里的。一般是指編譯器提供的可在c源程序中調(diào)用的函數(shù)。可分為兩類,一類是c語言標(biāo)準(zhǔn)規(guī)定的庫函數(shù),一類是編譯器特定的庫函數(shù)。(由于版權(quán)原因,庫函數(shù)的源代碼一般是不可見的,但在頭文件中你可以看到它對(duì)外的接口) libc中就是一個(gè)C標(biāo)準(zhǔn)庫,里面存放一些基本函數(shù),這些基本函數(shù)都是被標(biāo)準(zhǔn)化了的,而且這些函數(shù)通常都是用匯編直接實(shí)現(xiàn)的。 庫函數(shù)一般可以概括的分為兩類,一類是隨著操作系統(tǒng)提供的,另一類是由第三方提供的。隨著系統(tǒng)提供的這些庫函數(shù)把系統(tǒng)調(diào)用進(jìn)行封裝或者組合,可以實(shí)現(xiàn)更多的功能,這樣的庫函數(shù)能夠?qū)崿F(xiàn)一些對(duì)內(nèi)核來說比較復(fù)雜的操作。比如,read()函數(shù)根據(jù)參數(shù),直接就能讀文件,而背后隱藏的比如文件在硬盤的哪個(gè)磁道,哪個(gè)扇區(qū),加載到內(nèi)存的哪個(gè)位置等等這些操作,程序員是不必關(guān)心的,這些操作里面自然也包含了系統(tǒng)調(diào)用。而對(duì)于第三方的庫,它其實(shí)和系統(tǒng)庫一樣,只是它直接利用系統(tǒng)調(diào)用的可能性要小一些,而是利用系統(tǒng)提供的API接口來實(shí)現(xiàn)功能(API的接口是開放的)。部分Libc庫中的函數(shù)的功能的實(shí)現(xiàn)還是借助了系統(tǒng)掉調(diào)用,比如printf的實(shí)現(xiàn)最終還是調(diào)用了write這樣的系統(tǒng)調(diào)用;而另一些則不會(huì)使用系統(tǒng)調(diào)用,比如strlen, strcat, memcpy等。實(shí)時(shí)上,系統(tǒng)調(diào)用所提供給用戶的是直接而純粹的高級(jí)服務(wù),如果想要更人性化,具有更符合特定情況的功能,那么就要我們用戶自己來定義,因此就衍生了庫函數(shù),它把部分系統(tǒng)調(diào)用包裝起來,一方面把系統(tǒng)調(diào)用抽象了,一方面方便了用戶級(jí)的調(diào)用。系統(tǒng)調(diào)用和庫函數(shù)在執(zhí)行的效果上很相似(當(dāng)然庫函數(shù)會(huì)更符合需求),但是系統(tǒng)調(diào)用是運(yùn)行于內(nèi)核狀態(tài);而庫函數(shù)由用戶調(diào)用,運(yùn)行于用戶態(tài)。系統(tǒng)調(diào)用是為了方便使用操作系統(tǒng)的接口,而庫函數(shù)則是為了人們編程的方便。
[圖片上傳中。。。(24)]
10.線程和進(jìn)程,線程可以共享進(jìn)程里的哪些東西。 知道協(xié)程是什么嗎
進(jìn)程,是并發(fā)執(zhí)行的程序在執(zhí)行過程中分配和管理資源的基本單位,每一個(gè)進(jìn)程都有一個(gè)自己的地址空間,即進(jìn)程空間或(虛空間)。進(jìn)程空間的大小 只與處理機(jī)的位數(shù)有關(guān),一個(gè) 16 位長(zhǎng)處理機(jī)的進(jìn)程空間大小為 216 ,而 32 位處理機(jī)的進(jìn)程空間大小為 232 。進(jìn)程至少有 5 種基本狀態(tài),它們是:初始態(tài),執(zhí)行態(tài),等待狀態(tài),就緒狀態(tài),終止?fàn)顟B(tài)。
[圖片上傳中。。。(25)]
線程,在網(wǎng)絡(luò)或多用戶環(huán)境下,一個(gè)服務(wù)器通常需要接收大量且不確定數(shù)量用戶的并發(fā)請(qǐng)求,為每一個(gè)請(qǐng)求都創(chuàng)建一個(gè)進(jìn)程顯然是行不通的,——無論是從系統(tǒng)資源開銷方面或是響應(yīng)用戶請(qǐng)求的效率方面來看。因此,操作系統(tǒng)中線程的概念便被引進(jìn)了。線程,是進(jìn)程的一部分,一個(gè)沒有線程的進(jìn)程可以被看作是單線程的。線程有時(shí)又被稱為輕權(quán)進(jìn)程或輕量級(jí)進(jìn)程,也是 CPU 調(diào)度的一個(gè)基本單位。
共享進(jìn)程的地址空間,全局變量(數(shù)據(jù)和堆)。在一個(gè)進(jìn)程中,各個(gè)線程共享堆區(qū),而進(jìn)程中的線程各自維持自己的棧。
[圖片上傳中。。。(26)]
Each thread has its own:
棧區(qū)和棧指針(Stack area and stack pointer)
寄存器(Registers)
調(diào)度優(yōu)先級(jí)Scheduling properties (such as policy or priority)
信號(hào)(阻塞和懸掛)Signals (pending and blocked signals)
普通變量Thread specific data ( automatic variables )
[圖片上傳中。。。(27)]
線程是指進(jìn)程內(nèi)的一個(gè)執(zhí)行單元,也是進(jìn)程內(nèi)的可調(diào)度實(shí)體.與進(jìn)程的區(qū)別:(1)地址空間:進(jìn)程內(nèi)的一個(gè)執(zhí)行單元;進(jìn)程至少有一個(gè)線程;它們共享進(jìn)程的地址空間;而進(jìn)程有自己獨(dú)立的地址空間;(2)資源擁有:進(jìn)程是資源分配和擁有的單位,同一個(gè)進(jìn)程內(nèi)的線程共享進(jìn)程的資源(3)線程是處理器調(diào)度的基本單位,但進(jìn)程不是.4)二者均可并發(fā)執(zhí)行.進(jìn)程和線程都是由操作系統(tǒng)所體會(huì)的程序運(yùn)行的基本單元,系統(tǒng)利用該基本單元實(shí)現(xiàn)系統(tǒng)對(duì)應(yīng)用的并發(fā)性。進(jìn)程和線程的區(qū)別在于:簡(jiǎn)而言之,一個(gè)程序至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程至少有一個(gè)線程. 線程的劃分尺度小于進(jìn)程,使得多線程程序的并發(fā)性高。 另外,進(jìn)程在執(zhí)行過程中擁有獨(dú)立的內(nèi)存單元,而多個(gè)線程共享內(nèi)存,從而極大地提高了程序的運(yùn)行效率。 線程在執(zhí)行過程中與進(jìn)程還是有區(qū)別的。每個(gè)獨(dú)立的線程有一個(gè)程序運(yùn)行的入口、順序執(zhí)行序列和程序的出口。但是線程不能夠獨(dú)立執(zhí)行,必須依存在應(yīng)用程序中,由應(yīng)用程序提供多個(gè)線程執(zhí)行控制。 從邏輯角度來看,多線程的意義在于一個(gè)應(yīng)用程序中,有多個(gè)執(zhí)行部分可以同時(shí)執(zhí)行。但操作系統(tǒng)并沒有將多個(gè)線程看做多個(gè)獨(dú)立的應(yīng)用,來實(shí)現(xiàn)進(jìn)程的調(diào)度和管理以及資源分配。這就是進(jìn)程和線程的重要區(qū)別。進(jìn)程是具有一定獨(dú)立功能的程序關(guān)于某個(gè)數(shù)據(jù)集合上的一次運(yùn)行活動(dòng),進(jìn)程是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位. 線程是進(jìn)程的一個(gè)實(shí)體,是CPU調(diào)度和分派的基本單位,它是比進(jìn)程更小的能獨(dú)立運(yùn)行的基本單位.線程自己基本上不擁有系統(tǒng)資源,只擁有一點(diǎn)在運(yùn)行中必不可少的資源(如程序計(jì)數(shù)器,一組寄存器和棧),但是它可與同屬一個(gè)進(jìn)程的其他的線程共享進(jìn)程所擁有的全部資源. 一個(gè)線程可以創(chuàng)建和撤銷另一個(gè)線程;同一個(gè)進(jìn)程中的多個(gè)線程之間可以并發(fā)執(zhí)行.
[圖片上傳中。。。(28)]
協(xié)程:
定義:協(xié)程其實(shí)可以認(rèn)為是比線程更小的執(zhí)行單元。為啥說他是一個(gè)執(zhí)行單元,因?yàn)樗詭PU上下文。
協(xié)程切換:協(xié)程擁有自己的寄存器上下文和棧。協(xié)程調(diào)度切換時(shí),將寄存器上下文和棧保存到其他地方,在切回來的時(shí)候,恢復(fù)先前保存的寄存器上下文和棧。
(我們?cè)谧约涸谶M(jìn)程里面完成邏輯流調(diào)度,碰著i\o我就用非阻塞式的。那么我們即可以利用到異步優(yōu)勢(shì),又可以避免反復(fù)系統(tǒng)調(diào)用,還有進(jìn)程切換造成的開銷,分分鐘給你上幾千個(gè) 邏輯流不費(fèi)力。這就是協(xié)程。)
協(xié)程的調(diào)度完全由用戶控制,一個(gè)線程可以有多個(gè)協(xié)程,用戶創(chuàng)建了幾個(gè)線程,然后每個(gè)線程都是循環(huán)按照指定的任務(wù)清單順序完成不同的任務(wù),當(dāng)任務(wù)被堵塞的時(shí)候執(zhí)行下一個(gè)任務(wù),當(dāng)恢復(fù)的時(shí)候再回來執(zhí)行這個(gè)任務(wù),任務(wù)之間的切換只需要保存每個(gè)任務(wù)的上下文內(nèi)容,就像直接操作棧一樣的,這樣就完全沒有內(nèi)核切換的開銷,可以不加鎖的訪問全局變量,所以上下文的切換非常快;另外協(xié)程還需要保證是非堵塞的且沒有相互依賴,協(xié)程基本上不能同步通訊,多采用一步的消息通訊,效率比較高。
多線程和多進(jìn)程的優(yōu)劣:
多線程還是多進(jìn)程的爭(zhēng)執(zhí)由來已久,這種爭(zhēng)執(zhí)最常見到在B/S通訊中服務(wù)端并發(fā)技術(shù)的選型上,比如WEB服務(wù)器技術(shù)中,Apache是采用多進(jìn)程的(perfork模式,每客戶連接對(duì)應(yīng)一個(gè)進(jìn)程,每進(jìn)程中只存在唯一一個(gè)執(zhí)行線程),Java的Web容器Tomcat、Websphere等都是多線程的(每客戶連接對(duì)應(yīng)一個(gè)線程,所有線程都在一個(gè)進(jìn)程中)。
多進(jìn)程:fork
多線程:pthread_create
[圖片上傳中。。。(29)]
11.mysql的數(shù)據(jù)庫引擎有哪些,他們的區(qū)別
ISAM
ISAM是一個(gè)定義明確且歷經(jīng)時(shí)間考驗(yàn)的數(shù)據(jù)表格管理方法,它在設(shè)計(jì)之時(shí)就考慮到數(shù)據(jù)庫被查詢的次數(shù)要遠(yuǎn)大于更新的次數(shù)。因此,ISAM執(zhí)行讀取操作的速度很快,而且不占用大量的內(nèi)存和存儲(chǔ)資源。ISAM的兩個(gè)主要不足之處在于,它不支持事務(wù)處理,也不能夠容錯(cuò):如果你的硬盤崩潰了,那么數(shù)據(jù)文件就無法恢復(fù)了。如果你正在把ISAM用在關(guān)鍵任務(wù)應(yīng)用程序里,那就必須經(jīng)常備份你所有的實(shí)時(shí)數(shù)據(jù),通過其復(fù)制特性,MYSQL能夠支持這樣的備份應(yīng)用程序。
MYISAM
MYISAM是MYSQL的ISAM擴(kuò)展格式和缺省的數(shù)據(jù)庫引擎。除了提供ISAM里所沒有的索引和字段管理的大量功能,MYISAM還使用一種表格鎖定的機(jī)制,來優(yōu)化多個(gè)并發(fā)的讀寫操作。其代價(jià)是你需要經(jīng)常運(yùn)行OPTIMIZE TABLE命令,來恢復(fù)被更新機(jī)制所浪費(fèi)的空間。MYISAM還有一些有用的擴(kuò)展,例如用來修復(fù)數(shù)據(jù)庫文件的MYISAMCHK工具和用來恢復(fù)浪費(fèi)空間的MYISAMPACK工具。
MYISAM強(qiáng)調(diào)了快速讀取操作,這可能就是為什么MYSQL受到了WEB開發(fā)如此青睞的主要原因:在WEB開發(fā)中你所進(jìn)行的大量數(shù)據(jù)操作都是讀取操作。所以,大多數(shù)虛擬主機(jī)提供商和INTERNET平臺(tái)提供商只允許使用MYISAM格式。
HEAP
HEAP允許只駐留在內(nèi)存里的臨時(shí)表格。駐留在內(nèi)存使得HEAP比ISAM和MYISAM的速度都快,但是它所管理的數(shù)據(jù)是不穩(wěn)定的,而且如果在關(guān)機(jī)之前沒有進(jìn)行保存,那么所有的數(shù)據(jù)都會(huì)丟失。在數(shù)據(jù)行被刪除的時(shí)候,HEAP也不會(huì)浪費(fèi)大量的空間,HEAP表格在你需要使用SELECT表達(dá)式來選擇和操控?cái)?shù)據(jù)的時(shí)候非常有用。要記住,用完表格后要?jiǎng)h除表格。
INNODB和BERKLEYDB
INNODB和BERKLEYDB(BDB)數(shù)據(jù)庫引擎都是造就MYSQL靈活性的技術(shù)的直接產(chǎn)品,這項(xiàng)技術(shù)就是MySql++ API。在使用MySql的時(shí)候,你所面對(duì)的每一個(gè)挑戰(zhàn)幾乎都源于ISAM和MYIASM數(shù)據(jù)庫引擎不支持事務(wù)處理也不支持外來鍵。盡管要比ISAM和MYISAM引擎慢很多,但是INNODB和BDB包括了對(duì)事務(wù)處理和外來鍵的支持,這兩點(diǎn)都是前兩個(gè)引擎所沒有的。如前所述,如果你的設(shè)計(jì)需要這些特性中的一者或者兩者,那你就要被迫使用后兩個(gè)引擎中的一個(gè)了。
12.makefile嗎,一個(gè)文件依賴庫a,庫a依賴庫b,寫makefile的時(shí)候,a要放在b的前面還是后面
Makefile概述:
什么是makefile?或許很多Winodws的程序員都不知道這個(gè)東西,因?yàn)槟切¦indows的IDE都為你做了這個(gè)工作,但我覺得要作一個(gè)好的和professional的程序員,makefile還是要懂。這就好像現(xiàn)在有這么多的HTML的編輯器,但如果你想成為一個(gè)專業(yè)人士,你還是要了解HTML的標(biāo)識(shí)的含義。特別在Unix下的軟件編譯,你就不能不自己寫makefile了,會(huì)不會(huì)寫makefile,從一個(gè)側(cè)面說明了一個(gè)人是否具備完成大型工程的能力。
因?yàn)椋琺akefile關(guān)系到了整個(gè)工程的編譯規(guī)則。一個(gè)工程中的源文件不計(jì)數(shù),其按類型、功能、模塊分別放在若干個(gè)目錄中,makefile定義了一系列的規(guī)則來指定,哪些文件需要先編譯,哪些文件需要后編譯,哪些文件需要重新編譯,甚至于進(jìn)行更復(fù)雜的功能操作,因?yàn)閙akefile就像一個(gè)Shell腳本一樣,其中也可以執(zhí)行操作系統(tǒng)的命令。
makefile帶來的好處就是——“自動(dòng)化編譯”,一旦寫好,只需要一個(gè)make命令,整個(gè)工程完全自動(dòng)編譯,極大的提高了軟件開發(fā)的效率。make是一個(gè)命令工具,是一個(gè)解釋makefile中指令的命令工具,一般來說,大多數(shù)的IDE都有這個(gè)命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可見,makefile都成為了一種在工程方面的編譯方法。
現(xiàn)在講述如何寫makefile的文章比較少,這是我想寫這篇文章的原因。當(dāng)然,不同產(chǎn)商的make各不相同,也有不同的語法,但其本質(zhì)都是在“文件依賴性”上做文章,這里,我僅對(duì)GNU的make進(jìn)行講述,我的環(huán)境是RedHat Linux 8.0,make的版本是3.80。必竟,這個(gè)make是應(yīng)用最為廣泛的,也是用得最多的。而且其還是最遵循于IEEE 1003.2-1992 標(biāo)準(zhǔn)的(POSIX.2)。
在這篇文檔中,將以C/C++的源碼作為我們基礎(chǔ),所以必然涉及一些關(guān)于C/C++的編譯的知識(shí),相關(guān)于這方面的內(nèi)容,還請(qǐng)各位查看相關(guān)的編譯器的文檔。這里所默認(rèn)的編譯器是UNIX下的GCC和CC。
編譯和連接:
編譯:
定義:一般來說,無論是C、C++、還是pas,首先要把源文件編譯成中間代碼文件,在Windows下也就是 .obj 文件,UNIX下是 .o 文件,即 Object File,這個(gè)動(dòng)作叫做編譯(compile)。
描述:編譯時(shí),編譯器需要的是語法的正確,函數(shù)與變量的聲明的正確。只要所有的語法正確,編譯器就可以編譯出中間目標(biāo)文件。一般來說,每個(gè)源文件都應(yīng)該對(duì)應(yīng)于一個(gè)中間目標(biāo)文件(O文件或是OBJ文件)。
連接:
定義:然后再把大量的Object File合成執(zhí)行文件,這個(gè)動(dòng)作叫作鏈接(link)。
描述:通常是你需要告訴編譯器頭文件的所在位置(頭文件中應(yīng)該只是聲明,而定義應(yīng)該放在C/C++文件中),鏈接時(shí),主要是鏈接函數(shù)和全局變量,所以,我們可以使用這些中間目標(biāo)文件(O文件或是OBJ文件)來鏈接我們的應(yīng)用程序。鏈接器并不管函數(shù)所在的源文件,只管函數(shù)的中間目標(biāo)文件(Object File),在大多數(shù)時(shí)候,由于源文件太多,編譯生成的中間目標(biāo)文件太多,而在鏈接時(shí)需要明顯地指出中間目標(biāo)文件名,這對(duì)于編譯很不方便,所以,我們要給中間目標(biāo)文件打個(gè)包,在Windows下這種包叫“庫文件”(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件。
總結(jié)一下,源文件首先會(huì)生成中間目標(biāo)文件,再由中間目標(biāo)文件生成執(zhí)行文件。在編譯時(shí),編譯器只檢測(cè)程序語法,和函數(shù)、變量是否被聲明。如果函數(shù)未被聲明,編譯器會(huì)給出一個(gè)警告,但可以生成Object File。而在鏈接程序時(shí),鏈接器會(huì)在所有的Object File中找尋函數(shù)的實(shí)現(xiàn),如果找不到,那到就會(huì)報(bào)鏈接錯(cuò)誤碼(Linker Error),在VC下,這種錯(cuò)誤一般是:Link 2001錯(cuò)誤,意思說是說,鏈接器未能找到函數(shù)的實(shí)現(xiàn)。你需要指定函數(shù)的Object File.
Makefile
make命令執(zhí)行時(shí),需要一個(gè) Makefile 文件,以告訴make命令需要怎么樣的去編譯和鏈接程序。首先,我們用一個(gè)示例來說明Makefile的書寫規(guī)則。我們的規(guī)則是:1)如果這個(gè)工程沒有編譯過,那么我們的所有C文件都要編譯并被鏈接。2)如果這個(gè)工程的某幾個(gè)C文件被修改,那么我們只編譯被修改的C文件,并鏈接目標(biāo)程序。3)如果這個(gè)工程的頭文件被改變了,那么我們需要編譯引用了這幾個(gè)頭文件的C文件,并鏈接目標(biāo)程序。只要我們的Makefile寫得夠好,所有的這一切,我們只用一個(gè)make命令就可以完成,make命令會(huì)自動(dòng)智能地根據(jù)當(dāng)前的文件修改的情況來確定哪些文件需要重編譯,從而自己編譯所需要的文件和鏈接目標(biāo)程序。
Makefile的規(guī)則: target…:dependecies… command
target也就是一個(gè)目標(biāo)文件,可以是Object File,也可以是執(zhí)行文件。還可以是一個(gè)標(biāo)簽(Label),對(duì)于標(biāo)簽這種特性,在后續(xù)的“偽目標(biāo)”章節(jié)中會(huì)有敘述。dependicies就是,要生成那個(gè)target所需要的文件或是目標(biāo)。command也就是make需要執(zhí)行的命令。(任意的Shell命令)這是一個(gè)文件的依賴關(guān)系,也就是說,target這一個(gè)或多個(gè)的目標(biāo)文件依賴于dependicies中的文件,其生成規(guī)則定義在command中。說白一點(diǎn)就是說,dependicies中如果有一個(gè)以上的文件比target文件要新的話,command所定義的命令就會(huì)被執(zhí)行。這就是Makefile的規(guī)則。也就是Makefile中最核心的內(nèi)容。(深入探討makefile)
注意事項(xiàng):
1.命令要以[Tab]為開始
2.有clean
[圖片上傳中。。。(30)]