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. max file descriptors [4096] for elasticsearch process is too low, increase to at least [65536]

    elasticsearch安装时遇到的错误 问题翻译过来就是:elasticsearch用户拥有的可创建文件描述的权限太低,至少需要65536: 解决办法: #切换到root用户修改 vim /etc ...

  2. flask 请求上下文

    一篇引用大牛的 https://www.cnblogs.com/zhaopanpan/p/9457343.html ### 线程安全 ```python# 线程不安全class Foo(object) ...

  3. QT -- plan

     QT  --  跨平台的 C++ 图形用户界面  应用程序框架 GUI介绍框架项目文件  .pro第一个QT (hello QT)父窗口 和 子窗口的区别(控件,部件,构件)信号 和 槽(信号的处理 ...

  4. The Little Prince-12/08

    The Little Prince-12/08 今天来点中文的经典语录+内心独白好不好呢? 狐狸说:“对我来说,你只是一个小男孩,就像其他成千上万个小男孩一样没有什么两样.我不需要你.你也不需要我.对 ...

  5. 打印一个浮点值%f和%g

    详见代码 后续或有更新 #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { fl ...

  6. 计算概论(A)/基础编程练习2(8题)/7:整数的个数

    #include<stdio.h> int main() { ] = {}; // 输入k个正整数 scanf("%d",&k); // 循环读入和进行算术 w ...

  7. 新增async

    import asyncio from threading import Thread import time now = lambda: time.time() print('start',now( ...

  8. Docker学习笔记之了解 Docker 的核心组成

    0x00 概述 在掌握 Docker 的一些背景知识后,我们还不得不花费一节的篇幅来简单介绍有关 Docker 核心的一些知识.当然,大家不要觉得有“核心”这类的词,我们就要在这一节中深入 Docke ...

  9. php 腾讯云 对象存储V5版本 获取返回的上传文件的链接方法

    腾讯云 对象存储V5版本 文档地址:https://github.com/tencentyun/cos-php-sdk-v5 调用简单文件上传方法: 返回数据如下 Array ( [data:prot ...

  10. 通过经纬度获取所属城市信息-php

    测试经纬度信息,37.863036,113.598909.通过地图查询,所在城市为:阳泉. <?php class test{ public static $test_key = 'dfgfdg ...