C++_enhance-Record06—以复数类为例,进行实际工程中的操作演示

目录

所有函数都写在类的内部

实现函数重载的功能

所有函数都写在类的外部,但都在一个.hpp文件里面

提取构造函数到类外面

提取普通函数到类外面

提取类的成员函数到类外面

提取友元函数到类外面(难点)

总结

所有函数都写在类的外部,而且都分装在一个.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 ;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值