C++ 泛型編程(二):非類型模板參數,模板特化,模板的分離編譯

文章目錄

  • 非類型模板參數
  • 函數模板的特化
  • 類模板的特化
    • 全特化
    • 偏特化
      • 部分參數特化
      • 參數修飾特化
  • 模板分離編譯
    • 解決方法


非類型模板參數

模板的參數分為兩種:

  • 類型參數: 則是我們通常使用的方式,就是在模板的參數列表中在 class 后面加上參數的類型名稱。
  • 非類型參數: 則是用一個常量作為模板的參數,在模板中可以當作常量來使用,通常是需要指明大小或者初始化內容的才會用到。

類型參數我們在上一篇博客中講的很詳細了,不再贅述。而非類型參數比較常見的就是 c++ 中的 array
在這里插入圖片描述
array 的底層就是直接使用的數組,而數組創建時必須指明大小,并且大小得是個常量,所以就會用到非類型模板參數。

注意:

  1. 浮點數、自定義類型、類對象以及字符串是不允許作為非類型模板參數的。
  2. 非類型參數必須在編譯期就能確認結果。

通常情況下非類型模板參數都是使用字符型整型


函數模板的特化

當我們使用模板來實現一個函數,肯定是想利用它來解決邏輯相同但數據類型不同的一些問題,來實現代碼的復用,但是也存在某些特例,比如針對某一情景或者某一類型,這個模板需要有特殊的處理,這個時候就需要用到模板的特化。

例如:

template<class T>
bool IsEqual(T str1, T str2)
{return str1 == str2;
}int main()
{char str1[] = "hello";char str2[] = "hello";if (IsEqual(str1, str2))cout << "true";elsecout << "false";
}

在這里插入圖片描述

這里不同的原因是傳遞過去的是兩個 char* 類型,他們兩個比較的不是字符串的內容,而是指針的地址,這里 str1、str2 是在棧上開辟一塊空間后再將 hello 拷貝過去,而 IsEqual 比較的是兩者指向的兩塊不同內存(也就是兩個 hello 的內存)的首地址,不可能相同。

如果要比較 char* ,就得用到 strcmp 來對這個情況進行特殊處理, 也就是模板的特化

template<>
bool IsEqual<char*>(char* str1, char* str2)
{return strcmp(str1, str2) == 0;	
}
// extern int strcmp(const char *s1,const char *s2);

在這里插入圖片描述
函數模板的特化步驟:

  • 必須要先有一個基礎的函數模板
  • 關鍵字 template 后面接一對 空的尖括號 <>
  • 函數名后面的 <> 中指定需要特化的類型
  • 特化的函數其形參 一定要與 模板的形參 類型完全相同。

類模板的特化

類也是同理,如果需要有特殊情景也需要特化處理:

類模板如下:

template<class T1, class T2>
class test
{
public:test(){cout << "test<T1, T2>" << endl;}private:T1 _x;T2 _y;
};

全特化

全特化即是將模板參數列表中所有的參數都確定化。

這里對 test<int,double> 版本特化。

template<>
class test<int, double>
{
public:test(){cout << "test<int, double>" << endl;}private:int _x;double _y;
};int main()
{test<double, double> t1;test<int, double> t2;
}

在這里插入圖片描述


偏特化

偏特化即是任何針對模版參數進一步進行條件限制設計的特化版本。

偏特化有兩種表現方式:一種是部分參數特化,一種是參數修飾特化

部分參數特化

這里對第二個參數特化,只要第二個參數是 double 就會調用對應特化版本。

template<class T1>
class test<T1, double>
{
public:test(){cout << "test<T1, double>" << endl;}private:T1 _x;double _y;
};int main()
{test<double, double> t1;test<int, double> t2;test<float, double> t3;test<double, int> t4;
}

在這里插入圖片描述


參數修飾特化

比如用指針或者引用來修飾類型,也可以進行特化。

template<class T1, class T2>
class test<T1*, T2*>
{
public:test(){cout << "test<T1*, T2*>" << endl;}private:T1* _x;T2* _y;
};int main()
{test<double, int> t1;test<int*, double*> t2;test<float*, double*> t3;test<char*, double> t4;
}	

在這里插入圖片描述


模板分離編譯

對于一個代碼量比較多的項目,通常都會采用聲明與定義分離的方法,比如在頭文件進行聲明,在源文件完成代碼的實現,最后通過鏈接的方法鏈接成單一的可執行文件。但是 C++ 的編譯器卻不支持模板的分離編譯一旦進行分離編譯,就會出現鏈接錯誤。

//頭文件a.h
template<class T>
bool IsEqual(const T& str1, const T& str2);-------------
//源文件a.cpp
template<class T>
bool IsEqual(const T& str1, const T& str2)
{return str1 == str2;
}
--------------
//test.c
#include<iostream>
#include"a.h"
using namespace std;int main()
{cout << IsEqual(3, 5);cout << IsEqual('a', 'b');
}

這里看上去是沒有問題的,但是涉及到了模板的實例化規則。

首先,一個編譯單元是指一個 .cpp 文件以及它所 #include 的所有 .h 文件,.h 文件里的代碼將會被擴展到包含它的 .cpp 文件里,然后編譯器編譯該 .cpp 文件為一個 .obj 文件(假定我們的平臺是 win32Linux 則是 .o 文件),后者擁有 PE(Portable Executable,即windows可執行文件) 文件格式,并且本身包含的就已經是二進制碼,但是不一定能夠執行,因為并不保證其中一定有 main函數 。當編譯器將一個工程里的所有 .cpp 文件以分離的方式編譯完畢后,再由連接器(linker)進行連接成為一個 .exeLinux.out)文件。

那么來看上面的代碼,

  • test.ca.cpp 被編譯器編譯成 test.obja.obj
  • 當編譯器處理主函數調用 IsEqual 的時,這是第一次使用模板,會進行實例化,而實例化需要模板的定義,但是在 test.c 中并沒有模板的定義,雖然在 test.obj 文件中頭文件 a.h 也被展開,但可惜的是 a.h 中只有模板的聲明
  • 那么編譯器只能寄希望于連接器,希望它能夠在其他 .obj 里面找到 IsEqual實例,本題中連接器就是去 a.obj 中找實例,但問題在于,雖然 a.cpp 中有模板的定義,但并沒有使用過這個模板,因此 a.obj 中不存在 IsEqual實例。因此連接器只能返回一個連接錯誤

在這里插入圖片描述


解決方法

這個問題其實沒有什么完美的解決方法

  • 將聲明和定義放到同一個頭文件中。(導致頭文件代碼過于龐大)
  • 模板定義的位置顯式實例化。(不實用)

這個問題劉未鵬大佬寫的非常好,可以學習一下他的博客

為什么C++編譯器不能支持對模板的分離式編譯

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

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

相關文章

Java操作——獲取文件擴展名,去掉文件擴展名

昨天收郵件&#xff0c;得知要參加一個產品部的會議&#xff0c;猜想&#xff0c;也許是因為我做的這個產品demo問題。于是昨天忙活到凌晨3點半&#xff0c;結果早上一來才知道又被調戲了。發郵件的MM把郵件誤發給我了。悲催啊有木有&#xff0c;困啊有木有&#xff01;自己還是…

數據結構 | B樹、B+樹、B*樹

文章目錄搜索結構B樹B樹的插入B樹的遍歷B樹的性能B樹B樹的插入B樹的遍歷B*樹B*樹的插入總結搜索結構 如果我們有大量的數據需要永久存儲&#xff0c;就需要存儲到硬盤之中。但是硬盤的訪問速度遠遠小于內存&#xff0c;并且由于數據量過大&#xff0c;無法一次性加載到內存中。…

MySQL 索引 :哈希索引、B+樹索引、全文索引

文章目錄索引引言常見的索引哈希索引自適應哈希索引B樹索引聚集索引非聚集索引使用方法聯合索引最左前綴匹配規則覆蓋索引全文索引使用方法索引 引言 為什么需要索引&#xff1f; 倘若不使用索引&#xff0c;查找數據時&#xff0c;MySQL必須遍歷整個表。而表越大&#xff0c;…

服裝店怎么引流和吸引顧客 服裝店鋪收銀系統來配合

實體店的同城引流和經營是實體經濟的一個重要的一環&#xff0c;今天我們來分享服裝行業的實體店鋪怎么引流和吸引、留住顧客&#xff0c;并實現復購。大家點個收藏&#xff0c;不然劃走就再也找不到了&#xff0c;另外可以點個關注&#xff0c;下次有新的更好的招&#xff0c;…

約瑟夫環(丟手絹問題)

文章目錄問題描述思路代碼實現問題描述 有 1~N 個數字&#xff0c;從 1~m 依次報數&#xff0c;數到 m 的數字要被刪掉&#xff0c;求最后剩下的數字是&#xff1f; 思路 第一次報數第二次報數1n-m12n-m2……m-2n-2m-1n-1m被刪掉了m11m22……n-1n-1-mnn-m 通過上面的表格&…

MySQL 鎖的相關知識 | lock與latch、鎖的類型、簡談MVCC、鎖算法、死鎖、鎖升級

文章目錄lock與latch鎖的類型MVCC一致性非鎖定讀&#xff08;快照讀&#xff09;一致性鎖定讀&#xff08;當前讀&#xff09;鎖算法死鎖鎖升級lock與latch 在了解數據庫鎖之前&#xff0c;首先就要區分開 lock 和 latch。在數據庫中&#xff0c;lock 和 latch 雖然都是鎖&…

Hibernate使用原生SQL適應復雜數據查詢

HQL盡管容易使用&#xff0c;但是在一些復雜的數據操作上功能有限。特別是在實現復雜的報表統計與計算&#xff0c;以及多表連接查詢上往往無能為力&#xff0c;這時可以使用SQL&#xff08;Native SQL&#xff09;實現HQL無法完成的任務。 1、使用SQL查詢 使用SQL查詢可以通過…

MySQL 存儲引擎 | MyISAM 與 InnoDB

文章目錄概念innodb引擎的4大特性索引結構InnoDBMyISAM區別表級鎖和行級鎖概念 MyISAM 是 MySQL 的默認數據庫引擎&#xff08;5.5版之前&#xff09;&#xff0c;但因為不支持事務處理而被 InnoDB 替代。 然而事物都是有兩面性的&#xff0c;InnoDB 支持事務處理也會帶來一些…

MySQL 事務 | ACID、四種隔離級別、并發帶來的隔離問題、事務的使用與實現

文章目錄事務ACID并發帶來的隔離問題幻讀&#xff08;虛讀&#xff09;不可重復讀臟讀丟失更新隔離級別Read Uncommitted (讀未提交)Read Committed (讀已提交)Repeatable Read (可重復讀)Serializable (可串行化)事務的使用事務的實現Redoundo事務 事務指邏輯上的一組操作。 …

MySQL 備份與主從復制

文章目錄備份主從復制主從復制的作用備份 根據備份方法的不同&#xff0c;備份可劃分為以下幾種類型&#xff1a; 熱備(Hot Backup) &#xff1a; 熱備指的是在數據庫運行的時候直接備份&#xff0c;并且對正在運行的數據庫毫無影響&#xff0c;這種方法在 MySQL 官方手冊中又…

C++ 流的操作 | 初識IO類、文件流、string流的使用

文章目錄前言IO頭文件iostreamfstreamsstream流的使用不能拷貝或對 IO對象 賦值條件狀態與 iostate 類型輸出緩沖區文件流fstream類型文件模式文件光標函數tellg() / tellp()seekg() / seekp()向文件存儲內容/讀取文件內容string流istringstreamostringstream前言 我們在使用 …

Hibernate 更新部分更改的字段 hibernate update

Hibernate 中如果直接使用 Session.update(Object o);或則是Session.updateOrUpdate(Object o); 會把這個表中的所有字段更新一遍。 如&#xff1a; ExperClass4k e new ExperClass4k(); e.setTime(time); e.setQ_num(q_num); e.setK(k); if (str "finch_fix")…

C++ 類的行為 | 行為像值的類、行為像指針的類、swap函數處理自賦值

文章目錄概念行為像值的類行為像指針的類概念引用計數動態內存實現計數器類的swap概念swap實現自賦值概念 行為像值的類和行為像指針的類這兩種說法其實蠻拗口的&#xff0c;這也算是 《CPrimer》 翻譯的缺點之一吧。。。 其實兩者的意思分別是&#xff1a; 行為像值的類&am…

C++ 右值引用 | 左值、右值、move、移動語義、引用限定符

文章目錄C11為什么引入右值&#xff1f;區分左值引用、右值引用move移動語義移動構造函數移動賦值運算符合成的移動操作小結引用限定符規定this是左值or右值引用限定符與重載C11為什么引入右值&#xff1f; C11引入了一個擴展內存的方法——移動而非拷貝&#xff0c;移動較之拷…

且談關于最近軟件測試的面試

前段時間有新的產品需要招人&#xff0c;安排和參加了好幾次面試&#xff0c;下面就談談具體的面試問題&#xff0c;在面試他人的同時也面試自己。 面試問題是參與面試同事各自設計的&#xff0c;我也不清楚其他同事的題目&#xff0c;就談談自己設計的其中2道題。 過去面試總是…

C++ 多態 | 虛函數、抽象類、虛函數表

文章目錄多態虛函數重寫重定義&#xff08;參數不同&#xff09;協變&#xff08;返回值不同&#xff09;析構函數重寫&#xff08;函數名不同&#xff09;final和override重載、重寫、重定義抽象類多態的原理虛函數常見問題解析虛函數表多態 一種事物&#xff0c;多種形態。換…

C++ 運算符重載(一) | 輸入/輸出,相等/不等,復合賦值,下標,自增/自減,成員訪問運算符

文章目錄輸出運算符<<輸入運算符>>相等/不等運算符復合賦值運算符下標運算符自增/自減運算符成員訪問運算符輸出運算符<< 通常情況下&#xff0c;輸出運算符的第一個形參是一個 非常量ostream對象的引用 。之所以 ostream 是非常量是因為向流寫入內容會改變…

C++ 重載函數調用運算符 | 再探lambda,函數對象,可調用對象

文章目錄重載函數調用運算符lambdalambda等價于函數對象lambda等價于類標準庫函數對象可調用對象與function可調用對象function函數重載與function重載函數調用運算符 函數調用運算符必須是成員函數。 一個類可以定義多個不同版本的調用運算符&#xff0c;互相之間應該在參數數…

C++ 運算符重載(二) | 類型轉換運算符,二義性問題

文章目錄類型轉換運算符概念避免過度使用類型轉換函數解決上述問題的方法轉換為 bool顯式的類型轉換運算符類型轉換二義性重載函數與類型轉換結合導致的二義性重載運算符與類型轉換結合導致的二義性類型轉換運算符 概念 類型轉換運算符&#xff08;conversion operator&#…

Tomcat中JVM內存溢出及合理配置

Tomcat本身不能直接在計算機上運行&#xff0c;需要依賴于硬件基礎之上的操作系統和一個Java虛擬機。Tomcat的內存溢出本質就是JVM內存溢出&#xff0c;所以在本文開始時&#xff0c;應該先對Java JVM有關內存方面的知識進行詳細介紹。 一、Java JVM內存介紹 JVM管理兩種類型的…