做外贸要访问国外的网站怎么办,建设网站的视频,wordpress文章显示在页面,网站管理员权限11 类型泛化 1、函数模版1.1 前言1.2 函数模版1.3 隐式推断类型实参1.4 函数模板重载1.5 函数模板类型形参的默认类型#xff08;C11标准#xff09; 2、类模版2.1 类模板的成员函数延迟实例化2.2 类模板的静态成员2.3 类模板的递归实例化2.4 类模板类型形参缺省值 3、类模板… 11 类型泛化 1、函数模版1.1 前言1.2 函数模版1.3 隐式推断类型实参1.4 函数模板重载1.5 函数模板类型形参的默认类型C11标准 2、类模版2.1 类模板的成员函数延迟实例化2.2 类模板的静态成员2.3 类模板的递归实例化2.4 类模板类型形参缺省值 3、类模板的扩展3.1 模板型成员变量3.2 模板型成员函数3.3 模板型成员类型3.4 类模板中成员虚函数 4、模板的特殊用法4.1 数值型的类型形参4.2 模板型的类型形参 5、模板的经典错误补5.1 模板二次编译5.2 嵌套依赖5.3 利用类型形参调用成员函数模板5.4 子类模板访问基类模板 1、函数模版
1.1 前言
首先C为强类型语言 -优点有很多的数据类型基本类型、类类型)在类型安全性方面是无可比拟的 -缺点因为数据类型的多样性在很大程度上会给程序员编写通用代码带来麻烦 这就使得程序员需要为每一种数据类型编写完全相同或者近乎完全相同的代码即便它们在抽象层面是一致的。
int Max(int x, int y){return x y ? x : y;
}
double Max(double x, double y){return x y ? x : y;
}string Max(string x, string y){return x y ? x : y;
}
int main(){int nx 10, ny 11;cout Max(nx, ny) endl;double dx 1.0, dy 1.1;cout Max(dx, dy) endl;string sx 1.0, sy 1.1;cout Max(sx, sy) endl;...如果有其他类型的比较大小我们需要为每一种类型编写相同的代码
}1.2 函数模版
定义语法
templateclass|typename 类型形参1class|typename类型形参2...返回值类型 函数模板名调用形参1调用形参2…){...}示例
templatetypename TT Max(T x,Ty){return x y?x:y;
}注意:可以使用任何标识符作为类型形参的名称但使用“T”己经称为一种惯例“T”表示的是调用者在使用函数模板时指定的任意类型
使用使用函数模板必须对函数模板进行实例化 语法
函数模板名类型实参1类型实参2调用实参1…示例
// 定义函数模板
templatetypename TT Min(T x, T y){return x y ? y : x;
}int main(){int nx 10, ny 11;// 使用函数模板cout Minint(nx, ny) endl;double dx 1.0, dy 1.1;cout Mindouble(dx, dy) endl;string sx 1.0, sy 1.1;cout Minstring(sx, sy) endl;return 0;
}分析 编译器并没有把函数模板翻译成一个可以处理任何数据类型的单一实体编译器在实例化函数模板时根据类型实参从函数模板中产生一个真正的函数实体函数模板并不是一个函数实体通过实例化才能产生真正的函数实体函数模板可以看成是编译器生成函数实体的一个依据而已这种用具体数据类型替换函数模板类型形参的过程叫做实例化这个过程将产生一个函数模板的实例(函数实体)原则上来说可以使用任何类型来实例化函数模板不管其为基本类型还是类类型但前提是这个类型必须支持函数模板所要执行的操作
1.3 隐式推断类型实参
概念 若函数模板的调用形参和类型形参相关那么在实例化函数模板时即使不显式指明类型实参编译器也有能力根据调用实参的类型隐式推断出类型实参的类型获得和调用普通函数一致的书写形式
templatetypename TT Min(T x, T y){return x y ? y : x;
}
int main(){int nx 10, ny 11;// 调用形参和类型形参相关cout Min(nx, ny) endl;// -- cout Minint(nx, ny) endl;double dx 1.0, dy 1.1;cout Min(dx, dy) endl;string sx 1.0, sy 1.1;cout Min(sx, sy) endl;return 0;
}三种情况不能做隐式推断 1: 调用参数和类型参数不完全相关
template typename T, typename Dvoid Foo(D x){cout x endl;
}
int main(){int nx 10, ny 11;//Foo(nx);// 不能推断出T的类型Foofloat(nx);return 0;
}2: 隐式推断类型实参不能同时隐式类型转换
template typename Tvoid Foo1(T x,T y){cout x endl;
}
int main(){// Foo1(11,13.5);// 隐式推断类型实参不能同时隐式类型转换Foo1(11, (int)13.5);return 0;
}3: 返回值类型不能隐式推断
templatetypename R,typename T
R Bar(T x){R r;return t;
}
// 返回值类型不能隐式推断
void C11_06(){int a 10;//Bar(10);// 根据实参能推断出形参T的类型但是无法推断出R的类型Barfloat(10);
}1.4 函数模板重载
普通函数和能够实例化出该函数的函数模板构成重载关系 在调用参数类型匹配度相同情况下编译器优先选择普通函数除非函数模板可以产生调用参数类型匹配度更好的函数 隐式推断类型实参不能同时隐式类型转换但普通函数可以如果在传递参数时如果需要编译器做隐式类型转换则编译器选择普通函数。可以在实例化时用强行通知编译器选择函数模板。
void MaxType(int x, int y){cout 1.MaxType endl;
}
templatetypename Tvoid MaxType(T x, T y){cout 2.MaxType endl;
}int main(){int nx 10, ny 11;MaxType(nx,ny); // 调用普通函数 在调用参数类型匹配度相同情况下编译器优先选择普通函数double dx 1.0, dy 1.1;MaxType(dx, dy);// 调用函数模版 函数模板可以产生调用参数类型匹配度更好的函数MaxType(nx,dy);// 调用函数模版 隐式推断类型实参不能同时隐式类型转换但普通函数可以MaxType(nx, ny);// 强行通知编译器选择函数模板。return 0;
}1.5 函数模板类型形参的默认类型C11标准
函数模板的类型形参可以带有默认类型 在实例化函数模板时如果提供了类型实参则用所提供的类型实参来实例化函数模板在实例化函数模板时如果没提供类型实参则用类型形参的缺省类型来实例化函数模板如果某一个类型形参带有缺省类型则其后的类型形参都必须带有缺省类型
template typename Tshort, typename Dfloatvoid Foo2(int x0){T t;D d;cout t的类型 typeid(t).name() d的类型 typeid(d).name() endl;
}
int main(){Foo2int, double(100);// 用提供的类型Foo2(100);// 用默认类型return 0;
}2、类模版
形式
// 书写
templatetyepname类型形参1,...
class 类模板名{...
}
// 示例
templatetypename A,typename B
class CMath{
public:A m a;B func0{…}
}
使用类模板必须对类模板进行实例化产生真正的类 类模板本身并不代表一个确定的类型即不能用于定义对象只有通过类型实参实例化成真正的类后才具备类的特性(即可以定义对象)。
templatetypename T
class CMath{
public:CMath(T t1, T t2) :m_t1(t1), m_t2(t2){}T add(){ return m_t1 m_t2; }T sub();// 声明
private:T m_t1;T m_t2;
};
// T sub()定义 templatetypename T不能丢要用类名引成员切记不用要类模板引成员
templatetypename T
T CMathT::sub(){return m_t1 - m_t2;
}int main(){int nx 10, ny 11;CMathintm1(nx, ny);cout m1.add() endl;double dx 1.0, dy 1.1;CMathdoublem2(dx, dy);cout m2.add() endl;string sx 1.0, sy 1.1;CMathstringm3(sx, sy);cout m3.add() endl;return 0;
}2.1 类模板的成员函数延迟实例化
类模板被实例化产生真正类的一刻类模板中的成员函数并没有实例化 成员函数只有在被调用时才会被实例化即产生真正成员函数,称之为成员函数的延迟实例化 注意成员虚函数除外 某些类型虽然并没有提供类模板所需要的全部功能,但照样可以用它来实例化类模板只要不调用那些未提供功能的成员函数即可。
int main(){// CMathint 编译到这是类中只有成员变量没有成员函数// CMathintm1(nx,ny) // 此时编译器才会给类中添加构造函数// m1.add() // 此时才会去类中添加add的成员函数return 0;
}2.2 类模板的静态成员
类模板中的静态成员既不是每个对象拥有一份也不是类模板拥有一份应该是由类模板实例化出的每一个真正的类各自拥有一份且为该实例化类定义的所有对象共享
template typename T class A
{
public:static T m_t;// 声明static void foo(){cout AT::foo() endl;}
};
template typename T T A T ::m_tT();// 定义 T()零值初始化
// 类模板中的静态成员即不是每个对象拥有一份也不是类模板拥有一份
// 应该是由类模板实例化出的每一个真正的类各自拥有一份且为该实例化类定义的所有对象共享
int main(){Aint x, y;cout Aint::m_t: Aint::m_t endl; // Aint::m_t:00DB53F8cout x.m_t: x.m_t y.m_t: y.m_t endl; //x.m_t:00DB53F8 y.m_t:00DB53F8Adouble m, n;cout Adouble::m_t: Adouble::m_t endl; // Adouble::m_t:00DB5400cout m.m_t: m.m_t m.m_t: m.m_t endl;// m.m_t:00DB5400 m.m_t:00DB5400return 0;
}2.3 类模板的递归实例化
概念 利用类模板实例化产生的类来实例化类模板自身这种做法称之为类模板的递归实例化 应用 通过这种方法可以构建空间上具有递归特性的数据结构 例如多维数组 示例 ArrayArrayint
templatetypename Tclass Array{
public:T operator[](size_t i){return arr[i];}
private:T arr[10];
};
# include iomanip
int main(){Arrayint a; // a对象当成是一维数组看待for (int i 0; i 10; i){a[i] 1000 i;}for (int i 0; i 10; i){cout a[i] ;}cout endl;Array Arrayint b; // 类模板的递归实例化for (int i 0; i 10; i){for (int j 0; j 10; j){b[i][j] i*j;}}for (int i 1; i 10; i){for (int j 1; j i; j){cout i X j ;cout left setw(2) setfill( ) b[i][j] ;}cout endl;}return 0;
}2.4 类模板类型形参缺省值
类模板的类型形参可以带有缺省类型 在实例化类模板时如果提供了类型实参则用所提供的类型实参来实例化类模板在实例化类模板时如果没提供类型实参则用类型形参的缺省类型来实例化类模板如果某一个类型形参带有缺省类型则其后的类型形参都必须带有缺省类型
templatetypename Tshort,typename Dint
class CCMath{
private:T m_t;D m_d;
public:void print(){cout m_t的类型 typeid(m_t).name() endl;cout m_d的类型 typeid(m_d).name() endl;}
};int main(){CCMathm1;CCMathintm2;CCMathint,doublem3;m1.print();m2.print();m3.print();return 0;
}3、类模板的扩展
3.1 模板型成员变量
成员变量但其类型是由类模板实例化的未知类称之为模板型成员变量
示例
template typename T class Array{...}
template typename D class Sum{
public :ArrayD m_d; // 模板型成员变量
}templatetypename Tclass Array2{
public:T operator[](size_t i){return arr[i];}
private:T arr[10];
};
templatetypename Dclass Sum{
public:Sum(const Array2D v) :m_a(v){}D add(){ // 求和器D d D();for (int i 0; i 10; i){d m_a[i];}return d;}
private:Array2D m_a;//模板型成员变量
};int main(){Array2int a;for (int i 0; i 10; i){a[i] i1;}Sumint s(a);cout s.add() endl;return 0;
}3.2 模板型成员函数
模板型成员函数 又名 成员函数模板 示例
templatetypename T class BB{
public:templatetypename Dvoid foo(){ // 成员函数模板cout AA::fooD endl;}templatetypename Dvoid bar();// 声明
};
templatetypename T templatetypename D void BBT::bar(){// 定义cout BB :: bar endl;
};void C11_15(){AA a;a.fooint();BBintb; // 对类模板实例化b.fooint();// 对函数模板实例化b.barint();
}3.3 模板型成员类型
templatetypename Xclass A1{
public:templatetypename Yclass B1{public:templatetypename X void foo(){ // 模板型成员类型cout foo() endl;}};
};
int main(){A1int::B1intb;b.fooint();return 0;
}3.4 类模板中成员虚函数
template typename T class Base{
public:virtual void foo(){//虚函数cout BaseT::foo endl;}
};
// 只有在父子类中才可以将模板进行合并
templatetypename T, typename Dclass Derived :public BaseT{
public:void foo(){cout DerivedT,D::foo endl;}
};
int main(){Derivedint, double dr;Baseint* pdase dr;pdase-foo();return 0;
}注意类模板中可以有虚函数也可以表现出多态性但是不能有成员虚函数模板 这是因为根据成员虚函数的多态机制需要一个虚函数表表中保存成员虚函数的入口地址而这个表是编译器在实例化类模板时就创建而成员函数模板的实例化即产生真正的函数实体需要编译器处理完并且调用后才会完成这时才出现成员虚函数的地址所以成员函数模板的延迟实例化阻碍了虚函数表的构建
4、模板的特殊用法
4.1 数值型的类型形参
模板的类型形参也可以是数值类型只能是整数可以有缺省值
templatetypename T,int S10class Array{
public:T operator[](size_t i){return arr[i];}int size(){return S;}
private:T arr[S];
};
int main(){Arrayint, 30 a;for (int i 0; i a.size(); i){a[i] 1000 i;}for (int i 0; i a.size(); i){cout a[i] ;}return 0;
}4.2 模板型的类型形参
模板的类型形参也可以是类模版可以有缺省值
templatetypename Tclass Array{
public:T operator[](size_t i){return arr[i];}int size(){return 10;}
private:T arr[10];
};
templatetypename D, templatetypename M class C Array class SumArray{
public:SumArray(const CD v) :m_a(v){};D add(){D a D();for (int i 0; i m_a.size(); i){a m_a[i];}return a;}
private:CD m_a;
};
int main(){Arrayint a;for (int i 0; i a.size(); i){a[i] 1000 i;}SumArrayint, Array s(a);cout s.add() endl;
}5、模板的经典错误补
5.1 模板二次编译
如果都过不了第一次编译那么就谈不上第二次编译
编译器对模板会进行两次编译第一次编译发生在实例化模板之前产生真正函数或真正类之前只检查模板本身内部代码(只检查基本词法是否正确) 模板内部出现的所有标识符是否均有声明对于已知类型的调用要检查调用是否有效对于未知类型调用认为都合理 第二次编译第二次编译发生在实例化模板之后产生真正函数或真正类之后结合所使用的类型实参再次检查模板代码查看所有调用是否真的都有效
templatetypename Tvoid func(){// 第一次编译时编译器针对未知类型调用采用隐忍态度尽量认为都合理//fdf; //错误 模板内部出现的所有标识符是否均有声明 第一次编译A3 a; a.foo(); // 对于已知类型的调用要检查调用是否有效 第一次编译//a.fdfddf();// 错误 在A3类中没有fdfddf 第一次编译T t;t.jsjhdeasde();// 第一次编译不会报错这是因为对于未知类型调用认为都合理 第二次编译会报错这是因为第二次编译时编译器结合类型实参再次检查所有调用是否真的合理// t.fdsfdfd();// 这种的第一次编译也会报错
}
int main(){funcint();return 0;
}5.2 嵌套依赖
问题由于模板要经过两次编译在第一次编译模板的代码时类型形参的具体类型尚不明确编译器将把类型形参的嵌套类型理解为某个未知类型的静态成员变量因此编译器看到使用这样的标识符声明变量时会报告错误这就叫嵌套依赖。解决办法在类型形参的前面增加一个typename标识符意在告诉编译器其后是一个类模板的嵌套使用。
class A{
public:class B{public:void foo(){cout A::B::foo endl;}};
};
templatetypename Tvoid func1(){
// 第一次编译时会把T::B看做成B是T的静态成员变量编译器看到使用这样的标识符声明变量时会报告错误
// T::B 类型形参的嵌套类型 typename 告诉编译器B是T(未知类)的嵌套类型typename T::B b;b.foo();
}
int main(){func1A();return 0;
}5.3 利用类型形参调用成员函数模板
问题利用未知类定义的对象来访问成员函数模板时编译器在第一次编译时无法解析成员函数模板的类型参数列表的而报告编译错误。解决方法在成员函数模板之前增加template关键字意在告诉编译器其后是一个函数模板实例编译器就可以正确理解了。
class A{
public:template typename T void foo(){cout A::f00 endl;}
};
template typename T void func2(){T t;// 编译器第一次编译时未知类型定义的对象调用方法时不能带有 // template告诉编译器后面的是函数模板的实例化t.template fooint();
}
int main(){ func2A(); return 0;
}5.4 子类模板访问基类模板
问题在子类模板中访问基类模板的成员编译器第一次编译时只在子类模板和全局域中搜索使用的标识符号,不会到基类模板中搜索解决方法在子类模板中可以通过使用作用域限定符或显式使用this指针
template typename T class BaseA{
public:int m_i;void foo(){ cout BaseAT::foo() endl; }
};
templatetypename T, typename D class Deri : public BaseAT{
public:void bar(/* this*/ ){//m_i 100; 报错 //foo();BaseA::foo();BaseA::m_i 100;this-foo();this-m_i 100;}
};
int main(){Deriint, int d;d.bar();return 0;
}