C++教程(超長最全入門)
1、C++簡介
1.1 起源
-貝爾實(shí)驗(yàn)室20世紀(jì)80年代(1979)
1.2 應(yīng)用范圍
- 文字處理程序以及電子表格
- 編譯器
- 操作系統(tǒng)
- 大型游戲等
1.3 C++和C
- C語言是結(jié)構(gòu)化和模塊化的語言,面向過程。
- C++保留了C語言原有的所有優(yōu)點(diǎn),增加了面向?qū)ο蟮臋C(jī)制,俗稱“帶類的C",1983年更名為C++
2、開發(fā)工具
- 記事本(Notepad++)+命令行
- Visual C++ 6.0:經(jīng)典開發(fā)工具,與流行操作系統(tǒng)有沖突
- VS 2015等:功能強(qiáng)大,體積也強(qiáng)大
- Code::Blocks:開源免費(fèi)開發(fā)工具,專業(yè)開發(fā)人員推薦使用
- 其他開發(fā)工具:DeV C++、CLion、C-Free、Xcode、C4droid
3、基本語法
- 對象-對象具有狀態(tài)的行為。對象是類的實(shí)例。
- 類-類可以定義為對象行為、狀態(tài)的模版。
- 方法-從基本上講,一個方法表示一種行為,一個類可以包含多種方法。
- 變量
3.1 注釋
//單行注釋 /* 多行注釋 多行注釋 */
3.2關(guān)鍵字
asm | else | new | this |
---|---|---|---|
auto | enum | operator | throw |
bool | explicit | private | true |
break | export | protected | try |
case | extern | public | typedef |
catch | false | register | typeid |
char | float | reinterpret_cast | typename |
class | for | return | union |
const | friend | short | unsigned |
const_cast | goto | signed | using |
continue | if | sizeof | virtual |
default | inline | static | void |
delete | int | static_cast | volatile |
do | long | struct | wchar_t |
double | mutable | switch | while |
dynamic_cast | namespace | template |
3.3標(biāo)識符
- 標(biāo)識符是用來標(biāo)識變量、函數(shù)、類、模塊,或任何其他用戶自定義項(xiàng)目的名稱。一個標(biāo)識符以字母 A-Z 或 a-z 或下劃線 _ 開始,后跟零個或多個字母、下劃線和數(shù)字(0-9)。
- 標(biāo)識符內(nèi)不允許出現(xiàn)標(biāo)點(diǎn)字符,比如 @、& 和 %。C++ 是區(qū)分大小寫的編程語言。
4、數(shù)據(jù)類型
4.1基本數(shù)據(jù)類型
七種基本的C++數(shù)據(jù)類型:bool、char、int、float、double、void、wchar_t
類型修飾符:signed、unsigned、short、long
注:一些基本類型可以使用一個或多個類型修飾符進(jìn)行修飾,比如:signed short int簡寫為short、signed long int 簡寫為long。
類型名 | 占用字節(jié)數(shù) | 數(shù)值范圍 |
---|---|---|
void | 0 | |
bool | 1 | {true.false} |
wchar_t | 2或4個字節(jié) | |
char(signed char) | 1 | -128~+127 |
short(signed short) | 2 | -32768~+32767 |
int(signed int) | 4 | -2147483648~+2147483647 |
long(signed long) | 4 | -2147483648~+2147483647 |
long long(signed long long) | 8 | -9,223,372,036,854,775,808 ~9,223,372,036,854,775,807 |
float | 4 | -.34*1038~3.4*1038 |
double | 8 | -1.7*10308~1.7*10308 |
unsigned char | 1 | 0~255 |
unsigned shrot | 2 | 0~65525 |
unsigned(unsigned int) | 4 | 0~4294967295 |
unsigned long | 4 | 0~4294967295 |
unsigned long long | 8 | 0 ~ 18,446,744,073,709,551,615 |
//x64處理器 64位window10 vs2015 #include <iostream> using namespace std; int main() { bool b; char c;short s; int i; long l; long long ll; float f; double d; long double ld;long float lf; unsigned char uc; unsigned short us; unsigned int ui; unsigned long ul; unsigned long long ull; cout << sizeof(bool) << endl; cout << sizeof(char)<<" " << sizeof(short)<<" "<< sizeof(signed int) << " " << sizeof(long) << " " << sizeof(signed long long) << " " << sizeof(float) << " " << sizeof(double) << " " << sizeof(long float) << " " << sizeof(long double) << endl; cout <<sizeof(unsigned char)<<" "<< sizeof(unsigned short) << " " << sizeof(unsigned int) << " " << sizeof(unsigned long) << " " << sizeof(unsigned long long) << endl; cout << sizeof(unsigned) << endl; cout << "hello World!!!" <<endl; system("pause"); return 0; }
4.2 數(shù)據(jù)類型在不同系統(tǒng)中所占空間大小
這個與機(jī)器、操作系統(tǒng)、編譯器有關(guān)。比如同樣是在32bits的操作系統(tǒng)系,VC++的編譯器下int類型為占4個字節(jié);而tuborC下則是2個字節(jié)。
原因:
- c/c++規(guī)定int字長和機(jī)器字長相同
- 操作系統(tǒng)字長和機(jī)器字長未必一致
- 編譯器根據(jù)操作系統(tǒng)字長來定義int字長
類型 | 16位操作系統(tǒng) | 32位操作系統(tǒng) | 64位操作系統(tǒng) |
---|---|---|---|
char | 1 | 1 | 1 |
char* | 2 | 4 | 8 |
short | 2 | 2 | 2 |
int | 2 | 4 | 4 |
long | 4 | 4 | 8 |
long long | 8 | 8 | 8 |
注:long類型在不同編譯器中的占位不一樣: 32位時,VC++和GCC都是4字節(jié); 64位時,VC++是4字節(jié),GCC是8字節(jié)。
4.3 typedef聲明
//使用typedef為一個已有的類型取一個新的名字,語法如下: typedef type newname //eg: typedef int feet feet distance
4.4 枚舉類型
C++中的一種派生數(shù)據(jù)類型,它是由用戶定義的若干枚舉常量的集合;枚舉元素是一個整型,枚舉型可以隱式的轉(zhuǎn)換為int型,int型不能隱式的轉(zhuǎn)換為枚舉型。
//枚舉類型的語法: enum 枚舉名{ 標(biāo)識符[=整型常數(shù)], 標(biāo)識符[=整型常數(shù)], ... 標(biāo)識符[=整型常數(shù)] }枚舉變量;
如果枚舉沒有初始化, 即省掉"=整型常數(shù)"時, 則從第一個標(biāo)識符開始;
默認(rèn)情況下,第一個名稱的值為 0,第二個名稱的值為 1,第三個名稱的值為 2,以此類推。但是,您也可以給名稱賦予一個特殊的值,只需要添加一個初始值即可。
例如:
enum course {math,chinese,english,physics,chemistry}c; c = english; cout<<c<<endl; //2 //english為1 physics為2 chemistry為3,chinese仍為1,math仍為0 enum course {math,chinese,english=1,physics,chemistry};
5、變量
變量其實(shí)只不過是程序可操作的存儲區(qū)的名稱。C++ 中每個變量都有指定的類型,類型決定了變量存儲的大小和布局,該范圍內(nèi)的值都可以存儲在內(nèi)存中,運(yùn)算符可應(yīng)用于變量上。
5.1 變量的聲明和定義
- 變量聲明向編譯器保證變量以給定的類型和名稱存在,這樣編譯器在不需要知道變量完整細(xì)節(jié)的情況下也能繼續(xù)進(jìn)一步的編譯。
- 可以在 C++ 程序中多次聲明一個變量,但變量只能在某個文件、函數(shù)或代碼塊中被定義一次。
- 多個變量賦同一個值時,需要分別賦值。
int x = y = z = 66;//錯誤 int x = 3,y = 3,z = 3; int x, y ,z = 3; x = y = z;
變量的聲明(不分配內(nèi)存):extern 數(shù)據(jù)類型 變量名;
變量的定義:數(shù)據(jù)類型 變量名1,變量名2,...變量名n;
// 變量聲明 extern int a, b; int main () { // 變量定義 int a, b; // 初始化 a = 23; b = 25; return 0; }
5.2 變量的作用域
局部變量:在函數(shù)或一個代碼塊內(nèi)部聲明的變量,稱為局部變量。它們只能被函數(shù)內(nèi)部或者代碼塊內(nèi)部的語句使用。
全局變量:在所有函數(shù)外部定義的變量(通常是在程序的頭部),稱為全局變量。全局變量的值在程序的整個生命周期內(nèi)都是有效的。
- 局部變量和全局變量的名稱可以相同,但是在函數(shù)內(nèi),局部變量的值會覆蓋全局變量的值。
- 當(dāng)局部變量被定義時,系統(tǒng)不會對其初始化;定義全局變量時,系統(tǒng)會自動初始化值:
int float double 0,char ’\0‘,指針 NULL
int i = 66; int main () { int i = 88; cout << i<<endl;//8 return 0; } float f; double d; char c; int *p; int main() { cout << i << f << d << c << p << endl;//000 00000000 return 0 }
6、運(yùn)算符
- 算術(shù)運(yùn)算符:
+ - * / % ++ --
- 關(guān)系運(yùn)算符:
== != < > >= <=
- 邏輯運(yùn)算符:
&& || !
- 位運(yùn)算符:
& | ^ ~ << >>
- 賦值運(yùn)算符:
= += -= *= /= %= <<= >>= &= ^= !=
- 雜項(xiàng)運(yùn)算符:
sizeof //返回變量的大小,eg:sizeof(a)返回4 a是整型 sizeof(int) Condition?X:Y //三元運(yùn)算符 Condition為true,值為X,否則值為Y , //逗號表達(dá)式,值為最后一個表達(dá)式的值 .和-> //用于引用類、結(jié)構(gòu)和公用體的成員 Cast //強(qiáng)制類型轉(zhuǎn)換符 eg:int(2.202)返回2 & //指針運(yùn)算符 返回變量的地址 * //指針運(yùn)算符 指向一個變量
運(yùn)算符優(yōu)先級
類別 | 運(yùn)算符 | 結(jié)合性 |
---|---|---|
后綴 | () [] -> . ++ - - | 從左到右 |
一元 | + - ! ~ ++ - - (type)* & sizeof | 從右到左 |
乘除 | * / % | 從左到右 |
加減 | + - | 從左到右 |
移位 | << >> | 從左到右 |
關(guān)系 | < <= > >= | 從左到右 |
相等 | == != | 從左到右 |
位與 AND | & | 從左到右 |
位異或 XOR | ^ | 從左到右 |
位或 OR | | | 從左到右 |
邏輯與 AND | && | 從左到右 |
邏輯或 OR | || | 從左到右 |
條件 | ?: | 從右到左 |
賦值 | = += -= *= /= %=>>= <<= &= ^= | = |
逗號 | , | 從左到右 |
7、語法結(jié)構(gòu)
7.1 循環(huán)結(jié)構(gòu)
while
while(conditon)//0為false,非0為true { statement(s); }
for
for(init;conditon;increment)//0為false,非0或什么也不寫為true { statement(s); }
1.init首先被執(zhí),且只會執(zhí)行一次,也可以不寫任何語句。
2.然后會判斷conditon,true執(zhí)行循環(huán)主體,false跳過循環(huán)
3.執(zhí)行完循環(huán)主體,執(zhí)行increment,跳到2
int array[5] = { 11, 22, 33, 44, 55 }; for (int x : array) { cout << x << " "; } cout << endl; // auto 類型也是 C++11 新標(biāo)準(zhǔn)中的,用來自動獲取變量的類型 for (auto x : array) { cout << x << " "; }
for each
STL中的for增強(qiáng)循環(huán)。
int a[4] = { 4,3,2,1 }; for each (int var in a) { cout << var << " "; }
7.2 判斷結(jié)構(gòu)
if
if(expr) { statement;//如果expr為true將執(zhí)行的語句塊 } if(expr) { statement1;// 如果expr為true將執(zhí)行的語句塊 } else { statement2;// 如果expr為false將執(zhí)行的語句 } if(expr1) { statement1;// 如果expr1為true將執(zhí)行的語句塊 } elseif(expr2) { statement2;// 如果expr2為true將執(zhí)行的語句塊 } ... else { statementElse;// 當(dāng)上面的表達(dá)式都為false執(zhí)行的語句塊 }
switch
switch(expression){ case constant-expression : statement(s); break; case constant-expression : statement(s); break; // 您可以有任意數(shù)量的 case 語句 default : // 可選的 statement(s); }
- 每個case后滿的常量表達(dá)式必須各不相同。
- case語句和default語句出現(xiàn)的順序?qū)?zhí)行結(jié)果沒有影響。
- 若case后沒有break,執(zhí)行完就不會判斷,繼續(xù)執(zhí)行下一個case語句。直到遇到brerak。
- default后面如果沒有case,則break可以省略
- 多個case可以用一組執(zhí)行語句
char c = 'A'; switch (c) { case 'A': case 'B': case 'C': cout << "及格了" << endl; break; default: cout << "不及格" << endl; }
7.3 三元運(yùn)算符
//如果 Exp1 為真,則計(jì)算 Exp2 的值,結(jié)果即為整個 ? 表達(dá)式的值。如果 Exp1 為假,則計(jì)算 Exp3 的值,結(jié)果即為整個 ? 表達(dá)式的值 Exp1 ? Exp2 : Exp3;
7.4 預(yù)處理命令
預(yù)處理程序(刪除程序注釋,執(zhí)行預(yù)處理命令等)–>編譯器編譯源程序
- 宏定義:
#define 標(biāo)識符 字符串
- 文件包含:
#include<filename> 或者#include“filename”
- 條件編譯
//如果標(biāo)識符被#define定義過,執(zhí)行程序段1,否則執(zhí)行程序段2 #ifdef 標(biāo)識符 程序段1 #else 程序段2 #endif //如果標(biāo)識符沒有被#define定義過,執(zhí)行程序段1,否則執(zhí)行程序段2 #ifndef 標(biāo)識符 程序段1 #else 程序段2 #endif //如果表達(dá)式為true,執(zhí)行程序段1,否則執(zhí)行程序段2 #if 表達(dá)式 程序段1 #else 程序段2 #endif
8、數(shù)組
一些具有相同數(shù)據(jù)類型或相同屬性(類)的數(shù)據(jù)的集合,用數(shù)據(jù)名標(biāo)識,用下標(biāo)或序號區(qū)分各個數(shù)據(jù)。數(shù)組中的數(shù)據(jù)稱為元素。
8.1一維數(shù)組
定義一維數(shù)組的形式:數(shù)據(jù)類型 數(shù)據(jù)名[常量表達(dá)式]
初始化的形式:數(shù)據(jù)類型 數(shù)組名[常量表達(dá)式] = {初值表};
為數(shù)組的某一個元素賦值:數(shù)組名[下標(biāo)] =值(下標(biāo)從0開始)
數(shù)組的引用:數(shù)組名[下標(biāo)]
- 初始化數(shù)組時,可以只給部分?jǐn)?shù)組元素賦值
- 對全部元素?cái)?shù)組賦值時,可以不指定數(shù)組長度,編譯系統(tǒng)會根據(jù)初值個數(shù)確定數(shù)組的長度。
- static型數(shù)組元素不賦初值,系統(tǒng)會自動默認(rèn)為0。
int arr1[4] = {1,2,3,4}; int arr2[4] = { 1,2 }; int arr[4] = {0];//所有元素為0 static int arr3[3]; int arr4[4]; cout << "arr1:"<<arr1[0] << arr1[1] << arr1[2] << arr1[3] << endl; cout << "arr2:" << arr2[0] << arr2[1] << arr2[2] << arr2[3] << endl; cout << "arr3:" << arr3[0] << arr3[1] << arr3[2] << arr3[3] << endl; cout << "arr4:" << arr4[0] << arr4[1] << arr4[2] << arr4[3] << endl;
8.2二維數(shù)組
定義一維數(shù)組的形式:數(shù)據(jù)類型 數(shù)據(jù)名[常量表達(dá)式1][常量表達(dá)式2]
初始化的形式:數(shù)據(jù)類型 數(shù)組名[常量表達(dá)式1] [常量表達(dá)式2]= {初值表};
為數(shù)組的某一個元素賦值:數(shù)組名[行下標(biāo)][列下標(biāo)] =值(下標(biāo)從0開始)
數(shù)組的引用:數(shù)組名[行下標(biāo)][列下標(biāo)]
- 將所有數(shù)據(jù)寫在一個花括號內(nèi),自動按照數(shù)組元素個數(shù)在內(nèi)存中排列的順序賦值
- 可對部分元素賦值,其余元素的值自動取0.
- 定義初始化數(shù)組時,可以省略第一維的長度,第二維不能省,系統(tǒng)會自動確認(rèn)行數(shù)
int arr1[2][3]; int arr[2][3] = {0];//所有元素為0 int arr2[2][3] = { {1,2,3},{4,5,6} }; int arr3[2][3] = { 1,2,3 ,4,5,6 }; int arr4[2][3] = { {1},{4,6} }; int arr5[][3] = { 1,2,3 ,4,5,6 };
字符數(shù)組
char類型的數(shù)組,在字符數(shù)組中最后一位為’\0’)時,可以看成時字符串。在C++中定義了string類,在Visual C++中定義了Cstring類。
字符串中每一個字符占用一個字節(jié),再加上最后一個空字符。
如:
//字符串長度為8個字節(jié),最后一位是'\0'。 char array[10] = "yuanrui";//yuanrui\0\0\0 //也可以不用定義字符串長度,如: char arr[] = "yuanrui";//yuanrui\0
8.3 指向數(shù)組的指針
指針的概念會在后面詳細(xì)講解。
double *p; double arr[10]; p = arr;//p = &arr[0]; *(p+3);//arr[3]
8.4 數(shù)組與new(動態(tài)創(chuàng)建數(shù)組)
一維數(shù)組:
int* arr1 = new int[2];//delete []arr1; int* arr2 = new int[3]{ 1,2 };//delete []arr2
二維數(shù)組
int m=2, n=3; int** arr3 = new int*[2];//delete []arr3 for (int i = 0; i < 10; ++i) { arr3[i] = new int[3]; // delete []arr3[i] } int* arr4 = new int[m*n];//數(shù)據(jù)按行存儲 delete []arr3
8.5 數(shù)組與函數(shù)
數(shù)組->函數(shù)
如果傳遞二維數(shù)組,形參必須制定第二維的長度。
形式參數(shù)是一個指針:void function(int *param)
形式參數(shù)是一個已定義大小的數(shù)組:void function(int param[10])
形式參數(shù)是一個未定義大小的數(shù)組:void function(int param[])
二維數(shù)組:void function(int a[][3],int size)
函數(shù)返回?cái)?shù)組
C++ 不支持在函數(shù)外返回局部變量的地址,除非定義局部變量為 static 變量。
int * function(); int** function();
8.6 獲取數(shù)組的大小
動態(tài)創(chuàng)建(new)的基本數(shù)據(jù)類型數(shù)組無法取得數(shù)組大小
int a[3]; //第一種方法 cout<<sizeof(a)/sizeof(a[0])<<endl; //第二種方法 cout << end(a) - begin(a) << endl; //二維數(shù)組 int arr[5][3]; int lines = sizeof(arr) / sizeof(arr[0][0]); int row = sizeof(arr) / sizeof(arr[0]);//行 int col = lines / row;//列 cout << row << "::"<<col << endl; cout << end(arr) - begin(arr) << endl;//5行
9、函數(shù)
函數(shù)是實(shí)現(xiàn)模塊化程序設(shè)計(jì)思想的重要工具, C++程序中每一項(xiàng)操作基本都是由一個函數(shù)來實(shí)現(xiàn)的,C++程序中只能有一個主函數(shù)(main)
9.1 函數(shù)聲明與定義
- 函數(shù)類型-函數(shù)的返回值類型;函數(shù)名-必須符合C++標(biāo)識符命名規(guī)則,后面必須跟一對括號;函數(shù)體-實(shí)現(xiàn)函數(shù)功能的主題部分;參數(shù)列表-函數(shù)名后面的括號內(nèi),用于向函數(shù)傳遞數(shù)值或帶回?cái)?shù)值。
- 函數(shù)聲明中,參數(shù)名可以省略,參數(shù)類型和函數(shù)的類型不能省略。
- 函數(shù)聲明可以放在主調(diào)函數(shù)內(nèi)部,放在調(diào)用語句之前;也可以放在主調(diào)函數(shù)外,如果位于所有定義函數(shù)之前,后面函數(shù)定義順序任意,各個主調(diào)函數(shù)調(diào)用也不必再做聲明
- 當(dāng)函數(shù)定義在前,函數(shù)調(diào)用災(zāi)后,可以不用函數(shù)聲明。
后兩條總結(jié)一下就是:調(diào)用函數(shù)前,程序得知道有這個函數(shù),聲明就是提前讓程序知道有這么的玩意
函數(shù)聲明:
函數(shù)類型 函數(shù)名(參數(shù)列表); eg: int max(int a,int b);//聲明函數(shù)時,a,b可以省略 int max(int,int); void show();
函數(shù)定義:
函數(shù)類型 函數(shù)名(參數(shù)列表) { 函數(shù)體; } eg: int max(int a,int b) { int z; z = a>b?a:b; return z; }
9.2 函數(shù)的參數(shù)與返回值
- 形參:函數(shù)定義后面括號里的參數(shù),函數(shù)調(diào)用前不占內(nèi)存。
- 實(shí)參:函數(shù)調(diào)用括號里的參數(shù),可以是常量,變量或表達(dá)式等。
形參和實(shí)參必須個數(shù)相同、類型一致,順序一致
函數(shù)傳遞方式:傳值,指針,引用
關(guān)于指針和引用后面有詳細(xì)介紹。
//傳值-修改函數(shù)內(nèi)的形式參數(shù)對實(shí)際參數(shù)沒有影響 int add(int value) { value++; return value; } int main() { int v = 10; cout << "add() = " << add(v) << endl;//add() = 11 cout << "v = " << v << endl;//v = 10 return 0; } //指針-修改形式參數(shù)會影響實(shí)際參數(shù) int add(int* pValue) { (*pValue)++; return *pValue; } int main() { int v = 10; cout << "add() = " << add(&v) << endl;//add() = 11 cout << "v = " << v << endl;//v = 11 return 0; } //引用-修改形式參數(shù)會影響實(shí)際參數(shù) int add(int &value) { value++; return value; } int main() { int v = 10; cout << "add() = " << add(v) << endl;//add() = 11 cout << "v = " << v << endl;//v = 11 return 0; }
有默認(rèn)值參數(shù)的函數(shù)
int sum(int a, int b=2) { return (a + b); } int main () { cout << "Total value is :" << sum(100, 200);<< endl;//Total value is :300 cout << "Total value is :" << sum(100);<< endl;//Total value is :102 return 0; }
函數(shù)的返回值
- 返回值通過return給出,return后面跟表達(dá)式,且只能放回一個值;如果沒有表達(dá)式,可以不寫return;return后面的括號可有可無。
- return語句中的表達(dá)式類型應(yīng)與函數(shù)類型一致,否則自動轉(zhuǎn)換類型(函數(shù)類型決定返回值類型)
9.3 函數(shù)調(diào)用
函數(shù)可以單獨(dú)作為一個語句使用。有返回值的函數(shù),可將函數(shù)調(diào)用作為語句的一部分,利用返回值參與運(yùn)算。
函數(shù)調(diào)用形式:參數(shù)傳遞–>函數(shù)體執(zhí)行–>返回主調(diào)函數(shù)
函數(shù)名(實(shí)參列表); show();
函數(shù)的嵌套調(diào)用:
int a() { return 666; } int b(int sum) { return sum+a() } int main() { cout<<b(222)<<endl;//888 return 0; }
函數(shù)的遞歸調(diào)用:直接遞歸調(diào)用和間接遞歸調(diào)用
- 一個函數(shù)直接或間接遞歸調(diào)用該函數(shù)本身,稱為函數(shù)的遞歸調(diào)用
- 遞歸和回歸:原問題=>子問題 子問題的解=>原問題的解
//直接遞歸調(diào)用:求1+...n的值 int total(int sum) { if (sum == 1) { return 1; } return sum + total(sum - 1); } int main() { cout << "total = " << total(10) << endl;//total = 55 system("pause"); return 0; } //間接遞歸調(diào)用 int f2(); int f1() { ... f2() } int f2() { f1(); }
9.4 函數(shù)重載
同一個函數(shù)名對應(yīng)不同的函數(shù)實(shí)現(xiàn),每一類實(shí)現(xiàn)對應(yīng)著一個函數(shù)體,名字相同,功能相同,只是參數(shù)的類型或參數(shù)的個數(shù)不同。
多個同名函數(shù)只是函數(shù)類型(函數(shù)返回值類型)不同時,它們不是重載函數(shù)
int add(int a,int b) { return a+b; } double add(double a,double b) { return a+b; } int add(int a,int b,int c) { return a+b+c; }
9.5 內(nèi)聯(lián)(inline)函數(shù)
c++在編譯時可以講調(diào)用的函數(shù)代碼嵌入到主調(diào)函數(shù)中,這種嵌入到主調(diào)函數(shù)中的函數(shù)稱為內(nèi)聯(lián)函數(shù),又稱為內(nèi)嵌函數(shù)或內(nèi)置函數(shù)。
- 定義內(nèi)聯(lián)函數(shù)時,在函數(shù)定義和函數(shù)原型聲明時都使用inline,也可以只在其中一處使用,其效果一樣。
- 內(nèi)聯(lián)函數(shù)在編譯時用內(nèi)聯(lián)函數(shù)函數(shù)的函數(shù)體替換,所以不發(fā)生函數(shù)調(diào)用,不需要保護(hù)現(xiàn)場,恢復(fù)現(xiàn)場,節(jié)省了開銷。
- 內(nèi)聯(lián)函數(shù)增加了目標(biāo)程序的代碼量。因此,一般只將函數(shù)規(guī)模很小且使用頻繁的函數(shù)聲明為內(nèi)聯(lián)函數(shù)。
- 當(dāng)內(nèi)聯(lián)函數(shù)中實(shí)現(xiàn)過于復(fù)雜時,編譯器會將它作為一個普通函數(shù)處理,所以內(nèi)聯(lián)函數(shù)內(nèi)不能包含循環(huán)語句和switch語句。
內(nèi)聯(lián)函數(shù)格式如下:
inline 函數(shù)類型 函數(shù)名(形參列表) { 函數(shù)體; } inline int add(int a, int b) { return a + b; }
9.6 洞悉內(nèi)聯(lián)函數(shù)底層原理
1.使用Visual Studio 2015創(chuàng)建一個C++Win32控制臺程序,點(diǎn)擊項(xiàng)目->項(xiàng)目屬性設(shè)置內(nèi)聯(lián)函數(shù)優(yōu)化
2.編寫內(nèi)聯(lián)函數(shù)代碼,設(shè)置斷點(diǎn),debug啟動
#include <iostream> #include <string> using namespace std; inline int add(int a, int b) { return a + b;//斷點(diǎn)1 } int main() { int result = add(12, 34); cout << result << endl;//斷點(diǎn)2 return 0; }
3.調(diào)試->窗口->反匯編,然后就能看到編譯后的匯編程序
... int result = add(12, 34); 00B620DE mov eax,0Ch 00B620E3 add eax,22h //對eax中和22h中值進(jìn)行相加,賦值給eax 00B620E6 mov dword ptr [result],eax cout << result << endl; 00B620E9 mov esi,esp 00B620EB push offset std::endl<char,std::char_traits<char> > (0B610A5h) 00B620F0 mov edi,esp 00B620F2 mov eax,dword ptr [result] 00B620F5 push eax 00B620F6 mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0B6D098h)] 00B620FC call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0B6D0A8h)] 00B62102 cmp edi,esp 00B62104 call __RTC_CheckEsp (0B611C7h) 00B62109 mov ecx,eax 00B6210B call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0B6D0ACh)] 00B62111 cmp esi,esp 00B62113 call __RTC_CheckEsp (0B611C7h) return 0;
4.從匯編代碼中可以代碼編譯后內(nèi)聯(lián)函數(shù)直接嵌入到主函數(shù)中,并且斷點(diǎn)1不會執(zhí)行到,下面是沒使用內(nèi)聯(lián)函數(shù)(去掉inline關(guān)鍵字)的匯編代碼:
int result = add(12, 34); 00291A4E push 22h 00291A50 push 0Ch 00291A52 call add (02914D8h) //調(diào)用add函數(shù) 00291A57 add esp,8//移動堆棧指針esp,繼續(xù)執(zhí)行主函數(shù) 00291A5A mov dword ptr [result],eax cout << result << endl; 00291A5D mov esi,esp 00291A5F push offset std::endl<char,std::char_traits<char> > (02910A5h) 00291A64 mov edi,esp 00291A66 mov eax,dword ptr [result] 00291A69 push eax 00291A6A mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (029D098h)] cout << result << endl; 00291A70 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (029D0A8h)] 00291A76 cmp edi,esp 00291A78 call __RTC_CheckEsp (02911C7h) 00291A7D mov ecx,eax 00291A7F call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (029D0ACh)] 00291A85 cmp esi,esp 00291A87 call __RTC_CheckEsp (02911C7h) system("pause"); 00291A8C mov esi,esp 00291A8E push offset string "pause" (0299B30h) 00291A93 call dword ptr [__imp__system (029D1DCh)] 00291A99 add esp,4 00291A9C cmp esi,esp 00291A9E call __RTC_CheckEsp (02911C7h) return 0;
從以上代碼代碼可以看出,在主函數(shù)中調(diào)用(call)了add函數(shù)。
5.在內(nèi)聯(lián)函數(shù)中添加幾個循環(huán)后,編譯器就把內(nèi)聯(lián)函數(shù)當(dāng)做普通函數(shù)看待了,代碼如下:
inline int add(int a, int b) { int sum = 0; for (int i = 0; i < 100; i++) a++; for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) { sum++; } } return a + b; } int main() { int result = add(12, 34); cout << result << endl; return 0; }
int result = add(12, 34); 00181A4E push 22h 00181A50 push 0Ch 00181A52 call add (01814ECh) /// 00181A57 add esp,8 00181A5A mov dword ptr [result],eax cout << result << endl; 00181A5D mov esi,esp 00181A5F push offset std::endl<char,std::char_traits<char> > (01810A5h) 00181A64 mov edi,esp 00181A66 mov eax,dword ptr [result] 00181A69 push eax 00181A6A mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (018D098h)] cout << result << endl; 00181A70 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (018D0A8h)] 00181A76 cmp edi,esp 00181A78 call __RTC_CheckEsp (01811C7h) 00181A7D mov ecx,eax 00181A7F call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (018D0ACh)] 00181A85 cmp esi,esp 00181A87 call __RTC_CheckEsp (01811C7h) return 0; 00181AA3 xor eax,eax
10、字符串(string)
10.1 C風(fēng)格的字符串(字符數(shù)組)
C風(fēng)格的字符串實(shí)際上是使用 null 字符 ‘\0’ 終止的一維字符數(shù)組。
輸入字符串長度一定小于已定義的字符數(shù)組長度,最后一位是/0終止符號;不然輸出時無法知道在哪里結(jié)束。
字符數(shù)組的定義和初始化
char a[5] //字符個數(shù)不夠,補(bǔ)0; 字符個數(shù)超過報(bào)錯 char str[7] = {'h','e','i','r','e','n'}; char str[] = {'h','e','i','r','e','n'}; cin>>str;//輸入 輸入字符串長度一定小于已定義的字符數(shù)組長度 cout<<str;//輸出
字符串的處理函數(shù)
strcat(char s1[],const char s2[]);//將s2接到s1上 strcpy(char s1[],const char s2[]);//將s2復(fù)制到s1上 strcmp(const char s1[],const char s2[]);//比較s1,s2 s1>s2返回1 相等返回1,否則返回-1 strlen(char s[]);//計(jì)算字符串s的長度 字符串s的實(shí)際長度,不包括\0在內(nèi)
10.2 C++中的字符串(string)
字符串的定義和初始化
//定義 string 變量; string str1; //賦值 string str2 = "ShangHai"; string str3 = str2; str3[3] = '2';//對某個字符賦值 //字符串?dāng)?shù)組 string 數(shù)組名[常量表達(dá)式] string arr[3];
字符串的處理函數(shù)
#include <iostream> #include <algorithm> #include <string> string str;//生成空字符串 string s(str);//生成字符串為str的復(fù)制品 string s(str, strbegin,strlen);//將字符串str中從下標(biāo)strbegin開始、長度為strlen的部分作為字符串初值 string s(cstr, char_len);//以C_string類型cstr的前char_len個字符串作為字符串s的初值 string s(num ,c);//生成num個c字符的字符串 string s(str, stridx);//將字符串str中從下標(biāo)stridx開始到字符串結(jié)束的位置作為字符串初值 size()和length();//返回string對象的字符個數(shù) max_size();//返回string對象最多包含的字符數(shù),超出會拋出length_error異常 capacity();//重新分配內(nèi)存之前,string對象能包含的最大字符數(shù) >,>=,<,<=,==,!=//支持string與C-string的比較(如 str<”hello”)。 使用>,>=,<,<=這些操作符的時候是根據(jù)“當(dāng)前字符特性”將字符按字典順序進(jìn)行逐一得 比較,string (“aaaa”) <string(aaaaa)。 compare();//支持多參數(shù)處理,支持用索引值和長度定位子串來進(jìn)行比較。返回一個整數(shù)來表示比較結(jié)果,返回值意義如下:0:相等 1:大于 -1: push_back() insert( size_type index, size_type count, CharT ch );//在index位置插入count個字符ch insert( size_type index, const CharT* s );//index位置插入一個常量字符串 insert( size_type index, const CharT* s, size_type n);//index位置插入常量字符串 insert( size_type index, const basic_string& str );//index位置插入常量string中的n個字符 insert( size_type index, const basic_string& str, size_type index_str, size_type n);//index位置插入常量str的從index_str開始的n個字符 insert( size_type index, const basic_string& str,size_type index_str, size_type count = npos);//index位置插入常量str從index_str開始的count個字符,count可以表示的最大值為npos.這個函數(shù)不構(gòu)成重載 npos表示一個常數(shù),表示size_t的最大值,string的find函數(shù)如果未找到指定字符,返回的就是一個npos iterator insert( iterator pos, CharT ch ); iterator insert( const_iterator pos, CharT ch ); void insert( iterator pos, size_type n, CharT ch );//迭代器指向的pos位置插入n個字符ch iterator insert( const_iterator pos, size_type count, CharT ch );//迭代器指向的pos位置插入count個字符ch void insert( iterator pos, InputIt first, InputIt last ); iterator insert( const_iterator pos, InputIt first, InputIt last ); append() 和 + 操作符 //訪問string每個字符串 string s1("yuanrui"); // 調(diào)用一次構(gòu)造函數(shù) // 方法一: 下標(biāo)法 for( int i = 0; i < s1.size() ; i++ ) cout<<s1[i]; // 方法二:正向迭代器 for( string::iterator iter = s1.begin();; iter < s1.end() ; iter++) cout<<*iter; // 方法三:反向迭代器 for(string::reverse_iterator riter = s1.rbegin(); ; riter < s1.rend() ; riter++) cout<<*riter; iterator erase(iterator p);//刪除字符串中p所指的字符 iterator erase(iterator first, iterator last);//刪除字符串中迭代器區(qū)間[first,last)上所有字符 string& erase(size_t pos = 0, size_t len = npos);//刪除字符串中從索引位置pos開始的len個字符 void clear();//刪除字符串中所有字符 string& replace(size_t pos, size_t n, const char *s);//將當(dāng)前字符串從pos索引開始的n個字符,替換成字符串s string& replace(size_t pos, size_t n, size_t n1, char c); //將當(dāng)前字符串從pos索引開始的n個字符,替換成n1個字符c string& replace(iterator i1, iterator i2, const char* s);//將當(dāng)前字符串[i1,i2)區(qū)間中的字符串替換為字符串s //tolower()和toupper()函數(shù) 或者 STL中的transform算法 string s = "ABCDEFG"; for( int i = 0; i < s.size(); i++ ) s[i] = tolower(s[i]); transform(s.begin(),s.end(),s.begin(),::tolower); size_t find (constchar* s, size_t pos = 0) const;//在當(dāng)前字符串的pos索引位置開始,查找子串s,返回找到的位置索引,-1表示查找不到子串 size_t find (charc, size_t pos = 0) const;//在當(dāng)前字符串的pos索引位置開始,查找字符c,返回找到的位置索引,-1表示查找不到字符 size_t rfind (constchar* s, size_t pos = npos) const;//在當(dāng)前字符串的pos索引位置開始,反向查找子串s,返回找到的位置索引,-1表示查找不到子串 size_t rfind (charc, size_t pos = npos) const;//在當(dāng)前字符串的pos索引位置開始,反向查找字符c,返回找到的位置索引,-1表示查找不到字符 size_tfind_first_of (const char* s, size_t pos = 0) const;//在當(dāng)前字符串的pos索引位置開始,查找子串s的字符,返回找到的位置索引,-1表示查找不到字符 size_tfind_first_not_of (const char* s, size_t pos = 0) const;//在當(dāng)前字符串的pos索引位置開始,查找第一個不位于子串s的字符,返回找到的位置索引,-1表示查找不到字符 size_t find_last_of(const char* s, size_t pos = npos) const;//在當(dāng)前字符串的pos索引位置開始,查找最后一個位于子串s的字符,返回找到的位置索引,-1表示查找不到字符 size_tfind_last_not_of (const char* s, size_t pos = npos) const;//在當(dāng)前字符串的pos索引位置開始,查找最后一個不位于子串s的字符,返回找到的位置索引,-1表示查找不到子串 sort(s.begin(),s.end()); substr(pos,n);//返回字符串從下標(biāo)pos開始n個字符 strtok() char str[] = "I,am,a,student; hello world!"; const char *split = ",; !"; char *p2 = strtok(str,split); while( p2 != NULL ) { cout<<p2<<endl; p2 = strtok(NULL,split); }
11、指針和引用
11.1 指針
指針是一個變量,其值為另一個變量的地址。即內(nèi)存位置的直接地址。
聲明的一般形式:
數(shù)據(jù)類型是指針變量所指向的變量的數(shù)據(jù)類型,*表示其后的變量為指針變量
數(shù)據(jù)類型 *指針變量名; int *ip; //整型的指針 double *dp; //double 型的指針 float *fp; //浮點(diǎn)型的指針 char *ch; //字符型的指針
指針變量的初始化:
- &是取地址運(yùn)算符,&變量名表示變量的地址。
- 變量的數(shù)據(jù)類型必須于指針變量的數(shù)據(jù)類型一致。
- 為了安全起見,有時會把指針初始化為空指針(NULL或0)
數(shù)據(jù)類型 *指針變量名 = &變量名; *指針變量名 = &變量名; int a; int *p = &a; int *p2; p2 = &a;
指針變量的引用:
- & 取地址符 * 指針運(yùn)算符(間接運(yùn)算符),其后是指針變量,表示該指針變量所指向的變量。
- & *的優(yōu)先級是相同的,結(jié)合方式都是自左向右。比如 &*p等價于&(*p)。
int x = 3; int y; int *p; p = &x; y = *p;//y = a
指針運(yùn)算(地址運(yùn)算)
- 算術(shù)運(yùn)算(移動指針運(yùn)算):加減,自增自減。
- p+n運(yùn)算得到的地址是p+n*sizeof(數(shù)據(jù)類型)。
- 兩個相同數(shù)據(jù)類型的指針可以進(jìn)行加減運(yùn)算,一般用于數(shù)組的操作中。
- 關(guān)系運(yùn)算:指針指向同一串連續(xù)存儲單元才有意義,比如數(shù)組。與0比較,判斷是不是空指針。
- 賦值運(yùn)算:變量地址賦值給指針變量,數(shù)組元素地址賦值給指針變量,指針變量賦值給其他指針變量。
int arr[10],len; int *p1 = &arr[2],*p2 = &arr[5]; len = p2-p1;//arr[2] 和arr[5]之間的元素個數(shù) 3
new和delete運(yùn)算符
- new-為變量分配內(nèi)存空間;
- 可以通過判斷new返回的指針的值,判斷空間是否分配成功。
- delete-釋放空間
指針變量 = new 數(shù)據(jù)類型(初值); delete 指針變量; delete[] 指針變量;//釋放為多個變量分配的地址 int *ip; ip= new int(1); delete ip; int *ip; ip= new int[10]; for (int i = 0; i < 10;i++) { ip[i] = i; } delete[] ip; int a[3][4] = {0};
指針與數(shù)組
- 數(shù)組名是數(shù)組的首地址,eg:arr為arr[0]的地址。
- 訪問數(shù)組元素:arr[i],(arr+i),(p+i),p[i]
- 二維數(shù)組:arr+i == &arr[i],arr[i] == &arr[i][0] ,*(arr[i]+j) == arr[i][j]
- 指針訪問二維數(shù)組:指向二維數(shù)組元素,指向一維數(shù)組
- 數(shù)組指針:
數(shù)據(jù)類型 (*指針變量名) [m]
int arr[10]; int *p1 = arr;// *p1 = &arr[0]; int a[3][5] = { 0 }; int(*ap)[5]; ap = a; ap+1;//表示下一個一維數(shù)組
指針與字符串
- 字符串?dāng)?shù)組名:
char ch[] = "heiren";char *p = ch;
- 字符串:
char *p = "heiren";
- 指針賦值運(yùn)算:
char * p;p = "Heiren";
指針與函數(shù),指針可以作為函數(shù)的參數(shù),也可以作為函數(shù)的返回值。
11.2 引用
引用可以看做是數(shù)據(jù)的一個別名,通過這個別名和原來的名字都能夠找到這份數(shù)據(jù),類似于window中的快捷方式。
- 引用不占內(nèi)存空間,必須在定義的同時初始化,且不能再引用其他數(shù)據(jù)。
- 引用在定義時需要添加&,在使用時不能添加&,使用時添加&表示取地址
引用型變量聲明:數(shù)據(jù)類型 &引用名 = 變量名;
int a; int &b = a;//a和b表示相同的變量,具有相同的地址。
引用可以作為函數(shù)參數(shù),也可以作為函數(shù)返回值。
void swap(int &r1, int &r2) { int temp = r1; r1 = r2; r2 = temp; } int &add1(int &r) { r += 1; return r; } int main() { int a = 12; int b = add1(a); cout << a << " "<<b << endl;//13 13 return 0; }
將引用作為函數(shù)返回值時不能返回局部數(shù)據(jù)的引用,因?yàn)楫?dāng)函數(shù)調(diào)用完成后局部數(shù)據(jù)就會被銷毀。
函數(shù)在棧上運(yùn)行,函數(shù)掉用完,后面的函數(shù)調(diào)用會覆蓋之前函數(shù)的局部數(shù)據(jù)。
int &add1(int &r) { r += 1; int res = r; return res; } void test() { int xx = 123; int yy = 66; } int main() { int a = 12; int &b = add1(a); int &c = add1(a); test();//函數(shù)調(diào)用,覆蓋之前函數(shù)的局部數(shù)據(jù) cout << a << " "<<b <<" "<< c<<endl;//14 -858993460 -858993460 return 0; }
12、自定義數(shù)據(jù)類型
12.1 結(jié)構(gòu)體
結(jié)構(gòu)體可以包含不同數(shù)據(jù)類型的結(jié)構(gòu)。
定義結(jié)構(gòu)體的一般形式
struct 結(jié)構(gòu)體類型名 { 成員類型1 成員名1; 成員類型2 成員名2; ... ... 成員類型n 成員名n; };
結(jié)構(gòu)體變量名的定義和初始化:
//定義結(jié)構(gòu)體同時聲明結(jié)構(gòu)體變量名 struct 結(jié)構(gòu)體類型名 { 成員類型1 成員名1; 成員類型2 成員名2; ... ... 成員類型n 成員名n; }變量名1,變量名2,...變量名n; //先定義結(jié)構(gòu)體 [struct] 結(jié)構(gòu)體類型名 變量名; //直接定義 struct { 成員類型1 成員名1; 成員類型2 成員名2; ... ... 成員類型n 成員名n; }變量名1,變量名2,...變量名n; struct person { int year; int age; string name; }p1 = {2019,24,"heiren"}, p1 = { 2020,24,"heiren" }; struct person { int year; int age; string name; }; struct person p1 = { 2019,24,"heiren" }, p1 = { 2020,24,"heiren" }; struct { int year; int age; string name; }p1 = {2019,24,"heiren"}, p1 = { 2020,24,"heiren" };
結(jié)構(gòu)體變量的使用:
- 具有相同類型的結(jié)構(gòu)體變量可以進(jìn)行賦值運(yùn)算,但是不能輸入輸出
- 對結(jié)構(gòu)體變量的成員引用:
結(jié)構(gòu)體變量名.成員名
- 指向結(jié)構(gòu)體的指針變量引用格式:
指針變量名->成員名;
結(jié)構(gòu)體數(shù)組的定義,初始化和使用與結(jié)構(gòu)體變量、基本類型數(shù)組相似
struct person { int year; int age; string name; }p[2] ={ {2019,24,"heiren"}, { 2020,24,"heiren" }};//可以不指定數(shù)組元素個數(shù) p[1].age;
結(jié)構(gòu)體作為函數(shù)傳遞有三種:值傳遞,引用傳遞,指針傳遞
12.2 結(jié)構(gòu)體大小和字節(jié)對齊
現(xiàn)代計(jì)算機(jī)中內(nèi)存空間都是按照byte劃分的,從理論上講似乎對任何類型的變量的訪問可以從任何地址開始,但實(shí)際情況是在訪問特定類型變量的時候經(jīng)常在特 定的內(nèi)存地址訪問,這就需要各種類型數(shù)據(jù)按照一定的規(guī)則在空間上排列,而不是順序的一個接一個的排放,這就是對齊.
為什么需要字節(jié)對齊?各個硬件平臺對存儲空間的處理上有很大的不同。一些平臺對某些特定類型的數(shù)據(jù)只能從某些特定地址開始存取。比如有些平臺每次讀都是從偶地址開始,如果一個int型(假設(shè)為32位系統(tǒng))如果存放在偶地址開始的地方,那么一個讀周期就可以讀出這32bit,而如果存放在奇地址開始的地方,就需要2個讀周期,并對兩次讀出的結(jié)果的高低字節(jié)進(jìn)行拼湊才能得到該32bit數(shù)據(jù)。
三個個概念:
- 自身對齊值:數(shù)據(jù)類型本身的對齊值,結(jié)構(gòu)體或類的的自身對齊值是其成員中最大的那個值,例如char類型的自身對齊值是1,short類型是2;
- 指定對齊值:編譯器或程序員指定的對齊值,32位單片機(jī)的指定對齊值默認(rèn)是4;
- 有效對齊值:自身對齊值和指定對齊值中較小的那個。
字節(jié)對齊的三個準(zhǔn)則 - 結(jié)構(gòu)體變量的首地址能夠被其有效對齊值的大小所整除
- 結(jié)構(gòu)體的總大小為結(jié)構(gòu)體有效對齊值的整數(shù)倍。
- 結(jié)構(gòu)體每個成員相對于結(jié)構(gòu)體首地址的偏移量都是有效對齊值的整數(shù)倍。
可以通過#pragma pack(n)來設(shè)定變量以n字節(jié)對齊方式
舉個例子
//指定對齊值=8 struct st { // 空結(jié)構(gòu)體大小1 char c1;//1 char c2;//2 int i1;//8 int 起始地址按照字節(jié)對齊的原理應(yīng)該是它長度4的整數(shù)倍 char c3;//12 short s4;//12 short 起始地址按照字節(jié)對齊的原理應(yīng)該是它長度2的整數(shù)倍 12 + 2 = 12 double d;//24 double 起始地址按照字節(jié)對齊的原理應(yīng)該是它長度8的整數(shù)倍 12->16 + 8 = 24 char c4;//32 24 + 4 = 28 結(jié)構(gòu)體的總大小為8的整數(shù)倍 28->32 int i2;//32 28+4 = 32 int i3;//40 short s5;//40 }; cout << sizeof(st) << endl;//40 //指定對齊值=4 #pragma pack(4) struct st { //1 空結(jié)構(gòu)體大小1 char c1;//1 char c2;//2 int i1;//8 char c3;//12 short s4;//12 double d;//20 char c4;//24 int i2;//28 int i3;//32 short s5;//36 }s; cout << sizeof(st) << endl;//36
12.3 公用體(union)
幾個不同的變量共享同一個地址開始的內(nèi)存空間。
- 成員類型可以是基本數(shù)據(jù)類型,也可以是構(gòu)造數(shù)據(jù)類型。
- 公用體變量初始化時,只能對第一個成員賦值。
- 公用體變量所占的內(nèi)存長度等于最長的成員長度。
- 公用體變量在一個時刻只能一個成員發(fā)揮作用,賦值時,成員之間會互相覆蓋,最后一次被賦值的成員起作用。
定義
union 共同體類型名 { 成員類型1 成員名1; 成員類型2 成員名2; ... ... 成員類型n 成員名n; };
初始化
union data { int i; float f; char c; }x = {123}; union data { float f; int i; char c; }; data x = {12.3}; union { char c; int i; float f; }x = {'y‘};
引用
共同體變量名.成員名; union data { int i; float f; char c; }x = {12}; int main() { cout << x.i << " " << x.f << " " << x.c << endl;//12 1.68156e-44 x.c = 'c'; cout << x.i <<" "<< x.f << " " << x.c << endl;//99 1.38729e-43 c return 0; }
12.4 枚舉(enum)和typedef聲明
枚舉已經(jīng)在前面的章節(jié)介紹過,這里就不在贅述了。
typedef-為已存在的數(shù)據(jù)類型定義一個新的類型名稱,不能定義變量。
typedef聲明格式:typedef 類型名稱 類型標(biāo)識符;
typedef char *CP; typedef int INTEGER;
13、面向?qū)ο?/h2>
13.1 類
類也是一種數(shù)據(jù)類型。
類的聲明:
class 類名 { public: 公有數(shù)據(jù)成員; 公有成員函數(shù); private: 私有數(shù)據(jù)成員; 私有成員函數(shù); protected: 保護(hù)數(shù)據(jù)成員; 保護(hù)成員函數(shù); };
成員函數(shù)的定義:類內(nèi),類外,類外內(nèi)聯(lián)函數(shù)
//類外 返回類型 類名:成員函數(shù)名(參數(shù)列表) { 函數(shù)體; } //內(nèi)聯(lián)函數(shù):類外 inline 返回類型 類名:成員函數(shù)名(參數(shù)列表) { 函數(shù)體; }
內(nèi)聯(lián)函數(shù)的代碼會直接嵌入到主調(diào)函數(shù)中,可以節(jié)省調(diào)用時間,如果成員函數(shù)在類內(nèi)定義,自動為內(nèi)聯(lián)函數(shù)。
13.2 類成員的訪問權(quán)限以及類的封裝
- 和Java、C#不同的是,C++中public、private、protected只能修飾類的成員,不能修飾類,C++中的類沒有共有私有之分
- 類內(nèi)部沒有訪問權(quán)限的限制,都可以互相訪問。
- 在C++中用class定義的類中,其成員的默認(rèn)存取權(quán)限是private。
類外 | 派生類 | 類內(nèi) | |
---|---|---|---|
public | Y | Y | Y |
protected | N | Y | Y |
private | N | N | Y |
13.3 對象
//1.聲明類同時定義對象 class 類名 { 類體; }對象名列表; //2.先聲明類,再定義對象 類名 對象名(參數(shù)列表);//參數(shù)列表為空時,()可以不寫 //3. 不出現(xiàn)類名,直接定義對象 class { 類體; }對象名列表; //4.在堆上創(chuàng)建對象 Person p(123, "yar");//在棧上創(chuàng)建對象 Person *pp = new Person(234,"yar");//在堆上創(chuàng)建對象
注:不可以在定義類的同時對其數(shù)據(jù)成員進(jìn)行初始化,因?yàn)轭惒皇且粋€實(shí)體,不合法但是能編譯運(yùn)行
對象成員的引用:對象名.數(shù)據(jù)成員名 或者 對象名.成員函數(shù)名(參數(shù)列表)
13.4 構(gòu)造函數(shù)
是一種特殊的成員函數(shù),主要功能是為對象分配存儲空間,以及為類成員變量賦初值
- 構(gòu)造函數(shù)名必須與類名相同
- 沒有任何返回值和返回類型
- 創(chuàng)建對象自動調(diào)用,不需要用戶來調(diào)用,且只掉用一次
- 類沒有定義任何構(gòu)造函數(shù),編譯系統(tǒng)會自動為這個類生成一個默認(rèn)的無參構(gòu)造函數(shù)
構(gòu)造函數(shù)定義
//1.類中定義 2.類中聲明,類外定義 [類名::]構(gòu)造函數(shù)名(參數(shù)列表) { 函數(shù)體 }
創(chuàng)建對象
類名 對象名(參數(shù)列表);//參數(shù)列表為空時,()可以不寫
帶默認(rèn)參數(shù)的構(gòu)造函數(shù)
class Person { public: Person(int = 0,string = "張三"); void show(); private: int age; string name; }; Person::Person(int a, string s) { cout<<a<<" "<<s<<endl; age = a; name = s; } void Person::show() { cout << "age="<<age << endl; cout << "name=" <<name << endl; } int main() { Person p; //0 張三 Person p2(12);//12 張三 Person p3(123, "yar");//123 yar return 0; }
帶參數(shù)初始化表的構(gòu)造函數(shù)
類名::構(gòu)造函數(shù)名(參數(shù)列表):參數(shù)初始化表 { 函數(shù)體; } 參數(shù)初始化列表的一般形式: 參數(shù)名1(初值1),參數(shù)名2(初值2),...,參數(shù)名n(初值n) class Person { public: Person(int = 0,string = "張三"); void show(); private: int age; string name; }; Person::Person(int a, string s):age(a),name(s) { cout << a << " " << s << endl; }
構(gòu)造函數(shù)重載:構(gòu)造函數(shù)名字相同,參數(shù)個數(shù)和參數(shù)類型不一樣。
class Person { public: Person(); Person(int = 0,string = "張三"); Person(double,string); void show(); private: int age; double height; string name; }; ...
拷貝構(gòu)造函數(shù)
類名::類名(類名&對象名) { 函數(shù)體; } class Person { public: Person(Person &p);//聲明拷貝構(gòu)造函數(shù) Person(int = 0,string = "張三"); void show(); private: int age; string name; }; Person::Person(Person &p)//定義拷貝構(gòu)造函數(shù) { cout << "拷貝構(gòu)造函數(shù)" << endl; age = 0; name = "ABC"; } Person::Person(int a, string s):age(a),name(s) { cout << a << " " << s << endl; } int main() { Person p(123, "yar"); Person p2(p); p2.show(); return 0; } //輸出 123 yar 拷貝構(gòu)造函數(shù) age=0 name=ABC
13.5 析構(gòu)函數(shù)
是一種特殊的成員函數(shù),當(dāng)對象的生命周期結(jié)束時,用來釋放分配給對象的內(nèi)存空間愛你,并做一些清理的工作。
- 析構(gòu)函數(shù)名與類名必須相同。
- 析構(gòu)函數(shù)名前面必須加一個波浪號~。
- 沒有參數(shù),沒有返回值,不能重載。
- 一個類中只能有一個析構(gòu)函數(shù)。
- 沒有定義析構(gòu)函數(shù),編譯系統(tǒng)會自動為和這個類生成一個默認(rèn)的析構(gòu)函數(shù)。
析構(gòu)函數(shù)的定義:
//1.類中定義 2.類中聲明,類外定義 [類名::]~析構(gòu)函數(shù)名() { 函數(shù)體; }
13.6 對象指針
對象指針的聲明和使用
類名 *對象指針名; 對象指針 = &對象名; //訪問對象成員 對象指針->數(shù)據(jù)成員名 對象指針->成員函數(shù)名(參數(shù)列表) Person p(123, "yar"); Person* pp = &p; Person* pp2 = new Person(234,"yar") pp->show();
指向?qū)ο蟪蓡T的指針
數(shù)據(jù)成員類型 *指針變量名 = &對象名.數(shù)據(jù)成員名; 函數(shù)類型 (類名::*指針變量名)(參數(shù)列表); 指針變量名=&類名::成員函數(shù)名; (對象名.*指針變量名)(參數(shù)列表); Person p(123, "yar"); void(Person::*pfun)(); pfun = &Person::show; (p.*pfun)();
this指針
每個成員函數(shù)都有一個特殊的指針this,它始終指向當(dāng)前被調(diào)用的成員函數(shù)操作的對象
class Person { public: Person(int = 0,string = "張三"); void show(); private: int age; string name; }; Person::Person(int a, string s):age(a),name(s) { cout << a << " " << s << endl; } void Person::show() { cout << "age="<<this->age << endl; cout << "name=" <<this->name << endl; }
13.7 靜態(tài)成員
以關(guān)鍵字static開頭的成員為靜態(tài)成員,多個類共享。
- static 成員變量屬于類,不屬于某個具體的對象
- 靜態(tài)成員函數(shù)只能訪問類中靜態(tài)數(shù)據(jù)成員
靜態(tài)數(shù)據(jù)成員
//類內(nèi)聲明,類外定義 class xxx { static 數(shù)據(jù)類型 靜態(tài)數(shù)據(jù)成員名; } 數(shù)據(jù)類型 類名::靜態(tài)數(shù)據(jù)成員名=初值 //訪問 類名::靜態(tài)數(shù)據(jù)成員名; 對象名.靜態(tài)數(shù)據(jù)成員名; 對象指針名->靜態(tài)數(shù)據(jù)成員名;
靜態(tài)成員函數(shù)
//類內(nèi)聲明,類外定義 class xxx { static 返回值類型 靜態(tài)成員函數(shù)名(參數(shù)列表); } 返回值類型 類名::靜態(tài)成員函數(shù)名(參數(shù)列表) { 函數(shù)體; } //訪問 類名::靜態(tài)成員函數(shù)名(參數(shù)列表); 對象名.靜態(tài)成員函數(shù)名(參數(shù)列表); 對象指針名->靜態(tài)成員函數(shù)名(參數(shù)列表);
13.8 友元
借助友元(friend),可以使得其他類中得成員函數(shù)以及全局范圍內(nèi)得函數(shù)訪問當(dāng)前類得private成員。
友元函數(shù)
- 友元函數(shù)不是類的成員函數(shù),所以沒有this指針,必須通過參數(shù)傳遞對象。
- 友元函數(shù)中不能直接引用對象成員的名字,只能通過形參傳遞進(jìn)來的對象或?qū)ο笾羔榿硪迷搶ο蟮某蓡T。
//1.將非成員函數(shù)聲明為友元函數(shù) class Person { public: Person(int = 0,string = "張三"); friend void show(Person *pper);//將show聲明為友元函數(shù) private: int age; string name; }; Person::Person(int a, string s):age(a),name(s) { cout << a << " " << s << endl; } void show(Person *pper) { cout << "age="<< pper->age << endl; cout << "name=" << pper->name << endl; } int main() {; Person *pp = new Person(234,"yar"); show(pp); system("pause"); return 0; } //2.將其他類的成員函數(shù)聲明為友元函數(shù) //person中的成員函數(shù)可以訪問MobilePhone中的私有成員變量 class MobilePhone;//提前聲明 //聲明Person類 class Person { public: Person(int = 0,string = "張三"); void show(MobilePhone *mp); private: int age; string name; }; //聲明MobilePhone類 class MobilePhone { public: MobilePhone(); friend void Person::show(MobilePhone *mp); private: int year; int memory; string name; }; MobilePhone::MobilePhone() { year = 1; memory = 4; name = "iphone 6s"; } Person::Person(int a, string s):age(a),name(s) { cout << a << " " << s << endl; } void Person::show(MobilePhone *mp) { cout << mp->year << "年 " << mp->memory << "G " << mp->name << endl; } int main() { Person *pp = new Person(234,"yar"); MobilePhone *mp = new MobilePhone; pp->show(mp); system("pause"); return 0; }
友元類
當(dāng)一個類為另一個類的友元時,稱這個類為友元類。 友元類的所有成員函數(shù)都是另一個類中的友元成員。
語法形式:friend [class] 友元類名
- 類之間的友元關(guān)系不能傳遞
- 類之間的友元關(guān)系是單向的
- 友元關(guān)系不能被繼承
class HardDisk { public: HardDisk(); friend class Computer; private: int capacity; int speed; string brand; }; HardDisk::HardDisk():capacity(128),speed(0),brand("三星"){ } class Computer { public: Computer(HardDisk hd); void start(); private: string userName; string name; int ram; string cpu; int osType; HardDisk hardDisk; }; Computer::Computer(HardDisk hd):userName("yar"),name("YAR-PC"),ram(16),cpu("i7-4710"),osType(64) { cout << "正在創(chuàng)建computer..." << endl; this->hardDisk = hd; this->hardDisk.speed = 5400; cout << "硬盤轉(zhuǎn)動...speed = " << this->hardDisk.speed << "轉(zhuǎn)/分鐘" << endl; } void Computer::start() { cout << hardDisk.brand << " " << hardDisk.capacity << "G" << hardDisk.speed << "轉(zhuǎn)/分鐘" << endl; cout << "筆記本開始運(yùn)行..." << endl; } int main() { HardDisk hd; Computer cp(hd); cp.start(); system("pause"); return 0; }
13.9 類(class)與結(jié)構(gòu)體(struct)的區(qū)別
- 引入C語言的結(jié)構(gòu)體,是為了保證和c程序的兼容性。
- c語言中的結(jié)構(gòu)體不允許定義函數(shù)成員,且沒有訪問控制權(quán)限的屬性。
- c++為結(jié)構(gòu)體引入了成員函數(shù),訪問控制權(quán)限,繼承,多態(tài)等面向?qū)ο筇匦浴?/li>
- c語言中,空結(jié)構(gòu)體的大小為0,而C++中空結(jié)構(gòu)體大小為1。
- class中成員默認(rèn)是private,struct中的成員默認(rèn)是public。
- class繼承默認(rèn)是private繼承,而struct繼承默認(rèn)是public繼承。
- class可以使用模版,而struct不能。
舉個例子:
//結(jié)構(gòu)體默認(rèn)權(quán)限為public struct person { void show(); string name; int age; }; int main() { person p; p.name = "heiren"; p.age = 666; p.show(); cout <<"name="<< p.name <<" age="<< p.age << endl; system("pause"); return 0; }
將struct改為class,運(yùn)行報(bào)錯。
14、繼承和派生
14.1 繼承和派生概述
繼承就是再一個已有類的基礎(chǔ)上建立一個新類,已有的類稱基類或父類,新建立的類稱為派生類和子類;派生和繼承是一個概念,角度不同而已,繼承是兒子繼承父親的產(chǎn)業(yè),派生是父親把產(chǎn)業(yè)傳承給兒子。
一個基類可以派生出多個派生類,一個派生類可以繼承多個基類
派生類的聲明:
//繼承方式為可選項(xiàng),默認(rèn)為private,還有public,protected class 派生類名:[繼承方式]基類名 { 派生類新增加的成員聲明; };
繼承方式:
- public-基類的public成員和protected成員的訪問屬性保持不變,私有成員不可見。
- private-基類的public成員和protected成員成為private成員,只能被派生類的成員函數(shù)直接訪問,私有成員不可見。
- protected-基類的public成員和protected成員成為protected成員,只能被派生類的成員函數(shù)直接訪問,私有成員不可見。
繼承方式/基類成員 | public成員 | protected成員 | private成員 |
---|---|---|---|
public | public | protected | 不可見 |
protected | protected | protected | 不可見 |
private | private | private | 不可見 |
利用using關(guān)鍵字可以改變基類成員再派生類中的訪問權(quán)限;using只能修改基類中public和protected成員的訪問權(quán)限。
class Base { public: void show(); protected: int aa; double dd; }; void Base::show(){ } class Person:public Base { public: using Base::aa;//將基類的protected成員變成public using Base::dd;//將基類的protected成員變成public private: using Base::show;//將基類的public成員變成private string name; }; int main() { Person *p = new Person(); p->aa = 12; p->dd = 12.3; p->show();//出錯 delete p; return 0; }
派生類的構(gòu)造函數(shù)和析構(gòu)函數(shù)
- 先執(zhí)行基類的構(gòu)造函數(shù),隨后執(zhí)行派生類的構(gòu)造函數(shù)
- 先執(zhí)行派生類的析構(gòu)函數(shù),再執(zhí)行基類的析構(gòu)函數(shù)。
- 派生類的構(gòu)造函數(shù):
派生類名(總參數(shù)列表):基類名(基類參數(shù)列表),子對象名1(參數(shù)列表){構(gòu)造函數(shù)體;}
class Base { public: Base(int, double); ~Base(); private: int aa; double dd; }; Base::Base(int a, double d) :aa(a), dd(d) { cout << "Base Class 構(gòu)造函數(shù)!!!" << endl; } Base::~Base() { cout << "Base Class 析構(gòu)函數(shù)!!!" << endl; } class Person:public Base { public: Person(int,double,string); ~Person(); private: string name; }; Person::Person(int a,double d,string str):Base(a,d),name(str) { cout << "Person Class 構(gòu)造函數(shù)!!!" << endl; } Person::~Person() { cout << "Person Class 析構(gòu)函數(shù)!!!" << endl; } int main() { cout << "創(chuàng)建Person對象..." << endl; Person *p = new Person(1,2,"yar"); cout << "刪除Person對象...." << endl; delete p; system("pause"); return 0; }
14.2 多繼承
一個派生類同時繼承多個基類的行為。
多繼承容易讓代碼邏輯復(fù)雜、思路混亂,一直備受爭議,中小型項(xiàng)目中較少使用,后來的 Java、C#、PHP 等干脆取消了多繼承。
多重繼承派生類聲明的一般形式:
class 派生類名:繼承方式1 基類1,繼承方式2 基類2 { 派生類主體; };
多重繼承派生類的構(gòu)造函數(shù):
派生類名(總參數(shù)列表):基類名1(基類參數(shù)列表1),基類名2(基類參數(shù)列表2), 子對象名1,...(參數(shù)列表) { 構(gòu)造函數(shù)體; }`
二義性問題:多個基類中有同名成員,出現(xiàn)訪問不唯一的問題。
- 1.
類名::同名成員名;
- 2.派生類定義同名成員,訪問的就是派生類同名成員。
14.3 虛基類
c++引入虛基類使得派生類再繼承間接共同基類時只保留一份同名成員。
- 虛繼承的目的是讓某個類做出聲明,承諾愿意共享它的基類。其中,這個被共享的基類就稱為虛基類(Virtual Base Class)。
- 派生類的 同名成員 比虛基類的 優(yōu)先級更高
虛基類的聲明:class 派生類名:virtual 繼承方式 基類名
class A//虛基類 { protected: int a; }; class B: virtual public A { protected: int b; }; class C:virtual public A { protected: int c; }; class D:public B,public C { protected: int d; void show() { b = 123; c = 23; a = 1; } };
- 如果 B 或 C 其中的一個類定義了a,也不會有二義性,派生類的a 比虛基類的a 優(yōu)先級更高。
- 如果 B 和 C 中都定義了 a,那么D直接訪問a 將產(chǎn)生二義性問題。
應(yīng)用:c++中的iostream , istream , ostream,base_io
15、多態(tài)和虛函數(shù)
15.1 向上轉(zhuǎn)型
數(shù)據(jù)類型的轉(zhuǎn)換,編譯器會將小數(shù)部分直接丟掉(不是四舍五入)
int a = 66.9; printf("%d\n", a);//66 float b = 66; printf("%f\n", b);//66.000000
- 只能將將派生類賦值給基類(C++中稱為向上轉(zhuǎn)型): 派生類對象賦值給基類對象、將派生類指針賦值給基類指針、將派生類引用賦值給基類引用
- 派生類對象賦值給基類對象,舍棄派生類新增的成員;派生類指針賦值給基類指針,沒有拷貝對象的成員,也沒有修改對象本身的數(shù)據(jù),僅僅是改變了指針的指向;派生類引用賦值給基類引用,和指針的一樣。、
上轉(zhuǎn)型后通過基類的對象、指針、引用只能訪問從基類繼承過去的成員(包括成員變量和成員函數(shù)),不能訪問派生類新增的成員
15.2 多態(tài)
不同的對象可以使用同一個函數(shù)名調(diào)用不同內(nèi)容的函數(shù)。
- 靜態(tài)多態(tài)性-在程序編譯時系統(tǒng)就決定調(diào)用哪個函數(shù),比如函數(shù)重載和靜態(tài)多態(tài)性
- 動態(tài)多態(tài)性-在程序運(yùn)行過程中動態(tài)確定調(diào)用那個函數(shù),通過虛函數(shù)實(shí)現(xiàn)的。
15.3 虛函數(shù)
實(shí)現(xiàn)程序多態(tài)性的一個重要手段,使用基類對象指針訪問派生類對象的同名函數(shù)。
- 將基類中的函數(shù)聲明為虛函數(shù),派生類中的同名函數(shù)自動為虛函數(shù)。
- 聲明形式:
virtual 函數(shù)類型 函數(shù)名 (參數(shù)列表);
- 構(gòu)造函數(shù)不能聲明為虛函數(shù),析構(gòu)函數(shù)可以聲明為虛函數(shù)。
class A { public: virtual void show() { cout << "A show" << endl; } }; class B: public A { public: void show() { cout << "B show" << endl; } }; int main() { B b; b.show();//B show A *pA = &b; pA->show();//B show 如果show方法前沒用virtual聲明為虛函數(shù),這里會輸出A show system("pause"); return 0; }
15.4 純虛函數(shù)
在基類中不執(zhí)行具體的操作,只為派生類提供統(tǒng)一結(jié)構(gòu)的虛函數(shù),將其聲明為虛函數(shù)。
class A { public: virtual void show() = 0; }; class B: public A { public: void show() { cout << "B show" << endl; } };
抽象類:包含純虛函數(shù)的類稱為抽象類。由于純虛函數(shù)不能被調(diào)用,所以不能利用抽象類創(chuàng)建對象,又稱抽象基類。
16、運(yùn)算符重載
所謂重載,就是賦予新的含義。函數(shù)重載(Function Overloading)可以讓一個函數(shù)名有多種功能,在不同情況下進(jìn)行不同的操作。運(yùn)算符重載(Operator Overloading)也是一個道理,同一個運(yùn)算符可以有不同的功能。
運(yùn)算符重載是通過函數(shù)實(shí)現(xiàn)的,它本質(zhì)上是函數(shù)重載。
允許重載的運(yùn)算符
運(yùn)算符名稱 | 運(yùn)算符 |
---|---|
雙目算術(shù)運(yùn)算符 | +、-、*、、、% |
關(guān)系運(yùn)算符 | ==、!=、<、>、<=、>= |
邏輯運(yùn)算符 | ||、&&、! |
單目運(yùn)算符 | +、-、*(指針)、&(取地址) |
自增自減運(yùn)算符 | ++、– |
位運(yùn)算符 | |、&、-、……、<<、>> |
賦值運(yùn)算符 | =、+=、-=、*=、/=、%=、&=、!=、^=、<<= 、>>= |
空間分配和釋放 | new、delete、new[]、delete[] |
其他運(yùn)算符 | ()(函數(shù)調(diào)用) 、->(成員訪問)、->*(成員指針訪問)、,(逗號)、 |
不允許重載的運(yùn)算符
運(yùn)算符名稱 | 運(yùn)算符 |
---|---|
成員訪問運(yùn)算符 | . |
成員指針訪問運(yùn)算符 | . * |
域運(yùn)算符 | :: |
長度運(yùn)算符 | sizeof() |
條件運(yùn)算符 | ?: |
16.1 定義
重載運(yùn)算符遵循的規(guī)則:
- 不可以自己定義新的運(yùn)算符,只能對已有的C++運(yùn)算符重載。
- 不能改變運(yùn)算符運(yùn)算對象的個數(shù)。
- 不能改變運(yùn)算符的優(yōu)先級和結(jié)合性
- 應(yīng)與標(biāo)準(zhǔn)類型運(yùn)算功能相似,避免影響可讀性。
一般格式:
函數(shù)類型 operator運(yùn)算符(參數(shù)列表) { 函數(shù)體 } //舉個栗子:定義一個向量類,通過運(yùn)算符重載,可以用+進(jìn)行運(yùn)算。 class Vector3 { public: Vector3(); Vector3(double x,double y,double z); public: Vector3 operator+(const Vector3 &A)const; void display()const; private: double m_x; double m_y; double m_z; }; Vector3::Vector3() :m_x(0.0), m_y(0.0), m_z(0.0) {} Vector3::Vector3(double x, double y,double z) : m_x(x), m_y(y), m_z(z) {} //運(yùn)算符重載 Vector3 Vector3::operator+(const Vector3 &A) const { Vector3 B; B.m_x = this->m_x + A.m_x; B.m_y = this->m_y + A.m_y; B.m_z = this->m_z + A.m_z; return B; } void Vector3::display()const { cout<<"(" << m_x << "," << m_y << "," << m_z << ")" << endl; }
16.2 形式
運(yùn)算符重載的形式有兩種:重載函數(shù)作為類的成員,重載函數(shù)作為類的友元函數(shù)
根據(jù)運(yùn)算符操作數(shù)的不同:雙目運(yùn)算符作為類成員函數(shù),單目運(yùn)算符作為類的成員函數(shù),雙目運(yùn)算符作為類的友員函數(shù),單目運(yùn)算符作為類的友元函數(shù)。
- 雙目運(yùn)算符作為友元函數(shù)時需要制定兩個參數(shù)。
- 運(yùn)算符重載函數(shù)作為類成員函數(shù)可以顯式調(diào)用。
class Vector3 { public: Vector3(); Vector3(double x,double y,double z); public: Vector3 operator+(const Vector3 &A)const; Vector3 operator++(); friend Vector3 operator-(const Vector3 &v1, const Vector3 &v2); friend Vector3 operator--(Vector3 &v); void display()const; private: double m_x; double m_y; double m_z; }; Vector3::Vector3() :m_x(0.0), m_y(0.0), m_z(0.0) {} Vector3::Vector3(double x, double y,double z) : m_x(x), m_y(y), m_z(z) {} //運(yùn)算符重載 Vector3 Vector3::operator+(const Vector3 &A) const { Vector3 B; B.m_x = this->m_x + A.m_x; B.m_y = this->m_y + A.m_y; B.m_z = this->m_z + A.m_z; return B; } Vector3 Vector3::operator++() { this->m_x ++; this->m_y ++; this->m_z ++; return *this; } void Vector3::display()const { cout<<"(" << m_x << "," << m_y << "," << m_z << ")" << endl; } Vector3 operator-(const Vector3 &v1,const Vector3 &v2) { Vector3 B(v1.m_x - v2.m_x, v1.m_y - v2.m_y, v1.m_z - v2.m_z); return B; } Vector3 operator--( Vector3 &v) { v.m_x--; v.m_y--; v.m_z --; return v; } int main() { Vector3 v1(1, 2, 3); Vector3 v2(2, 3, 2); ++v1;//v1.operator++(); 作為類成員函數(shù)可以顯式調(diào)用 v1.display(); --v2; v2.display(); Vector3 v3 = v1 + v2;// v1.operator+(v2);作為類成員函數(shù)可以顯式調(diào)用 v3.display(); Vector3 v4 = v1 - v2; v4.display(); return 0; }
16.3 常用運(yùn)算符的重載
1.自增自減:
//前置運(yùn)算符 ++a --a operator++() operator--() operator++(Vector3 &v) operator--(Vector3 &v) //后置運(yùn)算符 a-- a++ operator++(int) operator--(int) operator++(Vector3 &v,int) operator--(Vector3 &v,int)
2.賦值運(yùn)算符:
String& String::operator=(String &s) { if(this!=&s) { delete[] str; int length = strlen(s.str); str = new char[length+1]; strcpy(str,s.str); } return (*this) }
3.輸入\輸出運(yùn)算符重載
friend ostream &operator<<( ostream &output, const Vector3 &v ) { output << "F : " <<v.m_x<< " I : " << v.m_y<<v.m_z; return output; } friend istream &operator>>( istream &input, Vector3 &v ) { input >> v.m_x>> v.m_y>>v.m_z; return input; }
16.4 實(shí)現(xiàn)類型轉(zhuǎn)換
- 不指定函數(shù)類型和參數(shù),返回值的類型由類型名來確定。
- 類型轉(zhuǎn)換函數(shù)只能作為成員函數(shù),不能作為友元函數(shù)。
類型轉(zhuǎn)換函數(shù)的一般形式:
operator 類型名() { 轉(zhuǎn)換語句; } class Vector3 { public: Vector3(); Vector3(double x,double y,double z); public: Vector3 operator+(const Vector3 &A)const; Vector3 operator++(); friend Vector3 operator-(const Vector3 &v1, const Vector3 &v2); friend Vector3 operator--(Vector3 &v,int); operator double() { return m_x + m_y + m_z; } void display()const; private: double m_x; double m_y; double m_z; }; int main() { Vector3 v1(1, 2, 3); double d = v1; cout << d << endl;//6 return 0; }
17、IO流
流-一連串連續(xù)不斷的數(shù)據(jù)集合。
17.1 流類和對象
- 輸入流-從輸入設(shè)備流向內(nèi)存的流。
- 輸出流-從內(nèi)存流出設(shè)備的流。
- 內(nèi)存緩沖區(qū)-用來存放流中的數(shù)據(jù)。
輸入輸出流程:鍵盤輸入=》鍵盤緩沖區(qū)=(回車觸發(fā))》程序的輸入緩沖區(qū)=》‘>>’提取數(shù)據(jù)
輸出緩沖區(qū)=(緩沖滿或endl)》‘<<’送到 顯示器顯示
輸入/輸出流類:
iostream:ios ,istream,ostream,iostream
fstream:ifstream,ofstream,fstream
strstream:istrstream,ostrstream,strstream
- istream 是用于輸入的流類,cin 就是該類的對象。
- ostream 是用于輸出的流類,cout 就是該類的對象。
- ifstream 是用于從文件讀取數(shù)據(jù)的類。
- ofstream 是用于向文件寫入數(shù)據(jù)的類。
- iostream 是既能用于輸入,又能用于輸出的類。
- fstream 是既能從文件讀取數(shù)據(jù),又能向文件寫入數(shù)據(jù)的類。
- istrstream 輸入字符串類
- ostrstream 輸出字符串類
- strstream 輸入輸出字符串流類
17.2 標(biāo)準(zhǔn)輸入輸出流
C++的輸入/輸出流庫(iostream)中定義了4個標(biāo)準(zhǔn)流對象:cin(標(biāo)準(zhǔn)輸入流-鍵盤),cout(標(biāo)準(zhǔn)輸出流-屏幕),cerr(標(biāo)準(zhǔn)錯誤流-屏幕),clog(標(biāo)準(zhǔn)錯誤流-屏幕)
- cerr 不使用緩沖區(qū),直接向顯示器輸出信息;而輸出到 clog 中的信息會先被存放到緩沖區(qū),緩沖區(qū)滿或者刷新時才輸出到屏幕。
- cout 是 ostream 類的對象,ostream 類的無參構(gòu)造函數(shù)和復(fù)制構(gòu)造函數(shù)都是私有的,所以無法定義 ostream 類的對象。
- 使用>>提取數(shù)據(jù)時,系統(tǒng)會跳過空格,制表符,換行符等空白字符。所以一組變量輸入值時,可用這些隔開。
- 輸入字符串,也是跳過空白字符,會在串尾加上字符串結(jié)束標(biāo)志\0。
int x; double y; cin>>x>>y; //輸入 22 66.0 兩個數(shù)之間可以用空格、制表符和回車分隔數(shù)據(jù) char str[10]; cin>>str;//hei ren 字符串中只有hei\0
輸入流中的成員函數(shù)
get函數(shù):cin.get(),cin.get(ch)(成功返回非0值,否則返回0),cin.get(字符數(shù)組(或字符指針),字符個數(shù)n,終止字符)
char c = cin.get();//獲取一個字符 while ((c = cin.get()) != EOF)//循環(huán)讀取,直到換行 { cout << c; } char ch; cin.get(ch); while (cin.get(ch))//讀取成功循環(huán) { cout << ch; } char arr[5]; cin.get(arr, 5, '\n');//輸入 heiren 結(jié)果 heir\0
getline函數(shù):cin.getline(字符數(shù)組(或字符指針),字符個數(shù)n,終止標(biāo)志字符)
讀取字符知道終止字符或者讀取n-1個字符,賦值給指定字符數(shù)組(或字符指針)
char arr0[30],arr1[30],arr2[40]; cin>>arr0;//遇到空格、制表符或回車結(jié)束 "Heiren" cin.getline(arr1,30);//字符數(shù)最多為29個,遇到回車結(jié)束 " Hello World" cin.getline(arr2,40,'*');//最多為39個,遇到*結(jié)束 "yar" //輸入 Heiren Hello World //yar*123
cin.peek() 不會跳過輸入流中的空格、回車符。在輸入流已經(jīng)結(jié)束的情況下,cin.peek() 返回 EOF。
ignore(int n =1, int delim = EOF)
int n; cin.ignore(5, 'Y');//跳過前5個字符或Y之前的字符,‘Y'優(yōu)先 cin >> n; //輸入1234567 -> 67 1234567Y345->345 //輸入2020.2.23 int year,month,day; cin >> year ; cin.ignore() >> month ; //用ignore跳過 '.' cin.ignore() >> day; cin.ignore(); //跳過行末 '\n' cout<< setfill('0') << setw(2) << month ;//設(shè)置填充字符'\0',輸出寬度2 cout << "-" << setw(2) << day << "-" << setw(4) << year << endl;
putback(char c),可以將一個字符插入輸入流的最前面。
輸出流對象
- 插入endl-輸出所有數(shù)據(jù),插入換行符,清空緩沖區(qū)
- \n-輸出換行,不清空緩沖區(qū)
- cout.put(參數(shù)) 輸出單個字符(可以時字符也可以是ASII碼)
格式化輸出
iomanip 中定義的流操作算子:
*不是算子的一部分,星號表示在沒有使用任何算子的情況下,就等效于使用了該算子,例如,在默認(rèn)情況下,整數(shù)是用十進(jìn)制形式輸出的,等效于使用了 dec 算子
流操縱算子 | 作 用 |
---|---|
*dec | 以十進(jìn)制形式輸出整數(shù) 常用 |
hex | 以十六進(jìn)制形式輸出整數(shù) |
oct | 以八進(jìn)制形式輸出整數(shù) |
fixed | 以普通小數(shù)形式輸出浮點(diǎn)數(shù) |
scientific | 以科學(xué)計(jì)數(shù)法形式輸出浮點(diǎn)數(shù) |
left | 左對齊,即在寬度不足時將填充字符添加到右邊 |
*right | 右對齊,即在寬度不足時將填充字符添加到左邊 |
setbase(b) | 設(shè)置輸出整數(shù)時的進(jìn)制,b=8、10 或 16 |
setw(w) | 指定輸出寬度為 w 個字符,或輸人字符串時讀入 w 個字符 |
setfill© | 在指定輸出寬度的情況下,輸出的寬度不足時用字符 c 填充(默認(rèn)情況是用空格填充) |
setprecision(n) | 設(shè)置輸出浮點(diǎn)數(shù)的精度為 n。在使用非 fixed 且非 scientific 方式輸出的情況下,n 即為有效數(shù)字最多的位數(shù),如果有效數(shù)字位數(shù)超過 n,則小數(shù)部分四舍五人,或自動變?yōu)榭茖W(xué)計(jì) 數(shù)法輸出并保留一共 n 位有效數(shù)字。在使用 fixed 方式和 scientific 方式輸出的情況下,n 是小數(shù)點(diǎn)后面應(yīng)保留的位數(shù)。 |
setiosflags(flag) | 將某個輸出格式標(biāo)志置為 1 |
resetiosflags(flag) | 將某個輸出格式標(biāo)志置為 0 |
boolapha | 把 true 和 false 輸出為字符串 不常用 |
*noboolalpha | 把 true 和 false 輸出為 0、1 |
showbase | 輸出表示數(shù)值的進(jìn)制的前綴 |
*noshowbase | 不輸出表示數(shù)值的進(jìn)制.的前綴 |
showpoint | 總是輸出小數(shù)點(diǎn) |
*noshowpoint | 只有當(dāng)小數(shù)部分存在時才顯示小數(shù)點(diǎn) |
showpos | 在非負(fù)數(shù)值中顯示 + |
*noshowpos | 在非負(fù)數(shù)值中不顯示 + |
*skipws | 輸入時跳過空白字符 |
noskipws | 輸入時不跳過空白字符 |
uppercase | 十六進(jìn)制數(shù)中使用 A~E。若輸出前綴,則前綴輸出 0X,科學(xué)計(jì)數(shù)法中輸出 E |
*nouppercase | 十六進(jìn)制數(shù)中使用 a~e。若輸出前綴,則前綴輸出 0x,科學(xué)計(jì)數(shù)法中輸出 e。 |
internal | 數(shù)值的符號(正負(fù)號)在指定寬度內(nèi)左對齊,數(shù)值右對 齊,中間由填充字符填充。 |
流操作算子使用方法:cout << hex << 12 << "," << 24;//c,18
setiosflags() 算子
setiosflags() 算子實(shí)際上是一個庫函數(shù),它以一些標(biāo)志作為參數(shù),這些標(biāo)志可以是在 iostream 頭文件中定義的以下幾種取值,它們的含義和同名算子一樣。
標(biāo) 志 | 作 用 |
---|---|
ios::left | 輸出數(shù)據(jù)在本域?qū)挿秶鷥?nèi)向左對齊 |
ios::right | 輸出數(shù)據(jù)在本域?qū)挿秶鷥?nèi)向右對齊 |
ios::internal | 數(shù)值的符號位在域?qū)拑?nèi)左對齊,數(shù)值右對齊,中間由填充字符填充 |
ios::dec | 設(shè)置整數(shù)的基數(shù)為 10 |
ios::oct | 設(shè)置整數(shù)的基數(shù)為 8 |
ios::hex | 設(shè)置整數(shù)的基數(shù)為 16 |
ios::showbase | 強(qiáng)制輸出整數(shù)的基數(shù)(八進(jìn)制數(shù)以 0 開頭,十六進(jìn)制數(shù)以 0x 打頭) |
ios::showpoint | 強(qiáng)制輸出浮點(diǎn)數(shù)的小點(diǎn)和尾數(shù) 0 |
ios::uppercase | 在以科學(xué)記數(shù)法格式 E 和以十六進(jìn)制輸出字母時以大寫表示 |
ios::showpos | 對正數(shù)顯示“+”號 |
ios::scientific | 浮點(diǎn)數(shù)以科學(xué)記數(shù)法格式輸出 |
ios::fixed | 浮點(diǎn)數(shù)以定點(diǎn)格式(小數(shù)形式)輸出 |
ios::unitbuf | 每次輸出之后刷新所有的流 |
ios::stdio | 每次輸出之后清除 stdout, stderr |
多個標(biāo)志可以用|運(yùn)算符連接,表示同時設(shè)置。例如:
cout << setiosflags(ios::scientific|ios::showpos) << 12.34;//+1.234000e+001
如果兩個相互矛盾的標(biāo)志同時被設(shè)置,結(jié)果可能就是兩個標(biāo)志都不起作用,應(yīng)該用 resetiosflags 清除原先的標(biāo)志
cout << setiosflags(ios::fixed) << 12.34 << endl; cout << resetiosflags(ios::fixed) << setiosflags(ios::scientific | ios::showpos) << 12.34 << endl;
ostream 類中的成員函數(shù):
成員函數(shù) | 作用相同的流操縱算子 | 說明 |
---|---|---|
precision(n) | setprecision(n) | 設(shè)置輸出浮點(diǎn)數(shù)的精度為 n。 |
width(w) | setw(w) | 指定輸出寬度為 w 個字符。 |
fill© | setfill © | 在指定輸出寬度的情況下,輸出的寬度不足時用字符 c 填充(默認(rèn)情況是用空格填充)。 |
setf(flag) | setiosflags(flag) | 將某個輸出格式標(biāo)志置為 1。 |
unsetf(flag) | resetiosflags(flag) | 將某個輸出格式標(biāo)志置為 0。 |
setf 和 unsetf 函數(shù)用到的flag,與 setiosflags 和 resetiosflags 用到的完全相同。
cout.setf(ios::scientific); cout.precision(8); cout << 12.23 << endl;//1.22300000e+001
18、文件操作
文件-指存儲在外部介質(zhì)上的數(shù)據(jù)集合,文件按照數(shù)據(jù)的組織形式不一樣,分為兩種:ASCII文件(文本/字符),二進(jìn)制文件(內(nèi)部格式/字節(jié))
ASCII文件輸出還是二進(jìn)制文件,數(shù)據(jù)形式一樣,對于數(shù)值數(shù)據(jù),輸出不同
18.1 文件類和對象
C++ 標(biāo)準(zhǔn)類庫中有三個類可以用于文件操作,它們統(tǒng)稱為文件流類。這三個類是:
- ifstream:輸入流類,用于從文件中讀取數(shù)據(jù)。
- ofstream:輸出流類,用于向文件中寫人數(shù)據(jù)。
- fstream:輸入/輸出流類,既可用于從文件中讀取數(shù)據(jù),又可用于 向文件中寫人數(shù)據(jù)。
文件流對象定義:
#include <fstream> ifstream in; ofstream out; fstream inout;
18.2 打開文件
打開文件的目的:建立對象與文件的關(guān)聯(lián),指明文件使用方式
打開文件的兩種方式:open函數(shù)和構(gòu)造函數(shù)
open函數(shù):void open(const char* szFileName, int mode);
模式標(biāo)記 | 適用對象 | 作用 |
---|---|---|
ios::in | ifstream fstream | 打開文件用于讀取數(shù)據(jù)。如果文件不存在,則打開出錯。 |
ios::out | ofstream fstream | 打開文件用于寫入數(shù)據(jù)。如果文件不存在,則新建該文件;如 果文件原來就存在,則打開時清除原來的內(nèi)容。 |
ios::app | ofstream fstream | 打開文件,用于在其尾部添加數(shù)據(jù)。如果文件不存在,則新建該文件。 |
ios::ate | ifstream | 打開一個已有的文件,并將文件讀指針指向文件末尾(讀寫指 的概念后面解釋)。如果文件不存在,則打開出錯。 |
ios:: trunc | ofstream | 單獨(dú)使用時與 ios:: out 相同。 |
ios::binary | ifstream ofstream fstream | 以二進(jìn)制方式打開文件。若不指定此模式,則以文本模式打開。 |
ios::in | ios::out | fstream | 打開已存在的文件,既可讀取其內(nèi)容,也可向其寫入數(shù)據(jù)。文件剛打開時,原有內(nèi)容保持不變。如果文件不存在,則打開出錯。 |
ios::in | ios::out | ofstream | 打開已存在的文件,可以向其寫入數(shù)據(jù)。文件剛打開時,原有內(nèi)容保持不變。如果文件不存在,則打開出錯。 |
ios::in | ios::out | ios::trunc | fstream | 打開文件,既可讀取其內(nèi)容,也可向其寫入數(shù)據(jù)。如果文件本來就存在,則打開時清除原來的內(nèi)容;如果文件不存在,則新建該文件。 |
ios::binary 可以和其他模式標(biāo)記組合使用,例如:
- ios::in | ios::binary表示用二進(jìn)制模式,以讀取的方式打開文件。
- ios::out | ios::binary表示用二進(jìn)制模式,以寫入的方式打開文件。
流類的構(gòu)造函數(shù)
eg:ifstream::ifstream (const char* szFileName, int mode = ios::in, int);
#include <iostream> #include <fstream> using namespace std; int main() { ifstream inFile("c:\\tmp\\test.txt", ios::in); if (inFile) inFile.close(); else cout << "test.txt doesn't exist" << endl; ofstream oFile("test1.txt", ios::out); if (!oFile) cout << "error 1"; else oFile.close(); fstream oFile2("tmp\\test2.txt", ios::out | ios::in); if (!oFile2) cout << "error 2"; else oFile.close(); return 0; }
18.3 文本文件的讀寫
對于文本文件,可以使用 cin、cout 讀寫。
eg:編寫一個程序,將文件 i.txt 中的整數(shù)倒序輸出到 o.txt。(12 34 56 78 90 -> 90 78 56 34 12)
#include <iostream> #include <fstream> using namespace std; int arr[100]; int main() { int num = 0; ifstream inFile("i.txt", ios::in);//文本模式打開 if (!inFile) return 0;//打開失敗 ofstream outFile("o.txt",ios::out); if (!outFile) { outFile.close(); return 0; } int x; while (inFile >> x) arr[num++] = x; for (int i = num - 1; i >= 0; i--) outFile << arr[i] << " "; inFile.close(); outFile.close(); return 0; }
18.4 二進(jìn)制文件的讀寫
- 用文本方式存儲信息不但浪費(fèi)空間,而且不便于檢索。
- 二進(jìn)制文件中,信息都占用 sizeof(對象名) 個字節(jié);文本文件中類的成員數(shù)據(jù)所占用的字節(jié)數(shù)不同,占用空間一般比二進(jìn)制的大。
ostream::write 成員函數(shù):ostream & write(char* buffer, int count);
class Person { public: char m_name[20]; int m_age; }; int main() { Person p; ofstream outFile("o.bin", ios::out | ios::binary); while (cin >> p.m_name >> p.m_age) outFile.write((char*)&p, sizeof(p));//強(qiáng)制類型轉(zhuǎn)換 outFile.close(); //heiren 燙燙燙燙燙燙啼 return 0; }
istream::read 成員函數(shù):istream & read(char* buffer, int count);
Person p; ifstream inFile("o.bin", ios::in | ios::binary); //二進(jìn)制讀方式打開 if (!inFile) return 0;//打開文件失敗 while (inFile.read((char *)&p, sizeof(p))) cout << p.m_name << " " << p.m_age << endl; inFile.close();
文件流類的 put 和 get 成員函數(shù)
#include <iostream> #include <fstream> using namespace std; int main() { ifstream inFile("a.txt", ios::binary | ios::in); if (!inFile) return 0; ofstream outFile("b.txt", ios::binary | ios::out); if (!outFile) { inFile.close(); return 0; } char c; while (inFile.get(c)) //每讀一個字符 outFile.put(c); //寫一個字符 outFile.close(); inFile.close(); return 0; }
一個字節(jié)一個字節(jié)地讀寫,不如一次讀寫一片內(nèi)存區(qū)域快。每次讀寫的字節(jié)數(shù)最好是 512 的整數(shù)倍
18.5 移動和獲取文件讀寫指針
- ifstream 類和 fstream 類有 seekg 成員函數(shù),可以設(shè)置文件讀指針的位置;
- ofstream 類和 fstream 類有 seekp 成員函數(shù),可以設(shè)置文件寫指針的位置。
- ifstream 類和 fstream 類還有 tellg 成員函數(shù),能夠返回文件讀指針的位置;
- ofstream 類和 fstream 類還有 tellp 成員函數(shù),能夠返回文件寫指針的位置。
函數(shù)原型
ostream & seekp (int offset, int mode); istream & seekg (int offset, int mode); //mode有三種:ios::beg-開頭往后offset(>=0)字節(jié) ios::cur-當(dāng)前往前(<=0)/后(>=0)offset字節(jié) ios::end-末尾往前(<=0)offect字節(jié) int tellg(); int tellp(); //seekg 函數(shù)將文件讀指針定位到文件尾部,再用 tellg 函數(shù)獲取文件讀指針的位置,此位置即為文件長度
舉個栗子:折半查找文件,name等于“Heiren”
#include <iostream> #include <fstream> //#include <vector> //#include<cstring> using namespace std; class Person { public: char m_name[20]; int m_age; }; int main() { Person p; ifstream ioFile("p.bin", ios::in | ios::out);//用既讀又寫的方式打開 if (!ioFile) return 0; ioFile.seekg(0, ios::end); //定位讀指針到文件尾部,以便用以后tellg 獲取文件長度 int L = 0, R; // L是折半查找范圍內(nèi)第一個記錄的序號 // R是折半查找范圍內(nèi)最后一個記錄的序號 R = ioFile.tellg() / sizeof(Person) - 1; do { int mid = (L + R) / 2; ioFile.seekg(mid *sizeof(Person), ios::beg); ioFile.read((char *)&p, sizeof(p)); int tmp = strcmp(p.m_name, "Heiren"); if (tmp == 0) { cout << p.m_name << " " << p.m_age; break; } else if (tmp > 0) R = mid - 1; else L = mid + 1; } while (L <= R); ioFile.close(); system("pause"); return 0; }
18.6 文本文件和二進(jìn)制文件打開方式的區(qū)別
- UNIX/Linux 平臺中,用文本方式或二進(jìn)制方式打開文件沒有任何區(qū)別。
- 在 UNIX/Linux 平臺中,文本文件以\n(ASCII 碼為 0x0a)作為換行符號;而在 Windows 平臺中,文本文件以連在一起的\r\n(\r的 ASCII 碼是 0x0d)作為換行符號。
- 在 Windows 平臺中,如果以文本方式打開文件,當(dāng)讀取文件時,系統(tǒng)會將文件中所有的\r\n轉(zhuǎn)換成一個字符\n,如果文件中有連續(xù)的兩個字節(jié)是 0x0d0a,則系統(tǒng)會丟棄前面的 0x0d 這個字節(jié),只讀入 0x0a。當(dāng)寫入文件時,系統(tǒng)會將\n轉(zhuǎn)換成\r\n寫入。
用二進(jìn)制方式打開文件總是最保險的。
19、泛型和模板
- 泛型程序設(shè)計(jì)在實(shí)現(xiàn)時不指定具體要操作的數(shù)據(jù)的類型的程序設(shè)計(jì)方法的一種算法,指的是算法只要實(shí)現(xiàn)一遍,就能適用于多種數(shù)據(jù)類型,優(yōu)勢在于代碼復(fù)用,減少重復(fù)代碼的編寫。
- 模板是泛型的基礎(chǔ),是創(chuàng)建泛型類或函數(shù)的藍(lán)圖或公式。
19.1 函數(shù)模板
函數(shù)模板的一般形式:
template<class T>或template<typename T> 函數(shù)類型 函數(shù)名(參數(shù)列表) { 函數(shù)體; } template<class T1,class T2,...>//class可以換成typename 函數(shù)類型 函數(shù)名(參數(shù)列表) { 函數(shù)體; } //舉個栗子 template<class T> T max(T a, T b) { return a > b ? a : b; } int main() { cout <<"max value is "<< max(12,34) << endl;//34 cout << "max value is " << max(12.4, 13.6) << endl;//13.6 cout << "max value is " << max(12.4, 13) << endl;//error 沒有與參數(shù)列表匹配的 函數(shù)模板 "max" 實(shí)例參數(shù)類型為:(double, int) return 0; }
19.2 類模板
聲明了類模板,就可以將類型參數(shù)用于類的成員函數(shù)和成員變量了。換句話說,原來使用 int、float、char 等內(nèi)置類型的地方,都可以用類型參數(shù)來代替。
類模板的一般形式:
template<class T>//class可以換成typename 模板頭 class 類名 { 函數(shù)定義; }; //多個類型參數(shù)和函數(shù)模板類似,逗號隔開
舉個栗子:
template<typename T1, typename T2> // 模板頭 沒有分號 class Point { public: Point(T1 x, T2 y) : x(x), y(y) { } public: T1 getX() const; //成員函數(shù)后加const,聲明該函數(shù)內(nèi)部不會改變成員變量的值 void setX(T1 x); T2 getY() const; void setY(T2 y); private: T1 x; T2 y; }; template<typename T1, typename T2> //模板頭 T1 Point<T1, T2>::getX() const { return x; } template<typename T1, typename T2> void Point<T1, T2>::setX(T1 x) { x = x; } template<typename T1, typename T2> T2 Point<T1, T2>::getY() const { return y; } template<typename T1, typename T2> void Point<T1, T2>::setY(T2 y) { y = y; } int main() { Point<int, double> p1(66, 20.5); Point<int, char*> p2(10, "東經(jīng)33度"); Point<char*, char*> *p3 = new Point<char*, char*>("西經(jīng)12度", "北緯66度"); cout << "x=" << p1.getX() << ", y=" << p1.getY() << endl; cout << "x=" << p2.getX() << ", y=" << p2.getY() << endl; cout << "x=" << p3->getX() << ", y=" << p3->getY() << endl; return 0; }
19.3 typename 和 class 的區(qū)別
在模板引入 c++ 后,采用class來定義模板參數(shù)類型,后來為了避免 class 在聲明類和模板的使用可能給人帶來混淆,所以引入了 typename 這個關(guān)鍵字。
- 模板定義語法中關(guān)鍵字 class 與 typename 的作用完全一樣。
- 不同的是typename 還有另外一個作用為:使用嵌套依賴類型(nested depended name)
class MyClass { public: typedef int LengthType; LengthType getLength() const { return this->length; } void setLength(LengthType length) { this->length = length; } private: LengthType length; }; template<class T> void MyMethod(T myclass) { //告訴 c++ 編譯器,typename 后面的字符串為一個類型名稱,而不是成員函數(shù)或者成員變量 typedef typename T::LengthType LengthType; // LengthType length = myclass.getLength(); cout << "length = " <<length<< endl; } int main() { MyClass my; my.setLength(666); MyMethod(my);//length = 666 return 0; }
19.4 強(qiáng)弱類型語言和c++模板的那點(diǎn)貓膩
計(jì)算機(jī)編程語言可以根據(jù)在 "定義變量時是否要顯式地指明數(shù)據(jù)類型"可以分為強(qiáng)類型語言和弱類型語言。
強(qiáng)類型語言-在定義變量時需要顯式地指明數(shù)據(jù)類型,為變量指明某種數(shù)據(jù)類型后就不能賦予其他類型的數(shù)據(jù)了,除非經(jīng)過強(qiáng)制類型轉(zhuǎn)換或隱式類型轉(zhuǎn)換。典型的強(qiáng)類型語言有 C/C++、Java、C# 等。
int a = 123; //不轉(zhuǎn)換 a = 12.89; //隱式轉(zhuǎn)換 12(舍去小數(shù)部分) a = (int)"heiren,HelloWorld"; //強(qiáng)制轉(zhuǎn)換(得到字符串的地址) 不同類型之間轉(zhuǎn)換需要強(qiáng)制
//Java 對類型轉(zhuǎn)換的要求比 C/C++ 更為嚴(yán)格,隱式轉(zhuǎn)換只允許由低向高轉(zhuǎn),由高向低轉(zhuǎn)必須強(qiáng)制轉(zhuǎn)換。 int a = 100; //不轉(zhuǎn)換 a = (int)12.34; //強(qiáng)制轉(zhuǎn)換(直接舍去小數(shù)部分,得到12)
弱類型語言-在定義變量時不需要顯式地指明數(shù)據(jù)類型,編譯器(解釋器)會根據(jù)賦給變量的數(shù)據(jù)自動推導(dǎo)出類型,并且可以賦給變量不同類型的數(shù)據(jù)。典型的弱類型語言有 JavaScript、Python、PHP、Ruby、Shell、Perl 等。
var a = 100; //賦給整數(shù) a = 12.34; //賦給小數(shù) a = "heiren,HelloWorld"; //賦給字符串 a = new Array("JavaScript","React","JSON"); //賦給數(shù)組
強(qiáng)類型語言在編譯期間就能檢測某個變量的操作是否正確,因?yàn)樽兞康念愋褪冀K哦都市確定的,加快了程序的運(yùn)行;對于弱類型的語言,變量的類型可以隨時改變,編譯器在編譯期間能確定變量的類型,只有等到程序運(yùn)行后、賦值后才能確定變量當(dāng)前是什么類型,所以傳統(tǒng)的編譯對弱類型語言意義不大。
- 解釋型語言-弱類型往往是解釋型語言,邊執(zhí)行邊編譯
- 編譯型語言-先編譯后執(zhí)行。
強(qiáng)類型語言較為嚴(yán)謹(jǐn),在編譯時就能發(fā)現(xiàn)很多錯誤,適合開發(fā)大型的、系統(tǒng)級的、工業(yè)級的項(xiàng)目;而弱類型語言較為靈活,編碼效率高,部署容易,學(xué)習(xí)成本低,在 Web 開發(fā)中大顯身手。另外,強(qiáng)類型語言的 IDE 一般都比較強(qiáng)大,代碼感知能力好,提示信息豐富;而弱類型語言一般都是在編輯器中直接書寫代碼。
C++模板退出的動力來源是對數(shù)據(jù)結(jié)構(gòu)的封裝:數(shù)據(jù)結(jié)構(gòu)關(guān)注的是數(shù)據(jù)的存儲以及對其的增刪改查操作,C++開發(fā)者們想封裝這些結(jié)構(gòu),但是這些結(jié)構(gòu)中數(shù)據(jù)成分的類型無法提前預(yù)測,于是模板誕生了。
STL(Standard Template Library,標(biāo)準(zhǔn)模板庫)就是c++對數(shù)據(jù)結(jié)構(gòu)封裝后的稱呼。
20、命名空間和異常處理
20.1 命名空間
命名空間實(shí)際上是由用戶自己命名的一塊內(nèi)存區(qū)域,用戶可以根據(jù)需要指定一個有名字的空間區(qū)域,每個命名空間都有一個作用域,將一些全局實(shí)體放在該命名空間中,就與其他全局實(shí)體分割開來。
命名空間定義的一般形式:
namespace [命名空間名]//名省略時,表示無名的命名空間 { 命名空間成員; }
命名空間成員的引用:命名空間名::命名空間成員名
使用命名空間別名:namespace 別名 = 命名空間名
使用using聲明命名空間成員的格式:using 命名空間名::命名空間成員名;
使用using聲明命名空間的全部成員:using namespace 命名空間名;
- using聲明后,在using語句所在的作用域中使用該命名空間成員時,不必再用命名空間名加以限定。
- 標(biāo)準(zhǔn)C++庫的所有標(biāo)識符(包括函數(shù)、類、對象和類模板)都是在一個名為std的命名空間中定義的。
- 無名的命名空間,只在本文件的作用域內(nèi)有效。
20.2 異常處理
異常就是程序在執(zhí)行過程中,由于使用環(huán)境變化和用戶操作等原因產(chǎn)生的錯誤,從而影響程序的運(yùn)行。
- 程序中常見的錯誤:語法錯誤,運(yùn)行錯誤
- 異常處理機(jī)制的組成:檢查異常(try)、拋出異常(throw)、捕獲并處理異常(catch)
異常處理語句:
- 被檢查的語句必須放在try后面的{}中,否則不起作用,{}不能省略。
- 進(jìn)行異常處理的語句必須放在catch后面的{}中,catch后()中的異常信息類型不能省略,變量名可以省略。
- catch語句塊不能單獨(dú)使用,必須和try語句塊作為整體出現(xiàn)。
- 在try-catch結(jié)構(gòu)中,只能有一個try,但可以有多個catch.
- catch(…)通常放在最后,可以捕獲任何類型的異常信息。
try { 被檢查的語句(可以有多個throw語句); } catch(異常信息類型 [變量名]) { }
throw語句:thow 變量或表達(dá)式;
throw放在try中,也可以單獨(dú)使用,向上一層函數(shù)尋找try-catch,沒有找到系統(tǒng)就會調(diào)用系統(tǒng)函數(shù)terminate,使程序終止運(yùn)行。
try { throw 123; } catch (int a) { cout << a << endl;//123 }
c++中沒有finally
類的異常處理,當(dāng)在try中定義了類對象,在try中拋出異常前創(chuàng)建的對象將被自動釋放。
異常規(guī)范-描述了一個函數(shù)允許拋出那些異常類型。
- 異常規(guī)范應(yīng)同時出現(xiàn)在函數(shù)聲明和函數(shù)定義中。
- 如果沒有異常規(guī)范,可以拋出任何類型的異常。
異常規(guī)范的一般形式:函數(shù)類型 函數(shù)名(參數(shù)類型)throw ([異常類型1,異常類型2,...])
float fun(float float)throw(int,float,double);
C++標(biāo)準(zhǔn)異常
異常 | 描述 |
---|---|
std::exception | 該異常是所有標(biāo)準(zhǔn) C++ 異常的父類。 |
std::bad_alloc | 該異常可以通過 new 拋出。 |
std::bad_cast | 該異??梢酝ㄟ^ dynamic_cast 拋出。 |
std::bad_exception | 這在處理 C++ 程序中無法預(yù)期的異常時非常有用。 |
std::bad_typeid | 該異??梢酝ㄟ^ typeid 拋出。 |
std::logic_error | 理論上可以通過讀取代碼來檢測到的異常。 |
std::domain_error | 當(dāng)使用了一個無效的數(shù)學(xué)域時,會拋出該異常。 |
std::invalid_argument | 當(dāng)使用了無效的參數(shù)時,會拋出該異常。 |
std::length_error | 當(dāng)創(chuàng)建了太長的 std::string 時,會拋出該異常。 |
std::out_of_range | 該異??梢酝ㄟ^方法拋出,例如 std::vector 和 std::bitset<>::operator。 |
std::runtime_error | 理論上不可以通過讀取代碼來檢測到的異常。 |
std::overflow_error | 當(dāng)發(fā)生數(shù)學(xué)上溢時,會拋出該異常。 |
std::range_error | 當(dāng)嘗試存儲超出范圍的值時,會拋出該異常。 |
std::underflow_error | 當(dāng)發(fā)生數(shù)學(xué)下溢時,會拋出該異常。 |
21、STL
C++標(biāo)準(zhǔn)模板庫(Standard Template Library,STL)是泛型程序設(shè)計(jì)最成功的實(shí)例。STL是一些常用數(shù)據(jù)結(jié)構(gòu)和算法的模板的集合,由Alex Stepanov主持開發(fā),于1998年被加入C++標(biāo)準(zhǔn)。
C++ 標(biāo)準(zhǔn)模板庫的核心包括三大組件:容器,算法,迭代器
21.1 容器
順序容器:可變長動態(tài)數(shù)組Vector、雙端隊(duì)列deque、雙向鏈表list
關(guān)聯(lián)容器:set、multliset、map、multimap
關(guān)聯(lián)容器內(nèi)的元素是排序的,所以查找時具有非常好的性能。
容器適配起:棧stack、隊(duì)列queu、優(yōu)先隊(duì)列priority_queue
所有容器具有的函數(shù):
int size(); bool empty();
順序容器和關(guān)聯(lián)容器函數(shù):
begin() end() rbegin() erase(...) clear()
順序容器獨(dú)有的函數(shù):
front() back() push_back(); pop_back(); insert(...);
21.2 迭代器
迭代器是一種檢查容器內(nèi)元素并遍歷元素的數(shù)據(jù)類型。C++更趨向于使用迭代器而不是下標(biāo)操作,因?yàn)闃?biāo)準(zhǔn)庫為每一種標(biāo)準(zhǔn)容器(如vector)定義了一種迭代器類型,而只用少數(shù)容器(如vector)支持下標(biāo)操作訪問容器元素。按照定義方式分為以下四種。
1.正向迭代器:容器類名::iterator 迭代器名;
2.常量正向迭代器:容器類名::const_iterator 迭代器名;
3.反向迭代器:容器類名::reverse_iterator 迭代器名;
4.常量反向迭代器:容器類名::const_reverse_iterator 迭代器名
21.3 算法
STL 提供能在各種容器中通用的算法(大約有70種),如插入、刪除、查找、排序等。算法就是函數(shù)模板。算法通過迭代器來操縱容器中的元素。
STL 中的大部分常用算法都在頭文件 algorithm 中定義。此外,頭文件 numeric 中也有一些算法。
許多算法操作的是容器上的一個區(qū)間(也可以是整個容器),因此需要兩個參數(shù),一個是區(qū)間起點(diǎn)元素的迭代器,另一個是區(qū)間終點(diǎn)元素的后面一個元素的迭代器。
會改變其所作用的容器。例如:
- copy:將一個容器的內(nèi)容復(fù)制到另一個容器。
- remove:在容器中刪除一個元素。
- random_shuffle:隨機(jī)打亂容器中的元素。
- fill:用某個值填充容器。
不會改變其所作用的容器。例如:
- find:在容器中查找元素。
- count_if:統(tǒng)計(jì)容器中符合某種條件的元素的個數(shù)。
#include <vector> #include <algorithm> #include <iostream> using namespace std; int main() { vector<int> v; v.push_back(1); v.push_back(2); v.push_back(3); v.push_back(4); //1,2,3,4 vector<int>::iterator p; p = find(v.begin(), v.end(), 3); //在v中查找3 若找不到,find返回 v.end() if (p != v.end()) cout << "1) " << *p << endl; //找到了 p = find(v.begin(), v.end(), 9); if (p == v.end()) cout << "not found " << endl; //沒找到 p = find(v.begin() + 1, v.end() - 1, 4); //在2,3 這兩個元素中查找4 cout << "2) " << *p << endl; //沒找到,指向下一個元素4 int arr[10] = { 10,20,30,40 }; int * pp = find(arr, arr + 4, 20); if (pp == arr + 4) cout << "not found" << endl; else cout << "3) " << *pp << endl; return 0; }
22、總結(jié)
終于寫完了,因?yàn)樾鹿谝咔椋诩腋綦x了一個多月,閑來無事,寫寫博客來總結(jié)一下自己之前學(xué)的知識;零零散散大約花了一周左右的時間,該篇文章面向C++的基礎(chǔ)知識,有許多詳細(xì),深層的沒有寫進(jìn)去。后面有時間會寫模板,STL,C++11新特性,Boost庫等的文章,剛開始寫博客,文章中有錯誤或有遺漏的知識點(diǎn),請大佬們指出。
相關(guān)文章
簡述C語言中system()函數(shù)與vfork()函數(shù)的使用方法
這篇文章主要介紹了簡述C語言中system()函數(shù)與vfork()函數(shù)的使用方法,是C語言入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下2015-08-08matlab遺傳算法求解車間調(diào)度問題分析及實(shí)現(xiàn)源碼
這篇文章主要為大家介紹了matlab遺傳算法求解車間調(diào)度問題解析,文中附含詳細(xì)實(shí)現(xiàn)源碼,有需要的朋友可以借鑒參考下,希望能夠有所幫助2022-02-02C語言中的一維數(shù)組與二維數(shù)組的實(shí)現(xiàn)
數(shù)組可以幫我們巧妙解決生活中的問題,使我們的代碼簡潔,本文主要介紹了C語言中的一維數(shù)組與二維數(shù)組,具有一定的參考價值,感興趣的可以了解一下2023-12-12關(guān)于C++虛函數(shù)與靜態(tài)、動態(tài)綁定的問題
這篇文章主要介紹了C++虛函數(shù)與靜態(tài)、動態(tài)綁定,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-10-10C++開發(fā)在IOS環(huán)境下運(yùn)行的LRUCache緩存功能
本文著重介紹如何在XCODE中,通過C++開發(fā)在IOS環(huán)境下運(yùn)行的緩存功能。算法基于LRU,最近最少使用,需要的朋友可以參考下2012-11-11C語言中棧的結(jié)構(gòu)和函數(shù)接口的使用示例
這篇文章主要介紹了C語言中棧的結(jié)構(gòu)和函數(shù)接口的使用,類似很多軟件都有撤銷的操作,這其實(shí)就是用棧這種方法來實(shí)現(xiàn)的,當(dāng)然不同的軟件具體實(shí)現(xiàn)代碼會有差異,不過原理大多都是一樣的2023-02-02C++使用string的大數(shù)取模運(yùn)算(5)
這篇文章主要為大家詳細(xì)介紹了C++使用string的大數(shù)取模運(yùn)算,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-09-09C語言實(shí)現(xiàn)的順序表功能完整實(shí)例
這篇文章主要介紹了C語言實(shí)現(xiàn)的順序表功能,結(jié)合完整實(shí)例形式分析了C語言順序表的創(chuàng)建、添加、刪除、排序、合并等相關(guān)操作技巧,需要的朋友可以參考下2018-04-04