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. Django 初始化数据库遇到问题(python manage.py migrate)

    问题:django.db.utils.InternalError: (1049, "Unknown database 'main'") 在Django 配置的数据库上 执行  cr ...

  2. go frame框架,关闭启动时打印的路由列表已经debug信息

    import ( "github.com/gogf/gf/frame/g" "github.com/gogf/gf/os/glog" ) func main() ...

  3. Linux命令head

    1.命令简介 head (head) 用来显示档案的开头至标准输出中.如果指定了多于一个文件,在每一段输出前会给出文件名作为文件头.如果不指定文件,或者文件为"-",则从标准输入读 ...

  4. FRP 中文文档

    https://github.com/fatedier/frp/blob/master/README_zh.md README | 中文文档 frp 是一个可用于内网穿透的高性能的反向代理应用,支持 ...

  5. 数据结构 - 顺序栈的实现 C++

    顺序栈封装 C++ 使用C++对顺序栈进行了简单的封装,实现了栈的基本操作 封装方法: pop(),top(),size(),empty(),push() 代码已经过测试 #pragma once # ...

  6. DT下重新定义设置发布发布条数后的跳转页面

    destoon系统有些地方还是做得不够细致,今天给大家分享一个重新定义发布条数满了以后的页面跳转  正常逻辑跳转应该是会员升级页面而不是会员中心首页,修改方法如下:(感谢DT朋友提供的修改方案)  打 ...

  7. 25、typing导入Python的数据类型模块、collections集合模块

    一.typing模块 1.typing模块的作用 类型检查,防止运行时出现参数和返回值类型不符合. 作为开发文档附加说明,方便使用者调用时传入和返回参数类型. 该模块加入后并不会影响程序的运行,不会报 ...

  8. 去除WordPress分类描述P标签

    我们知道栏目页调用栏目描述直接用<?php echo category_description(); ?>就ok了,但是使用上面的代码调用Wordpress分类描述,会自动出现P标签,如& ...

  9. Maven配置文件setting.xml详解

    注:本文来源于:大话JAVA的那些事 <Maven配置文件setting.xml详解> <?xml version="1.0" encoding="UT ...

  10. LOJ3097 SNOI2019 通信 题解

    题目链接 费用流,当建边需要依靠位置和权值两个偏序关系时,可以用cdq分治优化建边. 代码: #include<bits/stdc++.h> using namespace std; #d ...