目錄
一欧芽、類
C++ 中可以使用 struct飞傀、class 來(lái)定義一個(gè)類氢哮。struct 的默認(rèn)成員權(quán)限是 public袋毙,class 的默認(rèn)成員權(quán)限是 private。除此之外冗尤,二者基本無(wú)差別听盖。struct 中同樣可以存在成員函數(shù)。
struct Person {
//private:
// 成員變量
int age;
// 成員函數(shù)
void run() {
cout << " age is " <<this -> age << endl;
}
}
不同于OC裂七,C++ 中的對(duì)象既可以存在棧區(qū)皆看,也可以存在堆區(qū)。存在于棻沉悖空間的對(duì)象稱為自動(dòng) (auto) 變量腰吟。
// 在棧空間分配內(nèi)存給person對(duì)象
// 這個(gè)person對(duì)象的內(nèi)存會(huì)自動(dòng)回收徙瓶,不用開(kāi)發(fā)人員去管理
Person person;//調(diào)用此行代碼毛雇,此時(shí)已經(jīng)初始化完畢
person.age = 20;
person.run();
二、對(duì)象的內(nèi)存布局和 this
Person person;
person.age = 20;
person.run();
cout << sizeof(person) << endl;
上述代碼打印結(jié)果為 4 侦镇,4 僅表示成員變量所占據(jù)的內(nèi)存空間灵疮。成員函數(shù)不存在對(duì)象內(nèi)存空間中,原因在于成員函數(shù)是不同對(duì)象公共使用的壳繁,如果每個(gè)對(duì)象的內(nèi)存空間都拷貝一份會(huì)造成不必要的浪費(fèi)震捣。實(shí)際情況荔棉,對(duì)象在調(diào)用成員函數(shù)的時(shí)候,會(huì)自動(dòng)傳入當(dāng)前對(duì)象的內(nèi)存地址蒿赢,也即 this
江耀,this
是指向當(dāng)前對(duì)象的指針。注意不能使用this.age
訪問(wèn)成員變量诉植,this
是指針祥国,必須使用 this -> age
形式。這點(diǎn)可以結(jié)合匯編知識(shí)猜測(cè)到晾腔,因?yàn)榛炀幋a中函數(shù)的調(diào)用形式為call + 函數(shù)地址
舌稀。
// 成員函數(shù)
void run() {
//編譯器允許省略 this ->
cout << "age is " << this -> age << endl;
}
注意:C++ 中不存在方法緩存一說(shuō),OC 中之所以存在方法緩存主要是因?yàn)橛蓄悓?duì)象的存在灼擂。
三壁查、堆空間(申請(qǐng)、釋放剔应、清零)
堆空間的申請(qǐng)和釋放可以通過(guò)以下兩種形式:
- malloc | free
- new | delete
int size = sizeof(int);
// 申請(qǐng)4個(gè)字節(jié)的堆空間內(nèi)存
int *p = (int*)malloc(size);
// 從p開(kāi)始的4個(gè)字節(jié), 每個(gè)字節(jié)都存放0
memset(p, 0, size);
//將10放到內(nèi)存空間中
*p = 10;
//申請(qǐng)4個(gè)字節(jié)空間睡腿,內(nèi)部存放 char 類型
*char *p = (char *)malloc(4);
*p = 1;
*(p + 1) = 2;
*(p + 2) = 3;
*(p + 3) = 4;
// 沒(méi)有初始化
int *p1 = new int;
// 初始化為0
int *p2 = new int();
// 初始化為5
int *p3 = new int(5);
// 沒(méi)有初始化
int *p4 = new int[3];
// 全部元素初始化為0
int *p5 = new int[3]();
// 全部元素初始化為0
int *p6 = new int[3]{};
// 首元素初始化為5,其他元素初始化為0
int *p7 = new int[3]{ 5 };delete p1;
struct Person {
int m_age;
};
// 堆空間
Person *p = new Person();
p->m_age = 20;
delete p;
四峻贮、對(duì)象的內(nèi)存位置(全局席怪、棧、堆)
C++ 中的對(duì)象可以存在全局區(qū)纤控、椆夷恚空間或堆空間。
struct Person {
int m_age;
};
// 全局區(qū)
Person g_person;
int main() {
// 棿颍空間
Person person;
// 堆空間刻撒,相比于malloc、free耿导,更推薦此種方式
Person *p = new Person();
p->m_age = 20;
delete p;
// 堆空間
Person *p2 = (Person *) malloc(sizeof(Person));
free(p2);
return 0;
}
五声怔、構(gòu)造函數(shù)
構(gòu)造函數(shù),也叫構(gòu)造器舱呻,在對(duì)象創(chuàng)建的時(shí)候自動(dòng)調(diào)用醋火。主要有以下特點(diǎn):
- 函數(shù)名與類同名,無(wú)返回值(void都不能寫)狮荔,可以有參數(shù)胎撇,可以重載介粘,可以有多個(gè)構(gòu)造函數(shù)殖氏。
- 注意一旦自定義了構(gòu)造函數(shù),必須用其中一個(gè)自定義的構(gòu)造函數(shù)來(lái)初始化對(duì)象姻采。
- 通過(guò) malloc 創(chuàng)建的對(duì)象不會(huì)調(diào)用構(gòu)造函數(shù)雅采,所以一般建議使用 new 創(chuàng)建對(duì)象。
- 要聲明為
public
,才能被外界正常使用婚瓜。
struct Person {
int m_age;
Person() {
cout << "Person()" << endl;
// this->m_age = 0;
memset(this, 0, sizeof(Person));
}
Person(int age) {
cout << "Person(int age)" << endl;
this->m_age = age;
}
};
int main() {
// 棻模空間
Person person1; //調(diào)用Person()
Person person2(); //不會(huì)調(diào)用構(gòu)造函數(shù),這個(gè)只是函數(shù)聲明巴刻,函數(shù)名叫person2愚铡,無(wú)參,返回值類型是Person
Person person3(20);//調(diào)用Person(int age)
// 堆空間
Person *p1 = new Person; //調(diào)用Person()
Person *p2 = new Person(); //調(diào)用Person()
Person *p3 = new Person(30); //調(diào)用Person(int age)
return 0;
}
六胡陪、成員變量初始化方式
變量的初始化有兩種形式沥寥,一種是在構(gòu)造函數(shù)實(shí)現(xiàn),另一種是在沒(méi)有構(gòu)造函數(shù)的情況下 new
創(chuàng)建對(duì)象的時(shí)候添加小括號(hào)柠座。
方式一:
struct Person {
int m_age;
Person() {
cout << "Person()" << endl;
//以下兩種形式都可以初始化成員變量邑雅,更推薦第二種。
// this->m_age = 0;
memset(this, 0, sizeof(Person));
}
};
方式二:
// 全局區(qū)(成員變量初始化為0)
Person g_person;
int main() {
// 椔杈空間(成員變量沒(méi)有初始化)
Person person;
// 堆空間
Person *p1 = new Person; // 成員變量沒(méi)有初始化
Person *p2 = new Person(); // 成員變量有初始化
return 0;
}
七淮野、析構(gòu)函數(shù)(Destructor)
析構(gòu)函數(shù),也叫析構(gòu)器吹泡,在對(duì)象銷毀的時(shí)候自動(dòng)調(diào)用骤星。有以下特點(diǎn):
- 函數(shù)名以~開(kāi)頭,與類同名爆哑,無(wú)返回值(void都不能寫)妈踊,無(wú)參,不可以重載泪漂,有且只有一個(gè)析構(gòu)函數(shù)廊营。
- 通過(guò)malloc分配的對(duì)象free的時(shí)候不會(huì)調(diào)用構(gòu)造函數(shù)。
- 和構(gòu)造函數(shù)一樣萝勤,要聲明為
public
露筒,才能被外界正常使用。
class Person {
int m_age;
public:
// 對(duì)象創(chuàng)建完畢的時(shí)候調(diào)用
Person() {
cout << "Person()" << endl;
this->m_age = 0;
}
Person(int age) {
cout << "Person(int age)" << endl;
this->m_age = age;
}
// 對(duì)象銷毀(內(nèi)存被回收)的時(shí)候調(diào)用析構(gòu)函數(shù)
~Person() {
cout << "~Person()" << endl;
}
};
八敌卓、對(duì)象內(nèi)部?jī)?nèi)存管理(對(duì)象的成員變量為對(duì)象)
當(dāng)對(duì)象內(nèi)部的成員變量為對(duì)象時(shí)慎式,對(duì)象銷毀的時(shí)候,成員變量對(duì)象也會(huì)被銷毀趟径。但當(dāng)對(duì)象內(nèi)部的成員變量為對(duì)象指針時(shí)瘪吏,可以在對(duì)象內(nèi)部申請(qǐng)堆空間,并由對(duì)象內(nèi)部回收堆空間蜗巧。
struct Car {
int m_price;
Car() {
cout << "Car()" << endl;
}
~Car() {
cout << "~Car()" << endl;
}
};
成員變量為對(duì)象時(shí)掌眠,構(gòu)造函數(shù)和析構(gòu)函數(shù)都無(wú)需做額外處理:
struct Person {
int m_age;
Car m_car;
Person() {
cout << "Person()" << endl;
}
~Person() {
cout << "~Person()" << endl;
}
};
成員變量為對(duì)象指針時(shí),構(gòu)造函數(shù)和析構(gòu)函數(shù)都要做處理:
struct Person {
int m_age; //4
Car *m_car; //4
Person() {
cout << "Person()" << endl;
this->m_car = new Car();
//注意不能這樣寫幕屹,因?yàn)榇藭r(shí)Car對(duì)象在棧區(qū)蓝丙,出了構(gòu)造函數(shù)棧區(qū)被回收级遭,此時(shí)指針指向棧區(qū)很危險(xiǎn)腻贰。
/*Car car;
this->m_car = &car;*/
}
// 內(nèi)存回收使鹅、清理工作(回收Person對(duì)象內(nèi)部申請(qǐng)的堆空間)
~Person() {
cout << "~Person()" << endl;
delete this->m_car;
}
};
使用:
Person *p = new Person();
delete p;
九、方法聲明和實(shí)現(xiàn)分離
Person.h 文件中的聲明:
class Person {
int m_age;
public:
Person();
~Person();
void setAge(int age);
int getAge();
};
Person.cpp 文件中的實(shí)現(xiàn):
Person::Person() {
cout << "Person()" << endl;
}
Person::~Person() {
cout << "~Person()" << endl;
}
void Person::setAge(int age) {
this->m_age = age;
}
int Person::getAge() {
return this->m_age;
}
十埃脏、命名空間
命名空間主要用于避免命名沖突鸥跟。Java 中的命名空間是所謂的 package丢郊,OC 中沒(méi)有命名空間,一般可以用類前綴作區(qū)分医咨。
namespace ZW {
class Car {
public:
Car();
~Car();
};
}
int main() {
ZW::Car car;
return 0;
}
命名空間可以嵌套蚂夕。
namespace AA {
namespace BB {
int g_no;
class Person {
};
void test() {
}
}
}
int main() {
using namespace AA;
BB::g_no = 30;
using namespace AA::BB;
g_no = 10;
return 0;
}
十一、繼承以及內(nèi)存布局
OC 中所有的對(duì)象都繼承自 NSObject腋逆,Java 中所有的對(duì)象都繼承自 java.lang.object 婿牍。但 C++ 不同于 OC 和 Java,C++ 沒(méi)有最終的基類惩歉。
struct Person {
int m_age;
};
struct Student : Person {
int m_no;
};
struct GoodStudent : Student {
int m_money;
};
int main(int argc, const char * argv[]) {
Person person;// 4
Student stu;// 8
GoodStudent gs;// 12
gs.m_age = 20;
gs.m_no = 1;
gs.m_money = 666;
cout << sizeof(Person) << endl;//4
cout << sizeof(Student) << endl;//8
cout << sizeof(GoodStudent) << endl;//12
return 0;
}
下圖是 GoodStudent 的內(nèi)存布局等脂。其中,父類的成員變量在前撑蚌,子類的成員變量在后上遥。
十二、初始化列表和默認(rèn)參數(shù)
初始化列表
初始化列表一種便捷的初始化成員變量的方式争涌,只能用在構(gòu)造函數(shù)中粉楚,初始化順序只跟成員變量的聲明順序有關(guān)。下面代碼段中注釋的代碼可以用一種更簡(jiǎn)潔的方式表達(dá)亮垫。
struct Person {
int m_age;
int m_height;
Person(int age, int height) :m_age(age), m_height(height) {
}
/*Person(int age, int height) {
cout << "Person(int age, int height) " << this << endl;
this->m_age = age;
this->m_height = height;
}*/
默認(rèn)參數(shù)
默認(rèn)參數(shù)只能寫在函數(shù)的聲明中模软,構(gòu)造函數(shù)的初始化列表只能寫在實(shí)現(xiàn)中。
//類聲明
class Person {
int m_age;
int m_height;
public:
// 默認(rèn)參數(shù)只能寫在函數(shù)的聲明中
Person(int age = 0, int height = 0);
};
//實(shí)現(xiàn)
// 構(gòu)造函數(shù)的初始化列表只能寫在實(shí)現(xiàn)中
Person::Person(int age, int height) :m_age(age), m_height(height) {
}
int main() {
Person person;
Person person2(10);
Person person3(20, 180);
return 0;
}
十三饮潦、父類的構(gòu)造函數(shù)
- 1燃异、子類的構(gòu)造函數(shù)默認(rèn)會(huì)調(diào)用父類的無(wú)參構(gòu)造函數(shù)。父類的構(gòu)造函數(shù)先于子類的構(gòu)造函數(shù)調(diào)用继蜡。
- 2回俐、如果子類的構(gòu)造函數(shù)顯式地調(diào)用了父類的有參構(gòu)造函數(shù),就不會(huì)再去默認(rèn)調(diào)用父類的無(wú)參構(gòu)造函數(shù)稀并。
- 3仅颇、如果父類沒(méi)有無(wú)參構(gòu)造函數(shù),子類的構(gòu)造函數(shù)必須顯式調(diào)用父類的有參構(gòu)造函數(shù)碘举。
class Person {
int m_age;
public:
Person(int age) :m_age(age) {
cout << "Person(int age)" << endl;
}
};
class Student : public Person {
int m_score;
public:
Student() :Person(0) {
}
};
十四忘瓦、虛函數(shù)
虛函數(shù)是被 virtual
修飾的成員函數(shù),C++中的多態(tài)通過(guò)虛函數(shù)(virtual function)來(lái)實(shí)現(xiàn)殴俱,只要在父類中聲明為虛函數(shù)政冻,子類中重寫的函數(shù)也自動(dòng)變?yōu)樘摵瘮?shù)枚抵。C++ 中的多態(tài)不像 OC 那樣智能线欲,如果不使用 virtual
關(guān)鍵字明场,方法調(diào)用者只會(huì)根據(jù)指針類型去尋找方法并調(diào)用,而不是根據(jù)對(duì)象類型去尋找方法李丰。所以 C++ 若想實(shí)現(xiàn)多態(tài)苦锨,一般要借助 virtual
關(guān)鍵字。
class Animal {
public:
int m_age;
virtual void speak() {
cout << "Animal::speak()" << endl;
}
virtual void run() {
cout << "Animal::run()" << endl;
}
};
class Cat : public Animal {
public:
int m_life;
Cat() :m_life(0) {}
void speak() {
cout << "Cat::speak()" << endl;
}
void run() {
cout << "Cat::run()" << endl;
}
};
Animal *cat1 = new Cat();
cat1->speak();
cat1->run();
十六趴泌、虛表
虛函數(shù)的實(shí)現(xiàn)原理是虛表(也稱為虛函數(shù)表)舟舒,虛表里面存儲(chǔ)著最終需要調(diào)用的虛函數(shù)地址。
在沒(méi)有虛函數(shù)的情況下嗜憔,對(duì)象的內(nèi)存空間為成員變量占據(jù)的空間大小秃励。virtual
關(guān)鍵字修飾之后,對(duì)象內(nèi)存會(huì)增加 4 個(gè)字節(jié)吉捶,主要用于存儲(chǔ)虛表地址夺鲜,虛表內(nèi)存中存儲(chǔ)著虛函數(shù)地址(如果子類沒(méi)有重寫父類虛函數(shù),虛表中存儲(chǔ)父類的虛函數(shù)地址)呐舔。如果對(duì)象內(nèi)存中存在虛表地址币励,會(huì)優(yōu)先調(diào)用根據(jù)虛表中的函數(shù)地址調(diào)用對(duì)應(yīng)的函數(shù),以保證調(diào)用對(duì)應(yīng)類型對(duì)象的方法珊拼;如果不存在虛表食呻,就只能依據(jù)編譯器特性根據(jù)指針類型對(duì)調(diào)用對(duì)應(yīng)的方法。注意:每創(chuàng)建一個(gè) Cat 對(duì)象內(nèi)部都包含虛表地址澎现,但是這些 Cat 對(duì)象的虛表地址指向同一張?zhí)摫韮?nèi)存空間仅胞。
十七、純虛函數(shù)
純虛函數(shù)是指沒(méi)有函數(shù)實(shí)現(xiàn)且初始化為 0 的虛函數(shù)剑辫,主要用來(lái)定義接口規(guī)范饼问。純虛函數(shù)對(duì)應(yīng)的類也被稱為抽象類,抽象類不可以實(shí)例化揭斧,只有被繼承使用才有意義莱革。抽象類也可以包含非純虛函數(shù),如果父類是抽象類讹开,子類沒(méi)有完全實(shí)現(xiàn)純虛函數(shù)盅视,那么這個(gè)子類依然是抽象類。
//這是一個(gè)純虛函數(shù)對(duì)應(yīng)的類旦万,它是一個(gè)抽象類闹击,不能直接拿來(lái)初始化。
class Animal {
public:
virtual void speak() = 0;//純虛函數(shù)
virtual void run() = 0;//純虛函數(shù)
void test{
}
};
class Cat : public Animal {
public:
void run() {
}
};
十八成艘、多繼承
C++ 允許一個(gè)類可以有多個(gè)父類赏半,但一般不建議使用贺归,因?yàn)闀?huì)增加程序設(shè)計(jì)復(fù)雜度。
class Student {
public:
int m_score;
void study() {
cout << "Student::study() - score = " << m_score << endl;
}
void eat() {
cout << "Student::eat()" << endl;
}
};
class Worker {
public:
int m_salary;
void work() {
cout << "Worker::work() - salary = " << m_salary << endl;
}
void eat() {
cout << "Worker::eat()" << endl;
}
};
class Undergraduate : public Student, public Worker {
public:
int m_grade;
void play() {
cout << "Undergraduate::play() - grade = " << m_grade << endl;
}
void eat() {
cout << "Undergraduate::eat()" << endl;
}
};
多繼承同名函數(shù)問(wèn)題
多繼承的同名函數(shù)主要通過(guò)命名空間來(lái)做區(qū)分断箫。
Undergraduate ug;
ug.eat();
ug.Worker::eat();
ug.Student::eat();
十九拂酣、菱形繼承和虛繼承
菱形繼承
class Person {
public:
int m_age;
};
class Student : public Person {
public:
int m_score;
};
class Worker : public Person {
public:
int m_salary;
};
class Undergraduate : public Student, public Worker {
public:
int m_grade;
};
菱形繼承中最底部子類從基類繼承的成員變量冗余、重復(fù)仲义,所以最底部子類無(wú)法訪問(wèn)基類的成員婶熬,有二義性。即Undergraduate
對(duì)象無(wú)法訪問(wèn)基類 Person
的成員變量 m_age
埃撵。
虛繼承
虛繼承可以用來(lái)解決菱形繼承問(wèn)題赵颅。
class Student : virtual public Person {
public:
int m_score;
};
class Worker : virtual public Person {
public:
int m_salary;
};
二十、static
靜態(tài)成員變量
被 static
修飾的成員變量稱為靜態(tài)成員變量暂刘,可以通過(guò)對(duì)象(對(duì)象.靜態(tài)成員)饺谬、對(duì)象指針(對(duì)象指針->靜態(tài)成員)、類訪問(wèn)(類名::靜態(tài)成員)谣拣。存儲(chǔ)在全局區(qū)募寨,類似于全局變量,整個(gè)程序運(yùn)行過(guò)程中只有一份內(nèi)存芝发。同全局變量最大的區(qū)別在于绪商,靜態(tài)成員變量限制了作用域,僅相關(guān)類可以直接使用辅鲸。使用時(shí)需注意必須初始化格郁,且必須在類外面初始化。
class Person {
public:
static int ms_count;
};
//必須在類外面初始化
int Person::ms_count = 0;
int main() {
Person::ms_count = 20;
return 0;
}
靜態(tài)成員函數(shù)
被 static
修飾的成員函數(shù)稱為靜態(tài)成員函數(shù)独悴,存在于代碼區(qū)例书。內(nèi)部不能使用 this
指針,this
指針只能用在非靜態(tài)成員函數(shù)內(nèi)部刻炒。內(nèi)部不能訪問(wèn)非靜態(tài)成員變量\函數(shù)决采,只能訪問(wèn)靜態(tài)成員變量\函數(shù)。不能是虛函數(shù)坟奥,虛函數(shù)只能是非靜態(tài)成員函數(shù)树瞭。
class Car {
public:
static int m_price;
static void run() {
cout << "run()" << endl;
}
};
靜態(tài)成員應(yīng)用(單例)
class Rocket {
public:
// C++:靜態(tài)成員函數(shù)
// Java、OC:類方法
//提供一個(gè)公共的返回單例對(duì)象的靜態(tài)成員函數(shù)
static Rocket * sharedRocket() {
// API p_thread
if (ms_rocket == NULL) {
ms_rocket = new Rocket();
}
return ms_rocket;
}
static void deleteRocket() {
static Rocket *ms_rocket;
if (ms_rocket == NULL) return;
delete ms_rocket;
ms_rocket = NULL;
}
private:
//使用 static 保證只有一個(gè)爱谁,私有化禁止外部訪問(wèn)
static Rocket *ms_rocket;
//禁止外部創(chuàng)建
Rocket() {
cout << "Rocket()" << endl;
}
~Rocket() {
cout << "~Rocket()" << endl;
}
};
Rocket *Rocket::ms_rocket = NULL;
int main() {
Rocket *p1 = Rocket::sharedRocket();
// ......
Rocket::deleteRocket();
return 0;
}