C++类class
一、定義
構造函數:在定義一個類對象時會自動調用,可用于實現一些功能,比如new一個內存。
- 構造函數,沒有返回值也不寫void
- 函數名稱與類名相同
- 構造函數可以有參數,因此可以發生重載
- 程序在調用對象時候會自動調用構造,無須手動調用,而且只會調用一次
?
析構函數:在類對象銷毀時執行,可用于實現一些功能,比如delete一個內存。
- 析構函數,沒有返回值也不寫void
- 函數名稱與類名相同,在名稱前加上符號 ~
- 析構函數不可以有參數,因此不可以發生重載
- 程序在對象銷毀前會自動調用析構,無須手動調用,而且只會調用一次
?
#include <iostream>
using namespace std;
class Person
{
public:Person() { cout << "無參構造函數" << endl; }Person(int n_age) :age(n_age) { cout << "有參構造函數" << endl; }Person(const Person& p) { age = p.age; cout << "復制構造函數" << endl;}~Person() { cout << "析構函數" << endl; }void show_age(int n_age){this->age = n_age;cout << n_age << endl;}
private:int age;
};int main()
{// 1、無參構造函數Person p1;// 2、有參構造函數Person p2(10);// 3、復制構造函數Person p3(p2);p3.show_age(120);// 構造函數的定義// 1、括號Person p4(10);// 2、顯式法Person p5 = Person(100);// 3、隱式法(當類只有一個參數時,可以使用隱式法)Person p6 = 10;cout << "函數即將結束,開始析構" << endl;return 0;
}
1.2 explicit關鍵字
可以取消類的隱式構造
#include <iostream>
#include <string>
using namespace std;class Phone
{
public:Phone(string name) {this->phone_name = name;}Phone() {}~Phone() {}string phone_name;
};int main()
{string name = "huawei";Phone p = name;cout << p.phone_name << endl;return 0;
}
加入explicit關鍵字后可以取消隱式構造
#include <iostream>
#include <string>
using namespace std;class Phone
{
public:explicit Phone(string name) {this->phone_name = name;}Phone() {}~Phone() {}string phone_name;
};int main()
{string name = "huawei";// 錯誤,此時不能隱式構造Phone p = name;cout << p.phone_name << endl;return 0;
}
二、復制構造函數
C++中拷貝構造函數調用時機通常有三種情況
- 使用一個已經創建完畢的對象來初始化一個新對象
- 值傳遞的方式給函數參數傳值
- 以值方式返回局部對象
#include <iostream>
using namespace std;
class Person
{
public:Person() { cout << "無參構造函數" << endl; }Person(int n_age) :age(n_age) { cout << "有參構造函數" << endl; }Person(const Person& p) { age = p.age; cout << "復制構造函數" << endl;}~Person() { cout << "析構函數" << endl; }void show_age(int n_age){this->age = n_age;cout << n_age << endl;}
private:int age;
};void test01(Person p1)
{cout << "類對象作為函數形參傳遞,調用復制構造函數" << endl;return;
}
Person test02()
{Person p2(100);cout << "函數返回值為類對象時,調用復制構造函數" << endl;return p2;
}int main()
{Person p(100);Person p1(p);test01(p);Person p2 = test02();return 0;
}
三、淺拷貝/深拷貝
當類中含有指針類型成員變量時,需要進行深拷貝:
3.1 淺拷貝
#include <iostream>
#include <math.h>
using namespace std;
class Person
{
public:Person() { }Person(int n_age,int n_height) { age = n_age;height = new int(n_height);}Person(const Person& p) { age = p.age; // 淺拷貝height = p.height;}~Person() { if (height != NULL){cout << "釋放內存" << endl;delete height;}}void show_age(int n_age){this->age = n_age;cout << n_age << endl;}
private:int age;int* height;
};int main()
{Person p(23,160);Person p1(p);return 0;
}
淺拷貝中,p和p1的height指向同一個內存,當p釋放掉內存中的數據之后,p1所指向的內存中的數據為空,此時再釋放的話會報錯,因為這片內存中的數據已經不存在了。(注意,釋放的不是內存,而是內存中的數據)
3.2 深拷貝
#include <iostream>
#include <math.h>
using namespace std;
class Person
{
public:Person() { }Person(int n_age,int n_height) { age = n_age;height = new int(n_height);}Person(const Person& p) { age = p.age; // 深拷貝height = new int(*(p.height));}~Person() { if (height != NULL){cout << "釋放內存" << endl;delete height;}}void show_age(int n_age){this->age = n_age;cout << n_age << endl;}
private:int age;int* height;
};int main()
{Person p(23,160);Person p1(p);return 0;
}
四、類對象作為類成員
先調用成員類的構造,然后是該類的構造。析構順序相反。
#include <iostream>
using namespace std;class Phone
{
public:Phone(string n_name) :phone_name(n_name) {cout << "Phone構造函數" << endl;}Phone() {}~Phone() {cout << "Phone析構函數" << endl;}
private:string phone_name;
};class Person
{
public:Person() {}Person(string n_name, string n_phone):m_name(n_name),m_phone(n_phone) {cout << "Person構造函數" << endl;}~Person(){cout << "Person析構函數" << endl;}
private:string m_name;Phone m_phone;
};
int main()
{Person person1("xiaoming", "huawei");return 0;
}
五、this指針
5.1 this指針使用的原因
在類中,非靜態成員變量屬于類對象,而非靜態成員函數為所有類對象共享,不屬于某個類對象:
#include <iostream>
#include <string>
using namespace std;class Phone
{
public:explicit Phone(string name) {this->phone_name = name;}Phone() {}~Phone() {}void test_func() { cout << "成員函數不占用類對象的內存" << endl; }string phone_name;
};int main()
{string name = "huawei";// 錯誤,此時不能隱式構造cout << sizeof(name) << endl;Phone p(name);// 可見類對象只占有一個string類型變量的內存cout << sizeof(p) << endl;return 0;
}
5.2 this指針
每一個非靜態成員函數只會誕生一份函數實例,也就是說多個同類型的對象會共用一塊代碼
this指針的作用:
用于區分是哪個類對象調用了成員函數
this指針本質上是一個指針常量,因此其指向的對象不能變,指向的對象的值可以變
this指針的用途
當形參和成員變量同名時,可用this指針來區分
在類的非靜態成員函數中返回對象本身,可使用return *this
#include <iostream>
#include <string>
using namespace std;class Phone
{
public:explicit Phone(string name) {this->phone_name = name;}Phone() {}~Phone() {}void put_name(string phone_name) { // this->phone_name表示類的成員變量this->phone_name = phone_name;}// 使用this指針返回類自身Phone& get_phone_info(){this->phone_name += "10";return *this;}string phone_name;
};int main()
{Phone p;p.put_name("華為");cout << p.phone_name << endl;p.get_phone_info().get_phone_info().get_phone_info();cout << p.phone_name << endl;return 0;
}
六、友元
友元的目的就是讓一個函數或者類 訪問另一個類中私有成員
友元的三種實現
- 全局函數做友元
- 類做友元
- 成員函數做友元
6.1全局函數做友元
#include <iostream>
#include <string>
using namespace std;class room
{// 表明全局函數visit時友元,可以訪問私有變量friend void visit(room myroom);
public:room() {}room(string bedr) :bedroom(bedr) {}~room() {}
private:string bedroom;
};void visit(room myroom)
{cout << "go to " << myroom.bedroom << endl;
}int main()
{room myroom("dk's room");visit(myroom);return 0;
}
6.2類做友元
#include <iostream>
#include <string>
using namespace std;class room;
class person
{
public:person() {}person(string per) :name(per) {}~person() {}void visit();
private:string name;// 這里必須是指針,因為如果是變量的話// 編輯器不知道room類占了多少內存// 也就沒法開辟內存room* myroom;
};class room
{// person類時友元,可以訪問room類的私有變量friend class person;
public:room() {}~room() {}
private:string bedroom;
};void person::visit()
{this->myroom = new room;this->myroom->bedroom = "bedroom";// 此時person類中的room類對象myroom可以訪問room類中的私有變量cout << this->name << " is visiting " << this->myroom->bedroom << endl;return;
}int main()
{person myfriend("liming");myfriend.visit();return 0;
}
6.3成員函數做友元
#include <iostream>
#include <string>
using namespace std;class room;
class person
{
public:person() {}person(string per) :name(per) {}~person() {}void visit();
private:string name;// 這里必須是指針,因為如果是變量的話// 編輯器不知道room類占了多少內存// 也就沒法開辟內存room* myroom;
};class room
{// person類中的visit成員函數做友元,可以訪問room類的私有變量friend void person::visit();
public:room() {}~room() {}
private:string bedroom;
};void person::visit()
{this->myroom = new room;this->myroom->bedroom = "bedroom";// 此時person類中的room類對象myroom可以訪問room類中的私有變量cout << this->name << " is visiting " << this->myroom->bedroom << endl;return;
}int main()
{person myfriend("liming");myfriend.visit();return 0;
}
七、運算符重載
7.1 加法運算符重載
#include<iostream>
using namespace std;
class Person
{
public:Person() {}Person(int m_age):n_age(m_age) {}~Person() {}//相當于一個成員函數 +運算符重載1 p1.operator+(p2)Person operator+(Person p){Person temp;temp.n_age = this->n_age + p.n_age;return temp;}
public:int n_age;
};// 相當于一個函數 operator+(p,age)
Person operator+(Person p,int age)
{Person temp;temp.n_age = p.n_age + age;return temp;
}int main()
{Person p1(20);Person p2(20);Person p3 = p1 + p2;cout << p3.n_age << endl;Person p4 = p1 + 20;cout << p4.n_age << endl;
}
7.2 輸出運算符重載
#include<iostream>
using namespace std;
class Person
{
public:Person() {}Person(int m_age):n_age(m_age) {}~Person() {}public:int n_age;
};ostream& operator<<(ostream& out, Person p)
{out << "age is " << p.n_age << endl;return out;
}int main()
{Person p1(20);cout << p1 << "20" << endl;
}
7.3 函數調用運算符重載(仿函數)
- 函數調用運算符 () 也可以重載
- 由于重載后使用的方式非常像函數的調用,因此稱為仿函數
- 仿函數沒有固定寫法,非常靈活
#include<iostream>
using namespace std;
class mycomp
{
public:void operator()(int a,int b){cout << a + b << endl;}
};int main()
{mycomp com1;com1(10, 20);return 0;
}
八、多態
多態是C++的重要特征,與封裝、繼承并稱為C++的三大特征
多態分為靜態多態、動態多態
靜態多態:函數的地址的編譯時刻確定,主要包括 函數重載 和 運算符重載
動態多態:函數的地址在執行時刻確定,通過 派生類 和 虛函數實現
?
動態多態滿足的條件
(1)存在繼承關系
(2)子類重寫父類的虛函數(函數名,返回值類型,形參完全相同)
注意,這里和繼承中的同名函數重寫不同。如果是同名函數的重寫,父類和子類的同名函數的地址在編譯階段就固定了。而多態中,父類中的虛函數的地址在編譯的時候是不確定的。
其類似于函數的重載
多態優點:代碼組織結構清晰,可讀性強,利于前期和后期的擴展以及維護(不用去修改源碼,直接在子類中重寫虛函數即可)
?
多態使用條件:父類的指針或引用指向子類的對象
8.1 多態示例
8.1.1示例1
#include<iostream>
#include<string>
using namespace std;
class game
{
public:virtual void func(string name){cout << "I like playing " << name << endl;}
};class BH3:public game
{
public:BH3(string name) :n_name(name) {}void func(string name){cout << "I like playing " << this->n_name << endl;}string n_name;
};class YS:public game
{
public:YS(string name) :n_name(name) {}void func(string name){cout << "I like playing " << this->n_name << endl;}string n_name;
};void test_func(game& mygame)
{mygame.func("game");
}int main()
{BH3 bh3("崩壞3");test_func(bh3);YS ys("原神");test_func(ys);return 0;
}
8.1.2 示例2
#include<iostream>
using namespace std;
// 運算器基類
class calculate
{
public:virtual int getresult(int x1,int x2){return 0;}
};// 加法運算器
class addcalculate:public calculate
{
public:int getresult(int x1,int x2){return x1 + x2;}
};// 減法運算器
class subcalculate :public calculate
{
public:int getresult(int x1, int x2){return x1 - x2;}
};
int main()
{// 構建一個加法運算器// 父類的指針或引用指向子類的對象calculate* cal1 = new addcalculate();cout << "加法結果" << cal1->getresult(10, 20) << endl;// 構建一個減法運算器calculate* cal2 = new subcalculate();cout << "減法結果" << cal2->getresult(10, 20) << endl;
}
8.2 純虛函數/抽象類
在多態中,父類中的虛函數一般不會使用,使用的是子類中重寫的虛函數。
所以,一般將父類的虛函數寫為純虛函數,格式為:
virtual 返回值類型 函數名 (參數列表)= 0 ;
含有純虛函數的類稱為抽象類
抽象類的特點:
子類必須重寫抽象類中的純虛函數,否則子類也是抽象類
抽象類無法進行初始化
#include<iostream>
using namespace std;
class Calculator
{
public:// 純虛函數,此時Calculator為抽象類,無法進行初始化virtual int calculate(int x1, int x2) = 0;
};class AddCalculator :public Calculator
{
public:virtual int calculate(int x1, int x2){return x1 + x2;}
};class SubCalculate:public Calculator
{
public:virtual int calculate(int x1, int x2){return x1 - x2;}
};int main()
{// 錯誤,抽象類無法進行初始化// Calculator* cal = new Calculator();Calculator* Add_Cal = new AddCalculator;cout << Add_Cal->calculate(10, 20) << endl;Calculator* Sub_Cal = new SubCalculate;cout << Sub_Cal->calculate(10, 20) << endl;
}
8.3 虛析構/純虛析構
多態使用時,如果子類中有屬性開辟到堆區,那么父類指針在釋放時無法調用到子類的析構代碼。
此時需要在父類中設置虛析構。
虛析構和純虛析構共性:
- 可以解決父類指針釋放子類對象
- 都需要有具體的函數實現
虛析構和純虛析構區別:
- 如果是純虛析構,該類屬于抽象類,無法實例化對象
#include<iostream>
using namespace std;
// 運算器基類
class calculate
{
public:calculate() { cout << "父類的構造函數" << endl; }~calculate() { cout << "父類的析構函數" << endl; }virtual int getresult(int x1, int x2){return 0;}
};// 加法運算器
class addcalculate :public calculate
{
public:addcalculate() { cout << "子類的構造函數" << endl; }~addcalculate() { cout << "子類的析構函數" << endl; }int getresult(int x1, int x2){return x1 + x2;}
};int main()
{calculate* calculator = new addcalculate;delete calculator;
}
此時的輸出為:
父類的構造
子類的構造
父類的析構
此時沒有調用子類的析構,如果子類在構造中開辟了內存,在析構中釋放了內存。此時子類中開辟的內存無法被釋放,從而產生內存的泄露。
為解決此問題,將父類的析構函數改為虛析構函數。
#include<iostream>
using namespace std;
// 運算器基類
class calculate
{
public:calculate() { cout << "父類的構造函數" << endl; }virtual ~calculate() { cout << "父類的析構函數" << endl; }int getresult(int x1, int x2){return 0;}virtual int* test() { return NULL; }
};// 加法運算器
class addcalculate :public calculate
{
public:addcalculate() { cout << "子類的構造函數" << endl; val = new int(10);}~addcalculate() { cout << "子類的析構函數" << endl;delete val;}int getresult(int x1, int x2){return x1 + x2;}int* test(){return val;}int* val;
};int main()
{calculate* calculator = new addcalculate();int* space = calculator->test();delete calculator;cout << *space << endl;
}
此時*space為一個垃圾數據,說明該內存內部的數據已經被釋放。如果父類不是虛析構函數的話,輸出就會是10,說明內存中的數據沒有被釋放。
總結
- 上一篇: 2018微信网名经典
- 下一篇: C++/C文件读取