學習C++資料集合

學習C++

C++ 是一個難學易用的語言!
C++ 的難學,不僅在其廣博的語法,以及語法背後的語意,以及語意背後的深層思維,以及深層思維背後的物件模型;
C++ 的難學,還在於它提供了四種不同(但相輔相成)的程式設計思維模式:基于程序procedural-based,基于對象object-based,面向對象object-oriented,泛型思想/設計generic paradigm。
世上沒有白吃的午餐。又要有效率,又要有彈性,又要前瞻望遠,又要回溯相容,
又要能治大國,又要能烹小鮮,學習起來當然就不可能太簡單。C++ 相關書籍之多,車載斗量;如天上繁星,如過江之鯽。
廣博如四庫全書者有之(The C++ Programming Language、C++ Primer),
深奧如重山復水者有之(The Annotated C++ Reference Manual, Inside the C++ Object Model),
細說歷史者有之(The Design and Evolution of C++, Ruminations on C++),
獨沽一味者有之(Polymorphism in C++, Genericity in C++),
獨樹一幟者有之(Design Patterns,Large Scale C++ Software Design, C++ FAQs),
程式庫大全有之(The C++ Standard Library),
另辟蹊徑者有之(Generic Programming and the STL),
工程經驗之累積亦有之(Effective C++, More Effective C++, Exceptional C++)。
這其中,「工程經驗之累積」對已具 C++ 相當基礎的程式員而言,有著致命的吸引力與立竿見影的幫助。
Scott Meyers 的 Effective C++ 和 More Effective C++ 是此類佼佼,
Herb Sutter 的 Exceptional C++ 則是後起之秀。 

適合學生學習時能夠方便的在瀏覽器里直接編c++程序

參考資料

黑馬機器人—C++

雞啄米:C++編程入門系列之目錄和總結

++98基礎上學習C++11新特性

Effective Modern C++

C++ 入門教程

魚C工作室 C++快速入門

C++ Primer 5 代碼

C++設計成這樣的原因 《C++演化和設計》

boost庫學習

C++17 High Performance

C++17 STL Cookbook 代碼

C++ 響應式編程(Reactive Programming)

C++ Template 進階指南

C++17 高性能計算

CPP-Data-Structures-and-Algorithms

數據結構和算法動態可視化

VS2010/MFC編程入門教程之目錄和總結

cpp-tutor Code examples for tutoring modern C++ string Dynamic memory allocation C++ Unit testing Smart pointers

基礎議題

pointers(指針)
references(引用)
casts(類型轉換)
arrays(數組)
constructors(構造) default constructors(默認構造函數)

指針與引用的區別

指針與引用看上去完全不同(指針用操作符“*”和“->”,引用使用操作符“. ”),
但是它們似乎有相同的功能。指針與引用都是讓你間接引用其他對象。引用內部實現為常量指針
string& rs; // 錯誤,引用必須被初始化
string s("xyzzy");
string& rs = s; // 正確,rs 指向 s
指針沒有這樣的限制。
string *ps; // 未初始化的指針// 合法但危險

不存在指向空值的引用這個事實意味著使用引用的代碼效率比使用指針的要高。
因為在使用引用之前不需要測試它的合法性。

void printDouble(const double& rd)
{cout << rd; // 不需要測試 rd,它
} // 肯定指向一個 double 值// 相反,指針則應該總是被測試,防止其為空:
void printDouble(const double *pd)
{if (pd) { // 檢查是否為 NULLcout << *pd;}
}

指針與引用的另一個重要的不同是指針可以被重新賦值以指向另一個不同的對象。但是
引用則總是指向在初始化時被指定的對象,以后不能改變。

string s1("Nancy");
string s2("Clancy");
string& rs = s1; // rs 引用 s1
string *ps = &s1; // ps 指向 s1
rs = s2; // rs 仍舊引用 s1,// 但是 s1 的值現在是 "Clancy"ps = &s2; // ps 現在指向 s2;// s1 沒有改變 
在以下情況下你應該使用指針,一是你考慮到存在不指向任何對象的可能(在這種情況下,你能夠設置指針為空),二是你需要能夠在不同的時刻指向不同的對象(在這種情況下,你能改變指針的指向)。如果總是指向一個對象并且一旦指向一個對象后就不會改變指向,那么你應該使用引用。 

盡量使用 C++風格的類型轉換

仔細想想地位卑賤的類型轉換功能(cast),其在程序設計中的地位就象 goto 語句一樣令人鄙視。
但是它還不是無法令人忍受,因為當在某些緊要的關頭,類型轉換還是必需的,這時它是一個必需品。C風格的類型轉換,過于粗魯,能允許你在任何類型之間進行轉換.
C風格的類型轉換在程序語句中難以識別。在語法上,類型轉換由圓括號和標識符組成,而這些可以用在 Cpp中的任何地方。
C++通過引進四個新的類型轉換操作符克服了 C 風格類型轉換的缺點,這四個操作符是,static_cast,
const_cast,
dynamic_cast, 
和 reinterpret_cast。c強制類型轉換  (type) expression1. static_caststatic_cast<type>(expression) a. 基礎類型之間互轉。如:float轉成int、int轉成unsigned int等。int firstNumber, secondNumber;...double result = ((double)firstNumber)/secondNumber; // c風格 如果用上述新的類型轉換方法,你應該這樣寫:double result = static_cast<double>(firstNumber)/secondNumber;// c++風格static_cast 不能從表達式中去除 const 屬性,因為另一個新的類型轉換操作符 const_cast 有這樣的功能。const_cast 最普通的用途就是轉換掉對象的 const 屬性。b. 指針與void*之間互轉。如:float*轉成void*、CBase*轉成void*、函數指針轉成void*、void*轉成CBase*等c. 派生類指針【引用】轉成基類指針【引用】。如:Derive*轉成Base*、Derive&轉成Base&等d. 非virtual繼承時,可將基類指針【引用】轉成派生類指針【引用】(多繼承時,會做偏移處理)。如:Base*轉成Derive*、Base&轉成Derive&等
class Widget { ... };
class SpecialWidget: public Widget { ... };
void update(SpecialWidget *psw);
SpecialWidget sw; // sw 是一個非 const 對象。
const SpecialWidget& csw = sw; // csw 是 sw 的一個引用// 它是一個 const 對象
update(&csw); // 錯誤!不能傳遞一個 const SpecialWidget* 變量// 給一個處理 SpecialWidget*類型變量的函數// 2.const_cast<type>(expression)  轉換掉對象的 const 屬性====update(const_cast<SpecialWidget*>(&csw));// 正確,csw 的 const 被顯示地轉換掉(// csw 和 sw 兩個變量值在 update//函數中能被更新)
update((SpecialWidget*)&csw);// 同上,但用了一個更難識別//的 C 風格的類型轉換
Widget *pw = new SpecialWidget;
update(pw); // 錯誤!pw 的類型是 Widget*,但是 // update 函數處理的是 SpecialWidget*類型update(const_cast<SpecialWidget*>(pw));// 錯誤!const_cast 僅能被用在影響// constness or volatileness 的地方上。,// 不能用在向繼承子類進行類型轉換。 
3. dynamic_cast  dynamic_cast<type>(expression) 專門用于處理多態機制,對繼承體系內的對象(類中必須含有至少一個虛函數)的指針【引用】進行轉換,轉換時會進行類型檢查.它被用于安全地沿著類的繼承關系向下進行類型轉換。用 dynamic_cast 把指向基類的指針或引用轉換成指向其派生類或其兄弟類的指針或引用,而且你能知道轉換是否成功。失敗的轉換將返回空指針(當對指針進行類型轉換時)或者拋出異常(當對引用進行類型轉換時): 
Widget *pw; // 基類 對象 指針
...
update(dynamic_cast<SpecialWidget*>(pw));// 正確,傳遞給 update 函數一個指針// 是指向變量類型為 SpecialWidget 的 pw 的指針// 如果 pw 確實指向一個對象,// 否則傳遞過去的將使空指針。
void updateViaRef(SpecialWidget& rsw);
updateViaRef(dynamic_cast<SpecialWidget&>(*pw));//正確。 傳遞給 updateViaRef 函數// SpecialWidget pw 指針,如果 pw// 確實指向了某個對象// 否則將拋出異常 int firstNumber, secondNumber;
...
double result = dynamic_cast<double>(firstNumber)/secondNumber;// 錯誤!沒有繼承關系,想在沒有繼承關系的類型中進行轉換,你可能想到 static_cast。
const SpecialWidget sw;
...
update(dynamic_cast<SpecialWidget*>(&sw));// 錯誤! dynamic_cast 不能轉換掉 const。// 為了去除const,你總得用 const_cast。 
    4.reinterpret_cast   重新解釋reinterpret_cast <new_type> (expression)用來處理無關類型之間的轉換;它會產生一個新的值,這個值會有與原始參數(expressoin)有完全相同的比特位.字面意思:重新解釋(類型的比特位)a.從指針類型到一個足夠大的整數類型b.從整數類型或者枚舉類型到指針類型c.從一個指向函數的指針到另一個不同類型的指向函數的指針d.從一個指向對象的指針到另一個不同類型的指向對象的指針e.從一個指向類函數成員的指針到另一個指向不同類型的函數成員的指針f.從一個指向類數據成員的指針到另一個指向不同類型的數據成員的指針使用reinterpret_casts 的代碼很難移植。reinterpret_casts 的最普通的用途就是在函數指針類型之間進行轉換。
typedef void (*FuncPtr)(); // FuncPtr is 一個指向函數的指針,該函數沒有參數// 返回值類型為 void
FuncPtr funcPtrArray[10]; // funcPtrArray 是一個能容納10 個 FuncPtrs 指針的數組 //  如果要把一個指向下面函數的指針存入 funcPtrArray 數組:
// int doSomething();      // 你不能不經過類型轉換而直接去做,因為 doSomething 函數對于 funcPtrArray 數組來說有一個錯誤的類型。
// 在 FuncPtrArray 數組里的函數返回值是 void 類型,而 doSomething函數返回值是 int 類型。
funcPtrArray[0] = &doSomething; // 錯誤!類型不匹配
// reinterpret_cast 可以讓你迫使編譯器以你的方法去看待它們:
funcPtrArray[0] = // 可編譯通過reinterpret_cast<FuncPtr>(&doSomething);

可以用下面的宏替換來模擬新的類型轉換語法:

#define static_cast(TYPE,EXPR) ((TYPE)(EXPR))   // 后面為 c語言強轉方式
#define const_cast(TYPE,EXPR) ((TYPE)(EXPR))
#define reinterpret_cast(TYPE,EXPR) ((TYPE)(EXPR))

你可以象這樣使用使用:

double result = static_cast(double, firstNumber)/secondNumber; 
update(const_cast(SpecialWidget*, &sw));
funcPtrArray[0] = reinterpret_cast(FuncPtr, &doSomething); #define dynamic_cast(TYPE,EXPR) (TYPE)(EXPR)
// 請記住,這個模擬并不能完全實現 dynamic_cast 的功能,它沒有辦法知道轉換是否失敗。

不要對數組使用多態

類繼承的最重要的特性是你可以通過基類(父類) 指針或引用 來 操作 派生類(子類)。
多態和指針算法不能混合在一起來用,所以數組與多態也不能用在一起。 

避免無用的缺省構造函數

在一個完美的世界里,無需任何數據即可建立對象的類可以包含缺省構造函數,
而需要數據來建立對象的類則不能包含缺省構造函數。
唉!可是我們的現實世界不是完美的,所以我們必須考慮更多的因素。
特別是如果一個類沒有缺省構造函數,就會存在一些使用上的限制。 

C++類成員和數據成員初始化總結

C++為類中提供類成員的初始化列表
類對象的構造順序是這樣的:
1.分配內存,調用構造函數時,隱式/顯示的初始化各數據成員
2.進入構造函數后在構造函數中執行一般計算
規則:1.類里面的任何成員變量在定義時是不能初始化的。2.一般的數據成員可以在構造函數中初始化。3.const數據成員必須在構造函數的初始化列表中初始化。4.static要在類的定義外面初始化。   5.數組成員是不能在初始化列表里初始化的。6.不能給數組指定明顯的初始化。  這6條一起,說明了一個問題:C++里面是不能定義常量數組的!
因為3和5的矛盾。這個事情似乎說不過去啊?沒有辦法,我只好轉而求助于靜態數據成員。到此,我的問題解決。
但是我還想趁機復習一下C++類的初始化:1.初始化列表:CSomeClass::CSomeClass() : x(0), y(1){}2.類外初始化:int CSomeClass::myVar=3;3.const常量定義必須初始化,C++類里面使用初始化列表;4.C++類不能定義常量數組。

淺拷貝:只是將數據成員的值進行簡單的拷貝

深拷貝:在淺拷貝的基礎上,也將堆中的數據也進行拷貝

C++ 泛型技術 泛化技術 增加不確定性 通用性 靈活性

所謂泛型技術,說白了就是試圖使用不變的代碼來實現可變的算法

比如:模板技術,RTTI技術,虛函數技術

要么是試圖做到在編譯時決議,要么試圖做到運行時決議。

【【A】】 RTTI技術

RTTI(Run-Time Type Identification)是面向對象程序設計中一種重要的技術。

現行的C++標準對RTTI已經有了明確的支持。不過在某些情況下出于特殊的開發需要,

我們需要自己編碼來實現。本文介紹了一些關于RTTI的基礎知識及其原理和實現。

RTTI需求:

和很多其他語言一樣,C++是一種靜態類型語言。其數據類型是在編譯期就確定的,

不能在運行時更改。然而由于面向對象程序設計中多態性的要求,C++中的指針或引用

(Reference)本身的類型,可能與它實際代表(指向或引用)的類型并不一致。有時我們需

要將一個多態指針轉換為其實際指向對象的類型,就需要知道運行時的類型信息,

這就產生了運行時類型識別的要求。

  C++對RTTI的支持:C++提供了兩個關鍵字typeid(指示類型) 和dynamic_cast(類型強轉)和一個type_info類來支持RTTI

#############################################################
###【1】dynamic_cast操作符: 運行時強制類型轉換
它允許在運行時刻進行類型轉換,
從而使程序能夠在一個類層次結構安全地轉換類型。
dynamic_cast提供了兩種轉換方式,
把基類指針轉換成派生類指針,
或者把指向基類的左值轉換成派生類的引用。
見下例講述:

void company::payroll(employee *pe) {//指針//對指針轉換失敗,dynamic_cast返回NULLif(programmer *pm=dynamic_cast(pe)){  //基類 employee >>> 派生類 programmerpm->bonus(); }}
void company::payroll(employee &re) {//引用  變量別名try{//對引用轉換失敗的話,則會以拋出異常來報告錯誤programmer &rm = dynamic_cast(re);rm->bonus();
}catch(std::bad_cast){}
}

這里bonus是programmer的成員函數,基類employee不具備這個特性。

所以我們必須使用安全的由基類到派生類類型轉換,識別出programmer指針。

int a=1;int *p=&a;//指針是變量的地址    *p  定義時  和 在函數參數中時  是 表示指針變量   其他表示取值int a=1;int &b=a;//引用 是 變量別名     &放在左邊 以及在 函數參數中 是引用   方在右邊是 取地址

上面定義了一個整形變量和一個指針變量p,該指針變量指向a的存儲單元,

即p的值是a存儲單元的地址。

而下面2句定義了一個整形變量a和這個整形a的引用b,

事實上a和b是同一個東西,在內存占有同一個存儲單元。

區別:
【1】可以有const指針,但是沒有const引用;

【2】指針可以有多級,但是引用只能是一級(int **p;合法 而 int &&a是不合法的;

【3】指針的值可以為空,但是引用的值不能為NULL,并且引用在定義的時候必須初始化;

【4】指針的值在初始化后可以改變,即指向其它的存儲單元,而引用在進行初始化后就不會再改變了;

【5】"sizeof引用"得到的是所指向的變量(對象)的大小,而"sizeof指針"得到的是指針本身的大小;

【6】指針和引用的自增(++)運算意義不一樣;

【7】引用作為函數的參數進行傳遞,傳遞的是實參本身,不是實參的一個拷貝;

【8】 用指針傳遞參數,可實現對實參進行改變的目的,是因為傳遞過來的是實參的地址,但是指針不會改變。

#include<iostream>
using namespace std;void test(int *&p)//這里是 指針p的引用  ;如果是 *p 指針 p修改不了 可以修改p指向的內容
{int a=1;p=&a;//可以修改p   這里的& 是取地址cout<<p<<" "<<*p<<endl;//這里的*是取值
}int main(void)
{int *p=NULL;//這里的 *是 指針變量定義test(p);if(p!=NULL)cout<<"指針p不為NULL"<<endl;system("pause");return 0;
}

【2】typeid操作符:它指出指針或引用指向的對象的實際派生類型。

例如:

employee* pe=new manager;typeid(*pe) == typeid(manager) //等于truetypeid的返回是type_info類型。class type_info {private:type_info(const type_info&);type_info& operator=( const type_info& );public:virtual ~type_info();int operator==( const type_info& ) const;int operator!=( const type_info& ) const;const char* name() const;
};

##############################

【【B】】模板

【1】函數模板

函數的重載。例如:

int add(int a, int b)  
{  return a + b;  
}  
double add(double a, double b)  
{  return a + b;  
}  
char add(char a, char b)  
{  return a + b;  
}  

這些函數幾乎相同,每個函數的函數體是相同的,功能也是相同的,
它們之間唯一的不同在于形參的類型和函數返回值的類型。

C++有模板(template)機制,可以使用函數模板解決上述存在的問題。
函數模板(function template)是一個獨立于類型的函數,
可作為一種模式,產生函數的特定類型版本。

template<模板形參表>返回值類型 函數名(形式參數列表)  
{  函數體  
}  

模板形參表(template parameter list)是用一對尖括號<>括起來的
一個或多個模板形參的列表,不允許為空,形參之間以逗號分隔。

第一種形式如下所示:  <typename 類型參數名1,typename 類型參數名2,..>第二種形式如下所示:  <class 類型參數名1,class 類型參數名2,...> 

在函數模板形參表中,typename和class具有相同含義,可以互換使用,

或者兩個關鍵字都可以在同一模板形參表中使用。

不過由于C++中class關鍵字往往容易與類聯系在一起,

所以使用關鍵字typename比使用class更直觀,

typename可以更加直觀的反映出后面的名字是一個類型名。

模板定義的后面是函數定義,在函數定義中,可以使用模板形參表中的類型參數。

例如:
templateT add(T a, T b)
{
return a + b;
}

函數模板定義語法的含義是一個通用型函數,

這個函數類型和形參類型沒有具體的指定,而是一個類型記號表示

,類型記號由編譯器根據所用的函數而確定,這種通用型函數成為函數模板。

#include<iostream>  using namespace std;  template<typename T>T add(T a, T b)//函數模板
{  return a + b;  
}  int main()  
{  std::cout << "int_add    = "    << add(10,20)<< std::endl;  std::cout << "double_add = "    << add(10.2, 20.5) << std::endl;  std::cout << "char_add   = "    << add(10, 20) << std::endl;  std::system("pause");  return 0;  
}

【2】類模板

類似于函數模板的做法,類模板對數據成員的

數據類型和成員函數的參數類型進行泛化。

如下是類模板的一個基本定義形式,

關鍵字template說明類型T1~Tn是模本類型, typename 或 class關鍵字

成員函數可在類模板的聲明中定義。

template<class T1,class T2, ... ,class Tn> class 類名  
{  //數據成員聲明或定義;  
};  
template<class T1, class T2, ....., class Tn> 返回值 類名<T1,T2, ....., Tn>::成員函數1  
{  //函數定義  
}  
template<class T1, class T2, ....., class Tn> 返回值 類名<T1, T2, ....., Tn>::成員函數2  
{  //函數定義  }  

不同于非模板代碼的組織方式,函數模板和類模板的聲明和定義代碼,

一般都編寫在.h頭文件中,以免由于為具現而提示編譯鏈接錯誤。

下面給出一個類模板表示平面上點的示例:

template<class T> //【0】類模板定義 class Point       //【1】Point不是類名是模板名  {  public:  Point::x(0), y(0) {} //【2】默認構造函數  初始化為0 Point(const T a, const T b) :(x)(a), y(b) {}//【3】帶參數的構造函數    a賦值給x b賦值給y void Set(const T a, const T b);  void Display();  private:  T x;  T y;  
}; 

【【C】】虛函數技術

參考

C++中的虛函數的作用主要是實現了多態的機制。

關于多態,簡而言之就是用父類型別的指針指向其子類的實例

,然后通過父類的指針調用實際子類的成員函數。

這種技術可以讓父類的指針有“多種形態”.

class A{int a;// a的后面直接是虛函數,內存中存在為虛函數表指針    public:virtual void f();virtual void g(int);virtual void h(double);
};class B: public A{public:int b;void g(int);// 會覆蓋 A類的 A::gvirtual void m(B*);
};class C: public B{public:int c;void h(double);// 會覆蓋 B的父類A 的 A:hvirtual void n(C*);
}&C, 類C的實例的內存空間大概如下:
變量a
虛函數表指針vptr  ------>  |&A::f|&B::g|&C::h|&B::m||&C::n|.|  按繼承的先后順序存放函數
變量b
變量c

虛函數表

對C++ 了解的人都應該知道虛函數(Virtual Function)

是通過一張虛函數表(Virtual Table)來實現的。簡稱為V-Table。

這個表中,主是要一個類的虛函數的地址表,這張表解決了繼承、覆蓋的問題,

保證其容真實反應實際的函數。這樣,在有虛函數的類的實例中這個表被分配在了

這個實例的內存中,所以,當我們用父類的指針來操作一個子類的時候,

這張虛函數表就顯得由為重要了,它就像一個地圖一樣,指明了實際所應該調用的函數。

class Base {public:virtual void f() { cout << "Base::f" << endl; }virtual void g() { cout << "Base::g" << endl; }virtual void h() { cout << "Base::h" << endl; }};

我們可以通過Base的實例來得到虛函數表。 下面是實際例程:

        typedef void(*Fun)(void);//函數指針  Fun  void(void) 返回值為void、輸入值為voidBase b;// 定義Base類 的實例b Fun pFun = NULL;// 定義一個函數指針Fun 變量 pFun, 初始化為 NULLcout << "虛函數表地址:" << (int*)(&b) << endl;//  &b 取地址 (int*) 強制轉換成int類型的指針(表id)cout << "虛函數表 — 第一個函數地址:" << (int*)*(int*)(&b) << endl;// (int*)(&b) 虛函數表地址  *(int*)(&b) 取虛函數表地址 內的內容 為 虛函數地址  (int*) 強制轉換成int類型的指針pFun = (Fun)*((int*)*(int*)(&b));//得到第一個函數 *((int*)*(int*)(&b))pFun();

實際運行經果如下:

虛函數表地址:0012FED4

虛函數表 — 第一個函數地址:0044F148

Base::f

        (Fun)*((int*)*(int*)(&b)+0);  // Base::f()(Fun)*((int*)*(int*)(&b)+1);  // Base::g()(Fun)*((int*)*(int*)(&b)+2);  // Base::h()

|Base::f()|Base::g()|Base::h()|.|

對象的內存布局 和 虛函數表 :

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-N7M2PFXv-1691788331828)(https://github.com/Ewenwan/ShiYanLou/blob/master/learn_cpp/img/base_class_virtual_table.PNG)]

(&b) 對象的地址
(int*)(&b) 強行把&b 轉成int*,取得 虛函數表 的地址
(int*)*(int*)(&b)  解引用后再強轉 得到 虛函數 的地址注意:在上面這個圖中,我在虛函數表的最后多加了一個結點,
這是虛函數表的結束結點,就像字符串的結束符“/0”一樣,
其標志了虛函數表的結束。這個結束標志的值在不同的編譯器下是不同的。
在WinXP+VS2003下,這個值是NULL。
而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,這個值如果是1,
表示還有下一個虛函數表,如果值是0,表示是最后一個虛函數表。分別說明“無覆蓋”和“有覆蓋”時的虛函數表的樣子。
沒有覆蓋父類的虛函數是毫無意義的。
我之所以要講述沒有覆蓋的情況,主要目的是為了給一個對比。
在比較之下,我們可以更加清楚地知道其內部的具體實現。

【1】一般繼承(無虛函數覆蓋,子類中定義的函數名與父類中的不同)

假設有如下所示的一個繼承關系:

Derive 類 繼承 父類 Base

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-FUTF5a8J-1691788331829)(https://github.com/Ewenwan/ShiYanLou/blob/master/learn_cpp/img/inheritance_class.PNG)]

在這個繼承關系中,子類沒有重載任何父類的函數。那么,在派生類的實例中,

對于子類對象的實例:Derive d; 的虛函數表如下:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-AHQXR69Q-1691788331829)(https://github.com/Ewenwan/ShiYanLou/blob/master/learn_cpp/img/inheritance_class_virtual_table.PNG)]

我們可以看到下面幾點:

1)虛函數按照其聲明順序放于表中。

2)父類的虛函數在子類的虛函數前面。

|Base::f()|Base::g()|Base::h()|Derive::f1()|Derive::g1()|Derive::h1()|.|

【2】一般繼承(有虛函數覆蓋)

如果子類中有虛函數重載了父類的虛函數 f()

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-m4Brox1y-1691788331830)(https://github.com/Ewenwan/ShiYanLou/blob/master/learn_cpp/img/class_derive_over.PNG)]

1)子類覆蓋的f()函數被放到了虛表中原來父類虛函數的位置。

2)沒有被覆蓋的函數依舊。

|Derive::f1()|Base::g()|Base::h()|Derive::g1()|Derive::h1()|.|

這樣,我們就可以看到對于下面這樣的程序,

        Base *b = new Derive();// 父類指針Base* b 指向了子類 Derive()b->f();// 會調用子類中覆蓋了父類的同名的函數 Derive::f()

子類 Derive 的虛函數表:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-XK9hz81n-1691788331830)(https://github.com/Ewenwan/ShiYanLou/blob/master/learn_cpp/img/class_derive_over_virtual_table.PNG)]

由b所指的內存中的虛函數表的f()的位置已經被 Derive::f() 函數地址所取代,

于是在實際調用發生時,是Derive::f()被調用了。這就實現了多態。

【3】多重繼承(無虛函數覆蓋)

Derive 繼承 于 Base1 Base2 Base3

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-fQmajIkZ-1691788331830)(https://github.com/Ewenwan/ShiYanLou/blob/master/learn_cpp/img/class_derive_more_class.PNG)]

 |Base1::f()|Base1::g()|Base1::h()|Derive::f1()|Derive::g1()|Derive::h1()|.||Base2::f()|Base2::g()|Base2::h()|.||Base3::f()|Base3::g()|Base3::h()|.|

子類內存空間:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ZCrDKsHz-1691788331831)(https://github.com/Ewenwan/ShiYanLou/blob/master/learn_cpp/img/class_derive_more_class_virtual_table.PNG)]

我們可以看到:

1) 在子類內存空間中,存在每個父類的虛表。

2) 子類的成員函數被放到了第一個父類的表中。(所謂的第一個父類是按照聲明順序來判斷的)

這樣做就是為了解決不同的父類類型的指針指向同一個子類實例,而能夠調用到實際的函數。

【4】多重繼承(有虛函數覆蓋)

Derive 繼承 于 Base1 Base2 Base3 且有 同名函數 f()
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-UmheiTX3-1691788331831)(https://github.com/Ewenwan/ShiYanLou/blob/master/learn_cpp/img/class_derive_more_class_over.PNG)]

 |Derive::f()|Base1::g()|Base1::h()|Derive::g1()|Derive::h1()|.||Derive::f()|Base2::g()|Base2::h()|.||Derive::f()|Base3::g()|Base3::h()|.|

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-XkQawZoy-1691788331831)(https://github.com/Ewenwan/ShiYanLou/blob/master/learn_cpp/img/class_derive_more_class_over_virtual_table.PNG)]

三個父類虛函數表中的f()的位置被替換成了子類的函數指針。

這樣,我們就可以 用 任一靜態類型的父類來指向 子類,并調用子類的f()了。

如:

        Derive d;// 子類Base1 *b1 = &d;// 父類1的指針 b1 指向子類dBase2 *b2 = &d;// 父類2的指針 b2 指向子類dBase3 *b3 = &d;// 父類3的指針 b3 指向子類db1->f(); // Derive::f()  之類d的內存空間中 三個子類的 虛函數表 的第一個函數都是 Derive::f()b2->f(); // Derive::f()b3->f(); // Derive::f()b1->g(); //Base1::g()b2->g(); //Base2::g()b3->g(); //Base3::g()b1->g(); //Base1::h()b2->g(); //Base2::h()b3->g(); //Base3::h()

【5】安全性

一、通過父類型的指針訪問子類自己的虛函數 會出錯
      Base1 *b1 = new Derive();b1->g1();  //編譯出錯   g1() 為子類自己的虛函數b1->h1();  //編譯出錯   h1() 為子類自己的虛函數

任何妄圖使用父類指針想調用子類中的未覆蓋父類的成員函數的行為都會被編譯器視為非法。

二、訪問non-public (private 或者 protected)的虛函數

如果父類的虛函數是private或是protected的,但這些非public的虛函數同樣會存在于虛函數表中,

所以,我們同樣可以使用訪問虛函數表的方式來訪問這些non-public的虛函數,這是很容易做到的。

class Base {private:virtual void f() { cout << "Base::f" << endl; } 
};
class Derive : public Base{// 子類Derive 繼承 于 Base父類
};
typedef void(*Fun)(void);  // 函數指針void main() {Derive d;Fun  pFun = (Fun)*((int*)*(int*)(&d)+0);//通過虛函數指針 調用父類的 私有虛函數pFun();// 會打印 Base::f
}

下面是一個關于多重繼承的虛函數表訪問的例程:
多重繼承的虛函數表訪問的例程

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/36485.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/36485.shtml
英文地址,請注明出處:http://en.pswp.cn/news/36485.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

第五十三天

●剪輯——Pr 剪輯(Film editing)&#xff0c;即將影片制作中所拍攝的大量素材&#xff0c;經過選擇、取舍、分解與組接&#xff0c;最終完成一個連貫流暢、含義明確、主題鮮明并有藝術感染力的作品。 ?線性編輯 將素材按時間順序連接成新的連續畫面的技術 ?非線性編輯 …

Unity zSpace 開發

文章目錄 1.下載 zSpace 開發環境1.1 zCore Unity Package1.2 zView Unity Package 2. 導入工程3. 發布設置4.功能實現4.1 用觸控筆來實現對模型的拖拽&#xff1a; 5. 后續更新 1.下載 zSpace 開發環境 官網地址 1.1 zCore Unity Package zSpace 開發核心必須 1.2 zView …

MyBatis EntityWrapper DISTINCTQU用法

使用EntityWrapper進行DISTINCT查詢可以通過以下幾個步驟實現&#xff1a; 創建一個EntityWrapper對象&#xff1a;EntityWrapper<T> wrapper new EntityWrapper<>();使用setSqlSelect方法設置需要查詢的字段&#xff0c;并在字段前加上DISTINCT關鍵字&#xff0…

機器人CPP編程基礎-05完結The End

非常不可思議……之前四篇博文竟然有超過100的閱讀量…… 此文此部分終結&#xff0c;沒有繼續寫下去的必要了。 插入一個分享&#xff1a; 編程基礎不重要了&#xff0c;只要明確需求&#xff0c;借助AI工具就能完成一個項目。 當然也不是一次成功&#xff0c;工具使用也需要…

ORA-01539: 表空間 ‘GSC004‘ 未聯機

1.檢查表空間狀態&#xff1a; SELECT tablespace_name, status FROM dba_tablespaces WHERE tablespace_name GSC004;2.聯機表空間&#xff1a;如果表空間處于離線狀態&#xff0c;可以嘗試將其聯機。 ALTER TABLESPACE GSC004 ONLINE;3.檢查表空間文件&#xff0c;確保表空…

代碼隨想錄第48天 | 198. 打家劫舍、213. 打家劫舍II、337. 打家劫舍III

198. 打家劫舍 當前房屋偷與不偷取決于 前一個房屋和前兩個房屋是否被偷了。 遞歸五部曲&#xff1a; dp[i]&#xff1a;考慮下標i&#xff08;包括i&#xff09;以內的房屋&#xff0c;最多可以偷竊的金額為dp[i]。決定dp[i]的因素就是第i房間偷還是不偷。 如果偷第i房間&…

計算機視覺一 —— 介紹與環境安裝

傲不可長 欲不可縱 樂不可極 志不可滿 一、介紹 研究理論和應用 - 研究如何使機器“看”的科學 - 讓計算機具有人類視覺的所有功能 - 讓計算機從圖像中&#xff0c;提取有用的信息&#xff0c;并解釋 - 重構人眼&#xff1b;重構視覺皮層&#xff1b;重構大腦剩余部分 計…

利用ChatGPT繪制思維導圖——以新能源汽車競品分析報告為例

隨著人們對環境保護的日益關注和傳統燃油汽車的限制&#xff0c;全球范圍內對新能源汽車的需求不斷增長。新能源汽車市場的激烈競爭使得了解各個競品的特點和優劣成為關鍵。然而&#xff0c;針對這一領域的詳盡競品分析卻常常需要大量時間和精力。 在此背景下&#xff0c;人工智…

nestjs:nginx反向代理服務器后如何獲取請求的ip地址

問題&#xff1a; 如題 參考&#xff1a; nodejsnginx獲取真實ip-騰訊云開發者社區-騰訊云 「轉」從限流談到偽造 IP nginx remote_addr 解決辦法&#xff1a; 1.設置nginx 對于代理部分&#xff0c;對http header添加Host、X-Real-IP、X-Forwarded-For&#xff08;最重要&…

【SA8295P 源碼分析】69 - Android 側添加支持 busybox telnetd 服務

【SA8295P 源碼分析】69 - Android 側添加支持 busybox telnetd 服務 一、下載 busybox-1.36.1.tar.bz2 源碼包二、編譯 busybox 源碼三、將編譯后的 busybox 打包編入Android 鏡像中系列文章匯總見:《【SA8295P 源碼分析】00 - 系列文章鏈接匯總》 本文鏈接:《【SA8295P 源碼…

Qt+C++自定義控件儀表盤動畫仿真

程序示例精選 QtC自定義控件儀表盤動畫仿真 如需安裝運行環境或遠程調試&#xff0c;見文章底部個人QQ名片&#xff0c;由專業技術人員遠程協助&#xff01; 前言 這篇博客針對<<QtC自定義控件儀表盤動畫仿真>>編寫代碼&#xff0c;代碼整潔&#xff0c;規則&…

2023國賽數學建模D題思路分析

文章目錄 0 賽題思路1 競賽信息2 競賽時間3 建模常見問題類型3.1 分類問題3.2 優化問題3.3 預測問題3.4 評價問題 4 建模資料 0 賽題思路 &#xff08;賽題出來以后第一時間在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 競賽信息 全國大學生數學建模…

P8605 [藍橋杯 2013 國 AC] 網絡尋路

X 國的一個網絡使用若干條線路連接若干個節點。節點間的通信是雙向的。某重要數據包&#xff0c;為了安全起見&#xff0c;必須恰好被轉發兩次到達目的地。該包可能在任意一個節點產生&#xff0c;我們需要知道該網絡中一共有多少種不同的轉發路徑。 源地址和目標地址可以相同…

PHP原生類

什么是php原生類 原生類就是php內置類&#xff0c;不用定義php自帶的類&#xff0c;即不需要在當前腳本寫出&#xff0c;但也可以實例化的類 我們可以通過腳本找一下php原生類 <?php $classes get_declared_classes(); foreach ($classes as $class) {$methods get_clas…

網頁touch-action禁用手勢

問題 在iphone的safari 下滑網頁時&#xff0c;無法下滑 原因&#xff1a; 設置了touch-action: none html ,body {touch-action: none;}此行代碼為禁用手勢&#xff0c;但是在pc上Chrome瀏覽器模擬手機時&#xff0c;并不能生效(禁止手勢)。 但是此行代碼在iphone手機瀏覽器…

【Go 基礎篇】Go語言字符類型:解析字符的本質與應用

介紹 字符類型是計算機編程中用于表示文本和字符的數據類型&#xff0c;是構建字符串的基本單位。在Go語言&#xff08;Golang&#xff09;中&#xff0c;字符類型具有獨特的特點和表示方式&#xff0c;包括Unicode編碼、字符字面值以及字符操作。本篇博客將深入探討Go語言中的…

首次使用ninja的體驗

首先總結說自己的理解&#xff0c;就是NINJA是一個和MAKE同一級別的編譯工具&#xff0c;在CMAKE/GRADLE等工具之下工作 cmake目前可以生成makefile&#xff0c;也可以生成ninja文件(CMAKE選項中增加了-G Ninja&#xff09; 使用ninja all編譯生成的ninja文件 1.工具準備&…

Element組件淺嘗輒止5:Empty 空狀態組件

Empty空狀態組件&#xff1a;空狀態時的占位提示。 如第一次進入當前功能模塊時&#xff0c;數據狀態為空&#xff0c;則展示空狀態&#xff0c;可用到Empty組件 1.How? <el-empty description"描述文字"></el-empty> 2.自定義圖片 通過設置 image 屬…

plsql開發中動態sql的使用教程(不使用dbms_sql包)

一般的PL/SQL程序設計中&#xff0c;在DML和事務控制的語句中可以直接使用SQL&#xff0c;但是對于新建存儲過程&#xff0c;其中涉及傳參要被應用為列名時&#xff0c;不能在PL/SQL中直接使用&#xff0c;一會兒下面舉例介紹&#xff0c;那么要想實現設計的功能&#xff0c;可…

PyTorch Lightning教程七:可視化

本節指導如何利用Lightning進行可視化和監控模型 為何需要跟蹤參數 在模型開發中&#xff0c;我們跟蹤感興趣的值&#xff0c;例如validation_loss&#xff0c;以可視化模型的學習過程。模型開發就像駕駛一輛沒有窗戶的汽車&#xff0c;圖表和日志提供了窗口&#xff0c;讓我們…