C++學(xué)習(xí)問題
內(nèi)容原創(chuàng),未經(jīng)本人同意請勿轉(zhuǎn)載亚隙。聯(lián)系本人:jianshu_kevin@126.com
1磁餐,virtual函數(shù)
函數(shù)之前加上virtual關(guān)鍵字就表示該函數(shù)為虛函數(shù),在派生類中通過重寫該虛函數(shù)來實(shí)現(xiàn)對基類函數(shù)的覆蓋阿弃。
//基類中定義virtual函數(shù)
class base
{
public:
virtual void fun() {cout<<"BASE";}
};
//派生類中覆蓋fun函數(shù)
class derived: base
{
public:
//不管該函數(shù)之前是否添加virtual字段诊霹,該函數(shù)都是虛函數(shù)
void fun() {cout<<"DERIVED";}
};
/*多態(tài)使用*/
int main()
{
base* d = new derived();
/*類的多態(tài),調(diào)用的是派生類中的fun函數(shù)*/
d->fun();
}
//輸出
DERIVED
//
void call_fun(base* b)
{
//如果b是base類的實(shí)例渣淳,就調(diào)用base中的fun
//如果b是derived類的實(shí)例脾还,就調(diào)用derived中的fun
b->fun();
}
為何"虛"---動態(tài)聯(lián)編
virtual函數(shù)用到了動態(tài)聯(lián)編和推遲聯(lián)編的技術(shù),virtual函數(shù)在編譯的時(shí)候是無法確定的入愧,而是在運(yùn)行的時(shí)候被確定的鄙漏。
編譯器在發(fā)現(xiàn)類中有virtual函數(shù)的時(shí)候,就會為該類分配一個VTABLE函數(shù)指針數(shù)組棺蛛,這個數(shù)組里存放了類中的所有虛函數(shù)怔蚌。
一個類只有一個VTABLE,不管有多少個實(shí)例
派生類有各自的VTABLE
同一個虛函數(shù)在基類和派生類的VTABLE的相同位置
編譯器在編譯的時(shí)候?yàn)槊總€實(shí)例在內(nèi)存中分配一個vptr字段鞠值,該字段指向本實(shí)例的VTABLE
void call_fun(base* b)
{
//如果b是base類的實(shí)例媚创,就調(diào)用base中的fun
//如果b是derived類的實(shí)例彤恶,就調(diào)用derived中的fun
//編譯后該函數(shù)b->fun();變成
(base->vptr[1])();
//這樣根據(jù)傳遞進(jìn)來的實(shí)例不同钞钙,就調(diào)用不同的函數(shù)芒炼。
}
純虛函數(shù)(interface類)
class base
{
public:
virtual fun() = 0; //0標(biāo)志一個虛函數(shù)為純虛函數(shù)
}
純虛函數(shù)表示該類是一個抽象類,無法被實(shí)例化术徊,只能被派生類繼承覆蓋本刽。用來規(guī)范派生類,這就是所謂的“接口”類赠涮,用來約束派生類需要實(shí)現(xiàn)這些函數(shù)子寓。
為什么有的析構(gòu)函數(shù)必須寫成虛函數(shù)
派生類的構(gòu)造和析構(gòu)函數(shù)的正常執(zhí)行流程
- 創(chuàng)建派生類,先執(zhí)行基類的構(gòu)造函數(shù)笋除,再執(zhí)行派生類的構(gòu)造函數(shù)
- 銷毀派生類斜友,先執(zhí)行派生類的構(gòu)造函數(shù),再執(zhí)行基類的構(gòu)造函數(shù)
#include <iostream>
using namespace std;
class base
{
public:
base(){cout<<"base structure"<<endl;}
~base(){cout<<"base destructure"<<endl;}
};
class sub : public base
{
public:
sub(){cout<<"subclass structure"<<endl;}
~sub(){cout<<"subclass destructure"<<endl;}
};
int main()
{
sub* ps = new sub();
cout<<" "<<endl;
delete ps;
return 0;
}
///////////////執(zhí)行結(jié)果//////////////////
base structure //創(chuàng)建
subclass structure
subclass destructure //釋放
base destructure
///////////////執(zhí)行結(jié)果//////////////////
派生類的構(gòu)造和析構(gòu)函數(shù)的異常執(zhí)行流程
將main
函數(shù)中的ps
換成基類類型(多態(tài)特性)垃它,看看會有什么結(jié)果
int main()
{
base* ps = new sub();
count<<" "<<endl;
delete ps;
return 0;
}
///////////////執(zhí)行結(jié)果//////////////////
base structure //創(chuàng)建
subclass structure
base destructure //釋放鲜屏,只調(diào)用了基類的析構(gòu)函數(shù)
///////////////執(zhí)行結(jié)果//////////////////
上面的代碼改過后,發(fā)現(xiàn)釋放的時(shí)候国拇,只調(diào)用了基類的析構(gòu)函數(shù)洛史。但是構(gòu)造的時(shí)候基類、派生類構(gòu)造函數(shù)都被調(diào)用了酱吝。這就引發(fā)了內(nèi)存泄露的問題也殖。
總結(jié): 如果要用基類類型操作派生類,必須將析構(gòu)函數(shù)寫成
virtual
函數(shù)务热,否則會造成析構(gòu)一半毕源,從而出現(xiàn)內(nèi)存泄漏。
基類操作的正常析構(gòu)虛函數(shù)
#include <iostream>
using namespace std;
class base
{
public:
base(){cout<<"base structure"<<endl;}
virtual ~base(){cout<<"base destructure"<<endl;}
};
class sub : public base
{
public:
sub(){cout<<"subclass structure"<<endl;}
~sub(){cout<<"subclass destructure"<<endl;}
};
int main()
{
base* ps = new sub();
count<<" "<<endl;
delete ps;
return 0;
}
///////////////執(zhí)行結(jié)果//////////////////
base structure //創(chuàng)建
subclass structure
subclass destructure //釋放
base destructure
///////////////執(zhí)行結(jié)果//////////////////
上面的代碼只是在基類的析構(gòu)函數(shù)~base()前加了virtual ~base()
陕习,在釋放的時(shí)候霎褐,就能正常析構(gòu)了。想想virtual函數(shù)
的精髓该镣,不難理解由于C++的多態(tài)
特性冻璃,ps
雖然是base類型,但是它由sub
構(gòu)造而成损合,所以析構(gòu)的時(shí)候還是調(diào)用sub
提供的析構(gòu)函數(shù)省艳。
2,命令空間
定義
namespace ns1
{
int a;
int b;
class base{};
void fun(){}
}
分離式定義
one.h
#ifndef TWO_H_
#define TWO_H_
namespace two
{
void say();
}
#endif
two.h
#ifndef TWO_H_
#define TWO_H_
namespace two
{
void say();
};
#endif
one_two.cpp
#include <iostream>
#include "one.h"
#include "two.h"
void one::say()
{
cout<<"one say\r\n";
}
void two::say()
{
cout<<"two say\r\n";
}
//如果聲明的空間有類如何實(shí)現(xiàn)嫁审?律适?遏插?胳嘲?
使用
若想使用某個標(biāo)識符了牛,using 空間名::標(biāo)識符;
若想使用改namespace下的所有標(biāo)識符 using namespace 空間名;
//方法1
using namespace one;
//方法2
using one::say;
自定義空間名使用
#include <iostream>
#include <string>
using namespace std;
using namespace one;
using namespace two;
//全局函數(shù)
void say()
{
cout<<"global say\r\n";
}
int main()
{
//全局函數(shù)鹰祸,一定要加上::
//否則會出現(xiàn) 錯誤:調(diào)用重載的‘say()’有歧義
::say();
one::say();
two::say();
}
3福荸,模板
模板可以實(shí)現(xiàn)邏輯相同敬锐,但是數(shù)據(jù)類型不同的代碼復(fù)制台夺。當(dāng)用戶使用模板時(shí)颤介,參數(shù)由編譯器決定滚朵,這很像宏
辕近。
模板分為函數(shù)模板
和類模板
移宅。
函數(shù)模板
定義&使用
template <類型參數(shù)表> 返回類型 函數(shù)名(形參列表) {函數(shù)實(shí)體}
template <typename T> void print(const T& var)
{
cout<<var<<endl;
}
int main()
{
String a("hello template");
int num = 123;
print(a);
print(num);
}
/**********多個參數(shù)***********/
template <class T> T min(T ii, T jj, T kk)
{
T temp;
if(ii < jj) temp = ii;
else temp = jj;
if(temp > kk) temp = kk;
return temp;
}
int main()
{
int minNum = min(100, 30, 102);
cout<<minNum<<endl;
char minChar = min('z', 'a', 'h');
cout<<minChar<<endl;
return 0;
}
類模板
定義和使用
templete <類型參數(shù)列表> class base{};
4漏峰,操作符重載
4.1浅乔,怎么定義該函數(shù)
使用operator xx(xx表示操作符靖苇,例如==,+,-芯砸,等)
class person{
private:
int age;
public:
person(int a):age(a){};
inline operator == (const person& p) const;
};
inline person::operator==(const person& p) const
{
if(this->age == p.age)
{
return true;
}
else
{
return false;
}
}
using namespace std;
void main()
{
person p1(10);
person p2(20);
if(p1 == p2)
{
cout<<"the age equal"<<endl;
}
else
{
cout<<"the age different"<<endl;
}
}
4.2假丧,為什么要重載
對于系統(tǒng)的所有操作符包帚,一般情況下渴邦,只支持基本數(shù)據(jù)類型和標(biāo)準(zhǔn)庫中提供的class谋梭,對于用戶自己定義的class瓮床,如果想支持基本操作隘庄,比如比較大小丑掺,判斷是否相等述雾,等等绰咽,則需要用戶自己來定義關(guān)于這個操作符的具體實(shí)現(xiàn)取募。比如,判斷兩個人是否一樣大斗忌,我們默認(rèn)的規(guī)則是按照其年齡來比較织阳,所以,在設(shè)計(jì)person 這個class的時(shí)候造挽,我們需要考慮操作符==饭入,而且谐丢,根據(jù)剛才的分析乾忱,比較的依據(jù)應(yīng)該是age饭耳。那么為什么叫重載呢执解?這是因?yàn)樗ル纾诰幾g器實(shí)現(xiàn)的時(shí)候右蕊,已經(jīng)為我們提供了這個操作符的基本數(shù)據(jù)類型實(shí)現(xiàn)版本饶囚,但是現(xiàn)在他的操作數(shù)變成了用戶定義的數(shù)據(jù)類型class萝风,所以规惰,需要用戶自己來提供該參數(shù)版本的實(shí)現(xiàn)。
4.3揩晴,操作符重載為全局函數(shù)
估計(jì)沒人會這么使用
對于全局的操作符重載函數(shù)诅愚,左操作數(shù)的參數(shù)必須被顯式的定義违孝。
bool operator == (person const &p1, person const &p2);
如何決定使用全局還是類操作符重載函數(shù)呢苏研?
- 1 左操作符和比較對象是不是同一個類型
- 2 C++要求摹蘑,=衅鹿、[]大渤、()泵三、->衔掸、必須定義為類操作符重載函數(shù)
5敞映,重載函數(shù)
5.1 什么叫重載函數(shù)?
在同一個作用域內(nèi)捷犹,可以有一組具有名字相同萍歉,參數(shù)不同的一組函數(shù)翠桦。重載函數(shù)用來命名一組功能相似的函數(shù)丛晌。
5.2 為什么要用重載函數(shù)
- 必須寫很多函數(shù)名澎蛛,來完成功能相似的一組函數(shù)
- 類構(gòu)造函數(shù)谋逻,如果沒有重載毁兆。那如果要實(shí)例化不同的類是比較麻煩的气堕。
- 操作符重載本身也是函數(shù)重載茎芭,豐富了已有操作符的功能梅桩。
5.3 編譯器如何解決命名沖突
編譯器會把不同參數(shù)名字相同的函數(shù)宿百,用新的函數(shù)名取代垦页。恩外臂,其實(shí)也就還是不同名字宋光,但是寫起來方便很多
5.4 重寫函數(shù)(override)
子類重新定義父類中有相同名稱罪佳、相同參數(shù)的虛函數(shù)赘艳。
- 被重新定義的函數(shù)不能為static函數(shù)
- 重寫的函數(shù)一定要完全相同(包括返回值蕾管、參數(shù))
- 重寫的函數(shù)可以有不同的修飾符掰曾,例如在基類中是private旷坦,派生類可以寫成public旗芬、protected
5.5 重定義函數(shù)(redefining)
子類重新定義父類中具有相同名字的函數(shù)(參數(shù)列表可以不同)疮丛。
6这刷,static類
6.1 static成員函數(shù)
static數(shù)據(jù)成員是存儲在程序的靜態(tài)存儲區(qū),而并不是在椂蠢保空間上扬霜。獨(dú)立于任何類的對象。
注意:static成員函數(shù)
- 沒有this指針材原。因?yàn)閟tatic成員函數(shù)不是任何對象的組成部分
- 不能聲明為const類型,不能訪問非static成員季眷,也不能訪問static const成員余蟹。
6.2 static成員
注意:在類中不能對static成員進(jìn)行初始化,除非寫成
const static
子刮。
class person{
private:
static int age;
//static int age= 20; //錯誤:ISO C++ 不允許在類內(nèi)初始化非常量靜態(tài)成員'person::age'
const static int c_int = 100; //允許
static string name;
public:
void print()
{
cout<<"name: "<<name<<" age: "<<age<<endl;
}
}
int person::age = 30;
string person::name = "kevin";
int main()
{
//int person::age = 20; 錯誤:對限定名‘person::age’的使用無效
person p;
p.print();
return 0;
}
6.3 static和const不能同時(shí)修飾一個成員函數(shù)
因?yàn)閟tatic是屬于類
的威酒,而const函數(shù)是為了保證改函數(shù)不會修改對象
中的成員。兩個關(guān)鍵字作用的對象不一致,所以不能放到一起使用葵孤。
內(nèi)容原創(chuàng)担钮,未經(jīng)本人同意請勿轉(zhuǎn)載。聯(lián)系本人:jianshu_kevin@126.com
7尤仍,引用
引用就相當(dāng)于給一個變量取了另外一個名字(alias),對應(yīng)操作和直接操作原變量效果是一樣的。
- 聲明一個引用時(shí)一定要進(jìn)行初始化
- 引用本身不占用內(nèi)存,系統(tǒng)也不會給引用分配地址
7.1 引用和指針在函數(shù)參數(shù)的區(qū)別
相同點(diǎn)
使用指針和引用傳遞參數(shù)栋豫,對于運(yùn)算效果來說一樣的丛肢。所有對形參的操作都會直接反映到原變量當(dāng)中置尔。
不同點(diǎn)
指針作為形參時(shí)差导,函數(shù)需要給形參分配地址泣刹。而且操作中需要經(jīng)常用到*指針變量
的方式進(jìn)行運(yùn)算掀泳,代碼可讀性比較差马僻。
形參作為形參時(shí)女淑,函數(shù)在內(nèi)存中并沒有產(chǎn)生額外的地址我抠,而是對實(shí)參直接操作笛厦。
//常量引用
int a = 100;
const int &ra = a;
//錯誤逗宁,常量引用不允許賦值操作
ra = 1;
//正確
a = 1;
7.2 引用作為函數(shù)返回值
網(wǎng)上的一些說法
- 不能返回局部變量的引用(函數(shù)執(zhí)行完哼拔,內(nèi)存就會被釋放)
- 不能返回new出來的數(shù)據(jù)(雖然內(nèi)存還在曾我,但是會造成無法通過delete釋放引用的問題)
對這個說法有點(diǎn)懷疑虐秦,自己實(shí)際測試結(jié)果
- 測試一俺驶,局部變量的引用返回給另外一個引用
int* & ref_pointer(const int* b)
{
int* pI = (int*)malloc(100);
cout<<"pointer addr = "<<b<<endl;
cout<<"malloc p = "<<pI<<endl;
return pI;
}
int main()
{
int a = 100;
int* &refP = ref_pointer(&a);
cout<<"return ref_pointer addr = "<<refP<<endl;
return 0;
}
////////////////編譯會報(bào)警告,提示不能返回局部變量的引用////////////////
//g++ 警告:返回了對局部變量的‘pI’的引用 [-Wreturn-local-addr]
///////////////執(zhí)行結(jié)果//////////////////
pointer addr = 0x61ac28
malloc p = 0x20010308
return ref_pointer addr = 0x20010308
///////////////執(zhí)行結(jié)果//////////////////
//
執(zhí)行結(jié)果沒有出現(xiàn)問題尿贫,感覺局部變量的引用沒有問題括享,這是因?yàn)?strong>雖然refP是引用了局部變量pI娇斩,pI是放在堆棧中的,但是我們的代碼返回后带膀,這部分空間并沒有被再次利用,所以就會出現(xiàn)看起來運(yùn)行并沒有問題办铡。但是當(dāng)我們打開test函數(shù)的時(shí)候厦坛,發(fā)現(xiàn)問題了:
int main()
{
int a = 100;
int* &refP = ref_pointer(&a);
test();
cout<<"return ref_pointer addr = "<<refP<<endl;
return 0;
}
///////////////執(zhí)行結(jié)果//////////////////
pointer addr = 0x61ac28
malloc p = 0x20010308
return ref_pointer addr = 0
///////////////執(zhí)行結(jié)果//////////////////
test
函數(shù)執(zhí)行后會從新覆蓋堆棧,這就導(dǎo)致了局部變量pI被覆蓋了励翼,所以refP引用也就失效了。
- 測試二,局部變量的引用返回給變量
int main()
{
int a = 100;
int* refP = ref_pointer(&a);
test();
cout<<"return ref_pointer addr = "<<refP<<endl;
return 0;
}
///////////////執(zhí)行結(jié)果//////////////////
pointer addr = 0x61ac28
malloc p = 0x20010308
return ref_pointer addr = 0x20010308
///////////////執(zhí)行結(jié)果//////////////////
從返回結(jié)果看,雖然再次調(diào)用了test
但是依然沒有問題季研,并不像網(wǎng)上說的,不能返回局部變量的引用
總結(jié):1. 引用本質(zhì)上就是一個常量指針桥胞,引用本身是占用地址空間的奔则,對引用的運(yùn)算食铐,被直接轉(zhuǎn)化成對原變量地址內(nèi)容的操作。
2. 局部變量在return的時(shí)候已經(jīng)完成了給左值賦值僧鲁,除非返回給了另外一個引用虐呻,否則也不會出現(xiàn)問題。但是不同版本的編譯器可能不一樣
我編譯的時(shí)候寞秃,已經(jīng)出現(xiàn)warning了斟叼,所以為了代碼更強(qiáng)的通用性,最好也不要這么用春寿。
type & fun();
//正確寫法
type a = fun();
//錯誤寫法
type &a = fun();
8朗涩,this
類中的成員函數(shù),都有一個附件的隱含實(shí)參绑改,該實(shí)參(this)就是一個指向該類對象的指針谢床。
9,#include xx.h文件和xx有何區(qū)別
10厘线,訪問權(quán)限
三種訪問權(quán)限
- public 可以被任意實(shí)體訪問
- protected 只允許子類和本類成員函數(shù)訪問
- private 只允許本類成員函數(shù)訪問
三種繼承方式
- public繼承 不改變基類成員的訪問權(quán)限
- protected繼承 使得基類中public變成protected识腿,其他權(quán)限不變。
- private繼承 使得基類中所有成員權(quán)限變成private
class base{};
class deliverd : public base{}; //public繼承
11皆的, 重載和覆蓋
12覆履, new delete
12.1 和malloc、free有何區(qū)別
和malloc费薄,free一樣是用來動態(tài)分配內(nèi)存的硝全,不同的是new和delete會自動調(diào)用對象的構(gòu)造和析構(gòu)函數(shù)。
12.2 new[], delete[]
沒錯這個是針對數(shù)組的操作楞抡,delete
操作只會調(diào)用一次析構(gòu)函數(shù)伟众,而delete[]
會調(diào)用每個成員的析構(gòu)函數(shù)。一般new[]和delete[]配對使用召廷。
13凳厢, const成員函數(shù)
若將成員成員函數(shù)聲明為const,則該函數(shù)不允許修改類的數(shù)據(jù)成員
1)const成員函數(shù)可以訪問非const對象的非const數(shù)據(jù)成員竞慢、const數(shù)據(jù)成員先紫,也可以訪問const對象內(nèi)的所有數(shù)據(jù)成員;
2)非const成員函數(shù)可以訪問非const對象的非const數(shù)據(jù)成員筹煮、const數(shù)據(jù)成員遮精,但不可以訪問const對象的任意數(shù)據(jù)成員;
3)作為一種良好的編程風(fēng)格败潦,在聲明一個成員函數(shù)時(shí)本冲,若該成員函數(shù)并不對數(shù)據(jù)成員進(jìn)行修改操作,應(yīng)盡可能將該成員函數(shù)聲明為const 成員函數(shù)劫扒。