C++——C++重點知識點復習2(詳細復習模板,繼承)

目錄

模板

函數模板

類模板

非類型模板參數

模板的特化

函數模板特化

類模板的特化

為什么普通函數可以分離?

繼承

繼承概念

基類和派生類對象賦值轉換(切割,切片)

隱藏

派生類的默認成員函數

.復雜的菱形繼承及菱形虛擬繼承

繼承的總結和反思


模板

函數模板

函數模板的概念:

函數模板代表了一個函數家族,該函數模板與類型無關,在使用時被參數化,根據實參類型產生函數的特定類型版本

模板函數的語法格式:

注意:typename是用來定義模板參數關鍵字,也可以使用class(切記:不能使用struct代替class)
template<typename T,typename V>
void Func(T  t,V  v)
{// ...
}

函數模板的原理:

函數模板是一個藍圖,它本身并不是函數,是編譯器用使用方式產生特定具體類型函數的模具。所以其實模板就是將本來應該我們做的重復的事情交給了編譯器

在編譯器編譯階段,對于模板函數的使用,編譯器需要根據傳入的實參類型來推演生成對應類型的函數以供調用。比如:當用double類型使用函數模板時,編譯器通過對實參類型的推演,將T確定為double類型,然后產生一份專門處理double類型的代碼,對于字符類型也是如此。
函數模板的實例化:
隱式實例化:讓編譯器根據實參推演模板參數的實際類型
顯式實例化:在函數名后的<>中指定模板參數的實際類型

類模板

類模板的概念:

類模板與函數模板一樣代表了一個類家族,該類模板與類型無關,在使用時需要顯示實例化。

類模板的定義格式:

template<class T1, class T2, ..., class Tn>
class 類模板名
{// 類內成員定義
};

類模板的實例化

類模板實例化與函數模板實例化不同,類模板實例化需要在類模板名字后跟<>,然后將實例化的類型放在<>中即可,類模板名字不是真正的類,而實例化的結果才是真正的類.

非類型模板參數

模板參數分類類型形參與非類型形參
類型形參即:出現在模板參數列表中,跟在class或者typename之類的參數類型名稱
非類型形參:就是用一個常量作為類(函數)模板的一個參數,在類(函數)模板中可將該參數當成常量來使用
namespace bite
{// 定義一個模板類型的靜態數組template<class T, size_t N = 10>class array{public:T& operator[](size_t index){return _array[index];}const T& operator[](size_t index)const{return _array[index];}size_t size()const{return _size;}bool empty()const{return 0 == _size;}private:T _array[N];size_t _size;};
}
注意:
1. 浮點數、類對象以及字符串是不允許作為非類型模板參數的
2. 非類型的模板參數必須在編譯期就能確認結果

模板的特化

概念:通常情況下,使用模板可以實現一些與類型無關的代碼,但對于一些特殊類型的可能會得到一些錯誤的結果,需要特殊處理,比如:實現了一個專門用來進行小于比較的函數模板。

函數模板特化

函數模板的特化步驟:
1. 必須要先有一個基礎的函數模板
2. 關鍵字template后面接一對空的尖括號<>
3. 函數名后跟一對尖括號,尖括號中指定需要特化的類型
4. 函數形參表:?必須要和模板函數的基礎參數類型完全相同,如果不同編譯器可能會報一些奇怪的錯誤。
template<class T>//針對所有類型
bool Less(T left, T right)
{return left < right;
}template<> //對于int類型的特化,當傳入int類型時,會執行該函數
bool Less<int>(int left,int right)
{return left<right
}
注意:一般情況下如果函數模板遇到不能處理或者處理有誤的類型,為了實現簡單通常都是將該函數直接給出,通過函數重載來實現。

類模板的特化

1.全特化:將模板參數列表中所有的參數都確定化

template<class T1, class T2>
class Data
{
public:Data() {cout<<"Data<T1, T2>" <<endl;}
private:T1 _d1;T2 _d2;
};
template<>
class Data<int, char>
{
public:Data() {cout<<"Data<int, char>" <<endl;}
private:int _d1;char _d2;
}

2.偏特化:任何針對模版參數進一步進行條件限制設計的特化版本。比如對于以下模板類:

template<class T1, class T2>
class Data
{
public:Data() {cout<<"Data<T1, T2>" <<endl;}
private:T1 _d1;T2 _d2;
};

偏特化有兩種表現方式:

1 部分特化,將模板參數類表中的一部分參數特化

// 將第二個參數特化為int
template <class T1>
class Data<T1, int>
{
public:Data() {cout<<"Data<T1, int>" <<endl;}
private:T1 _d1;int _d2;
};

2.參數跟進一步的限制

偏特化并不僅僅是指特化部分參數,而是針對模板參數更進一步的條件限制所設計出來的一個特化版本。可以是指針,或者引用等等類型。
//兩個參數偏特化為指針類型
template <typename T1, typename T2>
class Data <T1*, T2*>
{ 
public:Data() {cout<<"Data<T1*, T2*>" <<endl;}private:
T1 _d1;T2 _d2;
};

模板聲明和定義不支持分離寫道.h,和.cpp中

原因:編譯器在編譯時,是對每個文件進行獨立編譯,如果分開定義,我們在編譯時由于是模板,沒有實例化,編譯器不知道該如何生成這段代碼,所以不會編譯這段代碼,在調用時由于沒有具體的函數地址,則無法被編譯器所識別。

// add.h(聲明,放頭文件)
template <typename T>
T add(T a, T b);  // 只聲明,不定義// add.cpp(定義,放源文件)
#include "add.h"
template <typename T>
T add(T a, T b) {  // 定義實現return a + b;
}// main.cpp(使用模板)
#include "add.h"
int main() {add(1, 2);  // 嘗試使用 add<int>return 0;
}
  1. 編譯?main.cpp?時,編譯器無法生成?add<int>
    main.cpp?只包含?add.h,只能看到模板的聲明,看不到?add.cpp?中的定義。沒有完整定義,編譯器不知道如何生成?add<int>?的實際代碼,只能暫時記下 “需要一個?add<int>?函數”。

  2. 編譯?add.cpp?時,編譯器也不會生成?add<int>
    add.cpp?中有模板的完整定義,但編譯器不知道?main.cpp?會用?int?類型實例化它。C++ 標準規定:編譯器不會主動為模板生成所有可能的實例(因為類型是無限的,比如?intdoublestring?等),只會在看到具體實例化請求時才生成。

  3. 鏈接階段報錯
    鏈接器需要把?main.cpp?中對?add<int>?的引用和實際代碼關聯起來,但此時兩個目標文件中都沒有?add<int>?的二進制指令,因此會報 “未定義的引用” 錯誤。

為什么普通函數可以分離?

普通函數(非模板)的聲明和定義可以分離,因為:

?
  • 編譯器在編譯?.cpp?時會為普通函數生成完整的二進制指令(比如?int add(int a, int b)?的代碼會直接生成在?add.o?中)。
  • 鏈接時,main.o?中對?add?的引用可以直接找到?add.o?中的二進制代碼。
?

而模板函數沒有 “默認生成” 的代碼,必須等待具體類型的實例化請求,這就要求實例化時必須能看到完整定義,所以我們建議將模板的聲明和定義放到一個文件中

模板總結
【優點】
1. 模板復用了代碼,節省資源,更快的迭代開發,C++的標準模板庫(STL)因此而產生
2. 增強了代碼的靈活性?
【缺陷】
1. 模板會導致代碼膨脹問題,也會導致編譯時間變長
2. 出現模板編譯錯誤時,錯誤信息非常凌亂,不易定位錯誤

繼承

繼承概念

繼承(inheritance)機制是面向對象程序設計使代碼可以復用的最重要的手段,它允許程序員在持原有類特性的基礎上進行擴展,增加功能,這樣產生新的類,稱派生類。繼承呈現了面向對象程序設計的層次結構,體現了由簡單到復雜的認知過程。以前我們接觸的復用都是函數復用,承是類設計層次的復用
意義:繼承的核心是 **“復用已有”+“擴展特有”**,它通過建立類之間的層次關系,減少了代碼冗余,提高了可維護性,并為多態提供了基礎,是面向對象編程中實現抽象和封裝的重要手段。

基類和派生類對象賦值轉換(切割,切片)

派生類對象 可以賦值給 基類的對象 / 基類的指針 / 基類的引用。這里有個形象的說法叫切片或者切割。寓意把派生類中父類那部分切來賦值過去。

基類對象不能賦值給派生類對象。

基類的指針或者引用可以通過強制類型轉換賦值給派生類的指針或者引用。但是必須是基類的指針是指向派生類對象時才是安全的。這里基類如果是多態類型,可以使用RTTI(RunTime Type Information)的dynamic_cast 來進行識別后進行安全轉換

隱藏

1. 在繼承體系中基類派生類都有獨立的作用域

2. 子類和父類中有同名成員,子類成員將屏蔽父類對同名成員的直接訪問,這種情況叫隱藏,也叫重定義。(在子類成員函數中,可以使用 基類::基類成員 顯示訪問)且不構成重寫

3. 需要注意的是如果是成員函數的隱藏,只需要函數名相同就構成隱藏。

4. 注意在實際中在繼承體系里面最好不要定義同名的成員

派生類的默認成員函數

1. 派生類的構造函數必須調用基類的構造函數初始化基類的那一部分成員。如果基類沒有默認

的構造函數,則必須在派生類構造函數的初始化列表階段顯示調用。

2. 派生類的拷貝構造函數必須調用基類的拷貝構造完成基類的拷貝初始化。

3. 派生類的operator=必須要調用基類的operator=完成基類的復制。

4. 派生類的析構函數會在被調用完成后自動調用基類的析構函數清理基類成員。因為這樣才能

保證派生類對象先清理派生類成員再清理基類成員的順序。

5. 派生類對象初始化先調用基類構造再調派生類構造。

6. 派生類對象析構清理先調用派生類析構再調基類的析構。

7. 因為后續一些場景析構函數需要構成重寫,重寫的條件之一是函數名相同那么編譯器會對析構函數名進行特殊處理,處理成destrutor(),所以父類析構函數不加virtual的情況下,子類析構函數和父類析構函數構成隱藏關系。

.復雜的菱形繼承及菱形虛擬繼承

單繼承:一個子類只有一個直接父類時稱這個繼承關系為單繼承
多繼承:一個子類有兩個或以上直接父類時稱這個繼承關系為多繼承
菱形繼承:菱形繼承是多繼承的一種特殊情況。

菱形繼承的問題:從下面的對象成員模型構造,可以看出菱形繼承有數據冗余和二義性的問題。Assistant的對象中Person成員會有兩份。
class Person
{string _name;
}:class Student :public Person
{int _num
};class Teacher : public Person
{int _id;
};class Assitant: public Student,public Teacher
{stirng _majorCourse;
};void Test ()
{// 這樣會有二義性無法明確知道訪問的是哪一個Assistant a ;
a._name = "peter";
// 需要顯示指定訪問哪個父類的成員可以解決二義性問題,但是數據冗余問題無法解決a.Student::_name = "xxx";a.Teacher::_name = "yyy";
}
虛擬繼承可以解決菱形繼承的二義性和數據冗余的問題。如上面的繼承關系,在Student和Teacher的繼承Person時使用虛擬繼承,即可解決問題。需要注意的是,虛擬繼承不要在其他方去使用。
class Person
{
public :string _name ; // 姓名
};
class Student : virtual public Person
{
protected :int _num ; //學號
};
class Teacher : virtual public Person
{
protected :int _id ; // 職工編號
};
class Assistant : public Student, public Teacher
{
protected :string _majorCourse ; // 主修課程
};
void Test ()
{Assistant a ;a._name = "peter";
}
虛擬繼承解決數據冗余和二義性的原理
為了研究虛擬繼承原理,我們給出了一個簡化的菱形繼承繼承體系,再借助內存窗口觀察對象成
員的模型
class A
{
public:int _a;
};
// class B : public A
class B : virtual public A
{
public:int _b;
};
// class C : public A
class C : virtual public A
{
public:int _c;
};
class D : public B, public C
{
public:int _d;
};
int main()
{D d;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;return 0;
}

下圖是菱形繼承的內存對象成員模型:可以發現有數據冗余問題

下圖是菱形虛擬繼承的內存對象成員模型:這里可以分析出D對象中將A放到的了對象組成的最下面,這個A同時屬于BC,那么BC如何去找到公共的A呢?這里是通過了BC的兩個指針,指向的一張表。這兩個指針叫虛基表指針,這兩個表叫虛基表。虛基表中存的偏移量。通過偏移量可以找到下面的A

繼承的總結和反思

1. 很多人說C++語法復雜,其實多繼承就是一個體現。有了多繼承,就存在菱形繼承,有了菱形繼承就有菱形虛擬繼承,底層實現就很復雜。所以一般不建議設計出多繼承,一定不要設計出菱形繼承。否則在復雜度及性能上都有問題。

2. 多繼承可以認為是C++的缺陷之一,很多后來的OO語言都沒有多繼承,如Java

3. 繼承和組合

public繼承是一種is-a的關系。也就是說每個派生類對象都是一個基類對象。

組合是一種has-a的關系。假設B組合了A,每個B對象中都有一個A對象。

先使用對象組合,而不是類繼承 。

繼承允許你根據基類的實現來定義派生類的實現。這種通過生成派生類的復用通常被稱為白箱復用(white-box reuse)。術語“白箱”是相對可視性而言:在繼承方式中,基類的內部細節對子類可見 。繼承一定程度破壞了基類的封裝,基類的改變,對派生類有很大的影響。派生類和基類間的依賴關系很強,耦合度高。

對象組合是類繼承之外的另一種復用選擇。新的更復雜的功能可以通過組裝或組合對象來獲得。對象組合要求被組合的對象具有良好定義的接口。這種復用風格被稱為黑箱復用(black-box reuse),因為對象的內部細節是不可見的。對象只以“黑箱”的形式出現。組合類之間沒有很強的依賴關系,耦合度低。優先使用對象組合有助于你保持每個類被封裝。

實際盡量多去用組合。組合的耦合度低,代碼維護性好。不過繼承也有用武之地的,有些關系就適合繼承那就用繼承,另外要實現多態,也必須要繼承。類之間的關系可以用繼承,可以用組合,就用組合。

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

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

相關文章

python 項目編號 2025821 有關于中英文數據的收集、處理

python專欄記錄&#xff1a;前言 批量讀取單詞 JSON 文件 → 解析出單詞、釋義、例句、短語 → 數據清洗&#xff08;去掉特殊符號&#xff09; → 同步更新到 MySQL 數據庫。 內容 import json import pymysql import re import time from pymysql.converters import escape_s…

Document Solutions .NET Bundle 8.2.0

Document Solutions .NET Bundle 8.2.0MESCIUS 的 Document Solutions .NET Bundle 是一套完整的 API 和查看工具&#xff0c;可增強文檔處理并提高效率。它包含 Excel、Word、PDF 和圖像文檔&#xff0c;以及 PDF 查看器、數據查看器和圖像查看器的標準許可證。它將強大的 .NE…

在職老D滲透日記day20:sqli-labs靶場通關(第27關)get報錯注入 過濾select和union ‘閉合

5.27.第27關 get報錯注入 過濾select和union 閉合function blacklist($id) { $id preg_replace(/[\/\*]/,"", $id); //strip out /* $id preg_replace(/[--]/,"", $id); //Strip out --. $id preg_replace(/[#]/,"", $id); //Strip out #. $…

Go 并發編程-channel

channel 文章目錄channel簡介基本概念類型表示法值表示法操作的特性初始化通道接收元素值Happens before發送值例1核心組件關鍵執行順序輸出示例&#xff08;可能順序&#xff09;設計要點例2例3關閉通道長度與容量單向通道主要用途增強代碼表達性和安全性&#xff08;最重要的…

開源和免費一樣嗎?以商城系統為例為您分析~

開源和免費并不完全一樣&#xff0c;二者在核心定義、權利范圍和實際應用中存在顯著區別&#xff0c;具體可以從以下幾個方面理解&#xff1a; 1. 核心定義不同開源&#xff08;Open Source&#xff09;&#xff1a; 指軟件的源代碼是公開可獲取的&#xff0c;任何人都可以查看…

CMOS知識點 MOS管飽和區電流公式

知識點16&#xff1a;同上篇一樣&#xff0c;MOS管主要有3個工作區域&#xff1a;截止區&#xff08;Cut-off Region&#xff09;&#xff1a; < &#xff0c;沒有溝道形成&#xff0c;幾乎沒有電流。線性區/三極管區&#xff08;Triode Region&#xff09;&#xff1a; &g…

【集合框架LinkedList底層添加元素機制】

在 Java 集合框架中&#xff0c;LinkedList 與 ArrayList 是兩種截然不同的線性表實現。如果說 ArrayList 像一個可以伸縮的“盒子陣列”&#xff0c;那么 LinkedList 就像一條由“節點”串聯而成的“雙向鏈條”。今天&#xff0c;我們將深入 LinkedList 的源碼&#xff0c;一步…

《P2700 逐個擊破》

題目背景三大戰役的平津戰場上&#xff0c;傅作義集團在以北平、天津為中心&#xff0c;東起唐山西至張家口的鐵路線上擺起了一字長蛇陣&#xff0c;并企圖在潰敗時從海上南逃或向西逃竄。為了就地殲敵不讓其逃走&#xff0c;指揮官制定了先切斷敵人東西兩頭退路然后再逐個殲滅…

C6.0:晶體管放大器的原理與應用(基極偏置篇)

將晶體管Q點偏置在負載線中點附近后&#xff0c;如果將一個小的交流信號耦合到基極上&#xff0c;便會產生一個交流的集電極電壓&#xff0c;交流集電極電壓與交流基極電壓波形相似&#xff0c;但是幅度要大了很多&#xff0c;即交流集電極電壓是對交流基極電壓的放大。本篇學習…

Oracle: cannot decrease column length because some value is too big

1.背景今天項目上查不到數據,查庫發現默認20位的字段被改為了200,用的還是char類型&#xff0c;填充了一堆空格 2.知識LENGTH() 函數用于計算字符串字段 長度TRIM() 函數用于去除字符串字段 column 前后的空格&#xff08;默認&#xff09;或指定字符&#xff1a;SUBSTR() 用于…

Elasticsearch 寫入全鏈路:從單機到集群

0. 先把術語擺正 Index&#xff08;索引&#xff09;&#xff1a;邏輯數據集合&#xff0c;≈ MySQL 的庫。Document&#xff08;文檔&#xff09;&#xff1a;一條 JSON 數據&#xff0c;≈ MySQL 的行。Field&#xff08;字段&#xff09;&#xff1a;文檔里的鍵值&#xff0…

Java多線程編程——基礎篇

目錄 前言 一、進程與線程 1、進程 2、線程 二、并發與并行 1、并發 2、并行 三、線程調度 1、CPU時間片 2、調度方式 ①時間片輪轉 ②搶占式調度 四、線程實現方式 1、繼承 Thread 類 Thread的多種構造函數&#xff1a; 2、實現 Runnable 接口 五、線程的核心方法 1、start() …

阿里云的centos8 服務器安裝MySQL 8.0

在 CentOS 8 上安裝 MySQL 8.0 可以通過添加 MySQL 官方 YUM 倉庫并使用 dnf 命令安裝。以下是具體步驟&#xff1a; 步驟如下&#xff1a; 下載并添加 MySQL 官方 YUM 倉庫 運行以下命令下載 MySQL 8.0 的 YUM 倉庫配置文件&#xff1a; sudo dnf install https://dev.mysql.…

【運維進階】Linux 正則表達式

Linux 正則表達式定義&#xff1a;正則表達式是一種pattern&#xff08;模式&#xff09;&#xff0c;用于與待搜索字符串匹配&#xff0c;以查找一個或多個目標字符串。組成&#xff1a;自成體系&#xff0c;由兩類字符構成普通字符&#xff1a;未被顯式指定為元字符的所有可打…

STM32輸入捕獲相位差測量技術詳解(基于TIM1復位模式)

本文將深入解析基于STM32定時器輸入捕獲功能的方波相位差測量技術&#xff0c;通過復位模式實現高精度相位檢測。以下是完整的代碼實現與詳細原理分析。一、相位差測量原理相位差測量基于兩個同頻方波信號下降沿時間差計算。核心原理&#xff1a;?復位模式?&#xff1a;將TIM…

什么是股指期貨可轉移阿爾法策略?

阿爾法&#xff08;Alpha&#xff09;是投資領域的一個術語&#xff0c;用來衡量投資組合的超額收益。簡單來說&#xff0c;阿爾法就是你在市場上賺的比平均水平多出來的那部分錢。比如&#xff0c;市場平均收益率是5%&#xff0c;但你的投資組合收益率是10%&#xff0c;那你的…

AXI GPIO S——ZYNQ學習筆記10

AXI GPIO 同意通道混合輸入輸出中斷控制#KEY set_property IOSTANDARD LVCMOS18 [get_ports {AXI_GPIO_KEY_tri_io[0]}] set_property PACKAGE_PIN J13 [get_ports {AXI_GPIO_KEY_tri_io[0]}] set_property IOSTANDARD LVCMOS18 [get_ports {AXI_GPIO_KEY_tri_io[1]}] set_pro…

如何通過傳感器選型優化,為設備壽命 “續航”?

在當今競爭激烈的工業領域&#xff0c;企業就像在一場沒有硝煙的戰爭中角逐&#xff0c;設備便是企業的“秘密武器”。設備的使用壽命&#xff0c;如同武器的耐用程度&#xff0c;直接決定了企業在生產戰場上的“戰斗力”。延長設備壽命&#xff0c;已然成為眾多企業降低生產成…

WebSocket連接的例子

// 初始化WebSocket連接 const initWebSocket () > {console.log("初始化鏈接中...")const websocketUrl ws://61.54.84.16:9090/;// WebSocket服務器地址websocket new WebSocket(websocketUrl)//使用真實的webscket// websocket new MockWebSocket(websocket…

c++之指針和引用

一 使用場景 C++ 什么時候使用指針?什么時候使用引用?什么時候應該按值傳遞?_引用什么時候用比較好-CSDN博客 只使用傳遞過來的值,而不對值進行修改 需要修改傳遞過來的值 內置數據類型 按值傳遞(小型結構) 指針傳遞 數組 指針傳遞 指針傳遞 結構 指針或引用(較大的結構…