1. 程式人生 > >校招準備系列1-C++基礎

校招準備系列1-C++基礎

C和C++的區別?

C++是C的超集,相容大部分C的語法的結構 C是面向過程的語言,而C++是面向物件的語言 C++支援函式過載,而C不支援函式過載 C++中有引用,而C沒有 C++中的模板/泛型程式設計 C中沒有異常處理,C++有tyr/catch, throw exception

變數的宣告與定義

宣告只是在名字表中註冊了這個名字(使得名字為程式所知) 定義是為該名字的變數在記憶體中分配空間(建立與名字關聯的實體) 變數要定義後才能使用

C++的三大特性

  • 封裝:隱藏物件的屬性和實現細節,僅對外提供公共訪問方式。使得程式碼模組化
  • 繼承:可以擴充套件已存在的模組,它們目的都是為了:程式碼重用。
  • 多型:不同型別的物件呼叫同一介面時產生不同的行為。為了實現另一個目的:介面重用。

過載overload、覆蓋(又稱重寫override)與隱藏

1.成員函式被“過載”的特徵: (1)相同的範圍(在同一個類中); (2)函式名字相同; (3)引數不同; (4)virtual 關鍵字可有可無。 2.“覆蓋”是指派生類重寫了基類虛擬函式,特徵是: (1)不同的範圍(分別位於派生類與基類); (2)函式名字相同; (3)引數相同; (4)基類函式必須有virtual 關鍵字。 3.“隱藏”是指派生類的函式遮蔽了與其同名的基類函式,規則如下: (1)如果派生類的函式與基類的函式同名,但是引數不同。此時,不論有無virtual關鍵字,基類的函式將被隱藏(注意別與過載混淆)。 (2)如果派生類的函式與基類的函式同名,並且引數也相同,但是基類函式沒有virtual 關鍵字。此時,基類的函式被隱藏(注意別與覆蓋混淆)。

作用域解析操作符scope resolution operator

extern關鍵字

在C++程式中呼叫被C編譯器編譯後的函式,為什麼要加extern “C”?

答:首先,extern是C/C++語言中表明函式和全域性變數作用範圍的關鍵字,該關鍵字告訴編譯器,其宣告的函式和變數可以在本模組或其它模組中使用。 通常,在模組的標頭檔案中對本模組提供給其它模組引用的函式和全域性變數以關鍵字extern宣告。extern "C"是連結宣告(linkage declaration),被extern "C"修飾的變數和函式是按照C語言方式編譯和連線的。作為一種面向物件的語言,C++支援函式過載,而C語言則不支援。函式被C++編譯後在符號庫中的名字與C語言的不同。例如,假設某個函式的原型為:void foo( int x, int y );該函式被C編譯器編譯後在符號庫中的名字為_foo,而C++編譯器則會產生像_foo_int_int之類的名字。這樣的名字包含了函式名、函式引數數量及型別資訊,C++就是靠這種機制來實現函式過載的。 所以,可以用一句話概括extern “C”這個宣告的真實目的:解決名字匹配問題,實現C++與C的混合程式設計。

#include<file.h>#include "file.h"的區別?

答:#include "file.h"會先在程式碼檔案所在當前目錄搜尋該檔案file.h,如果無法找到,再去環境變數INCLUDE指示的目錄下找

型別轉換

非bool型 -> bool型:初始值為0則結果為false,否則為true bool型 -> 非bool型:初始值為false則為0,初始值為true則為1 浮點數 -> 整數型別:僅保留整數部分的值 整數 -> 浮點型別:小數部分記為0 其他型別 -> 無符號型別:如果沒有超出無符號型別數的表示範圍,則正常轉換。否則,結果是初始值對無符號型別數的總數取模後的餘數。例如 unsigned char(0~255),如果給它賦值-1,則會轉換成255. !!!無符號數和有符號數切勿混合運算

左值與右值

在C++11中可以取地址的、有名字的就是左值;反之,不能取地址的、沒有名字的就是右值(將亡值或純右值) ++a得到左值,而a++得到右值。 int operator++ (int) 而++右操作符操作函式時,相當於這樣,返回的依然是一個int型,所以無論++在a的左邊多少個都是可以的。 const int operator++() 注意這裡返回的是一個const的,const只能作為右值,而不能作為左值的。 http://www.cnblogs.com/catch/p/3500678.html https://blog.csdn.net/hyman_yx/article/details/52044632

inline和巨集定義

內聯inline:只是向編譯器建議該函式需要內聯,但是編譯器並不一定會做。 行內函數要做引數型別檢查, 這是行內函數跟巨集相比的優勢。 行內函數是指用inline關鍵字修飾的簡單短小的函式,它在編譯階段會在函式呼叫處替換成函式語句,從而減少函式呼叫的開銷。在類內定義的函式被預設成行內函數。 行內函數有一定的限制,行內函數體要求程式碼簡單,不能包含複雜的結構控制語句。如果行內函數函式體過於複雜,編譯器將自動把行內函數當成普通函式來執行。 巨集定義是沒有型別檢查的,無論對還是錯都是直接替換。行內函數在編譯的時候會進行型別的檢查,行內函數滿足函式的性質,比如有返回值、引數列表等 巨集定義和行內函數使用的時候都是進行程式碼展開。不同的是巨集定義是在預處理的時候把所有的巨集名替換,行內函數則是在編譯階段把所有呼叫行內函數的地方把行內函數插入。 1.巨集定義是在預處理階段進行簡單替換,而行內函數是在編譯階段進行替換 2.編譯器會對行內函數的引數型別做安全檢查或自動型別轉換,而巨集定義則不會; 3.行內函數在執行時可除錯,而巨集定義不可以 4.在類中宣告同時定義的成員函式,自動轉化為行內函數。 5.巨集定義會出現二義性,而行內函數不會 6.行內函數可以訪問類的成員變數,巨集定義則不能

typedef

typedef是為已存在資料型別定義一個新名字。例如

typedef struct Student
{
    int a;
}stu;

Student s1;
stu s2;

const 有什麼用途?

const表示XXX是不變的。const變數需要初始化,否則以後也無法修改了,就沒有意義了。 const變數 const int a=10; const與指標 先說指向const變數的指標(底層const),它的意思是指標指向的內容是不能被修改的。它有兩種寫法。

  • const int* p; (推薦)
  • int const* p;

再說const指標(頂層const),它的意思是指標本身的值是不能被修改的。它只有一種寫法

  • int* const p=[address];(因為指標的值是const的,所以必須要初始化)

const與引用 const int &a=10;表示a是一個常量的引用 const與函式 返回值修飾為const的 const int & func(){} 這樣的話,返回值就可以當作左值。一般只出現在類的賦值函式中,目的是為了實現鏈式表達,例如<<。 const與類

class X{
    int i;
    int f() const;
}
int X::f() const {return i; }

const成員函式是不改變類的資料成員的一類函式,也就是隻讀函式。並且const成員函式不能呼叫非const成員函式(因為可能會造成修改)。 print() const和print()構成過載

static關鍵字的作用

對於類的static資料成員:若是const static 成員,可以在類內初始化,也可以在類外初始化;若是普通的static成員,必須在類外初始化。 static 全域性變數只能在本檔案中訪問,其他檔案不可見 static 函式只能在本檔案中訪問,其他檔案不可見 static總是使得變數或物件的儲存形式變成靜態儲存,連線方式變成內部連線(與之相反的是外部連線extern)。 對於區域性變數(已經是內部連線了),它僅改變其儲存方式;對於全域性變數(已經是靜態儲存了),它僅改變其連線型別。

靜態成員與非靜態成員之間的可訪問性?

靜態成員之間可以互相訪問;非靜態成員可以訪問靜態成員及非靜態成員,而靜態成員不能訪問非靜態成員。

關於sizeof小結

答:編譯階段處理 (1) sizeof不計算static變數佔得記憶體; (2) 指標的大小一定是4/8(32/64位編譯器)個位元組,而不管是什麼型別的指標(包括void*); (3) char型佔1個位元組,short int佔2個位元組,int佔4個位元組, long int佔4個位元組,float佔4位元組,double佔4/8位元組,string佔4位元組 一個空類佔1個位元組,單一繼承的空類佔1個位元組,虛繼承涉及到虛指標所以佔4/8個位元組(1個指標長度) (4) 陣列的長度 若指定了陣列長度,則不看元素個數,總位元組數=陣列長度*sizeof(元素型別) 若沒有指定長度,則按實際元素個數類確定 Ps:若是字元陣列,則應考慮末尾的空字元。 (5) 結構體物件的長度 在預設情況下,為方便對結構體內元素的訪問和管理,當結構體內元素長度小於處理器位數的時候,便以結構體內最長的資料元素的長度為對齊單位,即為其整數倍。若結構體內元素長度大於處理器位數則以處理器位數為單位對齊。 (6) unsigned影響的只是最高位的意義,資料長度不會改變,所以sizeof(unsigned int)=4 (7) 自定義型別的sizeof取值等於它的型別原型取sizeof (8) 對函式使用sizeof,在編譯階段會被函式的返回值的型別代替 (9) sizeof後如果是型別名則必須加括號,如果是變數名可以不加括號,這是因為sizeof是運算子 (10)當使用結構型別或者變數時,sizeof返回實際的大小。當使用靜態陣列時返回陣列的全部大小,sizeof不能返回動態陣列或者外部陣列的尺寸 (11) 對於類物件使用sizeof()。如果類是空的,至少size為1位元組,因為每個物件必須有一個互相區別的地址。如果類中有虛擬函式,由於有vptr,它會佔4位元組(如果是64位編譯器,則為8位元組)。對於派生類物件,考慮基類部分資料成員的大小。 (12) sizeof(std::string) = 32, sizeof(string_object) = 32 sizeof(vector) = 24, sizeof(vector_object) = 24 (13) sizeof(union_object)取決於它所有的成員中,佔用空間最大的一個成員的大小,然後對齊,對齊方式為補齊到成員中佔用空間最大的成員的整數倍大小。 (14) sizeof(enum_object) = 4/8

strlen得到字串的長度

strlen() returns the length of the C string str. 對於char陣列,從第一個元素開始計算直到首次遇到’\0’為止(’\0’自動新增且不計算在內);如果要對string使用,記得strlen(s.c_str())。

new和delete

new 運算子和 operator new()函式:
  • new:指我們在C++裡通常用到的運算子,比如A* a = new A; 對於new來說,有new和::new之分,前者位於std。
  • operator new():指對new的過載形式,它是一個函式,不是運算子。對於operator new來說,分為全域性過載和類過載。
new和delete操作
A* p = new A;
delete p;
A* p_a = new A[10];
delete[] p_a;

new A在堆上分配一個A物件大小的記憶體,並返回它的首地址。1.分配記憶體;2.呼叫A()構造物件。事實上,分配記憶體這一操作就是由operator new(size_t)來完成的。如果有過載,則會呼叫過載的operator new(size_t)。 delete p會 1.銷燬給定的指標指向的物件;2.釋放相應的記憶體; delete p中的p必須指向一個動態分配的物件或是一個空指標(即0)

引用與指標

引用是變數的一個別名,內部實現是隻讀指標 引用變數記憶體單元儲存的是被引用變數的地址。例如int &b = a;則&b(對b取地址)得到的是a的地址。

陣列和指標的區別?

不是一個東西。 陣列是用來表示記憶體中的一段連續儲存空間,有基地址base和長度length,且都是不可修改的。陣列的長度必須在編譯時確定,因為需要為它分配記憶體空間。陣列的地址不可修改。 指標用來儲存某個物件的地址,地址可以修改(不是const指標的話) 陣列名作右值時自動轉換成指向首元素的指標,所以a[2]和pa[2]本質上是一樣的,都是通過指標間接定址訪問元素 二維陣列作為引數傳遞時需要指明陣列的第二維,第一維不是必須要顯式指定的。

記憶體分配函式

  • malloc:在堆上申請記憶體,最常用;
  • calloc:malloc+初始化為0;
  • realloc:將原本申請的記憶體區域擴容,引數size大小即為擴容後大小,因此此函式要求size大小必須大於舊ptr記憶體大小。
  • alloca:唯一在棧上申請記憶體的,無需釋放;

C++中有malloc/free,為什麼還有new/delete?

答:malloc/free是C/C++標準庫函式,new/delete是C++運算子operator。他們都可以用於動態申請和釋放記憶體。 對於原始資料資料而言,二者沒有多大區別。malloc申請記憶體的時候要指定分配記憶體的位元組數,而且不會做初始化;new申請的時候有預設的初始化,同時可以指定初始化; 對於類型別的物件而言,用malloc/free無法滿足要求的。物件在建立的時候要自動執行建構函式,消亡之前要呼叫解構函式。由於malloc/free是庫函式而不是運算子,不在編譯器控制之內,不能把執行建構函式和解構函式的任務強加給它,因此,C++還需要new/delete。

new的過程
  1. 向堆上申請一塊記憶體空間(做夠容納物件A大小的資料)(operator new)
  2. 呼叫建構函式
  3. (呼叫A的建構函式(如果A有的話))(placement new) 返回正確的指標 注意:placement new是在已分配的一塊記憶體buffer上構造物件。需要手動呼叫物件的解構函式來析構物件。而記憶體buffer的釋放由buffer變數負責 https://blog.csdn.net/d_guco/article/details/54019495

記憶體對齊的原則

  1. 結構體內成員按自身長度自對齊。自身長度,如char=1,short=2,int=4,long=8,double=8。所謂自對齊,指的是該成員的起始位置的記憶體地址必須是它自身長度的整數倍。如int只能以0,4,8這類的地址開始
  2. 結構體的總大小為結構體的有效對齊值的整數倍,不足時應當補齊。當用#pragma pack(n)指定時,以n和結構體中最長的成員的長度中較小者為有效對齊值。

智慧指標

智慧指標是利用了一種叫做RAII(資源獲取即初始化)的技術對普通的指標進行封裝的模板類 智慧指標類將一個計數器與類指向的物件相關聯,引用計數跟蹤該類有多少個物件共享同一指標。每次建立類的新物件時,初始化指標並將引用計數置為1;當物件作為另一物件的副本而建立時,拷貝建構函式拷貝指標並增加與之相應的引用計數;對一個物件進行賦值時,賦值操作符減少左運算元所指物件的引用計數(如果引用計數為減至0,則刪除物件),並增加右運算元所指物件的引用計數;呼叫解構函式時,建構函式減少引用計數(如果引用計數減至0,則刪除該物件)。智慧指標就是模擬指標動作的類。所有的智慧指標都會過載 -> 和 * 操作符。 智慧指標還有許多其他功能,比較有用的是自動銷燬。這主要是利用棧物件的有限作用域以及臨時物件(有限作用域實現)解構函式釋放記憶體。

拷貝控制

拷貝建構函式 (引數是左值引用) 拷貝賦值運算子(引數是左值引用) 移動建構函式(引數是右值引用) 移動賦值運算子(引數是右值引用) 解構函式 https://www.cnblogs.com/xkfz007/archive/2012/05/11/2496447.html CSomething c[3];//使用無參建構函式C(),建立了3個物件。 可通過將拷貝控制成員定義為=default來顯示要求編譯器為我們rengong合成拷貝控制成員。 可以通過在拷貝建構函式和拷貝賦值運算子後面加=delete 來阻止拷貝。

C++ 拷貝建構函式和拷貝賦值運算子

https://www.cnblogs.com/wangguchangqing/p/6141743.html 呼叫的是拷貝建構函式還是賦值運算子,主要是看是否有新的物件例項產生。如果產生了新的物件例項,那呼叫的就是拷貝建構函式;如果沒有,那就是對已有的物件賦值,呼叫的是賦值運算子。 深拷貝淺拷貝主要是針對類中的指標和動態分配的空間來說的。淺拷貝僅拷貝類中成員變數的值,而深拷貝會把類中指標指向的記憶體拷貝一份(當然指標也會拷貝)。這兩塊記憶體互不影響。 類的靜態成員是所有類的例項共有的,儲存在全域性(靜態)區,只此一份,不管繼承、例項化還是拷貝都是一份。因此類的靜態成員不允許深拷貝。

僅有2種值傳遞方式

pass by value:實參的值被拷貝給形參 pass by reference:形參被繫結到了實參上,或者說,形參是實參的別名。

面向物件

關鍵知識點

派生類中和基類同名的變數,有各自的作用域,所以可以同時存在。

this指標

this是指向物件自己的指標。

多型分為哪幾種?

分為編譯時多型性和執行時多型性。

  • 編譯時多型性:通過函式過載實現。過載允許有多個同名的函式,而這些函式的引數列表不同。編譯器會根據實參型別來選定相應的函式。
  • 執行時多型性:通過虛擬函式實現。虛擬函式允許子類重新定義成員函式,而子類重新定義父類的做法稱為覆蓋(override),或者重寫。
動態繫結

執行時根據引用或指標所繫結物件的實際型別來選擇執行虛擬函式的某一個版本 建構函式、靜態函式不能是虛擬函式。 訪問說明符:控制派生來從基類繼承而來的成員是否對派生類的使用者可見 類成員的初始化應該交給自己類的建構函式去完成,派生類不要初始化從基類繼承而來的成員 派生類可以繼承基類的靜態成員,也可以覆蓋基類的靜態成員變數 C++11中,可以在派生類的成員函式的形參列表後使用override關鍵字,顯示地說明它將覆蓋基類的虛擬函式 C++11中防止繼承:在類名後、函式形參列表後使用final關鍵字 物件切割Object Slicing:當把一個派生類物件賦給一個基類物件時,會發生物件切割。派生類物件中僅有基類部分的成員會被拷貝和賦值。

虛擬函式相關

虛擬函式表是在編譯期就建立了,各個虛擬函式這時被組織成了一個虛擬函式的入口地址的陣列.而物件的隱藏成員–虛擬函式表指標是在執行期–也就是建構函式被呼叫時進行初始化的,這是實現多型的關鍵。 RTTI在C++中體現為dynamic_cast和typeid。如何實現RTTI? VS中虛擬函式表的-1位置存放了指向type_info的指標。對於存在虛擬函式的型別,typeid和dynamic_cast都會去查詢type_info.

多型性:指同樣的訊息被不同型別的物件接收時導致不同的行為(不同型別的物件呼叫同一介面時產生不同的行為)。這裡講的訊息就是指物件的成員函式的呼叫,而不同的行為是指不同的實現,也就是呼叫了不同的函式。引用或指標的靜態型別Base*與動態型別Derived*不同是C++語言支援多型性的根本所在(對於不是引用或指標的物件,型別是確定的,不存在動態繫結)。 只有引用或指標呼叫虛擬函式時會發生動態繫結(dynamic binding) 在建構函式中呼叫一個虛擬函式的情況,被呼叫的只是這個函式的本地版本(early binding的函式的版本)。 純虛擬函式:在虛擬函式後加上=0.表明該函式沒有實際意義,只能用來重寫。 抽象基類:含有純虛擬函式的類是抽象基類。抽象基類負責定義介面,其派生類可以覆蓋/重寫該介面。不能建立一個抽象基類的物件。對於一個抽象基類,如果其派生類沒有重寫基類中的所有虛擬函式,則派生類也還是抽象類(仍是不完整的) 虛擬函式雖然可以寫inline,但是最終編譯器並不會內聯。因為行內函數是靜態行為(編譯時),虛擬函式是動態行為(執行時),編譯時並不知道呼叫者是誰,內聯的程式碼該取誰的程式碼; 動態繫結如何實現:需要vtable來儲存特定類的虛擬函式的地址,而vptr指向vtable。vptr儲存在有虛擬函式的類的物件中,佔大小為1個指標的size。

建構函式為什麼不能是虛擬函式?

答:建構函式如果是虛擬函式,那麼虛擬函式機制、動態繫結生效之前,需要知道指標到底指向哪個物件(即物件已存在)。但是建構函式還未完成,物件並沒有產生。矛盾。

基類的解構函式不是虛擬函式會怎樣?

答:如果有一個基類指標指向派生類物件,當物件銷燬時,由於沒有虛擬函式,不會發生動態繫結,呼叫的是基類的解構函式(因為是基類物件),就會造成記憶體洩漏。

成員訪問許可權控制

public:用該關鍵字修飾的成員表示公有成員,該成員不僅可以在類內可以被訪問,在類外也是可以被訪問的,是類對外提供的可訪問介面; private:用該關鍵字修飾的成員表示私有成員,該成員僅在類內可以被訪問,在類體外是隱藏狀態; protected:用該關鍵字修飾的成員表示保護成員,保護成員在類體外同樣是隱藏狀態,但是對於該類的派生類來說,相當於公有成員,在派生類中可以被訪問。 詳見下圖:

基類成員訪問許可權 基類外部呼叫 基類類內訪問基類的成員 派生類類內訪問基類的成員
public Y Y Y
protected N Y Y
private N Y N

C++的3種繼承方式

public、protected、private 派生類中派生類部分成員的訪問許可權就是取決於派生類中定義的許可權,而基類部分成員的訪問許可權受到繼承方式的影響。見下圖。

基類成員的訪問許可權 public繼承後 protected繼承後 private繼承後
public public protected private
protected protected protected private
private private private private

C++友元

一個類class A想要允許非自己類的成員函式或一般函式訪問自己類A的私有成員(及受保護成員),而同時仍阻止一般的訪問的情況下,可以使用友元。

  • 友元函式:在類中將一般函式、其他類的成員函式宣告成友元函式,那麼這些友元函式就能夠訪問該類的私有、保護成員 友元類:在類A中將類B宣告成友元類,那麼這個友元類B的所有成員函式都是類A的友元函式,能夠訪問類A的私有、保護成員

記憶體區域劃分(五個段)

名稱 內容
程式碼段text 可執行程式碼、字串常量
資料段data 已初始化的全域性變數、靜態區域性變數、常量資料
bss段 未初始化的全域性變數
區域性變數、函式形參
動態記憶體分配

程式碼段 --text(code segment/text segment) .text段在記憶體中被對映為只讀,但.data和.bss是可寫的。 text段是程式程式碼段,在AT91庫中是表示程式段的大小,它是由編譯器在編譯連線時自動計算的,當你在連結定位檔案中將該符號放置在程式碼段後,那麼該符號表示的值就是程式碼段大小,編譯連線時,該符號所代表的值會自動代入到源程式中。在程式碼段中,也有可能包含一些只讀的常數變數,例如字串常量等。 全域性資料區(靜態儲存區)(static): 全域性變數和靜態變數的儲存是放在一塊的,初始化的全域性變數和靜態變數在一塊區域,未初始化的全域性變數和靜態變數在相鄰的另一塊區域。另外文字常量區,常量字串就是放在這裡,程式結束後由系統釋放。 (1) BSS段(bss segment):通常是指用來存放程式中未初始化的全域性變數、靜態全域性變數的一塊記憶體區域。BSS段屬於靜態記憶體分配。 (2) 資料段(data segment):通常是指用來存放程式中已初始化的全域性變數、靜態全域性變數和靜態區域性變數(無論是否初始化)的一塊記憶體區域。資料段屬於靜態記憶體分配。 stack: 棧,也稱堆疊(stack)儲存函式的區域性變數(但不包括static宣告的變數, static 意味著 在資料段中 存放變數),引數以及返回值。是一種“後進先出”(Last In First Out,LIFO)的資料結構,這意味著最後放到棧上的資料,將會是第一個從棧上移走的資料。對於哪些暫時存貯的資訊,和不需要長時間儲存的資訊來說,LIFO這種資料結構非常理想。在呼叫函式或過程後,系統通常會清除棧上儲存的區域性變數、函式呼叫資訊及其它的資訊。棧另外一個重要的特徵是,它的地址空間“向下減少”,即當棧上儲存的資料越多,棧的地址就越低。棧(stack)的頂部在可讀寫的RAM區的最後。 heap: 堆(heap)儲存函式內部動態分配記憶體,是另外一種用來儲存程式資訊的資料結構,更準確的說是儲存程式的動態變數。堆是“先進先出”(First In first Out,FIFO)資料結構。它只允許在堆的一端插入資料,在另一端移走資料。堆的地址空間“向上增加”,即當堆上儲存的資料越多,堆的地址就越高。

一些疑問 char str1[] = “abcd”; const char str3[] = “abcd”; const char *str5 = “abcd”; char *str7 = “abcd”; char陣列是區域性變數,儲存在棧中 char指標指向的字串是儲存在靜態儲存區中

記憶體分配方式

靜態分配和動態分配

  • 時間不同。靜態分配發生在程式編譯和連線的時候。動態分配則發生在程式調入和執行的時候。
  • 空間不同。堆都是動態分配的,沒有靜態分配的堆。棧有2種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,比如區域性變數的分配。動態分配是由malloc或new進行分配。不過棧的動態分配(使用alloca函式)和堆不同,它動態分配得到的記憶體是由編譯器進行釋放,無需我們手工實現。

動態記憶體分配與智慧指標

在呼叫建構函式之前,物件就已經建立好了(記憶體已分配) new與delete成對操作(對於陣列,type *p = new type[100]; delete [] fp;) C++11後提供智慧指標,shared_ptr、unique_ptr、weak_ptr、 auto_ptr(C++11後棄用)。auto_ptr不可作為容器元素,但unique_ptr,shared_ptr可以作為容器元素。。原因:stl的容器中有很多需要拷貝元素的操作,如排序、查詢等。(unique_ptr)auto_ptr拷貝賦值時是所有權轉移,而在容器中可能會產生臨時物件拷貝賦值,臨時物件獲得所有權之後就析構了釋放了物件。並且很難避免STL內部對容器中的元素實現賦值,這樣便會使容器中多個元素被置位NULL。。 auto_ptr有拷貝語義,拷貝後源物件變得無效,這可能引發很嚴重的問題;而unique_ptr則無拷貝語義,但提供了移動語義,這樣的錯誤不再可能發生,因為很明顯必須使用std::move()進行轉移。 auto_ptr不可指向動態陣列,unique_ptr可以指向動態陣列。因為unique_ptr有unique_ptr<T[]>過載版本,銷燬動態物件時呼叫delete[]。

auto_ptr

auto_ptr是C++標準庫中(<utility>)為了解決資源洩漏的問題提供的一個輕量級智慧指標類模板(注意:這只是一種簡單的智慧指標) auto_ptr的實現原理其實就是RAII,在構造的時候獲取資源,在析構的時候釋放資源,並進行相關指標操作的過載,使用起來就像普通的指標。

auto_ptr<A> a(new A());

auto_ptr沒有考慮引用計數,因此一個物件只能由一個auto_ptr所擁有,在給其他auto_ptr賦值的時候,會轉移這種所有權。 auto_ptr不能共享所有權,即不要讓兩個auto_ptr指向同一個物件。 auto_ptr不能指向陣列,因為auto_ptr在析構的時候只是呼叫delete,而陣列應該要呼叫delete[]。

shared_ptr

允許多個shared_ptr指向同一個物件。它通常使用make_shared()來構造。 shared_ptr有一個引用計數來記錄有多少個其他shared_ptr指向相同的物件。 當shared_ptr物件被拷貝時,計數加1;當shared_ptr物件被賦予新值或銷燬時,計數減1; 當一個share_ptr的引用計數為0時,它會自動釋放所管理的物件(包括動態分配的記憶體)。

unique_ptr

unique_ptr“唯一”擁有其所指物件,同一時刻只能有一個unique_ptr指向給定物件(通過禁止拷貝語義、只有移動語義來實現)。 通過reset方法重新指定、通過release方法釋放所有權、通過移動語義auto new_p = std::move(origin);轉移所有權。

weak_ptr

weak_ptr是為了配合shared_ptr而引入的一種智慧指標,因為它不具有普通指標的行為,沒有過載operator和->,它的最大作用在於協助shared_ptr工作,像旁觀者那樣觀測資源的使用情況。weak_ptr可以從一個shared_ptr或者另一個weak_ptr物件構造,獲得資源的觀測權。但weak_ptr沒有共享資源,它的構造不會引起指標引用計數的增加。 使用weak_ptr的成員函式use_count()可以觀測資源的引用計數. 另一個成員函式expired()的功能等價於use_count()==0,但更快,表示被觀測的資源(也就是shared_ptr的管理的資源)已經不復存在。 weak_ptr可以使用一個非常重要的成員函式lock()*從被觀測的shared_ptr獲得一個可用的shared_ptr物件, 從而操作資源。但當expired()==true的時候,lock()函式將返回一個儲存空指標的shared_ptr。 !!!不要混合使用普通指標和智慧指標

強制型別轉換

C風格的型別轉換 (int) a,或者 int (a)

C++11中四種類型轉換

1. static_cast

功能:完成編譯器認可的隱式型別轉換。 type2 b = staic_cast<type1>(a);將type1的型別轉化為type2的型別; 使用範圍: (1)基本資料型別之間的轉換,如int->double; (2)派生體系中向上轉型:將派生類指標或引用轉化為基類指標或引用(向上轉型);

2. dynamic_cast

功能:執行派生類指標或引用與基類指標或引用之間的轉換。 type2 b = dynamic_cast<type1>(a);將type1的型別轉化為type2的型別; (1)其他三種都是編譯時完成的,dynamic_cast是執行時處理的,執行時要進行執行時型別檢查; (2)基類中要有虛擬函式。因為執行時型別檢查的型別資訊在虛擬函式表中,有虛擬函式才會有虛擬函式表; (3)可以實現向上轉型和向下轉型,前提是必須使用public或protected繼承;

3. const_cast

功能:主要是用來去除複合型別中const和volatile屬性(沒有真正去除)。 原因是,我們可能呼叫了一個引數不是const的函式,而我們要傳進去的實際引數確實const的,但是我們知道這個函式是不會對引數做修改的。於是我們就需要使用const_cast去除const限定,以便函式能夠接受這個實際引數。 應該遵從這樣的原則:使用const_cast去除const限定的目的絕對不是為了修改它的內容,只是出於無奈。

4. reinterpret_cast

reinterpret_cast運算子是用來處理無關型別之間的轉換;它會產生一個新的值,這個值會有與原始引數(expressoin)有完全相同的位元位.

模板與泛型

編譯時就知道型別是什麼了 編譯器用推斷出的模板引數來為我們**例項化(instantiate)**一個特定版本的函式。 例項化:使用實際的模板實參代替對應的模板引數來創建出模板的一個新“例項”(instantiation)。 編譯器遇到模板定義時,並不生成程式碼。只有當我們例項化出模板的一個特定版本(使用模板)時,編譯器才生成程式碼。

函式模板

template <typename T>
int compare(const T &v1, const T &v2){
    if(v1 < v2) return -1;
    else if(v2 < v1) return 1;
    else return 0;
}

**模板實參推斷:**呼叫函式模板時,編譯器通常用函式實參來為我們推斷模板實參。 類模板因為沒有模板實參推斷,為了使用類模板,必須要提供額外的資訊(在模板名後面的尖括號中) 可以為函式模板提供顯示模板實參,顯示模板實參按由左至右的順序與對應的模板引數匹配。例如:

// 編譯器無法推斷T1,因為它未出現在函式引數列表中
template <typename T1, typename T2, typename T3>
T1 sum(T2, T3);

呼叫時可以使用sum<long long>(i,long);//long long sum(int, long)來顯示指定T1的型別。

成員函式模板

和函式模板類似。 如果在類模板外定義成員模板,必須同時為類模板和成員模板提供模板引數列表。類模板的引數列表在前,成員函式模板的引數列表在後。

過載與模板

函式模板可以被另一個模板或一個普通非模板函式過載。

模板特例化與偏特例化

函式模板特例化 當特例化一個函式模板時,必須為原模板中的每個模板引數都提供實參。使用關鍵字template後跟一個空尖括號<>,即template <>,以指出我們正在特例化一個模板。

template <typename T>
void fun(T a)
{
	cout << "The main template fun(): " << a << endl;
}

template <>   // 對int型特例化
void fun(int a)
{
	cout << "Specialized template for int type: " << a << endl;
}

int main()
{
	fun<char>('a');
	fun<int>(10);
	fun<float>(9.15);
	return 0;
	//output
	/*
	The main template fun(): a
    Specialized template for int type: 10
    The main template fun(): 9.15
    */
}

對於除int型外的其他資料型別,都會呼叫通用版本的函式模板fun(T a);對於int型,則會呼叫特例化版本的fun(int a)。**注意,一個特例化版本的本質是一個例項,而非函式的過載。**因此,特例化不影響函式匹配。 !!!函式模板不能偏特化

類模板特例化

template <typename T>
class Test{
public:
	void print(){
		cout << "General template object" << endl;
	}
};

template<>   // 對int型特例化
class Test<int>{
public:
	void print(){
		cout << "Specialized template object" << endl;
	}
};

另外,與函式模板不同,類模板的特例化不必為所有模板引數提供實參。我們可以只指定一部分而非所有模板引數,這種叫做類模板的偏特化 或部分特例化(partial specialization)。

類模板偏特例化

template <typename T, typename Allocator>
class vector
{
	/*......*/
};

// 部分特例化
template <typename Allocator>
class vector<bool, Allocator>
{
	/*......*/
};

在vector這個例子中,一個引數被繫結到bool型別,而另一個引數仍未繫結需要由使用者指定。**注意,一個類模板的部分特例化版本仍然是一個模板,**因為使用它時使用者還必須為那些在特例化版本中未指定的模板引數提供實參。