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

猜你喜欢

转载自blog.csdn.net/weixin_40878579/article/details/81369398