中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁 > news >正文

做網(wǎng)站是怎么回事國內(nèi)免費推廣產(chǎn)品的網(wǎng)站

做網(wǎng)站是怎么回事,國內(nèi)免費推廣產(chǎn)品的網(wǎng)站,英語培訓(xùn)建設(shè)網(wǎng)站方案,做網(wǎng)站內(nèi)容圖片多大1. 類和對象 1.1. 封裝 封裝的意義 將屬性和行為作為一個整體,表現(xiàn)生活中的事物;將屬性和行為加以權(quán)限控制 public -> 公共權(quán)限:類內(nèi)可以訪問,類外也可以訪問protected -> 保護(hù)權(quán)限:類內(nèi)可以訪問,…

1. 類和對象

1.1. 封裝

封裝的意義

  • 將屬性和行為作為一個整體,表現(xiàn)生活中的事物;
  • 將屬性和行為加以權(quán)限控制
    • public -> 公共權(quán)限:類內(nèi)可以訪問,類外也可以訪問
    • protected -> 保護(hù)權(quán)限:類內(nèi)可以訪問,類外不可以訪問,子類可以訪問
    • private -> 私有權(quán)限:類內(nèi)可以訪問,類外不可以訪問,子類不可以訪問

struct和class的區(qū)別

  • struct默認(rèn)權(quán)限為公共權(quán)限
  • class默認(rèn)權(quán)限為私有權(quán)限

1.2. 對象的初始化和清理

1.2.1. 構(gòu)造函數(shù)

  • 編譯器自動調(diào)用,完成對象的初始化工作
  • 在創(chuàng)建對象時為對象的成員屬性賦值
1.2.1.1. 構(gòu)造函數(shù)的分類

無參構(gòu)造函數(shù)

#include<iostream>
#include<string>
using namespace std;class Student
{
private:string mName;int mAge;
public:// 無參構(gòu)造函數(shù)Student() {cout << "調(diào)用無參構(gòu)造函數(shù)" << endl;};
};

有參構(gòu)造函數(shù)

#include<iostream>
#include<string>
using namespace std;class Student
{
private:string mName;int mAge;
public:// 有參構(gòu)造函數(shù)Student(string name, int age) {cout << "調(diào)用有參構(gòu)造函數(shù)" << endl;mName = name;mAge = age;}string getName(){return mName;}int getAge(){return mAge;}// 析構(gòu)函數(shù)~Student() {};
};

拷貝構(gòu)造函數(shù)

#include<iostream>
#include<string>
using namespace std;class Student
{
private:string mName;int mAge;
public:// 拷貝構(gòu)造函數(shù)Student(const Student &s) {mName = s.mName;mAge = s.mAge;};string getName(){return mName;}int getAge(){return mAge;}// 析構(gòu)函數(shù)~Student() {};
};int main()
{Student s1 = Student("lisi", 23);// 調(diào)用拷貝構(gòu)造函數(shù)Student s2 = s1;// 不能使用匿名對象構(gòu)造拷貝構(gòu)造方法,編譯器會把代碼轉(zhuǎn)換為Student s2; 認(rèn)為重復(fù)定義了一個s2的變量。//Student(s2);			// 錯誤的代碼
}
1.2.1.2. 構(gòu)造函數(shù)的初始化列表
#include<iostream>
#include<string>
using namespace std;class CDate
{
public:CDate(int year, int month, int day){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};class CGoods
{
public:CGoods(const char* n, int a, double p, int year, int month, int day):_date(year, month, day), // 這行代碼相當(dāng)于 CDate _date(year, month, day),即指定有參構(gòu)造函數(shù)構(gòu)造CDate對象,_amount(a), _price(p){strcpy(_name, n);					// _name成員變量必須要放到構(gòu)造函數(shù)體中初始化// _date = CDate(year, month, day) 	// 如果寫在構(gòu)造函數(shù)體中,相當(dāng)于 CDate _date; _date = CDate(year, month, day),// 即先構(gòu)造一個CDate對象(默認(rèn)使用無參構(gòu)造函數(shù)),然后再給這個對象賦值。// 但是CDate并沒有默認(rèn)構(gòu)造函數(shù),因此在編譯的時候就會報錯。	    }
private:char _name[20];int _amount;double _price;CDate _date;	// 成員對象
};#if 0
int main()
{CGoods good("沙發(fā)", 1, 12000, 2022, 1, 1);
}
#endif // 0
1.2.1.3. 構(gòu)造函數(shù)的成員初始化順序

成員變量的初始化順序只與其在類中被聲明的順序有關(guān),而與在初始化列表的順序無關(guān)。

//
//  main.cpp
//  數(shù)據(jù)成員的初始化順序問題
//
//  Created by apple on 2023/10/7.
//#include <iostream>
#include <string>using namespace std;class Point
{
public:Point(int ix):_iy(ix),_ix(_iy)// 會先初始化_ix,再初始化_iy{cout << "Point(int,int)" << endl;}void print(){cout << _ix << "\t" << _iy << endl;}private:int _ix;int _iy;
};int main(int argc, const char * argv[]) {Point p(3);p.print();return 0;
}

打印結(jié)果如下:

Point(int,int)

1 3

1.2.1.4. 拷貝構(gòu)造函數(shù)的調(diào)用時機(jī)(重點)

使用一個已經(jīng)創(chuàng)建完成的對象來初始化一個新對象

Student s1 = Student("lisi", 23);
// 調(diào)用拷貝構(gòu)造函數(shù)
Student s2 = s1;

值傳遞的方式給函數(shù)參數(shù)傳值

#include <iostream>
#include <string>
using namespace std;class Student
{
public:int age;int* p_height;// 構(gòu)造函數(shù)// 無參構(gòu)造函數(shù)Student() {cout << "調(diào)用無參構(gòu)造函數(shù)" << endl;};// 有參構(gòu)造函數(shù)Student(int a, int height) {cout << "調(diào)用有參構(gòu)造函數(shù)" << endl;age = a;p_height = new int(height);}// 自定義拷貝構(gòu)造函數(shù)Student(const Student &s) {cout << "調(diào)用自定義的拷貝構(gòu)造函數(shù)" << endl;age = s.age;p_height = new int(*s.p_height);};//析構(gòu)函數(shù)~Student() {// 在析構(gòu)函數(shù)中可以做一些資源釋放的工作if (p_height != nullptr) {delete p_height;p_height = nullptr;}cout << "調(diào)用析構(gòu)函數(shù)" << endl;};
};void func(Student s)		// 值傳遞
{cout << s.age <<"\t"<< *s.p_height << endl;
}int main()
{// 顯式構(gòu)造對象Student s = Student(23, 170);// 括號法構(gòu)造對象Student s1 (23, 170);// 以值的方式將對象傳遞給函數(shù)參數(shù),會調(diào)用拷貝構(gòu)造函數(shù)func(s);
}

輸出結(jié)果如下:

調(diào)用有參構(gòu)造函數(shù)調(diào)用有參構(gòu)造函數(shù)調(diào)用自定義的拷貝構(gòu)造函數(shù)23 170調(diào)用析構(gòu)函數(shù)調(diào)用析構(gòu)函數(shù)調(diào)用析構(gòu)函數(shù)

以值方式返回局部對象

#include<iostream>
#include<string>
using namespace std;class Student
{
public:int age;int* p_height;// 構(gòu)造函數(shù)// 無參構(gòu)造函數(shù)Student() {cout << "調(diào)用無參構(gòu)造函數(shù)" << endl;};// 有參構(gòu)造函數(shù)Student(int a, int height) {cout << "調(diào)用有參構(gòu)造函數(shù)" << endl;age = a;p_height = new int(height);}// 自定義拷貝構(gòu)造函數(shù)Student(const Student &s) {cout << "調(diào)用自定義的拷貝構(gòu)造函數(shù)" << endl;age = s.age;p_height = new int(*s.p_height);};//析構(gòu)函數(shù)~Student() {// 在析構(gòu)函數(shù)中可以做一些資源釋放的工作if (p_height != nullptr) {delete p_height;p_height = nullptr;}cout << "調(diào)用析構(gòu)函數(shù)" << endl;};
};Student func1()
{Student s = Student(21, 180);// 以值的方式返回局部對象,會調(diào)用拷貝函數(shù)return s;
}int main()
{Student s = func1();
}
1.2.1.5. 拷貝構(gòu)造函數(shù)的形式是固定的

拷貝構(gòu)造函數(shù)形式:類名(const 類名 & rhs)

是否可以去掉引用符號?即將其改為 類名(const 類名 rhs),答案是不可以,因為會產(chǎn)生構(gòu)造函數(shù)無窮遞歸調(diào)用的情況。

當(dāng)執(zhí)行 Point pt2 = pt 時,會調(diào)用拷貝構(gòu)造函數(shù),然后拷貝構(gòu)造函數(shù)的形參會初始化,初始化形參又會調(diào)用拷貝構(gòu)造函數(shù),這樣無窮遞歸下去,直到棧溢出。

是否可以去掉const關(guān)鍵字?即將其改為 類名( 類名 & rhs),答案是不可以,因為非const引用不能綁定右值。

假設(shè) func()函數(shù)的返回值是一個 Point 對象,當(dāng)執(zhí)行 Point pt2 = func() 時,會調(diào)用拷貝構(gòu)造函數(shù),然而當(dāng)給拷貝構(gòu)造函數(shù)傳遞參數(shù)時, 如果沒有 const,Point & rp = func() 是不正確的,因為 func()是一個臨時對象,是右值,非const引用不能綁定右值。

1.2.1.6. 構(gòu)造函數(shù)的調(diào)用規(guī)則
  • 如果用戶定義了有參構(gòu)造函數(shù),c++不再提供默認(rèn)的無參構(gòu)造函數(shù),但是會提供默認(rèn)的拷貝構(gòu)造函數(shù);
  • 如果用戶定義了拷貝構(gòu)造函數(shù),c++不再提供其他構(gòu)造函數(shù)。
1.2.1.7. 構(gòu)造函數(shù)的調(diào)用方式

總結(jié)

Student("zhaoliu", 21); // 匿名對象

Student s1("zhangsan", 20); // 括號法

Student s2 = Student("lisi", 18); // 顯式構(gòu)造

Student s3 = {"wangwu", 19}; // 隱式轉(zhuǎn)換

括號法

#include<iostream>
#include<string>
using namespace std;class Student
{
public:// 無參構(gòu)造函數(shù)Student() {cout << "調(diào)用無參構(gòu)造函數(shù)" << endl;};// 有參構(gòu)造函數(shù)Student(string n, int a) {cout << "調(diào)用有參構(gòu)造函數(shù)" << endl;name = n;age = a;}// 拷貝構(gòu)造函數(shù)Student(const Student &s) {name=s.name;age = s.age;};string getName(){return name;}int getAge(){return age;}// 析構(gòu)函數(shù)~Student() {};private:string name;int age;
};
int main()
{// 實例化對象:括號法// 調(diào)用無參構(gòu)造方法時,不能加"()",因為編譯器認(rèn)為這是一個函數(shù)的聲明Student s1;// 調(diào)用有參構(gòu)造方法Student s2("zhansan", 22);cout << s2.getName() << "\t"<<s2.getAge() << endl;// 匿名對象,當(dāng)前行執(zhí)行結(jié)束之后,系統(tǒng)會立即收掉匿名對象Student("wangwu", 23); }

顯示法

#include<iostream>
#include<string>
using namespace std;class Student
{
public:// 無參構(gòu)造函數(shù)Student() {cout << "調(diào)用無參構(gòu)造函數(shù)" << endl;};// 有參構(gòu)造函數(shù)Student(string n, int a) {cout << "調(diào)用有參構(gòu)造函數(shù)" << endl;name = n;age = a;}// 拷貝構(gòu)造函數(shù)Student(const Student &s) {name=s.name;age = s.age;};string getName(){return name;}int getAge(){return age;}// 析構(gòu)函數(shù)~Student() {};private:string name;int age;
};
int main()
{// 實例化對象:顯示法Student s3 = Student("lisi", 23);cout << s3.getName() << "\t" << s3.getAge() << endl;// 匿名對象,當(dāng)前行執(zhí)行結(jié)束之后,系統(tǒng)會立即收掉匿名對象Student("wangwu", 23); 
}

隱式轉(zhuǎn)換法

#include<iostream>
#include<string>
using namespace std;class Student
{
public:// 無參構(gòu)造函數(shù)Student() {cout << "調(diào)用無參構(gòu)造函數(shù)" << endl;};// 有參構(gòu)造函數(shù)Student(string n, int a) {cout << "調(diào)用有參構(gòu)造函數(shù)" << endl;name = n;age = a;}// 拷貝構(gòu)造函數(shù)Student(const Student &s) {name=s.name;age = s.age;};string getName(){return name;}int getAge(){return age;}// 析構(gòu)函數(shù)~Student() {};private:string name;int age;
};
int main()
{// 實例化對象:隱式轉(zhuǎn)換法,如果有參構(gòu)造函數(shù)只有一個參數(shù),就不需要加"{}",但是可能會導(dǎo)致問題,不推薦。Student s5 = { "zhaoliu", 26 };
}
1.2.1.8. 帶explicit 關(guān)鍵字的構(gòu)造函數(shù)

不帶 explicit 關(guān)鍵字

class MyClass {
public:MyClass(int x) {value = x;}
private:int value;
};void func(MyClass obj) {// do something
}int main() {MyClass a = 10; 	// 隱式調(diào)用MyClass(int)構(gòu)造函數(shù)func(10);      		// 隱式將int轉(zhuǎn)換為MyClass對象return 0;
}

帶 explicit 關(guān)鍵字

class MyClass {
public:explicit MyClass(int x) {value = x;}
private:int value;
};void func(MyClass obj) {// do something
}int main() {MyClass a = 10; 		// 錯誤:不能隱式轉(zhuǎn)換int到MyClassMyClass b(10);  		// 正確:顯式調(diào)用構(gòu)造函數(shù)func(10);       		// 錯誤:不能隱式轉(zhuǎn)換int到MyClassfunc(MyClass(10)); 		// 正確:顯式創(chuàng)建MyClass對象return 0;
}

1.2.2. 析構(gòu)函數(shù)

  • 編譯器自動調(diào)用,完成對象的清理工作
  • 在對象銷毀前執(zhí)行一些清理工作
1.2.2.1. 析構(gòu)函數(shù)的調(diào)用時機(jī)(重點)
  • 棧對象生命周期結(jié)束時,會自動調(diào)用析構(gòu)函數(shù);
  • 全局對象在main函數(shù)退出時,會自動調(diào)用析構(gòu)函數(shù);
  • (局部)靜態(tài)對象在main函數(shù)退出時,會自動調(diào)用析構(gòu)函數(shù);
  • 堆對象在執(zhí)行delete時,會自動調(diào)用析構(gòu)函數(shù)。

1.2.3. 淺拷貝和深拷貝(重點)

#include<iostream>
#include<string>
using namespace std;class Student
{
public:int age;int* p_height;// 無參構(gòu)造函數(shù)Student() {cout << "調(diào)用無參構(gòu)造函數(shù)" << endl;};// 有參構(gòu)造函數(shù)Student(int a, int height) {cout << "調(diào)用有參構(gòu)造函數(shù)" << endl;age = a;// 在堆內(nèi)存中創(chuàng)建一塊空間p_height = new int(height);}// 自定義拷貝構(gòu)造函數(shù)Student(const Student &s) {cout << "調(diào)用自定義的拷貝構(gòu)造函數(shù)" << endl;age = s.age;// p_height = s.p_height  編譯器默認(rèn)實現(xiàn)的就是這行代碼p_height = new int(*s.p_height);		// 自定義實現(xiàn)深拷貝};//析構(gòu)函數(shù)~Student() {// 在析構(gòu)函數(shù)中可以做一些資源釋放的工作if (p_height != nullptr) {// 釋放內(nèi)存delete p_height;p_height =nullptr;}cout << "調(diào)用析構(gòu)函數(shù)" << endl;};
};void test()
{Student s1(18, 170);cout << "s1對象的年齡:" << s1.age << ",s1對象的身高:" << *s1.p_height << endl;// Student s2 = s1;Student s2(s1);// 如果沒有自定義拷貝構(gòu)造函數(shù),默認(rèn)是淺拷貝,當(dāng)s2對象執(zhí)行析構(gòu)函數(shù)之后,p_height所指向的內(nèi)存就被釋放了// 然后,當(dāng)s1對象執(zhí)行析構(gòu)函數(shù)時,再釋放p_height的內(nèi)存就是非法操作了,因此會報錯,解決的辦法就是深拷貝。// 通過自定義拷貝構(gòu)造函數(shù)來實現(xiàn)深拷貝cout << "s2對象的年齡:" << s2.age << ",s2對象的身高:" << *s2.p_height << endl;
}
int main()
{test();
}

1.2.4. 賦值函數(shù)

class Computer
{
public:Computer(const char *brand, double price): _brand(new char[strlen(brand) + 1]()), _price(price){cout << "Computer(const char *, double)" << endl;}~Computer(){cout << "~Computer()" << endl;delete [] _brand;_brand = nullptr;}Computer &Computer::operator=(const Computer &rhs){if(this != &rhs) 	//1、自復(fù)制{delete [] _brand; 	//2、釋放左操作數(shù)_brand = nullptr;_brand = new char[strlen(rhs._brand) + 1](); //3、深拷貝strcpy(_brand, rhs._brand);_price = rhs._price;}return *this; 		//4、返回*this}
private:char *_brand;double _price;
};
  • 返回類型不必須是類類型:可以是其他類型,但一般沒有這種需求。
  • 返回值不一定是引用:可以返回值類型,但這樣會引入不必要的拷貝。
class MyClass {
public:MyClass operator=(const MyClass& other) {if (this != &other) {// 執(zhí)行賦值操作}return *this; // 這里會涉及一次拷貝}
};

1.2.5. 初始化列表

#include<iostream>
#include<string>
using namespace std;class Student
{
public:string s_name;int s_age;// 初始化列表,  s_name(name)相當(dāng)于string s_name=name,  s_age(a)相當(dāng)于int s_age = a;Student(string name, int a) :s_name(name), s_age(a){cout << "調(diào)用有參構(gòu)造函數(shù)" << endl;}
};int main()
{// 括號法創(chuàng)建對象Student s1 ("zhangsan", 18);// 顯示法創(chuàng)建對象Student s2 = Student("lisi", 19);cout << "s_name:" << s1.s_name << endl;cout << "s_age:" << s1.s_age << endl;
}

1.2.6. const成員變量(重點)

const成員變量必須要放在初始化列表中進(jìn)行初始化。

class Book{
public:Book( int s );
private:int i;const int j;int &k;Book::Book( int s ): i(s), j(s), k(s){}

1.2.7. 引用成員變量(重點)

引用成員變量也必須要放在初始化列表中進(jìn)行初始化。

1.2.8. 類對象作為類成員

結(jié)論:當(dāng)其他類對象作為本類成員,構(gòu)造函數(shù)先構(gòu)造其他類對象,再構(gòu)造本對象;析構(gòu)的順序與構(gòu)造相反。

1.2.9. 靜態(tài)成員變量

  • 所有對象共享該成員變量
  • 在編譯階段分配內(nèi)存
  • 類內(nèi)聲明,類外初始化
#include<iostream>
#include<string>
using namespace std;class Person
{
public:// 靜態(tài)成員變量,類內(nèi)聲明static int pAge;
};
// 類外初始化,注意初始化的語法
int Person::pAge = 18;void test()
{// 靜態(tài)成員變量可以直接通過類名訪問cout << Person::pAge << endl;
}int main()
{test();
}

1.2.10. 靜態(tài)成員函數(shù)

  • 靜態(tài)成員函數(shù)內(nèi)部只能訪問靜態(tài)成員屬性和靜態(tài)成員函數(shù);
  • 靜態(tài)成員函數(shù)的參數(shù)列表中沒有隱含的this指針。
#include<iostream>
#include<string>
using namespace std;class Person
{
public:// 靜態(tài)成員函數(shù),類內(nèi)聲明static void func();
};
// 類外初始化,注意初始化的語法
void Person::func() {cout << "Person類的靜態(tài)成員方法" << endl;
}int main()
{Person::func();
}

1.2.11. 虛函數(shù)(*)

virtual

#include <iostream>
#include <typeinfo>using namespace std;class Base
{
public:Base(int data = 10):ma(data){}		// 構(gòu)造函數(shù)virtual void show() { cout << "Base::show()" << endl; }void show(int) { cout << "Base::show(int)" << endl; }
private:int ma;
};
class Derive :public Base
{
public:Derive(int data=20):Base(data), mb(data){}void show() { cout << "Derive::show()" << endl; }
private:int mb;
};#if 1
int main()
{Derive d(50);Base *pb = &d;pb->show();    // 動態(tài)綁定 Derive::show()pb->show(10);  // 靜態(tài)綁定 Base::show(int)cout << sizeof(Base) << endl;cout << sizeof(Derive) << endl;cout << typeid(pb).name() << endl;		// 指針的類型 class Base*// 如果Base中沒有虛函數(shù),*pb識別的就是編譯時期的類型,即Base// 如果Base中有虛函數(shù),*pb識別的就是運行時期的類型 即RTTI指針指向的類型cout << typeid(*pb).name() << endl;		// 指針指向的類型 class Derive
}
#endif
  • 特性1:如果一個類里面定義了虛函數(shù),那么在編譯階段,編譯器就會給這個類產(chǎn)生唯一的一個vftable(虛函數(shù)表)。虛函數(shù)表中主要存儲的內(nèi)容就是RTTI指針虛函數(shù)的地址。當(dāng)程序運行時,每一張?zhí)摵瘮?shù)表都會加載到內(nèi)存的只讀數(shù)據(jù)區(qū)。
  • 特性2:如果一個類里面定義了虛函數(shù),那么這個類的內(nèi)存會多存儲一個指向虛函數(shù)地址的指針(vfptr)。

  • 特性3:如果派生類中的方法和繼承過來的某個方法在返回值、函數(shù)名、參數(shù)列表上都相同,而且基類中的該方法是虛函數(shù),那么派生類的這個方法會被自動處理成虛函數(shù)。

1.2.11.1. 靜態(tài)綁定

類中的普通函數(shù)在編譯時就確定了地址了,即為靜態(tài)綁定。c++的重載函數(shù)就是靜態(tài)綁定,因為在編譯的時候就需要確定調(diào)用的是哪個函數(shù)。

1.2.11.2. 動態(tài)綁定

類中的虛函數(shù)在編譯時不能確定函數(shù)地址,即為動態(tài)綁定。另外,必須使用指針(引用)的方式調(diào)用虛函數(shù)才會產(chǎn)生動態(tài)綁定。

Base &rb1 = b;
rb1.show();
Base &rb2 = d;
rb2.show();Derive *pd1 = &d;
pd1->show();

由對象直接調(diào)用虛函數(shù)不會產(chǎn)生動態(tài)綁定,因為可以確定是哪個對象調(diào)用的。

Base b;
b.show(); // 靜態(tài)綁定
Derive d;
d.show(); // 靜態(tài)綁定
1.2.11.3. 哪些函數(shù)不能成為虛函數(shù)

首先說一下虛函數(shù)依賴

  • 虛函數(shù)要能產(chǎn)生地址,存儲在虛函數(shù)表中;
  • 對象必須存在,只能通過對象來找到虛函數(shù)表的地址,進(jìn)而找到虛函數(shù)地址

從虛函數(shù)依賴可知,以下函數(shù)不能成為虛函數(shù)

  • 構(gòu)造函數(shù):要執(zhí)行完構(gòu)造函數(shù)后才能創(chuàng)建對象,因此在調(diào)用構(gòu)造函數(shù)時還沒有對象,不能成為虛函數(shù)。且在構(gòu)造函數(shù)中調(diào)用虛函數(shù)也不會發(fā)生靜態(tài)綁定;
  • static成員函數(shù):跟對象沒有關(guān)系,也是不行的。
1.2.11.4. 虛析構(gòu)函數(shù)

1.3. c++對象模型和this指針

1.3.1. 成員變量和成員函數(shù)內(nèi)存空間歸屬

  • 非靜態(tài)成員變量占用的內(nèi)存空間屬于類對象;
  • 靜態(tài)成員變量、靜態(tài)成員函數(shù)、非靜態(tài)成員函數(shù)占用的內(nèi)存空間都不屬于類對象;
  • 空對象占用的空間大小為1個字節(jié)。

1.3.2. this指針的使用

this指針本質(zhì)上是一個指針常量,即指向的對象是不可以再更改的,但是指向的對象的值是可以修改的。

每一個成員函數(shù)都擁有一個隱含的this指針,這個this指針作為函數(shù)的第一個參數(shù)。

#include<iostream>
#include<string>using namespace std;const int NAME_LEN = 20;class CGoods
{
public:// 編譯的時候會在第一個參數(shù)的位置添加this指針,void show(CGood* this);void show(){cout << "show方法" << endl;}// 編譯的時候會在第一個參數(shù)的位置添加this指針,void setPrice(CGood* this, double price)void setPrice(double price){_price = price;}
private:char _name[NAME_LEN];double _price;int _amount;
};#if 1
int main()
{CGoods good;good.show();
}
#endif
#include<iostream>
#include<string>using namespace std;class Person
{
public:int age;Person(int age){this->age = age;}// 引用作為函數(shù)的返回值 Person &p = personPerson& addAge(int age){// this指針this->age += age;// this指針的解引用就是當(dāng)前對象return *this;}
};void test()
{Person p(10);p.addAge(10).addAge(10).addAge(10);cout << p.age << endl;
}
int main()
{test();
}

1.4. C++ this和*this的區(qū)別

  • this返回的是當(dāng)前對象的地址(指向當(dāng)前對象的指針);
  • *this返回的是當(dāng)前對象的克隆和本身(若返回類型是A,則是克隆,若返回類型是A&,則是本身);
std::unique_ptr<PaddleClasModel> PaddleClasModel::Clone() const {std::unique_ptr<PaddleClasModel> clone_model =utils::make_unique<PaddleClasModel>(PaddleClasModel(*this));clone_model->SetRuntime(clone_model->CloneRuntime());return clone_model;

PaddleClasModel(*this)調(diào)用的是默認(rèn)的拷貝構(gòu)造函數(shù)

class PaddleClasModel {
public:PaddleClasModel(const PaddleClasModel& other);
};

1.4.1. 常對象和常函數(shù)

常函數(shù)

  • 成員函數(shù)后加const即為常函數(shù)
  • 常函數(shù)內(nèi)不可以修改成員屬性
  • 成員屬性聲明時加關(guān)鍵字mutable后,在常函數(shù)中依然可以修改

常對象

  • 聲明對象前加const即為常對象
  • 常對象只能調(diào)用常函數(shù)。常方法,編譯器添加的是 const 修飾的 this 指針
  • 非常對象也可以調(diào)用常函數(shù)
#include<iostream>
#include<string>using namespace std;class Person
{
public:int age;// 成員屬性加了mutable關(guān)鍵字,即使在常函數(shù)中也是可以更改name屬性的值的mutable string name;Person(int age, string name){this->age = age;this->name = name;}// 常函數(shù),使用const修飾,本質(zhì)上就是const Person * const p;因此既不可以更改對象,也不可以更改對象的屬性值void func() const{cout << "調(diào)用常函數(shù)" << endl;//this->age = 20;  錯誤,常函數(shù)不可以修改成員屬性// name屬性是可以修改的this->name = "wangwu";}void func2(){cout << "調(diào)用普通函數(shù)" << endl;}};void test()
{Person p(10, "lisi");p.func();cout << p.name << endl;// 常對象const Person p2(20, "zhaoliu");//p2.func2();		 那么p2.func();}
int main()
{test();
}

1.4.2. 空指針訪問成員函數(shù)

#include<iostream>
#include<string>
using namespace std;class Person
{
public:int age;void showClass(){cout << "this is Person class" << endl;}void showPersonAge(){// age 默認(rèn)指的是this->age既然this都是空了,那么訪問this->age肯定就會報錯了cout << "age = " << age << endl;}
};void test()
{Person *p = nullptr;//p->showClass();				// 正確//p->showPersonAge();			// 報錯
}int main()
{test();
}

1.5. 類和對象代碼應(yīng)用實踐

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>using namespace std;class String
{
public:String(const char *str=nullptr){if (str != nullptr){m_data = new char[strlen(str) + 1];strcpy(this->m_data, str);}else{m_data = new char[1];*m_data = '\0';}}// 拷貝構(gòu)造函數(shù)String(const String& str){m_data = new char[strlen(str.m_data) + 1];strcpy(m_data, str.m_data);}// 析構(gòu)函數(shù)~String(){delete[] m_data;m_data = nullptr;}// 賦值構(gòu)造函數(shù)String& operator=(const String & other){if (this == &other){return *this;}delete[] m_data;	// 先釋放之前指向堆內(nèi)存的空間m_data = new char[strlen(other.m_data) + 1];	// 再重新申請一塊堆空間,c語言的字符串最后有一個'\0'字符,因此需要多一個字符的長度strcpy(this->m_data, other.m_data);		// c語言的strcpy函數(shù),再將other的m_data值拷貝到堆空間return *this;}char* m_data;
};#if 1
int main()
{String s1;				// 調(diào)用無參構(gòu)造函數(shù)String s2("hello");		// 調(diào)用有參構(gòu)造函數(shù)	String s3(s2);			// 調(diào)用拷貝構(gòu)造函數(shù)s3 = s1;				// 調(diào)用賦值構(gòu)造函數(shù)
}
#endif

1.6. 指向類成員的指針

#include  <iostream>using namespace std;class Test
{
public:int mb;static int si;void func() { cout << "call Test::func" << endl; }static void static_func() { cout << "call Test::static_func" << endl; }
};int Test::si = 20;int main()
{Test t1;int Test::*p = &Test::mb;	// 如果寫成int *p = &Test::mb; 會報錯,無法從 int Test::* 轉(zhuǎn)換成 int *// 指針指向普通成員變量,脫離對象訪問成員是沒有意義的,因此,在訪問p時必須加上對象,不能直接是*p=20t1.*p = 20;cout << t1.mb << endl;// 指針指向靜態(tài)成員變量,這里就可以這樣寫了int *p1 = &Test::si;*p1 = 30;cout << Test::si << endl;// 指針指向普通成員方法void(Test::*func)() = &Test::func; // 如果寫成void(*func)() = &Test::func;  會報錯,無法從 void (__thiscall Test::*)(void)轉(zhuǎn)換成void (__cdecl *)(void)(t1.*func)();// 指針指向靜態(tài)成員方法void(*static_func)() = &Test::static_func;(*static_func)();}

1.7. 對象數(shù)組

1.8. 友元

友元提供了另一訪問類的私有成員的方案

1.8.1. 全局函數(shù)做友元

#include<iostream>
#include<string>using namespace std;class Building
{// 全局函數(shù)做友元,告訴編譯器,全局函數(shù)goodGay是Building類的好朋友,可以訪問類中的私有內(nèi)容friend void goodGay(Building building);
public:string m_SittingRoom;Building(){this->m_SittingRoom = "客廳";this->m_BedRom = "臥室";}
private:string m_BedRom;
};void goodGay(Building building)
{cout << "好基友正在訪問:" << building.m_SittingRoom << endl;cout << "好基友正在訪問:" << building.m_BedRom << endl;
}int main()
{Building building;goodGay(building);
}

1.8.2. 類做友元

#include<iostream>
#include<string>using namespace std;class Building;class GoodGay 
{
public:// 這里只是聲明了構(gòu)造方法GoodGay();// 這里只是聲明了成員函數(shù)void visit();
private:// 成員變量Building *building;
};
class Building
{// 友元類,告訴編譯器GoodGay類可以訪問Building類中的私有內(nèi)容friend class GoodGay;
public:// 聲明構(gòu)造方法Building();public:// 公共的成員變量string m_sittingRoom;
private:// 私有的成員變量string m_bedroom;
};
// 在類的外部定義Building構(gòu)造函數(shù)
Building::Building()
{this->m_bedroom = "臥室";this->m_sittingRoom = "客廳";
}
// 在類的外部定義GoodGay構(gòu)造函數(shù)
GoodGay::GoodGay() {building = new Building();
}
// 在類的外部定義visit方法
void GoodGay::visit()
{cout << "好基友正在訪問" << building->m_bedroom << endl;cout << "好基友正在訪問" << building->m_sittingRoom << endl;
}int main()
{GoodGay goodGay = GoodGay();goodGay.visit();
}

1.8.3. 成員函數(shù)做友元

#include<iostream>
#include<string>using namespace std;class Building;class GoodGay 
{
public:// 這里只是聲明了構(gòu)造方法GoodGay();// 這里只是聲明了成員函數(shù)void visit();
private:// 成員變量Building *building;
};
class Building
{// 告訴編譯器GoodGay類中的成員函數(shù)visit可以訪問Building類中的私有內(nèi)容friend void GoodGay::visit();
public:// 聲明構(gòu)造方法Building();public:// 公共的成員變量string m_sittingRoom;
private:// 私有的成員變量string m_bedroom;
};
// 在類的外部定義Building構(gòu)造函數(shù)
Building::Building()
{this->m_bedroom = "臥室";this->m_sittingRoom = "客廳";
}
// 在類的外部定義GoodGay構(gòu)造函數(shù)
GoodGay::GoodGay() {// 無參構(gòu)造函數(shù)"()"可以省略//building = new Building;building = new Building();}
// 在類的外部定義visit方法
void GoodGay::visit()
{cout << "好基友正在訪問" << building->m_bedroom << endl;cout << "好基友正在訪問" << building->m_sittingRoom << endl;
}int main()
{GoodGay goodGay = GoodGay();goodGay.visit();
}

1.9. 運算符重載

注意事項:

  • 對于內(nèi)置的數(shù)據(jù)類型的表達(dá)式的運算符是不可能改變的;
  • 不要濫用運算符重載

1.9.1. 算術(shù)運算符重載

1.9.1.1. 加號運算符重載
#include<iostream>
#include<string>using namespace std;class Person
{
public:int m_a;int m_b;// 使用成員函數(shù)重載+運算符//Person operator+(Person &p)//{//	Person temp;//	temp.m_a = m_a + p.m_a;//	temp.m_b = m_b + p.m_b;//	return temp;//}
};// 使用全局函數(shù)重載+運算符
Person operator+(Person &p1, Person &p2)
{Person temp;temp.m_a = p1.m_a + p2.m_a;temp.m_b = p1.m_b + p2.m_b;return temp;
}// 運算符重載的函數(shù)重載
Person operator+(Person &p1, int num)
{Person temp;temp.m_a = p1.m_a + num;temp.m_b = p1.m_b + num;return temp;
}
int main()
{// 使用括號法調(diào)用無參構(gòu)造函數(shù)不能加括號,因此是Person p1,而不是Person p1()Person p1;p1.m_a = 10;p1.m_b = 10;Person p2;p2.m_a = 10;p2.m_b = 10;Person p3 = p1 + p2;cout << "p3.m_a = " << p3.m_a << "\t" << "p3.m_b = " << p3.m_b << endl;Person p4 = p1 + 100;cout << "p4.m_a = " << p4.m_a << "\t" << "p4.m_b = " << p4.m_b << endl;
}
1.9.1.2. 左移運算符重載

作用:

  • 輸出自定義對象的成員變量;
  • 只能使用全局函數(shù)重載版本;
  • 如果要輸出對象的私有成員,可以配合友元一起使用。
#include<iostream>
#include<string>using namespace std;class Person
{// 全局函數(shù)做友元,可以訪問類中的私有屬性friend ostream & operator<<(ostream &out, Person &p);
private:int m_a;int m_b;
public:void setA(int a){m_a = a;}void setB(int b){m_b = b;}
};// 使用全局函數(shù)重載+運算符
ostream & operator<<(ostream &out, Person &p)
{out << "p.m_a = " << p.m_a << ", p.m_b = " << p.m_b;return out;
}int main()
{// 使用括號法調(diào)用無參構(gòu)造函數(shù)不能加括號,因此是Person p1,而不是Person p1()Person p1;p1.setA(10);p1.setB(10);cout << p1 << endl;
}
1.9.1.3. 遞增運算符重載
#include<iostream>
#include<string>using namespace std;class MyInteger
{// 全局函數(shù)做友元,可以訪問類中的私有屬性friend ostream & operator<<(ostream &out, MyInteger myint);
private:int m_a;
public:MyInteger(){m_a = 1;}// 重載前置++運算符,這里必須返回引用,即同一個對象MyInteger& operator++(){++m_a;return *this;}// 重載后置++運算符MyInteger operator++(int)			//int代表占位參數(shù){MyInteger temp = *this;			// 先保存當(dāng)前對象 *this 就表示當(dāng)前對象m_a++;							// 然后再讓對象中的m_a的值自增return temp;}
};// 使用全局函數(shù)重載<<運算符
ostream & operator<<(ostream &out, MyInteger myint)
{out << "myint.m_a = " << myint.m_a;return out;
}int main()
{MyInteger myint;cout << ++myint << endl;cout << myint << endl;cout << myint++ << endl;cout << myint << endl;
}
1.9.1.4. 賦值運算符重載
#include<iostream>
#include<string>using namespace std;class Person
{
public:int *m_age;
public:Person(int age){// new int 返回的是int類型的指針m_age = new int(age);}~Person(){if (m_age != nullptr){delete m_age;m_age = nullptr;}}// 重載賦值運算符Person& operator=(Person &p){if (m_age != nullptr) {delete m_age;					// 釋放m_age的內(nèi)存m_age = new int(*p.m_age);		// 重新拷貝一份,放在堆內(nèi)存,在拷貝之前需要將this指針指向的對象的m_age屬性的空間給釋放,防止野指針return *this;					// 為了能夠鏈?zhǔn)秸{(diào)用,需要返回對象的引用}}};int main()
{Person p1(10);Person p2(20);Person p3(30);// 默認(rèn)是淺拷貝,在析構(gòu)函數(shù)中清空內(nèi)存就會存在問題,因此需要手動改為深拷貝p3 = p2 = p1;cout << "p1的年齡為:" << *p1.m_age << endl;cout << "p2的年齡為:" << *p2.m_age << endl;cout << "p3的年齡為:" << *p3.m_age << endl;}
1.9.1.5. new&delete運算符重載

void* operator new(size_t size)

void operator delete(void* ptr)

#include<iostream>
#include<string>using namespace std;void* operator new(size_t size)			// 參數(shù)必須是size_t(unsigned long long),返回值必須是void*。是靜態(tài)成員函數(shù)
{cout << "調(diào)用了全局重載的new:" << size << "字節(jié)。\n";// 申請內(nèi)存void* ptr = malloc(size);cout << "申請到的內(nèi)存的地址是:" << ptr << endl;return ptr;
}void operator delete(void* ptr)			// 參數(shù)必須是void *,返回值必須是void。是靜態(tài)成員函數(shù)
{cout << "調(diào)用了全局重載的delete。\n";// 判斷是否是空指針if (ptr == 0) return;		// 對空指針delete是安全的。free(ptr);						// 釋放內(nèi)存。
}class CGirl       // 超女類CGirl。
{
public:int  m_bh;               // 編號。int  m_xw;               // 胸圍。CGirl(int bh, int xw) { m_bh = bh, m_xw = xw;  cout << "調(diào)用了構(gòu)造函數(shù)CGirl()\n"; }~CGirl() { cout << "調(diào)用了析構(gòu)函數(shù)~CGirl()\n"; }void* operator new(size_t size){cout << "調(diào)用了類的重載的new:" << size << "字節(jié)。\n";// 申請內(nèi)存void* ptr = malloc(size);cout << "申請到的內(nèi)存的地址是:" << ptr << endl;return ptr;}void operator delete(void* ptr)			// 參數(shù)必須是void *,返回值必須是void。{cout << "調(diào)用了類的重載的delete。\n";// 判斷是否是空指針if (ptr == 0) return;		// 對空指針delete是安全的。free(ptr);						// 釋放內(nèi)存。}
};int main()
{// 會調(diào)用全局重載函數(shù)newint* p1 = new int(3);// 會調(diào)用全局重載函數(shù)deletedelete p1;CGirl* p2 = new CGirl(3, 8);cout << "p2的地址是:" << p2 << "編號:" << p2->m_bh << ",胸圍:" << p2->m_xw << endl;delete p2;
}

1.10. 類的自動類型轉(zhuǎn)換

#include<iostream>
#include<string>using namespace std;class CGirl       // 超女類CGirl。
{
public:int         m_bh;           // 編號。string    m_name;     		// 姓名。double  m_weight;   		// 體重,單位:kg。// 默認(rèn)構(gòu)造函數(shù)。CGirl() { m_bh = 0;  m_name.clear();  m_weight = 0; cout << "調(diào)用了CGirl()\n"; }// 自我介紹的方法。void show() { cout << "bh=" << m_bh << ",name=" << m_name << ",weight=" << m_weight << endl; }// 關(guān)閉自動類型轉(zhuǎn)換,但是可以顯式轉(zhuǎn)換explicit CGirl(int bh) { m_bh = bh;  m_name.clear();  m_weight = 0; cout << "調(diào)用了CGirl(int bh)\n"; }//CGirl(double weight) { m_bh = 0;  m_name.clear();  m_weight = weight; cout << "調(diào)用了CGirl(double weight)\n"; }
};
int main()
{//CGirl g1(8);              // 常規(guī)的寫法。//CGirl g1 = CGirl(8);   	// 顯式轉(zhuǎn)換。//CGirl g1 = 8;             // 隱式轉(zhuǎn)換。CGirl g1;                   // 創(chuàng)建對象。g1 = (CGirl)8;              // 隱式轉(zhuǎn)換,用CGirl(8)創(chuàng)建臨時對象,再賦值給g。//CGirl g1 = 8.7;           // 隱式轉(zhuǎn)換。//g1.show();
}

1.11. 繼承

1.11.1. 繼承的基本語法

class 派生類名:[繼承方式]基類名

1.11.2. 繼承方式

  • public
  • protected
  • private

默認(rèn)是private。不管繼承方式如何,基類中的private成員在派生類中始終不能使用。

1.11.3. 繼承中構(gòu)造和析構(gòu)順序

  • 創(chuàng)建派生類對象時,先調(diào)用基類的構(gòu)造函數(shù),再調(diào)用派生類的構(gòu)造函數(shù);
  • 銷毀派生類對象時,先調(diào)用派生類的析構(gòu)函數(shù),再調(diào)用基類的析構(gòu)函數(shù)。如果手工調(diào)用派生類的析構(gòu)函數(shù),也會調(diào)用基類的析構(gòu)函數(shù)
#include <iostream>         // 包含頭文件。
using namespace std;        // 指定缺省的命名空間。class A {        // 基類
public:int m_a;
private:int m_b;
public:A() : m_a(0), m_b(0)                     			// 基類的默認(rèn)構(gòu)造函數(shù)。{cout << "調(diào)用了基類的默認(rèn)構(gòu)造函數(shù)A()。\n";}A(int a, int b) : m_a(a), m_b(b)     				// 基類有兩個參數(shù)的構(gòu)造函數(shù)。{cout << "調(diào)用了基類的構(gòu)造函數(shù)A(int a,int b)。\n";}A(const A &a) : m_a(a.m_a + 1), m_b(a.m_b + 1)   	// 基類的拷貝構(gòu)造函數(shù)。{cout << "調(diào)用了基類的拷貝構(gòu)造函數(shù)A(const A &a)。\n";}// 顯示基類A全部的成員。void showA() { cout << "m_a=" << m_a << ",m_b=" << m_b << endl; }
};class B :public A        			// 派生類
{
public:int m_c;B() : m_c(0), A()             	// 派生類的默認(rèn)構(gòu)造函數(shù),指明用基類的默認(rèn)構(gòu)造函數(shù)(不指明也無所謂)。順序也無所謂{cout << "調(diào)用了派生類的默認(rèn)構(gòu)造函數(shù)B()。\n";}B(int a, int b, int c) : A(a, b), m_c(c)           	// 指明用基類的有兩個參數(shù)的構(gòu)造函數(shù)。{cout << "調(diào)用了派生類的構(gòu)造函數(shù)B(int a,int b,int c)。\n";}B(const A& a, int c) :A(a), m_c(c)              	// 指明用基類的拷貝構(gòu)造函數(shù)。{cout << "調(diào)用了派生類的構(gòu)造函數(shù)B(const A &a,int c) 。\n";}// 顯示派生類B全部的成員。void showB() { cout << "m_c=" << m_c << endl << endl; }
};int main()
{B b1;                 	// 將調(diào)用基類默認(rèn)的構(gòu)造函數(shù)。b1.showA();     b1.showB();B b2(1, 2, 3);      	// 將調(diào)用基類有兩個參數(shù)的構(gòu)造函數(shù)。b2.showA();     b2.showB();A a(10, 20);      		// 創(chuàng)建基類對象。B b3(a, 30);      		// 將調(diào)用基類的拷貝造函數(shù)。b3.showA();     b3.showB();
}

注意事項:

  • 如果沒有指定基類構(gòu)造函數(shù),將使用基類的默認(rèn)構(gòu)造函數(shù)。如果基類沒有默認(rèn)構(gòu)造函數(shù),將報錯;
  • 可以用初始化列表指明要使用的基類構(gòu)造函數(shù);
  • 基類構(gòu)造函數(shù)負(fù)責(zé)初始化被繼承的數(shù)據(jù)成員,派生類構(gòu)造函數(shù)主要用于初始化新增的數(shù)據(jù)成員;
  • 派生類的構(gòu)造函數(shù)總是調(diào)用一個基類構(gòu)造函數(shù),包括拷貝構(gòu)造函數(shù);

1.11.4. 繼承中同名成員處理方式

  • 子類對象可以直接訪問到子類中同名成員;
  • 子類對象加作用域可以訪問到父類同名成員;
  • 當(dāng)子類與父類擁有同名的成員函數(shù),子類會隱藏父類中同名成員函數(shù),加作用域可以訪問到父類中同名函數(shù)。

1.11.5. 繼承同名靜態(tài)成員處理方式

  • 訪問子類同名成員,直接訪問即可;
  • 訪問父類同名成員,需要加作用域。

1.11.6. 多繼承語法

class 子類: 繼承方式 父類1, 繼承方式 父類2 ...

c++實際開發(fā)中不建議用多繼承

1.12. 多態(tài)

1.12.1. 背景

通過基類只能訪問派生類的成員變量,但是不能訪問派生類的成員函數(shù)。

1.12.2. 虛函數(shù)

為了消除這種尷尬,讓基類指針能夠訪問派生類的成員函數(shù),C++ 增加了虛函數(shù)(Virtual Function)。使用虛函數(shù)非常簡單,只需要在函數(shù)聲明前面增加 virtual 關(guān)鍵字。

注意事項:

  • 只需要在虛函數(shù)的聲明處加上 virtual 關(guān)鍵字,函數(shù)定義處可以加也可以不加;
  • 為了方便,你可以只將基類中的函數(shù)聲明為虛函數(shù),這樣所有派生類中具有遮蔽關(guān)系的同名函數(shù)都將自動成為虛函數(shù);
  • 當(dāng)在基類中定義了虛函數(shù)時,如果派生類沒有定義新的函數(shù)來遮蔽此函數(shù),那么將使用基類的虛函數(shù)。因為通過基類的指針只能訪問從基類繼承過去的成員,不能訪問派生類新增的成員;
  • 構(gòu)造函數(shù)不能是虛函數(shù);
  • 析構(gòu)函數(shù)可以聲明為虛函數(shù),而且有時候必須要聲明為虛函數(shù)。
#include <iostream>
using namespace std;//軍隊
class Troops {
public:// 基類設(shè)置為虛函數(shù)virtual void fight() { cout << "Strike back!" << endl; }
};//陸軍
class Army : public Troops {
public:void fight() { cout << "--Army is fighting!" << endl; }
};
//99A主戰(zhàn)坦克
class _99A : public Army {
public:void fight() { cout << "----99A(Tank) is fighting!" << endl; }
};
//武直10武裝直升機(jī)
class WZ_10 : public Army {
public:void fight() { cout << "----WZ-10(Helicopter) is fighting!" << endl; }
};
//長劍10巡航導(dǎo)彈
class CJ_10 : public Army {
public:void fight() { cout << "----CJ-10(Missile) is fighting!" << endl; }
};//空軍
class AirForce : public Troops {
public:void fight() { cout << "--AirForce is fighting!" << endl; }
};
//J-20隱形殲擊機(jī)
class J_20 : public AirForce {
public:void fight() { cout << "----J-20(Fighter Plane) is fighting!" << endl; }
};
//CH5無人機(jī)
class CH_5 : public AirForce {
public:void fight() { cout << "----CH-5(UAV) is fighting!" << endl; }
};
//轟6K轟炸機(jī)
class H_6K : public AirForce {
public:void fight() { cout << "----H-6K(Bomber) is fighting!" << endl; }
};int main() {// 基類指針Troops* p = new Troops;p->fight();//陸軍p = new Army;p->fight();p = new _99A;p->fight();p = new WZ_10;p->fight();p = new CJ_10;p->fight();//空軍p = new AirForce;p->fight();p = new J_20;p->fight();p = new CH_5;p->fight();p = new H_6K;p->fight();return 0;
}

1.12.3. 純虛函數(shù)

語法格式:virtual 返回值類型 函數(shù)名 (函數(shù)參數(shù)) = 0;

純虛函數(shù)沒有函數(shù)體,只有函數(shù)聲明,在虛函數(shù)聲明的結(jié)尾加上=0,表明此函數(shù)為純虛函數(shù)。

包含純虛函數(shù)的類稱為抽象類(Abstract Class)。之所以說它抽象,是因為它無法實例化,也就是無法創(chuàng)建對象。原因很明顯,純虛函數(shù)沒有函數(shù)體,不是完整的函數(shù),無法調(diào)用,也無法為其分配內(nèi)存空間。

抽象類通常是作為基類,讓派生類去實現(xiàn)純虛函數(shù)。派生類必須實現(xiàn)純虛函數(shù)才能被實例化。

#include <iostream>
using namespace std;//線
class Line {
public:Line(float len);virtual float area() = 0;virtual float volume() = 0;
protected:float m_len;
};
//類外定義構(gòu)造函數(shù)
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; }//正方體
class Cube : public Cuboid {
public:Cube(float len);float area();float volume();
};
Cube::Cube(float len) : Cuboid(len, len, len) { }
float Cube::area() { return 6 * m_len * m_len; }
float Cube::volume() { return m_len * m_len * m_len; }int main() {//Line* p0 = new Rec(10, 20); // errorLine* p = new Cuboid(10, 20, 30);cout << "The area of Cuboid is " << p->area() << endl;cout << "The volume of Cuboid is " << p->volume() << endl;p = new Cube(15);cout << "The area of Cube is " << p->area() << endl;cout << "The volume of Cube is " << p->volume() << endl;return 0;
}

運行結(jié)果:
The area of Cuboid is 2200
The volume of Cuboid is 6000
The area of Cube is 1350
The volume of Cube is 3375

本例中定義了四個類,它們的繼承關(guān)系為:Line --> Rec --> Cuboid --> Cube。

Line 是一個抽象類,也是最頂層的基類,在 Line 類中定義了兩個純虛函數(shù) area() 和 volume()。

在 Rec 類中,實現(xiàn)了 area() 函數(shù);所謂實現(xiàn),就是定義了純虛函數(shù)的函數(shù)體。但這時 Rec 仍不能被實例化,因為它沒有實現(xiàn)繼承來的 volume() 函數(shù),volume() 仍然是純虛函數(shù),所以 Rec 也仍然是抽象類。

直到 Cuboid 類,才實現(xiàn)了 volume() 函數(shù),才是一個完整的類,才可以被實例化。

可以發(fā)現(xiàn),Line 類表示“線”,沒有面積和體積,但它仍然定義了 area() 和 volume() 兩個純虛函數(shù)。這樣的用意很明顯:Line 類不需要被實例化,但是它為派生類提供了“約束條件”,派生類必須要實現(xiàn)這兩個函數(shù),完成計算面積和體積的功能,否則就不能實例化。

在實際開發(fā)中,你可以定義一個抽象基類,只完成部分功能,未完成的功能交給派生類去實現(xiàn)(誰派生誰實現(xiàn))。這部分未完成的功能,往往是基類不需要的,或者在基類中無法實現(xiàn)的。雖然抽象基類沒有完成,但是卻強(qiáng)制要求派生類完成,這就是抽象基類的“霸王條款”。

抽象基類除了約束派生類的功能,還可以實現(xiàn)多態(tài)。請注意第 51 行代碼,指針 p 的類型是 Line,但是它卻可以訪問派生類中的 area() 和 volume() 函數(shù),正是由于在 Line 類中將這兩個函數(shù)定義為純虛函數(shù);如果不這樣做,51 行后面的代碼都是錯誤的。我想,這或許才是C++提供純虛函數(shù)的主要目的。

關(guān)于純虛函數(shù)的幾點說明

1) 一個純虛函數(shù)就可以使類成為抽象基類,但是抽象基類中除了包含純虛函數(shù)外,還可以包含其它的成員函數(shù)(虛函數(shù)或普通函數(shù))和成員變量。
2) 只有類中的虛函數(shù)才能被聲明為純虛函數(shù),普通成員函數(shù)和頂層函數(shù)均不能聲明為純虛函數(shù)。如下例所示:

//頂層函數(shù)不能被聲明為純虛函數(shù)
void fun() = 0;   //compile error
class base{
public ://普通成員函數(shù)不能被聲明為純虛函數(shù)void display() = 0;  //compile error
};

1.12.4. 虛析構(gòu)函數(shù)

#include<iostream>
using namespace std;class ClxBase
{
public:ClxBase() {};virtual ~ClxBase() { cout << "delete ClxBase" << endl; };virtual void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
};class ClxDerived : public ClxBase
{
public:ClxDerived() {};~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };
};int main(int argc, char const* argv[])
{ClxBase* pTest = new ClxDerived;pTest->DoSomething();delete pTest;return 0;
}

打印結(jié)果如下:

Do something in class ClxDerived!

Output from the destructor of class ClxDerived!

delete ClxBase

如果基類ClxBase的析構(gòu)函數(shù)沒有定義成虛函數(shù),那么打印結(jié)果為:

Do something in class ClxDerived!

delete ClxBase

即不會調(diào)用派生類的析構(gòu)函數(shù),這樣會造成數(shù)據(jù)泄露的問題。

虛析構(gòu)函數(shù)的作用:

(1)如果父類的析構(gòu)函數(shù)不加virtual關(guān)鍵字
當(dāng)父類的析構(gòu)函數(shù)不聲明成虛析構(gòu)函數(shù)的時候,當(dāng)子類繼承父類,父類的指針指向子類時,delete掉父類的指針,只調(diào)用父類的析構(gòu)函數(shù),而不調(diào)用子類的析構(gòu)函數(shù)。
(2)如果父類的析構(gòu)函數(shù)加virtual關(guān)鍵字
當(dāng)父類的析構(gòu)函數(shù)聲明成虛析構(gòu)函數(shù)的時候,當(dāng)子類繼承父類,父類的指針指向子類時,delete掉父類的指針,先調(diào)用子類的析構(gòu)函數(shù),再調(diào)用父類的析構(gòu)函數(shù)。

1.12.5. 運行階段類型識別 dynamic_cast

語法格式:派生類指針 = dynamic_cast<派生類類型 *>(基類指針);

如果轉(zhuǎn)換成功,dynamic_cast返回對象的地址,如果失敗,返回nullptr。

#include <iostream>         // 包含頭文件。
using namespace std;        // 指定缺省的命名空間。class Hero                        // 英雄基類
{
public:int viability;      // 生存能力。int attack;         // 攻擊傷害。virtual void skill1() { cout << "英雄釋放了一技能。\n"; }virtual void skill2() { cout << "英雄釋放了二技能。\n"; }virtual void uskill() { cout << "英雄釋放了大絕招。\n"; }
};class XS :public Hero       // 西施派生類
{
public:void skill1() { cout << "西施釋放了一技能。\n"; }void skill2() { cout << "西施釋放了二技能。\n"; }void uskill() { cout << "西施釋放了大招。\n"; }void show() { cout << "我是天下第一美女。\n"; }
};class HX :public Hero       // 韓信派生類
{
public:void skill1() { cout << "韓信釋放了一技能。\n"; }void skill2() { cout << "韓信釋放了二技能。\n"; }void uskill() { cout << "韓信釋放了大招。\n"; }
};class LB :public Hero       // 李白派生類
{
public:void skill1() { cout << "李白釋放了一技能。\n"; }void skill2() { cout << "李白釋放了二技能。\n"; }void uskill() { cout << "李白釋放了大招。\n"; }
};int main()
{// 根據(jù)用戶選擇的英雄,施展一技能、二技能和大招。int id = 0;     // 英雄的id。cout << "請輸入英雄(1-西施;2-韓信;3-李白。):";cin >> id;// 創(chuàng)建基類指針,讓它指向派生類對象,用基類指針調(diào)用派生類的成員函數(shù)。Hero* ptr = nullptr;if (id == 1) {           // 1-西施ptr = new XS;}else if (id == 2) {      // 2-韓信ptr = new HX;}else if (id == 3) {      // 3-李白ptr = new LB;}if (ptr != nullptr) {ptr->skill1();ptr->skill2();ptr->uskill();// 如果基類指針指向的對象是西施,那么就調(diào)用西施的show()函數(shù)。//if (id == 1) {//	XS* pxs = (XS *)ptr;        // C風(fēng)格強(qiáng)制轉(zhuǎn)換的方法,程序員必須保證目標(biāo)類型正確。//	pxs->show();//}XS* xsptr = dynamic_cast<XS*>(ptr);				// 把基類指針轉(zhuǎn)換為派生類。if (xsptr != nullptr) xsptr->show();            // 如果轉(zhuǎn)換成功,調(diào)用派生類西施的非虛函數(shù)。delete ptr;}// 以下代碼演示把基類引用轉(zhuǎn)換為派生類引用時發(fā)生異常的情況。/*HX hx;Hero& rh = hx;try{XS & rxs= dynamic_cast<XS &>(rh);}catch (bad_cast) {cout << "出現(xiàn)了bad_cast異常。\n";}*/
}

注意:

1)dynamic_cast只適用于包含虛函數(shù)的類。

2)dynamic_cast可以將派生類指針轉(zhuǎn)換為基類指針,這種畫蛇添足的做法沒有意義。

3)dynamic_cast可以用于引用,但是,沒有與空指針對應(yīng)的引用值,如果轉(zhuǎn)換請求不正確,會出現(xiàn)bad_cast異常。

1.13. 函數(shù)模板

template <typename T>
void Swap(T &a, T &b)
{T tmp = a;a = b;b = tmp;
}

1.13.1. 函數(shù)模板的注意事項

  • 可以為類的成員函數(shù)創(chuàng)建模板,但不能是虛函數(shù)析構(gòu)函數(shù)。
#include<iostream>
#include<string>
using namespace std;class CGirl
{
public:template<typename T>CGirl(T a){cout << "a= " << a << endl;}template<typename T>void show(){cout << "show方法" << endl;}// 錯誤的//template<typename T>//virtual void show()//{//	cout << "show方法" << endl;//}//template<typename T>//~CGirl()//{//}
};int main()
{int a = 10;CGirl g = CGirl(a);g.show<int>();
}
  • 使用函數(shù)模板時,必須明確數(shù)據(jù)類型,確保實參與函數(shù)模板能匹配上。
#include<iostream>
#include<string>
using namespace std;template <typename T>
void Swap(T &a, T &b)	// 傳引用
{T tmp = a;a = b;b = tmp;
}int main()
{// 錯誤的,傳引用,必須是數(shù)據(jù)類型一致的,不能進(jìn)行隱式轉(zhuǎn)換//int a = 10;//char b = 30;//Swap(a, b);
}
#include<iostream>
#include<string>
using namespace std;template <typename T>
void Swap(T a, T b)		// 傳值
{T tmp = a;a = b;b = tmp;
}int main()
{// 正確的int a = 10;char b = 30;Swap<int>(a, b);	// 可以發(fā)生隱式轉(zhuǎn)換
}
#include<iostream>
#include<string>
using namespace std;// 函數(shù)模板中沒有用到模板參數(shù)
template <typename T>
void Swap()
{cout << "調(diào)用了Swap函數(shù)" << endl;
}int main()
{// 錯誤的//Swap();// 正確的,顯式的指定。Swap<int>();
}
  • 使用函數(shù)模板時,推導(dǎo)的數(shù)據(jù)類型必須適應(yīng)函數(shù)模板中的代碼。
#include<iostream>
#include<string>
using namespace std;template <typename T>
T Add(T a, T b)
{return a + b;
}class CGirl
{
};
int main()
{//錯誤的,CGirl對象沒有+運算//CGirl g1;//CGirl g2;//Add(g1 + g2);
}
  • 使用函數(shù)模板時,如果是自動類型推導(dǎo),不會發(fā)生隱式類型轉(zhuǎn)換,如果顯式指定了函數(shù)模板的數(shù)據(jù)類型,可以發(fā)生隱式類型轉(zhuǎn)換。
#include<iostream>
#include<string>
using namespace std;template <typename T>
void Swap(T a, T b)
{T tmp = a;a = b;b = tmp;
}int main()
{// 正確的int a = 10;char b = 30;Swap<int>(a, b);	// 顯式指定了int數(shù)據(jù)類型,可以發(fā)生從char到int的數(shù)據(jù)類型轉(zhuǎn)換
}

1.13.2. 函數(shù)模板具體化

#include <iostream>         // 包含頭文件。
using namespace std;        // 指定缺省的命名空間。class CGirl            		// 超女類。
{
public:int m_bh;              	// 編號。string m_name;   		// 姓名。int m_rank;          	// 排名。
};template <typename T>
void Swap(T& a, T& b);      // 交換兩個變量的值函數(shù)模板。template<>
void Swap<CGirl>(CGirl& g1, CGirl& g2);      	// 交換兩個超女對象的排名。這個函數(shù)是函數(shù)模板的具體化函數(shù)
// template<> 
// void Swap(CGirl& g1, CGirl& g2);      		// 交換兩個超女對象的排名。int main()
{int a = 10, b = 20;Swap(a, b);           	// 使用了函數(shù)模板。cout << "a=" << a << ",b=" << b << endl;CGirl g1, g2;g1.m_rank = 1; g2.m_rank = 2;Swap(g1, g2);     		// 使用了超女類的具體化函數(shù)。cout << "g1.m_rank=" << g1.m_rank << ",g2.m_rank=" << g2.m_rank << endl;
}

編譯器使用各種函數(shù)的規(guī)則:

  1. 具體化優(yōu)先于常規(guī)模板,普通函數(shù)優(yōu)先于具體化和常規(guī)模板。
  2. 如果希望使用函數(shù)模板,可以用空模板參數(shù)強(qiáng)制使用函數(shù)模板。
  3. 如果函數(shù)模板能產(chǎn)生更好的匹配,將優(yōu)先于普通函數(shù)。
#include <iostream>         // 包含頭文件。
#include<string>
using namespace std;        // 指定缺省的命名空間。void Swap(int a, int b)      // 普通函數(shù)。
{cout << "使用了普通函數(shù)。\n";
}template <typename T>
void Swap(T a, T b)          // 函數(shù)模板。
{cout << "使用了函數(shù)模板。\n";
}template <>
void Swap(int a, int b)     // 函數(shù)模板的具體化版本。
{cout << "使用了具體化的函數(shù)模板。\n";
}int main()
{Swap(1,2);			// 會調(diào)用普通函數(shù)Swap('c', 'd');		// 會調(diào)用函數(shù)模板,因為不用進(jìn)行隱式轉(zhuǎn)換Swap<>(1,2);		// 用空模板,會強(qiáng)制調(diào)用函數(shù)模板的具體化版本
}

1.13.3. 函數(shù)模板分文件編寫

記住下面兩點就可以了:

  • 函數(shù)模板只是函數(shù)的描述,沒有實體,創(chuàng)建函數(shù)模板的代碼放在頭文件中。
  • 函數(shù)模板的具體化有實體,編譯的原理和普通函數(shù)一樣,所以,聲明放在頭文件中,定義放在源文件中。

1.13.4. 函數(shù)模板高級

1.13.4.1. decltype關(guān)鍵字

語法:decltype(expression) var;

作用:用于分析表達(dá)式的數(shù)據(jù)類型

#include <iostream>         // 包含頭文件。
#include<string>
using namespace std;        // 指定缺省的命名空間。template <typename T>
auto Swap(T a, T b)          // 函數(shù)模板。
{decltype(a + b) temp = a + b;cout << "temp = " << temp << endl;return temp;
}int main()
{auto res = Swap('c', 'd');cout << res << endl;
}
#include <iostream>         // 包含頭文件。
#include<string>
using namespace std;        // 指定缺省的命名空間。int func()
{cout << "調(diào)用了func函數(shù)" << endl;return 3;
}int main()
{decltype(func()) f = func();		// 函數(shù)返回值的數(shù)據(jù)類型cout << f << endl;decltype(func) *f = func;			// 函數(shù)類型f();								// 調(diào)用func函數(shù)
}
1.13.4.2. typename 的用法
template <typename T>
struct MakeUniqueResult {using scalar = std::unique_ptr<T>;
};template <typename T, typename... Args>
typename MakeUniqueResult<T>::scalar make_unique(Args &&... args) {  // NOLINTreturn std::unique_ptr<T>(new T(std::forward<Args>(args)...));  // NOLINT(build/c++11)
}

上面代碼中,編譯器無法自動區(qū)分 MakeUniqueResult<T>::scalar 是一個類型還是一個成員變量。為了明確告訴編譯器 scalar 是一個類型,我們使用 typename 關(guān)鍵字。沒有 typename,編譯器會產(chǎn)生錯誤,因為它不能確定 scalar 的含義。

1.14. 類模板

1.14.1. 語法

template <class T>
class 類模板名
{類的定義;
};

1.14.2. 注意事項

  • 在創(chuàng)建對象的時候,必須指明具體的數(shù)據(jù)類型。
#include <iostream>         // 包含頭文件。
using namespace std;        // 指定缺省的命名空間。template <class T1, class T2>
class AA
{
public:T1 m_a;      // 通用類型用于成員變量。T2 m_b;      // 通用類型用于成員變量。AA() {  }        // 默認(rèn)構(gòu)造函數(shù)是空的。// 通用類型用于成員函數(shù)的參數(shù)。AA(T1 a, T2 b) :m_a(a), m_b(b) {  }// 通用類型用于成員函數(shù)的返回值。T1 geta()            // 獲取成員m_a的值。{T1 a = 2;        // 通用類型用于成員函數(shù)的代碼中。return m_a + a;}T2 getb();            // 獲取成員m_b的值。
};// 模板類的成員函數(shù)可以在類外實現(xiàn)。
template<class T1, class T2>
T2 AA<T1, T2>::getb()
{return m_b;
}
int main()
{AA<int, string>* a;		// 在創(chuàng)建對象的時候,必須指明具體的數(shù)據(jù)類型。AA a 是錯誤的。
}
  • 使用類模板時,數(shù)據(jù)類型必須適應(yīng)類模板中的代碼。
  • 類模板可以為通用數(shù)據(jù)類型指定缺省的數(shù)據(jù)類型。
#include <iostream>         // 包含頭文件。
using namespace std;        // 指定缺省的命名空間。template <class T1, class T2=string>		// 指定通用數(shù)據(jù)類型的數(shù)據(jù)類型
class AA
{
}
  • 模板類的成員函數(shù)可以在類外實現(xiàn)。
#include <iostream>         // 包含頭文件。
using namespace std;        // 指定缺省的命名空間。template <class T1, class T2>
class AA
{
public:T1 m_a;      // 通用類型用于成員變量。T2 m_b;      // 通用類型用于成員變量。AA() {  }        // 默認(rèn)構(gòu)造函數(shù)是空的。// 通用類型用于成員函數(shù)的參數(shù)。AA(T1 a, T2 b) :m_a(a), m_b(b) {  }T2 getb();            // 獲取成員m_b的值。
};// 模板類的成員函數(shù)可以在類外實現(xiàn)。
template<class T1, class T2>
T2 AA<T1, T2>::getb()
{return m_b;
}
  • 可以用new創(chuàng)建模板類對象。
#include <iostream>         // 包含頭文件。
using namespace std;        // 指定缺省的命名空間。template <class T1, class T2=string>
class AA
{
public:T1 m_a;      // 通用類型用于成員變量。T2 m_b;      // 通用類型用于成員變量。AA() {  }        // 默認(rèn)構(gòu)造函數(shù)是空的。// 通用類型用于成員函數(shù)的參數(shù)。AA(T1 a, T2 b) :m_a(a), m_b(b) {  }// 通用類型用于成員函數(shù)的返回值。T1 geta()            // 獲取成員m_a的值。{T1 a = 2;        // 通用類型用于成員函數(shù)的代碼中。return m_a + a;}
};int main()
{AA<int, string> *b = new AA<int, string>();
}
  • 在程序中,模板類的成員函數(shù)使用了才會創(chuàng)建。
#include <iostream>         // 包含頭文件。
using namespace std;        // 指定缺省的命名空間。template <class T1, class T2=string>
class AA
{
public:T1 m_a;      // 通用類型用于成員變量。T2 m_b;      // 通用類型用于成員變量。AA() { }        // 默認(rèn)構(gòu)造函數(shù)是空的。// 該成員函數(shù)并不會被調(diào)用,因此也不會報錯T1 gethaha(){return m_a.hahaha();}// 通用類型用于成員函數(shù)的參數(shù)。AA(T1 a, T2 b) :m_a(a), m_b(b) {  }
};int main()
{AA<int, string>* a;	//在程序中,模板類的成員函數(shù)使用了才會創(chuàng)建。
}

1.14.3. 類模板的具體化(重點)

  • 可以部分具體化,也可以完全具體化;
  • 具體化程度高的類優(yōu)先于具體化程度低的類,具體化的類優(yōu)先于沒有具體化的類。
#include <iostream>         // 包含頭文件。
using namespace std;        // 指定缺省的命名空間。/
// 類模板
template<class T1, class T2>
class AA {                 // 類模板。
public:T1 m_x;T2 m_y;AA(const T1 x, const T2 y) :m_x(x), m_y(y) { cout << "類模板:構(gòu)造函數(shù)。\n"; }void show() const;
};template<class T1, class T2>
void AA<T1, T2>::show() const {    // 成員函數(shù)類外實現(xiàn)。cout << "類模板:x = " << m_x << ", y = " << m_y << endl;
}
/
// 類模板完全具體化
template<>
class AA<int, string> {
public:int m_x;string m_y;AA(const int x, const string y) :m_x(x), m_y(y) { cout << "完全具體化:構(gòu)造函數(shù)。\n"; }void show() const;
};void AA<int, string>::show() const {    // 成員函數(shù)類外實現(xiàn)。cout << "完全具體化:x = " << m_x << ", y = " << m_y << endl;
}
/
// 類模板部分具體化
template<class T1>
class AA<T1, string> {
public:T1 m_x;string m_y;AA(const T1 x, const string y) :m_x(x), m_y(y) { cout << "部分具體化:構(gòu)造函數(shù)。\n"; }void show() const;
};template<class T1>
void AA<T1, string>::show() const {    // 成員函數(shù)類外實現(xiàn)。cout << "部分具體化:x = " << m_x << ", y = " << m_y << endl;
}
/int main()
{// 具體化程度高的類優(yōu)先于具體化程度低的類,具體化的類優(yōu)先于沒有具體化的類。AA<int, string> aa1(8, "我是一只傻傻鳥。");   		// 將使用完全具體化的類。AA<char, string> aa2(8, "我是一只傻傻鳥。");   		// 將使用部分具體化的類。AA<int, double> aa3(8, 999999);                     // 將使用模板類。
}

1.14.4. 類模板與繼承

模板類繼承普通類

#include<string>
#include<iostream>using namespace std;// 普通類
class AA
{
public:int m_a;AA(int a) :m_a(a) { cout << "調(diào)用了AA的構(gòu)造函數(shù)。\n"; }void func1() { cout << "調(diào)用了func1()函數(shù):m_a=" << m_a << endl;; }
};// 模板類
template<class T1, class T2>
class BB:public AA
{
public:T1 m_x;T2 m_y;BB(const T1 x, const T2 y, int a) :AA(a), m_x(x), m_y(y){cout << "調(diào)用了BB的構(gòu)造函數(shù)。\n";}// 常函數(shù)void func2() const{cout << "調(diào)用了func2()函數(shù):x = " << m_x << ", y = " << m_y << endl;}
};int main()
{BB<int, string> bb(8, "我是一只傻傻鳥", 3);bb.func2();}

普通類繼承模板類的實例化版本

#include<string>
#include<iostream>using namespace std;// 模板類
template<class T1, class T2>
class AA 
{
public:T1 m_x;T2 m_y;// 構(gòu)造函數(shù)AA(const T1 x, const T2 y) : m_x(x), m_y(y) { cout << "調(diào)用了AA的構(gòu)造函數(shù)。\n"; }// 常函數(shù)void func1() const{cout << "調(diào)用了func1()函數(shù):x = " << m_x << ", y = " << m_y << endl;}
};// 普通類
class BB:public AA<int,string>
{
public:int m_a;BB(int a, int x, string y) : AA(x, y), m_a(a) { cout << "調(diào)用了BB的構(gòu)造函數(shù)。\n"; }void func2() { cout << "調(diào)用了func2()函數(shù):m_a = " << m_a << endl;; }
};int main()
{BB bb(3, 8, "我是一只傻傻鳥。");bb.func1();bb.func2();
}
// 28行代碼  AA(x, y) 

普通類繼承模板類

#include<string>
#include<iostream>using namespace std;// 模板類
template<class T1, class T2>
class AA
{
public:T1 m_x;T2 m_y;// 構(gòu)造函數(shù)AA(const T1 x, const T2 y) : m_x(x), m_y(y) { cout << "調(diào)用了AA的構(gòu)造函數(shù)。\n"; }// 常函數(shù)void func1() const{cout << "調(diào)用了func1()函數(shù):x = " << m_x << ", y = " << m_y << endl;}
};// 普通類
template<class T1, class T2>
class BB :public AA<T1, T2>
{
public:int m_a;BB(int a, const T1 x, const T2 y) : AA<T1, T2>(x, y), m_a(a) { cout << "調(diào)用了BB的構(gòu)造函數(shù)。\n"; }void func2() { cout << "調(diào)用了func2()函數(shù):m_a = " << m_a << endl;; }
};int main()
{BB<int,string> bb(3, 8, "我是一只傻傻鳥。");bb.func1();bb.func2();
}// 關(guān)鍵代碼在29行, AA<T1, T2>(x, y)需要指定泛型

模板類繼承模板類

#include<string>
#include<iostream>using namespace std;template<class T1, class T2>
class BB
{
public:T1 m_x;T2 m_y;BB(const T1 x, const T2 y) : m_x(x), m_y(y) { cout << "調(diào)用了BB的構(gòu)造函數(shù)。\n"; }void func2() const { cout << "調(diào)用了func2()函數(shù):x = " << m_x << ", y = " << m_y << endl; }
};template<class T, class T1, class T2>
class CC :public BB<T1, T2>   // 模板類繼承模板類。
{
public:T m_a;CC(const T a, const T1 x, const T2 y) : BB<T1, T2>(x, y), m_a(a) { cout << "調(diào)用了CC的構(gòu)造函數(shù)。\n"; }void func3() { cout << "調(diào)用了func3()函數(shù):m_a = " << m_a << endl;; }
};int main()
{CC<int, int, string> cc(3, 8, "我是一只傻傻鳥。");cc.func3();cc.func2();
}

模板類繼承模板參數(shù)給出的基類

#include <iostream>         // 包含頭文件。
using namespace std;        // 指定缺省的命名空間。class AA {
public:AA() { cout << "調(diào)用了AA的構(gòu)造函數(shù)AA()。\n"; }AA(int a) { cout << "調(diào)用了AA的構(gòu)造函數(shù)AA(int a)。\n"; }
};class BB {
public:BB() { cout << "調(diào)用了BB的構(gòu)造函數(shù)BB()。\n"; }BB(int a) { cout << "調(diào)用了BB的構(gòu)造函數(shù)BB(int a)。\n"; }
};class CC {
public:CC() { cout << "調(diào)用了CC的構(gòu)造函數(shù)CC()。\n"; }CC(int a) { cout << "調(diào)用了CC的構(gòu)造函數(shù)CC(int a)。\n"; }
};template<class T>
class DD {
public:DD() { cout << "調(diào)用了DD的構(gòu)造函數(shù)DD()。\n"; }DD(int a) { cout << "調(diào)用了DD的構(gòu)造函數(shù)DD(int a)。\n"; }
};template<class T>
class EE : public T {          // 模板類繼承模板參數(shù)給出的基類。
public:EE() :T() { cout << "調(diào)用了EE的構(gòu)造函數(shù)EE()。\n"; }EE(int a) :T(a) { cout << "調(diào)用了EE的構(gòu)造函數(shù)EE(int a)。\n"; }
};int main()
{EE<AA> ea1;                 // AA作為基類。EE<BB> eb1;                 // BB作為基類。EE<CC> ec1;                 // CC作為基類。EE<DD<int>> ed1;            // EE<int>作為基類。// EE<DD> ed1;                // DD作為基類,錯誤。
}

1.14.5. 類模板與函數(shù)

#include <iostream>         // 包含頭文件。
using namespace std;        // 指定缺省的命名空間。template<class T1, class T2>
class AA    // 模板類AA。
{
public:T1 m_x;T2 m_y;AA(const T1 x, const T2 y) : m_x(x), m_y(y) { }void show() const { cout << "show()  x = " << m_x << ", y = " << m_y << endl; }
};// 采用普通函數(shù),參數(shù)和返回值是模板類AA的實例化版本。
AA<int, string> func(AA<int, string>& aa)
{aa.show();cout << "調(diào)用了func(AA<int, string> &aa)函數(shù)。\n";return aa;
}// 函數(shù)模板,參數(shù)和返回值是的模板類AA。
template <typename T1, typename T2>
AA<T1, T2> func(AA<T1, T2>& aa)
{aa.show();cout << "調(diào)用了func(AA<T1, T2> &aa)函數(shù)。\n";return aa;
}// 函數(shù)模板,參數(shù)和返回值是任意類型。
template <typename T>
T func(T& aa)
{aa.show();cout << "調(diào)用了func(AA<T> &aa)函數(shù)。\n";return aa;
}int main()
{AA<int, string> aa(3, "我是一只傻傻鳥。");func(aa);
}

1.14.6. 類模板與友元

非模板友元

#include <iostream>         // 包含頭文件。
using namespace std;        // 指定缺省的命名空間。template<class T1, class T2>
class AA
{T1 m_x;T2 m_y;
public:AA(const T1 x, const T2 y) : m_x(x), m_y(y) { }// 非模板友元:友元函數(shù)不是模板函數(shù),而是利用模板類參數(shù)生成的函數(shù),只能在類內(nèi)實現(xiàn)。friend void show(const AA<T1, T2>& a){cout << "x = " << a.m_x << ", y = " << a.m_y << endl;}/* friend void show(const AA<int, string>& a);friend void show(const AA<char, string>& a);*/
};//void show(const AA<int, string>& a)
//{
//    cout << "x = " << a.m_x << ", y = " << a.m_y << endl;
//}
//
//void show(const AA<char, string>& a)
//{
//    cout << "x = " << a.m_x << ", y = " << a.m_y << endl;
//}int main()
{AA<int, string> a(88, "我是一只傻傻鳥。");show(a);AA<char, string> b(88, "我是一只傻傻鳥。");show(b);
}

約束模板友元

#include <iostream>         // 包含頭文件。
using namespace std;        // 指定缺省的命名空間。// 約束模板友元:模板類實例化時,每個實例化的類對應(yīng)一個友元函數(shù)。
template <typename T>
void show(T& a);                                // 第一步:在模板類的定義前面,聲明友元函數(shù)模板。template<class T1, class T2>
class AA    // 模板類AA。
{friend void show<>(AA<T1, T2>& a);          // 第二步:在模板類中,再次聲明友元函數(shù)模板。T1 m_x;T2 m_y;public:AA(const T1 x, const T2 y) : m_x(x), m_y(y) { }
};template<class T1, class T2>
class BB    // 模板類BB。
{friend void show<>(BB<T1, T2>& a);          // 第二步:在模板類中,再次聲明友元函數(shù)模板。T1 m_x;T2 m_y;public:BB(const T1 x, const T2 y) : m_x(x), m_y(y) { }
};template <typename T>                          // 第三步:友元函數(shù)模板的定義。
void show(T& a)
{cout << "通用:x = " << a.m_x << ", y = " << a.m_y << endl;
}template <>                                    // 第三步:具體化版本。
void show(AA<int, string>& a)
{cout << "具體AA<int, string>:x = " << a.m_x << ", y = " << a.m_y << endl;
}template <>                                    // 第三步:具體化版本。
void show(BB<int, string>& a)
{cout << "具體BB<int, string>:x = " << a.m_x << ", y = " << a.m_y << endl;
}int main()
{AA<int, string> a1(88, "我是一只傻傻鳥。");show(a1);         // 將使用具體化的版本。AA<char, string> a2(88, "我是一只傻傻鳥。");show(a2);        // 將使用通用的版本。BB<int, string> b1(88, "我是一只傻傻鳥。");show(b1);         // 將使用具體化的版本。BB<char, string> b2(88, "我是一只傻傻鳥。");show(b2);        // 將使用通用的版本。
}

非約束模板友元

#include <iostream>         // 包含頭文件。
using namespace std;        // 指定缺省的命名空間。// 非類模板約束的友元函數(shù),實例化后,每個函數(shù)都是每個每個類的友元。
template<class T1, class T2>
class AA
{template <typename T> friend void show(T& a);       // 把函數(shù)模板設(shè)置為友元。T1 m_x;T2 m_y;
public:AA(const T1 x, const T2 y) : m_x(x), m_y(y) { }
};template <typename T> void show(T& a)                    // 通用的函數(shù)模板。
{cout << "通用:x = " << a.m_x << ", y = " << a.m_y << endl;
}template <>void show(AA<int, string>& a)                 // 函數(shù)模板的具體版本。
{cout << "具體<int, string>:x = " << a.m_x << ", y = " << a.m_y << endl;
}int main()
{AA<int, string> a(88, "我是一只傻傻鳥。");show(a);         // 將使用具體化的版本。AA<char, string> b(88, "我是一只傻傻鳥。");show(b);        // 將使用通用的版本。
}

1.14.7. 成員類模板

#include<iostream>using namespace std;template<class T1, class T2>
class AA
{
public:T1 m_x;T2 m_y;AA(const T1 x, const T2 y):m_x(x), m_y(y){cout << "調(diào)用AA的構(gòu)造函數(shù)\n";}void show(){cout << "m_x=" << m_x << ",m_y=" << m_y << endl;}template<class T>class BB{public:T m_a;T1 m_b;BB() {};void show();};BB<string> m_bb;template<typename T>void show(T tt);
};// 順序不能寫反
template<class T1, class T2>
template<class T>
void AA<T1, T2>::BB<T>::show()
{cout << "m_a=" << m_a << ",m_b=" << m_b << endl;
}// 順序不能寫反
template<class T1, class T2>
template<typename T>
void AA<T1, T2>::show(T t)
{cout << "tt=" << t << endl;cout << "m_x=" << m_x << ",m_y=" << m_y << endl;m_bb.show();
}
int main()
{AA<int, string> a(88, "我是一只傻傻鳥。");a.show();a.m_bb.m_a = "我有一只小小鳥。";a.m_bb.show();a.show("你是一只什么鳥?");
}

1.14.8. 類模板做參數(shù)

#include<iostream>using namespace std;template<class T, int len>
class LinkedList
{
public:T* m_head;			// 鏈表頭節(jié)點int m_len = len;	// 鏈表長度void insert() { cout << "向鏈表中插入了一條記錄。\n"; }void m_delete() { cout << "向鏈表中刪除了一條記錄。\n"; }void update() { cout << "向鏈表中更新了一條記錄。\n"; }
};template <class T, int len>
class Array             // 數(shù)組類模板
{
public:T* m_data;          // 數(shù)組指針int  m_len = len;   // 數(shù)組長度void insert() { cout << "向數(shù)組中插入了一條記錄。\n"; }void m_delete() { cout << "向數(shù)組中刪除了一條記錄。\n"; }void update() { cout << "向數(shù)組中更新了一條記錄。\n"; }
};//核心代碼 template<class , int> class 表示模板類參數(shù)
template<template<class , int> class table_type, class data_type, int len>
class LinearList
{
public:table_type<data_type, len> m_table;void insert() { m_table.insert(); }         // 線性表插入操作。void m_delete() { m_table.m_delete(); }			// 線性表刪除操作。void update() { m_table.update(); }			// 線性表更新操作。void oper()     // 按業(yè)務(wù)要求操作線性表。{cout << "len=" << m_table.m_len << endl;m_table.insert();m_table.update();}
};
int main()
{// 創(chuàng)建線性表對象,容器類型為鏈表,鏈表的數(shù)據(jù)類型為int,表長為20。LinearList<LinkedList, int, 20>  a;a.insert();a.m_delete();a.update();// 創(chuàng)建線性表對象,容器類型為數(shù)組,數(shù)組的數(shù)據(jù)類型為string,表長為20。LinearList<Array, string, 20>  b;b.insert();b.m_delete();b.update();}

2. 強(qiáng)制轉(zhuǎn)換

2.1. static_cast

用于內(nèi)置數(shù)據(jù)類型之間的轉(zhuǎn)換

#include <iostream>
using namespace std;int main(int argc, char* argv[])
{int ii = 3;long ll = ii;                     	// 絕對安全,可以隱式轉(zhuǎn)換,不會出現(xiàn)警告。double dd = 1.23;long ll1 = dd;                  	// 可以隱式轉(zhuǎn)換,但是,會出現(xiàn)可能丟失數(shù)據(jù)的警告。long ll2 = (long)dd;              	// C風(fēng)格:顯式轉(zhuǎn)換,不會出現(xiàn)警告。long ll3 = static_cast<long>(dd);   // C++風(fēng)格:顯式轉(zhuǎn)換,不會出現(xiàn)警告。cout << "ll1=" << ll1 << ",ll2=" << ll2 << ",ll3=" << ll3 << endl;
}

用于指針之間的轉(zhuǎn)換

#include <iostream>
using namespace std;void func(void* ptr) {   // 其它類型指針 -> void *指針 -> 其它類型指針double* pp = static_cast<double*>(ptr);
}int main(int argc, char* argv[])
{int ii = 10;//double* pd1 = &ii;                      	// 錯誤,不能隱式轉(zhuǎn)換。double* pd2 = (double*) &ii;      			// C風(fēng)格,強(qiáng)制轉(zhuǎn)換。//double* pd3 = static_cast<double*>(&ii);  // 錯誤,static_cast不支持不同類型指針的轉(zhuǎn)換。void* pv = &ii;                             // 任何類型的指針都可以隱式轉(zhuǎn)換成void*。double* pd4 = static_cast<double*>(pv);  	// static_cast可以把void *轉(zhuǎn)換成其它類型的指針。func(&ii);
}

2.2. const_cast

2.3. reinterpret_cast

類似c風(fēng)格的強(qiáng)制轉(zhuǎn)換

int* p = nullptr;
double* b = reinterpret_cast<double*>(p);	//正確,但是有風(fēng)險

2.4. dynamic_cast

主要用在繼承結(jié)構(gòu)中,可以支持RTTI類型識別的上下轉(zhuǎn)換

http://www.risenshineclean.com/news/8663.html

相關(guān)文章:

  • 泉州企業(yè)建站系統(tǒng)南寧排名seo公司
  • 求邯鄲網(wǎng)站制作搜索引擎排名優(yōu)化方案
  • pc網(wǎng)站原型設(shè)計工具河南平價的seo整站優(yōu)化定制
  • 別墅室內(nèi)設(shè)計網(wǎng)站寧波關(guān)鍵詞優(yōu)化企業(yè)網(wǎng)站建設(shè)
  • 網(wǎng)站開發(fā)中網(wǎng)頁之間的鏈接形式有模板建站流程
  • 哪個協(xié)會要做網(wǎng)站建設(shè)啊優(yōu)化網(wǎng)站快速排名軟件
  • 鄭州市哪里有網(wǎng)站建設(shè)河南網(wǎng)站排名優(yōu)化
  • 手表網(wǎng)站起名搜索引擎優(yōu)化指南
  • 在中國做外國網(wǎng)站怎么收錢網(wǎng)站查詢
  • pc網(wǎng)站怎么做旅游最新資訊
  • 網(wǎng)頁版游戲大全在線玩網(wǎng)絡(luò)優(yōu)化有前途嗎
  • 如何苗木網(wǎng)站建設(shè)全網(wǎng)推廣怎么做
  • 打開百度一下簡述搜索引擎優(yōu)化的方法
  • 嘉興秀洲區(qū)建設(shè)局網(wǎng)站推廣方式和推廣渠道
  • 深圳網(wǎng)站建設(shè) 工作室如何做宣傳推廣效果最好
  • 做推廣哪個網(wǎng)站最熱門百度推廣電話客服
  • 網(wǎng)站建設(shè) 化工大連seo網(wǎng)站推廣
  • 移動端電商網(wǎng)站百度網(wǎng)盤客服24小時電話人工服務(wù)
  • 給境外合法網(wǎng)站做數(shù)據(jù)優(yōu)化問題
  • 網(wǎng)站ui設(shè)計例子軟文廣告的案例
  • 專注番禺網(wǎng)站優(yōu)化網(wǎng)頁搜索快捷鍵是什么
  • 印度網(wǎng)站開發(fā)成本廣州頂正餐飲培訓(xùn)學(xué)校
  • 做360網(wǎng)站優(yōu)化快速排產(chǎn)品推廣方案ppt模板
  • 江蘇網(wǎng)站建設(shè)推廣高平網(wǎng)站優(yōu)化公司
  • 瑜伽網(wǎng)站設(shè)計推廣軟文范文
  • 如何添加網(wǎng)站 ico圖標(biāo)全球熱搜榜排名今日
  • 做寫字樓的網(wǎng)站有哪些河南seo網(wǎng)站多少錢
  • 昆明網(wǎng)站建設(shè)是什么意思福州seo技巧培訓(xùn)
  • html5 做手機(jī)網(wǎng)站seo公司怎么樣
  • 做分銷網(wǎng)站系統(tǒng)下載網(wǎng)站建立具體步驟是