目录
所有函数都写在类的外部,而且都分装在一个.hpp文件和一个.h文件里面
在项目开发中,一般需要把函数的声明与实现分开,这个所用到的语法并不简单,更何况再算上类模板这个形式的加入,语法就变的更复杂了,所以,进行分类的话可分为三种:
- 所有的类模板函数写在类的内部
- 所有的类模板函数写在类的外部,但都在在一个cpp中
- 所有的类模板函数写在类的外部,在不同的.h和.cpp中
下面以复数类为例,进行演示实际工程中的操作,
所有函数都写在类的内部
实现函数重载的功能
开始构建类:
class Complex
{
public:
Complex(int a = 0, int b = 0)
{
this->a = a;
this->b = b;
}
Complex operator+(Complex &c2)
{
Complex tmp(a + c2.a, b + c2.b);
return tmp;
}
void printCom()
{
cout << "a:" << a << "b:" << b << endl;
}
private:
int a;
int b;
};
那么,想重载"+"运算符,在类内部插入:
Complex operator+(Complex &c2)
{
Complex tmp(a + c2.a, b + c2.b);
return tmp;
}
想重载"<<"运算符,就将其设置为全局函数进行重载:
ostream & operator<<(ostream &out, Complex &c3)
{
out << "a:" << c3.a << " b: " << c3.b << endl;
return out;
}
而且因为在这里面,使用了上面类的私有变量“a、b” ,将这个的友元声明放到上面的类中:
friend ostream & operator<<(ostream &out, Complex &c3)
进行调用:
void main()
{
Complex c1(1, 2);
Complex c2(3, 4);
Complex<int> c3 = c1 + c2; //实现运算符重载
Complex operator+(Complex &c2)//运算符重载的函数原型
//c3.printCom();
cout << c3 << endl;
cout<<"hello..."<<endl;
system("pause");
return ;
}
就成功运行了!
那么下面就开始将上面的代码改成模板类,开始进行泛型编程,同时依据本节是要把所有函数都写在类的内部的要求,需要把上目的重载"<<"的友元函数,也写进去:
template <typename T>
class Complex
{
friend ostream & operator<<(ostream &out, Complex &c3)
{
out << c3.a << " + " << c3.b << "i" << endl;
return out;
}
public:
Complex(T a, T b)
{
this->a = a;
this->b = b;
}
Complex operator+ (Complex &c2)
{
Complex tmp(a+c2.a, b+c2.b);
return tmp;
}
void printCom()
{
cout << "a:" << a << " b: " << b << endl;
}
private:
T a;
T b;
};
再进行调用:
void main()
{
//需要把模板类 进行具体化以后 才能定义对象 C++编译器要分配内存
Complex<int> c1(1, 2);
Complex<int> c2(3, 4);
Complex<int> c3 = c1 + c2;
//c3.printCom();
cout << c3 << endl;
cout<<"hello..."<<endl;
system("pause");
return ;
}
显示的运算结果,也如我们所期。 可以发现,将所有函数都写在类的内部,是比较简单的!
那么,想再添加一个函数,因为"重载 '<<'、' >>' 只能用友元函数 ,其他运算符重载都要写成成员函数,不能造成友元函数的滥用",意思是之前已经通过重载的方式去重载"<<"符号,实现了"+"号连接的复数表示,如果想实现"-"号连接的复数表示不能再通过重载的方式了就,重载"<<"只能用友元函数,如果再进行重载就必须再用到友元函数,这是对友元函数的滥用。
综上,新建一个函数实现"-"号连接的复数表示,就不能再用重载操作了,只能新写一个成员函数来实现,在主函中添加:
{
Complex<int> c4 = MySub(c1, c2);
cout << c4 << endl;
}
然后,在Complex类的里面添加函数:
friend Complex MySub(Complex &c1, Complex &c2)
{
Complex tmp(c1.a - c2.a, c1.b - c2.b);
return tmp;
}
运行结果:
所有函数都写在类的外部,但都在一个.hpp文件里面
提取构造函数到类外面
依旧延续上面的函数,先把类中的构造函数进行外提,1)把里面的函数实现,搞成函数声明;2)添加类的域名;3)把"template"这个关键字也外提:
//构造函数的实现 写在了类的外部
template <typename T>
Complex<T>::Complex(T a, T b)
{
this->a = a;
this->b = b;
}
然后,在类中仅保留声明:
Complex(T a, T b);
这样就成功的把构造函数给提取出来了!
提取普通函数到类外面
下面再把"printCom"这个普通函数也给提出来,在类里面,把函数实现改成函数声明:
void printCom();
把函数实现的部分,提到类的外面:
template <typename T>
void Complex<T>::printCom()
{
cout << "a:" << a << " b: " << b << endl;
}
提取类的成员函数到类外面
接着把类的成员函数也提到类的外面:
//成员函数 实现 +运算符重载
template <typename T>
Complex<T> Complex<T>::operator+ (Complex<T> &c2)
{
Complex tmp(a+c2.a, b+c2.b);
return tmp;
}
可以看出,这个函数的外提是非常复杂的, 需要注意的是,函数的参数是需要具体类型化的,函数的函数值也是需要具体类型化的。
同时,在类中也要保留声明:
Complex operator+ (Complex &c2);
提取友元函数到类外面(难点)
接下来开始将友元函数提到类的外部来,
//友元函数 实现 << 运算符重载
template <typename T>
ostream & operator<<(ostream &out, Complex<T> &c3)
{
out << c3.a << " + " << c3.b << "i" << endl;
return out;
}
类中保留声明:
friend Complex operator<<(ostream &out, Complex &c3)
注意难点来了!难就难在这既是一个友元函数也是一个模板函数,当然,这不是最难的,最难的是,这个地方经常报这样的错:
/*
1>dm08_复数类_所有函数都写在类的外部(在一个cpp中).obj : error LNK2019: 无法解析的外部符号 "
class std::basic_ostream<char,struct std::char_traits<char> > &
__cdecl operator<<
(class std::basic_ostream<char,struct std::char_traits<char> > &,class Complex<int> &)"
(??6@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@std@@AAV01@AAV?$Complex@H@@@Z),
该符号在函数 _main 中被引用
1>E:\01-work\21_CPlusPlus课程\day09\泛型编程课堂操练\Debug\泛型编程课堂操练.exe : fatal error LNK1120: 1 个无法解析的外部命令
*/
这是为什么呢? 这是因为上面写了函数原型,下面也将函数原型中规中矩的去实现了出来,会发现函数的实现体和函数的声明是没有错误的,是完全匹配的,那产生上面这种报错的原因是,与函数模板的实现机制有关系的,即之前记录的,两次编译原理。是因为当编译器第一次编译函数模板的时候,经过简单的语法分析,把所有的函数模板都生成了一个函数头,而第二次再进行编译的时候,又生成了一个函数头,编译器会发现,这两个地方生成的函数头不一样!就造成了执行的时候找这个函数的实现体找不着的状况,即上面的报错。那这种状况,如何破呢?应该在类中声明的地方,将原来声明的地方,修改为:
friend ostream & operator<< <T> (ostream &out, Complex &c3);
就是后面的大括号的前面添一个"<T>"的符号,这样就成了!
师说:这个地方是C++编译器大牛做的不好的地方,可能是觉得"friend"关键字本身就是作为一个编译器开了后门的存在,用的就很少,再加上模板,用的就更少了,所以没补。但是,话说回来,如果滥用"friend"关键字,就是添加了"<T>"也照样报错,没法修正。
总结一遍:如果想把左移运算符"<<"在类的中实现的重载的函数,提到类的外面,需要清楚的知道,左移运算符是用友元函数实现的,友元函数是全局函数,不属于哪一个类,所以,一是千万不要用类的作用域名作用符"::"去修饰;二是它既是友元函数但同时也是个模板函数,不要忘了模板的关键字,检查形参,返回值;三是检查函数名是不是不匹配。对应的检查一遍函数的三要素:名称,参数,返回值!思路一定要清晰。
友元函数的真正应用场景,也只有在左移右移运算符重载的时候才用到,其他地方,都不要用!!!
用的话,就会出现问题!请看下面,接着把类中的另一个友元函数"Mysub"也提到类的外面中去,接下来就看看滥用友元函数会有什么后果,按照常规思路,先在类外面将函数实现部分给写清楚:
template <typename T>
Complex<T> MySub(Complex<T> &c1, Complex<T> &c2)
{
Complex<T> tmp(c1.a - c2.a, c1.b - c2.b);
return tmp;
}
然后,类中保留声明:
friend Complex<T> MySub<T> (Complex<T> &c1, Complex<T> &c2);
按理说这样就搞完了,那么进行编译,发现就是过不了,而且,跟上面那个处理的方式一样,在后面的大括号的前面添一个"<T>"的符号,需要添加这里又添加了 "MySub<T>"这个关键字。
还缺一堆的前置声明:
#include <iostream>
using namespace std;
template <typename T>
class Complex ; //类的前置声明
template <typename T>
Complex<T> MySub (Complex<T> &c1, Complex<T> &c2);
template <typename T>
class Complex
{
friend Complex<T> MySub<T> (Complex<T> &c1, Complex<T> &c2);
public:
private:
};
而且,在调用的时候,还需要:
Complex<int> c4 = MySub<int>(c1, c2);
cout << c4 << endl;
这样才能编译通过!
综上所述,能不用友元函数就别用!!!!用的话,太tm难搞了!
需要添加两个前置,一个声明,和调用的时候特殊符号。
总结
//构造函数 没有问题
//普通函数 没有问题
//友元函数:用友元函数重载 << >>
// friend ostream& operator<< <T> (ostream &out, Complex<T> &c3) ;
//友元函数:友元函数不是实现函数重载(非 << >>)
//1)需要在类前增加 类的前置声明 函数的前置声明
template<typename T>
class Complex;
template<typename T>
Complex<T> mySub(Complex<T> &c1, Complex<T> &c2);
//2)类的内部声明 必须写成:
friend Complex<T> mySub <T>(Complex<T> &c1, Complex<T> &c2);
//3)友元函数实现 必须写成:
template<typename T>
Complex<T> mySub(Complex<T> &c1, Complex<T> &c2)
{
Complex<T> tmp(c1.a - c2.a, c1.b - c2.b);
return tmp;
}
//4)友元函数调用 必须写成
Complex<int> c4 = mySub<int>(c1, c2);
cout << c4;
结论:友元函数只用来进行 左移 友移操作符重载。
所有函数都写在类的外部,而且都分装在一个.hpp文件和一个.h文件里面
依旧从上一部分开始延续, 且这种将,声明(.h),函数实现(.hpp),和主调函数(.cpp)三个部分,分别放在三个不同的文件中的做法,才是工程中,常用的做法。(.hpp文件是对声明的.h文件做一个补充)
将所有的声明部分,都搞到.h文件当中,
dm09_complex.h:
#pragma once
#include <iostream>
using namespace std;
template <typename T>
class Complex
{
//friend Complex<T> MySub (Complex<T> &c1, Complex<T> &c2);
friend ostream & operator<< <T> (ostream &out, Complex &c3);
public:
Complex(T a, T b);
void printCom();
Complex operator+ (Complex &c2);
private:
T a;
T b;
};
那么,.hpp文件该写成如下,里面只存放函数部分,
dm09_complex.hpp:
#include <iostream>
using namespace std;
#include "dm09_complex.h"
//构造函数的实现 写在了类的外部
template <typename T>
Complex<T>::Complex(T a, T b)
{
this->a = a;
this->b = b;
}
template <typename T>
void Complex<T>::printCom()
{
cout << "a:" << a << " b: " << b << endl;
}
template <typename T>
Complex<T> Complex<T>::operator+ (Complex<T> &c2)
{
Complex tmp(a+c2.a, b+c2.b);
return tmp;
}
template <typename T>
ostream & operator<<(ostream &out, Complex<T> &c3)
{
out << c3.a << " + " << c3.b << "i" << endl;
return out;
}
再开一个.hpp文件,里面专门存放主函数,该有的函数头,不能少!
dm09_complex_test.cpp:
#include <iostream>
using namespace std;
#include "dm09_complex.hpp"
void main()
{
//需要把模板类 进行具体化以后 才能定义对象 C++编译器要分配内存
Complex<int> c1(1, 2);
Complex<int> c2(3, 4);
Complex<int> c3 = c1 + c2;
//c3.printCom();
cout << c3 << endl;
cout<<"hello..."<<endl;
system("pause");
return ;
}
总体代码
dm07_复数类_所有函数都写在类的内部.cpp
#include <iostream>
using namespace std;
template <typename T>
class Complex
{
friend Complex MySub(Complex &c1, Complex &c2)
{
Complex tmp(c1.a - c2.a, c1.b - c2.b);
return tmp;
}
friend ostream & operator<<(ostream &out, Complex &c3)
{
out << c3.a << " + " << c3.b << "i" << endl;
return out;
}
public:
Complex(T a, T b)
{
this->a = a;
this->b = b;
}
Complex operator+ (Complex &c2)
{
Complex tmp(a+c2.a, b+c2.b);
return tmp;
}
void printCom()
{
cout << "a:" << a << " b: " << b << endl;
}
private:
T a;
T b;
};
//运算符重载的正规写法
// 重载 << >> 只能用友元函数 ,其他运算符重载 都要写成成员函数 , 不要滥用友元函数
/*
ostream & operator<<(ostream &out, Complex &c3)
{
out << "a:" << c3.a << " b: " << c3.b << endl;
return out;
}
*/
void main()
{
//需要把模板类 进行具体化以后 才能定义对象 C++编译器要分配内存
Complex<int> c1(1, 2);
Complex<int> c2(3, 4);
Complex<int> c3 = c1 + c2;
//c3.printCom();
cout << c3 << endl;
//滥用友元函数
{
Complex<int> c4 = MySub(c1, c2);
cout << c4 << endl;
}
cout<<"hello..."<<endl;
system("pause");
return ;
}
dm08_复数类_所有函数都写在类的外部(在一个cpp中).cpp
#include <iostream>
using namespace std;
template <typename T>
class Complex ; //类的前置声明
template <typename T>
Complex<T> MySub (Complex<T> &c1, Complex<T> &c2);
template <typename T>
class Complex
{
friend Complex<T> MySub<T> (Complex<T> &c1, Complex<T> &c2);
friend ostream & operator<< <T> (ostream &out, Complex &c3);
public:
Complex(T a, T b);
void printCom();
Complex operator+ (Complex &c2);
private:
T a;
T b;
};
//构造函数的实现 写在了类的外部
template <typename T>
Complex<T>::Complex(T a, T b)
{
this->a = a;
this->b = b;
}
template <typename T>
void Complex<T>::printCom()
{
cout << "a:" << a << " b: " << b << endl;
}
//本质是 : 模板是两次 编译生成的 第一次生成的函数头 和第二次生成的函数头 不一样
//成员函数 实现 +运算符重载
template <typename T>
Complex<T> Complex<T>::operator+ (Complex<T> &c2)
{
Complex tmp(a+c2.a, b+c2.b);
return tmp;
}
//友元函数 实现 << 运算符重载
/*
1>dm08_复数类_所有函数都写在类的外部(在一个cpp中).obj : error LNK2019: 无法解析的外部符号 "
class std::basic_ostream<char,struct std::char_traits<char> > &
__cdecl operator<<
(class std::basic_ostream<char,struct std::char_traits<char> > &,class Complex<int> &)"
(??6@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@std@@AAV01@AAV?$Complex@H@@@Z),
该符号在函数 _main 中被引用
1>E:\01-work\21_CPlusPlus课程\day09\泛型编程课堂操练\Debug\泛型编程课堂操练.exe : fatal error LNK1120: 1 个无法解析的外部命令
*/
template <typename T>
ostream & operator<<(ostream &out, Complex<T> &c3)
{
out << c3.a << " + " << c3.b << "i" << endl;
return out;
}
//
//滥用 友元函数
template <typename T>
Complex<T> MySub(Complex<T> &c1, Complex<T> &c2)
{
Complex<T> tmp(c1.a - c2.a, c1.b - c2.b);
return tmp;
}
void main()
{
//需要把模板类 进行具体化以后 才能定义对象 C++编译器要分配内存
Complex<int> c1(1, 2);
Complex<int> c2(3, 4);
Complex<int> c3 = c1 + c2;
//c3.printCom();
cout << c3 << endl;
//滥用友元函数
{
Complex<int> c4 = MySub<int>(c1, c2);
cout << c4 << endl;
}
cout<<"hello..."<<endl;
system("pause");
return ;
}
dm09_复数类_所有函数都写在类的外部(h和cpp分开).cpp
#include <iostream>
using namespace std;
template <typename T>
class Complex
{
friend Complex<T> MySub<T> (Complex<T> &c1, Complex<T> &c2);
friend ostream & operator<< <T> (ostream &out, Complex &c3);
public:
Complex(T a, T b);
void printCom();
Complex operator+ (Complex &c2);
private:
T a;
T b;
};
//构造函数的实现 写在了类的外部
template <typename T>
Complex<T>::Complex(T a, T b)
{
this->a = a;
this->b = b;
}
template <typename T>
void Complex<T>::printCom()
{
cout << "a:" << a << " b: " << b << endl;
}
//本质是 : 模板是两次 编译生成的 第一次生成的函数头 和第二次生成的函数头 不一样
//成员函数 实现 +运算符重载
template <typename T>
Complex<T> Complex<T>::operator+ (Complex<T> &c2)
{
Complex tmp(a+c2.a, b+c2.b);
return tmp;
}
template <typename T>
ostream & operator<<(ostream &out, Complex<T> &c3)
{
out << c3.a << " + " << c3.b << "i" << endl;
return out;
}
//
//滥用 友元函数
template <typename T>
Complex<T> MySub(Complex<T> &c1, Complex<T> &c2)
{
Complex<T> tmp(c1.a - c2.a, c1.b - c2.b);
return tmp;
}
void main()
{
//需要把模板类 进行具体化以后 才能定义对象 C++编译器要分配内存
Complex<int> c1(1, 2);
Complex<int> c2(3, 4);
Complex<int> c3 = c1 + c2;
//c3.printCom();
cout << c3 << endl;
//滥用友元函数
{
Complex<int> c4 = MySub<int>(c1, c2);
cout << c4 << endl;
}
cout<<"hello..."<<endl;
system("pause");
return ;
}