目录
1、函数与宏
2、函数模板
3、模板的两次编译
4、多参数函数模板
5、重载函数模板
6、C++中的类模板
7、模板的特化
8、继承模板类访问基类成员
9、typename与class
10、数组类模板
11、单例类模板
12、判断变量是不是指针
宏代码块
优点:代码复用,适合所有的类型 。缺点:编译器不知道宏的存在,缺少类型检查
函数
优点:真正的函数调用,编译器对类型进行检查
缺点:根据类型重复定义函数,无法代码复用
#include <iostream> using namespace std; #define SWAP(t, a, b) \ do \ { \ t c = a; \ a = b; \ b = c; \ }while(0) void Swap(int& a, int& b) { int c = a; a = b; b = c; } void Swap(double& a, double& b) { double c = a; a = b; b = c; } void Swap(string& a, string& b) { string c = a; a = b; b = c; } int main() { int a = 0; int b = 1; Swap(a, b); cout << "a = " << a << endl; cout << "b = " << b << endl; double m = 2; double n = 3; Swap(m, n); cout << "m = " << m << endl; cout << "n = " << n << endl; string d = "Nyist"; string t = "Wss"; Swap(d, t); cout << "d = " << d << endl; cout << "t = " << t << endl; return 0; }泛型编程的概念 :不考虑具体数据类型的编程方式
C++中的函数模板 :—种特殊的函数可用不同类型进行调用 ,看起来和普通函数很相似,区别是类型可被参数化
使用函数模板时:可以自动类型推导调用 ,也可以具体类型显示调用
本质:模板参数是在编译阶段被处理的单元,因此,在编译阶段必须准确无误的唯—确
#include <iostream> using namespace std; // template关键字用于声明开始进行泛型编程 // typename关键字用于声明泛指类型 template <typename T> void Swap(T& a, T& b) // Swap泛型写法中的T不是—个具体的数据类型,而是泛指任意的数据类型。 { T t = a; a = b; b = t; } template < typename T > void Sort(T a[], int len) { for(int i=0; i<len; i++) { for(int j=i; j<len; j++) { if( a[i] > a[j] ) // comparable { Swap(a[i], a[j]); } } } } template < typename T > void Println(T a[], int len) { for(int i=0; i<len; i++) { cout << a[i] << ", "; } cout << endl; } template < typename T, int N > // N不能接收变量 void func() { T a[N] = {0};// 使用模板参数定义局部数组 for(int i=0; i<N; i++) { a[i] = i; } for(int i=0; i<N; i++) { cout << a[i] << endl; } } int main() { int a = 0; int b = 1; Swap(a, b); // 自动推导 float c = 2; float d = 3; Swap<float>(c, d); // 显式调用 func<double, 10>(); int arr[5] = {5, 3, 2, 4, 1}; Println(arr, 5); Sort(arr, 5); Println(arr, 5); string s[5] = {"Java", "C++", "Pascal", "Ruby", "Basic"}; Println(s, 5); Sort(s, 5); Println(s, 5); return 0; }
编译器从函数模板通过具体类型产生不同的函数 ,编译器会对函数模板进行两次编译:对模板代码本身进行编译 ,对参数替换后的代码进行编译
函数模板本身不允许隐式类型转换 ,自动推导类型时,必须严格匹配 ;显式类型指定时,能够进行隐式类型转换
#include <iostream> using namespace std; class Test { Test(const Test&); // private,不能拷贝构造 public: Test(){} }; template < typename T > void Swap(T& a, T& b) // 先会对模板代码本身进行编译,确保无语法错误,替换参数类型后再次编译,确保再次无语法错误 { T c = a; // T如果是Test就会编译报错,因为Test拷贝构造函数访问权限为private a = b; b = c; } typedef void(FuncI)(int&, int&); typedef void(FuncD)(double&, double&); typedef void(FuncT)(Test&, Test&); int main() { FuncI* pi = Swap; // 编译器自动推导 T 为 int FuncD* pd = Swap; // 编译器自动推导 T 为 double //FuncT* pt = Swap; // 编译器自动推导 T 为 Test ,编译出错,说明二次编译 cout << "pi = " << reinterpret_cast<void*>(pi) << endl; cout << "pd = " << reinterpret_cast<void*>(pd) << endl; //cout << "pt = " << reinterpret_cast<void*>(pt) << endl; return 0; }
函数模板可以定义任意多个不同的类型参数,对于多参数函数模板 无法自动推导返回值类型 ,
可以从左向右部分指定类型参数 ,工程中将返回值参数作为第一个类型的参数
#include <iostream> using namespace std; template < typename T1, typename T2> void Print(T1 a, T2 b) { cout << a << "\t" << b << endl; } template < typename T1, typename T2, typename T3 > T1 Add(T2 a, T3 b) { return static_cast<T1>(a + b); } int main() { Print(1, "wss"); // T1 = int, T2 = double, T3 = double int r1 = Add<int>(0.5, 0.8); // 由于无法自动推导返回值类型,可以从左向右部分指定类型参数 // T1 = double, T2 = float, T3 = double double r2 = Add<double, float>(0.5, 0.8); // T1 = float, T2 = float, T3 = float float r3 = Add<float, float, float>(0.5, 0.8); cout << "r1 = " << r1 << endl; // r1 = 1 cout << "r2 = " << r2 << endl; // r2 = 1.3 cout << "r3 = " << r3 << endl; // r3 = 1.3 return 0; }函数模板可以像普通函数—样被重载
C++编译器优先考虑普通函数 ,如果函数模板可以产生—个更好的匹配,那么选择模板
可以通过空模板实参列表限定编译器只匹配模板
#include <iostream> using namespace std; template < typename T > T Max(T a, T b) { cout << "T Max(T a, T b)" << endl; return a > b ? a : b; } 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, T c) { cout << "T Max(T a, T b, T c)" << endl; return Max(Max(a, b), c); } int main() { int a = 1; int b = 2; cout << Max(a, b) << endl; // 普通函数 Max(int, int) cout << Max<>(a, b) << endl; // 函数模板 Max<int>(int, int) cout << Max(3.0, 4.0) << endl; // 函数模板 Max<double>(double, double) cout << Max(5.0, 6.0, 7.0) << endl; // 函数模板 Max<double>(double, double, double) cout << Max('a', 100) << endl; // 普通函数 Max(int, int) return 0; }函数模板能够根据实参对参数类型进行推导 ,函数模板支持显示的指定参数类型
函数模板通过具体类型产生不同的函数 ,函数模板可以定义任意多个不同的类型参数
函数模板可以像普通函数—样被重载
类模板
一些类主要用于存储和组织数据元素 ,类中数据组织的方式和数据元素的具体类型无关 。如:数组类,链表类,Stack类,Queue类,等
C++中将模板的思想应用于类,使得类的实现不关注数据元素的具体类型,而只关注类所需要实现的功能。
C++中的类模板
以相同的方式处理不同的类型 ,在类声明前使用template进行标识 ,< typename T >用于说明类中使用的泛指类型T
声明的泛指类型T可以出现在类模板的任意地方 ,编译器对类模板的处理方式和函数模板相同
☛ 从类模板通过具体类型产生不同的类
☛ 在声明的地方对类模板代码本身进行编译
☛ 在使用的地方对参数替换后的代码进行编译
类模板的使用
只能显式指定具体类型,无法自动推导 ,使用具体类型 <Type> 定义对象 → Operator<int> op1;
#include <iostream> #include <string> using namespace std; string operator - (string& l, string& r) { return "Minus"; } template < typename T > class Operator { public: T add(T a, T b) { return a + b; } T minus(T a, T b) { return a - b; } T multiply(T a, T b) { return a * b; } T divide(T a, T b) { return a / b; } }; int main() { Operator<int> op1; cout << op1.add(1, 2) << endl; Operator<string> op2; // 必须重载-操作符 ,否则第二次编译错误 cout << op2.add("a", "b") << endl; cout << op2.minus("a", "b") << endl; return 0; }类模板的工程应用:类模板必须在头文件中定义 ,类模板不能分开实现在不同的文件中 ,类模板外部定义的成员函数需要加上模板<>声明
#ifndef _OPERATOR_H_ #define _OPERATOR_H_ template < typename T > class Operator { public: T add(T a, T b); T minus(T a, T b); T multiply(T a, T b); T divide(T a, T b); }; template < typename T > T Operator<T>::add(T a, T b) { return a + b; } template < typename T > T Operator<T>::minus(T a, T b) { return a - b; } template < typename T > T Operator<T>::multiply(T a, T b) { return a * b; } template < typename T > T Operator<T>::divide(T a, T b) { return a / b; } #endif #include <iostream> #include "Operator.h" using namespace std; int main() { Operator<int> op1; cout << op1.add(1, 2) << endl; cout << op1.multiply(4, 5) << endl; cout << op1.minus(5, 6) << endl; cout << op1.divide(10, 5) << endl; return 0; }
类模板可以定义任意多个不同的类型参数
类模板可以被特化
-指定类模板的特定实现 ,部分类型参数必须显式指定 ,根据类型参数分开实现类模板
-特化只是模板的分开实现 ,本质上是同—个类模板 ;特化类模板的使用方式是统—的,必须显式指定每—个类型参数
类模板的特化类型
-部分特化-用特定规则约束类型参数
-完全特化-完全显式指定类型参数
#include <iostream> using namespace std; template <typename T1, typename T2> class Test{ public: Test(){cout << "template <typename T1, typename T2>" << endl;} }; // 部分特化 template <typename T> class Test<T, T>{ public: Test(){cout << "Test<T, T>" << endl;} }; // 部分特化 template <typename T> class Test<T, int>{ public: Test(){cout << "Test<T, int>" << endl;} }; // 完全特化 template <> class Test<int, int>{ public: Test(){cout << "Test<int, int>" << endl;} }; int main() { Test<double, short> t0; Test<double, double> t1; Test<double, int> t2; Test<int, int> t3; return 0; }#include <iostream> using namespace std; template <typename T1, typename T2> class Test { public: void add(T1 a, T2 b) { cout << "void add(T1 a, T2 b)" << endl; cout << a + b << endl; } }; // 关于指针的特化实现 template <typename T1, typename T2> class Test <T1*, T2*> { public: void add(T1* a, T2* b) { cout << "void add(T1* a, T2* b)" << endl; cout << *a + *b << endl; } }; // 当 Test 类模板的两个类型参数完全相同时,使用这个实现 template <typename T> class Test <T, T> { public: void add(T a, T b) { cout << "void add(T a, T b)" << endl; cout << a + b << endl; } void print() { cout << "class Test < T, T >" << endl; } }; // 当 T1 == void* 并且 T2 == void* 时 ,完全特化 template <> class Test < void*, void* > { public: void add(void* a, void* b) { cout << "void add(void* a, void* b)" << endl; cout << "Error to add void* param..." << endl; } }; int main() { Test<int, float> t1; Test<long, long> t2; Test<void*, void*> t3; t1.add(1, 2.5); t2.add(5, 5); t2.print(); t3.add(NULL, NULL); Test<int*, double*> t4; int a = 1; double b = 0.1; t4.add(&a, &b); return 0; }
重定义和特化的不同
重定义 : —个类模板和—个新类(或者两个类模板) ,使用的时候需要考虑如何选择的问题
特化 :以统—的方式使用类模板和特化类 ,编译器自动优先选择特化类
函数模板的特化: 函数模板只支待类型参数完全特化
工程中的建议:当需要重载函数模板时,优先考虑使用模板特化 ,当模板特化无法满足需求,再使用函数重载!
template < typename T > // 函数模板定义 bool Equal(T a, T b) { return a == b; } template < > // 函数模板完全特化 bool Equal<void*>(void* a, void* b) { return a == b; } #include <iostream> using namespace std; template < typename T1, typename T2 > class Test { public: void add(T1 a, T2 b) { cout << "void add(T1 a, T2 b)" << endl; cout << a + b << endl; } }; // 重定义 class Test_Void { public: void add(void* a, void* b) { cout << "void add(void* a, void* b)" << endl; cout << "Error to add void* param..." << endl; } }; template < typename T > bool Equal(T a, T b) { cout << "bool Equal(T a, T b)" << endl; return a == b; // 两个浮点数不能直接比较 } template < > // 函数模板的完全特化 bool Equal<double>(double a, double b) { const double delta = 0.00000000000001; double r = a - b; cout << "bool Equal<double>(double a, double b)" << endl; return (-delta < r) && (r < delta); } bool Equal(double a, double b) // 重载 { const double delta = 0.00000000000001; double r = a - b; cout << "bool Equal(double a, double b)" << endl; return (-delta < r) && (r < delta); } int main() { cout << Equal( 1, 1 ) << endl; // bool Equal(T a, T b) cout << Equal<>( 0.001, 0.001 ) << endl; // <>使用模板,否则会优先选择重载函数 return 0; }有趣的面试题
用最高效的方法求 1 + 2 + 3 + … + N 的值!
#include <iostream> using namespace std; template < int N > class Sum { public: static const int VALUE = Sum<N-1>::VALUE + N; }; template < > class Sum < 1 > { public: static const int VALUE = 1; // VALUE进入符号表,且会分配空间在全局数据区 }; // 先编译发现语法无误,带入参数10后再次编译,即编译期就可以确定VALUE的值 int main() { cout << "1 + 2 + 3 + ... + 10 = " << Sum<10>::VALUE << endl; // 仅仅访问常量值,编译阶段已经得到结果 cout << "1 + 2 + 3 + ... + 100 = " << Sum<100>::VALUE << endl; //cout << sizeof(Sum<10>) << endl; // 1 ,可以说Sum是空类 return 0; }
继承模板类要使用this指针或Base::访问基类成员
本质就是二次编译
第一阶段编译时对模板代码本身编译,确定语法等无误。此时子类继承父类,父类也是第一阶编译还未成型被忽略,
此时子类使用父类成员时必须显式指明。如:使用this表明变量就是本对象里有的 或 加上作用域指明父类有这个变量
第二阶段编译类型参数被替换,父子关系模型确认
#include <iostream> using namespace std; template <typename T> class Base { protected: T* m_array; int length; public: void print() { cout << "OK" << endl; } }; template <typename T, int N> class Derived : public Base<T> { T m_space[N]; public: Derived() { this->Base<T>::m_array = m_space; // this->m_array = m_space; this->length = 0; // Base<T>::m_length = 0; } }; int main() { Derived<int, 5> d; d.print(); // OK return 0; }令人郁闷的是在VS2015中测试,不加this也可以正常使用。。。但绝不编写依赖编译器的代码。。。
为什么模板中声明泛指类型有的使用typename,有的使用class?
如下面代码使用typename与class没有区别
#include <iostream> using namespace std; template < class T > // template < typename T > class Test { public: Test(T t) { cout << "t = " << t << endl; } }; template < class T > void func(T a[], int len) { for(int i=0; i<len; i++) { cout << a[i] << endl; } } int main() { Test<string> ts("aaa"); string ta[]={"W","W","W","W"}; func(ta, 4); Test<int> ti(100); int ai[]={1, 2, 3, 4}; func(ai, 4); return 0; }历史上的原因
-早期的C++直接复用class关键字来定义模板 ,但是泛型编程针对的不只是类类型 ,class关键字的复用使得代码出现二义性
typename诞生的直接诱因
-自定义类类型内部的嵌套类型 ,不同类中的同一个标识符可能导致二义性 ,编译器无法辨识标识符究竟是什么
-typename可以消除模板中的二义性
#include <iostream> using namespace std; int a = 0; class Test_1 { public: static const int TS = 1; }; class Test_2 { public: struct TS { int value; }; }; template < class T > void test_class() { // 两种理解方式 - 二义性 T::TS * a; // 1. 通过泛指类型 T 内部的数据类型 TS 定义指针变量 a (推荐的解读方式) // 2. 使用泛指类型 T 内部的静态成员变量 TS 与全局变量 a 进行乘法操作 } int main(int argc, char *argv[]) { test_class<Test_1>(); test_class<Test_2>(); // error: missing 'typename' prior to dependent type name 'Test_2::TS' // 编译器默认认为Test_2::TS是一个静态成员变量而不是数据类型 // 解决方案: typename T::TS * a; // typename的两个作用:1. 在模板定义中声明泛指类型 2. 明确告诉编译器其后的标识符为类型 // 所以此时test_class<Test_1>();会编译出错 return 0; }静态数组类模板
#ifndef _ARRAY_H_ #define _ARRAY_H_ template < typename T, int N > class Array { T m_array[N]; public: int length(); bool set(int index, T value); bool get(int index, T& value); T& operator[] (int index); T operator[] (int index) const; virtual ~Array(); }; template < typename T, int N > int Array<T, N>::length() { return N; } template < typename T, int N > bool Array<T, N>::set(int index, T value) { bool ret = (0 <= index) && (index < N); if( ret ) { m_array[index] = value; } return ret; } template < typename T, int N > bool Array<T, N>::get(int index, T& value) { bool ret = (0 <= index) && (index < N); if( ret ) { value = m_array[index]; } return ret; } template < typename T, int N > T& Array<T, N>::operator[] (int index) { return m_array[index]; } template < typename T, int N > T Array<T, N>::operator[] (int index) const { return m_array[index]; } template < typename T, int N > Array<T, N>::~Array() { } #endif堆数组模板类
#ifndef _HEAPARRAY_H_ #define _HEAPARRAY_H_ template < typename T > class HeapArray { private: int m_length; T* m_pointer; HeapArray(int len); HeapArray(const HeapArray<T>& obj); bool construct(); public: static HeapArray<T>* NewInstance(int length); int length(); bool get(int index, T& value); bool set(int index ,T value); T& operator [] (int index); T operator [] (int index) const; HeapArray<T>& self(); ~HeapArray(); }; template < typename T > HeapArray<T>::HeapArray(int len) { m_length = len; } template < typename T > bool HeapArray<T>::construct() { m_pointer = new T[m_length]; return m_pointer != NULL; } template < typename T > HeapArray<T>* HeapArray<T>::NewInstance(int length) { HeapArray<T>* ret = new HeapArray<T>(length); if( !(ret && ret->construct()) ) { delete ret; ret = 0; } return ret; } template < typename T > int HeapArray<T>::length() { return m_length; } template < typename T > bool HeapArray<T>::get(int index, T& value) { bool ret = (0 <= index) && (index < length()); if( ret ) { value = m_pointer[index]; } return ret; } template < typename T > bool HeapArray<T>::set(int index, T value) { bool ret = (0 <= index) && (index < length()); if( ret ) { m_pointer[index] = value; } return ret; } template < typename T > T& HeapArray<T>::operator [] (int index) { return m_pointer[index]; } template < typename T > T HeapArray<T>::operator [] (int index) const { return m_pointer[index]; } template < typename T > HeapArray<T>& HeapArray<T>::self() { return *this; } template < typename T > HeapArray<T>::~HeapArray() { delete[]m_pointer; } #endifmain.cpp
#include <iostream> #include <string> #include "Array.h" #include "HeapArray.h" using namespace std; int main() { Array<double, 5> ad; for(int i=0; i<ad.length(); i++) { ad[i] = i * i; } for(int i=0; i<ad.length(); i++) { cout << ad[i] << endl; } cout << endl; HeapArray<char>* pai = HeapArray<char>::NewInstance(10); if( pai != NULL ) { HeapArray<char>& ai = pai->self(); for(int i=0; i<ai.length(); i++) { ai[i] = i + 'a'; } for(int i=0; i<ai.length(); i++) { cout << ai[i] << endl; } } delete pai; return 0; }
定义—个类,使得这个类最多只能创建—个对象
要控制类的对象数目,必须对外隐藏构造函数,将构造函数的访问属性设置为private
定义instance并初始化为NULL ,当需要使用对象时,访问instance的值
- 如果是空值:创建对象,并用instance标记
- 如果是非空值:返回instance标记的对象
#include <iostream> using namespace std; class SObject { static SObject* c_instance; SObject(const SObject&); SObject& operator= (const SObject&); SObject() { //隐藏构造函数,达到无法在外部创建对象的目的 } public: static SObject* GetInstance(); //提供一个接口创建和访问唯一的对象 void print() { cout << "this = " << this << endl; } }; SObject* SObject::c_instance = NULL; SObject* SObject::GetInstance() { if( c_instance == NULL ) { c_instance = new SObject(); } return c_instance; } int main() { SObject* s = SObject::GetInstance(); SObject* s1 = SObject::GetInstance(); SObject* s2 = SObject::GetInstance(); s->print(); s1->print(); s2->print(); return 0; }
存在的问题 :需要使用单例模式时必须定义静态成员变量c_instance ,必须定义静态成员函数Getlnstance()
解决方案 :将单例模式相关的代码抽取出来,开发单例类 模板。当需要单例类时,直接使用单例类模板
#ifndef _SINGLETON_H_ #define _SINGLETON_H_ template < typename T > class Singleton { static T* c_instance; public: static T* GetInstance(); }; template < typename T > T* Singleton<T>::c_instance = NULL; template < typename T > T* Singleton<T>::GetInstance() { if( c_instance == NULL ) { c_instance = new T(); } return c_instance; } #endif #include <iostream> #include "Singleton.h" using namespace std; class SObject { friend class Singleton<SObject>; // 当前类需要使用单例模式 SObject(const SObject&); SObject& operator= (const SObject&); SObject() { } public: void print() { cout << "this = " << this << endl; } }; int main() { SObject* s = Singleton<SObject>::GetInstance(); SObject* s1 = Singleton<SObject>::GetInstance(); SObject* s2 = Singleton<SObject>::GetInstance(); s->print(); s1->print(); s2->print(); return 0; }C++中仍然支持C语言中的可变参数函数 ,C++编译器的匹配调用优先级: 1. 重载函数 2. 函数模板 3. 变参函数
指针的判别
将变量分为两类:指针 vs 非指针
利用函数模板和变参函数能够判断指针变量
编写函数:指针变量调用时返回true ,非指针变量调用时返回false
// 函数模板与变参函数的化学反应 template <typename T> bool IsPtr(T* v) // match pointer { return true; } bool IsPtr(...) // match non-pointer { return false; } #include <iostream> using namespace std; class Test { public: Test() { } virtual ~Test() { } }; template <typename T> bool IsPtr(T* v) // match pointer { return true; } bool IsPtr(...) // match non-pointer { return false; } int main(int argc, char *argv[]) { int i = 0; int* p = &i; cout << "p is a pointer: " << IsPtr(p) << endl; // 1 cout << "i is a pointer: " << IsPtr(i) << endl; // 0 Test t; Test* pt = &t; cout << "pt is a pointer: " << IsPtr(pt) << endl; // 1 /* * error: cannot pass object of non-trivial type 'Test' through variadic function; call will abort at runtime */ cout << "t is a pointer: " << IsPtr(t) << endl; // 0 或 程序崩溃 return 0; }存在的缺陷: 变参函数无法解析对象参数,可能造成程序崩溃!
进—步的挑战: 如何让编译器精确匹配函数,但不进行实际的调用?
完美解决方案:
#include <iostream> using namespace std; class Test { public: Test() { } virtual ~Test() { } }; template <typename T> char IsPtr(T* v) // match pointer { return 'd'; } int IsPtr(...) // match non-pointer { return 0; } #define ISPTR(p) (sizeof(IsPtr(p)) == sizeof(char)) // 通过使用sizeof在编译期间就能确定ISPTR(p)的值 int main(int argc, char *argv[]) { int i = 0; int* p = &i; cout << "p is a pointer: " << ISPTR(p) << endl; // true cout << "i is a pointer: " << ISPTR(i) << endl; // false Test t; Test* pt = &t; cout << "pt is a pointer: " << ISPTR(pt) << endl; // true cout << "t is a pointer: " << ISPTR(t) << endl; // false return 0; }