虛函數是容許被其子類從新定義的成員函數。html
虛函數的聲明:virtual returntype func(parameter);引入虛函數的目的是爲了動態綁定;ios
純虛函數聲明:virtual returntype func(parameter)=0;引入純虛函數是爲了派生接口。(使派生類僅僅只是繼承函數的接口)c++
防止內存泄漏。想去借助父類指針去銷燬子類對象的時候,不能去銷燬子類對象。假如沒有虛析構函數,釋放一個由基類指針指向的派生類對象時,不會觸發動態綁定,則只會調用基類的析構函數,不會調用派生類的。派生類中申請的空間則得不到釋放致使內存泄漏。程序員
幾乎同樣。i++返回的是i的值,++i返回的是i+1的值,即++i是一個肯定的值,是一個能夠修改的左值。算法
reserve()用於讓容器預留空間,避免再次分配內存;capacity()返回在從新進行分配之前所能容納的元素數量。編程
一般在類外申明static成員,可是static const的整型(bool,char,int,long)能夠在類中聲明且初始化,static const的其餘類型必須在類外初始化(包括整型數組)。數組
static的做用:安全
對變量:數據結構
1.局部變量:socket
在局部變量以前加上關鍵字static,局部變量就被定義成爲一個局部靜態變量。
1)內存中的位置:靜態存儲區
2)初始化:未經初始化的全局靜態變量會被程序自動初始化爲0(自動對象的值是任意的,除非他被顯示初始化)
3)做用域:做用域仍爲局部做用域,當定義它的函數或者語句塊結束的時候,做用域隨之結束。
注:當static用來修飾局部變量的時候,它就改變了局部變量的存儲位置(從原來的棧中存放改成靜態存儲區)及其生命週期(局部靜態變量在離開做用域以後,並無被銷燬,而是仍然駐留在內存當中,直到程序結束,只不過咱們不能再對他進行訪問),但未改變其做用域。
2.全局變量
在全局變量以前加上關鍵字static,全局變量就被定義成爲一個全局靜態變量。
1)內存中的位置:靜態存儲區(靜態存儲區在整個程序運行期間都存在)
2)初始化:未經初始化的全局靜態變量會被程序自動初始化爲0(自動對象的值是任意的,除非他被顯示初始化)
3)做用域:全局靜態變量在聲明他的文件以外是不可見的。準確地講從定義之處開始到文件結尾。
注:static修飾全局變量,並未改變其存儲位置及生命週期,而是改變了其做用域,使當前文件外的源文件沒法訪問該變量,好處以下:(1)不會被其餘文件所訪問,修改(2)其餘文件中可使用相同名字的變量,不會發生衝突。對全局函數也是有隱藏做用。而普通全局變量只要定義了,任何地方都能使用,使用前須要聲明全部的.c文件,只能定義一次普通全局變量,可是能夠聲明屢次(外部連接)。注意:全局變量的做用域是全局範圍,可是在某個文件中使用時,必須先聲明。
對類中的:
1.成員變量
用static修飾類的數據成員實際使其成爲類的全局變量,會被類的全部對象共享,包括派生類的對象。所以,static成員必須在類外進行初始化(初始化格式: int base::var=10;),而不能在構造函數內進行初始化,不過也能夠用const修飾static數據成員在類內初始化 。由於靜態成員屬於整個類,而不屬於某個對象,若是在類內初始化,會致使每一個對象都包含該靜態成員,這是矛盾的。
特色:
1.不要試圖在頭文件中定義(初始化)靜態數據成員。在大多數的狀況下,這樣作會引發重複定義這樣的錯誤。即便加上#ifndef #define #endif或者#pragma once也不行。
2.靜態數據成員能夠成爲成員函數的可選參數,而普通數據成員則不能夠。
3.靜態數據成員的類型能夠是所屬類的類型,而普通數據成員則不能夠。普通數據成員的只能聲明爲 所屬類類型的指針或引用。
2.成員函數
不能夠同時用const和static修飾成員函數。
C++編譯器在實現const的成員函數的時候爲了確保該函數不能修改類的實例的狀態,會在函數中添加一個隱式的參數const this*。但當一個成員爲static的時候,該函數是沒有this指針的。也就是說此時const的用法和static是衝突的。
咱們也能夠這樣理解:二者的語意是矛盾的。static的做用是表示該函數只做用在類型的靜態變量上,與類的實例沒有關係;而const的做用是確保函數不能修改類的實例的狀態,與類型的靜態變量沒有關係。所以不能同時用它們。
const的做用:
1.限定變量爲不可修改。
2.限定成員函數不能夠修改任何數據成員。
3.const與指針:
const char *p 表示 指向的內容不能改變。
char * const p,就是將P聲明爲常指針,它的地址不能改變,是固定的,可是它的內容能夠改變。
本質上的區別是,指針是一個新的變量,只是這個變量存儲的是另外一個變量的地址,咱們經過訪問這個地址來修改變量。
而引用只是一個別名,仍是變量自己。對引用進行的任何操做就是對變量自己進行操做,所以以達到修改變量的目的。
注:
(1)指針:指針是一個變量,只不過這個變量存儲的是一個地址,指向內存的一個存儲單元;而引用跟原來的變量實質上是同一個東西,只不過是原變量的一個別名而已。如:
int a=1;int *p=&a;
int a=1;int &b=a;
上面定義了一個整形變量和一個指針變量p,該指針變量指向a的存儲單元,即p的值是a存儲單元的地址。
而下面2句定義了一個整形變量a和這個整形a的引用b,事實上a和b是同一個東西,在內存佔有同一個存儲單元。
(2)能夠有const指針,可是沒有const引用(const引用可讀不可改,與綁定對象是否爲const無關)
注:引用能夠指向常量,也能夠指向變量。例如int &a=b,使引用a指向變量b。而爲了讓引用指向常量,必須使用常量引用,如const int &a=1; 它表明的是引用a指向一個const int型,這個int型的值不能被改變,而不是引用a的指向不能被改變,由於引用的指向原本就是不可變的,無需加const聲明。即指針存在常量指針int const *p和指針常量int *const p,而引用只存在常量引用int const &a,不存在引用常量int& const a。
(3)指針能夠有多級,可是引用只能是一級(int **p;合法 而 int &&a是不合法的)
(4)指針的值能夠爲空,可是引用的值不能爲NULL,而且引用在定義的時候必須初始化;
(5)指針的值在初始化後能夠改變,即指向其它的存儲單元,而引用在進行初始化後就不會再改變了。
(6)"sizeof引用"獲得的是所指向的變量(對象)的大小,而"sizeof指針"獲得的是指針自己的大小;
(7)指針和引用的自增(++)運算意義不同;
(8)指針使用時須要解引用(*),引用則不須要;
C++ 多態有兩種:靜態多態(早綁定)、動態多態(晚綁定)。靜態多態是經過函數重載實現的;動態多態是經過虛函數實現的。
1.定義:「一個接口,多種方法」,程序在運行時才決定要調用的函數。
2.實現:C++多態性主要是經過虛函數實現的,虛函數容許子類重寫override(注意和overload的區別,overload是重載,是容許同名函數的表現,這些函數參數列表/類型不一樣)。
注:多態與非多態的實質區別就是函數地址是靜態綁定仍是動態綁定。若是函數的調用在編譯器編譯期間就能夠肯定函數的調用地址,併產生代碼,說明地址是靜態綁定的;若是函數調用的地址是 須要在運行期間才肯定,屬於動態綁定。
3.目的:接口重用。封裝可使得代碼模塊化,繼承能夠擴展已存在的代碼,他們的目的都是爲了代碼重用。而多態的目的則是爲了接口重用。
4.用法:聲明基類的指針,利用該指針指向任意一個子類對象,調用相應的虛函數,能夠根據指向的子類的不一樣而實現不一樣的方法。
用一句話歸納:在基類的函數前加上virtual關鍵字,在派生類中重寫該函數,運行時將會根據對象的實際類型來調用相應的函數。若是對象類型是派生類,就調用派生類的函數;若是對象類型是基類,就調用基類的函數。
關於重載、重寫、隱藏的區別 Overload(重載):在C++程序中,能夠將語義、功能類似的幾個函數用同一個名字表示,但參數或返回值不一樣(包括類型、順序不一樣),即函數重載。 (1)相同的範圍(在同一個類中); (2)函數名字相同; (3)參數不一樣; (4)virtual 關鍵字無關緊要。 Override(覆蓋或重寫):是指派生類函數覆蓋基類函數,特徵是: (1)不一樣的範圍(分別位於派生類與基類); (2)函數名字相同; (3)參數相同; (4)基類函數必須有virtual 關鍵字。 注:重寫基類虛函數的時候,會自動轉換這個函數爲virtual函數,無論有沒有加virtual,所以重寫的時候不加virtual也是能夠的,不過爲了易讀性,仍是加上比較好。 Overwrite(重寫):隱藏,是指派生類的函數屏蔽了與其同名的基類函數,規則以下: (1)若是派生類的函數與基類的函數同名,可是參數不一樣。此時,不論有無virtual關鍵字,基類的函數將被隱藏(注意別與重載混淆)。 (2)若是派生類的函數與基類的函數同名,而且參數也相同,可是基類函數沒有virtual關鍵字。此時,基類的函數被隱藏(注意別與覆蓋混淆)。
虛函數表:
詳細解釋能夠參考博客:https://www.cnblogs.com/jin521/p/5602190.html
多態是由虛函數實現的,而虛函數主要是經過虛函數表(V-Table)來實現的。
若是一個類中包含虛函數(virtual修飾的函數),那麼這個類就會包含一張虛函數表,虛函數表存儲的每一項是一個虛函數的地址。以下圖:
這個類的每個對象都會包含一個虛指針(虛指針存在於對象實例地址的最前面,保證虛函數表有最高的性能),這個虛指針指向虛函數表。
注:對象不包含虛函數表,只有虛指針,類才包含虛函數表,派生類會生成一個兼容基類的虛函數表。
下圖是原始基類的對象,能夠看到虛指針在地址的最前面,指向基類的虛函數表(假設基類定義了3個虛函數)
假設如今派生類繼承基類,而且從新定義了3個虛函數,派生類會本身產生一個兼容基類虛函數表的屬於本身的虛函數表。
Derive Class繼承了Base Class中的3個虛函數,準確說是該函數的實體地址被拷貝到Derive Class的虛函數列表中,派生新增的虛函數置於虛函數列表後面,並按聲明順序擺放。
如今派生類重寫基類的x函數,能夠看到這個派生類構建本身的虛函數表的時候,修改了base::x()這一項,指向了本身的虛函數。
這個派生類多重繼承了兩個基類base1,base2,所以它有兩個虛函數表。
它的對象會有多個虛指針(聽說和編譯器相關),指向不一樣的虛函數表。
注:有關以上虛函數表等詳見c++對象模型。連接地址:http://www.javashuo.com/article/p-wkwpgphp-gb.html
純虛函數:
定義: 在不少狀況下,基類自己生成對象是不合情理的。爲了解決這個問題,方便使用類的多態性,引入了純虛函數的概念,將函數定義爲純虛函數(方法:virtual ReturnType Function()= 0;)純虛函數不能再在基類中實現,編譯器要求在派生類中必須予以重寫以實現多態性。同時含有純虛擬函數的類稱爲抽象類,它不能生成對象。稱帶有純虛函數的類爲抽象類。
特色:
1,當想在基類中抽象出一個方法,且該基類只作能被繼承,而不能被實例化;(避免類被實例化且在編譯時候被發現,能夠採用此方法)
2,這個方法必須在派生類(derived class)中被實現;
目的:使派生類僅僅只是繼承函數的接口。
size()指容器當前擁有的元素個數(對應的resize(size_type)會在容器尾添加或刪除一些元素,來調整容器中實際的內容,使容器達到指定的大小。);capacity()指容器在必須分配存儲空間以前能夠存儲的元素總數。
size表示的這個vector裏容納了多少個元素,capacity表示vector可以容納多少元素,它們的不一樣是在於vector的size是2倍增加的。若是vector的大小不夠了,好比如今的capacity是4,插入到第五個元素的時候,發現不夠了,此時會給他從新分配8個空間,把原來的數據及新的數據複製到這個新分配的空間裏。(會有迭代器失效的問題)
詳細參考:連接
底層數據結構:
根據應用場景進行選擇:
VS下檢測內存泄漏方法:
#define CRTDBG_MAP_ALLOC #include <stdlib.h> #include <crtdbg.h> //在入口函數中包含 _CrtDumpMemoryLeaks(); //便可檢測到內存泄露 //以以下測試函數爲例: int main() { char* pChars = new char[10]; //delete[]pChars; _CrtDumpMemoryLeaks(); system("pause"); return 0; }
解決方法:
/*若是不使用override,當你手一抖,將foo()寫成了f00()會怎麼樣呢?結果是編譯器並不會報錯,由於它並不知道你的目的是重寫虛函數,而是把它當成了新的函數。若是這個虛函數很重要的話,那就會對整個程序不利。 因此,override的做用就出來了,它指定了子類的這個虛函數是重寫的父類的,若是你名字不當心打錯了的話,編譯器是不會編譯經過的:*/ class A { virtual void foo(); } class B :public A { void foo(); //OK virtual foo(); // OK void foo() override; //OK } class A { virtual void foo(); }; class B :A { virtual void f00(); //OK virtual void f0o()override; //Error };
/*當不但願某個類被繼承,或不但願某個虛函數被重寫,能夠在類名和虛函數後添加final關鍵字,添加final關鍵字後被繼承或重寫,編譯器會報錯。例子以下:*/ class Base { virtual void foo(); }; class A : Base { void foo() final; // foo 被override而且是最後一個override,在其子類中不能夠重寫 void bar() final; // Error: 父類中沒有 bar虛函數能夠被重寫或final }; class B final : A // 指明B是不能夠被繼承的 { void foo() override; // Error: 在A中已經被final了 }; class C : B // Error: B is final { };
C++在C的基礎上增添類,C是一個結構化語言,它的重點在於算法和數據結構。C程序的設計首要考慮的是如何經過一個過程,對輸入(或環境條件)進行運算處理獲得輸出(或實現過程(事務)控制),而對於C++,首要考慮的是如何構造一個對象模型,讓這個模型可以契合與之對應的問題域,這樣就能夠經過獲取對象的狀態信息獲得輸出或實現過程(事務)控制。
1.編譯器處理方式
define – 在預處理階段進行替換
const – 在編譯時肯定其值
2.類型檢查
define – 無類型,不進行類型安全檢查,可能會產生意想不到的錯誤
const – 有數據類型,編譯時會進行類型檢查
3.內存空間
define – 不分配內存,給出的是當即數,有多少次使用就進行多少次替換,在內存中會有多個拷貝,消耗內存大
const – 在靜態存儲區中分配空間,在程序運行過程當中內存中只有一個拷貝
4.其餘
在編譯時, 編譯器一般不爲const常量分配存儲空間,而是將它們保存在符號表中,這使得它成爲一個編譯期間的常量,沒有了存儲與讀內存的操做,使得它的效率也很高。
宏替換隻做替換,不作計算,不作表達式求解。
{ char *dp = NULL; { char c; dp = &c; } //變量c釋放,dp變成空懸指針 }
void func() { char *dp = (char *)malloc(A_CONST); free(dp); //dp變成一個空懸指針 dp = NULL; //dp再也不是空懸指針 /* ... */ }
int func() { char *dp;//野指針,沒有初始化 static char *sdp;//非野指針,由於靜態變量會默認初始化爲0 }
本質區別是訪問的默認控制:默認的繼承訪問權限,class是private,struct是public;
功能不一樣:
sizeof是操做符,參數爲任意類型,主要計算類型佔用內存大小。
strlen()是函數,其函數原型爲:extern unsigned int strlen(char *s);其參數爲char*,strlen只能計算以"\0"結尾字符串的長度,計算結果不包括"\0"。
char* ss="0123456789"; //s1=4,ss爲字符指針在內存中佔用4個字節 int s1=sizeof(ss); //s2=10,計算字符串ss的長度 int s2=strlen(ss);
參數不一樣:
當將字符數組做爲sizeof()的參數時,計算字符數組佔用內存大小;當將字符數組做爲strlen()函數,字符數組轉化爲char*。由於sizeof的參數爲任意類型,而strlen()函數參數只能爲char*,當參數不是char*必須轉換爲char*。
char str[]="abced"; //a爲6(1*6),字符數組str包含6個元素(a,b,c,d,e,\0),每一個元素佔用1個字節 int a= sizeof(str); //len爲5,不包含"\0", int len=strlen(str); //str[0]是字符元素a,因此b=1 int b= sizeof(str[0]);
char :1個字節(固定)
*(即指針變量): 4個字節(32位機的尋址空間是4個字節。同理64位編譯器)(變化*)
short int : 2個字節(固定)
int: 4個字節(固定)
unsigned int : 4個字節(固定)
float: 4個字節(固定)
double: 8個字節(固定)
long: 4個字節
unsigned long: 4個字節(變化*,其實就是尋址控件的地址長度數值)
long long: 8個字節(固定)
64位操做系統
char :1個字節(固定)
*(即指針變量): 8個字節
short int : 2個字節(固定)
int: 4個字節(固定)
unsigned int : 4個字節(固定)
float: 4個字節(固定)
double: 8個字節(固定)
long: 8個字節
unsigned long: 8個字節(變化*其實就是尋址控件的地址長度數值)
long long: 8個字節(固定)
除*與long 不一樣其他均相同。
inline:在c/c++中,爲了解決一些頻繁調用的小函數大量消耗棧空間(棧內存)的問題,特別的引入了inline修飾符,表示爲內聯函數。
#include <stdio.h> //函數定義爲inline即:內聯函數 inline char* dbtest(int a) { return (i % 2 > 0) ? "奇" : "偶"; } int main() { int i = 0; for (i=1; i < 100; i++) { printf("i:%d 奇偶性:%s /n", i, dbtest(i)); } }//在for循環的每一個dbtest(i)的地方替換成了 (i % 2 > 0) ? "奇" : "偶",避免了頻繁調用函數,對棧內存的消耗
decltype:從表達式中推斷出要定義變量的類型,但卻不想用表達式的值去初始化變量。還有多是函數的返回類型爲某表達式的的值類型。
volatile:volatile 關鍵字是一種類型修飾符,用它聲明的類型變量表示能夠被某些編譯器未知的因素更改,好比:操做系統、硬件或者其它線程等。遇到這個關鍵字聲明的變量,編譯器對訪問該變量的代碼就再也不進行優化,從而能夠提供對特殊地址的穩定訪問。
static:
在變量和函數名前面若是未加static,則它們是全局可見的。加了static,就會對其它源文件隱藏,利用這一特性能夠在不一樣的文件中定義同名函數和同名變量,而沒必要擔憂命名衝 突。static能夠用做函數和變量的前綴,對於函數來說,static的做用僅限於隱藏 。
2.static變量中的記憶功能和全局生存期
存儲在靜態數據區的變量會在程序剛開始運行時就完成初始化,也是惟一的一次初始化。共有兩種變量存儲在靜態存儲區:全局變量和static變量,只不過和全局變量比起來,static能夠控制變量的可見範圍,說到底static仍是用來隱藏的。PS:若是做爲static局部變量在函數內定義,它的生存期爲整個源程序,可是其做用域仍與自動變量相同,只能在定義該變量的函數內使用該變量。退出該函數後, 儘管該變量還繼續存在,但不能使用它。
#include <stdio.h> int fun(){ static int count = 10; //在第一次進入這個函數的時候,變量a被初始化爲10!並接着自減1,之後每次進入該函數,a return count--; //就不會被再次初始化了,僅進行自減1的操做;在static發明前,要達到一樣的功能,則只能使用全局變量: } int count = 1; int main(void) { printf("global\t\tlocal static\n"); for(; count <= 10; ++count) printf("%d\t\t%d\n", count, fun()); return 0; }
---基於以上兩點能夠得出一個結論:把局部變量改變爲靜態變量後是改變了它的存儲方式即改變了它的生存期。把全局變量改變爲靜態變量後是改變了它的做用域, 限制了它的使用範圍。所以static 這個說明符在不一樣的地方所起的做用是不一樣的。
3.static的第三個做用是默認初始化爲0(static變量)
最後對static的三條做用作一句話總結。首先static的最主要功能是隱藏,其次由於static變量存放在靜態存儲區,因此它具有持久性和默認值0。
4.static的第四個做用:C++中的類成員聲明static(有些地方與以上做用重疊)
在類中聲明static變量或者函數時,初始化時使用做用域運算符來標明它所屬類,所以,靜態數據成員是類的成員,而不是對象的成員,這樣就出現如下做用:
(1)類的靜態成員函數是屬於整個類而非類的對象,因此它沒有this指針,這就致使 了它僅能訪問類的靜態數據和靜態成員函數。
(2)不能將靜態成員函數定義爲虛函數。
(3)因爲靜態成員聲明於類中,操做於其外,因此對其取地址操做,就多少有些特殊 ,變量地址是指向其數據類型的指針 ,函數地址類型是一個「nonmember函數指針」。
(4)因爲靜態成員函數沒有this指針,因此就差很少等同於nonmember函數,結果就 產生了一個意想不到的好處:成爲一個callback函數,使得咱們得以將C++和C-based X W indow系統結合,同時也成功的應用於線程函數身上。 (這條沒碰見過)
(5)static並無增長程序的時空開銷,相反她還縮短了子類對父類靜態成員的訪問 時間,節省了子類的內存空間。
(6)靜態數據成員在<定義或說明>時前面加關鍵字static。
(7)靜態數據成員是靜態存儲的,因此必須對它進行初始化。 (程序員手動初始化,不然編譯時通常不會報錯,可是在Link時會報錯誤)
(8)靜態成員初始化與通常數據成員初始化不一樣:
初始化在類體外進行,而前面不加static,以避免與通常靜態變量或對象相混淆;
初始化時不加該成員的訪問權限控制符private,public等;
初始化時使用做用域運算符來標明它所屬類;
因此咱們得出靜態數據成員初始化的格式:
<數據類型><類名>::<靜態數據成員名>=<值>
(9)爲了防止父類的影響,能夠在子類定義一個與父類相同的靜態變量,以屏蔽父類的影響。這裏有一點須要注意:咱們說靜態成員爲父類和子類共享,但咱們有重複定義了靜態成員,這會不會引發錯誤呢?不會,咱們的編譯器採用了一種絕妙的手法:name-mangling 用以生成惟一的標誌。
1.何時用到拷貝函數?
若是在類中沒有顯式地聲明一個拷貝構造函數,那麼,編譯器將會自動生成一個默認的拷貝構造函數,該構造函數完成對象之間的位拷貝。位拷貝又稱淺拷貝;
2.是否應該自定義拷貝函數?
自定義拷貝構造函數是一種良好的編程風格,它能夠阻止編譯器造成默認的拷貝構造函數,提升源碼效率。
3.什麼叫深拷貝?什麼是淺拷貝?二者異同?
4.深拷貝好仍是淺拷貝好?
若是實行位拷貝,也就是把對象裏的值徹底複製給另外一個對象,如A=B。這時,若是B中有一個成員變量指針已經申請了內存,那A中的那個成員變量也指向同一塊內存。這就出現了問題:當B把內存釋放了(如:析構),這時A內的指針就是野指針了,出現運行錯誤。
參考博客:https://blog.csdn.net/caoshangpa/article/details/79226270
http://www.cnblogs.com/BlueTzar/articles/1223313.html
構造函數:「先基後派」;析構函數:「先派後基」。
1.成員變量在使用初始化列表初始化時,與構造函數中初始化成員列表的順序無關,只與定義成員變量的順序有關。
2.若是不使用初始化列表初始化,在構造函數內初始化時,此時與成員變量在構造函數中的位置有關。
3.類中const成員常量必須在構造函數初始化列表中初始化。
4.類中static成員變量,只能在類內外初始化(同一類的全部實例共享靜態成員變量)。
初始化順序:
注:內存對齊是看類型,而不是看總的字節數。好比:
#include<iostream> using namespace std; struct AlignData1 { int a; char b[7];//a後面並不會補上3個字節,而是因爲char的類型因此不用補。 short c; char d; }Node; struct AlignData2 { bool a; int b[2];//a後面並不會補上7個字節,而是根據int的類型補3個字節。 int c; int d; }Node2; int main(){ cout << sizeof(Node) << endl;//16 cout << sizeof(Node2) << endl;//20 system("pause"); return 0; }
補充:
struct AlignData1 { char c; short b; int i; char d; }Node; 這個結構體在編譯之後,爲了字節對齊,會被整理成這個樣子: struct AlignData1 { char c; char padding[1]; short b; int i; char d; char padding[3]; }Node;
含有虛函數的類的大小:連接
補充:聯合體的大小計算:
聯合體所佔的空間不只取決於最寬成員,還跟全部成員有關係,即其大小必須知足兩個條件:1)大小足夠容納最寬的成員;2)大小能被其包含的全部基本數據類型的大小所整除。
union U1 { int n; char s[11]; double d; }; //16,char s[11]按照char=1能夠整除 union U2 { int n; char s[5]; double d; }; //8
補充:static_cast與dynamic_cast
#include <iostream> using namespace std; class CBasic { public: virtual int test(){return 0;} }; class CDerived : public CBasic { public: virtual int test(){ return 1;} }; int main() { CBasic cBasic; CDerived cDerived; CBasic * pB1 = new CBasic; CBasic * pB2 = new CDerived; CBasic * pB3 = new CBasic; CBasic * pB4 = new CDerived; //dynamic cast failed, so pD1 is null. CDerived * pD1 = dynamic_cast<CDerived * > (pB1); //dynamic cast succeeded, so pD2 points to CDerived object CDerived * pD2 = dynamic_cast<CDerived * > (pB2); //pD3將是一個指向該CBasic類型對象的指針,對它進行CDerive類型的操做將是不安全的 CDerived * pD3 = static_cast<CDerived * > (pB3); //static_cast成功 CDerived * pD4 = static_cast<CDerived * > (pB4); //dynamci cast failed, so throw an exception. // CDerived & rD1 = dynamic_cast<CDerived &> (*pB1); //dynamic cast succeeded, so rD2 references to CDerived object. CDerived & rD2 = dynamic_cast<CDerived &> (*pB2); return 0; }
注:CBasic要有虛函數,不然會編譯出錯;static_cast則沒有這個限制。
class A {};: sizeof(A) = 1;
class A { virtual Fun(){} };: sizeof(A) = 4(32位機器)/8(64位機器);
class A { static int a; };: sizeof(A) = 1;
class A { int a; };: sizeof(A) = 4;
class A { static int a; int b; };: sizeof(A) = 4;
類中用static聲明的成員變量不計算入類的大小中,由於static data不是實例的一部分。static的屬於全局的,他不會佔用類的存儲,他有專門的地方存儲 (全局變量區)
各自優點:
舉一個例子,好比數字0x12 34 56 78在內存中的表示形式爲:
低地址 -----------------> 高地址
0x12 | 0x34 | 0x56 | 0x78
低地址 ------------------> 高地址
0x78 | 0x56 | 0x34 | 0x12
template <class T>
void InsertFront(Node<T>* & head, T item)
上面一個函數的聲明,其中第一個參數*和&分別是什麼意思?
head是個指針,前面爲何加個&
原本「* head」表明的是傳指針的,可是隻能改變head指向的內容,而「* &head」意思是說head是傳進來的指針的同名指針,就能既改變*head指向的內容,又能改變head這個指針。好比:main()有個Node<int>* p,int t;當調用insertFront(p,t)是,若是template <class T> void InsertFront(Node<T>* & head, T item)中有對head進行賦值改變時,main()中的p也會跟着改變,若是沒有&這個別名標識時,p則不會隨着head的改變而改變。
http://www.javashuo.com/article/p-qvvgctsh-eg.html
在C語言中static的做用以下
第1、在修飾變量的時候,static修飾的靜態局部變量只執行一次,並且延長了局部變量的生命週期,直到程序運行結束之後才釋放。
第2、static修飾全局變量的時候,這個全局變量只能在本文件中訪問,不能在其它文件中訪問,即使是extern外部聲明也不能夠。
第3、static修飾一個函數,則這個函數的只能在本文件中調用,不能被其餘文件調用。Static修飾的局部變量存放在全局數據區的靜態變量區。初始化的時候自動初始化爲0;
(1)不想被釋放的時候,可使用static修飾。好比修飾函數中存放在棧空間的數組。若是不想讓這個數組在函數調用結束釋放可使用static修飾
(2)考慮到數據安全性(當程想要使用全局變量的時候應該先考慮使用static)
在C++中static關鍵字除了具備C中的做用還有在類中的使用
在類中,static能夠用來修飾靜態數據成員和靜態成員方法
靜態數據成員
(1)靜態數據成員能夠實現多個對象之間的數據共享,它是類的全部對象的共享成員,它在內存中只佔一份空間,若是改變它的值,則各對象中這個數據成員的值都被改變。
(2)靜態數據成員是在程序開始運行時被分配空間,到程序結束以後才釋放,只要類中指定了靜態數據成員,即便不定義對象,也會爲靜態數據成員分配空間。
(3)靜態數據成員能夠被初始化,可是隻能在類體外進行初始化,若爲對靜態數據成員賦初值,則編譯器會自動爲其初始化爲0
(4)靜態數據成員既能夠經過對象名引用,也能夠經過類名引用。
靜態成員函數
(1)靜態成員函數和靜態數據成員同樣,他們都屬於類的靜態成員,而不是對象成員。
(2)非靜態成員函數有this指針,而靜態成員函數沒有this指針。
(3)靜態成員函數主要用來訪問靜態數據成員而不能訪問非靜態成員。
講解全面的一篇博客:http://www.javashuo.com/article/p-kqoifllz-hp.html
若是你只是聲明一個空類,不作任何事情的話,編譯器會自動爲你生成一個默認構造函數、一個拷貝默認構造函數、一個默認拷貝賦值操做符和一個默認析構函數。這些函數只有在第一次被調用時,纔會被編譯器建立。全部這些函數都是inline和public的。
定義一個空類例如:
class Empty { }
一個空的class在C++編譯器處理事後就再也不爲空,編譯器會自動地爲咱們聲明一些member function,通常編譯過就至關於:
class Empty { public: Empty(); // 缺省構造函數// Empty( const Empty& ); // 拷貝構造函數// ~Empty(); // 析構函數// Empty& operator=( const Empty& ); // 賦值運算符// };
須要注意的是,只有當你須要用到這些函數的時候,編譯器纔會去定義它們。
基類指針能夠指向派生類的對象(多態性),若是刪除該指針delete []p;就會調用該指針指向的派生類析構函數,而派生類的析構函數又自動調用基類的析構函數,這樣整個派生類的對象徹底被釋放。
若是析構函數不被聲明成虛函數,則編譯器實施靜態綁定,在刪除基類指針時,只會調用基類的析構函數而不調用派生類析構函數,這樣就會形成派生類對象析構不徹底。
不能被繼承的函數和不能被重寫的函數。
1)普通函數
普通函數不屬於成員函數,是不能被繼承的。普通函數只能被重載,不能被重寫,所以聲明爲虛函數沒有意義。由於編譯器會在編譯時綁定函數。
而多態體如今運行時綁定。一般經過基類指針指向子類對象實現多態。
2)友元函數
友元函數不屬於類的成員函數,不能被繼承。對於沒有繼承特性的函數沒有虛函數的說法。
3)構造函數
首先說下什麼是構造函數,構造函數是用來初始化對象的。假如子類能夠繼承基類構造函數,那麼子類對象的構造將使用基類的構造函數,而基類構造函數並不知道子類的有什麼成員,顯然是不符合語義的。從另一個角度來說,多態是經過基類指針指向子類對象來實現多態的,在對象構造以前並無對象產生,所以沒法使用多態特性,這是矛盾的。所以構造函數不容許繼承。
4)內聯成員函數
咱們須要知道內聯函數就是爲了在代碼中直接展開,減小函數調用花費的代價。也就是說內聯函數是在編譯時展開的。而虛函數是爲了實現多態,是在運行時綁定的。所以顯然內聯函數和多態的特性相違背。
5)靜態成員函數
首先靜態成員函數理論是可繼承的。可是靜態成員函數是編譯時肯定的,沒法動態綁定,不支持多態,所以不能被重寫,也就不能被聲明爲虛函數。、
//.h
class String{ public: String(const char* str); String(const String &other); ~String(); String & operate=(const String &other); private: char* m_data; };
//.cpp String::String(const char*str){ if(str==NULL){ m_data=new char[1]; *m_data='\0'; } else{ int length=strlen(str); m_data=new char[length+1]; strcpy(m_data,str); } } String::String(const String &other){ int length=strlen(other.m_data); m_data=new char[length+1]; strcpy(m_data,other.m_data); } String::~String(){ delete [] m_data; } String::String& operate=(const String & other){ if(&other==*this)return *this;//檢查自賦值 delete[]m_data;//釋放原有的內存資源 int length=strlen(other.m_data); m_data=new char[length+1]; strcpy(m_data,other.m_data); return *this;//返回本對象的引用 }
參考:http://www.javashuo.com/article/p-mgvopjua-hu.html
注:1.引用計數問題
參考:http://www.javashuo.com/article/p-xixpwpyp-ck.html
2.智能指針支持的操做
#include <iostream> #include <string> #include <memory> using namespace std; class base { public: base(int _a): a(_a) {cout<<"構造函數"<<endl;} ~base() {cout<<"析構函數"<<endl;} int a; }; int main() { unique_ptr<base> up1(new base(2)); // unique_ptr<base> up2 = up1; //編譯器提示未定義 unique_ptr<base> up2 = move(up1); //轉移對象的全部權 // cout<<up1->a<<endl; //運行時錯誤 cout<<up2->a<<endl; //經過解引用運算符獲取封裝的原始指針 up2.reset(); // 顯式釋放內存 shared_ptr<base> sp1(new base(3)); shared_ptr<base> sp2 = sp1; //增長引用計數 cout<<"共享智能指針的數量:"<<sp2.use_count()<<endl; //2 sp1.reset(); // cout<<"共享智能指針的數量:"<<sp2.use_count()<<endl; //1 cout<<sp2->a<<endl; auto sp3 = make_shared<base>(4);//利用make_shared函數動態分配內存 }
3.智能指針的陷阱(循環引用等問題)
class B; class A { public: shared_ptr<B> m_b; }; class B { public: shared_ptr<A> m_a; }; int main() { { shared_ptr<A> a(new A); //new出來的A的引用計數此時爲1 shared_ptr<B> b(new B); //new出來的B的引用計數此時爲1 a->m_b = b; //B的引用計數增長爲2 b->m_a = a; //A的引用計數增長爲2 } //b先出做用域,B的引用計數減小爲1,不爲0; //因此堆上的B空間沒有被釋放,且B持有的A也沒有機會被析構,A的引用計數也徹底沒減小 //a後出做用域,同理A的引用計數減小爲1,不爲0,因此堆上A的空間也沒有被釋放 }
循環引用」簡單來講就是:兩個對象互相使用一個shared_ptr成員變量指向對方會形成循環引用。
即A內部有指向B,B內部有指向A,這樣對於A,B一定是在A析構後B才析構,對於B,A一定是在B析構後才析構A,這就是循環引用問題,違反常規,致使內存泄露。
解決循環引用方法:
1. 當只剩下最後一個引用的時候須要手動打破循環引用釋放對象。
2. 當A的生存期超過B的生存期的時候,B改成使用一個普通指針指向A。
3. 使用weak_ptr打破這種循環引用,由於weak_ptr不會修改計數器的大小,因此就不會產生兩個對象互相使用一個shared_ptr成員變量指向對方的問題,從而不會引發引用循環。
參考連接:https://blog.csdn.net/yangshiziping/article/details/52550291
1.宏定義不是函數,可是使用起來像函數。預處理器用複製宏代碼的方式代替函數的調用,省去了函數壓棧退棧過程,提升了效率。
內聯函數本質上是一個函數,內聯函數通常用於函數體的代碼比較簡單的函數,不能包含複雜的控制語句,while、switch,而且內聯函數自己不能直接調用自身。若是內聯函數的函數體過大,編譯器會自動 的把這個內聯函數變成普通函數。
2. 宏定義是在預處理的時候把全部的宏名用宏體來替換,簡單的說就是字符串替換
內聯函數則是在編譯的時候進行代碼插入,編譯器會在每處調用內聯函數的地方直接把內聯函數的內容展開,這樣能夠省去函數的調用的開銷,提升效率
3. 宏定義是沒有類型檢查的,不管對仍是錯都是直接替換
內聯函數在編譯的時候會進行類型的檢查,內聯函數知足函數的性質,好比有返回值、參數列表等
4. 宏定義和內聯函數使用的時候都是進行代碼展開。不一樣的是宏定義是在預編譯的時候把全部的宏名替換,內聯函數則是在編譯階段把全部調用內聯函數的地方把內聯函數插入。這樣能夠省去函數壓棧退棧,提升了效率
1. 內聯函數和普通函數的參數傳遞機制相同,可是編譯器會在每處調用內聯函數的地方將內聯函數內容展開,這樣既避免了函數調用的開銷又沒有宏機制的缺陷。
2. 普通函數在被調用的時候,系統首先要到函數的入口地址去執行函數體,執行完成以後再回到函數調用的地方繼續執行,函數始終只有一個複製。
內聯函數不須要尋址,當執行到內聯函數的時候,將此函數展開,若是程序中有N次調用了內聯函數則會有N次展開函數代碼。
3. 內聯函數有必定的限制,內聯函數體要求代碼簡單,不能包含複雜的結構控制語句。若是內聯函數函數體過於複雜,編譯器將自動把內聯函數當成普通函數來執行。
不能。
C++編譯器在實現const的成員函數(const加在函數右邊)的時候爲了確保該函數不能修改類的中參數的值,會在函數中添加一個隱式的參數const this*。但當一個成員爲static的時候,該函數是沒有this指針的。也就是說此時const的用法和static是衝突的。
即:static修飾的函數表示該函數是屬於類的,而不是屬於某一個對象的,沒有this指針。const修飾的函數表示該函數不能改變this中的內容,會有一個隱含的const this指針。二者是矛盾的。
1.溢出
要求分配的內存超出了系統能給你的,系統不能知足需求,因而產生溢出。
1)棧溢出
a.棧溢出是指函數中的局部變量形成的溢出(注:函數中形參和函數中的局部變量存放在棧上)
棧的大小一般是1M-2M,因此棧溢出包含兩種狀況,一是分配的的大小超過棧的最大值,二是分配的大小沒有超過最大值,可是接收的buff比新buff小(buff:緩衝區, 它本質上就是一段存儲數據的內存)
例子1:(分配的的大小超過棧的最大值)
void { char a[99999999999999999]; }
例子2:(接收的buff比新buff小)
void { char a[10] = {0}; strcpy(a, "abjjijjlljiojohihiihiiiiiiiiiiiiiiiiiiiiiiiiii"); }
注意:調試時棧溢出的異常要在函數調用結束後纔會檢測到,由於棧是在函數結束時纔會開始進行出棧操做
如:
int main(int argc, char* argv[]) { char a[10] = {0}; strcpy(a, "abjjijjlljiojohihiihiiiiiiiiiiiiiiiiiiiiiiiiii"); exit(0); return 0; }
上面狀況是檢測不到棧溢出的,由於函數還沒執行完就退出了
void fun() { char a[10] = {0}; strcpy(a, "abjjijjlljiojohihiihiiiiiiiiiiiiiiiiiiiiiiiiii"); } int main(int argc, char* argv[]) { fun(); exit(0); return 0; }
這種狀況調用完fun函數就會檢測到異常了
b.棧溢出的解決辦法
若是是超過棧的大小時,那就直接換成用堆;若是是不超過棧大小可是分配值小的,就增大分配的大小
2)內存溢出
使用malloc和new分配的內存,在拷貝時接收buff小於新buff時形成的現象
解決:增長分配的大小
2.越界
越界一般指的是數組越界,如
char a[9]={0};
cout << a[9] << endl;
3.泄漏
這裏泄漏一般是指堆內存泄漏,是指使用malloc和new分配的內存沒有釋放形成的
1) malloc 函數: void *malloc(unsigned int size)
在內存的動態分配區域中分配一個長度爲size的連續空間,若是分配成功,則返回所分配內存空間的首地址,不然返回NULL,申請的內存不會進行初始化。
2)calloc 函數: void *calloc(unsigned int num, unsigned int size)
按照所給的數據個數和數據類型所佔字節數,分配一個 num * size 連續的空間。
動態分配一個長度爲size的內存空間,並把內存空間的首地址賦值給ptr,把ptr內存空間調整爲size。
構造函數初始化列表以一個冒號開始,接着是以逗號分隔的數據成員列表,每一個數據成員後面跟一個放在括號中的初始化式。例如:
class CExample { public: int a; float b; //構造函數初始化列表 CExample(): a(0),b(8.8) {} //構造函數內部賦值 CExample() { a=0; b=8.8; } };
上面的例子中兩個構造函數的結果是同樣的。上面的構造函數(使用初始化列表的構造函數)顯式的初始化類的成員;而沒使用初始化列表的構造函數是對類的成員賦值,並無進行顯式的初始化。
初始化和賦值對內置類型的成員沒有什麼大的區別,像上面的任一個構造函數均可以。對非內置類型成員變量,爲了不兩次構造,推薦使用類構造函數初始化列表。但有的時候必須用帶有初始化列表的構造函數:
1.成員類型是沒有默認構造函數的類。若沒有提供顯示初始化式,則編譯器隱式使用成員類型的默認構造函數,若類沒有默認構造函數,則編譯器嘗試使用默認構造函數將會失敗。
2.const成員或引用類型的成員。由於const對象或引用類型只能初始化,不能對他們賦值。
初始化數據成員與對數據成員賦值的含義是什麼?有什麼區別?
首先把數據成員按類型分類並分狀況說明:
1.內置數據類型,複合類型(指針,引用)
在成員初始化列表和構造函數體內進行,在性能和結果上都是同樣的
2.用戶定義類型(類類型)
結果上相同,可是性能上存在很大的差異。由於類類型的數據成員對象在進入函數體前已經構造完成(先進行了一次隱式的默認構造函數調用),也就是說在成員初始化列表處進行構造對象的工做,調用構造函數,在進入函數體以後,進行的是對已經構造好的類對象的賦值,又調用了拷貝賦值操做符才能完成(若是並未提供,則使用編譯器提供的默認按成員賦值行爲)。
void f(vector<int> &v) { v[5]; // A v.at[5]; // B }
若是v非空,A行和B行沒有任何區別。若是v爲空,B行會拋出std::out_of_range異常,A行的行爲未定義。
c++標準不要求vector<T>::operator[]進行下標越界檢查,緣由是爲了效率,老是強制下標越界檢查會增長程序的性能開銷。設計vector是用來代替內置數組的,因此效率問題也應該考慮。不過使用operator[]就要本身承擔越界風險了。
若是須要下標越界檢查,請使用at。可是請注意,這時候的性能也是響應的會受影響,由於越界檢查增長了性能的開銷。
extern "C"
常量指針(被指向的對象是常量)
定義:又叫常指針,能夠理解爲常量的指針,指向的是個常量
關鍵點:
const int *p或int const *p
(記憶技巧:const讀做常量,*讀做指針)
#include <stdio.h> // 常量指針(被指向的對象是常量) int main() { int i = 10; int i2 = 11; const int *p = &i; printf("%d\n", *p);//10 i = 9; //OK,仍然能夠經過原來的聲明修改值, //Error,*p是const int的,不可修改,即常量指針不可修改其指向地址 //*p = 11; //error: assignment of read-only location ‘*p’ p = &i2;//OK,指針還能夠指向別處,由於指針只是個變量,能夠隨意指向; printf("%d\n", *p);//11 return 0; }
指針常量(指針自己是常量)
定義:
本質是一個常量,而用指針修飾它。指針常量的值是指針,這個值由於是常量,因此不能被賦值。
關鍵點:
int* const p;
//指針常量(指針自己是常量) #include <stdio.h> int main() { int i = 10; int *const p = &i; printf("%d\n", *p);//10 //Error,由於p是const 指針,所以不能改變p指向的內容 //p++;//error: increment of read-only variable ‘p’ (*p)++; //OK,指針是常量,指向的地址不能夠變化,可是指向的地址所對應的內容能夠變化 printf("%d\n", *p);//11 i = 9;//OK,仍然能夠經過原來的聲明修改值, return 0; }
很是好的一篇博客:連接