STL类模板入门
创始人
2024-04-04 16:50:14
0

Unit04 类模板

01 类模板的声明

  • 形式:template class 类模板名 { … };

    例如:

    template
    class CMath
    {
    public:A m_a;B func() { ... };
    };
    
  • 如果在类模板外实现成员函数

    template
    返回值类型 类模板名<类型形参1, ...>::函数名(调用形参1, ...)
    {函数体实现;
    }
    

    例如:

    template
    B Cmath::func()
    {....;
    }
    
  • 实例:

    #include 
    using namespace std;
    //类模板
    template
    class CMath
    {
    public:CMath(T const& t1, T const& t2) : m_t1(t1), m_t2(t2) {}// T add();//声明实现写一起T add() {return m_t1 + m_t2;}
    private:T m_t1;T m_t2;
    };
    //声明实现分开写
    // template
    // T CMath::add()
    // {
    //     return m_t1 + m_t2;
    // }int main()
    {return 0;
    }
    

02 类模板的使用

  • 使用类模板必须对类模板进行实例化(产生真正的类)

    类模板本身并不代表一个确定的类型(即不能用与定义对象),只有通过类型实参实例化成真正的类后才具备类的语义(即可以定义对象)。

    例如:CMath math;

#include 
using namespace std;
//类模板
template
class CMath
{
public:CMath(T const& t1, T const& t2) : m_t1(t1), m_t2(t2) {}// T add();//声明实现写一起T add() {return m_t1 + m_t2;}
private:T m_t1;T m_t2;
};
//声明实现分开写
// template
// T CMath::add()
// {
//     return m_t1 + m_t2;
// }int main()
{int nx = 10, ny = 20;CMath m1(nx, ny);cout << m1.add() << endl;double dx = 12.3, dy = 45.6;CMath m2(dx, dy);cout << m2.add() << endl;string sx = "hello", sy = " world";CMath m3(sx, sy);cout << m3.add() << endl;return 0;
}
  • 类模板被实例化时类模板中的成员函数并没有实例化,成员函数只有在被调用时才会被实例化(即产生真正成员函数)注意:成员虚函数除外。

  • 某些类型虽然并没有提供类模板所需要的全部功能但照样可以实例化类模板,只要不调用那些未提供功能的成员函数即可。

03 类模板的静态成员

  • 类模板中的静态成员即不是每个对象拥有一份。
  • 也不是类模板拥有一份。
  • 而应该是由类模板实例化出的每一个真正的类个有一份。
  • 且为该类实例化类定义的所有对象共享。
#include 
using namespace std;template
class A
{
public:static void print() {cout << "&m_i:" << &m_i;cout << ", &m_t:" << &m_t << endl;}static int m_i;static T m_t;
};
templateint A::m_i;   // = 0;
templateT A::m_t;     //= ??int main()
{A x, y, z;x.print();y.print();z.print();A::print();cout << endl;A m, n, t;m.print();n.print();t.print();A::print();return 0;
}

04 类模板的递归实例化

  • 可以使用任何类型来实例化类模板。
  • 由类模板实例化产生的类也可以用来实例化类模板自身,这种做法称之为类模板的递归实例化。
  • 通过这种方法可以构建空间上具有递归特性的数据结构(例如:多维数组)。
#include 
using namespace std;template
class Array
{
public:T& operator[](size_t i) {return m_arr[i];}
private:T m_arr[10];
};int main()
{Array< Array > m;for (int i = 0; i < 10; i++) {for (int j = 0; j < 10; j++) {m[i][j] = i + j;}}for (int i = 0; i < 10; i++) {for (int j = 0; j < 10; j++) {cout << m[i][j] << '\t';}cout << endl;}return 0;
}

Unit05 类模板扩展

01 全局特化

  • 全类特化:特化一个类模板可以特化该类模板所有的成员函数,相当于重新写了一个针对某种特定数据类型的具体类

    声明形式:template<>class 类模板名<类型参数1, ...> {...}

    例如:template<>class CMath {...}

  • 成员特化:类模板特化除了可以对整个类进行特化以外,可以只针对某部分成员函数进行特化。

    声明形式:template<>返回值类型 类模板名<类型参数1, ...>::成员函数名(调用参数1, ...) {...}

    例如:template<> char* const CMath::add(...) {...}

#include 
#include 
using namespace std;template
class CMath
{
public:CMath(const T& t1, const T& t2) : m_t1(t1), m_t2(t2) {}T add() {return m_t1 + m_t2;}
private:T m_t1;T m_t2;
};//成员特化
template<>
char* const CMath::add() {return strcat(m_t1, m_t2);
}/*
//全类特化
template<>
class CMath
{
public:CMath(char* const& t1, char* const& t2) : m_t1(t1), m_t2(t2) {}char* const add() {return strcat(m_t1, m_t2);}
private:char* const m_t1;char* const m_t2;
};
*/int main()
{char cx[256] = "hello", cy[256] = " world";CMath m(cx, cy);cout << m.add() << endl;return 0;
}

何时使用:当一个模板类中的某些成员函数中的操作针对某些数据类型无法执行时,使用全局特化来特殊处理这种数据类型实例化出来的具体类。其中全局特化的工作量较大,成员特化可以只针对那些无法执行的成员函数进行重写。

02 局部特化

  • 类模板的局部特化,除非必要否则尽量不要特化,因为特化版本过多容易引发编译器匹配歧义。
#include 
using namespace std;templateclass CMath {
public:static void foo() {cout << "1:CMath::foo" << endl;}
};//局部特化1
templateclass CMath {
public:static void foo() {cout << "2:CMath::foo" << endl;}
};//局部特化2
templateclass CMath {
public:static void foo() {cout << "3:CMath::foo" << endl;}
};//局部特化3
templateclass CMath {
public:static void foo() {cout << "4:CMath::foo" << endl;}
};int main()
{CMath::foo();   //1CMath::foo();    //2//CMath::foo();  //匹配歧义CMath::foo(); //1CMath::foo();    //匹配歧义return 0;
}

03 类型形参的缺省

  • 类模板的类型形参可以带缺省值。

    实例化类模板时,如果提供了类型实参则用所提供的类型实参来实例化类模板,如果没有提供类型实参则用相应的类型形参的缺省类型来实例化类模板。

  • 如果类模板的某个类型形参带有缺省值,那么它后面的类型形参都必须带缺省值。

#include 
#include 
using namespace std;templateclass CMath {
public:void print() {cout << "m_t:" << typeid(m_t).name() << ", "<< "m_d:" << typeid(m_d).name() << endl;}
private:T m_t;D m_d;
};int main()
{CMath m;m.print();CMath<> m2;m2.print();return 0;
}

04 数值形的模板参数

  • 类模板的模板形参并不限于类型参数,不同数值也可以作为模板的参数。
#include 
using namespace std;templateclass Array {
public:T& operator[](size_t i) {return m_arr[i];}size_t size() {return S;}
private:T m_arr[S];
};int main()
{Array a;for (int i = 0; i < a.size(); i++) {a[i] = i + 1;}for (int i = 0; i < a.size(); i++) {cout << a[i] << " ";}cout << endl;return 0;
}

05 模板技巧

01 模板型成员变量

成员变量,但其类型是由一个类模板实例化的未知类,那么它才可以称之为模板型成员变量

例如:

templateclass Array {...};
templateclass Sum {
public:Array m_s;    //模板型成员变量
};
#include 
using namespace std;templateclass Array {
public:T& operator[](size_t i) {return m_arr[i];}
private:T m_arr[10];
};
templateclass Sum {  //求和器
public:Sum(Array& s) : m_s(s) {}D add() {D d = 0;for (int i = 0; i < 10; i++) {d += m_s[i];}return d;}
private:Array m_s;    //模板型成员变量
};int main()
{Array a;for (int i = 0; i < 10; i++) {a[i] = i + 1;}Sum s(a);cout << s.add() << endl;return 0;
}

02 模板型成员函数

类模板的成员函数模板

例如:

templateclass CMath {
public:templatevoid foo() {...}  //成员函数模板
};

如果在类外实现:

templatetemplatevoid CMath::foo() {...}

#include 
using namespace std;templateclass CMath {
public:templatevoid foo();// templatevoid foo() {    //成员 函数模板//     cout << "CMath::foo()" << endl;// }
};template
templatevoid CMath::foo()
{cout << "CMath::foo()" << endl;
}int main()
{CMath m;m.foo();return 0;
}

03 模板型成员类型

类模板中嵌套的类模板

例如:

templateclass A {
public:templateclass B {...};    //模板型成员类型
};
#include 
using namespace std;templateclass A {
public:templateclass B {public:templateclass C;};
};template
template
templateclass A::B::C {
public:templatevoid foo() {cout << "foo()" << endl;}
};int main()
{A::B::C c;c.foo();return 0;
}

04 模板型模板参数

类模板的模板形参也可以是类模板,可以有缺省值

例如:

templateclass Array {...};
template< templateclass C=Array >class Sum {...
};
#include 
using namespace std;
templateclass Array {
public:T& operator[](size_t i) {return m_arr[i];}
private:T m_arr[10];
};
templateclass C>class Sum {
public:Sum(C& s) : m_s(s) {}D add() {D d = 0;for (int i = 0; i < 10; i++) {d += m_s[i];}return d;}
private:C m_s;
};int main()
{Array a;for (int i = 0; i < 10; i++) {a[i] = i + 1;}Sum s(a);cout << s.add() << endl;return 0;
}

Unit06 模板典型错误

01 嵌套依赖

  • 问题:

    由于模板要经过两次编译,在第一次编译模板的代码时,类型形参的具体类型尚不明确,编译器将把类型形参的嵌套类型理解为某个未知类型的静态成员变量,因此编译器看到使用这样的标识符声明变量时会报告错误,这就叫嵌套依赖。

  • 解决方法:

    在类型形参的前面增加一个typename标识符,意在告诉编译器其后时一个类模板的嵌套使用。

#include 
using namespace std;
class A {
public:class B {public:void foo() {cout << "A::B::foo()" << endl;}};
};
templatevoid Func() {typename T::B b;    //嵌套依赖b.foo();
}int main()
{Func();return 0;
}

02 依赖模板参数访问成员函数模板

  • 问题:

    利用未知类定义的对象来访问成员函数模板时,编译器在第一次编译时无法解析成员函数模板的类型参数列表的 <> 而报告编译错误。

  • 解决方法:

    在成员函数模板之前增加template关键字,意在告诉编译器其后是一个函数模板实例,编译器就可以正确理解 <> 了。

#include 
using namespace std;
class A {
public:templatevoid foo() {cout << "A::foo()" << endl;}
};
templatevoid Func() {D d;d.template foo();    //依赖模板参数访问成员函数模板
}int main()
{Func();return 0;
}

03 子模板访问基模板

  • 问题:

    在子类模板中访问基类模板的成员,编译器第一次编译时只在子类模板和全局域中搜索使用的标识符号,不会到基类模板中搜索。

  • 解决方法:

    在子类模板中可以通过使用作用域限定符或显示使用 this 指针。

#include 
using namespace std;
templateclass Base {
public:int m_i;void foo() {cout << "Base::foo()" << endl;}
};templateclass Derived : public Base {
public:void bar() {Base::m_i = 100;Base::foo();// this->m_i = 100;// this->foo();}
};int main()
{Derived d;d.foo();return 0;
}

04 零值初始化

  • 问题:

    基本类型不存在缺省构造函数,未被初始化的局部变量都具有一个不确定的值(int a;//值不确定)

    类类型由于存在缺省构造函数,在未被初始化的情况下可以有一个确定的缺省初始化状态。(Integer a;//值确定)

    基于以上两点,就会在模板实现中产生不一致的语法语义。

  • 解决方法:

    如果希望模板中,所有类型参数的变量,无论是类类型还是基本类型都以缺省方式获得初始化,就必须对其进行显示缺省构造 T()

#include 
using namespace std;class Integer {
public:Integer() : m_i(0) {}
private:int m_i;friend ostream& operator<<(ostream& os, const Integer& that);
};
ostream& operator<<(ostream& os, const Integer& that) {return os << that.m_i;
}templatevoid Func() {T t = T();    //Integer() / int()cout << "t=" << t << endl;
}int main()
{Func();Func();return 0;
}

05 类模板中的成员虚函数

  • 类模板中的普通成员函数可以是虚函数

    即可以为类定义成员虚函数,和普通类的成员虚函数一样,类模板的成员虚函数也可以表现出多态性。

  • 类模板中的成员函数模板不可以是虚函数

    根据成员虚函数的多态机制,需要一个虚函数表(表中保存成员虚函数的入口地址),而这个表是编译器在实例化类模板时就产生,类的成员函数模板的实例化(即产生真正的函数实体)需要编译器处理完调用后才会完成,这是才出现成员虚函数的地址。

  • 总结:

    成员函数模板的延迟编译阻碍了虚函数表的静态构建。

#include 
using namespace std;templateclass Base {
public:virtual void foo() {cout << "Base::foo()" << endl;}templatevoid bar() {}
};templateclass Derived : public Base {
public:virtual void foo() {cout << "Derived::foo()" << endl;}
};int main()
{Derived d;Base* pBase = &d;pBase->foo();pBase->bar();return 0;
}

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
客厅放八骏马摆件可以吗(家里摆... 今天给各位分享客厅放八骏马摆件可以吗的知识,其中也会对家里摆八骏马摆件好吗进行解释,如果能碰巧解决你...
苏州离哪个飞机场近(苏州离哪个... 本篇文章极速百科小编给大家谈谈苏州离哪个飞机场近,以及苏州离哪个飞机场近点对应的知识点,希望对各位有...