1. 程式人生 > >C++ 函式模板和類模板--泛型程式設計

C++ 函式模板和類模板--泛型程式設計

所謂函式模板,實際上是建立一個通用函式,其函式型別和形參型別不具體指定,用一個虛擬的型別來代表。這個通用函式就稱為函式模板。
凡是函式體相同的函式都可以用這個模板來代替,不必定義多個函式,只需在模板中定義一次即可。

一 函式模板初識
1) 為什麼要有函式模板?
函式業務邏輯一樣,但是函式引數型別不一樣,引入泛型程式設計,方便程式設計師程式設計。
2) 語法:
template <typename T>
void myswap(T &a,T &b)
{
}
a: tempalte是告訴C++要進行泛程式設計,看到T不要隨便報錯。
b :T表示型別
3) 呼叫
a:顯式呼叫:
myswap(x,y);
myswap(a,b);
b: 自動類推導
myswap(x,y);//根據引數型別來自動匹配

#include <iostream>
using namespace std;

// 函式的業務邏輯 一樣 
// 函式的引數型別 不一樣
void myswap01(int &a, int &b)
{
int c = 0;
c = a;
a = b;
b = c;
}

void myswap02(char &a, char &b)
{
char c = 0;
c = a;
a = b;
b = c;
}

template <typename T>
void myswap(T &a, T &b)
{
T c = 0;
c = a;
a = b;
b = c;
cout << "hello ....我是模板函式 歡迎 calll 我" << endl;
}
void main()
{
    {
    int x = 10; 
    int y = 20;

    myswap<int>(x, y); //1 函式模板 顯示型別 呼叫

    myswap(x, y);  //2 自動型別 推導
    printf("x:%d y:%d \n", x, y);
}
{
    char a = 'a'; 
    char b = 'b';
    myswap<char>(a, b); //1 函式模板 顯示型別 呼叫
    myswap(a, b);
    printf("a:%c b:%c \n", a, b);
}
}

4 函式模板作函式引數

#include <iostream>
using namespace std;

//讓int陣列進行排序

template <typename T,typename T2 >
int mySort(T *array, T2 size)
{
T2 i, j ;
T tmp;
if (array == NULL)
{
    return -1;
}

//選擇  
for (i=0; i<size; i++)
{
    for (j=i+1; j<size; j++)
    {
        if (array[i] < array[j])
        {
            tmp = array[i];
            array[i] = array[j];
            array[j] = tmp;
        }
    }
}
return 0;
}

template <typename T, typename T2>
int myPrint(T *array, T2 size)
{
T2 i = 0;
for (i=0; i<size; i++)
{
    cout << array[i] << " ";
}
return 0;
}

void main()
{
//char 型別
{
    char buf[] = "aff32ff2232fffffdssss";
    int len = strlen(buf);

    mySort<char, int>(buf, len);
    myPrint<char , int>(buf, len);

}

cout<<"hello..."<<endl;
system("pause");
return ;
}

5 函式模板遇上函式過載
1) 函式模板和普通函式區別:
函式模板不允許自動型別轉化,普通函式能夠進行自動型別轉換

int a = 10;
char c = 'z';
myswap(a, c); // 普通函式的呼叫:  可以進行隱式的型別轉換 
myswap(c, a); //這個是時候只會呼叫普通函式

2)函式模板遇上普通函式時的呼叫規則:
a:函式模板可以像普通函式一樣被過載
b:當函式模板和普通函式都符合呼叫時,C++編譯器優先考慮普通函式
c:如果函式模板可以產生一個更好的匹配,那麼選擇模板
d:可以通過空模板實參列表的語法限定編譯器只通過模板匹配,例如:myswap<>(a,b).

#include "iostream"
using namespace std;

int Max(int a, int b)
{
cout<<"int Max(int a, int b)"<<endl;
return a > b ? a : b;
}

template<typename T>
T Max(T a, T b)
{
    cout<<"T Max(T a, T b)"<<endl;
    return a > b ? a : b;
}

template<typename T>
T Max(T a, T b, T c)
{
cout<<"T Max(T a, T b, T c)"<<endl;
return Max(Max(a, b), c);
}

void main()
{
int a = 1;
int b = 2;

cout<<Max(a, b)<<endl; //當函式模板和普通函式都符合呼叫時,優先選擇普通函式
cout<<Max<>(a, b)<<endl; //若顯示使用函式模板,則使用<> 型別列表

cout<<Max(3.0, 4.0)<<endl; //如果 函式模板產生更好的匹配 使用函式模板

cout<<Max(5.0, 6.0, 7.0)<<endl; //過載

cout<<Max('a', 100)<<endl;  //呼叫普通函式 可以隱式型別轉換 
system("pause");
return ;
}

三 編譯器模板機制剖析
1 編譯器並不是把函式模板處理成能夠處理任意類的函式
2 編譯器從函式模板通過具體型別產生不同的函式
3 編譯器會對函式模板進行兩次編譯: 在宣告的地方對模板程式碼本身進行編譯;在呼叫的地方對引數替換後的程式碼進行編譯。

四 類模板初識
1 為什麼需要類模板
1)有時,有兩個或多個類,其功能是相同的,僅僅是資料型別不同
2) 類模板用於實現類所需資料的型別引數化
3) 類模板在表示如陣列、表、圖等資料結構顯得特別重要)
2 單個類模板語法
template <\typename type>
class Tclass{
//至少有一個成員變數為type型別
};

template<\typename T>  
class A   //A就是一個模板類(或類模板)
{ 
public: 
     A(T t) 
     { 
          this->t = t; 
     } 
     T &getT() 
     { 
               return t; 
     } 
private: 
     T t; };

void main() 
{ 
//模板了中如果使用了建構函式,則遵守以前的類的建構函式的呼叫規則 
     A<int> a(100); //需要進行型別具體化否則報錯。
     a.getT(); 
     printAA(a); 
     return ; }

3 從模板類派生普通類
子類從模板類繼承的時候,需要讓編譯器知道父類的資料型別具體是什麼
(資料型別的本質:固定大小記憶體塊的別名)A<\int>

這裡寫圖片描述

4 從模板類派生模板類
template <\typename T>
class C :public A<\T>
{
};

#include <iostream>
using namespace std;

//模板類 
template <class T>
class A
{
public:
A(T a)
{
    this->a = a;
}
public:
void printA()
{
    cout << "a: " << a << endl;
}
protected:
T a;
};

//從模板類 派生了 普通類
// 模板類派生時, 需要具體化模板類. C++編譯器需要知道 父類的資料型別具體是什麼樣子的
    //=====> 要知道父類所佔的記憶體大小是多少 只有資料型別固定下來,才知道如何分配記憶體 
class B : public A<int>
{
public:
B(int a=10, int b=20) : A<int>(a)//如果父類呼叫了有參建構函式需要顯示初始化,使用物件初始化列表
{
    this->b = b;
}
void printB()
{
    cout << "a:" << a << " b: " << b << endl;
}
private:
int b;
};
//從模板類 派生 模板類

template <typename T>
class C : public A<T>
{
public:
C(T c, T a) : A<T>(a)
{
    this->c = c;
}
void printC()
{
    cout << "c:" << c <<endl;
}
protected:
T c;
};

void main()
{
B  b1(1, 2);
b1.printB();

C<int> c1(1, 2);
c1.printC();

system("pause");
}

//類模板 做函式引數

//引數 ,C++編譯器 要求具體的類 所以所 要 A<int> &a 
void UseA( A<int> &a )
{
a.printA();
}

void main61()
{
//模板類(本身就是型別化的)====具體的類=====>定義具體的變數

A<int> a1(11), a2(20), a3(30); //模板類是抽象的  ====>需要進行 型別具體

UseA(a1);
UseA(a2);
UseA(a3);

cout<<"hello..."<<endl;
system("pause");
return ;
}

5 知識體系
1)所有類模板函式都寫在類的內部

#include <iostream>
using namespace std;

template <typename T>
class Complex
{
friend Complex MySub(Complex &c1, Complex &c2)
{
    Complex tmp(c1.a - c2.a, c1.b - c2.b);
    return tmp;
}

friend ostream & operator<<(ostream &out, Complex &c3)
{
    out <<  c3.a << " + " << c3.b <<  "i" << endl;
    return out;
}
public:
Complex(T a, T b)//在類的內部就不用寫template <typename T>
{
    this->a = a;
    this->b = b;
}

Complex operator+ (Complex &c2)
{
    Complex tmp(a+c2.a, b+c2.b);
    return tmp;
}

void printCom()
{
    cout << "a:" << a << " b: " << b << endl;
}
private:
T   a;
T   b;
};
//運算子過載的正規寫法 
// 過載 << >> 只能用友元函式  ,其他運算子過載 都要寫成成員函式 , 不要濫用友元函式
void main()
{
//需要把模板類 進行具體化以後  才能定義物件  C++編譯器要分配記憶體
Complex<int>    c1(1, 2);
Complex<int>    c2(3, 4);

Complex<int> c3 = c1 + c2;
//c3.printCom();
cout << c3 << endl;

//濫用友元函式
{
    Complex<int> c4 = MySub(c1, c2);
    cout << c4 << endl;

}

cout<<"hello..."<<endl;
system("pause");
return ;
}

2)所有的類模板函式寫在類的外部,在一個cpp中

#include <iostream>
using namespace std;

 //友元函式:友元函式不是實現函式過載(非<<、>>) 
 //需要在類前增加類的前置宣告函式的前置宣告 
template <typename T>
class Complex ; //類的前置宣告

template <typename T>
Complex<T> MySub (Complex<T> &c1, Complex<T> &c2);
template <typename T>
class Complex
{
friend Complex<T> MySub<T> (Complex<T> &c1, Complex<T> &c2);

friend ostream & operator<< <T> (ostream &out, Complex &c3);//解決方法在此處加<T>

public:
Complex(T a, T b);
void printCom();
Complex operator+ (Complex &c2);    

private:
T   a;
T   b;
};

//建構函式的實現 寫在了類的外部
template <typename T>   //每個模板函式前都要加這句
Complex<T>::Complex(T a, T b)//類模板需要具體化
{
    this->a = a;
this->b = b;
}

template <typename T>
void Complex<T>::printCom()
{
cout << "a:" << a << " b: " << b << endl;
}

//本質是 : 模板是兩次 編譯生成的 第一次生成的函式頭 和第二次生成的函式頭 不一樣
    //成員函式 實現 +運算子過載
template <typename T>
Complex<T>  Complex<T>::operator+ (Complex<T> &c2)//類模板需要具體化
{
Complex tmp(a+c2.a, b+c2.b);//此處加不加<T>都可以
return tmp;
}

//友元函式 實現 << 運算子過載
template <typename T>
ostream & operator<<(ostream &out, Complex<T> &c3)
{
out <<  c3.a << " + " << c3.b <<  "i" << endl;
return out;
}
//濫用 友元函式。結論:在不要用友元函式的地方不要用友元函式
template <typename T>
Complex<T> MySub(Complex<T> &c1, Complex<T> &c2)
{
Complex<T> tmp(c1.a - c2.a, c1.b - c2.b);
return tmp;
}

void main()
{
//需要把模板類 進行具體化以後  才能定義物件  C++編譯器要分配記憶體
Complex<int>    c1(1, 2);
Complex<int>    c2(3, 4);

Complex<int> c3 = c1 + c2;
//c3.printCom();
cout << c3 << endl;

//濫用友元函式
{
    Complex<int> c4 = MySub<int>(c1, c2);
    cout << c4 << endl;
}
cout<<"hello..."<<endl;
system("pause");
return ;
}

六 類模板中的關鍵字static

/*

編譯器並不是把函式模板處理成能夠處理任意類的函式
編譯器從函式模板通過具體型別產生不同的函式
編譯器會對函式模板進行兩次編譯
在宣告的地方對模板程式碼本身進行編譯;在呼叫的地方對引數替換後的程式碼進行編譯。
*/

#include <iostream>
using namespace std;

template <typename T>
class AA
{
public:
static T m_a;
};

template <typename T>
T AA<T>::m_a  = 0;
class AA1
{
public:
   static int m_a;

};
int AA1::m_a = 0;

class AA2
{
public:
static char m_a;
};
char AA2::m_a  = 0;
void main()
{
AA<int> a1, a2, a3;
a1.m_a = 10;
a2.m_a ++;
a3.m_a ++;
cout << AA<int>::m_a << endl;

AA<char> b1, b2, b3;
b1.m_a = 'a';
b2.m_a ++;
b2.m_a ++ ;

cout << AA<char>::m_a << endl;

//m_a 應該是 每一種型別的類 使用自己的m_a

cout<<"hello..."<<endl;
system("pause");
return ;
}