教程鏈接:
C語言中文網(wǎng) C++教程
第一章 從C到C++
case:
①學(xué)了C語言就相當(dāng)于學(xué)了C++的一半,從C語言轉(zhuǎn)向C++時(shí),不需要從頭開始,接著C語言往下學(xué)就可以豺谈。C++支持面向過程編程、面向?qū)ο缶幊毯头盒途幊涛秩模鳦語言僅支持面向過程編程胰锌。就面向過程編程而言治泥,C++和C語言幾乎是一樣。
②C++中的類可以看作C語言中結(jié)構(gòu)體的升級版筏勒。C語言中的struct只能包含變量移迫,而C++中的class除了可以包含變量,還可以包含函數(shù)管行。在C語言中厨埋,我們將它放在了struct 外面,它和成員變量是分離的捐顷;而在C++中荡陷,我們將它放在了class Student內(nèi)部,使它和成員變量聚集在一起迅涮,看起來更像一個(gè)整體废赞。
結(jié)構(gòu)體和類都可以看作是一種由用戶自己定義的復(fù)雜數(shù)據(jù)類型,在C語言中可通過結(jié)構(gòu)體來定義變量逗柴,在C++中可以通過類名來定義變量蛹头。不同的是顿肺,通過結(jié)構(gòu)體定義出來的變量還是叫變量戏溺,而通過類定義出來的變量有了新的名稱,叫做對象屠尊。
③C++源文件的后綴在不同的編譯器下有不同的后綴名旷祸。
推薦使用.cpp作為C++文件的后綴,更加通用和規(guī)范讼昆。
g++命令
gcc命令可以用來編譯C++源文件
1.編譯單個(gè)源文件:
gcc main.c -lstdc++
2.編譯多個(gè)源文件
gcc mian.c second.c -lstdc++
GCC中還有一個(gè)g++命令托享,專門用來編譯C++程序,廣大C++開發(fā)人員也都使用這個(gè)命令浸赫。
1.編譯單個(gè)文件
g++ main.cpp
2.編譯多個(gè)文件
g++ mian.cpp module.cpp
3.使用-o選項(xiàng)指定可執(zhí)行文件的名稱:
./demo
④C++命名空間
命名空間主要是解決命名沖突闰围。
ex:
namespace Li {
FILE fp = NULL;
}
namespace Han {
FILE fp = NULL;
}
namespace是C++中的關(guān)鍵字,用來頂一個(gè)命名空間既峡。語法格式為:
namespace name {
//variables,functions,classes
}
name是命名空間的名字羡榴,它里面可以包含變量、函數(shù)运敢、類校仑、typedef、#define等传惠,最后由{}包圍.
使用變量迄沫、函數(shù)時(shí)要指明它們所在的命名空間。以最上面的fp變量為例卦方⊙虼瘢可以如此使用:
Han::fp = fopen("two.txt","rb+");
::稱為域解析操作符,在C++中用來指明要使用的命名空間。除了直接使用域解析操作符困后,還可以采用using關(guān)鍵字聲明乐纸。,ex:
fp = fopen("one.txt","r"); //使用Li定義的變量fp
Han::fp = fopen("two.txt","rb+");//使用Han定義的fp
在using聲明后摇予,如果有未具體制定命名空間的變量產(chǎn)生了命名沖突汽绢,那么默認(rèn)采用命名空間Li中的變量。
完整的命名空間demo:
#include <iostream>
namespace Diy {
class Student {
public:
char *name;
int age;
float score;
public:
void say() {
printf("%s的年齡是%d,成績是%f\n",name,age,score);
}
};
}
int main() {
std::cout << "Hello, World!s" << std::endl;
Diy::Student stu1;
stu1.name = "小明";
stu1.age =22;
stu1.score = 87;
stu1.say();
return 0;
}
C++頭文件和std命名空間
C++之前用的都是C的庫侧戴,后來引入了命名空間的概念宁昭,也就是std,std是standard的縮寫酗宋,意思是標(biāo)準(zhǔn)命名空間积仗。
1.舊的C++頭文件,如iostream.h蜕猫、fstream.h等將會繼續(xù)被支持寂曹,盡管他們不在官方標(biāo)準(zhǔn)中。這些頭文件的內(nèi)容不在命名空間std中回右。
2.新的C++頭文件隆圆,如iostream、fstream等包含的基本功能和對應(yīng)的舊版頭文件相似翔烁,但頭文件的內(nèi)容在命名空間std中渺氧。
3.標(biāo)準(zhǔn)C頭文件如stdio.h、stdlib.h等繼續(xù)被支持蹬屹。頭文件的內(nèi)容不在std中侣背。
4.具有C庫功能的新C++頭文件具有如cstdio、cstdlib這樣的名字慨默。他們提供的內(nèi)容和相應(yīng)的舊的C頭文件相同贩耐,只是內(nèi)容在std中。
對于不帶.h的頭文件厦取,所有的符號都位于命名空間std中潮太,使用時(shí)需要聲明命名空間std;對于帶.h的頭文件蒜胖,沒有使用任何命名空間消别,所有符號都位于全局作用域。
But台谢,對于原來C語言的頭文件寻狂,即使按照C++的方式來使用,例如#include <cstdio>
這種形式朋沮,那么符號可以位于命名空間std中蛇券,也可以位于全局范圍內(nèi)缀壤,ex:
#include <cstdio>
void stdDemo(){
std::printf("Hello啊");
}
void cDemo(){
printf("你好啊~");
}
這都是能編譯過的。
5.雖然C++幾乎完全兼容C語言纠亚,C語言中的頭文件在C++中依然被支持塘慕,但C++新增的庫更加強(qiáng)大和靈活,我們盡量用C++新增的頭文件蒂胞,例如iostream图呢、fstream、string等骗随。所以以后盡量用C++的方式寫代碼吧蛤织。
ex:
void demoString() {
using namespace std;
string str;
int age;
cin>>str>>age;
cout<<str<<"已經(jīng)成立"<<age<<"年了!"<<endl;
}
result:
c語文中文網(wǎng)
6
c語文中文網(wǎng)已經(jīng)成立6年了鸿染!
這里我們只需要留意using namespace std;
它聲明了命名空間std指蚜,后續(xù)如果有未指定命名空間的符號,那么默認(rèn)使用std涨椒,代碼的string摊鸡、cin、count都位于命名空間std中蚕冬。
Tips:在demoString中這個(gè)函數(shù)中聲明了命名空間std免猾,它的作用范圍就只位于此函數(shù)當(dāng)中,如果在其他函數(shù)中又用到了std播瞳,就需要重新聲明5Э免糕!如果希望在所有函數(shù)當(dāng)中都是用命名空間std赢乓,可以將他聲明在全局范圍中~!
ex:
using namespace std;
將std直接聲明在所有函數(shù)外部石窑,雖然方便牌芋,但在中大型項(xiàng)目開發(fā)中是不被推薦的,這樣做增加了命名沖突的風(fēng)險(xiǎn)松逊,推薦在函數(shù)內(nèi)聲明std躺屁。(我覺的大家都喜歡方便..so,你懂得~)
C++輸入輸出
如圖经宏,C++中輸出輸出簡單的demo犀暑。
①C語言中的輸出輸出庫,scanf和printf仍然可以在C++中使用烁兰。
②在C++中要使用輸出輸出時(shí)耐亏,需要包含頭文件iostream,其中包含了輸出輸出的對象沪斟,例如常見的cin表示輸入广辰,cout表示輸出,cerr表示標(biāo)準(zhǔn)錯(cuò)誤。
③cout和cin都是C++的內(nèi)置對象择吊,而不是關(guān)鍵字李根!cout和cin分別就是ostream和istream類的對象,這就是C++中的內(nèi)置對象几睛。
④使用cout進(jìn)行輸出需要緊跟<<運(yùn)算符房轿,使用cin需要緊跟>>運(yùn)算符涮总,這兩個(gè)運(yùn)算符可以自行分析所處理的數(shù)據(jù)類型棱貌,無需向scanf和printf那樣給出格式控制字符串佛掖,不用%d页滚,%f什么的了~
⑤endl表示換行殴蹄,與c語言中的\n作用相同荒椭,也可以用\n來替代endl淆党,也就可以寫作:
cout<<"Please input an int number:\n"
佛致,endl吼蚁,end of line凭需。⑥cin和cout都可以連續(xù)進(jìn)行輸出輸出,比如:
cout和cin的用法非常強(qiáng)大靈活肝匆,上面只是最基本的功能粒蜈,后面我們會詳細(xì)介紹,它們比C語言中的scanf旗国、printf更加靈活易用枯怖。
C++ 布爾類型(bool)
在c語言中并沒有徹底從語法上支持“真”和“假”,只是用0和非0表示能曾。C++中新增了bool類型度硝,一般占用1個(gè)字節(jié)長度,兩個(gè)取值寿冕,true & false蕊程,在cin/cout中輸入輸出還是數(shù)字0或1,但是代碼中還是可以用true和false的驼唱。
常量 const關(guān)鍵字
在C語言中藻茂,const用來限制一個(gè)變量,表示這個(gè)變量不能改變玫恳,通常稱這樣的變量為常量(Constant)辨赐。在C++中const的含義并沒有改變,只是對細(xì)節(jié)進(jìn)行了一些調(diào)整京办。主要是以下兩點(diǎn):
①C++中的const更像是編譯階段的#define掀序。
const int m = 10;
int n = m;
將m的值賦給n,這個(gè)賦值的過程在C和C++中是有區(qū)別的臂港。
在C語言中森枪,編譯器會先到m所在的內(nèi)存取出一份數(shù)據(jù)视搏,再將這份數(shù)據(jù)賦值給n;而在C++中县袱,編譯器會直接將10賦值給m浑娜,沒有讀取內(nèi)存的過程。C++對const的處理少了讀取內(nèi)存的過程式散,優(yōu)點(diǎn)是提高了程序執(zhí)行效率筋遭,缺點(diǎn)是不能反映內(nèi)存的變化,一旦const變量被修改(通過指針還是可以修改的)暴拄,C++就不能取得最新的值漓滔。
void demoConst() {
// const int m = 10;
// int n = m;
const int n = 10;
int *p = (int *) &n;
*p = 99;
printf("%d\n", n);
}
這段代碼以C語言的方式編譯,結(jié)果是99乖篷,以C++的方式編譯結(jié)果是10响驴,差異來自于C和C++對const的處理方式不同。
C語言中對const的處理和普通變量一樣撕蔼,會到內(nèi)存中讀取數(shù)據(jù)豁鲤;C++對const的處理更像是編譯時(shí)期的#define,是一個(gè)值替換的過程鲸沮,不會讀取內(nèi)存琳骡。
②C++中全局const變量的可見范圍是當(dāng)前文件
普通全局變量的作用域是當(dāng)前文件,但在其他文件中也是可見的讼溺,使用extern聲明后就可以使用楣号。下面這段代碼在C和C++中都一樣。
如果在變量前加上extern關(guān)鍵字怒坯,運(yùn)行結(jié)果也一樣炫狱。這說明在C語言中,const變量在多文件編程時(shí)的表現(xiàn)和普通變量一樣敬肚,除了不能修改毕荐,沒有其他區(qū)別束析。
但是如果在C++中加上const艳馒,代碼就不能運(yùn)行了,因?yàn)镃++對const的特性做了調(diào)整员寇,全局const變量的作用域仍然是當(dāng)前文件弄慰,但是它在其他文件中是不可見的。蝶锋,如下圖;
總結(jié):
C和C++中全局const變量的作用域相同陆爽,都是當(dāng)前文件,不同的是它們的可見范圍:C語言中const全局變量的可見范圍是整個(gè)程序扳缕,在其他文件中使用extern聲明后就可以使用慌闭;而C++中全局變量的可見范圍僅限于當(dāng)前文件别威,在其他文件中不可見,所以它可以定義在頭文件(.h)中驴剔,多次引入后也不會出錯(cuò)省古。
new 和 delete運(yùn)算符
在C語言中,動態(tài)分配內(nèi)存用malloc()函數(shù)丧失,釋放內(nèi)存用free()函數(shù)豺妓,如下所示:
void testMalloc() {
int *p = (int*) malloc(sizeof(int)*10);//分配10個(gè)int型內(nèi)存空間
free(p);//釋放內(nèi)存
}
void testNewAndDelete() {
int *p = new int; //分配一個(gè)int型的內(nèi)存空間
delete p; //釋放內(nèi)存
int *d = new int[10];//分配10個(gè)int型的內(nèi)存空間
delete[] p;
}
在C++中,這兩個(gè)關(guān)鍵字仍然能用布讹,但是C++中又增加了兩個(gè)關(guān)鍵字琳拭,new和delete,new用來動態(tài)分配內(nèi)存描验,delete用來釋放內(nèi)存白嘁。
使用方式也很簡單,上面對比一下就可以看出來膘流。
new操作符會根據(jù)后面的數(shù)據(jù)類型來推斷所需空間的大小权薯。
為了避免內(nèi)存泄漏,通常new和delete睡扬,new[]和delete[]操作符應(yīng)該成對出現(xiàn)盟蚣,并且不要和C語言中malloc()、free()一起混用卖怜。
C++ inline內(nèi)聯(lián)函數(shù)
函數(shù)是一個(gè)可以重復(fù)使用的代碼塊屎开,CPU會一條一條挨著執(zhí)行其中的代碼。CPU再執(zhí)行主調(diào)函數(shù)代碼時(shí)如果遇到被調(diào)函數(shù)马靠,主調(diào)函數(shù)就會暫停奄抽,CPU轉(zhuǎn)而執(zhí)行被調(diào)函數(shù)的代碼,被調(diào)函數(shù)執(zhí)行完畢后再返回到主調(diào)函數(shù)中甩鳄,主調(diào)函數(shù)根據(jù)剛才的狀態(tài)繼續(xù)往下執(zhí)行逞度。執(zhí)行過程可以認(rèn)為是多個(gè)函數(shù)之間的相互調(diào)用的過程,它們形成了一個(gè)活簡單或復(fù)雜的調(diào)用鏈條妙啃,這個(gè)鏈條的起點(diǎn)是main()档泽,終點(diǎn)也是main()。當(dāng)mian()調(diào)用完所有的函數(shù)揖赴,它會返回一個(gè)值來結(jié)束自己的生命馆匿,從而結(jié)束整個(gè)程序。
函數(shù)調(diào)用是有時(shí)間和空間開銷的燥滑。如果函數(shù)體代碼較多渐北,需要較長的執(zhí)行時(shí)間,那么函數(shù)調(diào)用機(jī)制占用的時(shí)間可以忽略铭拧;如果函數(shù)只有一兩條語句赃蛛,那么大部分的時(shí)間都會花費(fèi)在函數(shù)調(diào)用機(jī)制上恃锉,這種時(shí)間開銷就不容忽視。為了消除這種時(shí)間開銷呕臂,在編譯時(shí)將函數(shù)調(diào)用出用函數(shù)體替換淡喜,類似于C語言中的宏展開。這種在函數(shù)調(diào)用處直接嵌入函數(shù)體的函數(shù)被稱為【內(nèi)聯(lián)函數(shù)】(Inline Function)诵闭,又稱內(nèi)嵌函數(shù)或者內(nèi)置函數(shù)炼团。
指定內(nèi)聯(lián)函數(shù)的方法很簡單,只需要在函數(shù)定義處增加inline關(guān)鍵字疏尿,如下:
注意瘟芝,要在函數(shù)定義處添加inline關(guān)鍵字,在函數(shù)聲明處添加inline關(guān)鍵字雖然沒錯(cuò)褥琐,但是是無效的锌俱,編譯器會忽略函數(shù)聲明處的inline關(guān)鍵字。
當(dāng)編譯器遇到函數(shù)調(diào)用swap(&m,&n)時(shí)敌呈,會用swap()函數(shù)的代碼替換swap(&m,&n),同時(shí)用實(shí)參代替形參贸宏,
swap(&m,&n)
被置換成:
temp = *(&m);
*(&m) = *(&n);
*(&n) = temp;
由于內(nèi)聯(lián)函數(shù)比較短小,通常做法是省略函數(shù)原型磕洪,也就是函數(shù)聲明吭练,將整個(gè)函數(shù)定義放在本應(yīng)該提供函數(shù)原型的地方(也就是將函數(shù)直接定義在main函數(shù)上面)。
函數(shù)內(nèi)聯(lián)函數(shù)的缺點(diǎn)也非常明顯析显,編譯后的程序會存在多份相同的函數(shù)拷貝鲫咽,如果被聲明為內(nèi)聯(lián)函數(shù)的函數(shù)體非常大,那么編譯后的程序體積也會變的很大谷异,所以必須牢記:一般只將那些短小的分尸、頻繁調(diào)用的函數(shù)聲明為內(nèi)聯(lián)函數(shù)~
inline聲明只是程序員對編譯器提出的一個(gè)建議,并不是強(qiáng)制性的歹嘹,編譯器有自己的判斷能力箩绍,他會根據(jù)具體情況決定是否這么做。
使用內(nèi)聯(lián)函數(shù)替代宏
//TODO
如何規(guī)范使用C++內(nèi)聯(lián)函數(shù)
inline關(guān)鍵字可以只在函數(shù)定義處添加尺上,也可以只在函數(shù)聲明處添加材蛛;但是在函數(shù)聲明處添加的inline關(guān)鍵字是無效的,編譯器會忽略掉尖昏。
內(nèi)聯(lián)函數(shù)不應(yīng)該有聲明仰税,應(yīng)該將函數(shù)定義在本應(yīng)該出現(xiàn)函數(shù)聲明的地方,這是一種良好的編程風(fēng)格抽诉。
函數(shù)默認(rèn)參數(shù)及使用場景
C++支持默認(rèn)參數(shù),默認(rèn)參數(shù)只能放在形參列表的最后吐绵,而且一旦為某個(gè)形參指定了默認(rèn)值迹淌,那么它后面的所有形參都必須有默認(rèn)值河绽。(這一點(diǎn)看起來還是kt默認(rèn)值更方便點(diǎn),沒這個(gè)強(qiáng)制)
ex:
void demoDefaultValue(int a,float b= 1.0,bool c = false){
cout<<"a,b,c value = "<<a<<b<<c;
}
可以在函數(shù)定義處聲明默認(rèn)值唉窃,也可以在函數(shù)聲明處聲明默認(rèn)值耙饰,但是有一點(diǎn)必須記住:在給定的作用域中只能指定一次默認(rèn)參數(shù)纹份。
如圖:
如果把聲明放在其他文件中就可以了:
在多文件編程時(shí)苟跪,我們通常的做法是將函數(shù)聲明放在頭文件中,并且一個(gè)函數(shù)只聲明一次蔓涧,但是多次聲明同一個(gè)函數(shù)也是合法的件已。
有一點(diǎn)需要注意,在給定的作用域中一個(gè)形參只能被賦予一次默認(rèn)參數(shù)元暴,函數(shù)的后續(xù)聲明只能為之前那些沒有默認(rèn)值的形參添加默認(rèn)值篷扩,并且該形參右側(cè)的所有形參必須都有默認(rèn)值。
這種聲明是正確的茉盏,第一次給c指定了默認(rèn)值鉴未,第二次給b指定了默認(rèn)值;但第二次不能再次給c指定默認(rèn)參數(shù)鸠姨,否則就是重復(fù)聲明同一個(gè)默認(rèn)參數(shù)铜秆。
C++支持函數(shù)重載
C++支持函數(shù)重載,這一點(diǎn)跟java差不多讶迁,參數(shù)的類型羽峰、參數(shù)的個(gè)數(shù)和參數(shù)的順序,只要有一個(gè)不同即可添瓷。
C++函數(shù)重載過程中的二義性和類型轉(zhuǎn)換
當(dāng)實(shí)參的類型和形參的類型不一致時(shí)情況就會變得稍微復(fù)雜梅屉,例如函數(shù)形參的類型是int,調(diào)用函數(shù)時(shí)卻將short類型的數(shù)據(jù)交給了它鳞贷,編譯器就需要先將short類型轉(zhuǎn)換為int類型才能匹配成功坯汤。
C++標(biāo)準(zhǔn)規(guī)定,在進(jìn)行重載決議時(shí)編譯器應(yīng)該按照下面的優(yōu)先級順序來處理實(shí)參的類型;
編譯器按照從高到低的順序來搜索重載函數(shù)搀愧,首先是精確匹配惰聂,然后是類型提醒,最后才是類型轉(zhuǎn)換咱筛,一旦在某個(gè)優(yōu)先級中找到唯一的一個(gè)重載函數(shù)就匹配成功搓幌,不再繼續(xù)往下搜索。
如果在一個(gè)優(yōu)先級中找到多個(gè)合適的重載函數(shù)迅箩,編譯器就會陷入兩難境地溉愁,不知道如何抉擇就視為一種錯(cuò)誤,這就是函數(shù)重載過程中的二義性錯(cuò)誤饲趋。
比如上面去掉func(int)的定義去調(diào)用func(49),三個(gè)函數(shù)都能匹配上拐揭,那么就會發(fā)生二義性錯(cuò)誤撤蟆,編譯器報(bào)錯(cuò)。
注意堂污,類型提升和類型轉(zhuǎn)換不是一碼事家肯,類型提升是積極的,是為了更加高效地利用計(jì)算機(jī)硬件盟猖,不會導(dǎo)致數(shù)據(jù)丟失或精度降低讨衣;而類型轉(zhuǎn)換是不得已而為之,不能保證數(shù)據(jù)的正確性式镐,也不能保證應(yīng)有的精度反镇。
C++和C的混合編程
C和C++進(jìn)行混合編程時(shí),考慮到對函數(shù)名的處理方式不同碟案,勢必會造成編譯器在程序鏈接階段無法找到函數(shù)具體的實(shí)現(xiàn)愿险,導(dǎo)致鏈接失敗。
這時(shí)候我們就需要借助【extern "C"】,解決C++和C在處理代碼方式上的差異性。
extern是C和C++的一個(gè)關(guān)鍵字玄渗,但對于extern "C",我們需要視為一個(gè)整體扮叨,與extern毫無關(guān)系, extern "C" 既可以修飾一句C++代碼领迈,也可以修飾一段C++代碼彻磁,它的功能是讓編譯器以處理C語言代碼的方式來處理修飾的C++代碼。
用extern "C" 之前:
在頭文件中使用 extern "C"
//第一種方式
extern "C" void display();
void display();
//第二種方式狸捅,實(shí)際開發(fā)中可以使用
extern "C" {
void display();
}
這樣在C和C++文件中使用這個(gè)頭文件的時(shí)候衷蜓,頭文件的內(nèi)容會被分別復(fù)制到這2個(gè)源文件中。編譯器根據(jù)不同的源文件分別進(jìn)行不同的處理尘喝。
類和對象
C++類的定義和對象的創(chuàng)建
C++中類和結(jié)構(gòu)體一樣磁浇,只是一種復(fù)雜數(shù)據(jù)類型的聲明,不占用內(nèi)存空間朽褪。而對象是類這種數(shù)據(jù)類型的一個(gè)變量置吓,或者說是通過類這種數(shù)據(jù)類型創(chuàng)建出來的一份實(shí)實(shí)在在的數(shù)據(jù),所以占用內(nèi)存空間缔赠。
類是用戶自定義的類型衍锚,C++本身并不提供現(xiàn)成的類的名稱、結(jié)構(gòu)和內(nèi)容嗤堰。
簡單的類定義:
class Student {
public:
char *name;
int age;
float score;
void say() {
cout << name << age << score << endl;
}
};
`class`是C++新增的關(guān)鍵字戴质,專門用來定義類。`Student`是類的名稱;類的首字母一般大寫置森。
`public`是C++新增的關(guān)鍵字斗埂,只能用在類的定義中符糊,表示的意思和其他語言一樣:成員變量或成員函數(shù)具有"公開"的訪問權(quán)限凫海。
類只是一個(gè)模板(Template),該數(shù)據(jù)類型的名稱是Student男娄。與char行贪、int、float等基本數(shù)據(jù)類型不同的是模闲,Student是一種復(fù)雜數(shù)據(jù)類型建瘫,可以包含基本類型,還有很多基本類型中沒有的特性尸折。
**創(chuàng)建對象**
`Student stu;//創(chuàng)建對象`
類名前可以加`class`關(guān)鍵字啰脚,不過我們一般都是省略掉。
也可以創(chuàng)建對象數(shù)組:
`Student stuArr[100];`
**訪問類的成員**
創(chuàng)建對象后可以使用點(diǎn)號`.`來訪問成員變量和成員函數(shù)实夹。如下所示 :
void test1() {
// class Student stu; //class 可省略
Student stu;//創(chuàng)建對象
Student stuArr[100]; //創(chuàng)建對象數(shù)組
stu.name = "KuGou~";
stu.age = 21;
stu.score = 20.5;
stu.say();
}
使用對象指針
C語言中經(jīng)典的指針在C++中仍然廣泛使用橄浓,尤其是指向?qū)ο蟮闹羔槪瑳]有它就不能實(shí)現(xiàn)某些功能亮航。
上面創(chuàng)建對象的方式:
Student stu;
會在棧上分配內(nèi)存荸实,通過地址符&
即可獲取其地址。
還有一種可以在堆上分配內(nèi)存缴淋,即使用new
關(guān)鍵字創(chuàng)建准给。但其與棧上創(chuàng)建出來的有點(diǎn)不同的是,棧上的有名字重抖,不必非得指定指針指向它露氮。但是【在堆上分配的是匿名的,沒有名字钟沛,只能得到一個(gè)指向他的指針畔规,所以必須使用一個(gè)指針變量來接收這個(gè)指針,否則以后再也無法找到這個(gè)對象了讹剔,更沒有辦法使用它】油讯。
棧內(nèi)存是程序自動管理的,不能使用使用delete刪除在棧上創(chuàng)建的對象延欠;
堆內(nèi)存由程序員管理陌兑,對象使用完畢后可以通過delete刪除。
在實(shí)際開發(fā)中由捎,new和delete往往成對出現(xiàn)兔综,以保證及時(shí)刪除不再使用的對象,防止無用內(nèi)存堆積。
堆上分配內(nèi)存的:
void test1() {
// class Student stu; //class 可省略
Student stu;//創(chuàng)建對象
Student stuArr[100]; //創(chuàng)建對象數(shù)組
//在棧上分配內(nèi)存
Student *pStu = &stu;
//在堆上分配內(nèi)存
Student *pStu2 = new Student;
// stu.name = "KuGou~";
// stu.age = 21;
// stu.score = 20.5;
// stu.say();
pStu2->name = "SingleDog";
pStu2->age = 27;
pStu2->score = 20.5;
pStu2->say();
delete pStu2;//刪除對象
}
#### C++類的成員變量和成員函數(shù)詳解
類的成員變量和普通變量一樣软驰,也有數(shù)據(jù)類型和名稱涧窒,占用固定長度的內(nèi)存。但是锭亏,在【定義類的時(shí)候不能對成員變量賦值】(這點(diǎn)和Java不同)纠吴,因?yàn)轭愔皇且环N數(shù)據(jù)類型或者說是一種模板,本身不占用內(nèi)存空間慧瘤,而變量的值則需要內(nèi)存來存儲戴已。
類的成員函數(shù)也和普通函數(shù)一樣,都有返回值和參數(shù)列表锅减,與一般函數(shù)的區(qū)別在于:
成員函數(shù)是一個(gè)類的成員糖儡,出現(xiàn)在類體中,它的作用范圍由類來決定怔匣;
普通函數(shù)是獨(dú)立的握联,作用范圍是全局的,或位于某個(gè)命名空間內(nèi)每瞒。
**我們可以在類中聲明函數(shù)金闽,在類體外定義。但是成員函數(shù)必須先在類體中作原型聲明独泞,然后再類外定義呐矾,也就是說類體的位置應(yīng)在函數(shù)定義之前。**
class Book {
public:
char *name;
char *author;
float price;
void logInfo();
};
void Book::logInfo() {
cout << name << "的作者是:" << author << ",價(jià)格是:" << price << endl;
}
void test2() {
Book *book_fp = new Book;
book_fp->name = "《麥田守望者》";
book_fp->author = "杰羅姆";
book_fp->price = 49.5;
book_fp->logInfo();
}
```
`::`被稱為域解析符懦砂,用來連接類名和函數(shù)名蜒犯,指明當(dāng)前函數(shù)屬于哪個(gè)類。
**在類體中和類體外定義成員函數(shù)的區(qū)別**
在類體中定義的成員函數(shù)會自動成為內(nèi)聯(lián)函數(shù)荞膘,在類體外定義的不會罚随。可以在類體外定義的函數(shù)前加`inline`關(guān)鍵字使其與函數(shù)體內(nèi)定義的相同羽资。
內(nèi)聯(lián)函數(shù)一般不是我們期望的淘菩,它會將函數(shù)調(diào)用處用函數(shù)體替代,所以最好在類體內(nèi)部對成員函數(shù)作聲明屠升,在類體外部進(jìn)行定義潮改,實(shí)際開發(fā)中大家都這么做的。
C++類成員的訪問權(quán)限以及類的封裝
C++通過public腹暖、protected汇在、private三個(gè)關(guān)鍵字來控制成員變量和成員函數(shù)的訪問權(quán)限,他們分別表示公有的脏答、受保護(hù)的糕殉、私有的亩鬼。
Tips:
Java和C#程序員需要注意,C++中的public阿蝶、private雳锋、protected只能修飾類的成員,不能修飾類羡洁,C++中的類沒有公有私有之說玷过。
在類的內(nèi)部,無論成員是被聲明為public焚廊、protected還是private都能互相訪問冶匹,沒有訪問權(quán)限的限制习劫。
在類的外部咆瘟,只能通過對象訪問成員,并且通過對象只能訪問public屬性的成員诽里,不能訪問private袒餐、protected屬性的成員。
下面來實(shí)際使用一下這幾個(gè)限定符:
class Person {
private:
char *m_name;
int m_age;
int m_score;
public:
void setName(char *name);
void setAge(int age);
void setScore(float score);
void show();
};
void Person::setName(char *name) {
m_name = name;
}
void Person::setAge(int age) {
m_age = age;
}
void Person::setScore(float score) {
m_score = score;
}
void Person::show() {
cout << m_name << "的年齡是:" << m_age << ",成績是:" << m_score << endl;
}
void test3() {
Person *person = new Person;
person->setName("二狗");
person->setAge(22);
person->setScore(87.5);
person->show();
}
成員變量大都以m_開頭谤狡,這是約定俗稱的寫法灸眼。以m_開頭一眼就可以看出這是個(gè)成員變量,又可以和成員函數(shù)中的形參名字區(qū)分開墓懂。(跟java的成員變量m開頭類似焰宣。)
C++對象的內(nèi)存模型
編譯器會將成員變量和成員函數(shù)分開存儲:【分別為每個(gè)對象的成員變量分配內(nèi)存,但是所有對象都共享同一段函數(shù)代碼捕仔∝盎】如下圖:
類可以看做是一種復(fù)雜的數(shù)據(jù)類型,可以用sizeof求得該類型的大小榜跌。在計(jì)算其類型大小時(shí)闪唆,只計(jì)算了成員變量的大小,并沒有把成員函數(shù)也包含在內(nèi)钓葫。
【對象的大小只受成員變量的影響悄蕾,和成員函數(shù)沒有關(guān)系〈「。】
C++函數(shù)的編譯
//TODO
C++構(gòu)造函數(shù)
在C++中帆调,有一種特殊的構(gòu)造函數(shù),它的名字和類名相同豆同,沒有返回值番刊,不需要用戶顯式調(diào)用(用戶也不能調(diào)用,是的诱告,跟java一樣撵枢,在創(chuàng)建對象的時(shí)候就直接調(diào)用了)民晒,而是在創(chuàng)建對象時(shí)自動執(zhí)行。這種特殊的成員函數(shù)就是構(gòu)造函數(shù)锄禽。
ex:
跟Java區(qū)別可能就在于:
①需要在public中聲明構(gòu)造函數(shù)
②構(gòu)造函數(shù)是在類的外部定義
③聲明和定義都不能出現(xiàn)返回值類型潜必,void也不行
④函數(shù)體中不能有return語句
其他用法都類似。
構(gòu)造函數(shù)必須是public屬性的沃但,否則創(chuàng)建對象時(shí)無法調(diào)用磁滚。設(shè)置為private、protected屬性也不會報(bào)錯(cuò)宵晚,但是沒有意義垂攘。
構(gòu)造函數(shù)的重載
構(gòu)造函數(shù)的調(diào)用是強(qiáng)制的,一旦在類中定義了構(gòu)造函數(shù)淤刃,那么創(chuàng)建對象時(shí)就一定要調(diào)用晒他,不調(diào)用是錯(cuò)誤的。如果有多個(gè)重載的構(gòu)造函數(shù)逸贾,那么創(chuàng)建對象時(shí)提供的實(shí)參必須和其中的一個(gè)構(gòu)造函數(shù)匹配陨仅;反過來時(shí),創(chuàng)建對象時(shí)只有一個(gè)構(gòu)造函數(shù)會被調(diào)用铝侵。(這倒是和Java一樣)
默認(rèn)構(gòu)造函數(shù)
一個(gè)類必須有構(gòu)造函數(shù)灼伤,要么用戶自己定義,要么編譯器自動生成咪鲜。一旦用戶自己定義了構(gòu)造函數(shù)狐赡,不管有幾個(gè),也不管形參如何疟丙,編譯器都不再自動生成颖侄。
調(diào)用沒有參數(shù)的構(gòu)造函數(shù)可以省略括號,之前的demo中也應(yīng)用過這一點(diǎn)隆敢。
C++構(gòu)造函數(shù)初始化列表
構(gòu)造函數(shù)的一項(xiàng)重要功能是對成員變量進(jìn)行初始化发皿,為了達(dá)到這個(gè)目的,可以在構(gòu)造函數(shù)的函數(shù)體中對變量一一賦值拂蝎,還可以采用初始化列表穴墅。
ex:
Person::Person(char *name, int age, float score):m_name(name),m_age(age),m_score(score) {
}
使用構(gòu)造函數(shù)初始化列表并沒有效率上的優(yōu)勢,僅僅是書寫方便温自,尤其是成員變量較多時(shí)玄货,這種寫法非常簡單明了。
初始化const成員變量
構(gòu)造函數(shù)初始化列表還有一個(gè)很重要的作用悼泌,就是初始化const成員變量松捉。初始化const成員變量的唯一方法就是使用初始化列表。(C++類中的成員變量不能再類中直接賦值)
析構(gòu)函數(shù)
創(chuàng)建對象時(shí)系統(tǒng)會自動調(diào)用構(gòu)造函數(shù)進(jìn)行初始化工作馆里,同樣隘世,銷毀對象時(shí)系統(tǒng)也會自動調(diào)用一個(gè)函數(shù)來進(jìn)行清理工作可柿,例如釋放分配的內(nèi)存、關(guān)閉打開的文件等丙者,這個(gè)函數(shù)就是析構(gòu)函數(shù)复斥。
析構(gòu)函數(shù)也是一種特殊的成員函數(shù),沒有返回值械媒,不需要程序員顯式調(diào)用目锭,實(shí)際上也無法顯式調(diào)用,而是在銷毀對象時(shí)自動執(zhí)行纷捞。構(gòu)造函數(shù)的名字和類名相同痢虹,而析構(gòu)函數(shù)的名字是在類名前加一個(gè)~
符號。
析構(gòu)函數(shù)沒有參數(shù)主儡,不能被重載奖唯,因此一個(gè)類只能有一個(gè)析構(gòu)函數(shù)。
ex:
class VLA {
public:
VLA(int len);
~VLA();
private:
int *at(int i);//獲取第i個(gè)元素的指針
private:
const int m_len;
int *m_arr;//數(shù)組指針
int *m_p;//指向第i個(gè)元素的指針
};
VLA::VLA(int len) : m_len(len) {
if (len > 0) {
m_arr = new int[len];
} else {
m_arr = NULL;
}
}
//析構(gòu)函數(shù)
VLA::~VLA() {
delete[] m_arr;
}
①析構(gòu)函數(shù)在對象被銷毀時(shí)調(diào)用缀辩,而對象的銷毀時(shí)機(jī)與它所在的內(nèi)存區(qū)域有關(guān)臭埋。
在所有函數(shù)之外創(chuàng)建的對象是全局對象,它和全局變量類似臀玄,位于內(nèi)存分區(qū)中的全局?jǐn)?shù)據(jù)區(qū),程序在結(jié)束執(zhí)行時(shí)會調(diào)用這些對象的析構(gòu)函數(shù)畅蹂。
②在函數(shù)內(nèi)部創(chuàng)建的對象是局部對象健无,它和局部變量類似,位于棧區(qū)液斜,函數(shù)執(zhí)行結(jié)束時(shí)會調(diào)用這些對象的析構(gòu)函數(shù)累贤。
③new創(chuàng)建的對象位于堆區(qū),通過delete刪除時(shí)才會調(diào)用析構(gòu)函數(shù)少漆;如果沒有
delete臼膏,析構(gòu)函數(shù)就不會被執(zhí)行。(所以示损,new和delete還是要成對出現(xiàn)啊!)
C++對象數(shù)組
數(shù)組中的每個(gè)元素都是對象渗磅。
C++成員對象和封閉類詳解
一個(gè)類的成員變量如果是另一個(gè)類的對象,就稱之為"成員變量"检访。包含成員對象的類叫封閉類始鱼。
生成封閉類對象的語句一定要讓編譯器能夠弄明白其成員對象是如何初始化的,否則就會編譯錯(cuò)誤脆贵。
成員對象的消亡
封閉類對象生成時(shí)医清,先執(zhí)行所有成員對象的構(gòu)造函數(shù),然后才執(zhí)行封閉類自己的構(gòu)造函數(shù)卖氨。
但當(dāng)封閉類對象消亡時(shí)会烙,先執(zhí)行封閉類的析構(gòu)函數(shù)负懦,然后再執(zhí)行成員對象的析構(gòu)函數(shù),成員對象析構(gòu)函數(shù)的執(zhí)行次序和構(gòu)造函數(shù)的執(zhí)行次序相反柏腻,先構(gòu)造的后析構(gòu)密似。
C++ this指針詳解
this是C++的一個(gè)關(guān)鍵字,也是一個(gè)const指針葫盼,它指向當(dāng)前對象祟昭,通過它可以訪問當(dāng)前對象的所有成員笨农。
所謂當(dāng)前對象,是指正在使用的對象,例如對于stu.show()
续捂,stu就是當(dāng)前對象,this是指向stu潦俺。
ex:
this只能用在類的內(nèi)部碉怔,通過this可以訪問類的所有成員,包括private/protected/public屬性的峰档。
this是一個(gè)指針败匹,要用->
來訪問成員變量或成員函數(shù)。
this實(shí)際上是成員函數(shù)的一個(gè)形參讥巡,在調(diào)用成員函數(shù)時(shí)將對象的地址作為實(shí)參傳遞給this掀亩。不過this這個(gè)形參是隱式的,它并不出現(xiàn)在代碼中欢顷,而是在編譯階段由編譯器默默地將它添加到形參列表中槽棍。
C++ static靜態(tài)成員變量
靜態(tài)成員變量是一種特殊的成員變量,它被關(guān)鍵字static
修飾抬驴。
static成員變量屬于類炼七,不屬于某個(gè)具體的對象,即使創(chuàng)建多個(gè)對象布持,也只為這個(gè)靜態(tài)變量分配一個(gè)內(nèi)存豌拙。
static成員變量的內(nèi)存既不是在聲明類時(shí)分配,也不是在創(chuàng)建對象時(shí)分配题暖,而是在類外初始化時(shí)分配按傅。即沒有在類外初始化的static的成員變量不能用。(經(jīng)測試發(fā)現(xiàn)初始化不能放在方法中芙委!只能在類外體進(jìn)行3逊蟆)
static成員變量不占用對象的內(nèi)存,而是在所有對象之外開辟內(nèi)存灌侣,即使不創(chuàng)建對象也可以訪問推捐。
static成員變量和普通static變量一樣,都在內(nèi)存分區(qū)中的全局?jǐn)?shù)據(jù)分配內(nèi)存侧啼,到程序結(jié)束時(shí)才釋放牛柒。這意味著static成員變量不隨對象的創(chuàng)建而分配內(nèi)存堪簿,也不隨對象的銷毀而釋放內(nèi)存。而普通成員變量在對象創(chuàng)建時(shí)分配內(nèi)存皮壁,在對象銷毀時(shí)釋放內(nèi)存椭更。
初始化時(shí)可以賦初值,也可以不賦值蛾魄,如果不賦值虑瀑,默認(rèn)初始化為0.
C++ static靜態(tài)成員函數(shù)詳解
普通成員函數(shù)可以訪問所有成員,靜態(tài)成員函數(shù)只能訪問靜態(tài)成員滴须。
編譯器在編譯一個(gè)普通成員函數(shù)時(shí)舌狗,會隱式地增加一個(gè)形參this,并把當(dāng)前對象的地址賦值給this扔水,所以普通成員函數(shù)只能在創(chuàng)建對象后通過對象來調(diào)用痛侍,因?yàn)樗枰?dāng)前對象的地址。而靜態(tài)成員函數(shù)可以通過類來直接調(diào)用魔市,編譯器不會為它增加形參this主届,它不需要當(dāng)前對象的地址。
靜態(tài)成員函數(shù)與普通成員函數(shù)的根本區(qū)別在于:普通成員函數(shù)有this指針待德,可以訪問類中的任意成員君丁;而靜態(tài)成員函數(shù)沒有this指針,只能訪問靜態(tài)成員磅网。(包括靜態(tài)成員變量和靜態(tài)成員函數(shù))
C++ const成員變量和成員函數(shù)
const成員變量的用法和普通const變量的用法相似谈截,只需要在聲明時(shí)加上const關(guān)鍵字。初始化const成員變量只有一種方法涧偷,就是通過構(gòu)造函數(shù)的初始化列表,之前也說過了毙死。
const成員函數(shù)
const成員函數(shù)可以使用類中的所有成員(看似和普通函數(shù)一樣)燎潮,但是不能修改他們的值(emotional damage?)扼倘,這種措施主要還是為了保護(hù)數(shù)據(jù)而設(shè)置的确封。const成員函數(shù)也稱為常成員函數(shù)。
通常我們將get函數(shù)設(shè)置為常成員函數(shù)再菊,讀取成員變量的函數(shù)的名字通常以get開頭爪喘,后跟成員變量的名字,所以通常將他們稱為get函數(shù)纠拔。
常成員函數(shù)需要在聲明和定義的時(shí)候在函數(shù)和頭部的結(jié)尾加上const關(guān)鍵字秉剑,ex:
這里需要注意的是,const是放在函數(shù)名后面的稠诲,跟java的不同侦鹏。
需要強(qiáng)調(diào)的是诡曙,必須在成員函數(shù)的聲明和定義處同時(shí)加上const關(guān)鍵字。
比如:
char *getName() const
和char *getname()
是兩個(gè)不同的函數(shù)原型略水。不加const可就是另外一個(gè)函數(shù)了~
Tips:區(qū)分一下const的位置:
①函數(shù)開頭的const用來修飾函數(shù)的返回值价卤,表示返回值是const類型,也就是不能修改渊涝,例如const char * getName()
②函數(shù)頭部的結(jié)尾加上const表示常成員函數(shù)慎璧,這種函數(shù)只能讀取成員變量的值,而不能修改成員變量的值跨释,例如char * getname() const
C++ const對象
const可以用來修飾對象胸私,稱為常對象。一旦將對象定義為常對象之后煤傍,就只能調(diào)用類的const成員(包括const成員變量和const成員函數(shù))了盖文。
定義常對象的語法和定義常量的語法類似:
const class object(params);
class const object(params)
也可以定義const指針:
const class *p = new class(params);
class const *p = new class(params);
C++友元函數(shù)和友元類
一個(gè)類中可以由public、protected蚯姆、private三種屬性的成員五续,通過對象可以訪問public成員,只有本類中的函數(shù)可以訪問本類的private成員龄恋。但有一種例外情況:友元疙驾。借助友元,可以使得其他類中的成員函數(shù)以及全局范圍內(nèi)的函數(shù)訪問當(dāng)前類的private成員郭毕。
在當(dāng)前類以外定義的它碎、不屬于當(dāng)前類的函數(shù)也可以在類中聲明,但要在前面加friend關(guān)鍵字显押,這樣就構(gòu)成了友元函數(shù)扳肛。友元函數(shù)可以是不屬于任何類的非成員函數(shù),也可以是其他類的成員函數(shù)乘碑。
友元函數(shù)可以訪問當(dāng)前類中的所有成員挖息,包括public、protected兽肤、private屬性的套腹。
ex:
友元函數(shù)不是類的成員函數(shù),在友元函數(shù)中不能直接訪問類的成員资铡,必須要借助對象!
也可以在類中聲明其他類的友元函數(shù)电禀。
友元類
友元類中的所有成員函數(shù)都是另外一個(gè)類的友元函數(shù)。例如將類B聲明為類A的友元類笤休,那么類B中的所有成員函數(shù)都是類A的友元函數(shù)尖飞,可以訪問類A的所有成員,包括public、protected葫松、private屬性的瓦糕。
ex:
Tips:
①友元的關(guān)系是單向的而不是雙向的。如果聲明了類B是類A的友元類腋么,不等同于類A是類B的友元類咕娄,類A中的成員函數(shù)不能訪問類B中的private成員。
②友元的關(guān)系不能傳遞珊擂。如果類B是類A的友元類圣勒,類C是類B的友元類,不等于類C是類A的友元類摧扇。
除非有必要圣贸,一般不建議把整個(gè)類聲明為友元類,而只將某些成員函數(shù)聲明為友元函數(shù)扛稽,這樣更安全一些吁峻。
C++ class和struct的區(qū)別
在C語言中,struct只能包含成員變量在张,不能包含成員函數(shù)用含。而在C++中,struct類似于class帮匾,既可以包含成員變量啄骇,又可以包含成員函數(shù)。
C++中的struct和class基本是通用的瘟斜,唯有幾個(gè)細(xì)節(jié)不同:
①使用class時(shí)缸夹,類中的成員默認(rèn)都是private屬性的;而使用struct時(shí)螺句,結(jié)構(gòu)體中的成員默認(rèn)都是public屬性的虽惭。
②class繼承默認(rèn)是private繼承,而struct繼承默認(rèn)都是public繼承
③class可以使用模板蛇尚,而struct不能
C++ string詳解
string類處理起字符串來會方便很多趟妥,完全可以替代C語言中的字符數(shù)組或字符串指針。
使用string類需要包含頭文件<string>
,看一下string相關(guān)的方法:
void test6() {
string s1; //只定義未初始化佣蓉,編譯器會將默認(rèn)值""賦值給s1
string s2 = "c plus plus";
string s3 = s2;
string s4(5, 's'); //由5個(gè)'s'組成的字符串
int s4len = s4.length();
}
在實(shí)際編程中,有時(shí)候必須要使用C分割的字符串亲雪,比如打開文件時(shí)的路徑勇凭,為此string類提供了一個(gè)轉(zhuǎn)換函數(shù)c_str()
,該函數(shù)能將string字符串轉(zhuǎn)換為C風(fēng)格的字符串。
訪問字符串中的字符
string字符串也可以像C風(fēng)格的字符串一樣按照下標(biāo)來訪問其中的每一個(gè)字符义辕。string字符串的起始下標(biāo)仍是從0開始虾标。
for (int i = 0; i <s4len ; ++i) {
cout<<s4[i]<<" ";
}
字符串的拼接
有了string類,我們可以使用+
或+=
運(yùn)算符來直接拼接字符串灌砖,就不需要使用C語言中的strcat()/strcpy()/malloc()等函數(shù)來拼接字符串了璧函,也不用擔(dān)心空間不夠會溢出了傀蚌。
string字符串的增刪改查
①插入字符串:
insert()
函數(shù)可以在string字符串中指定的位置插入另一個(gè)字符串,它的一種原型為:
string& insert(size_t pos,const string& str);
pos表示要插入的位置蘸吓,也就是下標(biāo)善炫;str表示要插入的字符串,可以是string字符串库继,也可以是C風(fēng)格的字符串箩艺。
s2.insert(7,"amazing~");
②刪除字符串
erase()
函數(shù)可以刪除string中的一個(gè)字符串,它的原型為:
string& erase(size_t pos = 0, size_t len =npos);
pos表示要刪除的子字符串的起始下標(biāo)宪萄,len表示要刪除子字符串的長度艺谆。如果不指明len的話,那么直接刪除從pos到字符串結(jié)束處的所有字符拜英。
③提取子字符串
substr()
函數(shù)用于從string字符串中提取字符串静汤,它的原型為:
string substr(size_t pos = 0 , size_t len = npos) const;
pos為要提取的子字符串的起始下標(biāo),len為要提取的子字符串的長度居凶。
④字符串查找
string類提供了幾個(gè)與字符串查找有關(guān)的函數(shù)虫给,如下:
1>find()函數(shù)
find函數(shù)用于在string字符串中查找子字符串出現(xiàn)的位置,其中兩種原型為:
size_t find (const string& str, size_t pos = 0) const;
size_t find (const char* s, size_t pos = 0) const;
第一個(gè)參數(shù)為待查找的子字符串排监,可以是string字符串狰右,也可以是C風(fēng)格的字符串。第二個(gè)參數(shù)為開始查找的位置舆床;如果不指名棋蚌,則從第0個(gè)字符開始查找。
2>rfind()函數(shù)
find()函數(shù)是從第二個(gè)參數(shù)開始往后找挨队,而rfind()函數(shù)則最多查找到第二個(gè)參數(shù)處谷暮,如果到第二個(gè)參數(shù)所指定的下標(biāo)還沒有找到子字符串,則返回一個(gè)無窮大值盛垦。
3>find_first_of()函數(shù)
find_first_of()函數(shù)用于查找子字符串和字符串共同具有的字符在字符串中首次出現(xiàn)的位置
ex:
void test6() {
string s1; //只定義未初始化湿弦,編譯器會將默認(rèn)值""賦值給s1
string s2 = "c plus plus";
string s3 = s2;
string s4(5, 's'); //由5個(gè)'s'組成的字符串
int s4len = s4.length();
for (int i = 0; i <s4len ; ++i) {
cout<<s4[i]<<" ";
}
s2.insert(7,"amazing~");
//刪除字符串
s2.erase(0,2);
int index = s2.find('p',3);
int index2 = s2.find_first_of('s',5);
int index3 = s2.rfind('s',10);
}
本章小結(jié)
里面的內(nèi)容在上面都有提到,貼個(gè)鏈接吧:
C++類和對象的總結(jié)
C++引用
C++引用入門
參數(shù)的傳遞本質(zhì)上是一次賦值的過程腾夯,賦值就是對內(nèi)存進(jìn)行拷貝颊埃。所謂內(nèi)存拷貝,是指將一塊內(nèi)存上的數(shù)據(jù)復(fù)制到另一塊內(nèi)存上蝶俱。
對于char班利、bool、int等基本類型的數(shù)據(jù)榨呆,他們占用的字節(jié)往往只有幾個(gè)字節(jié)罗标,內(nèi)存拷貝十分迅速。而數(shù)組、結(jié)構(gòu)體闯割、對象是一系列數(shù)據(jù)的集合彻消,數(shù)據(jù)的數(shù)量沒有限制,可能很少宙拉,也可能成千上萬宾尚,對它們進(jìn)行頻繁的內(nèi)存拷貝可能消耗很多時(shí)間,拖慢程序的執(zhí)行效率鼓黔。
所以C/C++禁止在函數(shù)調(diào)用時(shí)直接傳遞數(shù)組的內(nèi)容央勒,而是【強(qiáng)制傳遞數(shù)組指針】。而對于結(jié)構(gòu)體和對象沒有這種限制澳化,調(diào)用函數(shù)時(shí)既可以傳遞指針崔步,也可以直接傳遞內(nèi)容;
在C++中缎谷,有一種比指針更加便捷的傳遞聚合類型數(shù)據(jù)的方式井濒,那就是引用。
引用可以看作是數(shù)據(jù)的一個(gè)別名列林,通過這個(gè)別名和原來的名字都能夠找到這份數(shù)據(jù)瑞你。類似Windows的快捷方式,一個(gè)可執(zhí)行程序可以有多個(gè)快捷方式希痴,所以一個(gè)數(shù)據(jù)也可以有多個(gè)別名者甲。
引用的定義類似于指針,只是用&
取代了*
砌创,語法格式為:
type &name = data;
Type是被引用的數(shù)據(jù)的類型虏缸,name是引用的名稱,data是被引用的數(shù)據(jù)嫩实。引用必須在定義的同時(shí)初始化刽辙,并且以后也要從一而終,不能再引用其他數(shù)據(jù)甲献,這有點(diǎn)類似于常量.
EX:
void testA() {
int a = 99;
int &r = a;
cout << "a value:" << a << endl;
cout << "r value:" << r << endl;
}
變量r就是變量a的引用宰缤,它們用來指代同一份數(shù)據(jù);也可以說r是變量a的另一個(gè)名字晃洒。
注意慨灭,引用在定義時(shí)需要添加&,在使用時(shí)不能加&球及,因?yàn)槭褂脮r(shí)添加&表示取地址缘挑。
但有一個(gè)問題,這時(shí)候如果修改r的值桶略,a的值也會隨之改變,因?yàn)閞和a都指向同一個(gè)地址。
如果不希望通過引用來修改原始的數(shù)據(jù)际歼,可以在定義的時(shí)候添加const關(guān)鍵字惶翻,形式為:
const type &name = value;
也可以是:
type const &name = value;
C++引用作為函數(shù)參數(shù)
在定義或聲明函數(shù)時(shí),我們可以將函數(shù)的形參指定為引用的形式鹅心,這樣在調(diào)用函數(shù)時(shí)就會將實(shí)參和形參綁定在一起吕粗,讓它們都指代同一份數(shù)據(jù)。如此一來旭愧,如果在函數(shù)體中修改了形參的數(shù)據(jù)颅筋,那么實(shí)參的數(shù)據(jù)也會被修改,從而擁有”在函數(shù)內(nèi)部影響函數(shù)外部數(shù)據(jù)”的效果输枯。
Ex:
void swapA(int &r1, int &r2) {
int temp = r1;
r1 = r2;
r2 = temp;
}
void swapB(int *p1, int *p2) {
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
按引用傳參在使用形式上比指針更加直觀议泵,鼓勵使用,一般可以代替指針桃熄。
C++引用作為函數(shù)返回值
ex:
int &plusA(int &r) {
r += 10;
return r;
}
void testB() {
int a = 10;
int &r = a;
int b = plusA(r);
cout << "result:" << b << endl;
}
C++引用的本質(zhì)
其實(shí)引用只是對指針進(jìn)行了簡單的分裝,它的底層依然是通過指針實(shí)現(xiàn)的,引用占用的內(nèi)存和指針占用的內(nèi)存長度一樣误窖,在32位環(huán)境下是4個(gè)字節(jié)猴伶,在64位環(huán)境下是8個(gè)字節(jié),之所以不能獲取引用的地址螟深,
是因?yàn)榫幾g器進(jìn)行了內(nèi)存了內(nèi)部轉(zhuǎn)換谐宙。
C++的發(fā)明人Bjarne Stroustrup說過,他在C++中引入引用的直接目的就是為了讓代碼書寫更漂亮界弧,尤其是后面運(yùn)算符重載凡蜻。
引用和指針的區(qū)別
①引用必須在定義時(shí)初始化,并且以后也要從一而終夹纫,不能再指向其他數(shù)據(jù)咽瓷;而指針沒有這個(gè)限制,指針在定義時(shí)不必賦值舰讹,以后也能指向任意數(shù)據(jù)茅姜。
②可以有const指針,但是沒有const引用月匣。
③指針可以有多級钻洒,但是引用只能有一級,例如int **p
是合法的锄开,而int &&r
是不合法的素标。
④指針和引用的自增(++)自減(--)運(yùn)算意義不一樣。對指針使用++表示指向下一份數(shù)據(jù)萍悴,對引用使用++表示它所指代的數(shù)據(jù)本身加1头遭;自減(--)也是類似的道理寓免。
C++引用不能綁定到臨時(shí)數(shù)據(jù)
指針是數(shù)據(jù)貨代嗎在內(nèi)存中的地址,指針變量指向的就是內(nèi)存中的數(shù)據(jù)或代碼计维。這里注意這個(gè)關(guān)鍵字袜香,內(nèi)存。指針只能指向內(nèi)存鲫惶,不能指向寄存器蜈首,因?yàn)榧拇嫫骱陀脖P沒法尋址。
C++代碼中大部分內(nèi)容都是放在內(nèi)存中的欠母,例如定義的變量欢策、創(chuàng)建的對象、字符串常量赏淌、函數(shù)形參踩寇、函數(shù)體本身、new或malloc分配的內(nèi)存猜敢,這些內(nèi)容都可以用&
來獲取地址姑荷,進(jìn)而用指針指向他們。
但除此之外缩擂,表達(dá)式的結(jié)果鼠冕、函數(shù)的返回值等,他們可能放在內(nèi)存中胯盯,也可能會放在寄存器中懈费。一旦被放到寄存器中,就沒辦法用&
獲取他們的地址博脑,也就沒法用指針指向他們了憎乙。
繼承與派生
繼承可以理解為一個(gè)類從另一個(gè)類獲取成員變量和成員函數(shù)的過程。例如類B繼承于類A叉趣,那么B就擁有A的成員變量和成員函數(shù)泞边。(這個(gè)和java沒什么不同)
在C++中,派生和繼承是一個(gè)概念疗杉,只是站的角度不同阵谚。繼承是兒子接收父親的產(chǎn)業(yè),派生是父親把產(chǎn)業(yè)傳承給兒子烟具。
被繼承的類稱為父類或基類梢什,繼承的類被稱為子類或派生類。子類和父類通常放在一起稱呼朝聋,基類和派生類通常放在一起稱呼嗡午。(和Java類似啦)
#include <iostream>
using namespace std;
class People {
public:
void setname(char *name);
void setage(int age);
char *getname();
int getage();
private :
char *m_name;
int m_age;
};
void People::setname(char *name) {
m_name = name;
}
void People::setage(int age) {
m_age = age;
}
char *People::getname() {
return m_name;
}
int People::getage() {
return m_age;
}
class Student : public People {
public:
void setscore(float score);
float getscore();
private:
float m_score;
};
void Student::setscore(float score) {
m_score = score;
}
float Student::getscore() {
return m_score;
}
void testX() {
Student stu;
stu.setname("小明");
stu.setage(22);
stu.setscore(99.5);
cout << stu.getname() << "的年齡是:" << stu.getage() << ",成績是:" << stu.getscore() << endl;
}
int main() {
testX();
return 1;
}
這里繼承可能跟java不同的點(diǎn)在于繼承的時(shí)候冀痕,父類名前也要加public荔睹,表示共有繼承狸演,繼承方式包括public、private和protected应媚,此項(xiàng)可選严沥,默認(rèn)為private。
繼承的一般語法為:
class 派生類名:[繼承方式] 基類名{
派生類新增加的成員
};
C++三種繼承方式
繼承方式限定了基類成員在派生類中的訪問權(quán)限中姜,包括public、private和protected跟伏。
protected成員和private成員類似丢胚,也不能通過對象訪問。但是當(dāng)存在繼承關(guān)系時(shí)受扳,protected和private就不一樣了:基類中的protected成員可以在派生類中使用携龟,而基類中的private成員不能在派生類中使用。
不同的繼承方式會影響基類成員在派生類中的訪問權(quán)限勘高。
1.public繼承方式
基類中所有public成員在派生類中為public屬性
基類中所有protected成員在派生類中為protected屬性峡蟋;
基類中所有private成員在派生類中不能使用;
2.protected繼承方式
基類中的所有public成員在派生類中為protected屬性华望;
基類中的所有protected成員在派生類中為protected屬性蕊蝗;
基類中的所有private成員在派生類中不能使用;
3.private繼承方式
基類中的所有public成員在派生類中均為private屬性赖舟;
基類中的所有protected成員在派生類中均為private屬性蓬戚;
基類中的所有private成員在派生類中不能使用。
基類成員在派生類中的訪問權(quán)限不得高于繼承方式中指定的權(quán)限宾抓。例如繼承方式為protected時(shí)子漩,那么基類成員在派生類中的訪問權(quán)限最高為protected,高于protected的會降級為protected石洗,但低于protected不會升級幢泼。
也就是說,繼承方式中的public讲衫、protected缕棵、private是用來指明基類成員在派生類中的最高訪問權(quán)限的。
在派生類中訪問基類private成員的唯一方法就是借助基類的非private成員函數(shù)焦人,如果基類沒有非private成員函數(shù)挥吵,那么該成員在派生類中將無法訪問。
改變訪問權(quán)限
使用using關(guān)鍵字可以改變基類成員在派生類中的訪問權(quán)限花椭,例如將public改為private忽匈、將protected改為public。
Tips:using只能改變基類中public和protected成員的訪問權(quán)限矿辽,不能改變private成員的訪問權(quán)限丹允,因?yàn)榛愔衟rivate成員在派生類中是不可見的郭厌,根本不能使用,所以基類中的private成員在派生類中無論如何都不能訪問雕蔽。
#include <iostream>
using namespace std;
class People {
public:
void show();
protected:
char *m_name;
int m_age;
};
void People::show() {
cout<<m_name<<"的年齡是:"<<m_age<<endl;
}
class Student : public People {
public:
void learning();
public:
using People::m_name;//將protected改為public
using People::m_age; //將protected改為public
float m_score;
private:
using People::show; //將public改為private
};
void Student::learning() {
cout<<"我是:"<<m_name<<",今年"<<m_age<<"歲折柠,這次考了"<<m_score<<endl;
}
void testX() {
Student stu;
stu.m_name = "小明";
stu.m_age = 16;
stu.m_score = 99.5f;
//stu.show();//compile error
stu.learning();
}
int main() {
testX();
return 1;
}
C++繼承時(shí)的名字遮蔽問題
如果派生類中的成員和基類中的成員重名,那么就會遮蔽從基類繼承過來的成員批狐。
所謂遮蔽扇售,就是在派生類中使用該成員時(shí),實(shí)際上使用的是派生類新增的成員嚣艇,而不是從基類繼承來的承冰。
但是基類中的函數(shù)仍然可以調(diào)用,不過要加上類名和域解析符食零。ex:
//使用的是從基類繼承來的成員函數(shù)
stu.People::show();
基類成員函數(shù)和派生類成員函數(shù)不構(gòu)成重載
基類成員和派生類成員的名字一樣時(shí)會造成遮蔽困乒,這句話對于成員變量很好理解,對于成員函數(shù)要引起注意贰谣,不管函數(shù)的參數(shù)如何娜搂,只要名字一樣就會造成遮蔽。煥換句話說吱抚,基類成員函數(shù)和派生類成員函數(shù)不會構(gòu)成重載百宇,如果派生類有同名函數(shù),那么就會遮蔽基類中的所有同名函數(shù)频伤,不管它們的參數(shù)是否一樣恳谎。
C++類繼承時(shí)的作用域嵌套
//TODO
C++繼承時(shí)的對象內(nèi)存模型
//TODO
C++基類和派生類的構(gòu)造函數(shù)
之前說過基類的成員函數(shù)可以被繼承,可以通過派生類的對象訪問憋肖,但這僅僅指的是普通的成員函數(shù)因痛,類的構(gòu)造函數(shù)不能被繼承。構(gòu)造函數(shù)不能被繼承是有道理的岸更,因?yàn)榧词贡焕^承了鸵膏,它的名字和派生類的名字也不一樣,不能成為派生類的構(gòu)造函數(shù)怎炊,當(dāng)然更不能成為普通的成員函數(shù)谭企。(和java不同)
在設(shè)計(jì)派生類時(shí),對繼承過來的成員變量的初始化工作也要由派生類的構(gòu)造函數(shù)完成评肆,但是大部分基類都有private的成員變量债查,他們在派生類中無法訪問,更不能使用派生類的構(gòu)造函數(shù)來初始化瓜挽。
解決這個(gè)問題的思路是:
在派生類的構(gòu)造函數(shù)中調(diào)用基類的構(gòu)造函數(shù)盹廷。
EX:
class Human{
protected:
char *m_name;
int m_age;
public:
Human(char *,int);
};
Human::Human(char * name, int age):m_name(name),m_age(age) {}
class Child:public Human{
private:
float m_score;
public:
Child(char*,int ,float);
void display();
};
Child::Child(char *name,int age,float score):Human(name,age),m_score(score){
}
void Child::display() {
cout<<m_name<<"的年齡是:"<<m_age<<",其成績?yōu)椋?<<m_score<<endl;
}
void testY(){
Child child("二狗",12,87.5);
child.display();
}
構(gòu)造函數(shù)的調(diào)用順序
基類構(gòu)造函數(shù)總是被優(yōu)先調(diào)用,這說明在創(chuàng)建派生類對象時(shí)久橙,會先調(diào)用基類構(gòu)造函數(shù)俄占,再調(diào)用派生類構(gòu)造函數(shù)管怠,派生類構(gòu)造函數(shù)中只能調(diào)用直接基類的構(gòu)造函數(shù),不能調(diào)用間接基類的缸榄。
C++基類和派生類的析構(gòu)函數(shù)
和構(gòu)造函數(shù)一樣渤弛,析構(gòu)函數(shù)也不能被繼承。與構(gòu)造函數(shù)不同的是甚带,在派生類的析構(gòu)函數(shù)中不用顯式地調(diào)用基類的析構(gòu)函數(shù)她肯,因?yàn)槊總€(gè)類只有一個(gè)析構(gòu)函數(shù),編譯器知道如何選擇鹰贵,無須程序員干涉辕宏。
另外析構(gòu)函數(shù)的執(zhí)行順序和構(gòu)造函數(shù)的執(zhí)行順序也剛好相反:
1.創(chuàng)建派生類對象時(shí),析構(gòu)函數(shù)的執(zhí)行順序和繼承順序相同砾莱,即先執(zhí)行基類構(gòu)造函數(shù),再執(zhí)行派生類構(gòu)造函數(shù)凄鼻。
2.而銷毀派生類對象時(shí)腊瑟,析構(gòu)函數(shù)的執(zhí)行順序和繼承順序相反,即先執(zhí)行派生類析構(gòu)函數(shù)块蚌,再執(zhí)行基類析構(gòu)函數(shù)闰非。
ex:
class A{
public:
A(){cout<<"A constructor"<<endl;}
~A(){cout<<"A destructor"<<endl;}
};
class B: public A{
public:
B(){cout<<"B constructor"<<endl;}
~B(){cout<<"B destructor"<<endl;}
};
class C: public B{
public:
C(){cout<<"C constructor"<<endl;}
~C(){cout<<"C destructor"<<endl;}
};
void testZ(){
C test;
}
輸出:
A constructor
B constructor
C constructor
C destructor
B destructor
A destructor
C++多繼承詳解
之前派生類都只有一個(gè)基類,稱為單繼承峭范。除此之外财松,C++也支持多繼承,一個(gè)派生類可以有兩個(gè)或多個(gè)基類纱控。
(但是多繼承容日讓代碼邏輯復(fù)雜辆毡、思路混亂,中小型項(xiàng)目中較少使用甜害,后來的java舶掖、c#、PHP取消了多繼承尔店。)
多繼承的語法比較簡單眨攘,將多個(gè)基類用逗號隔開即可。例如聲明了類A嚣州、類B和類C鲫售,那么可以用下面方式聲明派生類D:
class D:public A,private B,private C{
}
多繼承形式下的構(gòu)造函數(shù)和單繼承形式基本相同,只是要在派生類的構(gòu)造函數(shù)中調(diào)用多個(gè)基類的構(gòu)造函數(shù)该肴。如下:
D(形參列表):A(實(shí)參列表)情竹,B(實(shí)參列表),C(實(shí)參列表){
}
基類構(gòu)造函數(shù)的調(diào)用順序和他們在派生類構(gòu)造函數(shù)中出現(xiàn)的順序無關(guān),而是和聲明派生類時(shí)基類出現(xiàn)的順序相同沙庐。
ex:
class BaseA {
public:
BaseA(int a, int b);
~BaseA();
private:
int m_a;
int m_b;
};
class BaseB {
public:
BaseB(int c, int d);
~BaseB();
private:
int m_c;
int m_d;
};
class BaseC {
public :
BaseC(int a, int d);
~BaseC();
private:
int m_a;
int m_d;
};
class Deliver : public BaseC, public BaseA, public BaseB {
public:
Deliver(int a, int b, int c, int d, int e);
~Deliver();
private:
int m_e;
};
BaseA::BaseA(int a, int b) : m_a(a), m_b(b) {
cout << "BaseA constructor" << endl;
}
BaseA::~BaseA() {
cout << "BaseA destructor" << endl;
}
BaseB::BaseB(int c, int d) : m_c(c), m_d(d) {
cout << "BaseB constructor" << endl;
}
BaseB::~BaseB() {
cout << "BaseB destructor" << endl;
}
BaseC::BaseC(int a, int d) : m_a(a), m_d(d) {
cout << "BaseC constructor" << endl;
}
BaseC::~BaseC() {
cout << "BaseC destructor" << endl;
}
Deliver::Deliver(int a, int b, int c, int d, int e) : BaseC(a, d), BaseA(a, b), BaseB(c, d), m_e(e) {
cout << "Deliver constructor" << endl;
}
Deliver::~Deliver() {
cout << "Deliver destructor" << endl;
}
void testV() {
Deliver deliver(1, 2, 3, 4, 5);
}
int main() {
testV();
return 1;
}
C++虛繼承和虛基類詳解
多繼承是指從多個(gè)直接基類中產(chǎn)生派生類的能力鲤妥,多繼承的派生類繼承了所有父類的成員佳吞。但多個(gè)基類可能會帶來錯(cuò)綜復(fù)雜的問題,比如命名沖突棉安。
C++虛繼承和虛基類詳解
正如文章所提到的底扳,主要就是菱形繼承。
比如A繼承B,C,然后B,C又都繼承D贡耽。這樣B衷模,C都有同樣的從D中繼承的成員變量,編譯器就不知道選用哪個(gè)蒲赂,就會產(chǎn)生歧義阱冶。
為了解決多繼承時(shí)的命名沖突和冗余數(shù)據(jù)問題,C++提出了虛繼承滥嘴,使得在派生類中只保留一份間接基類的成員木蹬。
虛繼承就是在繼承方式前面加上virtual
關(guān)鍵字。
虛繼承的目的是讓某個(gè)類做出聲明若皱,承諾愿意共享它的基類镊叁。其中,這個(gè)被共享的基類就被稱為虛基類走触。在這種機(jī)制下晦譬,不論虛基類在繼承體系中出現(xiàn)了多少次,在派生類中都只包含一份虛基類的成員互广。
虛基類只影響從指定虛基類的派生類中進(jìn)一步派生出來的類敛腌,它不會影響派生類本身。也就是不影響B(tài),C惫皱,只影響D像樊。
C++虛繼承時(shí)的構(gòu)造參數(shù)
在虛繼承中,虛基類是由最終的派生類初始化的逸吵;換句話說凶硅,最終派生類的構(gòu)造函數(shù)必須要調(diào)用虛基類的構(gòu)造函數(shù)。對最終的派生類來說扫皱,虛基類是間接基類足绅,而不是直接基類。這跟普通繼承不同韩脑,在普通繼承中氢妈,派生類構(gòu)造函數(shù)中只能調(diào)用直接基類的構(gòu)造函數(shù),不能調(diào)用間接基類的段多。
#include <iostream>
using namespace std;
//虛基類A
class A{
public:
A(int a);
protected:
int m_a;
};
A::A(int a): m_a(a){ }
//直接派生類B
class B: virtual public A{
public:
B(int a, int b);
public:
void display();
protected:
int m_b;
};
B::B(int a, int b): A(a), m_b(b){ }
void B::display(){
cout<<"m_a="<<m_a<<", m_b="<<m_b<<endl;
}
//直接派生類C
class C: virtual public A{
public:
C(int a, int c);
public:
void display();
protected:
int m_c;
};
C::C(int a, int c): A(a), m_c(c){ }
void C::display(){
cout<<"m_a="<<m_a<<", m_c="<<m_c<<endl;
}
//間接派生類D
class D: public B, public C{
public:
D(int a, int b, int c, int d);
public:
void display();
private:
int m_d;
};
D::D(int a, int b, int c, int d): A(a), B(90, b), C(100, c), m_d(d){ }
void D::display(){
cout<<"m_a="<<m_a<<", m_b="<<m_b<<", m_c="<<m_c<<", m_d="<<m_d<<endl;
}
int main(){
B b(10, 20);
b.display();
C c(30, 40);
c.display();
D d(50, 60, 70, 80);
d.display();
return 0;
}
在最終派生類 D 的構(gòu)造函數(shù)中首量,除了調(diào)用 B 和 C 的構(gòu)造函數(shù),還調(diào)用了 A 的構(gòu)造函數(shù),這說明 D 不但要負(fù)責(zé)初始化直接基類 B 和 C加缘,還要負(fù)責(zé)初始化間接基類 A鸭叙。而在以往的普通繼承中,派生類的構(gòu)造函數(shù)只負(fù)責(zé)初始化它的直接基類拣宏,再由直接基類的構(gòu)造函數(shù)初始化間接基類沈贝,用戶嘗試調(diào)用間接基類的構(gòu)造函數(shù)將導(dǎo)致錯(cuò)誤。
C++向上轉(zhuǎn)型
在c++中經(jīng)常會發(fā)生數(shù)據(jù)類型的轉(zhuǎn)換勋乾,例如將int類型的數(shù)據(jù)賦值給float類型的變量時(shí)宋下,編譯器會把int類型的數(shù)據(jù)轉(zhuǎn)換為float類型再賦值;反過來辑莫,float類型的數(shù)據(jù)在經(jīng)過類型轉(zhuǎn)換后也可以賦值給int類型的變量学歧。
類其實(shí)也是一種數(shù)據(jù)類型,也可以發(fā)生數(shù)據(jù)類型轉(zhuǎn)換各吨,不過這種轉(zhuǎn)換只有在基類和派生類之間才有意義枝笨,并且只能將派生類賦值給基類,包括將派生類對象賦值給基類對象揭蜒、將派生類指針賦值給基類指針伺帘、將派生類引用賦值給基類引用,這在C++中被稱為向上轉(zhuǎn)型忌锯。相應(yīng)的,將基類賦值給派生類稱為向下轉(zhuǎn)型领炫。
向上轉(zhuǎn)型十分安全偶垮,由編譯器自動完成;向下轉(zhuǎn)型有風(fēng)險(xiǎn)帝洪,需要程序員手動干預(yù)似舵。
ex:
#include <iostream>
using namespace std;
//基類
class A{
public:
A(int a);
public:
void display();
public:
int m_a;
};
A::A(int a): m_a(a){ }
void A::display(){
cout<<"Class A: m_a="<<m_a<<endl;
}
//派生類
class B: public A{
public:
B(int a, int b);
public:
void display();
public:
int m_b;
};
B::B(int a, int b): A(a), m_b(b){ }
void B::display(){
cout<<"Class B: m_a="<<m_a<<", m_b="<<m_b<<endl;
}
int main(){
A a(10);
B b(66, 99);
//賦值前
a.display();
b.display();
cout<<"--------------"<<endl;
//賦值后
a = b;
a.display();
b.display();
return 0;
}
輸出結(jié)果為:
Class A: m_a=10
Class B: m_a=66, m_b=99
Class A: m_a=66
Class B: m_a=66, m_b=99
賦值的本質(zhì)是將現(xiàn)有的數(shù)據(jù)寫入已分配好的內(nèi)存中,對象的內(nèi)存只包含了成員變量葱峡,所以對象之間的賦值時(shí)成員變量的賦值砚哗,成員函數(shù)不存在賦值問題。
這種轉(zhuǎn)換關(guān)系是不可逆的砰奕,只能用派生類對象給基類對象賦值蛛芥,而不能用基類對象給派生類賦值。理由很簡單军援,基類不包含派生類的成員變量仅淑,無法對派生類的成員變量賦值。同理胸哥,同一基類的不同派生類對象之間也不能賦值涯竟。
將派生類指針賦值給基類指針
C++多態(tài)與虛函數(shù)
面向?qū)ο蟪绦蛟O(shè)計(jì)語言統(tǒng)一都有封裝汰聋、繼承和多態(tài)三種機(jī)制。
多態(tài)指的是同一名字的事物可以完成不同的功能泉蝌。多態(tài)可以分為編譯時(shí)多態(tài)和運(yùn)行時(shí)多態(tài)榄棵。前者主要指函數(shù)的重載、對重載函數(shù)的調(diào)用筐钟,在編譯時(shí)就能根據(jù)實(shí)參確定應(yīng)該調(diào)用哪個(gè)函數(shù)揩瞪,因此叫編譯時(shí)多態(tài);而后者則和繼承盗棵、虛函數(shù)等概念有關(guān)壮韭。本章提及的多態(tài)都是運(yùn)行時(shí)多態(tài)。
ex:
//
// Created by 18041 on 2022/3/31.
//
#include "chapter5.h"
#include <iostream>
using namespace std;
class People {
public:
People(char *name, int age);
void display();
protected:
char *m_name;
int m_age;
};
People::People(char *name, int age) : m_name(name), m_age(age) {}
void People::display() {
cout << m_name << "今年" << m_age << "歲了纹因," << "是個(gè)無業(yè)游民" << endl;
}
class Teacher : public People {
public:
Teacher(char *name, int age, int salary);
void display();
protected:
int m_salary;
};
Teacher::Teacher(char *name, int age, int salary) : People(name, age), m_salary(salary) {
}
void Teacher::display() {
cout << m_name << "今年" << m_age << "歲了喷屋," << "是個(gè)教師,每月收入為:" << m_salary << endl;
}
void testAA() {
People *p = new People("王小二", 23);
p->display();
p = new Teacher("李二狗", 24, 6000);
p->display();
}
int main() {
testAA();
return 1;
}
輸出結(jié)果:
王小二今年23歲了瞭恰,是個(gè)無業(yè)游民
李二狗今年24歲了屯曹,是個(gè)無業(yè)游民
當(dāng)基類指針p指向派生類Teacher對象時(shí),雖然使用了Teacher的成員變量惊畏,但是卻沒有使用它的成員函數(shù)恶耽,導(dǎo)致結(jié)果不倫不類。
也就是說:通過基類指針只能訪問派生類的成員變量颜启,但是不能訪問派生類的成員函數(shù)偷俭。
為了消除這種尷尬,讓基類指針能夠訪問派生類的成員函數(shù)缰盏,C++增加了虛函數(shù)涌萤。只需要在函數(shù)聲明前面增加virtual關(guān)鍵字。
ex:
class People {
public:
People(char *name, int age);
virtual void display();
protected:
char *m_name;
int m_age;
};
People::People(char *name, int age) : m_name(name), m_age(age) {}
void People::display() {
cout << m_name << "今年" << m_age << "歲了口猜," << "是個(gè)無業(yè)游民" << endl;
}
class Teacher : public People {
public:
Teacher(char *name, int age, int salary);
virtual void display();
protected:
int m_salary;
};
輸出:
王小二今年23歲了负溪,是個(gè)無業(yè)游民
李二狗今年24歲了,是個(gè)教師济炎,每月收入為:6000
注意基類和派生類的成員函數(shù)都需要添加virtual關(guān)鍵字川抡。
有了虛函數(shù),基類指針指向基類對象時(shí)就使用基類的成員须尚,包括成員函數(shù)和成員變量崖堤,指向派生類對象時(shí)就使用派生類的成員。換言之耐床,基類指針可以按照基類的方式來做事倘感,也可以按照派生類的方式來做事,他有多種形態(tài)咙咽,或者說有多種表現(xiàn)形式老玛,我們稱這種現(xiàn)象為多態(tài)。
C++提供多態(tài)的目的是:
可以通過基類指針對所有派生類的成員變量和成員函數(shù)進(jìn)行全方位的訪問,尤其是成員函數(shù)蜡豹。如果沒有多態(tài)麸粮,只能訪問成員變量。
通過引用來實(shí)現(xiàn)多態(tài):
EX:
void testBB() {
People p("王小二", 23);
Teacher t("李二狗", 46, 8300);
People &rp = p;
People &rt = t;
rp.display();
rt.display();
}
不過引用不像指針靈活镜廉,指針可以隨時(shí)改變指向弄诲,而引用只能指代固定的對象,在多態(tài)性方面缺乏表現(xiàn)力娇唯。
C++虛函數(shù)注意事項(xiàng)以及構(gòu)成多態(tài)的條件
C++虛函數(shù)對于多態(tài)具有決定性的作用齐遵,有虛函數(shù)才能構(gòu)成多態(tài)。
說一下虛函數(shù)的構(gòu)成條件:
1.只需要在虛函數(shù)的聲明處加上virtual關(guān)鍵字塔插,函數(shù)定義處可以加也可以不加梗摇。
2.為了方便,可以只將基類中的函數(shù)聲明為虛函數(shù)(派生類中可以不加virtual關(guān)鍵字)想许,這樣所有派生類中具有遮蔽關(guān)系的同名函數(shù)都將自動稱為虛函數(shù)伶授。
3.當(dāng)在基類中定義了虛函數(shù)時(shí)流纹,如果派生類沒有定義新的函數(shù)來遮蔽此函數(shù)糜烹,那么將使用基類的虛函數(shù)。
4.只有派生類的虛函數(shù)覆蓋基類的虛函數(shù)才能構(gòu)成多態(tài)漱凝。(通過基類指針訪問派生類函數(shù))疮蹦。例如基類虛函數(shù)的原型為:
virtual void func();//注意是無參
派生類虛函數(shù)原型為:
virtual void func(int);//注意有參
那么當(dāng)基類指針p指向派生類對象時(shí),語句p -> func(100);
將會出錯(cuò)茸炒,而語句:
p -> func();
將調(diào)用基類的函數(shù)挚币。
5.構(gòu)造函數(shù)不是虛函數(shù)。對于基類的構(gòu)造函數(shù)扣典,它僅僅是派生類構(gòu)造函數(shù)中被調(diào)用,這種機(jī)制不同于繼承慎玖。也就是說贮尖,派生類不繼承基類的構(gòu)造函數(shù),將構(gòu)造函數(shù)聲明為虛函數(shù)沒有什么意義趁怔。
6.析構(gòu)函數(shù)可以聲明為虛函數(shù)湿硝,而且有時(shí)候必須要聲明為虛函數(shù),后面會講润努。
什么時(shí)候聲明虛函數(shù)
首先看成員函數(shù)所在的類是否會作為基類关斜。然后看成員函數(shù)在類的繼承后有無可能被更改功能,如果希望更改其功能的铺浇,一般應(yīng)該將它聲明為虛函數(shù)痢畜。如果成員函數(shù)在類被繼承后功能不需要修改,或派生類用不到該函數(shù),則不要把它聲明為虛函數(shù)丁稀。
C++純虛函數(shù)和抽象類
在C++中吼拥,可以將虛函數(shù)聲明為純虛函數(shù),語法格式為:
virtual 返回值類型 函數(shù)名(函數(shù)參數(shù)) = 0线衫;
純虛函數(shù)沒有函數(shù)體凿可,只有函數(shù)聲明,在虛函數(shù)聲明的結(jié)尾加上 = 0授账,表明此函數(shù)為純虛函數(shù)枯跑。
包含純虛函數(shù)的類的稱為抽象類。
抽象類通常是作為基類白热,讓派生類去實(shí)現(xiàn)純虛函數(shù)敛助。派生類必須實(shí)現(xiàn)純虛函數(shù)才能被實(shí)例化。
EX:
class Line {
public:
Line(float len);
virtual float area() = 0;
virtual float volume() = 0;
protected:
float m_len;
};
Line::Line(float len) : m_len(len) {
}
class Rec : public Line {
public:
Rec(float len,float width);
float area();
protected:
float m_width;
};
Rec::Rec(float len,float width):Line(len),m_width(width){
}
float Rec::area() {
return m_len*m_width;
}
//長方體
class Cuboid : public Rec {
public:
Cuboid(float len,float width,float height);
float area();
float volume();
protected:
float m_height;
};
Cuboid::Cuboid(float len,float width,float height):Rec(len,width),m_height(height){
}
float Cuboid::area() {
return 2*(m_len*m_width+m_len*m_height+m_width*m_height);
}
float Cuboid::volume() {
return m_len*m_width*m_height;
}
void testCC(){
Line *p = new Cuboid(10,20,30);
cout<<"The area of Cuboid is "<<p->area()<<endl;
}
int main() {
testCC();
return 1;
}
在實(shí)際開發(fā)中棘捣,你可以定義一個(gè)抽象基類辜腺,只完成部分功能,未完成的功能交給派生類去實(shí)現(xiàn)乍恐。
抽象基類除了約束派生類的功能评疗,還可以實(shí)現(xiàn)多態(tài)。注意p的類型是Line茵烈,但是他卻可以訪問派生類中的area()和volume(),正是由于在Line類中將這兩個(gè)函數(shù)函定義為純虛函數(shù)百匆;如果不這樣做,那么后面的代碼都是錯(cuò)誤的呜投。
關(guān)于純虛函數(shù)的幾點(diǎn)說明:
1.一個(gè)純虛函數(shù)就可以使類稱為抽象基類加匈,但是抽象基類中除了包含純虛函數(shù)外,還可以包含其它的成員函數(shù)和成員變量仑荐。
2.只有類中的虛函數(shù)才能被聲明為純虛函數(shù)雕拼,普通成員函數(shù)和頂層函數(shù)均不能聲明為純虛函數(shù)。
C++ typeid運(yùn)算符:獲取類型信息
typeid運(yùn)算符用來獲取一個(gè)表達(dá)式的類型信息粘招。類型信息對于編程語言非常重要啥寇,它描述了數(shù)據(jù)的各種屬性:
1.對于基本類型的數(shù)據(jù),類型信息所包含的內(nèi)容比較簡單洒扎,主要是指數(shù)據(jù)的類型辑甜。
2.對于類類型的數(shù)據(jù),即對象袍冷,類型信息是指對象所屬的類磷醋、所包含的成員、所在的繼承關(guān)系等胡诗。
類型信息是創(chuàng)建數(shù)據(jù)的模板邓线,數(shù)據(jù)占用多大內(nèi)存淌友、能進(jìn)行什么樣的操作、該如何操作等褂痰,這些都由它的類型信息決定亩进。
typeid的操作對象既可以是表達(dá)式,也可以是數(shù)據(jù)類型缩歪,兩種使用方法如下:
typeid(dataType)
typeid(expression)
這和sizeof運(yùn)算符非常類似归薛,只不過sizeof有時(shí)候可以省略括號,而typeid必須帶上括號匪蝙。
typeid會把獲取到的類型信息保存到一個(gè)type_info類型的對象里面主籍,并返回該對象的常引用;當(dāng)需要具體的類型信息時(shí)逛球,可以通過成員函數(shù)來提取千元。(類似Java的Class類?).typeid的使用非常靈活颤绕,ex:
C++標(biāo)準(zhǔn)只對type_info類做了很有限的規(guī)定幸海,成員函數(shù)少,功能弱奥务,而且各個(gè)平臺的實(shí)現(xiàn)不一致物独。
可以發(fā)現(xiàn),不像 Java氯葬、C# 等動態(tài)性較強(qiáng)的語言挡篓,C++ 能獲取到的類型信息非常有限,也沒有統(tǒng)一的標(biāo)準(zhǔn)帚称,如同“雞肋”一般官研,大部分情況下我們只是使用重載過的“==”運(yùn)算符來判斷兩個(gè)類型是否相同。
由上所述看似typeid有些雞肋闯睹,但實(shí)際上在某個(gè)方面還是會大量使用typeid關(guān)鍵字的
判斷類型是否相等
ex:
例子中可以用來判斷基本類型戏羽,可以判斷類是否相當(dāng),如下:
運(yùn)算符重載
C++運(yùn)算符重載基礎(chǔ)教程
所謂重載楼吃,就是賦予新的含義始花。函數(shù)重載(Function Overloading)可以讓一個(gè)函數(shù)名有多種功能,在不同情況下進(jìn)行不同的操作所刀。運(yùn)算符重載(Operator Overloading)也是一個(gè)道理,同一個(gè)運(yùn)算符可以有不同的功能捞挥。
ex:
#include "chapter6.h"
#include <iostream>
using namespace std;
//運(yùn)算符重載實(shí)現(xiàn)復(fù)數(shù)的加法運(yùn)算
class complex {
public:
complex();
complex(double real, double imag);
public:
complex operator+(const complex &A) const;
void display() const;
private:
double m_real;//實(shí)部
double m_imag;//虛部
};
complex::complex() : m_real(0.0), m_imag(0.0) {}
complex::complex(double real, double imag) : m_real(real), m_imag(imag) {}
//實(shí)現(xiàn)運(yùn)算符重載
complex complex::operator+(const complex &A) const {
complex B;
B.m_real = this->m_real + A.m_real;
B.m_imag = this->m_imag + A.m_imag;
return B;
}
void complex::display() const {
cout<<m_real<<" + "<< m_imag <<"i"<<endl;
}
void testGG(){
complex c1(4.3,5.8);
complex c2(2.4,3.7);
complex c3;
c3 = c1 + c2;
c3.display();
}
int main(){
testGG();
return 0;
}
運(yùn)算符重載其實(shí)就是定義一個(gè)函數(shù)浮创,在函數(shù)體內(nèi)實(shí)現(xiàn)想要的功能,當(dāng)用到該運(yùn)算符時(shí)砌函,編譯器會自動調(diào)用這個(gè)函數(shù)斩披。也就是說溜族,運(yùn)算符重載是通過函數(shù)實(shí)現(xiàn)的,它本質(zhì)上是函數(shù)重載垦沉。
運(yùn)算符重載的格式為:
返回值類型 operator 運(yùn)算符名稱 (形參表列){
//TODO:
}
operator是關(guān)鍵字煌抒,專門用于定義重載運(yùn)算符的函數(shù)。我們可以將operator 運(yùn)算符名稱
這一部分看作函數(shù)名厕倍,對于上面的代碼寡壮,函數(shù)名就是operator +
。
運(yùn)算符重載函數(shù)除了函數(shù)名有特定的格式讹弯,其他地方和普通函數(shù)并沒有區(qū)別
全局范圍內(nèi)重載運(yùn)算符
運(yùn)算符重載函數(shù)不僅可以作為類的成員函數(shù)况既,還可以作為全局函數(shù)。
修改上面代碼组民,ex:
#include <iostream>
using namespace std;
class complex{
public:
complex();
complex(double real, double imag);
public:
void display() const;
//聲明為友元函數(shù)
friend complex operator+(const complex &A, const complex &B);
private:
double m_real;
double m_imag;
};
complex operator+(const complex &A, const complex &B);
complex::complex(): m_real(0.0), m_imag(0.0){ }
complex::complex(double real, double imag): m_real(real), m_imag(imag){ }
void complex::display() const{
cout<<m_real<<" + "<<m_imag<<"i"<<endl;
}
//在全局范圍內(nèi)重載+
complex operator+(const complex &A, const complex &B){
complex C;
C.m_real = A.m_real + B.m_real;
C.m_imag = A.m_imag + B.m_imag;
return C;
}
int main(){
complex c1(4.3, 5.8);
complex c2(2.4, 3.7);
complex c3;
c3 = c1 + c2;
c3.display();
return 0;
}
運(yùn)算符重載函數(shù)不是complex類的成員函數(shù)(雖然是定義在此類之中),但是卻用到了complex類的private成員變量棒仍,所以必須在complex類中將該函數(shù)聲明為友元函數(shù)。
運(yùn)算符重載所實(shí)現(xiàn)的功能雖然完全可以用函數(shù)替代臭胜,但運(yùn)算符重載使得程序書寫更加人性化莫其,易于閱讀。運(yùn)算符被重載后耸三,原有的功能仍然保留乱陡,沒有喪失或改變。
通過運(yùn)算符重載吕晌,擴(kuò)大了C++已有運(yùn)算符的功能蛋褥,使之能用于對象。
運(yùn)算符重載時(shí)要遵循的規(guī)則
1.并不是所有的運(yùn)算符都可以重載睛驳,能夠重載的運(yùn)算符包括:
+ - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> <<= >>= == != <= >= && || ++ -- , ->* -> () [] new new[] delete delete[]
長度運(yùn)算符sizeof
烙心、條件運(yùn)算符?:
、成員選擇符.
和 域解析運(yùn)算符::
不能被重載乏沸。
2.重載不能改變運(yùn)算符的優(yōu)先級和結(jié)合性
3.重載不會改變運(yùn)算符的用法淫茵,原有有幾個(gè)操作符、操作數(shù)在左邊還是右邊蹬跃,這些都不會被改變匙瘪。例如~
號右邊只有一個(gè)操作數(shù),+
號總是出現(xiàn)在兩個(gè)操作數(shù)之間蝶缀,重載后也必須如此丹喻。
4.運(yùn)算符重載函數(shù)不能有默認(rèn)的參數(shù),否則就改變了運(yùn)算符操作數(shù)的個(gè)數(shù)翁都,這顯然是錯(cuò)誤的碍论。
5.運(yùn)算符重載函數(shù)既可以作為類的成員函數(shù),也可以作為全局函數(shù)柄慰。
①當(dāng)運(yùn)算符重載函數(shù)作為類的成員函數(shù)的時(shí)候
二元運(yùn)算符的參數(shù)只有一個(gè)鳍悠,一元運(yùn)算符不需要參數(shù)税娜。之所以少一個(gè)參數(shù),是因?yàn)檫@個(gè)參數(shù)是隱含的藏研。
比如之前定義的complex里重載的加法運(yùn)算符:
complex operator+(const complex &A) const
當(dāng)執(zhí)行“
c3 = c1 + c2
會被轉(zhuǎn)換成:
c3 = c1.operator+(c2);
通過this指針隱含的訪問c1的成員變量敬矩。
②當(dāng)運(yùn)算符重載函數(shù)作為全局函數(shù)時(shí)
二元操作符就需要兩個(gè)參數(shù),一元操作符需要一個(gè)參數(shù)蠢挡,而且其中必須有一個(gè)參數(shù)是對象弧岳,防止修改內(nèi)置類型的運(yùn)算符的性質(zhì)。
比如袒哥,下面的重載就是錯(cuò)誤的:
int operator + (int a,int b) {
return (a-b);
}
如果允許這么重載缩筛,表達(dá)式4+3
的結(jié)果應(yīng)該是1還是7呢,所以這種做法是不允許的堡称,必須有一個(gè)參數(shù)是對象瞎抛。
C++重載數(shù)學(xué)運(yùn)算符(實(shí)例演示)
四則運(yùn)算符(+、-却紧、桐臊、/、+=晓殊、-=断凶、=、/=)和關(guān)系運(yùn)算符(>巫俺、<认烁、<=、>=介汹、==却嗡、!=)都是數(shù)學(xué)運(yùn)算符,被重載的幾率很高嘹承。
本節(jié)還是以Complex復(fù)數(shù)類進(jìn)行重載窗价,且因?yàn)閺?fù)數(shù)不能比較大小,所以這里不重載>,<,<=,>=運(yùn)算符了叹卷。
到底以成員函數(shù)還是全局函數(shù)(友元函數(shù))的形式重載運(yùn)算符
下面的幾章TODO,暫時(shí)用不到
模板
模板入門教程
在《C++函數(shù)重載》中定義了四個(gè)名字相同撼港、參數(shù)列表不同的四個(gè)函數(shù):
//交換 int 變量的值
void Swap(int *a, int *b){
int temp = *a;
*a = *b;
*b = temp;
}
//交換 float 變量的值
void Swap(float *a, float *b){
float temp = *a;
*a = *b;
*b = temp;
}
//交換 char 變量的值
void Swap(char *a, char *b){
char temp = *a;
*a = *b;
*b = temp;
}
//交換 bool 變量的值
void Swap(bool *a, bool *b){
char temp = *a;
*a = *b;
*b = temp;
}
在C++中,數(shù)據(jù)的類型也可以通過參數(shù)來傳遞骤竹,在函數(shù)定義時(shí)可以不指名具體的數(shù)據(jù)類型帝牡,當(dāng)發(fā)生函數(shù)調(diào)用時(shí),編譯器會自動推斷數(shù)據(jù)類型蒙揣。這就是類型的參數(shù)化(大致可以理解為java的泛型)
所謂函數(shù)模板靶溜,實(shí)際上是建立一個(gè)通用函數(shù),它所用到的數(shù)據(jù)的類型(包括返回值類型鸣奔、形參類型墨技、局部變量類型)可以用一個(gè)虛擬的類型來代替,等發(fā)生函數(shù)調(diào)用時(shí)再根據(jù)傳入的實(shí)參來逆推出真正的類型挎狸,這個(gè)通用函數(shù)就被稱為函數(shù)模板扣汪。
一旦定義了函數(shù)模板,就可以將類型參數(shù)用于函數(shù)定義和函數(shù)聲明了锨匆。說的直白一點(diǎn)崭别,原來用int、float恐锣、char等內(nèi)置類型的地方茅主,都可以用類型參數(shù)來代替。
使用模板重定義函數(shù):
//
// Created by 18041 on 2022/4/29.
//
#include "chapter7.h"
#include <iostream>
using namespace std;
template<typename T> void swap(T *a, T *b) {
T temp = *a;
*a = *b;
*b = temp;
}
void testM1() {
int n1 = 100, n2 = 200;
swap(&n1, &n2);
cout << "n1:" << n1 << " n2:" << n2 << endl;
char n3 = 'A', n4 = 'B';
swap(&n3, &n4);
cout << "n3:" << n3 << " n4:" << n4 << endl;
}
int main() {
testM1();
return 0;
}
template
是定義函數(shù)模板的關(guān)鍵字土榴,它后面緊跟尖括號<>
.
typename
是另外一個(gè)關(guān)鍵字诀姚,用來聲明具體的類型參數(shù),這里的類型參數(shù)就是
T
.從整體上看玷禽,template<typename T>
被稱為模板頭赫段。
類型參數(shù)的命名規(guī)則跟其他標(biāo)識符的命名規(guī)則一樣,但使用T/T1/T2/Type等已經(jīng)成為一種慣例矢赁。
上面我們是用指針來實(shí)現(xiàn)變量值的交換糯笙,后面我們學(xué)過引用,也可以通過定義引用的函數(shù)模板來達(dá)到交換值的目的撩银,ex:
template<typename Type>
void swap2(Type &a, Type &b) {
Type temp = a;
a = b;
b = temp;
}
void testM2() {
int n1 = 100, n2 = 200;
swap2(n1, n2);
cout << "n1:" << n1 << " n2:" << n2 << endl;
float n3 = 20.5, n4 = 48.2;
swap2(n3, n4);
cout << "n3:" << n3 << " n4:" << n4 << endl;
}
總結(jié)一下定義模板函數(shù)的語法:
template <typename 類型參數(shù)1给涕,typename 類型參數(shù)2,...> 返回值類型 函數(shù)名(形參列表) {
//在函數(shù)體中使用類型參數(shù)
}
typename
關(guān)鍵字也可以使用class
關(guān)鍵字代替额获,他們沒有任何區(qū)別够庙,很多代碼仍在使用class關(guān)鍵字,包括C++標(biāo)準(zhǔn)庫咪啡、一些開源程序等首启。
C++類模板
C++除了函數(shù)模板,還支持類模板撤摸。函數(shù)模板中定義的類型參數(shù)可以用在函數(shù)聲明和函數(shù)定義中毅桃,類模板中定義的類型參數(shù)可以用在類聲明和類實(shí)現(xiàn)中。類模板的目的同樣是將數(shù)據(jù)的類型參數(shù)化准夷。
聲明類模板的語法為:
template<typename 類型參數(shù)1钥飞,typename 類型參數(shù)2,...> class 類名{
//TODO;
}
類模板和參數(shù)模板都是以template開頭(當(dāng)然也可以用class),后跟類型參數(shù)衫嵌;參數(shù)類型不能為空读宙;
一旦聲明了類模板,就可以將類型參數(shù)用于類的成員函數(shù)和成員變量了楔绞。換句說结闸,原來使用int唇兑、float、char等內(nèi)置類型的地方桦锄,都可以使用類型參數(shù)來替代扎附。
ex:我們要定義一個(gè)類來表示坐標(biāo),但坐標(biāo)的類型不確定结耀,可以是整數(shù)留夜、小數(shù)和字符串,例如:
x = 10 ,y = 100
x = 12.88 ,y = 128.10
x ="東經(jīng)180度",y = "北緯210度"
那我們就可以定義模板類來實(shí)現(xiàn):
//模板類
template<typename T1, typename T2>
class Point {
public:
Point(T1 x, T2 y) : m_x(x), m_y(y) {}
public:
T1 getX() const;
void setX(T1 x);
T2 getY() const;
void setY(T2 y);
private:
T1 m_x;
T2 m_y;
};
//對成員函數(shù)定義
template<typename T1, typename T2>
T1 Point<T1, T2>::getX() const {
return m_x;
}
template<typename T1, typename T2>
T2 Point<T1, T2>::getY() const {
return m_y;
}
除了類的聲明之外图甜,我們還需要在類外定義成員函數(shù)次员。在類外定義成員函數(shù)時(shí)仍然需要帶上模板頭望忆,格式為:
template<typename 類型參數(shù)1,typename 類型參數(shù)2,...>
返回值類型 類名<類型參數(shù)1音羞,類型參數(shù)2锋八,...>::函數(shù)名(形參列表){
//TODO
}
大話C++模板編程的來龍去脈
不同的編程語言根據(jù)不同的標(biāo)準(zhǔn)可以分為不同的類痒芝,根據(jù)“在定義變量時(shí)是否需要顯式地指明數(shù)據(jù)類型”可以分為【強(qiáng)類型語言】和【弱類型語言】赃春;
1.強(qiáng)類型語言
強(qiáng)類型語言在定義變量時(shí)需要顯式地指明數(shù)據(jù)類型,且一旦指定某個(gè)數(shù)據(jù)類型匪凡,該變量以后就不能賦予其他類型的數(shù)據(jù)了膊畴,除非強(qiáng)制類型轉(zhuǎn)換或隱式轉(zhuǎn)換。典型的強(qiáng)類型語言有C/C++病游、Java唇跨、C#等。
C/C++中使用變量:
int a = 100;
a = 12.34;//隱式轉(zhuǎn)換衬衬,會直接舍去小數(shù)部分买猖,得到12
a = (int)"http://c.biancheng.net";//強(qiáng)制轉(zhuǎn)換,會得到字符串地址滋尉;
2.弱類型語言
弱類型語言在定義變量時(shí)不需要顯式地指明數(shù)據(jù)類型玉控,編譯器會根據(jù)賦給變量的數(shù)據(jù)自動推導(dǎo)出類型,并且可以賦給不同類型的數(shù)據(jù)狮惜。典型的弱類型語言有JavaScript高诺、Python、Kotlin碾篡、Shell等虱而;
示例這里就不說了。
不管是強(qiáng)類型語言還是弱類型語言开泽,在編譯器內(nèi)部都有一個(gè)類型系統(tǒng)來維護(hù)變量的各種信息牡拇。
對于強(qiáng)類型的語言,變量的類型從始至終都是確定的、不變的惠呼,編譯器在編譯期間就能監(jiān)測某個(gè)變量的操作是否正確导俘,這樣最終生成的程序中就不用再維護(hù)一套類型信息了,從而減少了內(nèi)存的使用剔蹋,加快了程序的運(yùn)行趟畏;但也不絕對,有些特殊情況下還是需要等到運(yùn)行階段才能確定變量的類型信息滩租。比如C++中的多臺,編譯器在編譯階段會在對象內(nèi)存模型中增加虛函數(shù)表利朵、type_info對象等輔助信息律想,以維護(hù)一個(gè)完整的繼承鏈,等到程序運(yùn)行后再執(zhí)行一段代碼才能確定調(diào)用哪個(gè)函數(shù)绍弟;
弱類型語言往往是一邊執(zhí)行一邊編譯技即,這樣便可以根據(jù)上下文推到出很多有用的信息,讓編譯更加高效樟遣。這種一邊執(zhí)行一邊編譯的語言我們稱為解釋型語言而叼,而將傳統(tǒng)的先編譯后執(zhí)行的語言稱為編譯型語言。
強(qiáng)類型語言較為嚴(yán)謹(jǐn)豹悬,在編譯時(shí)就能發(fā)現(xiàn)很多錯(cuò)誤葵陵,適合開發(fā)大型的、系統(tǒng)級瞻佛、工業(yè)級的項(xiàng)目脱篙;而弱類型語言較為靈活,編碼效率高伤柄,部署容易绊困,學(xué)習(xí)成本地,在Web開發(fā)中大顯身手适刀;
C++支持模板主要就是為了彌補(bǔ)強(qiáng)類型語言"不夠靈活"的缺點(diǎn)秤朗;
模板所支持的類型是寬泛的,沒有限制的笔喉,我們可以使用任意類型來替換取视,這種編程方式稱為泛型編程。(所以就是跟java的泛型編程一個(gè)意思常挚。)
C++模板最初推出的直接動力
C++ 模板也是被迫推出的贫途,最直接的動力來源于對數(shù)據(jù)結(jié)構(gòu)的封裝。數(shù)據(jù)結(jié)構(gòu)關(guān)注的是數(shù)據(jù)的存儲待侵,以及存儲后如何進(jìn)行增加丢早、刪除、修改和查詢操作,它是一門基礎(chǔ)性的學(xué)科怨酝,在實(shí)際開發(fā)中有著非常廣泛的應(yīng)用傀缩。C++ 開發(fā)者們希望為線性表、鏈表农猬、圖赡艰、樹等常見的數(shù)據(jù)結(jié)構(gòu)都定義一個(gè)類,并把它們加入到標(biāo)準(zhǔn)庫中斤葱,這樣以后程序員就不用重復(fù)造輪子了慷垮,直接拿來使用即可。
但是這個(gè)時(shí)候遇到了一個(gè)無法解決的問題揍堕,就是數(shù)據(jù)結(jié)構(gòu)中每份數(shù)據(jù)的類型無法提前預(yù)測料身。以鏈表為例,它的每個(gè)節(jié)點(diǎn)可以用來存儲小數(shù)衩茸、整數(shù)芹血、字符串等,也可以用來存儲一名學(xué)生楞慈、教師幔烛、司機(jī)等,還可以直接存儲二進(jìn)制數(shù)據(jù)囊蓝,這些都是可以的饿悬,沒有任何限制。而 C++ 又是強(qiáng)類型的聚霜,數(shù)據(jù)的種類受到了嚴(yán)格的限制乡恕,這種矛盾是無法調(diào)和的。
要想解決這個(gè)問題俯萎,C++ 必須推陳出新傲宜,跳出現(xiàn)有規(guī)則的限制,開發(fā)新的技術(shù)夫啊,于是模板就誕生了函卒。模板雖然不是 C++ 的首創(chuàng),但是卻在 C++ 中大放異彩撇眯,后來也被 Java报嵌、C# 等其他強(qiáng)類型語言采用。
TODO 付費(fèi)章節(jié)
其他章節(jié)暫且不細(xì)看熊榛,我們當(dāng)下的目的是用起來锚国,而不是深鉆。