C++是基於C語言擴展發展而來的面向對象的程序設計語言,本文將主要討論C++語言基於C語言擴展的方面。ios
C語言中變量的定義必須在做用域開始的位置進行定義。c++
#include <stdio.h> int main(int argc, char *argv[]) { int i;//定義變量 int j; //使用變量 for(i = 0; i < 10; i++) { for(j = 0; j < 10; j++) { } } //error: 'for' loop initial declarations are only allowed in C99 mode for(int k = 0; k < 10; k++) { } return 0; }
C++更強調語言的實用性,C++中全部的變量能夠在須要使用時再定義。express
#include <iostream> using namespace std; int main(int argc, char *argv[]) { int a = 7; //使用時定義變量 for(int i = 0; i < 10; i++) { for(int j = 0; j < 10; j++) { } } return 0; }
在C語言中:編程
const int a = 100; int *p = &a;
在C++語言中:數組
const int a = 100;//必須在定義的時候初始化 const int *p = &a;//類型必須嚴格匹配
在C++語言中不能隱式轉換數據類型。error: invalid conversion from 'const int*' to 'int*' [-fpermissive]
ide
C語言中,沒有定義bool類型,表示真用非0,假用0。
C++語言中,定義了本身的bool類型,真爲true,假爲false,是基本類型。sizeof(bool) = 1
C++編譯器會將非0值轉換爲true,0轉換爲false函數
#include <iostream> using namespace std; int main(int argc, char *argv[]) { bool a = 0; printf("a = %d\n", a);//0 bool b = -1; printf("b = %d\n", b);//1 b = b + 1;//1 + 1 = 2 => 1 printf("b = %d\n", b);//1 printf("sizeof(bool) = %d\n", sizeof(bool));//1 return 0; }
C語言中枚舉本質就是整型,枚舉變量能夠用任意整型賦值。而c++中枚舉變量,只能用被枚舉出來的元素初始化。
在C語言中,枚舉的使用工具
#include <stdio.h> enum weekday { monday,tuesday,wednesday,thursday,friday,saturday,sunday }; int main(int argc, char **argv) { enum weekday day = monday; enum weekday a = sunday; enum weekday b = 100; //weekday c = sunday;錯誤用法,須要使用enum聲明 printf("%d %d %d\n", day, a, b); return 0; }
在C++語言中枚舉的使用oop
enum weekday { monday,tuesday,wednesday,thursday,friday,saturday,sunday }; int main() { weekday day = sunday; enum weekday a = monday; cout<<day<<" "<<a<<endl; return 0; }
C語言中表達式不能夠作左值,C++中某些表達式能夠作左值。(a = b) = 6;
學習
register關鍵字請求編譯器將局部變量存儲到寄存器中。
C語言中,沒法獲取register關鍵字修飾的局部變量的地址。
C++語言中,C++依然支持register關鍵字,但C++編譯器對register關鍵字進行了優化,C++語言中能夠獲取register變量的地址。對於早期的C++編譯器,C++編譯器發現程序中須要獲取register關鍵字修飾的變量地址時,register關鍵字對變量的聲明失效;對於現代C++編譯器,register關鍵字的存在只是爲了兼容C語言,register關鍵字自己已經無任何意義。
C語言中能夠重複定義多個重名的全局變量,同名的全局變量被連接到全局數據區的一個地址空間。
#include <stdio.h> //定義重名的全局變量合法 int a; int a; int main(int argc, char *argv[]) { return 0; }
C++語言不容許定義多個同名的全局變量。
#include <iostream> using namespace std; //定義重名的全局變量是非法的 int a; int a; //error: redefinition of 'int a' int main(int argc, char *argv[]) { return 0; }
C語言中,struct定義了一種變量的集合,struct定義的標識符不是一種新類型。C語言中的struct內部不能夠定義函數。
#include <stdio.h> struct tag_student { const char* name; int age; }; typedef struct tag_student Student; int main(int argc, char *argv[]) { //合法定義 Student s; s.name = "lee"; s.age = 30; //非法定義 tag_student ts;//error: unknown type name 'tag_student' //合法定義 struct tag_student sts; sts.name = "bauer"; sts.age = 23; return 0; }
C語言中只有使用typedef關鍵字重命名struct後纔可使用Student定義變量。
C++語言中struct用於定義一種全新的類型,可使用struct定義的標識符直接定義變量。
#include <iostream> using namespace std; struct Student { const char* name; int age; }; int main(int argc, char *argv[]) { Student s; s.name = "bauer"; s.age = 20; return 0; }
在C語言中,函數在定義時沒有給出參數、返回值的類型,默認爲int。
int f()表示返回值爲int,接受任意參數的函數
f(void)表示返回值爲int的無參數函數
#include <stdio.h> //接受任意個參數,返回int類型 func1() { return 30; } //無參函數,返回int類型 func2(void) { return 20; } //參數i默認爲int類型 void func3(i) { printf("i = %d\n", i); } int main(int argc, char *argv[]) { int a = func1(1,2,3); printf("a = %d\n", a);//30 int b = func2(); printf("b = %d\n", b);//20 func3(10);//10 return 0; }
C++語言中,全部的標識符都必須顯示的聲明類型。C語言中的默認類型在C++中是不合法的。
#include <iostream> using namespace std; //error: ISO C++ forbids declaration of 'func1' with no type [-fpermissive] func1() { return 30; } //無參數,返回int int func2() { return 20; } int func3(void) { return 10; } int main(int argc, char *argv[]) { //error: too many arguments to function 'int func2()' int a = func2(20); int b = func2(); int c = func3(); return 0; }
C語言中,const修飾的變量是隻讀的,本質仍是變量,能夠藉助指針修改變量空間的值;const修飾的變量會分配存儲空間,const修飾的局部變量分配在棧上,const修飾的全局變量分配在只讀存儲區,修改const修飾的全局變量的值將會致使異常錯誤;const只在編譯期有效,在運行期無效;const關鍵詞用於向編譯器代表const修飾的變量不能作左值。
#include <stdio.h> //const全局變量,分配在只讀存儲區 const int number = 10; int main(int argc, char *argv[]) { const int c = 0; int* p = (int*)&c;//編譯器會爲c分配空間 printf("Begin...\n"); *p = 5; printf("c = %d\n", c);//5 c = 10;//error: assignment of read-only variable 'c' int* cp = &number; *cp = 100;//程序異常 const int number = 10; int array[number] = {0};//只讀變量,編譯時沒法肯定其值 //error: variable-sized object may not be initialized printf("End...\n"); return 0; }
C++語言中,對C語言基礎的const進行了優化處理。編譯器編譯過程當中遇到const修飾的標識符時,會將const修飾的標識符放入符號表中。若是後續編譯過程當中發現const修飾的標識符時,直接使用符號表中const修飾的標識符對應的值直接替換。但在如下狀況下C++編譯器會給const聲明的常量分配空間:
A、const修飾的常量爲全局(extern修飾),而且須要在其它文件中使用
B、使用&操做符對cosnt常量取地址
C++編譯器雖然會對const常量分配空間,但不會使用其存儲空間的值。
const常量的判別:
A、只有用字面量初始化的const常量纔會進入符號表
B、使用其餘變量初始化的const常量仍然是隻讀變量
C、被volatile修飾的const常量不會進入符號表
const引用類型與初始化變量的類型相同時,初始化變量爲只讀變量;不一樣時,生成一個新的只讀變量。
#include <iostream> using namespace std; int main(int argc, char *argv[]) { const int a = 10; //error: invalid conversion from 'const int*' to 'int*' int* p = &a; int* cp = (int*)&a; *cp = 100; printf("a = %d\n", a);//10 printf("*cp = %d\n", *cp);//100 int b = 3; //使用其它變量初始化的const常量是隻讀變量 const int c = b; //error: variable-sized object 'array' may not be initialized int array[c] = {0}; //使用volatile修飾的const常量不會進入符號表 volatile const int d = 10; //error: variable-sized object 'varray' may not be initialized int varray[d] = {0}; return 0; }
C++語言中const與宏定義的不一樣在於,const常量由編譯器處理,編譯器會對const常量進行類型檢查和做用域檢查,而宏定義由預處理器進行處理,是單純的文本替換。
#include <iostream> using namespace std; void func1() { #define NUMBER 100 const int number = 10; printf("NUMBER = %d\n",NUMBER); printf("number = %d\n",number); } void func2() { //宏定義沒有做用域概念,預處理時直接替換 printf("NUMBER = %d\n",NUMBER); printf("number = %d\n",number); //'number' was not declared in this scope } int main(int argc, char *argv[]) { func1(); func2(); const int number = 10; //編譯時使用符號表的值替換 int array[number] = {0}; return 0; }
C語言中,三目運算符返回變量的值,三目運算符表達式不能作左值使用。
C++語言中,三目運算符可直接返回變量自己,三目運算符表達式能夠做爲左值使用。可是當三目運算符表達式可能返回的值中有一個是常量值,則三目運算符表達式不能做爲左值使用。
C++中,當三目運算符表達式可能返回的都是變量時,返回的是變量的引用;當三目運算符表達式可能返回的有常量值時,返回的是值。
#include <iostream> using namespace std; int main(int argc, char *argv[]) { int a = 3; int b = 2; //返回變量自己,能夠作左值 (a>b?a:b) = 10; printf("a>b?a:b = %d\n",a>b?a:b); //返回變量的值,不能作左值 (a<b?1:b) = 20; //error: lvalue required as left operand of assignment return 0; }
cin和cout是C++的標準輸入流和輸出流,在頭文件 iostream 中定義。
流名 含義 隱含設備 流名 含義 隱含設備
cin 標準輸入 鍵盤 cerr 標準錯誤輸出 屏幕
cout 標準輸出 屏幕 clog cerr 的緩衝輸出 屏幕
int main() { char name[30]; int age; cout<<"pls input name and age:"<<endl; cin>>name; cin>>age; cout<<"your name is: "<<name<<endl; cout<<"your age is: "<<age<<endl; return 0; }
A、按進制輸出數據類型
cout<<dec<<i<<endl; cout<<hex<<i<<endl; cout<<oct<<i<<endl;
B、設置域寬,設置左右對齊及填充字符
int main() { cout<<setw(10)<<1234<<endl; cout<<setw(10)<<setfill('0')<<1234<<endl; cout<<setw(10)<<setfill('0')<<setiosflags(ios::left)<<1234<<endl; cout<<setw(10)<<setfill('-')<<setiosflags(ios::right)<<1234<<endl; return 0; }
C、實型數據的設置
cout<<setw(5)<<'a'<<endl<<setw(5)<<100<<endl <<setprecision(2)<<setiosflags(ios::fixed)<<120.00<<endl;
C語言中不容許重名函數的存在。
C++語言中爲了簡化編程容許重名函數的存在,即便用同一個函數名定義不一樣的函數,重名函數稱爲函數重載。
重載函數本質是定義的相互獨立的不一樣函數。當函數名和不一樣的參數搭配時函數的含義不一樣。
int abs(int a) { return a>0? a:-a; } double abs(double a) { return a>0? a:-a; }
函數重載的規則以下:
A、函數名相同。
B、參數個數不一樣,參數的類型不一樣,參數順序不一樣,都可構成重載。
C、返回值類型不一樣則不能夠構成重載。
#include <iostream> using namespace std; int func(int a, int b, int c = 0) { return a + b + c; } int func(int a, int b) { return a + b; } int main(int argc, char *argv[]) { //函數調用時出現二義性 int x = func(1,2); //error: call of overloaded 'func(int, int)' is ambiguous return 0; }
編譯器調用重載函數的匹配規則以下:
A、將全部同名函數做爲候選者
B、尋找可行的候選參數
C、匹配成功或失敗
函數重載的匹配規則以下:
A、精確匹配實參,找到則調用。
B、經過默認參數可以匹配實參
C、經過默認類型轉換匹配實參
經過默認類型轉換匹配實參時,經過隱式轉換尋求一個匹配,找到則調用。
C++容許int到long和double的隱式類型轉換,所以在函數重載時會引發二義性,解決方法是在調用時強轉類型。
#include <iostream> using namespace std; int func(int a, int b) { return a + b; } double func(double a, double b,double c) { cout << "func(double a, double b,double c)"<<endl; return a + b + c; } long func(long a, long b,long c) { cout << "func(long a, long b,long c)"<<endl; return a + b + c; } int main(int argc, char *argv[]) { int a = 1; int b = 2; int c = 3; //int xa = func(a,b,c);//出現二義性 int x = func((long)a,(long)b,(long)c); printf("x = %d\n", x); return 0; }
編譯器調用重載函數匹配失敗的規則:
A、若是最終找到的候選函數不惟一,則出現二義性,編譯報錯。
B、若是沒法匹配全部候選者,函數未定義,編譯報錯。
重載函數使用默認參數可能會形成二義性。
#include <iostream> using namespace std; int func(int a, int b, int c = 0) { return a + b + c; } int func(int a, int b) { return a + b; } int main(int argc, char *argv[]) { //函數調用時出現二義性 int x = func(1,2); //error: call of overloaded 'func(int, int)' is ambiguous return 0; }
C++利用name mangling(傾軋)技術,來更名函數名,區分參數不一樣的同名函數。
C++的name mangling實現使用 v c i f l d表示void char int float long double及其引用。
void func(char a); // func_c(char a) void func(char a, int b, double c);//func_cid(char a, int b, double c);
name mangling發生在兩個階段,.cpp編譯階段和.h的聲明階段。只有兩個階段同時進行,才能匹配調用。
#include <iostream> using namespace std; //函數類型:int(int,int) int func(int a, int b) { return a + b; } //函數類型:double(double, double, double) double func(double a, double b,double c) { cout << "func(double a, double b,double c)"<<endl; return a + b + c; } //函數類型:long(long,long,long) long func(long a, long b,long c) { cout << "func(long a, long b,long c)"<<endl; return a + b + c; } int main(int argc, char *argv[]) { int a = 1; int b = 2; int c = 3; //int func(int a, int b) printf("x1 = 0x%X\n", (int(*)(int,int))func); //long func(long a, long b,long c) printf("x2 = 0x%X\n", (long(*)(long,long,long))func); //double func(double a, double b,double c) printf("x3 = 0x%X\n", (double(*)(double,double,double))func); return 0; }
使用nm工具查看main.o文件中符號表信息的命令以下:nm.exe -a main.o
func重載函數的符號表信息以下:
00000036 T __Z4funcddd 00000029 T __Z4funcii 00000090 T __Z4funclll
上述的信息表示代碼中的三個重載函數。
將重載函數名賦值給函數指針時,根據重載規則選擇與函數指針參數列表一致的函數。重載函數的函數類型與函數指針類型必須嚴格匹配(不能有任何類型的隱式轉換),此時函數返回類型將參與函數類型匹配。
函數重載必須發生在同一個做用域,沒法經過函數名獲得重載函數的入口地址。
重載函數的函數類型不一樣。
#include <iostream> using namespace std; //函數類型:int(int,int) typedef int(*pFunc1)(int,int); int func(int a, int b) { cout << "func(int a, int b)"<<endl; return a + b; } //函數類型:double(double, double, double) typedef double(*pFunc2)(double,double,double); double func(double a, double b,double c) { cout << "func(double a, double b,double c)"<<endl; return a + b + c; } //函數類型:long(long,long,long) typedef long(*pFunc3)(long,long,long); long func(long a, long b,long c) { cout << "func(long a, long b,long c)"<<endl; return a + b + c; } int main(int argc, char *argv[]) { int a = 1; int b = 2; int c = 3; pFunc1 func1 = func; int x1 = func1(1,2);//int func(int a, int b) printf("func1 = %d\n", x1); pFunc2 func2 = func; int x2 = func2(1,2,3);//double func(double a, double b,double c) printf("func2 = %d\n", x2); pFunc3 func3 = func; int x3 = func3(1,2,3);//long func(long a, long b,long c) printf("func3 = %d\n", x3); return 0; }
重載函數的調用可能會存在隱式類型轉換,好比int到long、double類型的轉換,可是要函數指針調用重載函數時,函數指針的類型必須與重載函數的類型嚴格匹配。
函數重載的注意事項以下:
A、函數重載必然發生在同一個做用域中。
B、編譯器須要使用參數列表或函數類型進行函數的選擇。
C、不能直接經過函數名獲得重載函數的入口地址。
C++徹底兼容C語言,所以必須徹底兼容C的類庫。因爲.c文件的類庫文件中函數名並無發生name manling行爲,而在包含.c文件所對應的.h文件時,.h 文件要發生name manling行爲,於是會在編譯連接時候發生錯誤。
C++爲了不上述錯誤的發生,重載了關鍵字extern。只須要要避免name manling的函數前,加extern "C"若有多個,則extern "C"{}。
C語言標準庫中實際上對C++語言程序引用時作了特殊處理,在C++語言編譯器編譯時使用extern "C"將C語言的標準庫函數排除了命名傾軋。
爲了確保不管在C、C++編譯器中C代碼以C語言方式編譯:
#ifdef __cplusplus extern "C"{ #endif //c-style code #ifdef __cplusplus } #endif
C++編譯器不能以C語言方式編譯重載函數,C++編譯器將函數名和參數列表編譯爲目標名,C語言編譯方式只將函數名做爲目標名進行編譯。
C++調用C語言編碼的.dll時,當包含.dll的頭文件或聲明接口函數時須要加extern 「C」。
add.h源碼:
#ifndef ADD_H #define ADD_H extern int add(int a, int b); #endif
add.c源碼:
#include "add.h" int add(int a, int b) { return a + b; }
main.cpp源碼:
#include <stdio.h> extern "C" { #include "add.h" } int main() { int c = add(10, 100); printf("%d\n", c); return 0; }
gcc add.c -o add.o g++ add.o main.cpp
C代碼中引用C++的函數和變量時,C++頭文件須要添加extern 「C」,但在C代碼中不能直接引用聲明瞭extern 「C」的C++頭文件,C代碼中只須要將C++中定義的extern 「C」函數聲明爲extern類型便可。
add.h源碼:
#ifndef ADD_H #define ADD_H extern "C" int add(int a, int b); #endif
add.cpp源碼:
#include "add.h" int add(int a, int b) { return a + b; }
main.c源碼:
#include <stdio.h> //#include "add.h" 錯誤 extern int add(int a, int b); int main() { int x = add(1, 2); printf("x = %d\n", x); return 0; }
編譯:
g++ -c add.cpp -o add.o gcc main.c add.o -lstdc++
或是gcc add.cpp main.c -lstdc++
g++會自動進行C++標準庫的鏈接;用gcc鏈接C++程序也能夠,但須要人爲指定鏈接C++標準庫(-lstdc++),不然就會出現undefined reference to __gxx_personality_v/0
之類的錯誤。
C++提供了運算符重載機制。能夠爲自定義數據類型重載運算符。實現構造數據類型也能夠像基本數據類型同樣的運算特性。
struct COMP { float real; float image; }; COMP operator+(COMP one, COMP another) { one.real += another.real; one.image += another.image; return one; } int main() { COMP c1 = {1,2}; COMP c2 = {3,4}; COMP sum = operator+(c1,c2); //c1+c2; cout<<sum.real<<" "<<sum.image<<endl; return 0; }
實例代碼重載了一個全局的操做符+號用於實現將兩個自定義結構體類型相加。本質是函數的調用。
C++語言中,能夠在函數聲明時爲參數提供一個默認值。當函數調用沒有提供參數的值時,使用默認值。
函數默認參數的規則以下:
A、默認參數的順序,是從右向左,不能跳躍。
B、定義在前,調用在後(此時定義和聲明爲一體),默認參數在定義處;聲明在前,調用在後,默認參數在聲明處。
C、一個函數,不能既做重載,又做默認參數的函數。當你少寫一個參數時,系統沒法確認是重載仍是默認參數。
函數調用時參數從左到右匹配,若是一個參數使用了默認值,則後續參數必須使用默認值。
A、單個參數
#include <iostream> #include <time.h> using namespace std; void weatherForcast(char * w="sunny") { time_t t = time(0); char tmp[64]; strftime( tmp, sizeof(tmp), "%Y/%m/%d %X %A ",localtime(&t) ); cout<<tmp<< "today is weahter "<<w<<endl; } int main(int argc, char *argv[]) { //sunny windy cloudy foggy rainy weatherForcast(); weatherForcast("rainny"); weatherForcast(); return 0; }
B、多個參數
#include <iostream> using namespace std; float volume(float length, float weight = 4,float high = 5) { return length*weight*high; } int main(int argc, char *argv[]) { float v = volume(10); float v1 = volume(10,20); float v2 = volume(10,20,30); cout<<v<<endl; cout<<v1<<endl; cout<<v2<<endl; return 0; }
C++語言中能夠爲函數提供佔位參數,佔位參數只有類型聲明,沒有參數名聲明。因爲C++類型檢查較爲嚴格,爲兼容C語言,能夠將函數參數默認值和佔位參數結合使用。
C語言中func函數以下:
#include <stdio.h> void func() { } int main(int argc, char *argv[]) { func(5,10); return 0; }
C語言中func函數接收任意個數的參數。
C++語言中對func函數增長佔位參數可使C語言中的func函數快速地知足C++語言的語法要求,代碼以下:
#include <iostream> using namespace std; void func(int x, int = 0) { } int main(int argc, char *argv[]) { func(5,10); return 0; }
變量名,自己是一段內存的引用,即別名(alias)。引用,是爲己有變量起一個別名。Type& name = var;
int a; int &b = a;
普通引用在定義時必須使用同類型的變量進行初始化。
A、引用沒有定義,是一種關係型聲明。聲明它和原有某一變量(實體)的關係。故 而類型與原類型保持一致,且不分配內存,與被引用的變量有相同的地址。
B、聲明的時候必須初始化,一經聲明,不可變動。
C、可對引用再次引用。屢次引用的結果,是某一變量具備多個別名。
D、&符號前有數據類型時,是引用。其它皆爲取地址。
#include <iostream> using namespace std; int main(int argc, char *argv[]) { int a = 3; int c = 6; float f = 3.14; int& b = a; printf("&a = 0x%X\n", &a); printf("&b = 0x%X\n", &b); //error: redeclaration of 'int& b' int& b = c;//error //給b賦值 b = c; //引用的類型必須與變量類型相同 int& d = f;//error //error: invalid initialization of reference of type 'int&' from expression of type 'float' //引用在定義時必須初始化 int& rd;//error //error: 'rd' declared as reference but not initialized //引用不可使用字面值初始化 int& r = 10;//error //error: invalid initialization of non-const reference of type 'int&' from an rvalue of type 'int' return 0; }
函數中的引用形參不須要進行初始化,函數調用時進行初始化。
#include <iostream> using namespace std; //引用 void swap(int &a, int &b) { int tmp; tmp = a; a = b; b = tmp; printf("swap(int &a, int &b)\n"); } //指針 void swap(int* a, int* b) { int tmp; tmp = *a; *a = *b; *b = tmp; printf("swap(int* a, int* b)\n"); } int main(int argc, char *argv[]) { int a = 3; int b = 6; swap(a,b); printf("a = %d\n",a);//6 printf("b = %d\n",b);//3 swap(&a,&b); printf("a = %d\n",a);//3 printf("b = %d\n",b);//6 return 0; }
四、引用的提升
A、能夠定義指針的引用,但不能定義引用的引用。
int a; int* p = &a; int*& rp = p; // ok int& r = a; int&& rr = r; // error
B、能夠定義指針的指針(二級指針),但不能定義引用的指針。
int a; int* p = &a; int** pp = &p; // ok int& r = a; int&* pr = &r; // error
C、能夠定義指針數組,但不能定義引用數組,能夠定義數組引用。
int a, b, c; int* parr[] = {&a, &b, &c}; // ok int& rarr[] = {a, b, c}; // error int arr[] = {1, 2, 3}; int (&rarr)[3] = arr; // ok 的
數組是連續的存儲空間,數組中的元素若是是引用,會致使數組的元素存儲不連續。引用數組會破壞數組存儲空間的連續性。
const引用所引用的對象必須是const的,將普通引用綁定到const引用對象是不合法的。 const type& name = var;
const引用可以使用相關類型的對象(常量,非同類型的變量或表達式)初始化,const引用讓變量具備只讀屬性,是const引用與普通引用最大的區別。
非const引用只能綁定到與該引用同類型的對象。
當const引用使用字面常量值初始化時,C++編譯器會爲常量值分配空間,使用字面常量對const引用初始化將生成一個只讀變量。
#include <iostream> using namespace std; int main(int argc, char *argv[]) { int a = 10; const int& ra = a;//const引用,爲只讀變量 //只讀變量不能做爲左值 ra = 100;//error: assignment of read-only reference 'c' int* p = (int*)&ra; *p = 5; printf("ra = %d\n", ra);//5 const int& rb = 10;//rb爲只讀變量,佔用內存空間 //只讀變量不能做爲左值 rb = 100;//error int arraya[ra] = {0};//error //error: variable-sized object 'arraya' may not be initialized int arrayb[rb] = {0};//error //error: variable-sized object 'arrayb' may not be initialized double pi = 3.14; int& rpi = pi;//非法 //error: invalid initialization of reference of type 'int&' from expression of type 'double' const int& crpi = pi;//合法 printf("rpi= %d\n",crpi);//3 return 0; }
C++編譯器在編譯過程當中使用指針常量做爲引用的內部實現,所以引用所佔用空間大小與指針相同。引用的本質是一個指針常量。type & name<====> type * const name;
使用引用時不能返回局部變量的引用
#include <iostream> using namespace std; int main(int argc, char *argv[]) { printf("sizeof(char&) = %d\n", sizeof(char&));//1 char c = 'a'; char& rc = c; printf("sizeof(char&) = %d\n", sizeof(rc));//1 return 0; }
上述代碼的彙編代碼以下:
將字符’a’(0x61)存儲到指針寄存器0x1b,將0x1b放入eax數據寄存器中,再將eax數據寄存器存儲內容放入指針寄存器0x1c中。
C語言中提供了malloc和free兩個系統函數,完成對堆內存的申請和釋放。而C++則提供了兩關鍵字new和delete。
new分配內存空間時,不能保證按需分配,分配內存空間大小可能會大於所需空間大小。所以,new會分配至少申請大小的內存空間。
A、開闢單變量地址空間
int *p = new int; //開闢大小至少爲sizeof(int)空間 int *a = new int(5); //開闢大小至少爲sizeof(int)空間,並初始化爲 5
B、開闢數組空間
一維: int a = new int[100];//開闢一個大小很多於400字節的整型數組空間
二維: int (a)[6] = new int[5][6]
三維: int (*a)[5][6] = new int[3][5][6]
A、釋放單變量空間
int *a = new int; delete a; //釋放單個 int 的空間
B、釋放數組空間
int *a = new int[5]; delete []a; //釋放 int 數組空間
int *p = new int(5); cout<<*p<<endl; delete p; char *pp = new char[10]; strcpy(pp,"china"); cout<<pp<<endl; delete []pp; string *ps = new string("china"); cout<<*ps<<endl; //cout<<ps<<endl; delete ps; char **pa= new char*[5]; memset(pa,0,sizeof(char*[5])); pa[0] = "china"; pa[1] = "america"; char **pt = pa; while(*pt) { cout<<*pt++<<endl; } delete []pt; int (*qq)[3][4] = new int[2][3][4]; delete []qq;
//C++ 內存申請失敗會拋出異常 try{ int *p = new int[10]; } catch(const std::bad_alloc e) { return -1; } //C++ 內存申請失敗不拋出異常版本 int *q = new (std::nothrow)int[10]; if(q == NULL) return -1;
C++中堆空間的分配和釋放注意事項以下:
A、new/delete 是關鍵字,效率高於 malloc 和 free.
B、配對使用,避免內存泄漏和多重釋放。
C、避免交叉使用。好比 malloc 申請的空間去 delete,new 出的空間被 free;
D、重點用在類對像的申請與釋放。申請的時候會調用構造器完成初始化,
釋放的時候,會調用析構器完成內存的清理。
malloc與new的區別以下:
A、new是C++關鍵字,malloc是C語言庫函數
B、new以具體類型爲單位進行內存分配,malloc以字節位單位分配內存
C、new在申請單個類型變量時能夠進行初始化,malloc不具有
D、new在全部C++編譯器中都支持,malloc在某些系統開發中不可調用
E、new可以觸發構造函數的調用,malloc僅分配須要的內存空間
F、對象的建立只能使用new,malloc不適合面向對象開發
free與delete的區別以下:
A、delete是C++關鍵字,free是庫函數
B、delete在全部C++編譯器中都支持,free在某些系統開發中不可調用
C、delete可以觸發析構函數的調用,free僅歸還分配的內存空間
D、對象的銷燬只能使用delete,free不適合面向對象開發
E、free能夠歸還new申請的內存空間,但不會調用析構函數,可能會形成內存泄漏
F、delete能夠釋放malloc分配的內存空間,但會調用析構函數,會形成其餘問題。
C語言中有宏函數的概念。宏函數的特色是內嵌到調用代碼中去,避免了函數調用的開銷。但宏函數的處理髮生在預處理階段,缺乏做用域檢查和類型檢查。
C++提供了inline關鍵字,請求C++編譯器將一個函數進行內聯編譯(C++編譯器能夠拒絕),C++編譯器會直接將內聯的函數體代碼插入函數調用的地方。
內聯函數聲明時inline關鍵字必須和函數定義結合在一塊兒,不然編譯器會直接忽略內聯請求。
inline int sqr(int x) { return x*x; }
內聯函數的優勢:避免調用時的額外開銷(入棧與出棧操做)
內聯函數的缺點:內聯函數的函數體在代碼段中會出現多個「副本」,所以會增長代碼段的空間。
內聯函數的本質:以犧牲代碼段空間爲代價,提升程序的運行時間的效率。
內聯函數的適用場景:函數體很「小」,且被「頻繁」調用。
Inline關鍵字是對編譯器的建議,若是編譯器認爲inline聲明的函數能夠內聯,則編譯器會將函數內聯,若是編譯器認爲inline聲明的函數的函數體太長,則不會內聯,按普通函數處理。
using namespace std; inline int func(int a, int b) { return a*a + b*b; } int main(int argc, char *argv[]) { int a = 3; int b = 4; int c = func(a,b); return 0; }
上述代碼在QtCreator+MinGW編譯器下調試時,查看彙編代碼以下:
調用func函數時,C++編譯器沒有將func函數內聯,仍然使用函數調用。
inline聲明的函數,內聯請求可能被C++編譯器拒絕。
現代C++編譯器可以進行編譯優化,一些函數即便沒有inline關鍵字聲明也可以內聯編譯,同時現代C++編譯器提供了擴展的語法,可以對函數進行強制內聯。如現代G++編譯器使用__attribute__((always_inline))
聲明強制內聯,MSVC編譯器使用__forceinline
聲明強制內聯,再也不使用inline關鍵字。
#include <iostream> using namespace std; __attribute__((always_inline)) int func(int a, int b); int main(int argc, char *argv[]) { int a = 3; int b = 4; int c = func(a,b); return 0; } int func(int a, int b) { return a*a + b*b; }
上述代碼在QtCreator+MinGW編譯器中進行調試時,main函數中func函數調用代碼的彙編代碼以下:
MinGW編譯器已經對func函數進行了內聯。
C++中使用inline關鍵字內聯編譯函數的限制:
A、不能存在任何形式的循環語句
B、不能存在過多的條件判斷語句
C、函數體不能過於龐大
D、不能對函數進行取地址操做
E、函數內聯聲明必須在調用語句前
對於現代C++編譯器的擴展語法提供的強制內聯不受上述條件限制。
C語言中的類型轉換是強制轉換,任何類型間均可以轉換,過於粗暴。
C++語言引入了static_cast、dynamic_cast、const_cast、reinterpret_cast四個關鍵字處理不一樣類型間的轉換。
靜態類型轉換是在編譯期內便可決定其類型的轉換。
靜態類型轉換的使用場合:
A、用於基本類型間的轉換
B、不能用於基本類型指針間的轉換
C、用於有繼承關係類對象間的轉換和類指針間的轉換(轉換通常從子對象向父對象轉換)
語法格式:
static_cast<目標類型> (標識符)
應用實例:
#include <stdio.h> class A { private: int a; int b; public: A() { a = 0; b = 0; } void print() { printf("a = %d, b = %d\n", a, b); } }; class B : public A { private: int c; int d; public: void display() { printf("c = %d, d = %d\n", c, d); } }; class C { public: void print() { printf("hello\n"); } }; int main() { float f = static_cast<float>(9)/10;//基本類型的轉換 printf("f = %f\n", f); A a; B b; A aa = static_cast<A>(b);//將子類對象轉換爲父類對象 aa.print(); //B bb = static_cast<B>(a);//不能將父對象轉換爲子對象 A* pa = static_cast<A*>(&a);//在同類型對象指針間轉換 pa->print(); pa = static_cast<A*>(&b);//將子類對象指針轉換爲父類對象指針 pa->print(); B* pb = static_cast<B*>(&b); pb->display(); pb = static_cast<B*>(&a); pb->display(); //C c = static_cast<C>(a);//沒有轉換構函數,不能將A類型轉換爲C類型 //c.print(); return 0; }
動態類型轉換的使用場合:
A、用於有繼承關係的類指針間的轉換
B、有交叉關係的類指針間的轉換
C、具備類型檢查
D、必須有虛函數支持
語法格式:
dynamic_cast<目標類型> (標識符)
用於有直接或間接繼承關係的指針(引用)的強制轉換
轉換指針成功將會獲得目標類型的指針,轉換失敗將獲得一個空指針;
轉換引用成功將獲得目標類型的引用,轉換失敗將獲得一個異常操做信息。
使用實例:
#include <iostream> #include <string> using namespace std; class Base { public: Base() { cout << "Base::Base()" << endl; } virtual ~Base() { cout << "Base::~Base()" << endl; } }; class Derived : public Base { }; int main() { Base* p = new Derived; Derived* pd = dynamic_cast<Derived*>(p);//將指向子類對象的父類指針轉換爲子類指針,轉換成功 if( pd != NULL ) { cout << "pd = " << pd << endl; } else { cout << "Cast error!" << endl; } delete p; cout << endl; p = new Base; pd = dynamic_cast<Derived*>(p);//將指向父類對象的父類指針轉換爲子類指針,轉換失敗 if( pd != NULL ) { cout << "pd = " << pd << endl; } else { cout << "Cast error!" << endl; } delete p; return 0; }
常量類型轉換的使用場合:
A、用於去除變量的只讀屬性
B、目標類類型只能是指針或引用
語法格式:
const_cast<目標類型> (標識符) //目標類類型只能是指針或引用。
const_cast將轉換掉表達式的const屬性
應用實例:
#include <iostream> using namespace std; int main() { const int& a = 10; int& b = const_cast<int&>(a);//將a的只讀屬性去除,並初始化b b = 0; cout << "a = " << a << endl;//0 cout << "b = " << b << endl;//0 const int c = 100; int& d = const_cast<int&>(c);//爲c分配一個只讀空間 //int x = const_cast<int>(c);//error,目標類型只能爲指針和引用 d = 1000; cout << "c = " << c << endl;//100 cout << "d = " << d << endl;//1000 return 0; }
重解釋類型轉換使用場合:
A、用於指針類型間的強制轉換
B、用於整數和指針類型間的強制轉換
語法格式:
reinterpret_cast<目標類型> (標識符)
爲數據的二進制形式從新解釋,可是不改變其值。
使用實例:
#include <iostream> using namespace std; int main(int argc, char *argv[]) { int a = 100; char c = 'a'; int* pa = reinterpret_cast<int*>(&c); printf("*pa = %d\n", *pa);//687781729 printf("*pa = %c\n", *pa);//'a' //int x = reinterpret_cast<int>(c);//error //int y = reinterpret_cast<int>(1.1);//error return 0; }
C語言中,只有一個全局做用域,全部的全局標識符共享一個做用域,所以標識符之間可能存在衝突。
C++語言中,提出了命名空間的概念。命名空間將全局做用域分爲不一樣的部分,不一樣命令空間中的標識符能夠重名而不會發生衝突,命名空間能夠嵌套。全局做用域即默認命名空間。
global scope是一個程序中最大的scope,是引發命名衝突的根源。C語言沒有從語言層面提供命名空間機制來解決。global scope是無名的命名空間。
NameSpace是對全局區域的再次劃分。
命名空間的聲明以下:
namespace NAMESPACE { 全局變量 int a; 數據類型 struct Stu{}; 函數 void func(); }
直接指定命名空間: NameSpace::a = 5;
使用using+命名空間+空間元素:using NameSpace::a; a = 2000;
使用using +namespace+命名空間;
#include <iostream> using namespace std; namespace MySpace { int x = 1; int y = 2; } namespace Other { int x = 3; int y = 4; } int main() { { using namespace MySpace; cout<<x<<y<<endl; } { using namespace Other; cout<<x<<y<<endl; } { MySpace::x = 100; Other::y = 200; cout<<MySpace::x<<Other::y<<endl; } return 0; }
可使用塊語句將命名空間限定在塊語句內部。
namespace MySpace { int x = 1; int y = 2; namespace Other { int m = 3; int n = 4; } }
在實際項目開發中,能夠將一個類或者具備相同屬性的多個類聲明在一個命名空間內,在使用時只須要聲明命名空間便可。
#ifndef A_H #define A_H namespace XX { class A { public: A(); ~A(); }; } #endif // A_H
#include "a.h" using namespace XXX { A::A() {} A::~A() {} }
除了使用字符數組來處理字符串之外,C++引入了字符串類型。能夠定義字符串變量。
string str; str = "china"; string str2 = " is great "; string str3 = str2;
string str = "china"; cout << sizeof(str) << " " << str.max_size() << " " << str.size()<<endl;
4 1073741820 5
A、賦值string str3 = str2;
B、加法string combine = str + str2;
C、關係
string s1 = "abcdeg"; string s2 = "12345"; if(s1>s2) cout<<"s1>s2"<<endl; else cout<<"s1<s2"<<endl;
string數組是高效的,若是用二維數組來存入字符串數組的話,則容易浪費空間,此時列數是由最長的字符串決定。若是用二級指針申請堆空間,依據大小申請相應的空間,雖然解決了內存浪費的問題,可是操做麻煩。用 string 數組存儲,字符串數組的話,效率即高又靈活。
string sArray[10] = { "0", "1", "22", "333", "4444", "55555", "666666", "7777777", "88888888", "999999999", }; for(int i=0; i<10; i++) { cout<<sArray[i]<<endl; }
int capacity()const; //返回當前容量(即string中沒必要增長內存便可存放的元素個數) int max_size()const; //返回string對象中可存放的最大字符串的長度 int size()const; //返回當前字符串的大小 int length()const; //返回當前字符串的長度 bool empty()const; //當前字符串是否爲空 void resize(int len,char c);//把字符串當前大小置爲len,並用字符c填充不足的部分