天天看點

【C++初階】函數模闆與類模闆

文章目錄

  • ​​引言.泛型程式設計​​
  • ​​一.函數模闆​​
  • ​​1.基本使用​​
  • ​​2.拔高訓練​​
  • ​​2-1自動推演執行個體化和顯式執行個體化​​
  • ​​2-2優先選擇自己寫的“加法”函數​​
  • ​​二.類模闆​​
  • ​​1.基本使用​​
  • ​​2.小試牛刀​​

引言.泛型程式設計

泛型程式設計可以實作通過書寫模闆,讓編譯器利用模闆套用在不同類型上,進而生成不同類型所對應的代碼

模闆分為:

1.函數模闆

2,類模闆

一.函數模闆

1.基本使用

或許我們還滿足于C++的函數重載能夠使用同名函數實作不同類型變量的交換

但是大佬們不這麼想,有了函數重載的确解決了C語言的大部分問題,但是函數重載有兩個不太友善的地方:

  1. 對于我提前已知的類型,函數重載實作的代碼,複用性不夠好,代碼邏輯幾乎完全一樣,顯得備援
  2. 對于我提前未知的類型,得臨時函數重載,改巴改巴形成一個該類型的重載函數
于是,大佬心裡就在想,能不能像鑄鐵一樣,刻出一個模子(模闆),然後通過澆築不同的材料(不同的類型),進而鍛造成不同材料制成的寶刀(不同類型的目标代碼)

函數重載版本:

void Swap(int& left, int& right)
{
  int temp = left;
  left = right;
  right = temp;
}

void Swap(double& left, double& right)
{
  double temp = left;
  left = right;
  right = temp;
}

int main()
{
  int a = 1, b = 2;
  Swap(a, b);

  
  double c = 1.1, d = 2.2;
  Swap(c, d);
  return 0;
}      
【C++初階】函數模闆與類模闆

函數模闆版本:

class A
{
  friend ostream& operator<<(ostream& out, const A& a);
public:
  A(int a = 10)
    :_a(a)
  {
    ;
  }
private:
  int _a;
};

//template<class T>
template<typename T>
void Swap(T& left, T& right)
{
  T temp = left;
  left = right;
  right = temp;
}

inline ostream& operator<<(ostream& out, const A& a)
{
  out << a._a << endl;
  return out;
}

int main()
{
  //提前已知的類型1.
  int a = 1, b = 2;
  Swap(a, b);
  
  //2.
  double c = 1.1, d = 2.2;
  Swap(c, d);

  //提前未知的類型
  A a1(12), a2(23);
  Swap(a1, a2);

  return 0;
}      
【C++初階】函數模闆與類模闆
通過函數模闆我們可以看到泛型程式設計的優點:
  1. 提高代碼的複用性
  2. 萬變不離其宗,對于提前未知的類型也可以靈活應對
ps:通過單步調試,我們可以看到三次都能進入void Swap(T& left, T& right),但是他們調用的并非是這個模闆,而是由這個模闆執行個體化出來的函數,這能進到模闆,那是編譯器為了友善展示特意地.
【C++初階】函數模闆與類模闆
這個模闆是寫給編譯器的,編譯器會根據你傳入的類型自動推演并執行個體化出對應類型的函數代碼

ps:關于模闆參數的問題:

【C++初階】函數模闆與類模闆
3個,4、6、7正确,聲明模闆的格式為:template<類型 形參名1,類型 形參名2…>,類型有class和typename。

2.拔高訓練

2-1自動推演執行個體化和顯式執行個體化

那如果我想用一個同類型的Swap模闆執行個體化出來的函數交換兩個不同類型的實參,可以嗎?

答案:不可以,沒辦法完成類型的轉換,因為函數得先被推演出來才能發生類型轉換,但是這次是函數在推演得時候就出現問題了.

另外就算不是用函數推,這裡也行不通,那是因為這是傳引用傳參,傳引用傳參的話,發生類型轉換的時候就會産生一個臨時變量,這個時候就會出現臨時變量是const試圖傳給形參是非const引用的問題,也不能完成隐式類型轉換.(因為要交換,也不能在形參上加const修飾)

template<typename T>
void Swap(T& left, T& right)
{
  T temp = left;
  left = right;
  right = temp;
}

int main()
{
  int a = 10;
  double c = 1.1;
  Swap(a, c);
  return 0;
}      

那如果我就是想這樣不同類型實參之間進行邏輯業務(比如求和),我們可以采取下面3種方法:

1.建立一個不同類型參數的函數模闆

這樣的話,不同類型和同一類型的實參都可以随意調用
template<typename T1,typename T2>
T2 Add(const T1& left, const T2& right)
{
    return left + right;
}
int main()
{
    int a1 = 10, a2 = 20;
    double d1 = 1.1, d2 = 2.2;
    //同一類型之間
    cout << Add(a1, a2) << endl;
    cout << Add(d1, d2) << endl;

    //不同類型之間
    cout << Add(a1, d2) << endl;
    cout << Add(d1, a2) << endl;

    return 0;
}      

2.實參處強制類型轉換使得自動推演執行個體化能作用

3.調用和函數處顯式推演執行個體化

2.3一起示範:

template<typename T>
T Add(const T& left, const T& right)
{
  return left + right;
}
int main()
{

  int a = 10;
  double c = 1.1;
  //2.自動推演執行個體化
  cout << Add(a, (int)c) << endl;
  cout << Add((double)a, c) << endl;

  //3.顯式執行個體化
  cout << Add<int>(a, c) << endl;
  cout << Add<double>(a, c) << endl;
  
  return 0;
}      
ps:模闆參數的書寫方式和函數參數很像,但是模闆參數定義的是類型,函數參數定義的是形參變量
//正确寫法:
// template<typename T1,typename T2>
//錯誤寫法:
//template<typename T1>
//template<typename T2>      

2-2優先選擇自己寫的“加法”函數

template<typename T>

//通用的加法函數
T Add(const T& left, const T& right)
{
  return left + right;
}

//專門處理int的加法函數
int Add(const int& left, const int& right)
{
  return left + right;
}

int main()
{
  int a1 = 1, a2 = 2;
  cout << Add(a1, a2) << endl;
  return 0;
}      
【C++初階】函數模闆與類模闆

通過調試我們發現:

編譯器也是一個懶狗,當模闆生成的通用加法函數和自己寫的某類型的加法函數同時存在時,(盲猜可能是函數名修飾規則不一樣,是以能同時存在)

編譯器會優先選擇我們自己寫的某類型的加法函數,而不是采用模闆生成的.

那假如我就是想編譯器能調用模闆生成的加法函數:

使用模闆的顯式執行個體化:

Add< int>(a1,a2)

【C++初階】函數模闆與類模闆

二.類模闆

1.基本使用

先來看看我們之前用類型重命名寫的棧類:

//這裡使用了typedef類型重命名

//類型重命名
typedef int STDateType;

class Stack
{
public:
  Stack(int capacity = 4)
  {
    _a = (STDateType*)malloc(sizeof(STDateType) * _capacity);
    _capacity = capacity;
    _size = 0;
  }
  ~Stack()
  {
    _a = nullptr;
    _size = _capacity = 0;
  }
  void Push(STDateType x)
  {
    _a[_size++] = x;
  }
private:
  STDateType* _a;
  int _capacity;
  int _size;
};

int main()
{
  Stack St1(100);
  return 0;
}      

這typedef似乎也能滿足我實作不同類型的棧的需求,但是這typedef有一個不足的地方:

假如我在一份代碼裡想實作兩個存放不同類型變量的棧,typedef就無法滿足.

Stack St1(100);
  Stack St2('a');      
實際上,typedef完成的是代碼的可維護性,而非我們今天所講的泛型程式設計,我們今天要講的類模闆才是真正的泛型程式設計!
template<class T>

class Stack
{
public:
  Stack(int capacity = 4)
  {
    _a = (T*)malloc(sizeof(T) * _capacity);
    _capacity = capacity;
    _size = 0;
  }
  ~Stack()
  {
    _a = nullptr;
    _size = _capacity = 0;
  }
  void Push(const T& x)
  {
    _a[_size++] = x;
  }
private:
  T* _a;
  int _capacity;
  int _size;
};

int main()
{
  //函數模闆可以根據實參傳遞形參,推演模闆參數
  //類模闆一般沒有推演時機,是以隻能顯式執行個體化
  Stack<int> St1(100);
  St1.Push(1);

  //他們都是一個類執行個體化出來的
  //但是模闆參數不同,他們就是不同類
  Stack<double> St2(200);
  St2.Push(2.2);
  return 0;
}      

ps:

  1. 類模闆一般沒有推演時機(比如棧類,構造函數隻傳個數,沒有棧内元素),是以隻能顯式執行個體化
  2. 模闆參數不同,就是不同類(和函數模闆參數不一樣,就是不同函數)
//類模闆的顯式執行個體化
Stack<int> St1(10);
//函數模闆的執行個體化
add<int>(1, 2);      
大膽的嘗試:一個模闆參數能同時用在類模闆和函數模闆上或不同的兩個函數模闆上嗎?—不可以
【C++初階】函數模闆與類模闆

2.小試牛刀

用類模闆模拟實作一個數組類(這裡好多寫的很有啟發性的文法代碼)
#include<assert.h>

#define N 10
//4.命名空間域:解決與庫中array的沖突問題
namespace song
{
  template<class T>
  class array
  {
  public:
    //1.inline内聯
    //2.assert斷言:越界100%檢查
    //3.引用作傳回值的兩個好處
    inline T& operator[](size_t i)
    {
      assert(i < N);
      return _a[i];
    }
  private:
    T _a[N];
  };
}

int main()
{
  song::array<int> a;
  for (int i = 0; i < N; i++)
  {
    a[i] = i;
    ++a[i];
    cout << a[i] << endl;
  }

  return 0;
}      

寫的好的幾個地方:

//1.inline内聯
  //2.assert斷言:越界100%檢查
  //3.引用作傳回值的兩個好處
  //4.命名空間域:解決與庫中array的沖突問題      

這裡隻講第3點兩個好處:

首先一定得采用傳引用傳回(出了作用域,_a[i]還是存在),在這裡傳引用傳回行不通

  1. 傳引用傳回可以修改(在auto講過)—–傳值行不通的原因
  2. 減少拷貝

繼續閱讀