static變量存儲在靜態數據區
相對於function:在函數內,變量,內存只被分配一次,多次調用值相同
相對於其他模塊(.c文件):變量和函數,不能被模塊外其他函數訪問(private)
相對於類:類中的static變量和函數屬於整個類,而不是對象
全局變量 VS 全局靜態變量
若程序由一個源文件構成時,全局變量與全局靜態變量沒有區別。
若程序由多個源文件構成時,全局變量與全局靜態變量不同:全局靜態變量使得該變量成為定義該變量的源文件所獨享,即:全局靜態變量對組成該程序的其它源文件是無效的。(private)
malloc與free是C++/C語言的標准庫函數,new/delete是C++的運算符。它們都可用於申請動態內存和釋放內存
new和delete對應,new調用構造函數,delete會調用析構函數。
new在實現上調用了malloc函數。new出來的指針是直接帶類型信息的。而malloc返回的都是void*
指針。new delete在實現上其實調用了malloc,free函數。
delete VS delete []
delete只會調用一次析構函數,而delete[]會調用每一個成員的析構函數
malloc,calloc,realloc,free
四個函數都被包含在stdlib.h函數庫內。回值都是請求系統分配的地址,如果請求失敗就返回NULL
void* realloc(void* ptr, unsigned newsize);
realloc是給一個已經分配了地址的指針重新分配空間,參數ptr為原有的空間地址,newsize是重新申請的地址長度。並把原來大小內存空間中的內容復制到newsize中。realloc 不能保證重新分配後的內存空間和原來的內存空間指在同一內存地址, 它返回的指針很可能指向一個新的地址。
void* malloc(unsigned size);`
在內存的動態存儲區中分配一塊長度為”size”字節的連續區域,返回該區域的首地址
void* calloc(size_t nelem, size_t elsize);`
calloc調用形式為(類型*)calloc(n,size):在內存的動態存儲區中分配n塊長度為”size”字節的連續區域,返回首地址。
free(q);
其中q為已經分配的塊;
引用就是某個目標變量的別名(alias),對應用的操作與對變量直接操作效果完全相同。在內存中並沒有產生實參的副本使用指針作為函數的參數雖然也能達到與使用引用的效果,但是,在被調函數中同樣要給形參分配存儲單元(值傳遞),且需要重復使用”*指針變量名”的形式進行運算,這很容易產生錯誤且程序的閱讀性較差;
引用VS指針
1) 引用必須被初始化,指針不必。
2) 引用初始化以後不能被改變指向的對象,指針可以改變所指的對象。
3) 不存在指向空值的引用,但是存在指向空值的指針。
備注:引用時C++的特性,C中沒有,引用是通過指針來實現的,對傳遞做了一些限制,但是對程序員是透明的。引用的目的,將 & 從調用者移到了被調用者處。
結構體:struct ,是由一系列相同類型或不同類型的數據構成數據的集合,也叫結構。其最主要的作用就是封裝。
聯合體:union,幾個不同的變量存放在同一塊內存區域中。也就是使用覆蓋技術,幾個變量互相覆蓋。同一時刻只能使用一種變量。
struct VS class
默認的繼承訪問權限,struct是public的,class是private的。其他沒有區別。
數據對齊
定義常量:const修飾的類型為TYPE的變量value是不可變的。必須初始化
const TYPE ValueName = value;
指針使用CONST
(1)在*之前:指針所指向的內容是常量不可變
const (char) *pContent;
(char) const *pContent;
(2)在*之後:指針本身是常量不可變
(char*) const pContent;
函數相關使用CONST
(1)const修飾函數參數:修飾的參數在函數內不可以改變
void function(const int Var);
(2)const 修飾函數返回值:返回值是一個常量
const int fun1()
int const fun1()
(3)const修飾成員函數:函數不能修改任何成員變量,不能調用非const成員函數
class A
{
…
void function()const;
//1. 常成員函數, 它不改變對象的成員變量(除了static的),也不能調用類中任何非const成員函數。
//2. const類對象只能調用其const成員函數
//3. 對於在類外定義的成員函數,必須在成員函數的定義和聲明中都指定關鍵字const,不然將被視為重載
}
const VS #define
const 常量有數據類型,編譯器可以對前者進行類型安全檢查
而宏常量是別名,沒有數據類型,沒有類型安全檢查
typedef, #define
一種類型的別名,有類型檢查而不只是簡單的宏替換。可以做類型,數組,函數的類型替換。
數組名對應著(而不是指向)一塊內存,其地址與容量在生命期內保持不變,只有數組的內容可以改變。
指針可以隨時指向任意類型的內存塊,它的特征是“可變”,所以我們常用指針來操作動態內存。指針遠比數組靈活,但也更危險。
sizeof可以計算出數組的容量,對指針操作得到的是一個指針變量的字節數。
當數組名作為參數傳入時,實際上數組就退化成指針了。
隱式類型轉換分三種,即算術轉換、賦值轉換和輸出轉換。
sizeof是算符(類似宏定義的特殊關鍵字),strlen是函數。
sizeof操作符的結果類型是size_t,其值在編譯時即計算好了,參數可以是數組、指針、類型、對象、函數等。它的功能是:獲得保證能容納實現所建立的最大對象的字節大小。
具體而言,當參數分別如下時,sizeof返回的值表示的含義如下:
數組——編譯時分配的數組空間大小;
指針——存儲該指針所用的空間大小(存儲該指針的地址的長度,是長整型,應該為4);
類型——該類型所占的空間大小;
對象——對象的實際占用空間大小;
函數——函數的返回類型所占的空間大小。函數的返回類型不能是void。
strlen的功能是:返回字符串的長度。該函數實際完成的功能是從代表該字符串的第一個地址開始遍歷,直到遇到結束符NULL。
多態是怎麼實現的?
多態,簡而言之就是用父類型別的指針指向其子類的實例,然後通過父類的指針調用實際子類的成員函數。
每個有虛函數的類(不是對象)都有一個虛表,父類的虛表,子類的虛表,虛表裡面存儲的是一個個指向函數的指針,父類的虛表存儲父類的虛函數,在子類中,如果沒有覆蓋(override)父類的虛函數,虛表不變,如果覆蓋了,那虛表更新,指向當前類中的函數。
每個含有虛函數的對象都有一個虛函數指針,這個指針指向該類的虛表
過程:當通過父類指針找到對象的時候,(判斷)如果是虛函數,則是通過(子類的,通過this指針指向虛指針)虛指針找到虛表(子類虛表),再通過虛表對應項找到函數地址入口,這就是動態綁定,而不是在編譯時候就決定了入口地址的靜態綁定!
虛函數存在哪?
虛函數表vtable在Linux/Unix中存放在可執行文件的只讀數據段中(rodata),微軟的編譯器將虛函數表存放在常量段
靜態聯編VS動態聯編
聯編是指一個計算機程序的不同部分彼此關聯的過程。決定程序中誰調用誰,在運行的過程決定。如多態。
靜態聯編:是指聯編工作在編譯階段完成的,在程序運行之前完成的,運行的時候就已經確定了。
一個由c/C++編譯的程序占用的內存分為以下幾個部分
1、棧區(stack)— 由編譯器自動分配釋放,存放函數的參數值,局部變量的值等。其操作方式類似於數據結構中的棧。
2、堆區(heap) — 一般由程序員分配釋放,若程序員不釋放,程序結束時可能由OS回收。注意它與數據結構中的堆是兩回事,分配方式類似於鏈表。
3、全局區(靜態區)(static)—,全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域, 未初始化的全局變量和未初始化的靜態變量在相鄰的另一塊區域。程序結束後由系統釋放 。
4、文字常量區—常量字符串就是放在這裡的。 程序結束後由系統釋放。
5、程序代碼區—存放函數體的二進制代碼。
├———————┤低地址
│ 代碼區
├———————┤
│ 文字常量區
├———————┤
│ 全局數據區 │
├———————┤
│ 堆 │
├———————┤
│ 棧 │
├———————┤高地址
堆從低地址向高地址擴展
棧從高地址向低地址擴展
全局變量————>全局區
有初始值,data段
無初始值,bss段,不占用內存空間,在運行的時候被編譯器自動初始化為0
局部變量(函數內)——->棧區
無初始值,隨機
總結:
放在靜態數據區的(全局的)變量編譯器會初始化,在棧裡面的(局部)變量不會初始化。
總結:
如果是int *ptr
;ptr++
等價於ptr+1*sizeof(int)
;
所以每次加1都能夠指向相鄰的下一個元素。
sizeof(ptr)也就是指針本身的大小永遠都是4字節,所以ptr是一個4字節的值,這個值就是指向的內容的地址
ptr指的是字節的序號(但是)
ptr2-ptr1會被翻譯成(ptr2-ptr1)/sizeof(int)這對程序員是透明的,便於操作數組
內聯函數,是向編譯器發出的請求,編譯階段把函數內容替換到代碼中去,有類型檢查
宏定義,編譯預處理階段,簡單的替代,沒有類型檢查
內聯函數:本質是用程序代碼的空間換取程序調用的時間,適合在一個大項目裡,有一個小函數(簡單幾行,沒有循環語句,循環語句使得調用開銷相對變小)不斷被重復調用
內聯函數使用:聲明(不需要),定義(inline)
非靜態成員變量總合。
加上編譯器為了CPU計算,作出的數據對齊處理。
加上為了支持虛函數,產生的額外負擔。
指針函數與函數指針
指針函數(函數返回指針):是指帶指針的函數,本質是函數,返回值是某一類型的指針
函數指針:指向函數的指針變量
int (*f) (int x); /* 聲明一個函數指針 */
f=func; /* 將func函數的首地址賦給指針f */
指針數組,數組指針
指針數組:一個數組,裡面都是指針,int *a[10]
數組指針:一個指針,指向一個數組,int (*a)[10]
[]結合的優先級高於a
指針常量與常量指針
int const *p1 = &b;//const 在前,定義為常量指針 (常量的指針)
int *const p2 = &c;//*在前,定義為指針常量 (指針指向常量)
***a
***(a+1)
**(*a+1)
*(**a+1)
***a+1
越在上面,增加的維度越高
從廣義上,能夠從一個數值拎起一大堆數據的東西都可以叫做句柄。Windows系統中有許多內核對象,比如打開的文件,創建的線程,程序的窗口,等等,這些對象往往很大,而且還經常變化。
那麼怎麼在程序間或程序內部的子過程(函數)之間傳遞這些數據呢?
在進程的地址空間中設一張表(句柄即編號->實際地址的映射),表裡頭專門保存一些編號和由這個編號對應一個地址,而由那個地址去引用實際的對象,這個編號跟那個地址在數值上沒有任何規律性的聯系,純粹是個映射而已。
句柄作用:作為一個索引在一個表中查找對應的內核對象的實際地址。是指向指針的指針,但是不能對它做不安全的操作。windows用句柄操作系統資源(對象),隱藏了系統具體的信息。指針則直接記錄了物理內存地址。
auto_ptr
在C++的程序中Obj *ptr=new Obj(),需要delete ptr去釋放這個ptr指向的內存。C++提供一種方法,可以不用delete就可以在函數結束的時候自動釋放內存。(Effective C++ 條款13,以對象管理資源)
auto_ptr是一個模板類,這個類型的對象內部的析構函數完成對堆內存的釋放,所以不要對這個對象的內存進行delete了。
//demo for manager heap memory by auto_ptr.
auto_ptr pInt(new int(0));
*pInt = 2;
cout<<*pInt<
其實
auto_ptr
是一個類,包裝指針,並且重載了反引用(dereference)運算符operator *
和成員選擇運算符operator ->
,以模仿指針的行為。
shared_ptr
的作用有如同指針,但會記錄有多少個shared_ptrs共同指向一個對象。這便是所謂的引用計數(reference counting)。一旦最後一個這樣的指針被銷毀,也就是一旦某個對象的引用計數變為0,這個對象會被自動刪除。這在非環形數據結構中防止資源洩露很有幫助。
auto_ptr沒有考慮引用計數,因此一個對象只能由一個auto_ptr所擁有,在給其他auto_ptr賦值的時候,會轉移這種擁有關系。
this指針
this不占class或者對象的空間
this在成員函數調用前構造,在成員函數結束時消失。
this的作用是在調用成員函數的時候把對象傳進去
A a;
a.foo(10);
===被編譯器翻譯為===>
A::foo(&a,10)
那麼foo的原型
foo(int p)
===被編譯器翻譯為===>
foo(A * const this,int p)
所以在成員函數定義的時候可以寫,this->pram=XXX;
C++程序編譯過程
預處理器cpp
預處理器cpp將對源文件中的宏進行展開,#define,#include
編譯器gcc/g++
編譯器將文件編譯成匯編文件
匯編器as
匯編器將匯編文件編譯成機器碼
鏈接器ld
將有關的目標文件彼此相連接,也即將在一個文件中引用的符號同該符號在另外一個文件中的定義連接起來,使得所有的這些目標文件成為一個能夠诶操作系統裝入執行的統一整體。
size_t
為了使自己的程序有很好的移植性,c++程序員應該盡量使用size_t和size_type而不是int, unsigned
size_t是全局定義的類型;size_type是STL類中定義的類型屬性,用以保存任意string和vector類對象的長度
size_t類型是通過typedef定義的一些無符號整型的別名,通常是unsigned int或unsigned long,甚至是unsigned long long。每種標准C的實現應該選擇足夠大的無符號整型,來代表目標平台可能的最大對象,但不能供過於求。
如果傳入-1會變成4294967295,因為使用無符號整數表示
C++ 運算符重載
運算符重載的實質是函數重載。在實現過程中,首先把指定的運算表達式轉化為對運算符函數的調用
在編譯過程中:根據實參的類型把表達式轉化為運算符函數,運算對象轉化為運算符函數的實參
分為兩種:
運算符重載為成員函數(一般說來,單目運算符(+=)最好被重載為成員)
complex operator+(const complex &c);
友元函數形式,C++ 中的string就是這樣(一般說來,對雙目運算符(+,-,*,/)最好被重載為友元函數)
friend complex operator +(const complex &c1, const complex &c2);
+,-,*,/
返回對象,=
返回引用,返回值主要考慮的是連續運算
friend ostream & operator<<(ostream & os,const MyString &st);
(單目與雙目,是指操作數的數目)
友元
友元定義在類外,友元可以訪問類中的私有成員
好處,提高運行效率(在類外調用開銷小),壞處,破壞封裝性
參數處理順序
Cout是對<<運算符的重載,返回的是cout這個ostream對象本身。<<連續使用時,返回的cout對象繼續調用上面函數對下一個操作數進行輸出,如此反復,直到該語句結束。因此,cout執行順序是從左到右。
編譯器對函數實參的表達式處理順序是從右向左。如
void fun(int a, int b)
先處理b後處理a
#include
C++中
#include
包含頭文件帶 .h 和不帶 .h 的區別?
帶 .h 的頭文件是舊標准(c)的,如果想用新的標准(C++)的頭文件就不要帶 .h
C++增加了名稱空間概念,借以將原來聲明在全局空間下的標識符聲明在了namespace std下。
為了和C語言兼容,C++標准化過程中,原有C語言頭文件標准化後,頭文件名前帶個c字母,如cstdio、cstring、ctime、ctype等等。
#include --> #include
#include --> #include
#include --> #include
多重繼承,虛繼承
多重繼承
是從多於一個直接基類派生類的能力,多重繼承的派生類繼承其所有父類的屬性。
虛擬繼承
是多重繼承中特有的概念,是為解決多重繼承的。在多重繼承下,一個基類可以在派生層次中出現多次。virtual表明一種願望,即在後序的派生類當中共享虛基類的同一份實例。
實例:istream,ostream,iostream
構造函數初始化方式
順序和普通多重繼承稍有區別,先初始化虛基類,然後依次初始化繼承於虛基類的子類。
private protect public的可見性
訪問:
(函數+友元函數)——子類函數———類對象
private: 只能由該類中的函數、其友元函數訪問,不能被子類訪問,該類的對象也不能訪問.
protected: 可以被該類中的函數、子類的函數、以及其友元函數訪問,但不能被該類的對象訪問
public: 可以被該類中的函數、子類的函數、其友元函數訪問,也可以由該類的對象訪問
繼承:(父類對子類的可見性由父類內訪問說明符有關,繼承方式影響的是子類生成的對象(或子類的子類)對於父類成員的可見性)
使用private繼承,父類的所有方法在子類中變為private;
使用protected繼承,父類的protected和public方法在子類中變為protected,private方法不變;
使用public繼承,父類中的方法屬性不發生改變,但是畢竟是兩個類,不能訪問private成員
抽象類,純虛函數
定義了純虛函數的類成為抽象類,抽象類不能實例化。
virtual void fun()=0;
C++類型轉換
隱式類型轉換
算數轉換,復制轉換,傳參數
顯示類型轉換,static_cast
static_cast < type-id > ( expression )
作用1:編譯器在編譯期處理,在基類和派生類之間轉換時使用,子類的類型提升到父類的類型。
作用2:static_cast(expression) 用來強制類型轉換,例如:
static_cast(2.1)
顯示類型轉換,dynamic_cast
dynamic_cast < type-id > ( expression ) RTTI的功能
dynamic_cast(expression) 主要用來進行安全向下轉型
例如:只有基類可以使用,但是想調用子類的函數。如果可以,嘗試使用多態來代替這種方法。
namepsace
指標識符的可見范圍,C++標准庫中的標識符都被定義到了std的namespace中
推薦用法
using std::cin
是舊標准,標識符是全局的
是遵循C++標准的,需要指定命名空間才能使用
C++默認產生的成員函數
構造函數,析構函數,拷貝構造函數,賦值函數
構造函數:創建一個對象時,系統自動調用構造函數。
析構函數:在函數體內定義的對象,當函數執行結束時,該對象所在類的析構函數會被自動調用
拷貝構造函數:拷貝構造函數中只有一個參數,這個參數是對某個同類對象的引用。在三種情況下被調用:
用類的一個已知的對象去初始化該類的另一個對象時。(初始化時用”Object A=B”,也可以用A(B)的形式。)
函數的形參是類的對象,調用函數進行形參和實參的結合時。(定義一個函數A,A函數的形參是類的對象,在另外一個函數B中調用這個函數A,實參將具體的對象傳遞給形參,這時候會調用拷貝構造函數。)
函數的返回值是類的對象,函數執行完返回調用者。(定義一個函數A,該函數的返回值是一個類的對象,在函數B中定義類的對象來接受函數A的返回,這個時候會調用拷貝構造函數。)
賦值函數:賦值構造函數是將一個參數對象中私有成員賦給一個已經在內存中占據內存的對象的私有成員(賦值構造函數被賦值的對象必須已經在內存中,否則調用的將是拷貝構造函數)
深拷貝與淺拷貝
所謂淺拷貝,指的是在對象復制時,只是對對象中的數據成員進行簡單的賦值,上面的例子都是屬於淺拷貝的情況,默認拷貝構造函數執行的也是淺拷貝。
在“深拷貝”的情況下,對於對象中動態成員,就不能僅僅簡單地賦值了,而是重新動態分配空間。需要重寫拷貝構造函數來實現。
形參與實參
實參:調用時傳遞給函數的參數
形參:是在定義函數名和函數體的時候使用的參數,目的是用來接收調用該函數時傳入的參數.在調用函數時,實參將賦值給形參。
小問題
C++是不是類型安全的?
不是。兩個不同類型的指針之間可以強制轉換
main 函數執行以前,還會執行什麼代碼?
全局對象(全局變量,全局靜態變量)的構造函數會在main 函數之前執行。
assert
assert宏的原型定義在中,其作用是如果它的條件返回錯誤,則終止程序執行
#include
和#include"a.h"
有什麼區別?
對於#include,編譯器從標准庫路徑開始搜索
a.h對於#include “a.h” ,編譯器從用戶的工作路徑開始搜索a.h(自己的)
構造函數和析構函數能否聲明為虛函數?
構造函數不能聲明為虛函數,析構函數可以聲明為虛函數,而且有時是必須聲明為虛函數。
構造函數的調用時從上到下的,如果在構造函數中調用或者構造函數本身就是虛函數那麼,子類的對象還沒有構造完成就調用會出錯析構函數不一樣,析構函數常常需要通過多態來調用合適的函數來析構指針指向的對象。
C++ STL
STL是什麼
定義:標准模板庫(Standard Template Library,STL)
組成:容器(基於模板)+算法。STL是一些“容器”的集合,這些“容器”有list,vector,set,map等,STL也是算法和其他一些組件的集合。
優點:跨平台,可重用,高性能,免費。STL被內建在大部分編譯器中,也可以說STL就是C++的一部分,使用起來很方便。
泛型編程:一個程序可以看做是輸入,操作,輸出,當不確定輸入和輸出的類型,但是對於數據操作的原則是一樣的時候,可以使用泛型(模板機制)代替,這樣可以更加抽象地表達程序,STL就是一個大量使用了泛型編程的例子。
模板機制:模板就是實現代碼重用機制的一種工具,它可以實現類型參數化,即把類型定義為參數, 從而實現了真正的代碼可重用性。
STL API參考
vector
實現原理
vector是在堆內存中連續存儲的動態可伸縮數組。
說明
stl array固定大小,stl vector是動態可伸縮的,當前內存不夠用時,內存翻倍。
常用接口
std::vector second (4,100);
vector::operator[]
vector::push_back()
vector::pop_back()
vector::clear() //清空
vector::empty() //判斷是否為空
vector::size() // 當前內容大小
vector::capacity() // 當前內存的容量
vector::resize() // 調整當前內容大小
dequeue
實現原理
dequeue在內存中是一段一段的連續空間。
如果需要新的空間,則在內存中開辟一段空間,通過指針連接到當前dequeue的頭端或者尾端。
dequeue通過復雜的結構,維護了這些空間整體連續的假象,並且提供了隨機存取的接口。
常用接口
deque::push_back()
deque::push_front()
deque::pop_back()
deque::pop_front()
vector::operator[]
queue::front() // 隊列第一個元素
queue::back() // 隊列最後一個元素
stack
實現原理
stack默認使用雙向隊列deque實現,加了適配器,修改接口
常用接口
stack::push()
stack::pop()
stack::top()
stack::empty()
stack::size()
queue
實現原理
queue使用雙向隊列deque實現,加了適配器,修改接口
常用接口
queue::front() // 隊列頭,先插進去的元素
queue::back() // 隊列尾,後插進去的元素
queue::push()
queue::pop()
queue::empty()
queue::size()
list
實現原理
STL中的list就是一雙向鏈表,可高效地進行插入刪除元素。
常用接口
deque::push_back()
deque::push_front()
deque::pop_back()
deque::pop_front()
queue::front()
queue::back()
vector,list,dequeue
vector - 會自動增長的數組
deque - 擁有vector和list兩者優點的雙端隊列
list - 擅長插入刪除的鏈表
priority_queue
實現原理
優先隊列(堆),默認底層使用vector來實現。
說明
默認情況下是大頂堆,可以自定義為小頂堆
常用接口
priority_queue Q;
int a[5]={3,4,5,2,1};
priority_queue Q(a,a+5);
priority_queue, greater > q;
小頂堆:greater
大頂堆:less
empty() 如果優先隊列為空,則返回真
pop() 刪除第一個元素
push() 加入一個元素
size() 返回優先隊列中擁有的元素的個數
top() 返回優先隊列中有最高優先級的元素
使用vector實現堆操作
algorithm中的make_heap,pop_heap,sort_heap來完成對vector的操作
#include
#include
#include
using namespace std;
int main()
{
int a[] = {15, 1, 12, 30, 20};
vector ivec(a, a+5);
for(vector::iterator iter=ivec.begin();iter!=ivec.end();++iter)
cout<<*iter<<" ";
cout<::iterator iter=ivec.begin();iter!=ivec.end();++iter)
cout<<*iter<<" ";
cout<::iterator iter=ivec.begin();iter!=ivec.end();++iter)
cout<<*iter<<" ";
cout<::iterator iter=ivec.begin();iter!=ivec.end();++iter)
cout<<*iter<<" ";
cout<::iterator iter=ivec.begin();iter!=ivec.end();++iter)
cout<<*iter<<" ";
cout<
map
實現原理
使用紅黑樹實現,插入,查找,刪除的效率都是log(n)
常用接口
std::map first;
map::operator[] // 如果匹配則返回,不匹配就插入
map::insert() //也可以用來添加
查找
map myMap;
if(myMap.find(key)==maplive.end()) // 沒有
if(myMap.count(key)) // 找到有value的
刪除
myMap.erase(key)
遍歷一個map
map::iterator it;
for(it=m.begin();it!=m.end();++it)
cout<<"key: "<first <<" value: "<second<
set
實現原理
使用紅黑樹實現,類似於map只不過只有key,沒有value
說明
元素唯一,插入後自動排序,插入,查找,刪除效率都是logn
常用接口
std::set s;
插入
insert()
clear() ,刪除set容器中的所有的元素
empty() ,判斷set容器是否為空
size() ,返回當前set容器中的元素個數
查找
iter = s.find(2)) != s.end()
s.count(2) != 0
刪除
s.erease(key)
multimap/multiset
實現原理
使用紅黑樹實現,插入,查找,刪除的效率都是log(n)
說明
它允許重復鍵。multimap 中能存儲重復鍵的能力大大地影響它的接口和使用。multimap不能使用下標操作符[],插入和讀取。
常用接口
插入使用pair類型
multimap DNS_daemon;
DNS_daemon.insert(make_pair("213.108.96.7","cppluspluszone.com"));
查找
count(k) 成員函數返回與給定鍵關聯的值得數量。
遍歷查找到的元素
multimap::iterator it;
int num=m.count("Jack");
it = m.find("Jack");
cout<<"the search result is :"<
multiset類似於multimap
unordered_map
實現原理
使用hash表實現,提供了和map一樣的接口
說明
unordered_map來自於boost,後來收錄到tr1中
與map的區別是,
map使用紅黑樹實現,unordered_map使用hash表實現
map中的key是有序的,unordered_map中的key是無序的。
string
實現原理
string也是stl中的一種容器,提供iterator和相關的接口
常用接口
s.empty() 判斷是否為空,bool型
s.size() 或 s.length() 返回字符的個數
s[n] 返回位置為n的字符,從0開始計數
s1+s2 連接
int stoi (const string& str, size_t* idx = 0, int base = 10);
//C++,把string轉化為int,如果不是以數字開頭的會出Exception
clear();//清除string中的內容
resize(0);
string substr(int pos = 0,int n = npos) const;//返回pos開始的n個字符組成的字符串
s.find ( " cat " ) ; //超找第一個出現的字符串”cat“,返回其下標值,查不到返回 4294967295,也可查找字符;
s.append(args); //將args接到s的後面
s.compare ( " good " ) ; //s與”good“比較相等返回0,比"good"大返回1,小則返回-1;
reverse ( s.begin(), s.end () ); //反向排序函數,即字符串反轉函數
常用函數
find
InputIterator find (InputIterator first, InputIterator last, const T& val)
例如:
multimap::iterator it;
int num=m.count("Jack");
it = m.find("Jack");
或者
it = find(m.begin(), m.end(), "Jack")
swap
template void swap ( T& a, T& b )
int x=10, y=20; // x:10 y:20
std::swap(x,y); // x:20 y:10
std::vector foo (4,x), bar (6,y); // foo:4x20 bar:6x10
std::swap(foo,bar); // foo:6x10 bar:4x20
reverse
std::reverse(myvector.begin(),myvector.end());
sort/stable_sort
std::sort (myvector.begin(), myvector.begin()+4);
或
bool myfunction (int i,int j) { return (i myvector (myints, myints+8); // 32 71 12 45 26 80 53 33
// using function as comp
std::sort (myvector.begin()+4, myvector.end(), myfunction); // 12 32 45 71(26 33 53 80)
Effective C++ 讀書筆記
條款1,視C++為一個語言聯邦
可以把C++看成四個組成部分:
C語言的部分
Object Oriented C++ 繼承封裝多態
Template C++ 使用模板編程
STL
每一個部分都有各自的規約
條款2, 盡量以const,enum,inline替換#define
#define只有替換功能,在預處理階段完成,沒有類型檢查,也沒有封裝性
使用const替代變量定義,inline替代函數定義
預處理器中,#include必不可少,#ifdef,#else可以用來進行控制編譯
條款3,盡可能使用const
只要是事實,就把它說出來。只要是const就要聲明為const類型。
const修飾變量
const charp = greeting等價於char constp = greeting
char * const p = greeting 指針不可更改指向對象
const修飾函數,是最有威力的應用
(1) const 返回值
(2) const 函數參數,使用最多
(3) const 成員函數,表明這個函數不能修改任何成員變量(static變量可以修改),也不能調用任何非const成員
補充,
volidate int a,告訴編譯器這個值可能被未知因素修改,每次都要從內存中重新讀取
mutable int a,可以突破const成員函數限制,在函數中被修改
條款4,確定對象被使用前已先被初始化
成員初始化應該在構造函數之前,意味著要使用成員初始化列表進行成員變量的初始化
說明:成員變量總是以聲明的次序被初始化
對於static變量,使用Singleton+inline,保證在對象使用前初始化
條款5,了解C++默默編寫並調用了哪些函數
構造函數,拷貝構造函數,賦值函數,析構函數
條款6,若不想使用編譯器自動生成的函數,就該明確拒絕
如果某些對象不可復制(不能使用copy constructor)
不是很安全的做法:把拷貝構造函數聲明為private
更好的做法:寫一個UnCopyable基類,copy constructor聲明為private
條款7,為多態基類聲明virtual析構函數
class B:A{}
A *b=new B()
delete b
因為b是A類型的指針,所以會導致局部銷毀(只有A的部分被銷毀)
原則:
企圖作為(多態的)base class的類理論上都應該有virtual函數,否則不應該作為base class(虛指針會額外增加空間)
任何帶有virtual 函數的類都應該把析構函數聲明為virtual
不要試圖繼承任何STL容器,因為他們沒有virtual的析構函數
條款8,別讓異常逃離析構函數
析構函數不能拋出異常,否則會導致不明行為。
析構函數應該吞下這個異常,防止傳播
調用一個自己的函數,使得用戶有機會來處理這個異常。
條款9, 絕不要在構造或者析構過程中調用virtual函數
構造過程
class A{
public:
A(){
virtual fun()
}
}
class B:A{}
B b;
構造B->構造A->調用fun(),這時B還沒構造完(被編譯器看成A對象),導致virtual 函數不會下降到子類執行。
析構過程
析構B->析構A->調用fun(),這時B已經被析構掉了,同樣virtual函數不會下降,得不到想要的結果。
條款10,令operator= 返回一個reference to *this
為了保證連續運算如:A=B=C 相當於A = (B = C)
返回一個引用,不會調用copy constructor
對於+=同樣適用
條款11,在operator= 中處理自我賦值
判斷一下,if (this == &rhs) return *this
條款12,復制對象時勿忘其每一個成分
可能出現的問題
(1)對象中的非內置類型不能得到賦值
(2)對象從父類繼承而來的變量不能得到賦值
賦值所有local成員(內置類型,對象)
調用所有base class中的適當的copy constructor
條款13,以對象管理資源
C++申請釋放的資源:堆內存(最常用),文件,互斥鎖,數據庫連接等。一旦申請資源,就必須釋放,否則就會造成內存洩露。
以對象管理資源相當於,使用一個類(RAII類)封裝這個資源,在構造時初始化,在析構時釋放。聲明這個對象時使用棧內存聲明。
常用:
auto_ptr
,封裝對象,重寫了指針行為,看起來像一個指針。只能指向一個對象。復制或者賦值,會刪除原來的指針。
shared_ptr
,類似於auto_ptr,不過允許多個指針指向同一個對象,內部提供引用計數。
這兩個是最常見的RAII類,在構造時初始化,析構時delete。(注意不能auto_ptr
(new std::string[10])數組對象)
條款14,在資源管理類中小心copying行為
類似於
auto_ptr
或者shared_ptr
的處理方式,對於復制。可以:
禁止復制
引用計數,類似於shared_ptr
條款15,在資源管理類中提供對原始資源的訪問
隱式:如
auto_ptr
重寫了指針行為,*ptr,ptr->
使得這個變量看起來像一個指針。從而可以訪問封裝的資源
顯示:提供get()函數返回資源
條款16,使用new和delete時要采用相同的形式
A *a=new A() ,釋放時 使用delete a
int *a=new a[100],釋放時使用delete []a
條款17,以獨立語句將newed對象置入智能指針
std::tr1::shared_ptr pw(new Widget)
processWidget(pwd, priority())
使用單獨語句,不要放到一起可能會造成編譯先後導致指針丟失。
其實不是很明白這點
條款18,讓接口容易被使用,不易被誤用
導入新類型
Date(int month, int day, int year)
多個參數,使用Month,Day,Year類型,可以預防接口被誤用
接口一致性
如:stl每個容器都有size()方法
條款19,設計class猶如設計type
設計一個類時需要考慮很多問題:
創建和銷毀
初始化(初始化列表),拷貝構造函數
pass by value && pass by reference
繼承關系
類型轉換
操作符重載
標准函數駁回(private copy constructor)
public private
效率,異常
不夠一般化,太過一般化
是否真的需要這個類型
條款20, 寧以pass by reference to const 替換 pass by value
區別
pass by value:
要調用copy constructor,可能是費時的操作
pass by reference to const:
const Student &s,const保證變量在函數內不會被修改
pass by value可能導致多態失效
void printNameAndDisplay(Window w)
傳入子類對象,不能實現多態
在編譯器底層,reference是通過指針來實現的
條款21,必須返回對象時,別妄想返回其reference
const Rational operator* (const Rational &lhs, const Rational &rhs)
如果返回reference
返回local stack的對象(Rational r),則函數退出時,這個對象已經被銷毀了
返回heap-allocate對象,會造成何時delete的問題。
返回static對象,if(ab == cd),導致一個static對象不夠用的問題
原則,必須在返回reference和object作出一個選擇,程序員的工作就是選出正確的那個
條款22,將變量聲明為private
public接口內全部都是函數,可以產生用戶使用這個類時,良好的一致性
private parameter可以產生封裝的效果,封裝使得變更更加容易
假如有一個public變量,如果取消它,所有使用它的客戶代碼都會被破壞
假如有一個protect變量,如果取笑它,所有使用它的derived class都會被破壞
所以protect並不比public更具有封裝性
條款23,寧以non-member、non-friend替換member函數
多個操作具有先後順序,應該把他們綁定到一起
封裝->客戶端難修改->更多彈性去改變
non-member(non-friend)函數VSmember函數
non-member函數不能訪問private成分,提供更大的封裝性
條款24,若所有參數皆需類型轉換,請為此采用non-member函數
實現有理數類Rational,乘法的操作符重載
開始可能會向使用成員函數的寫法
const Rational operator*(const Rational & rhs) const
但是希望完成乘法交換律
Rational r
Rational result = 2 * r
需要對2進行隱式類型轉換,方法
const Rational operator*(const Rational &lhs, const Rational &rhs)
使用non-member函數。
不是很明白
條款25,考慮寫出一個不拋出異常的swap函數
std::swap(T& a, T& b)可以對兩個對象進行交換
如果這樣做的效率不高,可以考慮自己寫一個不會拋出異常的swap成員函數
例如:stl 容器中就有很多swap函數,只交換指針,而不會復制對象。
自行實現這樣一個swap成員函數(可以使用std::swap調換指針)
在命名空間內提供一個swap(Widget &a,Widget &b)去實現一個非成員函數來調用前者。
條款26,盡可能延後變量定義式的出現時間
對變量進行定義,意味著承受構造的成本。
原則:應該延後變量定義到使用前的一刻為止。
條款27,盡量少做轉型動作
C風格的轉型
(int)2.1
int(2.1)
C++的新式轉型:
const_cast(expression)
將對象的常量性移除
dynamic_cast(expression)
主要用來進行安全向下轉型
例如:只有基類可以使用,但是想調用子類的函數。嘗試使用多態來代替。
static_cast(expression)
主要用來強制類型轉換
例如:static_cast(2.1)
盡量使用C++風格的轉型
條款28,避免返回handles指向對象內部成分
class A{
public:
void func();
}
class B{
private:
A *a
}
如果在B類中提供
A&
的返回(假設為rt),那麼用戶可以調用rt.func()
修改B中的private成員了。
這是一種放松封裝的行為。
條款29,為“異常安全”而努力是值得的
異常安全的函數提供以下三個保證之一(從弱到強):
基本承諾:如果拋出異常,程序內的任何事物仍然保持在有效狀態下
強烈保證:函數調用成功,則完全成功。函數調用失敗,則程序回復到調用之前的狀態
nothrow:保證絕對不拋出異常。(通常完全使用內置類型的操作,提供不拋出異常的保證)
一個軟件系統,要麼具備異常安全性,要麼不具備。只提供部分異常安全性函數,不能叫做具備異常安全性的系統。
以對象管理資源,是一種很好的防止內存洩露,保證異常安全性的方法。
條款30,透徹了解inlining的裡裡外外
inline函數意味著對這個函數的每一次調用,使用函數本體替換
好處:減少調用成本
壞處:增加代碼體積
inline函數適合小型被頻繁調用的函數
函數內部有for循環不適合inline,因為本身的開銷已經夠大,減少調用的開銷意義不大。
inline只是一個向編譯器發出的申請,編譯器可以忽略它。
如編譯器拒絕復雜函數inline(帶有遞歸,循環),virtual函數也會使inline落空。
條款31,將文件間的編譯依存關系降到最低
方法1,使用Handle class
增加一個實現類去真正實現類的功能,原來的類只維護一個指向實現類的指針
方法2,使用Interface class
基類是虛基類,不包括任何成員變量。
條款32,確定你的public繼承是is-a的關系
如題
條款33,避免遮掩繼承而來的名稱
假如:Derived:Base
當編譯器通過函數名稱去找相應函數,會先從Derived類作用域找,然後再從Base類的作用域找
當使用函數重載的時候就可能出現問題。
使用using Base::func可以避免這種情況。
條款34,區分接口繼承和實現繼承
對於non-virtual函數的繼承
意味著,子類必須有和父類一樣的實現
對於virtual
(1)pure-virtual, 只繼承接口,意味著每個子類的行為都很有可能不一樣
(2)imprure-virtual, 提供缺省的實現,意味著有一些子類的行為可能一樣
可以使用pure-virtual+缺省行為分離(另外寫一個函數)的方法,解決有可能子類在不知情的情況下繼承了並不需要的缺省的實現。
條款35,考慮virtual函數以外的其他選擇
NVI Non-virtual Interface
使用public non-virtual 函數調用private virtual函數(做一下修飾而已)
使用函數指針
使用tr1::function封裝函數指針,代替函數指針的行為
使用strategy設計模式
將想要virtual的行為封裝成一個類(Calculator),在類內部進行多態計算,通過傳入的對象指針來判斷。
條款36,絕不重新定義繼承而來的non-virtual函數
class A{
public:
void fun()
}
class B:A{
public:
void fun()
}
A *ptA=new B()
B *ptB=new B()
ptA->fun()調用A中的fun
ptB->fun()調用B中的fun
因為non-virtual函數不能進行動態綁定,調用函數只跟指針類型有關,所以
不要重寫父類的non-virtual函數
父類的non-virtual函數意味著,所有子類的實現都是這樣
條款37,絕不重新定義進程而來的缺省參數值
缺省參數都是靜態綁定的,即使是在virtual的函數中
條款38,復合(組合)是has-a的關系
條款39,明智而審慎地使用private繼承
private繼承意味著所有父類的成員在子類中都變為private,
好處:可以讓基類部分最優化,減少尺寸。
條款40,明智而審慎地使用多重繼承
一個class繼承自多個base class,那麼父類成分有相同函數,就需要顯示指定。
對於鑽石型繼承,B:A,C:A,D:B,D:C,需要指定虛繼承,來避免重復繼承A中的成分
虛繼承需要編譯器做很多工作,要付出一定成本,一般不用。
如果有單一繼承可以滿足需求,一般這個方案一定比多重繼承要好。
條款41,了解隱式接口和編譯器多態
運行時多態,通過虛指針和虛函數實現
編譯時多態
(1) 函數重載,相同函數名不同參數列表
(2) 在模板特化的時候,根據類型生成具體的函數
條款42,了解typename的雙重意義
template< class T> class Widget;
templateclass Widget;
並沒有什麼不同
當使用嵌套從屬名稱,如:
template
typename C::const_iterator iter(container.begin())
const_iterator是依賴於C的名稱,這時候必須用typename
條款43,學習處理模板化基類內的名稱
對於模板C++的繼承,由於基類模板可能被特化,特化使得基類內的成員不確定,C++會拒絕從模板化基類中尋找繼承而來的名稱
解決辦法:
在使用base class之前使用this->
使用using
條款44,將與參數無關的代碼抽離templates
使用帶參template可能會引起代碼膨脹,如:
template
解決辦法:
使用模板父類去處理由於size_t而造成的代碼膨脹的問題
條款45,運用成員函數模板接受所有兼容類型的參數
智能指針是使用模板實現的,那如果我們要智能指針之間(具有繼承關系的)能夠相互轉化,賦值,解決辦法:
使用成員函數模板,對兼容的類型進行構造和賦值
條款46,需要類型轉換時請為模版定義非成員函數
Rational a(1,2);
Rational result = a*2; // Error
模板化實例,不進行隱式類型轉換,使用friend方法。
條款47,請使用traits classes表現類型信息
引用:
traits class是個類模板,在不修改一個實體(通常是數據類型或常量)的前提下,把屬性和方法關聯到一個編譯時的實體。在c++中的具體實現方式是:首先定義一個類模板,然後進行顯式特化或進行相關類型的部分特化。
我的理解是:traits是服務於泛型編程的,其目的是讓模板更加通用,同時把一些細節向普通的模板用戶隱藏起來。當用不同的類型去實例化一個模板時,不可避免有些類型會存在一些與眾不同的屬性,若考慮這些特性的話,可能會導致形成的模板不夠“泛型”或是過於繁瑣,而traits的作用是把這些特殊屬性隱藏起來,從而實現讓模板更加通用。
條款48,認識template元編程
模版元編程有兩個效力:第一,它讓某些事情更容易;第二,可將工作從運行期轉移到編譯期。
引用:
所謂元編程就是編寫直接生成或操縱程序的程序,C++ 模板給 C++ 語言提供了元編程的能力,模板使 C++ 編程變得異常靈活,能實現很多高級動態語言才有的特性(語法上可能比較丑陋,一些歷史原因見下文)。普通用戶對 C++ 模板的使用可能不是很頻繁,大致限於泛型編程,但一些系統級的代碼,尤其是對通用性、性能要求極高的基礎庫(如 STL、Boost)幾乎不可避免的都大量地使用 C++ 模板,一個稍有規模的大量使用模板的程序,不可避免的要涉及元編程(如類型計算)。
條款49,了解new_handler的行為
new_handler 的意思就是說,當使用operator new 無法分配內存時,轉交給用戶,用戶來做一些事情。
條款50,了解new和delete的合理替換時機
有時候,我們替換掉編譯器提供的new或者delete。重寫operator new。三個常見理由:
用來檢測運用上的錯誤,超額分配一些內存,再額外的空間放置一些內存;
為了強化效能,編譯器提供的new/delete是通用的,通用就意味著冗余和效率低下,為什麼?這個很好理解,因為他要支持很多情況下,也必須考慮很多情況。我們重寫new/delete,也就是說,對於特定情況,給出特定的實現。
為了收集使用上的統計數據。
條款51,編寫new和delete時需固守常規
自定義new/delete的時候,需要遵守一些規則。
循環申請,直到成功或者拋出異常
class專屬版本處理,分配大小與class大小不一致的錯誤。
delete的時候,判斷是否為null。
條款52,寫了placement new也要寫placement delete
條款53,不要輕忽編譯器的警告
條款54,讓自己熟悉包括TR1在內的標准程序庫
C++11(原名C++0x)於2011年8月12日公布。
TR1是一份文檔,由編譯器實現,在std::tr1命名空間下
C++11納入了大部分TR1的內容
條款55,讓自己熟悉Boost
Boost是一個社區,提供很多程序庫,作為新的C++標准的試驗場。