成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

告別懵圈!一文徹底搞懂 C++ 模板的類型與非類型參數 (附源碼解析)

開發
模板是 C++ 強大泛型編程能力的基石,理解了它的參數,就基本掌握了開啟這扇大門的鑰匙。

很多人 一聽到"模板"就覺得頭大,覺得那是 C++ 黑魔法,只有編譯器開發者才能玩得轉。其實,模板是 C++ 強大泛型編程能力的基石,理解了它的參數,就基本掌握了開啟這扇大門的鑰匙。

一、 為啥要有模板參數

想象一下,你要寫一個函數,計算兩個整數的和。很簡單:

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

然后,產品經理跑過來說:"我們還需要計算兩個 double 的和!" 于是你復制粘貼,改類型:

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

接著,他又來了:"float 也要!"、"long long 也不能少!"… 你是不是想打人?代碼重復,維護困難,簡直是噩夢。

這時候,C++ 模板閃亮登場!它就像一個"代碼生成器"的藍圖。你告訴它:"嘿,我要一個 add 函數,它能處理某種類型的數據,具體是啥類型,等我用的時候再告訴你!"

template <typename T> // T 就是一個“類型參數”
T add(T a, T b) 
{
    return a + b;
}

看到 <typename T> 了嗎?

這里的 T 就是我們今天的主角之一:類型參數。它像一個占位符,代表"任何一種類型"。當你調用 add<int>(3, 5) 時,編譯器心領神會:"哦,用戶指定 T 是 int",然后咔咔咔在背后幫你生成了 int add(int, int) 的版本。你調用 add<double>(3.14, 2.71),它又生成 double add(double, double) 的版本。一份代碼,N 種用途,爽不爽?

這就是模板參數存在的意義:讓代碼更通用、更靈活,減少重復,提高復用性,并且這一切通常在編譯時完成,不損失運行時性能。

初學者可能想問:這里的 T 是固定的嗎?必須是 T 嗎?

T 并不是一個固定的名稱,而是一個占位符類型名,由開發者自行定義。可以用任何合法的標識符(如 U, Type, MyType 等)替換 T

例如:

template <typename MyType>
void print(MyType value) {
    // MyType 是自定義的類型占位符
}

T 是約定俗成的默認名稱(類似循環中的 i),但并非強制。

在復雜場景中,我更喜歡使用具有描述性的名稱(如 KeyType, ValueType)。

基礎回顧完畢,現在咱們正式深入了解這兩類參數。

二、 類型參數

類型參數,顧名思義,就是用來指定一個類型的參數。它是模板中最常見、最基礎的參數。

聲明方式:通常使用 typename 或 class 關鍵字來聲明。這兩個關鍵字在這里是完全等價的,看個人或團隊喜好。

template <typename T> 
class MyContainer { /* ... */ };

template <class U> 
void process(U data) { /* ... */ };

template <typename Key, class Value> 
class MyMap { /* ... */ }; // 可以有多個

作用:它允許你在定義模板時,將具體的類型"延后"決定。你可以用這個參數來定義成員變量的類型、函數參數的類型、返回值的類型等等。

1. 實戰演練:扒一扒 std::vector 的源碼(概念層面)

一起來看下 C++ 標準庫里的老大哥 std::vector。它的基本形態(簡化版)大概是這樣的:

// (概念性簡化,非完整源碼)
namespace std {
    template <typename T, typename Allocator = std::allocator<T>> // 看這里!T 和 Allocator 都是類型參數
    class vector {
    public:
        using value_type = T; // 用 T 定義內部類型別名
        using allocator_type = Allocator;
        using pointer = typename std::allocator_traits<Allocator>::pointer; // T 通過 Allocator 影響指針類型
        using reference = value_type&; // T 定義引用類型
        // ... 構造函數、析構函數等 ...
        void push_back(const T& value); // 函數參數類型是 T
        void push_back(T&& value);      // 重載版本,參數類型也是 T
        reference operator[](size_t n); // 返回值類型是 T 的引用
        const_reference operator[](size_t n) const; // const 版本
        // ... 其他成員函數 ...
    private:
        Allocator alloc; // 成員變量,類型是 Allocator
        pointer data_start; // 指針,其指向的類型最終由 T 和 Allocator 決定
        pointer data_end;
        pointer storage_end;
        // ... 內部輔助函數,會大量使用 T 和 Allocator ...
        void reallocate(); // 內部實現會用到 allocator 分配 T 類型的內存
    };
}

(1) typename T: 

這是最核心的類型參數。它決定了 vector 容器里存儲的元素是什么類型。你想存 int?那就 std::vector<int>,此時模板內所有的 T 都被替換成 int。你想存 std::string?那就 std::vector<std::string>,T 就變成了 std::string。甚至可以存自定義的類 MyClass,寫成 std::vector<MyClass>。T 的靈活性讓 vector 成為了一個“萬能容器”。

(2) typename Allocator = std::allocator<T>: 

這是第二個類型參數,代表內存分配器的類型。它稍微高級一點,還帶了個默認值 std::allocator<T>。這意味著如果你不指定第二個參數(像我們平時那樣 std::vector<int>),編譯器就默認使用標準的內存分配器。但如果你有特殊需求,比如想用自定義的內存池,你可以提供自己的分配器類型:std::vector<int, MyCoolAllocator<int>>。注意,這個 Allocator 類型本身也經常是模板,并且它的行為通常也依賴于 T(比如 std::allocator<T> 需要知道要分配多大的內存,這取決于 T 的大小)。

小結類型參數:

  • 它是模板的“靈魂”,決定了模板實例化的“材質”或“內容類型”。
  • 使用 typename 或 class 聲明。
  • 極大地提高了代碼的泛用性。
  • 幾乎所有泛型容器、算法的核心都依賴于類型參數。

三、 非類型參數

再看看非類型參數,也叫值參數(Value Parameters)。

聲明方式:直接聲明一個帶有具體類型的變量名。這個類型必須是編譯時常量能確定的類型。

常見的允許類型包括:

  • 整型或枚舉類型 (int, unsigned int, char, bool, enum 等)
  • 指針類型 (指向對象或函數的指針,包括成員指針)
  • 左值引用類型 (指向對象或函數的引用)
  • std::nullptr_t (C++11 起)
  • auto (C++17 起,編譯器自動推導類型,但必須是上述允許的類型之一)
  • C++20 起,還允許特定的類類型(字面值常量類,Literal Class Types)和浮點數(需要編譯器支持和特定選項),這個感興趣的另外去學習。

作用:它允許你在模板實例化時,傳遞一個編譯時常量值。這個值會成為模板定義內部的一個常量,可以用來決定數組大小、循環次數、作為 switch case 的標簽、或者用于某些需要編譯時常量的計算中。

實戰演練 1:穩如磐石的 std::array

std::vector 的大小是運行時動態變化的,而 C++11 引入的 std::array 則代表了固定大小的數組。它是如何做到固定大小的呢?答案就在非類型參數!

// (概念性簡化)
namespace std {
    template <typename T, std::size_t N> // T 是類型參數,N 是非類型參數!
    struct array {
        // 使用 T
        using value_type = T;
        // ... 其他類型別名 ...
        // 關鍵:內部存儲,大小由 N 決定!
        T _elements[N]; // 這是一個真正的 C 風格數組,大小在編譯時就固定為 N
        // 成員函數
        constexpr std::size_t size() const noexcept { // size() 直接返回編譯時常量 N
            return N;
        }
        T& operator[](std::size_t index); // 訪問元素,當然還是 T 類型
        const T& operator[](std::size_t index) const;
        // ... 迭代器、fill、swap 等 ...
        // 很多操作可能在內部利用 N 進行編譯時優化,比如循環展開
    };
}

看看這里的 std::size_t N:

  • std::size_t: 這是非類型參數的類型,它指定了 N 必須是一個無符號整數,通常用來表示大小或索引。
  • N: 這是非類型參數的名字。

如何使用:當你寫 std::array<int, 10> 時,T 被替換為 int,N 被替換為常量值 10。編譯器會生成一個特定的類,其內部有一個 int _elements[10] 的成員。如果你寫 std::array<double, 100>,T 是 double,N 是 100,生成類的內部就是 double _elements[100]。

好處:

  • 性能:因為大小 N 是編譯時常量,std::array 通常可以直接在棧上分配內存(如果大小合適),避免了堆分配的開銷。它的 size() 方法是 constexpr,意味著可以在編譯時獲取大小,編譯器可以基于這個固定大小進行各種優化(比如循環展開)。
  • 類型安全:std::array<int, 10> 和 std::array<int, 11> 是完全不同的類型!這可以在編譯時捕捉到很多錯誤,比如你試圖將一個大小為 10 的數組賦值給大小為 11 的數組。

實戰演練 2:std::get 從元組中取元素

std::tuple 允許你將不同類型的元素聚合在一起。那怎么在編譯時取出特定位置的元素呢?答案還是非類型參數!

#include <tuple>
#include <string>
#include <iostream>
int main() {
    std::tuple<int, double, std::string> myTuple(10, 3.14, "Hello");
    // 使用 std::get<I>(tuple)
    int i = std::get<0>(myTuple);       // 獲取第 0 個元素 (int),這里的 0 就是非類型參數
    double d = std::get<1>(myTuple);    // 獲取第 1 個元素 (double),這里的 1 是非類型參數
    std::string s = std::get<2>(myTuple); // 獲取第 2 個元素 (string),這里的 2 是非類型參數
    std::cout << i << ", " << d << ", " << s << std::endl;
    // std::get<3>(myTuple); // 編譯錯誤!索引越界,編譯器在編譯時就能發現
    return 0;
}

std::get 函數模板大概長這樣(概念上的):

namespace std {
    // 通過索引獲取元素
    template <std::size_t I, typename... Types> // I 是非類型參數 (索引),Types... 是類型參數包
    /* 返回類型依賴于 I 和 Types... */ 
    get(tuple<Types...>& t) noexcept;
    // 還有 const&, &&, const&& 的重載版本
}

這里的 std::size_t I 就是一個非類型參數。你傳遞一個編譯時常量整數(如 0, 1, 2)給它,std::get 就能在編譯時知道你要訪問元組中的哪個元素,并返回對應類型的引用。這比運行時通過索引訪問(如果可以的話)要快得多,而且更安全,因為無效的索引會在編譯階段就被拒絕。

小結非類型參數:

  • 它是模板的"規格",決定了模板實例化的"尺寸"、"編號"或"特定配置值"。
  • 聲明時需要指定參數的類型(通常是整型、指針、引用等)。
  • 傳遞的是編譯時常量值。
  • 常用于定義固定大小(如 std::array)、指定索引(如 std::get)。
  • 可以增強類型安全(不同值的模板實例是不同類型)。

四、 類型與非類型參數的協作

圖片

很多強大的模板會同時使用類型參數和非類型參數,std::array<T, N> 就是最經典的例子。T 決定了數組元素的“材質”,N 決定了數組的“大小”。兩者結合,創造出一個既泛型(適用于多種類型)又高效(固定大小,編譯時優化)的數據結構。

再比如,你可以寫一個模板函數,打印一個 std::array 的內容:

#include <array>
#include <iostream>

template <typename T, std::size_t N> // 同時使用類型參數 T 和非類型參數 N
void print_array(const std::array<T, N>& arr) {
    std::cout << "[ ";
    for (std::size_t i = 0; i < N; ++i) { // 循環上限直接用 N
        std::cout << arr[i] << (i == N - 1 ? "" : ", ");
    }
    std::cout << " ]" << std::endl;
}

int main() {
    std::array<int, 5> ints = {1, 2, 3, 4, 5};
    std::array<double, 3> doubles = {1.1, 2.2, 3.3};
    std::array<char, 4> chars = {'a', 'b', 'c', 'd'};

    print_array(ints);    // 編譯器推導出 T=int, N=5
    print_array(doubles); // 編譯器推導出 T=double, N=3
    print_array(chars);   // 編譯器推導出 T=char, N=4

    return0;
}

這個 print_array 函數因為同時接受 T 和 N 作為模板參數,所以可以完美地處理任何類型、任何(固定)大小的 std::array。編譯器在調用點會根據傳入的 std::array 類型自動推導出 T 和 N 的值。

1. C++17 和 C++20 的小升級(錦上添花)

auto 作為非類型參數 (C++17):你可以讓編譯器自動推導非類型參數的具體類型,只要它符合要求。

template <auto Value> // Value 的類型由傳入的常量值決定
void process_value() {
    // ... 可以使用 Value,它的類型是確定的 ...
    std::cout << "Processing value: " << Value << " of type " << typeid(decltype(Value)).name() << std::endl;
}

process_value<10>();   // Value 是 int, 值為 10
process_value<'a'>(); // Value 是 char, 值為 'a'
// process_value<3.14>(); // C++17 通常還不支持浮點數作為非類型參數 (C++20 有條件支持)

五、 總結

  • 類型參數 :用 typename 或 class 聲明,是類型的占位符。它讓模板能夠適用于不同的數據類型,是泛型容器(如 vector)和泛型算法的基礎。它決定了"做什么"或"用什么材質"。
  • 非類型參數 :聲明時帶有具體類型(如 int, size_t, 指針,C++17 auto 等),是編譯時常量的占位符。它讓模板能夠根據編譯時確定的值進行定制,常用于固定大小(如 array)、索引(如 get)或配置。它決定了"多大尺寸"、"哪個編號"或"具體配置"。
責任編輯:趙寧寧 來源: CppPlayer
相關推薦

2010-02-04 14:22:25

C++函數模板非類型參

2010-02-04 14:01:43

C++非類型類模板參數

2020-12-07 06:19:50

監控前端用戶

2021-07-08 10:08:03

DvaJS前端Dva

2020-03-18 14:00:47

MySQL分區數據庫

2022-06-07 10:13:22

前端沙箱對象

2021-06-30 08:45:02

內存管理面試

2023-11-23 13:39:17

2021-08-05 06:54:05

觀察者訂閱設計

2019-11-06 17:30:57

cookiesessionWeb

2021-11-20 10:27:43

Python數據類型

2021-11-22 06:21:31

Python數據類型Python基礎

2022-04-11 10:56:43

線程安全

2021-01-13 05:21:59

參數

2024-08-08 14:57:32

2019-06-29 15:32:43

計算機互聯網 技術

2020-12-21 07:54:46

CountDownLa用法源碼

2021-10-20 08:49:30

Vuexvue.js狀態管理模式

2024-01-03 13:39:00

JS,Javascrip算法

2023-11-23 06:50:08

括號
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧美在线国产精品 | 日韩精品在线看 | 日韩欧美三级 | 成人在线视频网站 | 夜夜爽99久久国产综合精品女不卡 | 在线观看日韩 | 日本一区二区影视 | 日韩2020狼一二三 | 99久久成人 | 久久精品国产一区二区三区不卡 | 逼逼视频 | 精品久久久久久一区二区 | 日本在线视频中文字幕 | 99热精品在线观看 | 99久久免费精品国产男女高不卡 | 99久久日韩精品免费热麻豆美女 | 久久无毛 | 国产夜恋视频在线观看 | 欧洲在线视频 | 亚洲国产成人精品女人久久久 | 国产精品色综合 | 日日夜夜精品 | 日韩在线播放第一页 | 亚洲第一黄色网 | 精品免费国产视频 | 欧美一二三 | 免费在线观看黄网站 | 国产精品毛片久久久久久 | 亚洲精品久久久久avwww潮水 | 99视频在线免费观看 | 午夜a区| 亚洲人久久 | 99热碰| 国产成人高清 | 亚洲精品www久久久久久广东 | 欧美成人aaa级毛片在线视频 | 91久久精品国产免费一区 | 欧美色综合一区二区三区 | 国产在线观看一区二区三区 | 一区二区三区欧美 | 日本羞羞影院 |