• 【C++】泛型编程 ⑪ ( 类模板的运算符重载 - 函数实现 写在类外部的不同的 .h 头文件和 .cpp 代码中 )



    将 类模板 函数声明 与 函数实现 分开进行编码 , 有 三种 方式 :

    • 类模板 的 函数声明 与 函数实现 都写在同一个类中 , 也就是没有分开进行编码 ;
    • 类模板 的 函数实现 在 类外部进行 , 函数声明 和 实现 写在相同的 .cpp 源码文件中 ;
    • 类模板 的 函数实现 在 类外部进行 , 函数声明 和 实现 写在不同的 .h 和 .cpp 源码文件中 ;

    在博客 【C++】泛型编程 ⑨ ( 类模板的运算符重载 - 函数声明 和 函数实现 写在同一个类中 | 类模板 的 外部友元函数问题 ) 中实现了第一种情况 , 类模板 的 函数声明 与 函数实现 都写在同一个类中 , 也就是没有分开进行编码 ;

    在博客 【C++】泛型编程 ⑩ ( 类模板的运算符重载 - 函数实现 写在类外部的同一个 cpp 代码中 | 类模板 的 外部友元函数二次编译问题 ) 中 , 分析了 第二种情况 , 类模板 的 函数实现 在 类外部进行 , 写在 一个 cpp 源码文件中 ;

    在本篇博客中 , 开始分析 第三种 情况 , 函数实现 在 类外部进行 , 函数声明 和 实现 写在不同的 .h 和 .cpp 源码文件中 ;





    一、类模板的运算符重载 - 函数实现 写在类外部的不同的 .h 头文件和 .cpp 代码中




    1、分离代码 后的 友元函数报错信息 - 错误示例


    上一篇博客 【C++】泛型编程 ⑩ ( 类模板的运算符重载 - 函数实现 写在类外部的同一个 cpp 代码中 | 类模板 的 外部友元函数二次编译问题 ) 中 , 分析了 第二种情况 , 类模板 的 函数实现 在 类外部进行 , 写在 一个 cpp 源码文件中 ;

    将上述源码 分别写到 .h 头文件 , .cpp 代码文件 中 ;


    Student.h 头文件内容


    Student.h 头文件内容 :

    #include "iostream"
    using namespace std;
    
    template <typename T>
    class Student
    {
    	// 左移运算符重载
    	friend ostream& operator<< <T> (ostream& out, Student& s);
    
    public:
    	// 构造函数
    	Student(T x, T y);
    
    	// 重载 + 运算符
    	Student operator+(Student& s);
    
    public:
    	T a, b;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    Student.cpp 代码文件内容


    Student.cpp 代码文件内容 :

    #include "Student.h"
    
    // 类模板构造函数
    // 使用  Student:: 域操作符访问函数
    template <typename T>
    Student<T>::Student(T x, T y)
    {
    	this->a = x;
    	this->b = y;
    }
    
    // 重载 + 运算符
    // 使用  Student:: 域操作符访问函数
    template <typename T>
    Student<T> Student<T>::operator+(Student<T>& s)
    {
    	// 函数内部的类的  模板类型 , 可加 Student 可不加 Student
    	// 不加  也可以使用 , 加了也不会报错
    	Student student(this->a + s.a, this->b + s.b);
    	return student;
    }
    
    // Student 类的友元函数
    // 左移运算符重载 函数
    template <typename T>
    ostream& operator<<(ostream& out, Student<T>& s)
    {
    	out << "a:" << s.a << " b: " << s.b << endl;
    	return out;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    Test.cpp 代码文件内容


    Test.cpp 代码文件内容 :

    #include "iostream"
    using namespace std;
    
    #include "Student.h"
    
    int main() {
    	// 模板类不能直接定义变量
    	// 需要将 模板类 具体化之后才能定义变量
    	Student<int> s(666, 888);
    	cout << s << endl;
    
    	Student<int> s2(222, 111);
    	cout << s2 << endl;
    
    	// 验证 加法运算符 + 重载
    	Student<int> s3 = s + s2;
    
    	// 验证 左移运算符 << 重载
    	cout << s3 << endl;
    
    	// 控制台暂停 , 按任意键继续向后执行
    	system("pause");
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    执行报错信息


    执行 Test.cpp 中的 main 函数 , 报如下错误 :

    1>------ 已启动生成: 项目: HelloWorld, 配置: Debug Win32 ------
    1>Student.cpp
    1>Test.cpp
    1>正在生成代码...
    1>Test.obj : error LNK2019: 无法解析的外部符号 "class std::basic_ostream > & __cdecl std::<<(class std::basic_ostream > &,class Student &)" (?<<@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@1@AAV21@AAV?$Student@H@@@Z),该符号在函数 _main 中被引用
    1>Test.obj : error LNK2019: 无法解析的外部符号 "public: __thiscall Student::Student(int,int)" (??0?$Student@H@@QAE@HH@Z),该符号在函数 _main 中被引用
    1>Test.obj : error LNK2019: 无法解析的外部符号 "public: class Student __thiscall Student::operator+(class Student &)" (??H?$Student@H@@QAE?AV0@AAV0@@Z),该符号在函数 _main 中被引用
    1>Y:\002_WorkSpace\002_VS\HelloWorld\HelloWorld\Debug\HelloWorld.exe : fatal error LNK1120: 3 个无法解析的外部命令
    1>已完成生成项目“HelloWorld.vcxproj”的操作 - 失败。
    ========== 生成: 成功 0 个,失败 1 个,最新 0 个,跳过 0==========
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述


    2、问题分析


    在上述示例中 , 只需要改一个地方 , 即可成功执行 ,

    #include "Student.h"
    
    • 1

    导入源码时 , 导入 Student.cpp 文件 , 不能导入 Student.h ;

    // 此处不能导入 .h 头文件
    // 必须导入 .cpp 源码文件
    #include "Student.cpp"
    
    • 1
    • 2
    • 3

    这是 类模板 的实现机制 决定的 ;

    还是 两次编译 造成的问题 ;

    编译代码时 , 编译到 Student.h 时 , 会生成一个 类模板 函数头 ,

    编译 Student.cpp 时 , 类模板函数 不会像 普通函数 一样 , 寻找函数头 , 找不到对应的 函数头 ;


    #include "Student.cpp" 包含进来 , Student.cpp 中就有 Student.h , 变相的将这两个代码定义在同一个文件中 ;

    相当于 将 类模板 的 函数声明 和 函数实现 都定义在了 Student.h 头文件中 ;

    这种类型的头文件 可以改成 .hpp 后缀 , 表明该文件中同时包含了 函数声明 和 函数实现 ;





    二、代码示例 - 函数实现 写在类外部的不同的 .h 头文件和 .cpp 代码中




    1、完整代码示例



    Student.h 头文件内容


    Student.h 头文件内容 :

    #include "iostream"
    using namespace std;
    
    template <typename T>
    class Student
    {
    	// 左移运算符重载
    	friend ostream& operator<< <T> (ostream& out, Student& s);
    
    public:
    	// 构造函数
    	Student(T x, T y);
    
    	// 重载 + 运算符
    	Student operator+(Student& s);
    
    public:
    	T a, b;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    Student.cpp 代码文件内容


    Student.cpp 代码文件内容 :

    #include "Student.h"
    
    // 类模板构造函数
    // 使用  Student:: 域操作符访问函数
    template <typename T>
    Student<T>::Student(T x, T y)
    {
    	this->a = x;
    	this->b = y;
    }
    
    // 重载 + 运算符
    // 使用  Student:: 域操作符访问函数
    template <typename T>
    Student<T> Student<T>::operator+(Student<T>& s)
    {
    	// 函数内部的类的  模板类型 , 可加 Student 可不加 Student
    	// 不加  也可以使用 , 加了也不会报错
    	Student student(this->a + s.a, this->b + s.b);
    	return student;
    }
    
    // Student 类的友元函数
    // 左移运算符重载 函数
    template <typename T>
    ostream& operator<<(ostream& out, Student<T>& s)
    {
    	out << "a:" << s.a << " b: " << s.b << endl;
    	return out;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    Test.cpp 代码文件内容


    Test.cpp 代码文件内容 :

    #include "iostream"
    using namespace std;
    
    // 此处不能导入 .h 头文件
    // 必须导入 .cpp 源码文件
    #include "Student.cpp"
    
    int main() {
    	
    	// 模板类不能直接定义变量
    	// 需要将 模板类 具体化之后才能定义变量
    	Student<int> s(666, 888);
    	cout << s << endl;
    
    	Student<int> s2(222, 111);
    	cout << s2 << endl;
    
    	// 验证 加法运算符 + 重载
    	Student<int> s3 = s + s2;
    
    	// 验证 左移运算符 << 重载
    	cout << s3 << endl;
    
    	// 控制台暂停 , 按任意键继续向后执行
    	system("pause");
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    2、执行结果


    执行结果 :

    a:666 b: 888
    
    a:222 b: 111
    
    a:888 b: 999
    
    请按任意键继续. . .
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

  • 相关阅读:
    cloud探索 - AWS容器
    CRC校验码2018-架构师(六十一)
    三维模型3DTile格式轻量化在网络传输中的重要性分析
    《Linux 内核设计与实现》13. 虚拟文件系统
    pybullet 安装失败缺失 Microsoft Visual C++ 14.0 解决方案
    Java多线程悲观锁和乐观锁
    《向量数据库指南》——完善产品拼图,GBASE南大通用发布向量数据库
    【仿牛客网笔记】 Spring Boot进阶,开发社区核心功能-统一处理异常
    C#创建磁性窗体的方法:创建特殊窗体
    LeetCode第155题—最小栈
  • 原文地址:https://blog.csdn.net/han1202012/article/details/134535423