C++STL - 模板的其他特性
之前已经总结过函数模板和类模板了,对于模板还有一些其他的特性,这篇主要介绍这些特性.主要都是一些特殊情况.
模板的其他特性
1.缺省参数
(1)类模板的模板参数可以带有缺省值,实例化该模板时,如果提供了相应的实参,则忽略缺省值,反之则以缺省作为对应形参的值
(2)如果某个模板参数带有缺省值,那么它后面的所有参数都必须带有缺省值
(3)C++98不允许为函数模板的参数指定缺省值<尖括号里面的>, c++11允许
编译的时候需要加上 -std=c++11
(4)对于函数模板,如果模板参数的缺省值与隐式推断的类型不一致,以隐式推断的类型为准,忽略其缺省值.==>c++11标准下.
#include <iostream> #include <typeinfo> using namespace std; template<typename A = int,typename B = double, typename C = string> //template<typename A,typename B = double, typename C = string> //ok //template<typename A = int,typename B, typename C = string> //error //template<typename A = int,typename B = double, typename C> //error class X { public: static void foo (void) { cout << typeid (A).name () << ' ' << typeid (B).name () << ' ' << typeid (C).name () << endl; } }; int _x = 200; void foo (int x, int y = /*x*/_x) { cout << x << ' ' << y << endl; } template<typename A, typename B = A> //template<typename A = B, typename B = int> //error class Y { public: static void foo (void) { cout << typeid (A).name () << ' ' << typeid (B).name () << endl; } }; template<typename A = int, typename B = double, typename C = string> void fun (void) { cout << typeid (A).name () << ' ' << typeid (B).name () << ' ' << typeid (C).name () << endl; } template<typename T = int> void bar (T arg) { cout << typeid (arg).name () << endl; } int main (void) { X<char, short, long>::foo (); // c s l X<char, short>::foo (); // c s Ss X<char>::foo (); // c d Ss // X<>::foo (); // i d Ss foo (100); // PUSH ... 100 // PUSH ... _x Y<long long>::foo (); // Y<>::foo (); fun<char, short, long> (); fun<char, short> (); fun<char> (); fun<> (); fun (); bar (1.23); return 0; }
2.非类型参数
(1)模板处理可以接受类型参数意外,也可以接受非类型参数,即数值参数,非类型参数不能用typename声明,而要注明其具体类型,而且传递给模板非类型参数的实参必须是常量,常量表达式,或者带有常属性(const限定)的变量,但是不能同时具有挥发性(volatile限定).
#include <iostream> #include <iomanip> using namespace std; template<typename T = int, size_t S = 3> class Array { public: T& operator[] (size_t i) { return m_a[i]; } T const& operator[] (size_t i) const { return const_cast<Array&> (*this)[i]; } T m_a[S]; }; int square (int x) { return x * x; } template<int x> int square (void) { return x * x; } /* int square (void) { return 100;} */ int main (void) { Array<int> a; /* a.m_a[0] = 10; a.m_a[1] = 20; a.m_a[2] = 30; */ a[0] = 10; // a.operator[] (0) = 10; a[1] = 20; a[2] = 30; Array<int> const b = a; // cout << b.m_a[0] << ' ' << b.m_a[1] << ' ' << b.m_a[2] << endl; cout << b[0] << ' ' << b[1] << ' ' << b[2] << endl; Array<string> c; c[0] = "北京"; c[1] = "上海"; c[2] = "广州"; cout << c[0] << ' ' << c[1] << ' ' << c[2] << endl; int const /*volatile */x = 2, y = 1; Array<Array<int, x+y+1>, 1+1+1> d; // Array<Array<int, 2+1+1>, 1+1+1> d; for (int i = 0; i < 3; ++i) for (int j = 0; j < 4; ++j) d[i][j] = i * 4 + j + 1; // d.operator[](i).operator[](j) = ...; for (int i = 0; i < 3; ++i) { for (int j = 0; j < 4; ++j) cout << setw (2) << d[i][j] << ' '; cout << endl; } Array<Array<Array<int> > > e; cout << square (10) << endl; cout << square<10> () << endl; return 0; }
(2)向模板传递字符串形式的非类型参数:形参必须使用字符指针,实参必须使用非静态全局字符数组
(3)模板的浮点型参数只能使用整数类型???
3.typename关键字
(1)在模板参数被实例化之前,模板参数所表示的具体类型并不确定,编译器会把依赖于模板参数的嵌套(内部)类型理解为某个类的静态成员变量,当其看到用该变量定义其他变量的代码时,会报告错误.
(2)typename关键字可以告诉编译器,其后的标识符不是静态成员变量,而是某种类型,编译器就会将其有关该编译的类型检查推迟到模板实例化过程中,避免编译错误 ----- 解决嵌套依赖.
class关键字:1)声明类 2)声明模板的类型实参typename关键字:1)解决嵌套依赖 2)声明模板的类型参数struct关键字:声明类
#include <iostream> using namespace std; class A { public: typedef unsigned int uint; class B {}; }; template<typename T> void foo (void) { typename T::uint u; typename T::B b; } void bar (void) {} int main (void) { A::uint u; A::B b; foo<A> (); return 0; }
4.template关键字作用
(1)声明函数模板和类型模板
(2)解决嵌套模板:
依赖于模板参数的类型的内部模板
在模板代码中,通过依赖于模板参数的对象,引用或指针,访问其带有模板特性的成员需要使用template关键字显示指明其后的名称是一个模板,避免编译器将模板参数表的左右尖括号理解为小于号和大于号,导致编译失败
#include <iostream> #include <typeinfo> using namespace std; template<typename T> class A { public: // 嵌套模板函数 template<typename U> void foo (void) const { U var; cout << typeid (m_var).name () << ' ' << typeid (var).name () << endl; } private: T m_var; }; template<typename T> void bar (T const& a, T const* b) { a.template foo<double> (); b->template foo<double> (); } int main (void) { A<int> a; a.foo<double> (); bar (a, &a); return 0; }
5.子类访问基类模板
在子类模板中访问那些在基类模板中声明且依赖于模板参数的符号,应该在前面加上作用域限定符::,或显示使用this指针,否则编译器将试图在全局域中寻找该符号,引发错误.
#include <cstdlib> #include <iostream> using namespace std; class A { public: int m_var; void foo (void) const {} class X {}; }; class B : public A { public: void bar (void) const { cout << /*A::*/m_var << endl; /*A::*/foo (); /*A::*/X x; } }; template<typename T> class C { public: int m_var; void foo (void) const {} class X {}; void exit (int status) const { cout << "再见!" << endl; } }; template<typename T> class D : public C<T> { public: void bar (void) const { // cout << C<T>::m_var << endl; cout << this->m_var << endl; // C<T>::foo (); this->foo (); typename C<T>::X x; // C<T>::exit (0); this->exit (0); } }; int main (void) { B b; b.bar (); D<int> d; d.bar (); return 0; }
6.模板型模板参数
类模板的模板参数如果结合的实参不是具体类型而是另外一个模板,那么不能使用typename关键字声明该参数,而是写明其所结合的类模板实参的原型:
template<模板形参表> class 模板性参数参数名
#include <iostream> using namespace std; template<typename T> class Array { public: void pushBack (T const& data) { cout << "向数组尾端压入数据" << endl; } void popBack (void) { cout << "从数组尾端弹出数据" << endl; } }; template<typename T> class List { public: void pushBack (T const& data) { cout << "向链表尾端压入数据" << endl; } void popBack (void) { cout << "从链表尾端弹出数据" << endl; } }; template<typename T,template<typename> class C> class Stack { public: void push (T const& data) { m_c.pushBack (data); } void pop (void) { m_c.popBack (); } private: C<T> m_c; }; int main (void) { Stack<int, Array> sia; sia.push (100); sia.pop (); Stack<int, List> sil; sil.push (200); sil.pop (); return 0; }
7.嵌套模板的外部定义
如果将嵌套于一个类模板内部模板防盗包装模板的外部定义,需要按照作用域层次顺序,从外到内,从前到后依次使用独立template字句声明其模板参数表
8."零"初始化
基本类型不存在缺省构造函数,所以未被显示初始化的局部变量和成员变量具有一个未定义的初值.如果希望模板函数或者模板类中所有参数化类型的变量,无论是基本类型还是类类型,都能以缺省方式呗初始化,就必须显示进行缺省构造(写出构造函数),即"零"初始化.
#include <iostream> using namespace std; template<typename T> void foo (void) { T var = T (); // int var = int (); // string var = string (); // Integer var = Integer (); cout << '[' << var << ']' << endl; } template<typename T> class Foo { public: Foo (void) : m_var () {} T m_var; }; class Integer { public: Integer (int arg = 0) : m_var (arg) {} friend ostream& operator<< (ostream& os,Integer const& i) { return os << i.m_var; } private: int m_var; }; int main (void) { /* foo<int> (); foo<string> (); foo<Integer> (); */ Foo<int> fi; cout << '[' << fi.m_var << ']'<< endl; Foo<string> fs; cout << '[' << fs.m_var << ']' << endl; Foo<Integer> fn; cout << '[' << fn.m_var << ']' << endl; return 0; }
9.虚函数和多态
(1)类模板中可以声明虚函数,而且只要实例化该模板时所提供的类型实参不违背函数有效覆盖的条件,就可以形成多态
(2)由于模板函数的延迟编译要晚于类或者类模板中虚表的构建,因此模板函数不能同时又是虚函数.
不能被声明为虚函数的函数:全局函数,静态成员函数,构造函数,模板性成员函数
#include <iostream> using namespace std; template<typename A, typename B> class X { public: // 模板中的虚函数 virtual A foo (B arg) const { cout << "X::foo" << endl; return A (); } // 不可以声明虚模板函数 /* template<typename C> virtual void bar (void) const {} */ }; template<typename A, typename B, typename C, typename D> class Y : public X<C, D> { public: A foo (B arg) const { cout << "Y::foo" << endl; return A (); } }; int main (void) { Y<int, double, int, double> y1; X<int, double>& x1 = y1; x1.foo (1.2); Y<int, double, int, char> y2; X<int, char>& x2 = y2; x2.foo ('A'); /* Y<int, double, char, double> y3; X<char, double>& x3 = y3; x3.foo (1.2); */ return 0; }