保定定興網(wǎng)站建設(shè)安卓手機優(yōu)化神器
類和對象
1. 靜態(tài)對象的探討與全局對象的構(gòu)造順序
靜態(tài)對象的探討
類中的靜態(tài)成員變量(類類型靜態(tài)成員)
- 類中靜態(tài)變量的聲明與定義(
類中聲明類外定義
)
#include<iostream>
using namespace std;namespace _nmspl
{class A{public:A():m_i(5){cout << "A:A()缺省構(gòu)造函數(shù)執(zhí)行了" << endl;}~A(){cout << "A:~A()缺省析構(gòu)函數(shù)執(zhí)行了" << endl;}int m_i;};class B{public:static A m_sa; // 靜態(tài)成員變量的聲明};A B::m_sa; // 這是對類B靜態(tài)成員變量m_sa的定義
}int main()
{_nmspl::B bobj;cout << bobj.m_sa.m_i;return 0;
}
- 沒有創(chuàng)建類B時
類中靜態(tài)成員變量即使沒有被調(diào)用,也會被構(gòu)造和析構(gòu)
inline
-
inline關(guān)鍵字最初用于建議編譯器嘗試將一個函數(shù)體直接插入到調(diào)用該函數(shù)的地方,以減少函數(shù)調(diào)用的開銷。這并不意味著編譯器一定會內(nèi)聯(lián)這個函數(shù),它只是對編譯器的一個提示。
-
在C++17增加新用法,
- 內(nèi)聯(lián)變量
-
內(nèi)聯(lián)靜態(tài)成員變量
-
visual studio中改變C++標準
#include<iostream> using namespace std;namespace _nmspl {class A{public:A():m_i(5){cout << "A:A()缺省構(gòu)造函數(shù)執(zhí)行了" << endl;}~A(){cout << "A:~A()缺省析構(gòu)函數(shù)執(zhí)行了" << endl;}int m_i;};//class B//{//public:// static A m_sa; // 靜態(tài)成員變量的聲明//};//A B::m_sa; // 這是對類B靜態(tài)成員變量m_sa的定義class B{public:inline static A m_sa; // 靜態(tài)成員即聲明又定義}; }
-
函數(shù)中的靜態(tài)對象(類類型靜態(tài)對象)
如果函數(shù)沒有被調(diào)用過,那么這個靜態(tài)對象不會被構(gòu)造,即使函數(shù)被調(diào)用多次,靜態(tài)對象也只會被創(chuàng)建依次
區(qū)別于:類中靜態(tài)成員變量即使沒有被調(diào)用,也會被構(gòu)造和析構(gòu)
全局對象的構(gòu)造順序問題
- 全局對象的構(gòu)造順序不確定的
注意不要出現(xiàn)構(gòu)造一個全局對象,需要另外一個全局對象,因為無法確定誰先被構(gòu)造
- 出現(xiàn)錯誤
- 出現(xiàn)錯誤
// class1.h
#ifndef __CLASS1_H__
#define __CLASS1_H__class Class1 {
public:Class1();~Class1();
};
#endif// class2.h
#ifndef __CLASS2_H__
#define __CLASS2_H__class Class2 {
public:Class2();~Class2();
public:int m_i;
};
#endif// class1.cpp
#include<iostream>
using namespace std;
#include"Class1.h"
#include"Class2.h"extern Class2 gclass2;
Class1::Class1()
{cout << "調(diào)用Class2中的m_i=" << gclass2.m_i << endl;cout << "Class1:構(gòu)造函數(shù)()" << endl;
}
Class1::~Class1()
{cout << "Class1:析構(gòu)函數(shù)()" << endl;
}// class2.cpp
#include<iostream>
using namespace std;
#include"Class2.h"Class2::Class2():m_i(5)
{cout << "Class2:構(gòu)造函數(shù)()" << endl;
}
Class2::~Class2()
{cout << "Class2:析構(gòu)函數(shù)()" << endl;
}// main.cpp
#include<iostream>
using namespace std;
#include"Class1.h"
#include"Class2.h"
Class1 gclass1;
Class2 gclass2;
int main()
{return 0;
}
- 如果需要可以使用函數(shù)進行構(gòu)造返回
// class1.h
#ifndef __CLASS1_H__
#define __CLASS1_H__class Class1 {
public:Class1();~Class1();
};
#endif// class2.h
#ifndef __CLASS2_H__
#define __CLASS2_H__class Class2 {
public:Class2();~Class2();
public:int m_i;
};
#endif// class1.cpp
#include<iostream>
using namespace std;
#include"Class1.h"
#include"Class2.h"#include"func.h"extern Class2 gclass2;
Class1::Class1()
{cout << getClass2().m_i << endl;cout << "Class1:構(gòu)造函數(shù)()" << endl;
}
Class1::~Class1()
{cout << "Class1:析構(gòu)函數(shù)()" << endl;
}// class2.cpp
#include<iostream>
using namespace std;
#include"Class2.h"Class2& getClass2()
{static Class2 gclass2; // 不要在多線程中調(diào)用return gclass2;
}
Class2::Class2():m_i(5)
{cout << "Class2:構(gòu)造函數(shù)()" << endl;
}
Class2::~Class2()
{cout << "Class2:析構(gòu)函數(shù)()" << endl;
}
// func.h
#ifndef __FUNC_H__
#define __FUNC_H__class Class2; // 類的前置聲明
Class2& getClass2();
#endif
// main.cpp
#include<iostream>
using namespace std;
#include"Class1.h"
#include"Class2.h"
Class1 gclass1;
//Class2 gclass2;
int main()
{return 0;
}
2. 拷貝構(gòu)造函數(shù)和拷貝賦值運算符
拷貝構(gòu)造函數(shù)和拷貝賦值運算符的書寫
#include<iostream>using namespace std;namespace _nmspl
{class A{public:A() :m_caa(0), m_cab(0) {}//拷貝構(gòu)造函數(shù)A(const A& tmpobj){m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;}//拷貝賦值運算符重載A& operator+(const A& tmpobj){m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;return *this;}public:int m_caa;int m_cab;};
}
int main()
{
}
對象自我賦值產(chǎn)生的問題
#include<iostream>
#include<cstring>
using namespace std;namespace _nmspl
{class A{public:A() :m_caa(0), m_cab(0) {}//拷貝構(gòu)造函數(shù)A(const A& tmpobj){m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;}//拷貝賦值運算符重載A& operator+(const A& tmpobj){m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;return *this;}public:int m_caa;int m_cab;};
}namespace _nmsp2
{class A{public:A() :m_caa(0), m_cab(0),m_cap(new char[100]) {}//拷貝構(gòu)造函數(shù)A(const A& tmpobj){m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;// 注意這個是錯誤的,拷貝構(gòu)造函數(shù)時內(nèi)存還未分配,直接new即可/*if (m_cap != NULL) {delete[] m_cap;m_cap = NULL;}*/m_cap = new char[100];memcpy(m_cap,tmpobj.m_cap,100);}//拷貝賦值運算符重載A& operator+(const A& tmpobj){if (&tmpobj == this)return *this;// 注意這個是需要進行內(nèi)存釋放的,因為已經(jīng)調(diào)用過構(gòu)造函數(shù)了if (m_cap != NULL){delete[] m_cap;m_cap = NULL;}m_cap = new char[100];strcap(m_cap, tmpobj.m_cap);m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;return *this;}~A(){delete[] m_cap; }public:int m_caa;int m_cab;char* m_cap;};
}
int main()
{
}
繼承關(guān)系下的拷貝構(gòu)造函數(shù)和拷貝賦值運算符的書寫
- 關(guān)鍵點
- 當又子類時,一定要將父類的析構(gòu)函數(shù)設(shè)置為虛函數(shù)。不然在多態(tài)時,子類的析構(gòu)函數(shù)不會被調(diào)用。
當父類和子類同時都有拷貝構(gòu)造函數(shù)和賦值運算符重載函數(shù)時,子類一定要主動去調(diào)用父類的這兩個函數(shù)。不然父類的這兩個函數(shù)不會被調(diào)用。
- 在C++中,將基類(父類)的析構(gòu)函數(shù)聲明為虛函數(shù)是非常重要的,特別是在涉及到多態(tài)和繼承的情況下。這樣做的主要原因是確保當通過基類指針刪除派生類對象時,能夠正確地調(diào)用派生類的析構(gòu)函數(shù),從而釋放派生類中可能分配的所有資源。這有助于避免內(nèi)存泄漏或其他資源管理的問題。
#include <iostream>class Base {
public:~Base() { std::cout << "Base destructor\n"; }
};class Derived : public Base {int* data;
public:Derived() { data = new int[10]; } // 分配一些資源~Derived() { delete[] data; std::cout << "Derived destructor\n"; }
};int main() {Base* basePtr = new Derived();delete basePtr; // 這里只調(diào)用了Base的析構(gòu)函數(shù)
}
在這個例子中,Base 類的析構(gòu)函數(shù)不是虛函數(shù)。當我們通過 Base* 指針刪除 Derived 對象時,只有 Base 的析構(gòu)函數(shù)被調(diào)用。這意味著 Derived 類中的資源(即 data 數(shù)組)沒有被釋放,導致了內(nèi)存泄漏。
- 當子類B為空時
#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<cstring> using namespace std;namespace _nmspl {class A{public:A() :m_caa(0), m_cab(0) {}//拷貝構(gòu)造函數(shù)A(const A& tmpobj){m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;}//拷貝賦值運算符重載A& operator+(const A& tmpobj){m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;return *this;}public:int m_caa;int m_cab;}; }namespace _nmsp2 {class A{public:A() :m_caa(0), m_cab(0),m_cap(new char[100]) {}//拷貝構(gòu)造函數(shù)A(const A& tmpobj){m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;// 注意這個是錯誤的,拷貝構(gòu)造函數(shù)時內(nèi)存還未分配,直接new即可/*if (m_cap != NULL) {delete[] m_cap;m_cap = NULL;}*/m_cap = new char[100];memcpy(m_cap,tmpobj.m_cap,100);}//拷貝賦值運算符重載A& operator+(const A& tmpobj){if (&tmpobj == this)return *this;// 注意這個是需要進行內(nèi)存釋放的,因為已經(jīng)調(diào)用過構(gòu)造函數(shù)了if (m_cap != NULL){delete[] m_cap;m_cap = NULL;}m_cap = new char[100];memcpy(m_cap, tmpobj.m_cap,100);m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;return *this;}virtual ~A(){delete[] m_cap; }public:int m_caa;int m_cab;char* m_cap;};class B:public A{}; } int main() {_nmsp2::B bobj1;bobj1.m_caa = 100;bobj1.m_cab = 200;strcpy(bobj1.m_cap,"new class");_nmsp2::B bobj2 = bobj1; // 執(zhí)行類A的拷貝構(gòu)造函數(shù)bobj2 = bobj1; // 執(zhí)行類A的拷貝賦值運算符 }
- 當子類B有自己的拷貝和賦值;
#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<cstring> using namespace std;namespace _nmspl {class A{public:A() :m_caa(0), m_cab(0) {}//拷貝構(gòu)造函數(shù)A(const A& tmpobj){m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;}//拷貝賦值運算符重載A& operator+(const A& tmpobj){m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;return *this;}public:int m_caa;int m_cab;}; }namespace _nmsp2 {class A{public:A() :m_caa(0), m_cab(0),m_cap(new char[100]) {}//拷貝構(gòu)造函數(shù)A(const A& tmpobj){m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;// 注意這個是錯誤的,拷貝構(gòu)造函數(shù)時內(nèi)存還未分配,直接new即可/*if (m_cap != NULL) {delete[] m_cap;m_cap = NULL;}*/m_cap = new char[100];memcpy(m_cap,tmpobj.m_cap,100);cout << "父類的拷貝構(gòu)造函數(shù)" << endl;}//拷貝賦值運算符重載A& operator+(const A& tmpobj){if (&tmpobj == this)return *this;// 注意這個是需要進行內(nèi)存釋放的,因為已經(jīng)調(diào)用過構(gòu)造函數(shù)了if (m_cap != NULL){delete[] m_cap;m_cap = NULL;}m_cap = new char[100];memcpy(m_cap, tmpobj.m_cap,100);m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;cout << "父類的拷貝賦值運算符" << endl;return *this;}virtual ~A(){delete[] m_cap; }public:int m_caa;int m_cab;char* m_cap;};class B:public A{public:B() = default;B(const B& b){cout << "子類的拷貝構(gòu)造函數(shù)" << endl;}void operator=(const B& b){cout << "子類的拷貝賦值運算符" << endl;//return B();}}; } int main() {_nmsp2::B bobj1;bobj1.m_caa = 100;bobj1.m_cab = 200;strcpy(bobj1.m_cap,"new class");_nmsp2::B bobj2 = bobj1; // 只調(diào)用子類的拷貝構(gòu)造函數(shù)bobj2 = bobj1; // 只調(diào)用子類的拷貝賦值運算符 }
只調(diào)用子類的函數(shù)
- 需要程序自己主動去調(diào)用父類的拷貝構(gòu)造函數(shù)與拷貝賦值運算符函數(shù)
class B : public A{public:B() = default;B(const B& b) : A(b){cout << "子類的拷貝構(gòu)造函數(shù)" << endl;}B& operator=(const B& b){A::operator=(b);cout << "子類的拷貝賦值運算符" << endl;return *this;}};
注意:調(diào)用父類的構(gòu)造函數(shù)的錯誤寫法
B(const B& b)
{
A(b);//存在二義性,創(chuàng)建對象或者調(diào)用函數(shù)
cout << “子類的拷貝構(gòu)造函數(shù)” << endl;
}
- 檢查內(nèi)存是否釋放(只有在F5才起作用)
_CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF); int* p = new int[10];
3. 類的public繼承(is-a關(guān)系)及代碼編寫規(guī)則
- 子類繼承父類得方式-有三種:公有;受保護:私有繼承
- public代表得是一種is-a(是一種)的關(guān)系。通過這個子類產(chǎn)生的對象也一定是一個父類對象。
- 人類(人類),人類(男人):父類表現(xiàn)的是一種更泛化的概念,而子類表現(xiàn)得是一種更特化的概念.
- public繼承關(guān)系的檢驗規(guī)則:能夠在父類對象上做的行為也必然能在子類對象上做,每個子類對象同時也都是一個父類對象。
- 里氏替換(利斯科夫替換)原則:任何基類出現(xiàn)的地方都應該可以無差別的使用子類替換.
子類遮蔽父類的普通成員函數(shù)
- 對于public繼承,不建議也不應該使用子類的普通成員函數(shù)遮蔽同名的父類的普通成員函數(shù)
- 既然在父類中是普通成員函數(shù),那么就代表在子類中不會有不同的行為,代表的是一種不變性。
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <cstring> using namespace std; class Humain { public:void eat(){cout << "人類吃食物" << endl;}virtual ~Humain(){} };class Man :public Humain { public:void eat(){cout << "男人吃面試" << endl;} }; int main() {Man man;man.eat(); // 調(diào)用子類的函數(shù)man.Humain::eat(); // 調(diào)用父類的成員函數(shù) }
父類的純虛函數(shù)接口
- 純虛函數(shù),讓子類繼承父類的純虛函數(shù)接口。
- 純虛函數(shù)
- 擁有此函數(shù)的類變成了抽象類,抽象類不能生成該類對象
- 任何繼承該類的類,必須實現(xiàn)這個純虛函數(shù)。
父類的虛函數(shù)接口
- 虛函數(shù)讓子類繼承父類的虛函數(shù)接口和實現(xiàn),子類也可以提供實現(xiàn)
為純虛函數(shù)指定實現(xiàn)體
- 為純虛函數(shù)指定實現(xiàn)體
- 強制子類必須去實現(xiàn)該函數(shù)
- 讓一些確實不需要單獨實現(xiàn)該接口的子類有機會直接調(diào)用父類的該實現(xiàn)體
類的public繼承(is-a關(guān)系)綜合范例
public繼承關(guān)系下的代碼編寫規(guī)則
4. 類與類之間的組合關(guān)系和委托關(guān)系
組合關(guān)系(復合關(guān)系-Compositon)
- 一個類中的定義中含有其他類類型變量
has-a關(guān)系(is-part-os)
is-implemented-in-terms-of關(guān)系
- 根據(jù)…實現(xiàn)…
// multimap:鍵可以重復
// 我們現(xiàn)在先去實現(xiàn)一個鍵不可以重復的map
// 繼承關(guān)系
//class MyMap :public multimap<T, U> {…};
template<typename T,typename U>
class MyMap
{
public:void insert(const T& key, const U & value){if (container.find(key) != container)return;container.insert(make_pair<T, U>(key, value));}size_t size(){return container.size();}
private:multimap<T, U> container;
};
組合關(guān)系的UML圖
實心菱形框 - 組合關(guān)系中的Human與Info對象具有相同的生命周期
委托關(guān)系(聚合關(guān)系:Deletation)
- 一個類中具有指向?qū)幫庖粋€類的指針
空菱形框 - 生命周期不一樣
5. 類的private繼承探討
- public繼承
class Humain { public: };class Man :public Humain { public: }; int main() {Man man;Humain & human = man; // 父類引用綁定子類對象Humain* pHUman = &man; //父類指針指向子類對象return 0; }
- private繼承:就不屬于is-a關(guān)系了
- private繼承是一種
組合關(guān)系
,是組合關(guān)系中的is-implemented-in-terms-of
根據(jù)…實現(xiàn)… - 一般優(yōu)先考慮使用組合關(guān)系,只有在一些比較特殊的情況和必要的情況下,比如牽扯一些保護的成員、私有成員、虛函數(shù)等 案例如下
6. 不能被拷貝構(gòu)造和拷貝賦值的類對象
- 給構(gòu)造函數(shù)寫
delete
,編譯器也不會自動生成默認的構(gòu)造函數(shù),需要程序員自己去寫class A { public:A(const& A a) = delete; }; int main() {A a; //報錯return 0; }
實現(xiàn)方案一:delete
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
using namespace std;class A
{
public:A(const& A a) = delete;A& operator=(const& A a)= delete;
};
int main()
{A a;return 0;
}
實現(xiàn)方案二:private
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
using namespace std;class A
{
private:A(const& A a) = delete;A& operator=(const& A a)= delete;
};
int main()
{A a;return 0;
}
但是類內(nèi)還是可以訪問這兩個函數(shù)
實現(xiàn)方案三:不提供實現(xiàn)
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
using namespace std;class A
{
public:A() = default;A(const A& a) ;A& operator=(const A& a);
};
int main()
{A a;A a1(a);a1 = a;return 0;
}
調(diào)用會出現(xiàn)鏈接錯誤
實現(xiàn)法案四:繼承Noncopyable成員
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
using namespace std;class Noncopyable
{
protected:Noncopyable() {};~Noncopyable() {};
private:Noncopyable(const Noncopyable& a) ;Noncopyable& operator=(const Noncopyable& a);
};
class A :private Noncopyable
{};
int main()
{A a;A a1(a);a1 = a;return 0;
}
7. 虛析構(gòu)函數(shù)的內(nèi)存泄露問題深談
- 一個類如果不是父類,建議此類的析構(gòu)函數(shù)不要定義為虛析構(gòu)函數(shù)。因為這樣會因為虛函數(shù)表增一個虛函數(shù)表指針
- 為什么會出現(xiàn)內(nèi)存泄露問題
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <cstring> using namespace std;class ThirdPart { public:ThirdPart() = default;~ThirdPart() {cout << "~ThirdPart()被調(diào)用" << endl;}; }; class A :public ThirdPart { public:int* m_p;A() {m_p = new int[100];}~A(){cout << "~A()被調(diào)用" << endl;delete m_p;} }; int main() {ThirdPart * ths = new A();delete ths;return 0; }
不要隨便public繼承一個類
- 一個類不可以被繼承:final
class ThirdPart final { public:ThirdPart() = default;~ThirdPart() {cout << "~ThirdPart()被調(diào)用" << endl;}; };
只有父類指針指向子類對象或者父類引用綁定到子類對象時,父類才需要虛析構(gòu)函數(shù)
如果子類private或protected繼承父類,那么父類指針不能指向子類對象,只能時public繼承,需要父類提供虛析構(gòu)函數(shù)
-
一個函數(shù)的成員函數(shù)被聲明為非public中,在main函數(shù)不能被調(diào)用
class A { ~A(){cout << "~A" << endl;} }; int main() {A* p = new A(); delete p; // erro:注意這里是去調(diào)用A的析構(gòu)函數(shù),而A的析構(gòu)函數(shù)不能被調(diào)用return 0; } ``
-
下面也就好理解了
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <cstring> using namespace std;class Noncopyable { protected:Noncopyable() {};~Noncopyable(){cout << "~Noncopyable" << endl;}; private:Noncopyable(const Noncopyable& a);Noncopyable& operator=(const Noncopyable& a); }; class A :public Noncopyable { ~A(){cout << "~Noncopyable" << endl;} }; int main() {Noncopyable* p = new A();delete p;return 0; }
8. 類設(shè)計中的有些技巧
8.1 優(yōu)先考慮為成員變量提供訪問接口
class A
{
public: int m_a;
};class A
{
public :int getA(){return m_a;}
private: int m_a;
};
8.2 如何避免將父類的虛函數(shù)暴露給子類
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
using namespace std;class A
{
public:void fun(){func();}virtual ~A() {}
private:virtual void func(){cout << "A::func()" << endl;}
};class B:public A
{
public:B() = default;
private:virtual void func(){cout << "B::func()" << endl;}
};
int main()
{A* p = new B();p->fun(); //B::func()return 0;
}
fun函數(shù)是func虛函數(shù)的一行通道性質(zhì)的代碼。非虛擬接口(Nonvirtual Interface NVI)
如果能將虛函數(shù)設(shè)置為私有,則優(yōu)先考慮將其設(shè)置為私有
8.3 不要在類的構(gòu)造函數(shù)和析構(gòu)函數(shù)中調(diào)用虛函數(shù)
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
using namespace std;class A
{
public:void fu(){func1_vir();}A(){func1_vir();}virtual ~A() {func2_vir();}virtual void func1_vir(){cout << "A::func1_vir()" << endl;}virtual void func2_vir(){cout << "A::func2_vir()" << endl;}
};class B:public A
{
public:B(){func1_vir();}virtual ~B(){func2_vir();}virtual void func1_vir(){cout << "B::func1_vir()" << endl;}virtual void func2_vir(){cout << "B::func2_vir()" << endl;}
};
int main()
{A* p = new B();cout << "begin_____" << endl;p->func1_vir();p->func2_vir();p->fu();cout << "end_____" << endl;delete p;return 0;
/* 輸出結(jié)果
A::func1_vir()
B::func1_vir()
begin_____
B::func1_vir()
B::func2_vir()
B::func1_vir()
end_____
B::func2_vir()
A::func2_vir()*/
}
A::func1_vir() 父類中構(gòu)造函數(shù)調(diào)用的虛函數(shù)是父類的虛函數(shù)
B::func1_vir() 子類中構(gòu)造函數(shù)調(diào)用的虛函數(shù)是子類的虛函數(shù)
begin_____
B::func1_vir()
B::func2_vir()
B::func1_vir() 定義在父類中的非虛函數(shù)fu()中的虛函數(shù)調(diào)用的是子類的虛函數(shù)
end_____
B::func2_vir() 子類中析構(gòu)函數(shù)調(diào)用的虛函數(shù)是子類的虛函數(shù)
A::func2_vir() 父類中析構(gòu)函數(shù)調(diào)用的虛函數(shù)是父類的虛函數(shù)
如果在父類的構(gòu)造函數(shù)中調(diào)用了一個子類的虛函數(shù)是無法做到的,因為執(zhí)行到父類的構(gòu)造函數(shù)時對象的子類部分還沒有被構(gòu)造出來
如果在父類的析構(gòu)函數(shù)中調(diào)用一個子類的虛函數(shù)也是無法做到的,因為執(zhí)行到父類的析構(gòu)函數(shù)時對象的子類部分其實已經(jīng)被銷毀了
在構(gòu)造函數(shù)或析構(gòu)函數(shù)中,虛函數(shù)可能會失去虛函數(shù)的作用而被當作一個普通函數(shù)
8.4 析構(gòu)函數(shù)的虛與非虛談
- 父類的析構(gòu)函數(shù)不一定必須是虛函數(shù),當父類指針指向子類或父類引用綁定子類時,父類需要寫一個public修飾的析構(gòu)函數(shù),這樣就可以通過父類的接口銷毀子類對象,否則會導致內(nèi)存泄漏
- 用protect修飾析構(gòu)函數(shù)
- 無法創(chuàng)建子類對象
- 無法讓父類指針指向父類或者子類對象
- 如果一個父類的析構(gòu)函數(shù)不是虛函數(shù),并且也不利用這個父類創(chuàng)建對象,也不會用到這個父類類型的指針,則應該考慮將父類的的析構(gòu)函數(shù)使用protected修飾 ,增加代碼安全性
- 父類的析構(gòu)函數(shù)不是虛函數(shù),本身就暗示了不會通過父類的接口有銷毀子類的對象
8.5 抽象類的模擬
- 抽象類要求至少有一個純虛函數(shù)
- 抽象類:不能用來生成對象
- 將模擬的抽象類的構(gòu)造函數(shù)和拷貝構(gòu)造函數(shù)都使用protected修飾
class PVC { protected:PVC() {cout << "PVC()" << endl;}PVC(const PVC& pvc) {} }; class SubPVC :public PVC { public:SubPVC(){cout << "SubPVC()" << endl;} }; int main() {PVC* p = new SubPVC(); // YesPVC* p = new PVC(); // errorreturn 0; }
- 將模擬的抽象類的析構(gòu)函數(shù)設(shè)置為純虛函數(shù),并在類外提供實現(xiàn)體(大多數(shù)純虛函數(shù)沒有實現(xiàn)體,但是純虛函數(shù)是個例外,為了釋放資源,所以一般要有一個實現(xiàn)體)
class PVC { protected:PVC() {}virtual ~PVC() = 0; }; PVC::~PVC() {} class SubPVC :public PVC { public:~SubPVC() {} };
- 將模擬的抽象類的析構(gòu)函數(shù)使用protected修飾
8.6 盡量避免隱式類型轉(zhuǎn)換
- 類型轉(zhuǎn)換構(gòu)造函數(shù)
class A { public:A(int i){cout << "A()" << endl;} };int main() {A a = 5.2; // 將5構(gòu)造成一個臨時對象Areturn 0; }
- explicit
class A { public:explicit A(int i){cout << "A()" << endl;} };int main() {//A a = 5; // errorA a = A(5); return 0; }
8.7 強制類對象不可以或只可以在堆上分配內(nèi)存
8.7.1 強制類對象不可以在堆上分配內(nèi)存
- 重載類中的
operator new
和operator delete
,并使用private修飾class A { public: private:static void* operator new(size_t size);static void operator delete(void *p);};int main() {A* a = new A(); // errorA* a = new A[3]; // 但是卻可以new數(shù)組return 0; }
- 再次修改
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <cstring> using namespace std;class A { public: private:static void* operator new(size_t size);static void operator delete(void *p);static void* operator new[](size_t size);static void operator delete[](void* p);};int main() {A* a = new A[3];return 0; }
8.7.2 強制類對象只可以在堆上分配內(nèi)存
- 使用private修飾析構(gòu)函數(shù)
class A
{
public:void destiry(){delete this;}
private:~A() {}; // 這樣寫也會導致創(chuàng)建在堆中的對象,不能delete。// 所以需要一個函數(shù)進行顯示的調(diào)用};int main()
{A a; // errorA* p = new A();p->destiry();return 0;
}
9. 命名空間使用的一些注意事項
- 使用using聲明命名空間的代碼不要放在.h文件中 ,否則會造成命名空間污染
- 在
.cpp
文件中,using聲明語句放在include語句之后
10. 類定義的相互依賴與類的前向聲明
-
前向聲明
// a1.h #ifndef __A1_H__ #define __A1_H__ //#include"a2.h" class A2; class A1 { public:A2* pm; }; #endif // !__A1_H__// a2.h #ifndef __A2_H__ #define __A2_H__ //#include"a1.h" class A1;class A2 { public:A1* pm; }; #endif // !__A1_H__
-
有些情況下需要類的完整定義而不是前向聲明
- 在類A1的定義中加入類A2類型的對象
- 在類A1的定義中需要知道類A2對象的大小
- 在類A1中需要調(diào)用A2的成員函數(shù)
#ifndef __A2_H__ #define __A2_H__ //#include"a1.h" class A1;class A2 { public:A1* pm;A1 pm; // error }; #endif // !__A1_H__
-
類A1與類A2之間的直接1依賴.一般是避免這種設(shè)計。而是通過引入一個新類,讓類A1和類A2都依賴這個新的類,從而打破兩個類的之間的直接依賴
-
解決1:
- 引入中間層
-
解決2
- 使用接口
引用計數(shù)基礎(chǔ)理論和實踐
1. shared_ptr 實現(xiàn)及string存儲簡單說明
1.1 shared_ptr智能指針實現(xiàn)簡單說明
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
#include<memory>
using namespace std;int main()
{shared_ptr<int> myp(new int(5));cout << "icount = " << myp.use_count() << endl; // 1{shared_ptr<int> myp1(myp);cout << "icount = " << myp.use_count() << endl;// 2}shared_ptr<int> myp1(myp);cout << "icount = " << myp.use_count() << endl;// 2return 0;
}
1.2 string類型字符串存儲方式的簡單說明
-
string類型字符串存儲方式的簡單說明
-
貪婪拷貝
-
寫時復制
-
短字符優(yōu)化
-
在VS2022中(貪婪拷貝)
int main() {std::string str1("123");std::string str2 = str1;printf("str1的地址:%p\n", str1.c_str());printf("str2的地址:%p\n", str2.c_str());/* str1的地址:0000000C7398F920 str2的地址:0000000C7398F960*/return 0; }
2. 通過copy-on-write方式實現(xiàn)的mystring類
2.1 骨架與計數(shù)設(shè)計
2.2 構(gòu)造函數(shù)
2.3 拷貝構(gòu)造函數(shù)
2.4 析構(gòu)函數(shù)
2.5 拷貝賦值運算符
2.6 外部加鎖,內(nèi)部加鎖,寫時復制
- 外部加鎖:調(diào)用者負責,用調(diào)用者決定跨線程使用共享對象時的加鎖時機
- 內(nèi)部加鎖:對象將所有對自己的訪問串行化,通過每個成員函數(shù)加鎖的方法來實現(xiàn),這樣就不會在多線程中共享該對象時進行外部加鎖了。
2.7 通過指針修改mystring所指字符串的內(nèi)容
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include<memory>class MyString
{
public:MyString(const char* tmp=""):pvalue(new stringvalue(tmp)){//point = tmp;}MyString(const MyString& tmp) :pvalue(tmp.pvalue) // 拷貝構(gòu)造函數(shù){pvalue->refcount++;}MyString& operator=(const MyString& tmp) // 拷貝賦值運算符重載{/* if (&tmp == this)return *this;delete[] point;point = new char[strlen(tmp.point) + 1];strcpy(point,tmp.point);return *this;*/if (&tmp == this)return *this;//delete[] pvalue->point;--pvalue->refcount;//自己所指的引用計數(shù)減一if (pvalue->refcount == 0)delete pvalue; //把自己所指向的pvalue刪除pvalue = tmp.pvalue;pvalue->refcount++;return *this;}//const char& operator[](int idx)const // 非const 可以與const版本共存,但是都存在時都會調(diào)用非const版本的//{// return pvalue->point[idx];//}char& operator[](int idx) // const []{if (pvalue->refcount > 1){--pvalue->refcount;pvalue = new stringvalue(pvalue->point); // 寫時復制}return pvalue->point[idx];}~MyString(){pvalue->refcount--;if (pvalue->refcount == 0)delete pvalue;}
private://char* point;struct stringvalue{size_t refcount; // 引用計數(shù)char* point; stringvalue(const char* tmpstr){point = new char[strlen(tmpstr) + 1];strcpy(point,tmpstr);}~stringvalue(){delete[] point;}};
private:stringvalue* pvalue;
};
int main()
{return 0;
}