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(,);
Complex<int> b(,);
Complex<int> c = a + b; return ;
}

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(,);
Complex<int> c2(,); Complex<int> c = c1 + c2; cout<<c<<endl; return ;
}

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(,);
Complex<int> c2(,); Complex<int> c = c1 + c2; cout<<c<<endl; return ;
}

友元函数的定义写在类的外部--错误信息

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(,);
Complex<int> c2(,); Complex<int> c = c1 + c2; cout<<c<<endl; return ;
}

原文链接:https://www.jianshu.com/p/70ca94872418

【C++】C++中的类模板的更多相关文章

  1. 在VS中使用类模板出现出现LNK2019: 无法解析的外部符号错误。

    在VS中使用类模板出现出现LNK2019: 无法解析的外部符号错误,应在一个.h文件中完成方法的声明与实现,不要将实现放在cpp文件里,VS貌似不支持类模板分离

  2. C++中的类模板

    一.学习笔记 1.类模板的格式(1)声明 template<typename T> /* 使用T表示某种类型,比如: */ class AAA { private: T obj; publ ...

  3. c++ 中pair类模板的用法详解

    pair: 头文件:#include<utility> 类模板:template <class T1, class T2> struct pair 参数:T1是第一个值的数据类 ...

  4. VS中的类模板

    在目录C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\ItemTemplates\CSharp\Co ...

  5. c++中的类(class)-----笔记(类模板)

    1,一个模板类至少具有一个类参数,类参数是个符号以表示将要被某个确定数据类型代替的类型. #include<iostream> #include<string> using n ...

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

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

  7. C++中的函数模板

    我们在定义函数时,可以通过定义函数模板,来简化一些功能相同而数据类型不同的函数的定义和调用过程. C++中的函数模板 对于类的声明来说,也有同样的问题.有时,有两个或多个类,其功能是相同的,仅仅是数据 ...

  8. c++的函数模板和类模板

    函数模板和普通函数区别结论: 函数模板不允许自动类型转化 普通函数能够进行自动类型转换 函数模板和普通函数在一起,调用规则: 1 函数模板可以像普通函数一样被重载 2 C++编译器优先考虑普通函数 3 ...

  9. C++_进阶之函数模板_类模板

     C++_进阶之函数模板_类模板 第一部分 前言 c++提供了函数模板(function template.)所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来 ...

随机推荐

  1. tensorflow提示:No module named ''tensorflow.python.eager".

        版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/qq_27921205/articl ...

  2. DTC测试

    DTC配置好后要在2台server之间测试下是否能使用. 1.在A台上建立ODBC的连接B. 控制面板→管理工具→ODBC Datat  Source(32bit) 点击添加 选择SQL SERVER ...

  3. k8s的常用命令(一)

    常用的kubectl命令   kubectl run kubia --image=luksa/kubia --port=8080 --generator=run/v1   --image 指定镜像 - ...

  4. springboot+jndi+tomcat配置多数据源

    1.在application.properties中,添加jndi配置,如下图 2.新建dataSourceConfig类 3.dataSourceConfig类详细代码,这里只贴出其中一个,多个数据 ...

  5. Win10 Microsoft Store 微软商店 Error 0x00000193 解决方法

    0x00 前言 最近使用 CFW 过程中使用 Fiddle Web Debug 设置 Store 的回环代理的过程中发现无论是否使用代理,Store 都无法访问网络的问题,在最下面的提示中出现了 0x ...

  6. linux cgroups简介(上)

    Linux CGroups简介 1.CGroups是什么 与Linux namespace对比来看,Linux namespace用来限制进程的运行范围或者运行环境的可见性,比如:uts限制进程读取到 ...

  7. saas系统多租户数据隔离的实现(一)数据隔离方案

    0. 前言 前几天跟朋友聚会的时候,朋友说他们公司准备自己搞一套saas系统,以实现多个第三方平台的业务接入需求.聊完以后,实在手痒难耐,于是花了两天时间自己实现了两个saas系统多租户数据隔离实现方 ...

  8. Unity 渲染教程(二):着色器基础

    转载:https://www.jianshu.com/p/7db167704056 这是关于渲染基础的系列教程的第二部分.这个渲染基础的系列教程的第一部分是有关矩阵的内容.在这篇文章中我们将编写我们的 ...

  9. js图片转为base64的格式

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  10. thrift rpc通信

    thrift rpc通信 框架 别人的简历: 负责抓取程序的开发和维护,对抓取内容进行数据提取.整理.1.定向数据抓取程序的维护和开发,了解了Sqlite数据库.Thrift服务和多线程的开发调试.2 ...