C++

中有一个重要特性,那就是模板类型。类似于Objective-C中的泛型。C++通过类模板来实现泛型支持。

1 基础的类模板

类模板,可以定义相同的操作,拥有不同数据类型的成员属性。

通常使用template来声明。告诉编译器,碰到T不要报错,表示一种泛型.

如下,声明一个普通的类模板:

template <typename T>
class Complex{ public:
//构造函数
Complex(T a, T b)
{
this->a = a;
this->b = b;
} //运算符重载
Complex<T> operator+(Complex &c)
{
Complex<T> tmp(this->a+c.a, this->b+c.b);
return tmp;
} private:
T a;
T b;
} int main()
{
//对象的定义,必须声明模板类型,因为要分配内容
Complex<int> a(10,20);
Complex<int> b(20,30);
Complex<int> c = a + b; return 0;
}

2 模板类的继承

在模板类的继承中,需要注意以下几点:

  • 如果父类自定义了构造函数,记得子类要使用构造函数列表来初始化
  • 继承的时候,如果子类不是模板类,则必须指明当前的父类的类型,因为要分配内存空间
  • 继承的时候,如果子类是模板类,要么指定父类的类型,要么用子类的泛型来指定父类
template <typename T>
class Parent{
public:
Parent(T p)
{
this->p = p;
} private:
T p;
}; //如果子类不是模板类,需要指明父类的具体类型
class ChildOne:public Parent<int>{ public:
ChildOne(int a,int b):Parent(b)
{
this->cone = a;
} private:
int cone;
}; //如果子类是模板类,可以用子类的泛型来表示父类
template <typename T>
class ChildTwo:public Parent<T>{ public:
ChildTwo(T a, T b):Parent<T>(b)
{
this->ctwo = a;
} private:
T ctwo;
};

3 内部声明定义普通模板函数和友元模板函数

普通模板函数和友元模板函数,声明和定义都写在类的内部,也不会有什么报错。

template <typename T>
class Complex { //友元函数实现运算符重载
friend ostream& operator<<(ostream &out, Complex &c)
{
out<<c.a << " + " << c.b << "i";
return out;
} public:
Complex(T a, T b)
{
this->a = a;
this->b = b;
} //运算符重载+
Complex operator+(Complex &c)
{
Complex temp(this->a + c.a, this->b + c.b);
return temp;
} //普通加法函数
Complex myAdd(Complex &c1, Complex &c2)
{
Complex temp(c1.a + c2.a, c1.b + c2.b);
return temp;
} private:
T a;
T b;
}; int main()
{
Complex<int> c1(1,2);
Complex<int> c2(3,4); Complex<int> c = c1 + c2; cout<<c<<endl; return 0;
}

4 内部声明友元模板函数+外部定义友元模板函数

如果普通的模板函数声明在内的内部,定义在类的外部,不管是否处于同一个文件,就跟普通的函数一样,不会出现任何错误提示。但是如果是友元函数就会出现报错,是因为有二次编译这个机制存在。

4.1 模板类和模板函数的机制

在编译器进行编译的时候,编译器会产生类的模板函数的声明,当时实际确认类型后调用的时候,会根据调用的类型进行再次帮我们生成对应类型的函数声明和定义。我们称之为二次编译。同样,因为这个机制,会经常报错找不到类的函数的实现。在模板类的友元函数外部定义时,也会出现这个错误。解决方法是 “ 类的前置声明和函数的前置声明 ”。

  • 按照普通模板函数的样式处理友元函数
#include <iostream>
using namespace std; template <typename T>
class Complex { //友元函数实现运算符重载
friend ostream& operator<<(ostream &out, Complex<T> &c); public:
Complex(T a, T b); //运算符重载+
Complex<T> operator+(Complex<T> &c); //普通加法函数
Complex<T> myAdd(Complex<T> &c1, Complex<T> &c2); private:
T a;
T b;
}; //友元函数的实现
template <typename T>
ostream& operator<<(ostream &out, Complex<T> &c)
{
out<<c.a << " + " << c.b << "i";
return out;
} //函数的实现
template <typename T>
Complex<T>::Complex(T a, T b)
{
this->a = a;
this->b = b;
} template <typename T>
Complex<T> Complex<T>::operator+(Complex<T> &c)
{
Complex temp(this->a + c.a, this->b + c.b);
return temp;
} template <typename T>
Complex<T> Complex<T>::myAdd(Complex<T> &c1, Complex<T> &c2)
{
Complex temp(c1.a + c2.a, c1.b + c2.b);
return temp;
} int main()
{
Complex<int> c1(1,2);
Complex<int> c2(3,4); Complex<int> c = c1 + c2; cout<<c<<endl; return 0;
}
  • 友元函数的定义写在类的外部--错误信息
Undefined symbols for architecture x86_64:
"operator<<(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, Complex<int>&)", referenced from:
_main in demo1.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

上面的错误信息,就是典型的二次编译的错误信息,找不到友元函数的函数实现。所以,如果友元模板函数的定义写在函数的外部,需要进行类和函数的前置声明,来让编译器找到函数的实现

4.2 前置声明解决二次编译问题
  • 类的前置声明
  • 友元模板函数的前置声明
  • 友元模板函数声明需要增加泛型支持

5 声明和定义分别在不同的文件(模板函数、模板友元)

类的声明和实现,分别在不同的文件下,需要增加一个hpp文件支持。或者尽量将模板函数与模板友元放在一个文件下。

  • 类的声明与函数的声明写在.h文件
  • 类的实现及函数的实现写在.cpp文件
  • 将.cpp文件改成.hpp文件
  • 在主函数中调用.hpp文件,而不是引用.h文件

如果碰到.h和.hpp文件都存在的情况下,引用.hpp文件。

demo2.h文件

存放类的声明和函数的声明

#include <iostream>
using namespace std; //类的前置声明
template <typename T>
class Complex; //友元函数的声明
template <typename T>
ostream& operator<<(ostream &out, Complex<T> &c); template <typename T>
class Complex { //友元函数实现运算符重载
friend ostream& operator<< <T> (ostream &out, Complex<T> &c); public:
Complex(T a, T b); //运算符重载+
Complex<T> operator+(Complex<T> &c); //普通加法函数
Complex<T> myAdd(Complex<T> &c1, Complex<T> &c2); private:
T a;
T b;
};

demo2.hpp文件

包括模板函数的实现

#include "demo2.h"

//友元函数的实现
template <typename T>
ostream& operator<<(ostream &out, Complex<T> &c)
{
out<<c.a << " + " << c.b << "i";
return out;
} //函数的实现
template <typename T>
Complex<T>::Complex(T a, T b)
{
this->a = a;
this->b = b;
} template <typename T>
Complex<T> Complex<T>::operator+(Complex<T> &c)
{
Complex temp(this->a + c.a, this->b + c.b);
return temp;
} template <typename T>
Complex<T> Complex<T>::myAdd(Complex<T> &c1, Complex<T> &c2)
{
Complex temp(c1.a + c2.a, c1.b + c2.b);
return temp;
}

main.cpp文件

需要调用hpp文件

#include <iostream>
using namespace std;
#include "demo2.hpp" int main()
{
Complex<int> c1(1,2);
Complex<int> c2(3,4); Complex<int> c = c1 + c2; cout<<c<<endl; return 0;
}

更多参考

C++类模板和模板类的更多相关文章

  1. C++:类模板与模板类

    6.3 类模板和模板类 所谓类模板,实际上是建立一个通用类,其数据成员.成员函数的返回值类型和形参类型不具体指定,用一个虚拟的类型来代表.使用类模板定义对象时,系统会实参的类型来取代类模板中虚拟类型从 ...

  2. C++中模板类使用友元模板函数

    在类模板中可以出现三种友元声明:(1)普通非模板类或函数的友元声明,将友元关系授予明确指定的类或函数.(2)类模板或函数模板的友元声明,授予对友元所有实例的访问权.(3)只授予对类模板或函数模板的特定 ...

  3. CI 模板解析器类

    模板解析器类可以解析你的视图文件中的伪变量.它可以解析简单的变量或者以变量作为标签的结构.如果你以前没有用过模板引擎,那么伪变量如下所示: <html><head><ti ...

  4. 读书笔记 effective c++ Item 43 了解如何访问模板化基类中的名字

    1. 问题的引入——派生类不会发现模板基类中的名字 假设我们需要写一个应用,使用它可以为不同的公司发送消息.消息可以以加密或者明文(未加密)的方式被发送.如果在编译阶段我们有足够的信息来确定哪个信息会 ...

  5. C++ 类模板与模板类详解

    在C++的Template中很多地方都用到了typename与class这两个关键字,有时候这两者可以替换,那么这两个关键字是否完全一样呢? 事实上class用于定义类,在模板引入c++后,最初定义模 ...

  6. 如何导出标准模板库(STL)类的实例化和包含STL类对象数据成员的类

    本文翻译自 https://support.microsoft.com/zh-cn/help/168958/how-to-export-an-instantiation-of-a-standard-t ...

  7. 《C++ Primer Plus》第16章 string类和标准模板库 学习笔记

    C++提供了一组功能强大的库,这些库提供了很多常见编程问题的解决方案以及简化其他问题的工具string类为将字符串作为对象来处理提供了一种方便的方法.string类提供了自动内存管理动能以及众多处理字 ...

  8. C++ 类模板三(类模版中的static关键字)

    //类模版中的static关键字 #include<iostream> using namespace std; /* 类模板本质上是c++编译器根据类型参数创建了不同的类, c++编译器 ...

  9. C++ 类模板一(类模板的定义)

    //类模版语法 #include<iostream> using namespace std; /* 类模板和函数模板深入理解 1.编译器并不是把函数模板处理成能处理任何类型的函数 2.编 ...

随机推荐

  1. java 数组和集合

    1.概念说明 区别:数组固定长度的,集合,数组的长度是可以变化的. List,继承Collection,可重复.有序的对象 Set,继承Collection,不可重复.无序的对象 Map,键值对,提供 ...

  2. HDU 1233 还是畅通工程 (最小生成树 )

    某省调查乡村交通状况,得到的统计表中列出了任意两村庄间的距离.省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可),并要求铺设的公路 ...

  3. zabbix agent配置详解(windows)

    客户端操作  标注:监控zabbix_agentd客户端安装对象是win server 2008操作系统 64位. 1.  下载zabbix_agentd监控客户端软件安装包(windows操作系统客 ...

  4. animate和translate

    transition, transform, tanslate,animation分别为过渡,变换,平移.动画.transform的属性包括:rotate() / skew() / scale() / ...

  5. The Little Prince-12/14

    The Little Prince-12/14 世界上有那么多的玫瑰花,但是只有你们是一直陪伴在我身边,我们相互灌溉. ————世界上有那么多的人,但是只有你们是一直陪伴在我身边,我们相互关心. “在 ...

  6. The Little Prince-12/10

    The Little Prince-12/10 审判自己比审判别人难多了.如果你成功地正确审判了自己,那么你就是一个真正的智者了. ————确实,正视自己是非常难的人生准则.以人为镜,可以明得失,从别 ...

  7. Tomcat启动程序端口冲突、确认相应进程及杀死冲突进程的解决方案

    一. 查看所有进程占用的端口 在开始-运行-cmd,输入:netstat –ano可以查看所有进程 二.查看占用指定端口的程序(1)命令窗口输出 命令:netstat –ano | findstr & ...

  8. Web开发笔记 #08# Jackson组合多个对象的属性构成JSON(以及添加自定义属性)

    参考文档:https://github.com/FasterXML/jackson-databind 关于ObjectMapper的线程安全 截自官方文档: 组合多个对象的属性构成JSON(以及添加自 ...

  9. c++ STL中的set和multiset

    1.结构 set和multiset会根据特定的排序原则将元素排序.两者不同之处在于,multisets允许元素重复,而set不允许重复. set中的元素可以是任意类型的,但是由于需要排序,所以元素必须 ...

  10. css背景样式background

    background用来定义html元素的背景效果 background-color:定义元素的背景颜色,背景的颜色值通常有三种定义方法 1.十六进制方式,如"#ff0000" 2 ...