面向對象的編程的主要思想html
和數據封裝其中,以提升程序的重用性,靈活性和可擴展性。類是建立對象的模板,一個類能夠建立多個對象。對象是類的實例化。java
類是抽象的,不佔用存儲空間;而對象具體的,佔用存儲空間。c++
面向對象有三大特性:封裝,繼承,多態。程序員
1.C++的三大特性爲:繼承,多態,封裝算法
(1)繼承。一個對象直接使用另外一個對象的屬性和方法。express
優勢:1.減小重複的代碼。編程
2.繼承是多態的前提。數組
3.繼承增長了類的耦合性。安全
缺點:1.繼承在編譯時刻就定義了,沒法在運行時刻改變父類繼承的實現;bash
2.父類一般至少定義了子類的部分行爲,父類的改變均可能影響子類的行爲;
3.若是繼承下來的子類不適合解決新問題,父類必須重寫或替換,那麼這種依賴關係就限制了靈活性,最終限制了複用性。
繼承的方式有三種分別爲公有繼承(public),保護繼承(protect),私有繼承(private)。
虛繼承:
爲了解決從不一樣途徑繼承來的同名的數據成員在內存中有不一樣的拷貝形成數據不一致問題,將共同基類設置爲虛基類。這時從不一樣的路徑繼承過來的同名數據成員在內存中就只有一個拷貝,同一個函數名也只有一個映射。這樣不只就解決了二義性問題,也節省了內存,避免了數據不一致的問題。
class 派生類名:virtual 繼承方式 基類名
virtual是關鍵字,聲明該基類爲派生類的虛基類。
在多繼承狀況下,虛基類關鍵字的做用範圍和繼承方式關鍵字相同,只對緊跟其後的基類起做用。
聲明瞭虛基類以後,虛基類在進一步派生過程當中始終和派生類一塊兒,維護同一個基類子對象的拷貝。
(2)多態。
C++中有兩種多態,稱爲動多態(運行期多態)和靜多態(編譯期多態),
多態:是對於不一樣對象接收相同消息時產生不一樣的動做。C++的多態性具體體如今運行和編譯兩個方面:
在程序運行時的多態性經過繼承和虛函數來體現;
在程序編譯時多態性體如今函數和運算符的重載上;
虛函數就是多態的具體表現,虛函數的做用是容許在派生類中從新定義與基類同名的函數,而且能夠經過基類指針或引用來訪問基類和派生類中的同名函數。
虛函數如何實現的?
虛函數是經過一張虛函數表實現的,有多少個虛函數,就有多少個指針;在這個表中,主要是一個類的虛函數的地址表,這張表解決了繼承、覆蓋的問題;實際上在編譯的時候,編譯器會自動加上虛表虛函數的做用實現動態聯編,也就是說在程序運行階段動態的選擇合適的成員函數,在定義了虛函數以後,能夠在基類的派生類中對虛函數從新定義。虛表的使用方法是若是派生類在本身定義中沒有修改基類的虛函數,咱們就指向基類的虛函數;若是派生類改寫了基類的虛函數,這時續表則將原來指向基類的虛函數的地址替換爲指向自身虛函數的指針。必須經過基類類型的引用或指針進行函數調用纔會發生多態
重載,是指「同一標識符」在同一做用域的不一樣場合具備不一樣的語義,這個標識符能夠是函數名或運算符
接口的多種不一樣實現方式即爲多態。
優勢:1.大大提升了代碼的可複用性;
2.提升了了代碼的可維護性,可擴充性;
缺點: 1)易讀性比較很差,調試比較困難
2)模板只能定義在.h文件中,當工程大了以後,編譯時間十分的變態
(3)封裝。隱藏對象的屬性和實現細節,僅僅對外提供接口和方法。
優勢: 1)隔離變化;2)便於使用; 3)提升重用性; 4)提升安全性
缺點: 1)若是封裝太多,影響效率; 2)使用者不能知道代碼具體實現。
重載(overload)和覆蓋(override):
重載:寫一個與已有函數同名可是參數表不一樣的函數;
覆蓋:虛函數老是在派生類中被改寫。
派生類
派生類的定義格式以下:
class <派生類名>:[繼承方式]<基類名1>
[,[繼承方式]<基類名2>,...,[繼承方式]<基類名n>]
{
<派生類新增的數據成員和成員函數定義>
};
說明:
(1)定義派生類關鍵字能夠是class或者是struct,二者區別是:用class定義派生類,默認的繼承方式是private,用struct定義派生類,默認的繼承方式爲public。新增長的成員默認屬性也是class對應private屬性,struct對應public屬性。
(2)基類不能被派生類繼承的兩類函數是構造函數和析構函數。
1.C和C++的區別
1、主要區別:
C語言是面向過程的編程,它最重要的特色是函數,經過main函數來調用各個子函數。程序運行的順序都是程序員事先決定好的。
C++是面向對象的編程,類是它的主要特色,在程序執行過程當中,先由主main函數進入,定義一些類,根據須要執行類的成員函數,過程的概念被淡化了,以類驅動程序運行,類就是對象,因此咱們稱之爲面向對象程序設計。面向對象在分析和解決問題的時候,將涉及到的數據和數據的操做封裝在類中,經過類能夠建立對象,以事件或消息來驅動對象執行處理。
2、聯繫:c是c++的子集,因此大部c語言程序均可以不加修改的拿到c++下使用。
2.C++和JAVA區別
Java面向對象,沒有指針,編寫效率高,執行效率較低。
java內存自動回收(GC垃圾回收機制),多線程編程。
JAVA的應用在高層,C++在中間件和底層
c++用析構函數回收垃圾,java自動回收(GC算法)
Java比C++程序可靠性更高
Java語言不須要程序對內存進行分配和回收
Java語言中沒有指針的概念,引入了真正的數組
Java用接口(Interface)技術取代C++程序中的多繼承性
3. const 有什麼用途
主要有三點:
1:定義只讀變量,即常量
2:修飾函數的參數和函數的返回值
const能夠修飾函數的返回值,參數及,函數的定義體,被const修飾會受到強制的保護,能防止意外的修改,從而提升函數的健壯性。
3: 修飾函數的定義體,這裏的函數爲類的成員函數,被const修飾的成員函數表明不修改爲員變量的值。
3. 指針和引用的區別
1:引用是變量的一個別名,內部實現是隻讀指針
2:引用只能在初始化時被賦值,其餘時候值不能被改變,指針的值能夠在任什麼時候候被改變
3:引用不能爲NULL,指針能夠爲NULL
4:引用變量內存單元保存的是被引用變量的地址
5:「sizeof 引用" = 指向變量的大小 , "sizeof 指針"= 指針自己的大小
6:引用能夠取地址操做,返回的是被引用變量自己所在的內存單元地址
7:引用使用在源代碼級至關於普通的變量同樣使用,作函數參數時,內部傳遞的實際是變量地址
4. C++中有了malloc / free , 爲何還須要 new / delet e
1,malloc與free是C++/C語言的標準庫函數,new/delete是C++的運算符。它們均可用於申請動態內存和釋放內存。
2,對於非內部數據類型的對象而言,光用malloc/free沒法知足動態對象的要求。
對象在建立的同時要自動執行構造函數,對象在消亡以前要自動執行析構函數。
因爲malloc/free是庫函數而不是運算符,不在編譯器控制權限以內,不可以把執行構造函數和析構函數的任務強加於malloc/free。
3,所以C++語言須要一個能完成動態內存分配和初始化工做的運算符new,以一個能完成清理與釋放內存工做的運算符delete。注意new/delete不是庫函數。
new/delete、malloc/free底層實現原理:
概述:new/delete的底層實現是調用malloc/free函數實現的,而malloc/free的底層實現也不是直接操做內存而是調用系統API實現的。
5.delete與 delete []區別
delete只會調用一次析構函數,而delete[]會調用每個成員的析構函數。
6. 編寫類String 的構造函數,析構函數,拷貝構造函數和賦值函數
https://blog.csdn.net/zz460833359/article/details/46651401
7. 單鏈表的逆置
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (head == NULL || head->next == NULL) return head;
ListNode* rHead = reverseList(head->next); // 反轉獲得新鏈表的頭節點
head->next->next = head; // 當前節點的下一個節點的next指針反轉過來
head->next = NULL; // 設置新鏈表的尾節點
return rHead;
}
}; 8. 堆和棧的區別
一個由C/C++編譯的程序佔用的內存分爲如下幾個部分
1、棧區(stack)― 由編譯器自動分配釋放 ,存放函數的參數值,局部變量的值等。其操做方式相似於數據結構中的棧。
2、堆區(heap) ― 通常由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收 。
注意它與數據結構中的堆是兩回事,分配方式卻是相似於鏈表,呵呵。
3、全局區(靜態區)(static)― 全局變量和靜態變量的存儲是放在一塊的,
初始化的全局變量和靜態變量在一塊區域, 未初始化的全局變量和未初始化的靜態變量在相鄰的另外一塊區域。 - 程序結束後有系統釋放
4、文字常量區 ―常量字符串就是放在這裏的。 程序結束後由系統釋放
5、程序代碼區―存放函數體的二進制代碼。
棧區與堆區的區別:
1)堆和棧中的存儲內容:棧存局部變量、函數參數等。堆存儲使用new、malloc申請的變量等;
2)申請方式:棧內存由系統分配,堆內存由本身申請;
3)申請後系統的響應:棧——只要棧的剩餘空間大於所申請空間,系統將爲程序提供內存,不然將報異常提示棧溢出。
堆——首先應該知道操做系統有一個記錄空閒內存地址的鏈表,當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大於所申請空間的堆結點,而後將該結點從空閒結點鏈表 中刪除,並將該結點的空間分配給程序;
4)申請大小的限制:Windows下棧的大小通常是2M,堆的容量較大;
5)申請效率的比較:棧由系統自動分配,速度較快。堆使用new、malloc等分配,較慢;
總結:棧區優點在處理效率,堆區優點在於靈活;
內存模型:自由區、靜態區、動態區;
根據c/c++對象生命週期不一樣,c/c++的內存模型有三種不一樣的內存區域,即:自由存儲區,動態區、靜態區。
自由存儲區:局部非靜態變量的存儲區域,即日常所說的棧;
動態區: 用new ,malloc分配的內存,即日常所說的堆;
靜態區:全局變量,靜態變量,字符串常量存在的位置;
注:代碼雖然佔內存,但不屬於c/c++內存模型的一部分;
13. 頭文件種的ifndef/define/endif 是幹什麼用的
主要用於防止重複定義宏和重複包含頭文件
8、動態規劃的本質
答:動歸,本質上是一種劃分子問題的算法,站在任何一個子問題的處理上看,當前子問題的提出都要依據現有的相似結論,而當前問題的結論是後面問題求解的鋪墊。任何動態規劃都是基於存儲的算法,核心是狀態轉移方程。
析構函數的調用狀況
析構函數調用的次序是先派生類的析構後基類的析構,也就是說在基類的的析構調用的時候,派生類的信息已經所有銷燬了。定義一個對象時先調用基類的構造函數、而後調用派生類的構造函數;析構的時候剛好相反:先調用派生類的析構函數、而後調用基類的析構函數。
11、結構與聯合有和區別?
(1). 結構和聯合都是由多個不一樣的數據類型成員組成, 但在任何同一時刻, 聯合中只存放了一個被選中的成員(全部成員共用一塊地址空間), 而結構的全部成員都存在(不一樣成員的存放地址不一樣)。
(2). 對於聯合的不一樣成員賦值, 將會對其它成員重寫, 原來成員的值就不存在了, 而對於結構的不一樣成員賦值是互不影響的。
14.有哪幾種狀況只能用intialization list 而不能用assignment?
答案:當類中含有const、reference 成員變量;基類的構造函數都須要初始化表。
15. C++是否是類型安全的?
答案:不是。兩個不一樣類型的指針之間能夠強制轉換(用reinterpret cast)。C#是類型安全的。
16. main 函數執行之前,還會執行什麼代碼?
答案:全局對象的構造函數會在main 函數以前執行。
17. 描述內存分配方式以及它們的區別?
1) 從靜態存儲區域分配。內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在。例如全局變量,static 變量。
2) 在棧上建立。在執行函數時,函數內局部變量的存儲單元均可以在棧上建立,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置於處理器的指令集。
3) 從堆上分配,亦稱動態內存分配。程序在運行的時候用malloc 或new 申請任意多少的內存,程序員本身負責在什麼時候用free 或delete 釋放內存。動態內存的生存期由程序員決定,使用很是靈活,但問題也最多。
18.分別寫出BOOL,int,float,指針類型的變量a 與「零」的比較語句。
答案:
BOOL : if ( !a ) or if(a)
int : if ( a == 0)
float : const EXPRESSION EXP = 0.000001
if ( a < EXP && a >-EXP)
pointer : if ( a != NULL) or if(a == NULL)
19.請說出const與#define 相比,有何優勢?
答案:const做用:定義常量、修飾函數參數、修飾函數返回值三個做用。被Const修飾的東西都受到強制保護,能夠預防意外的變更,能提升程序的健壯性。
1) const 常量有數據類型,而宏常量沒有數據類型。編譯器能夠對前者進行類型安全檢查。而對後者只進行字符替換,沒有類型安全檢查,而且在字符替換可能會產生意料不到的錯誤。
2) 有些集成化的調試工具能夠對const 常量進行調試,可是不能對宏常量進行調試。
20.簡述數組與指針的區別?
數組要麼在靜態存儲區被建立(如全局數組),要麼在棧上被建立。指針能夠隨時指向任意類型的內存塊。
(1)修改內容上的差異
char a[] = 「hello」;
a[0] = ‘X’;
char *p = 「world」; // 注意p 指向常量字符串
p[0] = ‘X’; // 編譯器不能發現該錯誤,運行時錯誤
(2) 用運算符sizeof 能夠計算出數組的容量(字節數)。sizeof(p),p 爲指針獲得的是一個指針變量的字節數,而不是p 所指的內存容量。C++/C 語言沒有辦法知道指針所指的內存容量,除非在申請內存時記住它。注意當數組做爲函數的參數進行傳遞時,該數組自動退化爲同類型的指針。
char a[] = "hello world";
char *p = a;
cout<< sizeof(a) << endl; // 12 字節
cout<< sizeof(p) << endl; // 4 字節
計算數組和指針的內存容量
void Func(char a[100])
{
cout<< sizeof(a) << endl; // 4 字節而不是100 字節
}
第21題: int (*s[10])(int) 表示的是什麼?
int (*s[10])(int) 函數指針數組,每一個指針指向一個int func(int param)的函數。
第23題:將程序跳轉到指定內存地址
要對絕對地址0x100000賦值,咱們能夠用(unsigned int*)0x100000 = 1234;那麼要是想讓程序跳轉到絕對地址是0x100000去執行,應該怎麼作?
*((void (*)( ))0x100000 ) ( ); 首先要將0x100000強制轉換成函數指針,即: (void (*)())0x100000 而後再調用它: *((void (*)())0x100000)(); 用typedef能夠看得更直觀些: typedef void(*)() voidFuncPtr; *((voidFuncPtr)0x100000)();
第24題:int id[sizeof(unsigned long)];這個對嗎?爲何?
答案:正確 這個 sizeof是編譯時運算符,編譯時就肯定了 ,能夠當作和機器有關的常量。
第26題:const 與 #define 的比較,const有什麼優勢?
(1) const 常量有數據類型,而宏常量沒有數據類型。編譯器能夠對前者進行類型安全檢查。而對後者只進行字符替換,沒有類型安全檢查,而且在字符替換可能會產生意料不到的錯誤(邊際效應) 。
(2) 有些集成化的調試工具能夠對 const 常量進行調試,可是不能對宏常量進行調試。
第29題:基類的析構函數不是虛函數,會帶來什麼問題?
【參考答案】派生類的析構函數用不上,會形成資源的泄漏。
第30題:全局變量和局部變量有什麼區別?是怎麼實現的?操做系統和編譯器是怎麼知道的?
【參考答案】
生命週期不一樣:
全局變量隨主程序建立和建立,隨主程序銷燬而銷燬;局部變量在局部函數內部,甚至局部循環體等內部存在,退出就不存在;
使用方式不一樣:經過聲明後全局變量程序的各個部分均可以用到;局部變量只能在局部使用;分配在棧區。
操做系統和編譯器經過內存分配的位置來知道的,全局變量分配在全局數據段而且在程序開始運行的時候被加載。局部變量則分配在堆棧裏面 。
C++類在內存中的空間分配
注意點1:一個類對象的地址就是類所包含的這一片內存空間的首地址,這個首地址對應具體該類某一個成員變量的地址。
注意點2:類的成員函數不佔用棧空間
類自己是不佔有內存的,但是 若是類生成實例那麼將會在內存中分配一塊內存來存儲這個類。
文本文件和二進制文件存取
文本文件和二進制文件的定義:
計算機在物理內存上面存放的都是二進制,因此文本文件和二進制文件的主要區別是在邏輯上的而不是物理上的。而從文件的編碼方式來看,文件能夠分爲文本文件和二進制文件。文本文件是基於字符編碼的文件,常見的有ASCII、Unicode等,二進制文件是基於值編碼的文件,能夠當作是變長編碼,你能夠根據本身的須要,決定多少個比特表明一個值。
文本文件和二進制文件的存儲:
二進制文件就是把內存中的數據按其在內存中存儲的形式原樣輸出到磁盤中存放,即存放的是數據的原形式。
文本文件是把數據的終端形式的二進制數據輸出到磁盤上存放,即存放的是數據的終端形式
在實際存儲中最好是將數據分紅字符數據和非字符數據兩類:
若是存儲的是字符數據,不管採用文本文件仍是二進制文件都是沒有任何區別的,因此討論使用文本文件仍是二進制文件是沒有意義的。
若是存儲的是非字符數據,又要看咱們使用的狀況來決定:
a:若是是須要頻繁的保存和訪問數據,那麼應該採起二進制文件進行存放,這樣能夠節省存儲空間和轉換時間。
B:若是須要頻繁的向終端顯示數據或從終端讀入數據,那麼應該採用文本文件進行存放,這樣能夠節省轉換時間。
文本文件的打開方式和二進制文件打開方式的區別:
(1)文本模式中回車被當成一個字符'\n',在文件中若是讀到0x1B,文本模式會認爲這是文件結束符,會按照必定方式對數據作相應的轉換。
(2)二進制模式中'\n'會被認爲是兩個字符0x0D,0x0A;在讀到0x1B時,二進制模式不會對文件進行處理。
C和C++中struct的區別
1. C中struct是用戶自定義數據類型(UDT);
C++中struct是抽象數據類型(ADT),支持成員函數的定義,(C++中的struct能繼承,能實現多態)。
2. C中struct是沒有權限的設置的,且struct中只能是一些變量的集合體,能夠封裝數據卻不能夠隱藏數據,並且成員不能夠是函數。
C++中,struct的成員默認訪問說明符爲public(爲了與C兼容),class中的默認訪問限定符爲private,struct增長了訪問權限,且能夠和類同樣有成員函數。
struct做爲類的一種特例是用來自定義數據結構的。一個結構標記聲明後,
3. C中必須在結構標記前加上struct,才能作結構類型名(除:typedef struct class{};);
C++中結構體標記(結構體名)能夠直接做爲結構體類型名使用,此外結構體struct在C++中被看成類的一種特例。
求出兩個長鏈表交叉的那個結點
方法一
兩個沒有環的鏈表若是是相交於某一結點,如上圖所示,這個結點後面都是共有的。因此若是兩個鏈表相交,那麼兩個鏈表的尾結點的地址也是同樣的。程序實現時分別遍歷兩個單鏈表,直到尾結點。判斷尾結點地址是否相等便可。時間複雜度爲O(L1+L2)。
如何找到第一個相交結點?判斷是否相交的時候,記錄下兩個鏈表的長度,算出長度差len,接着先讓較長的鏈表遍歷len個長度,而後兩個鏈表同時遍歷,判斷是否相等,若是相等,就是第一個相交的結點。
void Is_2List_Intersect(LinkList L1, LinkList L2) {
if (L1 == NULL || L2 == NULL) {
exit(ERROR);
}
LinkList p = L1;
LinkList q = L2;
int L1_length = 0;
int L2_length = 0;
int len = 0;
while (p->next) {
L1_length ++;
p = p->next;
}
while (q->next) {
L2_length ++;
q = q->next;
}
printf("p: = %d\n", p);
printf("q: = %d\n", q);
printf("L1_length: = %d\n", L1_length);
printf("L2_length: = %d\n", L2_length);
if (p == q) {
printf(" 相交\n");
/*p從新指向短的鏈表 q指向長鏈表*/
if (L1_length > L2_length) {
len = L1_length - L2_length;
p = L2->next;
q = L1->next;
}
else {
len = L2_length - L1_length;
p = L1->next;
q = L2->next;
}
while (len) {
q = q->next;
len--;
}
while (p != q) {
p = p->next;
q = q->next;
}
printf("相交的第一個結點是:%d\n", p->data );
}
else {
printf("不相交 \n");
}
}
用宏表示一年的秒數
#define SECONDS_PER_YEAR 60*60*24*365(UL)
1.hello程序的生命週期是從一個高級c語言程序開始的,而後爲了在系統上運行hello.c程序,每條c語句都必須被其餘程序轉化爲一系列的低級機器語言指令。
2.預處理階段。預處理器(cpp)根據以字符#開頭的命令,修改原始的C程序。#include <stdio.h>命令告訴預處理器讀取系統頭文件stdio.h的內容,並將它直接插入到程序文本中。結果就獲得另外一個C程序,一般以.i做爲文件擴展名。
3.編譯階段。編譯器(ccl)將文本文件hello.i翻譯成文本文件hello.s。它包含一個彙編語言程序。彙編語言程序中的每條語句都以一種標準的文本格式確切地描述了一條低級機器語言指令。彙編語言爲不一樣編譯器提供了通用的輸出語言。
4.彙編階段。彙編器(as)將hello.s翻譯成機器語言指令。並將結果保存在目標文件hello.o中。hello.o是一種二進制文件。它的字節編碼是機器語言指令而不是字符。
5.鏈接階段。hello程序調用printf函數。它是c編譯器都會提供的標準c庫中的一個函數。printf函數存在於一個名爲printf.o的單獨的預編譯好的目標文件中,而這個文件必須以某種方式合併到咱們的hello.o程序中。鏈接器就是負責這種合併的。
c語言中,常見數據類型的字節數
32位編譯器
char :1個字節
char*(即指針變量): 4個字節(32位的尋址空間是2^32, 即32個bit,也就是4個字節。同理64位編譯器)
short int : 2個字節
int: 4個字節
unsigned int : 4個字節
float: 4個字節
double: 8個字節
long: 4個字節
long long: 8個字節
unsigned long: 4個字節
64位編譯器
char :1個字節
char*(即指針變量): 8個字節
short int : 2個字節
int: 4個字節
unsigned int : 4個字節
float: 4個字節
double: 8個字節
long: 8個字節
long long: 8個字節
unsigned long: 8個字節
c++常見容器,vector容器capacity和size區別,如何動態增加
在Vector容器中有如下幾個關於大小的函數
size() 返回容器的大小
empty() 判斷容器是否爲空
max_size() 返回容器最大的能夠存儲的元素
capacity() 返回容器當前可以容納的元素數量
1. 容器的大小一旦超過capacity的大小,vector會從新配置內部的存儲器,致使和vector元素相關的全部reference、pointers、iterator都會失效。
2.內存的從新配置會很耗時間。
Vector內存擴展方式
vector內存成長方式可歸結如下三步曲:
(1)另覓更大空間;
(2)將原數據複製過去;
(3)釋放原空間三部曲。
vector遍歷有哪幾種方式(儘量多)
for (size_t i =0; i < vec.size(); i ++) {
}
for_each(ivec.begin(),ivec.end(),fun);
cv:Mat 有幾種訪問方式
初始化
初始化一個Mat文件出來通常有兩種形式:
// 1、imread
Mat src = imread("csdn.png");
//2、create
Mat src;
if(src.empty())
{
src.create(Size size, VC_8UC3);
}
訪問方式:
at<type>(i,j)訪問
例:int ROWS = 100; // height
int COLS = 200; // width
Mat img1(ROWS , COLS , CV_32FC1);
for (int i=0; i<ROWS ; i++)
{
for (int j=0; j<COLS ; j++)
{
img1.at<float>(i,j) = 3.2f;
}
}
方式2: ptr<type>(i) [j] 方式
例:for (int i=0; i<ROWS ; i++)
{
float* pData1=img5.ptr<float>(i);
for (int j=0; j<COLS ; j++)
{
pData1[j] = 3.2f;
}
}
例:方式3:img.data + step[0]*i + step[1]*j 方式
map容器增刪改查,和unorder_map區別,map底層如何實現
增
numCountMap.insert({numName,thisAddTime});
numCountMap.insert(make_pair(numName,thisAddTime));
numCountMap.insert(pair<int,int>(numName,thisAddTime));
numCountMap.insert(map<int,int>::value_type(numName,thisAddTime));
遍歷
for(const auto &it : numCountMap)
{}
查
map<int,int>::iterator it=numCountMap.find(alterNum);
刪
int eraseReturn=numCountMap.erase(1);
修改和訪問
int alterNum=3;
map<int,int>::iterator alterit=numCountMap.find(alterNum);
if(alterit!=numCountMap.end())
{
alterit->second=6;
cout<<"alter num 3 occurs 6 time"<<endl;
}
c++智能指針
C++11中引入了智能指針的概念,方便管理堆內存。
從較淺的層面看,智能指針是利用了一種叫作RAII(資源獲取即初始化)的技術對普通的指針進行封裝,這使得智能指針實質是一個對象,行爲表現的卻像一個指針。
智能指針的做用是防止忘記調用delete釋放內存和程序異常的進入catch塊忘記釋放內存。另外指針的釋放時機也是很是有考究的,屢次釋放同一個指針會形成程序崩潰,這些均可以經過智能指針來解決。
智能指針還有一個做用是把值語義轉換成引用語義。
.智能指針的使用
智能指針在C++11版本以後提供,包含在頭文件<memory>中,shared_ptr、unique_ptr、weak_ptr
c++14/17新特性
C++11包括大量的新特性:包括lambda表達式,類型推導關鍵字auto、decltype,和模板的大量改進。
C++14的主要特性能夠分爲三個領域:Lambda函數、constexpr和類型推導。
C++ 17 旨在使 C++ 成爲一個不那麼臃腫複雜的編程語言,以簡化該語言的平常使用,使開發者能夠更簡單地編寫和維護代碼。
C++ 17 是對 C++ 語言的重大更新,引入了許多新的語言特性:
UTF-8 字符文字;
摺疊表達式 (fold expressions):用於可變的模板;
內聯變量 (inline variables):容許在頭文件中定義變量;
在 if 和 switch 語句內能夠初始化變量;
結構化綁定 (Structured Binding):for (auto [key,value] : my_map) {…};
虛函數和純虛函數區別
虛函數(impure virtual)
C++的虛函數主要做用是「運行時多態」,父類中提供虛函數的實現,爲子類提供默認的函數實現。
子類能夠重寫父類的虛函數實現子類的特殊化。
純虛函數(pure virtual)
C++中包含純虛函數的類,被稱爲是「抽象類」。抽象類不能使用new出對象,只有實現了這個純虛函數的子類才能new出對象。
C++中的純虛函數更像是「只提供申明,沒有實現」,是對子類的約束,是「接口繼承」。
C++中的純虛函數也是一種「運行時多態」。
虛函數表
模板
函數模板的寫法
函數模板的通常形式以下:
Template <class或者也能夠用typename T>
返回類型 函數名(形參表)
{//函數定義體 }
說明: template是一個聲明模板的關鍵字,表示聲明一個模板關鍵字class不能省略,若是類型形參多餘一個 ,每一個形參前都要加class <類型 形參表>能夠包含基本數據類型能夠包含類類型.
函數模板:模板(Templates)使得咱們能夠生成通用的函數,這些函數可以接受任意數據類型的參數,可返回任意類型的值,而不須要對全部可能的數據類型進行函數重載。這在必定程度上實現了宏(macro)的做用。
類模板:使得一個類能夠有基於通用類型的成員,而不須要在類生成的時候定義具體的數據類型
c++繼承多態
c++深拷貝淺拷貝
淺拷貝:編譯器僅僅拷貝告終構體的值,而沒有建立新的內存空間,而是共享同一塊內存空間
深拷貝:編譯器會爲拷貝的對象分配必定的內存空間
構造函數/委託構造函數/拷貝構造函數(深拷貝/淺拷貝)
構造函數——用於初始化對象
函數名與類名相同,不能有返回值類型,能夠有形式參數,也能夠沒有形式參數,能夠是inline函數,能夠重載,能夠帶默認參數值。
在對象建立時自動調用。
默認構造函數(default constructor):調用時能夠不須要實參的構造函數。有如下兩種:
1. 參數表爲空的構造函數
2. 所有參數都有默認值的構造函數。
委託構造函數:在一個類中重載多個構造函數時,這些函數只是形參不一樣,初始化列表不一樣,而初始化算法和函數體都是相同的。這個時候,爲了不重複,C++11新標準提供了委託構造函數。更重要的是,能夠保持代碼的一致性,若是之後要修改構造函數的代碼,只須要在一處修改便可。
拷貝構造函數
· 拷貝構造函數是一種特殊的構造函數,其形參爲本類對象的引用。
· 做用:用一個已經存在的對象去初始化同類型的新對象。
三種典型狀況須要調用拷貝構造函數:
1. 定義一個對象時,用本類的另外一個對象做爲初始值,發生拷貝構造;
2. 若是函數的形參是類的對象,調用函數時,將使用實參對象初始化形參對象,發生拷貝構造;
3. 若是函數的返回值是類的對象時,函數執行完成返回主函數時,將使用return語句中的對象初始化一個臨時無名對象,傳遞給主調函數,此時發生拷貝構造。
https://blog.csdn.net/weixin_39924163/article/details/80171448
emplace_back和push_back區別
emplace_back和push_back都是向容器內添加數據.
對於在容器中添加類的對象時, 相比於push_back,emplace_back能夠避免額外類的複製和移動操做.
class CExample
{
private:
int a;
public:
//構造函數
CExample(int b)
{
a=b;
printf("constructor is called\n");
}
//拷貝構造函數
CExample(const CExample & c)
{
a=c.a;
printf("copy constructor is called\n");
}
//析構函數
~CExample()
{
cout<<"destructor is called\n";
}
void Show()
{
cout<<a<<endl;
}
};
如何實現一個c++的單例模式
內聯函數和宏的區別
內聯函數和宏很相似,而區別在於,宏是由預處理器對宏進行替代,而內聯函數是經過編譯器控制來實現的。並且內聯函數是真正的函數,只是在須要用到的時候,內聯函數像宏同樣的展開,因此取消了函數的參數壓棧,減小了調用的開銷。你能夠象調用函數同樣來調用內聯函數,而沒必要擔憂會產生於處理宏的一些問題。\
如何實現一個只在堆或者棧上初始化的類
只能創建在堆上
將析構函數設爲私有,類對象就沒法創建在棧上了。代碼以下:
1. class A
2. {
3. public :
4. A(){}
5. void destory(){ delete this ;}
6. private :
7. ~A(){}
8. };
只能創建在棧上
只要禁用new運算符就能夠實現類對象只能創建在棧上
指針函數和函數指針
指針函數
int*fun(intx,inty);
函數指針
int (*fun)(intx,inty);
指針數組和數組指針
數組指針 (*p)[n]
數組指針:是指針——指向數組的指針。
指針數組 *p[n]
指針數組:是數組——裝着指針的數組。
· define和inline(編譯哪一個階段?)
define:定義預編譯時處理的宏,只是簡單的字符串替換,無類型檢查。
inline:1、inline關鍵字用來定義一個類的內聯函數,引入它的主要緣由是用它替代C中表達式形式的宏定義,編譯階段完成。 2、內聯函數要作類型安全檢查,inline是指嵌入代碼,在調用函數的地方不是跳轉,而是把代碼直接寫到那裏去,對於短小的函數來講,inline函數能夠獲得必定效率的提高,和c時代的宏函數相比,inline函數更加安全可靠,這個是以增長空間的消耗爲代價的。
ps:定義內聯函數只是給編譯器一個建議,可是最後的決定權仍是在於編譯器,若是函數的邏輯比較複雜(有循環遞歸之類的),此時則會內聯失敗。根據google編程規範,內聯函數通常在10行如下,並且邏輯簡單。
· 返回局部變量?
函數不能返回指向棧內存的指針
緣由:返回值是拷貝值,局部變量的做用域爲函數內部,函數執行結束,棧上的局部變量會銷燬,內存釋放。
可返回的局部變量:
1. 返回局部變量自己
2.常量:
3. 靜態局部變量
當屢次調用一個函數且要求在調用之間保留某些變量的值時,可考慮採用靜態局部變量。
4. 堆內存中的局部變量
· 子函數返回結構體有什麼問題?返回對象調用了哪些函數?
C 語言中函數返回結構體時若是結構體較大, 則在調用函數中產生該結構的臨時變量,並將該變量首地址傳遞給被調用函數,被調用函數返回時根據該地址修改此臨時變量的內容,以後在調用函數中再將該變量複製給用戶定義的變量,這也正是 C 語言中所謂值傳遞的工做方式。
若是結構體較小, 則函數返回時所用的臨時變量可保存在寄存器中,返回後將寄存器的值複製給用戶定義的變量便可。
· volatile幹嗎的?
volatile 關鍵字是一種類型修飾符,用它聲明的類型變量表示能夠被某些編譯器未知的因素更改,好比:操做系統、硬件或者其它線程等。遇到這個關鍵字聲明的變量,編譯器對訪問該變量的代碼就再也不進行優化,從而能夠提供對特殊地址的穩定訪問。
volatile用在以下的三個地方:
中斷服務程序中修改的供其它程序檢測的變量須要加volatile;
多任務環境下各任務間共享的標誌應該加volatile;
存儲器映射的硬件寄存器一般也要加volatile說明,由於每次對它的讀寫均可能由不一樣意義;
· 編譯器基本原理?
gcc編譯器
編譯器在開始工做以前,須要知道當前的系統環境,好比標準庫在哪裏、軟件的安裝位置在哪裏、須要安裝哪些組件等等。這是由於不一樣計算機的系統環境不同,經過指定編譯參數,編譯器就能夠靈活適應環境,編譯出各類環境都能運行的機器碼。這個肯定編譯參數的步驟,就叫作」配置」(configure)。
這些配置信息保存在一個配置文件之中,約定俗成是一個叫作configure的腳本文件。一般它是由autoconf工具生成的。編譯器經過運行這個腳本,獲知編譯參數。
源碼確定會用到標準庫函數(standard library)和頭文件(header)。它們能夠存放在系統的任意目錄中,編譯器實際上沒辦法自動檢測它們的位置,只有經過配置文件才能知道。
編譯的第二步,就是從配置文件中知道標準庫和頭文件的位置。通常來講,配置文件會給出一個清單,列出幾個具體的目錄。等到編譯時,編譯器就按順序到這幾個目錄中,尋找目標。
對於大型項目來講,源碼文件之間每每存在依賴關係,編譯器須要肯定編譯的前後順序。假定A文件依賴於B文件,編譯器應該保證作到下面兩點。
(1)只有在B文件編譯完成後,纔開始編譯A文件。
(2)當B文件發生變化時,A文件會被從新編譯。
編譯順序保存在一個叫作makefile的文件中,裏面列出哪一個文件先編譯,哪一個文件後編譯。而makefile文件由configure腳本運行生成,這就是爲何編譯時configure必須首先運行的緣由。
在肯定依賴關係的同時,編譯器也肯定了,編譯時會用到哪些頭文件。
不一樣的源碼文件,可能引用同一個頭文件(好比stdio.h)。編譯的時候,頭文件也必須一塊兒編譯。爲了節省時間,編譯器會在編譯源碼以前,先編譯頭文件。這保證了頭文件只需編譯一次,沒必要每次用到的時候,都從新編譯了。
不過,並非頭文件的全部內容,都會被預編譯。用來聲明宏的#define命令,就不會被預編譯。
預編譯完成後,編譯器就開始替換掉源碼中bash的頭文件和宏。以本文開頭的那段源碼爲例,它包含頭文件stdio.h,替換後的樣子以下。
1.
externintfputs(constchar *, FILE *);
2.
extern FILE *stdout;
3.
intmain(void)
4. {
5.
fputs("Hello, world!\n", stdout);
6.
return0;
7. }
爲了便於閱讀,上面代碼只截取了頭文件中與源碼相關的那部分,即fputs和FILE的聲明,省略了stdio.h的其餘部分(由於它們很是長)。另外,上面代碼的頭文件沒有通過預編譯,而實際上,插入源碼的是預編譯後的結果。編譯器在這一步還會移除註釋。
這一步稱爲」預處理」(Preprocessing),由於完成以後,就要開始真正的處理了。
預處理以後,編譯器就開始生成機器碼。對於某些編譯器來講,還存在一箇中間步驟,會先把源碼轉爲彙編碼(assembly),而後再把彙編碼轉爲機器碼。
下面是本文開頭的那段源碼轉成的彙編碼。
種轉碼後的文件稱爲對象文件(object file)。
對象文件還不能運行,必須進一步轉成可執行文件。若是你仔細看上一步的轉碼結果,會發現其中引用了stdout函數和fwrite函數。也就是說,程序要正常運行,除了上面的代碼之外,還必須有stdout和fwrite這兩個函數的代碼,它們是由C語言的標準庫提供的。
編譯器的下一步工做,就是把外部函數的代碼(一般是後綴名爲.lib和.a的文件),添加到可執行文件中。這就叫作鏈接(linking)。這種經過拷貝,將外部函數庫添加到可執行文件的方式,叫作靜態鏈接(static linking),後文會提到還有動態鏈接(dynamic linking)。
make命令的做用,就是從第四步頭文件預編譯開始,一直到作完這一步。
上一步的鏈接是在內存中進行的,即編譯器在內存中生成了可執行文件。下一步,必須將可執行文件保存到用戶事先指定的安裝目錄。
表面上,這一步很簡單,就是將可執行文件(連帶相關的數據文件)拷貝過去就好了。可是實際上,這一步還必須完成建立目錄、保存文件、設置權限等步驟。這整個的保存過程就稱爲」安裝」(Installation)。
可執行文件安裝後,必須以某種方式通知操做系統,讓其知道可使用這個程序了。好比,咱們安裝了一個文本閱讀程序,每每但願雙擊txt文件,該程序就會自動運行。
這就要求在操做系統中,登記這個程序的元數據:文件名、文件描述、關聯後綴名等等。Linux系統中,這些信息一般保存在/usr/share/applications目錄下的.desktop文件中。另外,在Windows操做系統中,還須要在Start啓動菜單中,創建一個快捷方式。
這些事情就叫作」操做系統鏈接」。make install命令,就用來完成」安裝」和」操做系統鏈接」這兩步。
寫到這裏,源碼編譯的整個過程就基本完成了。可是隻有不多一部分用戶,願意耐着性子,從頭至尾作一遍這個過程。事實上,若是你只有源碼能夠交給用戶,他們會認定你是一個不友好的傢伙。大部分用戶要的是一個二進制的可執行程序,馬上就能運行。這就要求開發者,將上一步生成的可執行文件,作成能夠分發的安裝包。
因此,編譯器還必須有生成安裝包的功能。一般是將可執行文件(連帶相關的數據文件),以某種目錄結構,保存成壓縮文件包,交給用戶。
正常狀況下,到這一步,程序已經能夠運行了。至於運行期間(runtime)發生的事情,與編譯器一律無關。可是,開發者能夠在編譯階段選擇可執行文件鏈接外部函數庫的方式,究竟是靜態鏈接(編譯時鏈接),仍是動態鏈接(運行時鏈接)。因此,最後還要提一下,什麼叫作動態鏈接。
前面已經說過,靜態鏈接就是把外部函數庫,拷貝到可執行文件中。這樣作的好處是,適用範圍比較廣,不用擔憂用戶機器缺乏某個庫文件;缺點是安裝包會比較大,並且多個應用程序之間,沒法共享庫文件。動態鏈接的作法正好相反,外部函數庫不進入安裝包,只在運行時動態引用。好處是安裝包會比較小,多個應用程序能夠共享庫文件;缺點是用戶必須事先安裝好庫文件,並且版本和安裝位置都必須符合要求,不然就不能正常運行。
現實中,大部分軟件採用動態鏈接,共享庫文件。這種動態共享的庫文件,Linux平臺是後綴名爲.so的文件,Windows平臺是.dll文件,Mac平臺是.dylib文件。
· 數組和指針(重點難在二維)
int **a;
a = new int*[M];
for (int x = 0; x < M; x++)
a[x] = new int[M];
面向對象的編程的主要思想
和數據封裝其中,以提升程序的重用性,靈活性和可擴展性。類是建立對象的模板,一個類能夠建立多個對象。對象是類的實例化。
類是抽象的,不佔用存儲空間;而對象具體的,佔用存儲空間。
面向對象有三大特性:封裝,繼承,多態。
1.C++的三大特性爲:繼承,多態,封裝
(1)繼承。一個對象直接使用另外一個對象的屬性和方法。
優勢:1.減小重複的代碼。
2.繼承是多態的前提。
3.繼承增長了類的耦合性。
缺點:1.繼承在編譯時刻就定義了,沒法在運行時刻改變父類繼承的實現;
2.父類一般至少定義了子類的部分行爲,父類的改變均可能影響子類的行爲;
3.若是繼承下來的子類不適合解決新問題,父類必須重寫或替換,那麼這種依賴關係就限制了靈活性,最終限制了複用性。
繼承的方式有三種分別爲公有繼承(public),保護繼承(protect),私有繼承(private)。
虛繼承:
爲了解決從不一樣途徑繼承來的同名的數據成員在內存中有不一樣的拷貝形成數據不一致問題,將共同基類設置爲虛基類。這時從不一樣的路徑繼承過來的同名數據成員在內存中就只有一個拷貝,同一個函數名也只有一個映射。這樣不只就解決了二義性問題,也節省了內存,避免了數據不一致的問題。
class 派生類名:virtual 繼承方式 基類名
virtual是關鍵字,聲明該基類爲派生類的虛基類。
在多繼承狀況下,虛基類關鍵字的做用範圍和繼承方式關鍵字相同,只對緊跟其後的基類起做用。
聲明瞭虛基類以後,虛基類在進一步派生過程當中始終和派生類一塊兒,維護同一個基類子對象的拷貝。
(2)多態。
C++中有兩種多態,稱爲動多態(運行期多態)和靜多態(編譯期多態),
多態:是對於不一樣對象接收相同消息時產生不一樣的動做。C++的多態性具體體如今運行和編譯兩個方面:
在程序運行時的多態性經過繼承和虛函數來體現;
在程序編譯時多態性體如今函數和運算符的重載上;
虛函數就是多態的具體表現,虛函數的做用是容許在派生類中從新定義與基類同名的函數,而且能夠經過基類指針或引用來訪問基類和派生類中的同名函數。
虛函數如何實現的?
虛函數是經過一張虛函數表實現的,有多少個虛函數,就有多少個指針;在這個表中,主要是一個類的虛函數的地址表,這張表解決了繼承、覆蓋的問題;實際上在編譯的時候,編譯器會自動加上虛表虛函數的做用實現動態聯編,也就是說在程序運行階段動態的選擇合適的成員函數,在定義了虛函數以後,能夠在基類的派生類中對虛函數從新定義。虛表的使用方法是若是派生類在本身定義中沒有修改基類的虛函數,咱們就指向基類的虛函數;若是派生類改寫了基類的虛函數,這時續表則將原來指向基類的虛函數的地址替換爲指向自身虛函數的指針。必須經過基類類型的引用或指針進行函數調用纔會發生多態
重載,是指「同一標識符」在同一做用域的不一樣場合具備不一樣的語義,這個標識符能夠是函數名或運算符
接口的多種不一樣實現方式即爲多態。
優勢:1.大大提升了代碼的可複用性;
2.提升了了代碼的可維護性,可擴充性;
缺點: 1)易讀性比較很差,調試比較困難
2)模板只能定義在.h文件中,當工程大了以後,編譯時間十分的變態
(3)封裝。隱藏對象的屬性和實現細節,僅僅對外提供接口和方法。
優勢: 1)隔離變化;2)便於使用; 3)提升重用性; 4)提升安全性
缺點: 1)若是封裝太多,影響效率; 2)使用者不能知道代碼具體實現。
重載(overload)和覆蓋(override):
重載:寫一個與已有函數同名可是參數表不一樣的函數;
覆蓋:虛函數老是在派生類中被改寫。
派生類
派生類的定義格式以下:
class <派生類名>:[繼承方式]<基類名1>
[,[繼承方式]<基類名2>,...,[繼承方式]<基類名n>]
{
<派生類新增的數據成員和成員函數定義>
};
說明:
(1)定義派生類關鍵字能夠是class或者是struct,二者區別是:用class定義派生類,默認的繼承方式是private,用struct定義派生類,默認的繼承方式爲public。新增長的成員默認屬性也是class對應private屬性,struct對應public屬性。
(2)基類不能被派生類繼承的兩類函數是構造函數和析構函數。
1.C和C++的區別
1、主要區別:
C語言是面向過程的編程,它最重要的特色是函數,經過main函數來調用各個子函數。程序運行的順序都是程序員事先決定好的。
C++是面向對象的編程,類是它的主要特色,在程序執行過程當中,先由主main函數進入,定義一些類,根據須要執行類的成員函數,過程的概念被淡化了,以類驅動程序運行,類就是對象,因此咱們稱之爲面向對象程序設計。面向對象在分析和解決問題的時候,將涉及到的數據和數據的操做封裝在類中,經過類能夠建立對象,以事件或消息來驅動對象執行處理。
2、聯繫:c是c++的子集,因此大部c語言程序均可以不加修改的拿到c++下使用。
2.C++和JAVA區別
Java面向對象,沒有指針,編寫效率高,執行效率較低。
java內存自動回收(GC垃圾回收機制),多線程編程。
JAVA的應用在高層,C++在中間件和底層
c++用析構函數回收垃圾,java自動回收(GC算法)
Java比C++程序可靠性更高
Java語言不須要程序對內存進行分配和回收
Java語言中沒有指針的概念,引入了真正的數組
Java用接口(Interface)技術取代C++程序中的多繼承性
3. const 有什麼用途
主要有三點:
1:定義只讀變量,即常量
2:修飾函數的參數和函數的返回值
const能夠修飾函數的返回值,參數及,函數的定義體,被const修飾會受到強制的保護,能防止意外的修改,從而提升函數的健壯性。
3: 修飾函數的定義體,這裏的函數爲類的成員函數,被const修飾的成員函數表明不修改爲員變量的值。
3. 指針和引用的區別
1:引用是變量的一個別名,內部實現是隻讀指針
2:引用只能在初始化時被賦值,其餘時候值不能被改變,指針的值能夠在任什麼時候候被改變
3:引用不能爲NULL,指針能夠爲NULL
4:引用變量內存單元保存的是被引用變量的地址
5:「sizeof 引用" = 指向變量的大小 , "sizeof 指針"= 指針自己的大小
6:引用能夠取地址操做,返回的是被引用變量自己所在的內存單元地址
7:引用使用在源代碼級至關於普通的變量同樣使用,作函數參數時,內部傳遞的實際是變量地址
4. C++中有了malloc / free , 爲何還須要 new / delet e
1,malloc與free是C++/C語言的標準庫函數,new/delete是C++的運算符。它們均可用於申請動態內存和釋放內存。
2,對於非內部數據類型的對象而言,光用malloc/free沒法知足動態對象的要求。
對象在建立的同時要自動執行構造函數,對象在消亡以前要自動執行析構函數。
因爲malloc/free是庫函數而不是運算符,不在編譯器控制權限以內,不可以把執行構造函數和析構函數的任務強加於malloc/free。
3,所以C++語言須要一個能完成動態內存分配和初始化工做的運算符new,以一個能完成清理與釋放內存工做的運算符delete。注意new/delete不是庫函數。
new/delete、malloc/free底層實現原理:
概述:new/delete的底層實現是調用malloc/free函數實現的,而malloc/free的底層實現也不是直接操做內存而是調用系統API實現的。
5.delete與 delete []區別
delete只會調用一次析構函數,而delete[]會調用每個成員的析構函數。
6. 編寫類String 的構造函數,析構函數,拷貝構造函數和賦值函數
https://blog.csdn.net/zz460833359/article/details/46651401
7. 單鏈表的逆置
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (head == NULL || head->next == NULL) return head;
ListNode* rHead = reverseList(head->next); // 反轉獲得新鏈表的頭節點
head->next->next = head; // 當前節點的下一個節點的next指針反轉過來
head->next = NULL; // 設置新鏈表的尾節點
return rHead;
}
}; 8. 堆和棧的區別
一個由C/C++編譯的程序佔用的內存分爲如下幾個部分
1、棧區(stack)― 由編譯器自動分配釋放 ,存放函數的參數值,局部變量的值等。其操做方式相似於數據結構中的棧。
2、堆區(heap) ― 通常由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收 。
注意它與數據結構中的堆是兩回事,分配方式卻是相似於鏈表,呵呵。
3、全局區(靜態區)(static)― 全局變量和靜態變量的存儲是放在一塊的,
初始化的全局變量和靜態變量在一塊區域, 未初始化的全局變量和未初始化的靜態變量在相鄰的另外一塊區域。 - 程序結束後有系統釋放
4、文字常量區 ―常量字符串就是放在這裏的。 程序結束後由系統釋放
5、程序代碼區―存放函數體的二進制代碼。
棧區與堆區的區別:
1)堆和棧中的存儲內容:棧存局部變量、函數參數等。堆存儲使用new、malloc申請的變量等;
2)申請方式:棧內存由系統分配,堆內存由本身申請;
3)申請後系統的響應:棧——只要棧的剩餘空間大於所申請空間,系統將爲程序提供內存,不然將報異常提示棧溢出。
堆——首先應該知道操做系統有一個記錄空閒內存地址的鏈表,當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大於所申請空間的堆結點,而後將該結點從空閒結點鏈表 中刪除,並將該結點的空間分配給程序;
4)申請大小的限制:Windows下棧的大小通常是2M,堆的容量較大;
5)申請效率的比較:棧由系統自動分配,速度較快。堆使用new、malloc等分配,較慢;
總結:棧區優點在處理效率,堆區優點在於靈活;
內存模型:自由區、靜態區、動態區;
根據c/c++對象生命週期不一樣,c/c++的內存模型有三種不一樣的內存區域,即:自由存儲區,動態區、靜態區。
自由存儲區:局部非靜態變量的存儲區域,即日常所說的棧;
動態區: 用new ,malloc分配的內存,即日常所說的堆;
靜態區:全局變量,靜態變量,字符串常量存在的位置;
注:代碼雖然佔內存,但不屬於c/c++內存模型的一部分;
13. 頭文件種的ifndef/define/endif 是幹什麼用的
主要用於防止重複定義宏和重複包含頭文件
8、動態規劃的本質
答:動歸,本質上是一種劃分子問題的算法,站在任何一個子問題的處理上看,當前子問題的提出都要依據現有的相似結論,而當前問題的結論是後面問題求解的鋪墊。任何動態規劃都是基於存儲的算法,核心是狀態轉移方程。
析構函數的調用狀況
析構函數調用的次序是先派生類的析構後基類的析構,也就是說在基類的的析構調用的時候,派生類的信息已經所有銷燬了。定義一個對象時先調用基類的構造函數、而後調用派生類的構造函數;析構的時候剛好相反:先調用派生類的析構函數、而後調用基類的析構函數。
11、結構與聯合有和區別?
(1). 結構和聯合都是由多個不一樣的數據類型成員組成, 但在任何同一時刻, 聯合中只存放了一個被選中的成員(全部成員共用一塊地址空間), 而結構的全部成員都存在(不一樣成員的存放地址不一樣)。
(2). 對於聯合的不一樣成員賦值, 將會對其它成員重寫, 原來成員的值就不存在了, 而對於結構的不一樣成員賦值是互不影響的。
14.有哪幾種狀況只能用intialization list 而不能用assignment?
答案:當類中含有const、reference 成員變量;基類的構造函數都須要初始化表。
15. C++是否是類型安全的?
答案:不是。兩個不一樣類型的指針之間能夠強制轉換(用reinterpret cast)。C#是類型安全的。
16. main 函數執行之前,還會執行什麼代碼?
答案:全局對象的構造函數會在main 函數以前執行。
17. 描述內存分配方式以及它們的區別?
1) 從靜態存儲區域分配。內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在。例如全局變量,static 變量。
2) 在棧上建立。在執行函數時,函數內局部變量的存儲單元均可以在棧上建立,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置於處理器的指令集。
3) 從堆上分配,亦稱動態內存分配。程序在運行的時候用malloc 或new 申請任意多少的內存,程序員本身負責在什麼時候用free 或delete 釋放內存。動態內存的生存期由程序員決定,使用很是靈活,但問題也最多。
18.分別寫出BOOL,int,float,指針類型的變量a 與「零」的比較語句。
答案:
BOOL : if ( !a ) or if(a)
int : if ( a == 0)
float : const EXPRESSION EXP = 0.000001
if ( a < EXP && a >-EXP)
pointer : if ( a != NULL) or if(a == NULL)
19.請說出const與#define 相比,有何優勢?
答案:const做用:定義常量、修飾函數參數、修飾函數返回值三個做用。被Const修飾的東西都受到強制保護,能夠預防意外的變更,能提升程序的健壯性。
1) const 常量有數據類型,而宏常量沒有數據類型。編譯器能夠對前者進行類型安全檢查。而對後者只進行字符替換,沒有類型安全檢查,而且在字符替換可能會產生意料不到的錯誤。
2) 有些集成化的調試工具能夠對const 常量進行調試,可是不能對宏常量進行調試。
20.簡述數組與指針的區別?
數組要麼在靜態存儲區被建立(如全局數組),要麼在棧上被建立。指針能夠隨時指向任意類型的內存塊。
(1)修改內容上的差異
char a[] = 「hello」;
a[0] = ‘X’;
char *p = 「world」; // 注意p 指向常量字符串
p[0] = ‘X’; // 編譯器不能發現該錯誤,運行時錯誤
(2) 用運算符sizeof 能夠計算出數組的容量(字節數)。sizeof(p),p 爲指針獲得的是一個指針變量的字節數,而不是p 所指的內存容量。C++/C 語言沒有辦法知道指針所指的內存容量,除非在申請內存時記住它。注意當數組做爲函數的參數進行傳遞時,該數組自動退化爲同類型的指針。
char a[] = "hello world";
char *p = a;
cout<< sizeof(a) << endl; // 12 字節
cout<< sizeof(p) << endl; // 4 字節
計算數組和指針的內存容量
void Func(char a[100])
{
cout<< sizeof(a) << endl; // 4 字節而不是100 字節
}
第21題: int (*s[10])(int) 表示的是什麼?
int (*s[10])(int) 函數指針數組,每一個指針指向一個int func(int param)的函數。
第23題:將程序跳轉到指定內存地址
要對絕對地址0x100000賦值,咱們能夠用(unsigned int*)0x100000 = 1234;那麼要是想讓程序跳轉到絕對地址是0x100000去執行,應該怎麼作?
*((void (*)( ))0x100000 ) ( ); 首先要將0x100000強制轉換成函數指針,即: (void (*)())0x100000 而後再調用它: *((void (*)())0x100000)(); 用typedef能夠看得更直觀些: typedef void(*)() voidFuncPtr; *((voidFuncPtr)0x100000)();
第24題:int id[sizeof(unsigned long)];這個對嗎?爲何?
答案:正確 這個 sizeof是編譯時運算符,編譯時就肯定了 ,能夠當作和機器有關的常量。
第26題:const 與 #define 的比較 ,const有什麼優勢?
(1) const 常量有數據類型,而宏常量沒有數據類型。編譯器能夠對前者進行類型安全檢查。而對後者只進行字符替換,沒有類型安全檢查,而且在字符替換可能會產生意料不到的錯誤(邊際效應) 。
(2) 有些集成化的調試工具能夠對 const 常量進行調試,可是不能對宏常量進行調試。
第29題:基類的析構函數不是虛函數,會帶來什麼問題?
【參考答案】派生類的析構函數用不上,會形成資源的泄漏。
第30題:全局變量和局部變量有什麼區別?是怎麼實現的?操做系統和編譯器是怎麼知道的?
【參考答案】
生命週期不一樣:
全局變量隨主程序建立和建立,隨主程序銷燬而銷燬;局部變量在局部函數內部,甚至局部循環體等內部存在,退出就不存在;
使用方式不一樣:經過聲明後全局變量程序的各個部分均可以用到;局部變量只能在局部使用;分配在棧區。
操做系統和編譯器經過內存分配的位置來知道的,全局變量分配在全局數據段而且在程序開始運行的時候被加載。局部變量則分配在堆棧裏面 。
C++類在內存中的空間分配
注意點1:一個類對象的地址就是類所包含的這一片內存空間的首地址,這個首地址對應具體該類某一個成員變量的地址。
注意點2:類的成員函數不佔用棧空間
類自己是不佔有內存的,但是 若是類生成實例那麼將會在內存中分配一塊內存來存儲這個類。
文本文件和二進制文件存取
文本文件和二進制文件的定義:
計算機在物理內存上面存放的都是二進制,因此文本文件和二進制文件的主要區別是在邏輯上的而不是物理上的。而從文件的編碼方式來看,文件能夠分爲文本文件和二進制文件。文本文件是基於字符編碼的文件,常見的有ASCII、Unicode等,二進制文件是基於值編碼的文件,能夠當作是變長編碼,你能夠根據本身的須要,決定多少個比特表明一個值。
文本文件和二進制文件的存儲:
二進制文件就是把內存中的數據按其在內存中存儲的形式原樣輸出到磁盤中存放,即存放的是數據的原形式。
文本文件是把數據的終端形式的二進制數據輸出到磁盤上存放,即存放的是數據的終端形式
在實際存儲中最好是將數據分紅字符數據和非字符數據兩類:
若是存儲的是字符數據,不管採用文本文件仍是二進制文件都是沒有任何區別的,因此討論使用文本文件仍是二進制文件是沒有意義的。
若是存儲的是非字符數據,又要看咱們使用的狀況來決定:
a:若是是須要頻繁的保存和訪問數據,那麼應該採起二進制文件進行存放,這樣能夠節省存儲空間和轉換時間。
B:若是須要頻繁的向終端顯示數據或從終端讀入數據,那麼應該採用文本文件進行存放,這樣能夠節省轉換時間。
文本文件的打開方式和二進制文件打開方式的區別:
(1)文本模式中回車被當成一個字符'\n',在文件中若是讀到0x1B,文本模式會認爲這是文件結束符,會按照必定方式對數據作相應的轉換。
(2)二進制模式中'\n'會被認爲是兩個字符0x0D,0x0A;在讀到0x1B時,二進制模式不會對文件進行處理。
C和C++中struct的區別
1. C中struct是用戶自定義數據類型(UDT);
C++中struct是抽象數據類型(ADT),支持成員函數的定義,(C++中的struct能繼承,能實現多態)。
2. C中struct是沒有權限的設置的,且struct中只能是一些變量的集合體,能夠封裝數據卻不能夠隱藏數據,並且成員不能夠是函數。
C++中,struct的成員默認訪問說明符爲public(爲了與C兼容),class中的默認訪問限定符爲private,struct增長了訪問權限,且能夠和類同樣有成員函數。
struct做爲類的一種特例是用來自定義數據結構的。一個結構標記聲明後,
3. C中必須在結構標記前加上struct,才能作結構類型名(除:typedef struct class{};);
C++中結構體標記(結構體名)能夠直接做爲結構體類型名使用,此外結構體struct在C++中被看成類的一種特例。
求出兩個長鏈表交叉的那個結點
方法一
兩個沒有環的鏈表若是是相交於某一結點,如上圖所示,這個結點後面都是共有的。因此若是兩個鏈表相交,那麼兩個鏈表的尾結點的地址也是同樣的。程序實現時分別遍歷兩個單鏈表,直到尾結點。判斷尾結點地址是否相等便可。時間複雜度爲O(L1+L2)。
如何找到第一個相交結點?判斷是否相交的時候,記錄下兩個鏈表的長度,算出長度差len,接着先讓較長的鏈表遍歷len個長度,而後兩個鏈表同時遍歷,判斷是否相等,若是相等,就是第一個相交的結點。
void Is_2List_Intersect(LinkList L1, LinkList L2) {
if (L1 == NULL || L2 == NULL) {
exit(ERROR);
}
LinkList p = L1;
LinkList q = L2;
int L1_length = 0;
int L2_length = 0;
int len = 0;
while (p->next) {
L1_length ++;
p = p->next;
}
while (q->next) {
L2_length ++;
q = q->next;
}
printf("p: = %d\n", p);
printf("q: = %d\n", q);
printf("L1_length: = %d\n", L1_length);
printf("L2_length: = %d\n", L2_length);
if (p == q) {
printf(" 相交\n");
/*p從新指向短的鏈表 q指向長鏈表*/
if (L1_length > L2_length) {
len = L1_length - L2_length;
p = L2->next;
q = L1->next;
}
else {
len = L2_length - L1_length;
p = L1->next;
q = L2->next;
}
while (len) {
q = q->next;
len--;
}
while (p != q) {
p = p->next;
q = q->next;
}
printf("相交的第一個結點是:%d\n", p->data );
}
else {
printf("不相交 \n");
}
}
用宏表示一年的秒數
#define SECONDS_PER_YEAR 60*60*24*365(UL)
1.hello程序的生命週期是從一個高級c語言程序開始的,而後爲了在系統上運行hello.c程序,每條c語句都必須被其餘程序轉化爲一系列的低級機器語言指令。
2.預處理階段。預處理器(cpp)根據以字符#開頭的命令,修改原始的C程序。#include <stdio.h>命令告訴預處理器讀取系統頭文件stdio.h的內容,並將它直接插入到程序文本中。結果就獲得另外一個C程序,一般以.i做爲文件擴展名。
3.編譯階段。編譯器(ccl)將文本文件hello.i翻譯成文本文件hello.s。它包含一個彙編語言程序。彙編語言程序中的每條語句都以一種標準的文本格式確切地描述了一條低級機器語言指令。彙編語言爲不一樣編譯器提供了通用的輸出語言。
4.彙編階段。彙編器(as)將hello.s翻譯成機器語言指令。並將結果保存在目標文件hello.o中。hello.o是一種二進制文件。它的字節編碼是機器語言指令而不是字符。
5.鏈接階段。hello程序調用printf函數。它是c編譯器都會提供的標準c庫中的一個函數。printf函數存在於一個名爲printf.o的單獨的預編譯好的目標文件中,而這個文件必須以某種方式合併到咱們的hello.o程序中。鏈接器就是負責這種合併的。
c語言中,常見數據類型的字節數
32位編譯器
char :1個字節
char*(即指針變量): 4個字節(32位的尋址空間是2^32, 即32個bit,也就是4個字節。同理64位編譯器)
short int : 2個字節
int: 4個字節
unsigned int : 4個字節
float: 4個字節
double: 8個字節
long: 4個字節
long long: 8個字節
unsigned long: 4個字節
64位編譯器
char :1個字節
char*(即指針變量): 8個字節
short int : 2個字節
int: 4個字節
unsigned int : 4個字節
float: 4個字節
double: 8個字節
long: 8個字節
long long: 8個字節
unsigned long: 8個字節
c++常見容器,vector容器capacity和size區別,如何動態增加
在Vector容器中有如下幾個關於大小的函數
size()返回容器的大小
empty()判斷容器是否爲空
max_size()返回容器最大的能夠存儲的元素
capacity()返回容器當前可以容納的元素數量
1. 容器的大小一旦超過capacity的大小,vector會從新配置內部的存儲器,致使和vector元素相關的全部reference、pointers、iterator都會失效。
2.內存的從新配置會很耗時間。
Vector內存擴展方式
vector內存成長方式可歸結如下三步曲:
(1)另覓更大空間;
(2)將原數據複製過去;
(3)釋放原空間三部曲。
vector遍歷有哪幾種方式(儘量多)
for (size_t i =0; i < vec.size(); i ++) {
}
for_each(ivec.begin(),ivec.end(),fun);
cv:Mat 有幾種訪問方式
初始化
初始化一個Mat文件出來通常有兩種形式:
// 1、imread
Mat src = imread("csdn.png");
//2、create
Mat src;
if(src.empty())
{
src.create(Size size, VC_8UC3);
}
訪問方式:
at<type>(i,j)訪問
例:int ROWS = 100; // height
int COLS = 200; // width
Mat img1(ROWS , COLS , CV_32FC1);
for (int i=0; i<ROWS ; i++)
{
for (int j=0; j<COLS ; j++)
{
img1.at<float>(i,j) = 3.2f;
}
}
方式2: ptr<type>(i) [j] 方式
例:for (int i=0; i<ROWS ; i++)
{
float* pData1=img5.ptr<float>(i);
for (int j=0; j<COLS ; j++)
{
pData1[j] = 3.2f;
}
}
例:方式3:img.data + step[0]*i + step[1]*j 方式
map容器增刪改查,和unorder_map區別,map底層如何實現
增
numCountMap.insert({numName,thisAddTime});
numCountMap.insert(make_pair(numName,thisAddTime));
numCountMap.insert(pair<int,int>(numName,thisAddTime));
numCountMap.insert(map<int,int>::value_type(numName,thisAddTime));
遍歷
for(const auto &it : numCountMap)
{}
查
map<int,int>::iterator it=numCountMap.find(alterNum);
刪
int eraseReturn=numCountMap.erase(1);
修改和訪問
int alterNum=3;
map<int,int>::iterator alterit=numCountMap.find(alterNum);
if(alterit!=numCountMap.end())
{
alterit->second=6;
cout<<"alter num 3 occurs 6 time"<<endl;
}
c++智能指針
C++11中引入了智能指針的概念,方便管理堆內存。
從較淺的層面看,智能指針是利用了一種叫作RAII(資源獲取即初始化)的技術對普通的指針進行封裝,這使得智能指針實質是一個對象,行爲表現的卻像一個指針。
智能指針的做用是防止忘記調用delete釋放內存和程序異常的進入catch塊忘記釋放內存。另外指針的釋放時機也是很是有考究的,屢次釋放同一個指針會形成程序崩潰,這些均可以經過智能指針來解決。
智能指針還有一個做用是把值語義轉換成引用語義。
.智能指針的使用
智能指針在C++11版本以後提供,包含在頭文件<memory>中,shared_ptr、unique_ptr、weak_ptr
c++14/17新特性
C++11包括大量的新特性:包括lambda表達式,類型推導關鍵字auto、decltype,和模板的大量改進。
C++14的主要特性能夠分爲三個領域:Lambda函數、constexpr和類型推導。
C++ 17 旨在使 C++ 成爲一個不那麼臃腫複雜的編程語言,以簡化該語言的平常使用,使開發者能夠更簡單地編寫和維護代碼。
C++ 17 是對 C++ 語言的重大更新,引入了許多新的語言特性:
UTF-8 字符文字;
摺疊表達式 (fold expressions):用於可變的模板;
內聯變量 (inline variables):容許在頭文件中定義變量;
在 if 和 switch 語句內能夠初始化變量;
結構化綁定 (Structured Binding):for (auto [key,value] : my_map) {…};
虛函數和純虛函數區別
虛函數(impure virtual)
C++的虛函數主要做用是「運行時多態」,父類中提供虛函數的實現,爲子類提供默認的函數實現。
子類能夠重寫父類的虛函數實現子類的特殊化。
純虛函數(pure virtual)
C++中包含純虛函數的類,被稱爲是「抽象類」。抽象類不能使用new出對象,只有實現了這個純虛函數的子類才能new出對象。
C++中的純虛函數更像是「只提供申明,沒有實現」,是對子類的約束,是「接口繼承」。
C++中的純虛函數也是一種「運行時多態」。
虛函數表
模板
函數模板的寫法
函數模板的通常形式以下:
Template <class或者也能夠用typename T>
返回類型 函數名(形參表)
{//函數定義體 }
說明: template是一個聲明模板的關鍵字,表示聲明一個模板關鍵字class不能省略,若是類型形參多餘一個 ,每一個形參前都要加class <類型 形參表>能夠包含基本數據類型能夠包含類類型.
函數模板:模板(Templates)使得咱們能夠生成通用的函數,這些函數可以接受任意數據類型的參數,可返回任意類型的值,而不須要對全部可能的數據類型進行函數重載。這在必定程度上實現了宏(macro)的做用。
類模板:使得一個類能夠有基於通用類型的成員,而不須要在類生成的時候定義具體的數據類型
c++繼承多態
c++深拷貝淺拷貝
淺拷貝:編譯器僅僅拷貝告終構體的值,而沒有建立新的內存空間,而是共享同一塊內存空間
深拷貝:編譯器會爲拷貝的對象分配必定的內存空間
構造函數/委託構造函數/拷貝構造函數(深拷貝/淺拷貝)
構造函數——用於初始化對象
函數名與類名相同,不能有返回值類型,能夠有形式參數,也能夠沒有形式參數,能夠是inline函數,能夠重載,能夠帶默認參數值。
在對象建立時自動調用。
默認構造函數(default constructor):調用時能夠不須要實參的構造函數。有如下兩種:
1. 參數表爲空的構造函數
2. 所有參數都有默認值的構造函數。
委託構造函數:在一個類中重載多個構造函數時,這些函數只是形參不一樣,初始化列表不一樣,而初始化算法和函數體都是相同的。這個時候,爲了不重複,C++11新標準提供了委託構造函數。更重要的是,能夠保持代碼的一致性,若是之後要修改構造函數的代碼,只須要在一處修改便可。
拷貝構造函數
· 拷貝構造函數是一種特殊的構造函數,其形參爲本類對象的引用。
· 做用:用一個已經存在的對象去初始化同類型的新對象。
三種典型狀況須要調用拷貝構造函數:
1. 定義一個對象時,用本類的另外一個對象做爲初始值,發生拷貝構造;
2. 若是函數的形參是類的對象,調用函數時,將使用實參對象初始化形參對象,發生拷貝構造;
3. 若是函數的返回值是類的對象時,函數執行完成返回主函數時,將使用return語句中的對象初始化一個臨時無名對象,傳遞給主調函數,此時發生拷貝構造。
https://blog.csdn.net/weixin_39924163/article/details/80171448
emplace_back和push_back區別
emplace_back和push_back都是向容器內添加數據.
對於在容器中添加類的對象時, 相比於push_back,emplace_back能夠避免額hg外類的複製和移動操做.
class CExample
{
private:
int a;
public:
//構造函數
CExample(int b)
{
a=b;
printf("constructor is called\n");
}
//拷貝構造函數
CExample(const CExample & c)
{
a=c.a;
printf("copy constructor is called\n");
}
//析構函數
~CExample()
{
cout<<"destructor is called\n";
}
void Show()
{
cout<<a<<endl;
}
};
如何實現一個c++的單例模式
內聯函數和宏的區別
內聯函數和宏很相似,而區別在於,宏是由預處理器對宏進行替代,而內聯函數是經過編譯器控制來實現的。並且內聯函數是真正的函數,只是在須要用到的時候,內聯函數像宏同樣的展開,因此取消了函數的參數壓棧,減小了調用的開銷。你能夠象調用函數同樣來調用內聯函數,而沒必要擔憂會產生於處理宏的一些問題。
如何實現一個只在堆或者棧上初始化的類
只能創建在堆上
將析構函數設爲私有,類對象就沒法創建在棧上了。代碼以下:
1. class A
2. {
3. public :
4. A(){}
5. void destory(){ delete this ;}
6. private :
7. ~A(){}
8. };
只能創建在棧上
只要禁用new運算符就能夠實現類對象只能創建在棧上
指針函數和函數指針
指針函數
int *fun(int x,int y);
函數指針
int (*fun)(int x,int y);
指針數組和數組指針
數組指針 (*p)[n]
數組指針:是指針——指向數組的指針。
指針數組 *p[n]
指針數組:是數組——裝着指針的數組。
· define和inline(編譯哪一個階段?)
define:定義預編譯時處理的宏,只是簡單的字符串替換,無類型檢查。
inline:1、inline關鍵字用來定義一個類的內聯函數,引入它的主要緣由是用它替代C中表達式形式的宏定義,編譯階段完成。 2、內聯函數要作類型安全檢查,inline是指嵌入代碼,在調用函數的地方不是跳轉,而是把代碼直接寫到那裏去,對於短小的函數來講,inline函數能夠獲得必定效率的提高,和c時代的宏函數相比,inline函數更加安全可靠,這個是以增長空間的消耗爲代價的。
ps:定義內聯函數只是給編譯器一個建議,可是最後的決定權仍是在於編譯器,若是函數的邏輯比較複雜(有循環遞歸之類的),此時則會內聯失敗。根據google編程規範,內聯函數通常在10行如下,並且邏輯簡單。
· 返回局部變量?
函數不能返回指向棧內存的指針
緣由:返回值是拷貝值,局部變量的做用域爲函數內部,函數執行結束,棧上的局部變量會銷燬,內存釋放。
可返回的局部變量:
1. 返回局部變量自己
2.常量:
3. 靜態局部變量
當屢次調用一個函數且要求在調用之間保留某些變量的值時,可考慮採用靜態局部變量。
4. 堆內存中的局部變量
· 子函數返回結構體有什麼問題?返回對象調用了哪些函數?
C 語言中函數返回結構體時若是結構體較大, 則在調用函數中產生該結構的臨時變量,並將該變量首地址傳遞給被調用函數,被調用函數返回時根據該地址修改此臨時變量的內容,以後在調用函數中再將該變量複製給用戶定義的變量,這也正是 C 語言中所謂值傳遞的工做方式。
若是結構體較小, 則函數返回時所用的臨時變量可保存在寄存器中,返回後將寄存器的值複製給用戶定義的變量便可。
· volatile幹嗎的?
volatile 關鍵字是一種類型修飾符,用它聲明的類型變量表示能夠被某些編譯器未知的因素更改,好比:操做系統、硬件或者其它線程等。遇到這個關鍵字聲明的變量,編譯器對訪問該變量的代碼就再也不進行優化,從而能夠提供對特殊地址的穩定訪問。
volatile用在以下的三個地方:
中斷服務程序中修改的供其它程序檢測的變量須要加volatile;
多任務環境下各任務間共享的標誌應該加volatile;
存儲器映射的硬件寄存器一般也要加volatile說明,由於每次對它的讀寫均可能由不一樣意義;
· 編譯器基本原理?
gcc編譯器
編譯器在開始工做以前,須要知道當前的系統環境,好比標準庫在哪裏、軟件的安裝位置在哪裏、須要安裝哪些組件等等。這是由於不一樣計算機的系統環境不同,經過指定編譯參數,編譯器就能夠靈活適應環境,編譯出各類環境都能運行的機器碼。這個肯定編譯參數的步驟,就叫作」配置」(configure)。
這些配置信息保存在一個配置文件之中,約定俗成是一個叫作configure的腳本文件。一般它是由autoconf工具生成的。編譯器經過運行這個腳本,獲知編譯參數。
源碼確定會用到標準庫函數(standard library)和頭文件(header)。它們能夠存放在系統的任意目錄中,編譯器實際上沒辦法自動檢測它們的位置,只有經過配置文件才能知道。
編譯的第二步,就是從配置文件中知道標準庫和頭文件的位置。通常來講,配置文件會給出一個清單,列出幾個具體的目錄。等到編譯時,編譯器就按順序到這幾個目錄中,尋找目標。
對於大型項目來講,源碼文件之間每每存在依賴關係,編譯器須要肯定編譯的前後順序。假定A文件依賴於B文件,編譯器應該保證作到下面兩點。
(1)只有在B文件編譯完成後,纔開始編譯A文件。
(2)當B文件發生變化時,A文件會被從新編譯。
編譯順序保存在一個叫作makefile的文件中,裏面列出哪一個文件先編譯,哪一個文件後編譯。而makefile文件由configure腳本運行生成,這就是爲何編譯時configure必須首先運行的緣由。
在肯定依賴關係的同時,編譯器也肯定了,編譯時會用到哪些頭文件。
不一樣的源碼文件,可能引用同一個頭文件(好比stdio.h)。編譯的時候,頭文件也必須一塊兒編譯。爲了節省時間,編譯器會在編譯源碼以前,先編譯頭文件。這保證了頭文件只需編譯一次,沒必要每次用到的時候,都從新編譯了。
不過,並非頭文件的全部內容,都會被預編譯。用來聲明宏的#define命令,就不會被預編譯。
預編譯完成後,編譯器就開始替換掉源碼中bash的頭文件和宏。以本文開頭的那段源碼爲例,它包含頭文件stdio.h,替換後的樣子以下。
1. extern int fputs(const char *, FILE *);
2. extern FILE *stdout;
3. int main(void)
4. {
5. fputs("Hello, world!\n", stdout);
6. return 0;
7. }
爲了便於閱讀,上面代碼只截取了頭文件中與源碼相關的那部分,即fputs和FILE的聲明,省略了stdio.h的其餘部分(由於它們很是長)。另外,上面代碼的頭文件沒有通過預編譯,而實際上,插入源碼的是預編譯後的結果。編譯器在這一步還會移除註釋。
這一步稱爲」預處理」(Preprocessing),由於完成以後,就要開始真正的處理了。
預處理以後,編譯器就開始生成機器碼。對於某些編譯器來講,還存在一箇中間步驟,會先把源碼轉爲彙編碼(assembly),而後再把彙編碼轉爲機器碼。
下面是本文開頭的那段源碼轉成的彙編碼。
種轉碼後的文件稱爲對象文件(object file)。
對象文件還不能運行,必須進一步轉成可執行文件。若是你仔細看上一步的轉碼結果,會發現其中引用了stdout函數和fwrite函數。也就是說,程序要正常運行,除了上面的代碼之外,還必須有stdout和fwrite這兩個函數的代碼,它們是由C語言的標準庫提供的。
編譯器的下一步工做,就是把外部函數的代碼(一般是後綴名爲.lib和.a的文件),添加到可執行文件中。這就叫作鏈接(linking)。這種經過拷貝,將外部函數庫添加到可執行文件的方式,叫作靜態鏈接(static linking),後文會提到還有動態鏈接(dynamic linking)。
make命令的做用,就是從第四步頭文件預編譯開始,一直到作完這一步。
上一步的鏈接是在內存中進行的,即編譯器在內存中生成了可執行文件。下一步,必須將可執行文件保存到用戶事先指定的安裝目錄。
表面上,這一步很簡單,就是將可執行文件(連帶相關的數據文件)拷貝過去就好了。可是實際上,這一步還必須完成建立目錄、保存文件、設置權限等步驟。這整個的保存過程就稱爲」安裝」(Installation)。
可執行文件安裝後,必須以某種方式通知操做系統,讓其知道可使用這個程序了。好比,咱們安裝了一個文本閱讀程序,每每但願雙擊txt文件,該程序就會自動運行。
這就要求在操做系統中,登記這個程序的元數據:文件名、文件描述、關聯後綴名等等。Linux系統中,這些信息一般保存在/usr/share/applications目錄下的.desktop文件中。另外,在Windows操做系統中,還須要在Start啓動菜單中,創建一個快捷方式。
這些事情就叫作」操做系統鏈接」。make install命令,就用來完成」安裝」和」操做系統鏈接」這兩步。
寫到這裏,源碼編譯的整個過程就基本完成了。可是隻有不多一部分用戶,願意耐着性子,從頭至尾作一遍這個過程。事實上,若是你只有源碼能夠交給用戶,他們會認定你是一個不友好的傢伙。大部分用戶要的是一個二進制的可執行程序,馬上就能運行。這就要求開發者,將上一步生成的可執行文件,作成能夠分發的安裝包。
因此,編譯器還必須有生成安裝包的功能。一般是將可執行文件(連帶相關的數據文件),以某種目錄結構,保存成壓縮文件包,交給用戶。
正常狀況下,到這一步,程序已經能夠運行了。至於運行期間(runtime)發生的事情,與編譯器一律無關。可是,開發者能夠在編譯階段選擇可執行文件鏈接外部函數庫的方式,究竟是靜態鏈接(編譯時鏈接),仍是動態鏈接(運行時鏈接)。因此,最後還要提一下,什麼叫作動態鏈接。
前面已經說過,靜態鏈接就是把外部函數庫,拷貝到可執行文件中。這樣作的好處是,適用範圍比較廣,不用擔憂用戶機器缺乏某個庫文件;缺點是安裝包會比較大,並且多個應用程序之間,沒法共享庫文件。動態鏈接的作法正好相反,外部函數庫不進入安裝包,只在運行時動態引用。好處是安裝包會比較小,多個應用程序能夠共享庫文件;缺點是用戶必須事先安裝好庫文件,並且版本和安裝位置都必須符合要求,不然就不能正常運行。
現實中,大部分軟件採用動態鏈接,共享庫文件。這種動態共享的庫文件,Linux平臺是後綴名爲.so的文件,Windows平臺是.dll文件,Mac平臺是.dylib文件。
· 數組和指針(重點難在二維)
int **a;
a = new int*[M];
for (int x = 0; x < M; x++)
a[x] = new int[M];