泛型编程概念及函数模板
- 泛型编程的引入
- 模板
- 函数模板
- 函数模板注意事项
- 函数模板举例
- 普通函数和函数模板的区别
- 隐式类型转换上的区别
- 调用上的规则和区别
- 模板的局限性
- 学习模板的意义
白云泉
唐代:白居易
天平山上白云泉,云自无心水自闲。
何必奔冲山下去,更添波浪向人间。
泛型编程的引入
想要比较两个整形数据的大小,写一个形参数是整形的函数。想要比较两个浮点型数据的大小,写一个形参是浮点型的函数。想要比较两个字符的ascii码的大小,写一个形参是char的函数。实际上几个函数的代码都差不多,那你觉得啰里八嗦写了一堆代码,烦不烦?
所以,如果可以把数据类型也作为一种可以灵活传递的参数,就太人性了。这种编程就是泛型编程了。
泛型,广泛的类型,指的仍是一种数据类型,它是一种通用类型,它可以代替其它多种类型,这个方法可以大规模的减少程序代码的编写量和重复性。泛型编程利用的主要技术是模板。
模板
C++中有两种模板机制:函数模板,类模板
函数模板
- 函数的返回类型和形参的类型可以不具体指定,用代号T来表示,T称为泛型(generic type)。使用这种类型占位符的编程方式就叫泛型编程。
- 在函数调用的时候再去确认T的实际类型。
- 语法: template, 在有了这个声明后,其下紧跟的函数就是一个函数模板。template:声明创建模板,typename:表明其后的符号代表一种数据类型,也可以用class代替。后面可以有多个typename。T:通用数据类型,也可以用其它符号代替。
- 函数模板的两种实现方式
– 自动类型转换
– 显式指定类型,推荐。
code:
# include <iostream>
using namespace std;template <typename T> // 声明一个模板,告诉编译器后面代码中紧跟着的T是一个通用数据模型
T max_2(T a, T b) // T是抽象的一个类型,在使用的时候再确定具体类型
{T result = a > b ? a : b;cout << "the max value: " << result << endl;return result;
}template <typename T> // 声明一个模板,告诉编译器后面代码中紧跟着的T是一个通用数据模型
void swap_2(T &a, T &b) // T是抽象的一个类型,在使用的时候再确定具体类型
{T temp = a;a = b;b = temp;
}int main()
{max_2(8, 5);max_2(8.6, 9.5);int a = 666;int b = 888;cout << "原始的a: " << a << ", 原始的b: " << b << endl;swap_2(a, b); // 模板实现的方式是自动类型转换cout << "交换后的a: " << a << ", 交换后的b: " << b << endl;float c = 666.666;float d = 888.888;cout << "原始的c: " << c << ", 原始的d: " << d << endl;swap_2<float>(c, d); // 模板实现的方式是显式指定类型cout << "交换后的c: " << c << ", 交换后的d: " << d << endl;system("pause");return 0;
}result:
the max value: 8
the max value: 9.5
原始的a: 666, 原始的b: 888
交换后的a: 666, 交换后的b: 888
原始的c: 666.666, 原始的d: 888.888
交换后的c: 888.888, 交换后的d: 666.666
函数模板注意事项
- 如果使用自动类型推导,如果只使用类型T,则必须要能推导出一致的数据类型T。
- T必须要有确定的类型,可以使用显示指示类型方式直接指定。
code:
#include <iostream>
using namespace std;template <typename T>
void swap_1(T& a, T& b) // T是抽象的一个类型,在使用的时候再确定具体类型
{T temp = a;a = b;b = temp;
}template <typename T>
void print_info()
{cout << "print_info函数" << endl;
}int main()
{int a = 11; int b = 22;char c = 'g';//swap_1(a, c); // 错误,在模板中类型T,函数调用是有int,有char,无法推导出T的类型cout << "原始的a: " << a << ", 原始的b: " << b << endl;swap_1(a, b); // 模板实现的方式是自动类型转换cout << "交换后的a: " << a << ", 交换后的b: " << b << endl;// print_info(); // 错误,没有确定的T,无法使用自动类型推导的方式print_info<int>(); // 使用显示指定类型,T有了确定的类型system("pause");return 0;
}result:
原始的a: 11, 原始的b: 22
交换后的a: 22, 交换后的b: 11
print_info函数
函数模板举例
code:
#include <iostream>
using namespace std;
template <typename T>
void my_swap(T &a, T &b)
{T temp = a;a = b;b = temp;
}template <typename T>
void my_sort(T array[], int num) // T是抽象的一个类型,在使用的时候再确定具体类型
{for(int i_loop=0; i_loop<num; i_loop++){int max_idx = i_loop;for (int j_loop = i_loop + 1; j_loop < num; j_loop++){if (array[j_loop] > array[max_idx]){my_swap(array[j_loop], array[max_idx]);}}}
}template <typename T>
void print_info(T array[], int num)
{for (int i_loop = 0; i_loop < num; i_loop++){cout << array[i_loop] << " ";}cout << endl;
}template <typename T>
void test01(T array, int num) // 这里的T是个指针类型的
{cout << "排序前的数据: ";print_info(array, num);my_sort(array, num);cout << "排序后的数据: ";print_info(array, num);
}int main()
{int array1[] = { 1,0,-2,4,8,10,6,9,44 };int num1 = sizeof(array1) / sizeof(array1[0]);char array2[] = "afgbdozs";int num2 = sizeof(array1) / sizeof(array1[0]);test01(array1, num1);test01<char*>(array2, num2);system("pause");return 0;
}result:
排序前的数据: 1 0 -2 4 8 10 6 9 44
排序后的数据: 44 10 9 8 6 4 1 0 -2
排序前的数据: a f g b d o z s
排序后的数据: z s o g f d b a
普通函数和函数模板的区别
隐式类型转换上的区别
- 普通函数调用时可以发生自动类型转换(隐式类型转换)。
- 函数模板调用时,如果使用自动类型推导,不发生隐式类型转换。
- 函数模板调用时,如果使用显式指定类型的方式,可以发生隐式类型转换。
code:
#include <iostream>
using namespace std;
template <typename T>
void my_swap(T& a, T& b)
{T temp = a;a = b;b = temp;
}void my_add01(int a, int b)
{ cout << "add result: " << a + b << endl;
}template <typename T>
void my_add02(T a, T b) // T是抽象的一个类型,在使用的时候再确定具体类型
{cout << "add result: " << a + b << endl;
}int main()
{int a = 68;int b = 22;char c = 'a';cout << "普通函数调用" << endl;my_add01(a, b);my_add01(a, c);cout << "函数模板自动类型推导调用" << endl;my_add02(a, b);// my_add02(a, c); // 错误,无法实现隐式类型转换,不知道替换成是int还是charcout << "函数模板显式指定类型调用" << endl;my_add02<int>(a, b);my_add02<int>(a, c);system("pause");return 0;
}result:
普通函数调用
add result: 90
add result: 165
函数模板自动类型推导调用
add result: 90
函数模板显式指定类型调用
add result: 90
add result: 165
调用上的规则和区别
既然有函数模板,就不要再提供普通函数了,不明确。实际开发中最好不要同时出现二者。
- 如果普通函数和函数模板都可以调用,优先普通函数。
- 可以使用显式指定类型的方式明确调用函数模板,也可以在类型指定中指定为空,强制调用。
- 函数模板也可以实现重载。
- 如果函数模板产生更好的匹配,则优先调用函数模板。
code:
// 如果普通函数和函数模板都可以调用,优先普通函数。
#include <iostream>
using namespace std;void print_info(int a)
{cout << "调用的是普通函数" << endl;
}template<typename T>
void print_info(T a)
{cout << "T的类型" << typeid(T).name() << endl;
}template<typename T1, typename T2>
void print_info(T1 a, T2 b)
{cout << "T1的类型" << typeid(T1).name() << endl;cout << "T2的类型" << typeid(T2).name() << endl;
}int main()
{cout << "---------- 1 -----------" << endl;print_info(3); // 默认是调用普通函数的cout << "---------- 2 -----------" << endl;print_info<>(3); // 通过空模板参数列表实现函数模板的调用,也可以显式指定类型法cout << "---------- 3 -----------" << endl;print_info<int, char>(33, 'a'); // 函数模板也可以发生重载cout << "---------- 4 -----------" << endl;print_info<int>(66); // 函数模板也可以发生重载cout << "---------- 5 -----------" << endl;// 如果使用函数模板,则会直接将T指定为char,而如果使用普通函数,则需要进行隐式类型转换// 函数模板产生更好的匹配,则优先调用函数模板system("pause");print_info('c'); return 0;
}result:
---------- 1 -----------
调用的是普通函数
---------- 2 -----------
T的类型int
---------- 3 -----------
T1的类型int
T2的类型char
---------- 4 -----------
T的类型int
---------- 5 -----------
模板的局限性
- 模板并非是万能的,有些特定的数据类型,需要做可特殊的实现。
先看如下代码:
如果func1中传的参数是数组,则在调用阶段会出错。
如果func2中传的参数是类的对象,按照什么比较大小?
template<typename T>
void func1(T a, T b)
{a = b;
}template<typename T>
T func2(T a, T b)
{return a > b ? a : b;
}
利用具体化的模板,可以解决自定义类型的通用化
- 下面代码中,在my_compare函数中,如果传递的参数是Person类型的,是无法比较大小的,所以单独对Person类型的实现具体化的代码,则当传递的参数是Person类型时,会走具体化的代码。
- template<> bool my_compare(Person a, Person b), 前面要有template<> ,函数定义中的参数类型直接指明为Person。
code:
#include <iostream>
using namespace std;class Person
{
public:Person(string name, int age){m_name = name;m_age = age;}string m_name;int m_age;
};template<typename T>
bool my_compare(T a, T b)
{cout << "T的类型: " << typeid(T).name() << endl;//return (a == b) ? true : false; // 如果T是Person,这里的比较会报错,无法用==比较,除非==重载return true;
}// 利用具体化的Person的代码实现,具体化调用,当传递T是Person时,走这段代码
template<>
bool my_compare(Person a, Person b)
{if ((a.m_name == b.m_name) && (a.m_age == b.m_age)){return true;}else{return false;}
}void test01()
{Person p1("Jerry", 2);Person p2("Tom", 5);cout << (my_compare(p1, p2) ? "a==b" : "a!=b") << endl;my_compare(3, 5);
}
int main()
{test01();system("pause");return 0;
}result:
a!=b
T的类型: int
学习模板的意义
- 一般不是为了写模板,而是在STL能够运用系统提供的模板。
注意:本文部分内容来自黑马