天天看點

模闆·初階

文章目錄

  • ​​自學網站​​
  • ​​泛型程式設計​​
  • ​​函數模闆​​
  • ​​函數模闆·格式​​
  • ​​函數模闆·原理​​
  • ​​函數模闆·執行個體化​​
  • ​​隐式執行個體化​​
  • ​​顯式執行個體化​​
  • ​​模闆參數·比對原則​​
  • ​​類模闆​​
  • ​​類模闆·格式​​
  • ​​類模闆·執行個體化​​
  • ​​練習題​​

自學網站

泛型程式設計

首先,何為泛型程式設計呢?

泛型程式設計指不再是針對某種類型,能适應廣泛的類型,跟具體類型無關的代碼。

是以引入了模闆的概念,模闆分為函數模闆和類模闆。

template<class T> //用class/typename都可以

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

大家想想我們之前是怎麼實作Swap函數的:

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;
}

// ……      

我們之前就是利用函數重載,但是你有沒有想過,重載出來的函數除了類型不同之外,其餘均相同,是以這樣會導緻代碼複用率更低,而且代碼的可維護性也不好。

那模闆就是告訴編譯器有一個模子,讓編譯器根據不同的類型利用該模子來生成代碼。

再次提到泛型程式設計:編寫與類型無關的通用代碼,是代碼複用的一種手段。模闆是泛型程式設計的基礎。

模闆·初階

函數模闆

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

函數模闆的類型一般是編譯器根據實參傳遞給形參,然後推演出來的。

函數模闆·格式

template< class T1, class T2, ……, class Tn>
傳回值類型 函數名(參數清單){ }      

比如:

template<class T>

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

其實模闆參數在很多地方跟函數參數是很像的:

模闆參數傳遞的是類型,如:

//類型也可以給預設值
template<class T = char>      

函數參數傳遞的是對象值,如:

T* func(int n = 10)
{
  //……
}      

還有一點需要注意哦,普通的函數是有位址的,但是函數模闆沒有位址。

typename是用來定義模闆參數的關鍵字,也可以使用class,注意一下,咱們的struct 在這裡不能代替class 哦。

函數模闆·原理

函數模闆隻是一個藍圖,它本身并不是函數,是編譯器使用特定方式産生具體類型函數的模具。是以其實模闆就是将本來應該我們做的重複的工作交給了編譯器。

模闆·初階

在編譯器編譯階段,對于模闆函數的使用,編譯器需要根據傳入的實參類型來推演生成對應類型的函數以供調用。比如上圖:當用int 類型使用函數模闆時,編譯器通過對實參類型的推演,将T 确定為int 類型,然後産生一份專門處理int 類型的代碼,當然了,對于 double類型也如此。

函數模闆·執行個體化

當不同類型的參數使用函數模闆時,稱為函數模闆的執行個體化。模闆參數執行個體化分為:隐式執行個體化和顯式執行個體化。

隐式執行個體化

隐式執行個體化就是:讓編譯器根據傳入的實參類型推演生成模闆參數的實際類型。

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

int main()
{
  int a1 = 10, a2 = 20;
  double d1 = 10.0, d2 = 20.0;
  Add(a1, a2);//編譯正常
  Add(d1, d2);//編譯正常

  Add(a1, d1);//編譯報錯

  return 0;
}      

為什麼下面這行代碼編譯報錯了呢?

Add(a1, d1);      

因為在編譯期間,當編譯器看到該執行個體化時,需要推演其實參類型,通過實參a1 将T推演為int ,通過實參d1 将T 推演為double ,但是模闆參數清單隻有一個T ,是以編譯器無法确定将這裡的T視為int 還是double 而報錯。

注意哦,在模闆中,編譯器一般不會進行類型轉換操作,因為一旦轉換出現問題,編譯器就需要承擔責任。

那怎麼處理上述的錯誤呢?

// 有兩種處理方式:1.使用者自己來強制轉換;2.使用顯式執行個體化
Add(a, (int d));      

顯式執行個體化

顯式執行個體化就是:在函數名後的<>中指定模闆參數的實際類型。

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

int main()
{
  int a1 = 10, a2 = 20;
  double d1 = 10.0, d2 = 20.0;

  //顯式執行個體化
  Add<int>(a1, d1);

  return 0;
}      

函數模闆的類型一般是編譯器根據實參傳遞給形參,推演出來的;如果不能自動推演,那麼就需要我們顯式執行個體化指定模闆參數。

template<class T>
T* func(int n)
{
  return new T[n];
}

int main()
{
  //函數模闆顯式執行個體化
  int* p1 = func<int>(10);
  double* p2 = func<double>(10);
  return 0;
}      

如果類型不比對,編譯器會嘗試進行隐式類型轉換,若轉換無法成功編譯器将會報錯。

模闆參數·比對原則

原則一:一個非模闆函數可以和一個同名的函數模闆同時存在,而且該函數模闆還可以被執行個體化成這個非模闆函數。

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

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

int main()
{
  Add(1, 2);//與非模闆函數比對,不需要函數模闆執行個體化
  Add<int>(1, 2);//編譯器顯式執行個體化
  
  return 0;
}      

原則二:對與非模闆函數和同名函數模闆,如果其他條件都相同,在調用時會優先調用非模闆函數。但是如果函數模闆可以産生一個具有更好比對的函數,那麼将選擇模闆。

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

//通用加法函數
template<class T1, class T2>
T1 Add(const T1& left, const T2& right)
{
  return left + right;
}

int main()
{
  Add(1, 2);//與非模闆函數比對,不需要函數模闆執行個體化
  Add(1, 2.0);//函數模闆可以生成更加比對的版本
  
  return 0;
}      

原則三:函數模闆不允許自動類型轉換,但是普通函數可以進行自動類型轉換。

類模闆

類模闆·格式

template<class T>
class 類模闆名 //注意哦這是類模闆名
{
  //類内成員定義
}      

比如:

//動态順序表
//注意哦,這裡的vector不是具體的類,是編譯器根據被執行個體化的類型生成具體類的模具,是以它是類模闆名
template<class T>
class vector
{
public:
  vector(size_t capacity = 10)
    :_a(new T[capacity])
    ,_size(0)
    ,_capacity(capacity)
  {}

  //聲明(隻是為了示範在類外如何定義)
  ~vector();

private:
  T* _a;
  size_t _size;
  size_t _capacity;
};

//注意類模闆中函數放在類外進行定義時,需要加模闆參數清單
template<class T>
vector<T>::~vector()//指定類域
{
  if (_a)
  {
    delete _a;
    _a = nullptr;
    _size = _capacity = 0;
  }
}      

注意哦,上面的vector不是具體的類,是編譯器根據被執行個體化的類型生成具體類的模具,是以它是類模闆名。

類模闆·執行個體化

類模闆執行個體化與函數模闆執行個體化不同,類模闆執行個體化需要在類模闆的名字後面加上<>,然後将執行個體化後的類型放到<>中即可,類模闆名并不是真正的類,而執行個體化的結果才是真正的類。

// vector是類模闆名,vector<int>才是類型
//類模闆的類型是顯式執行個體化,明确指定的
vector<int> s1;
vector<double> s2;      

練習題

1、下面有關C++中為什麼用模闆類的原因,描述錯誤的是? ( )

A.可用來建立動态增長和減小的資料結構

B.它是類型無關的,是以具有很高的可複用性

C.它運作時檢查資料類型,保證了類型安全

D.它是平台無關的,可移植性

解析:

A.模闆可以具有非類型參數,用于指定大小,可以根據指定的大小建立動态結構;

B.模闆最重要的一點就是類型無關,提高了代碼複用性;

C.模闆運作時不檢查資料類型,也不保證類型安全,相當于類型的宏替換;

D.隻要支援模闆文法,模闆的代碼就是可移植的。

2、下列關于模闆的說法正确的是( )

A.模闆的實參在任何時候都可以省略

B.類模闆與模闆類所指的是同一概念

C.類模闆的參數必須是虛拟類型的

D.類模闆中的成員函數全是模闆函數

解析:

A.不一定,參數類型不同時有時需要顯示指定類型參數;

B.類模闆是一個類家族,模闆類是通過類模闆執行個體化的具體類;

繼續閱讀