C++ primer plus 使用类上
目录
一 符号的重载
二 运算符号重载的问题
三 友元的引言
四 友元
五 类的自动类型转换
六 补充
总结
一 符号的重载
现在我们需要一个程序是把两个对象进行基于运算符号的操作
首先我们有这么一个程序,是计算对象里面的值的加减的时候,我们需要用到比较复杂的函数,如果这个时候,我们可以直接用+或者-的话来对这个对象的值进行操作,那么我们的复杂度是不是大大节省了很多很多,所以我么就来学习,怎么设计符号的重载来帮助我们编程
那么不管符号的重载怎么写,我们先用我们之前的方法进行编写两个值的比较,这里是编写了一个时间里的小时和分钟的计算
符号的重载,当我们有两个重载函数的时候,只要形参的个数或者顺序不一样那么就不是一样的重载
头文件#ifndef __COOL_H__ #define __COOL_H__ #include<iostream> using namespace std; class Time{ private: int minute; int hour; public: Time(); Time(int h,int m); void Addhour(int h); void Addmin(int m); Time AddClass(const Time& a) const; void show() const; }; #endif
工具函数
#include"cool.h" Time::Time(){ hour = minute = 0; } Time::Time(int h,int m){ hour = h; minute = m; } void Time::Addhour(int h){ hour = hour + h; } void Time::Addmin(int m){ minute = minute + 1; hour = minute / 60; minute = minute % 60; } Time Time::AddClass(const Time& a) const{ Time P; P.minute = a.minute + minute; P.hour = a.hour + hour + minute/60; P.minute %= 60; return P; } void Time::show() const{ cout << "minute : " << minute << " " << "hour : " << hour << endl; }
主函数
#include "cool.h" int main(){ Time cat(9,10); Time dog(10,10000); cat.show(); dog.show(); Time total_val = dog.AddClass(cat); total_val.show(); return 0; }
我们看看输出的结果
很重成功,我么输出了正确的结果,那么我们现在要写一个函数的重载
符号重载的格式
operatorop(argument-list)
operator 位操符 op为该符号 里面就是参数列表
例如, operator +( )重载+运算符, operator *( )重载*运算符。 op 必须是有效的 C++运算符,不能虚构一 个新的符号。 例如,不能有operator@( )这样的函数, 因为 C++中没有@运算符。然而, operator [ ] ( )函数 将重载门运算符,因为[]是数组索引运算符
那我们改上面的代码也就是这样
我们这样修改一下就好了,输还是上述结果
小结
我么在定义函数重载运算符号的时候,只需要在我们原来功能函数上加上一个operator op就好了,这样就可以实现我们的重载符号了
二 运算符号重载的问题
1 重载符号的规则
重载符号的时候,一个操作数的类型至少是自定义类型
使用运算符的时候不能违反运算符原来的句法规则(如不可以把%变成单目运算符)
不可以修改符号的优先级
要在原有的运算符的基础上去添加运算符
大多数运算符可以通过成员函数和非成员函数进行重载运算符
这个第五点,大多数可以通过成员函数或者非成员函数,然后这个非成员函数就是我们可以把这个类的数据设置为公有就可以了,但是这也会有弊端
这里的等于号这些只可以在成员函数进行重载是什么意思呢?
我们可以举例=为一个例子#ifndef __COOL_H__ #define __COOL_H__ class Num{ private: int num; public: Num(); Num(int n); void show() const; }; #endif
#include<iostream> #include"cool.h" using namespace std; int main(){ Num a; a = 10; return 1; }
#include<iostream> #include"cool.h" using namespace std; Num::Num(int n){ num = n; } Num::Num(){ num = 0; cout << "hello world" << endl; } void Num::show()const { cout << "this result is : " << num << endl; }
我们现在可以看到程序里面a=10,这个并不匹配,根据我们的常识可以知道一个整形不可以给一个类的对象进行赋值,但是我们可以看到还是可以输出结果
这个打印出了hello world这个字符串,这又是为什么呢?
其实C++给我们写出了一个默认的赋值运算符的重载函数,这样我们就可以进行运行,这个默认的运算符重载函数是去寻找对应的与10对应类型的构造函数,然后可以理解这个10是一个实参
接下来我们来验证一下
我们可以看输出结果
这个是函数,我们先输出YOU LOVE ME这个就是int类型,说明这个直接去找int了,说明我们的想法是成立的
然后我们就知道了类是给我们了一个=赋值运算符重载函数
三 友元的引言
除了像上面那种直接把两个对象相加的操作,这个是基于两个对象进行操作,但是当我们不用一个对象,用一个对象和一个基本的操作数呢?会如何呢?
我们可以利用上一篇那个时间的程序进行编写一个这样的样例
头文件#ifndef __COOL_H__ #define __COOL_H__ #include<iostream> using namespace std; class Time{ private: int minute; int hour; public: Time(); Time(int h,int m); void Addhour(int h); void Addmin(int m); Time operator+(const Time& a) const; Time operator*(double a); void show() const; }; #endif
工具函数
#include"cool.h" Time::Time(){ hour = minute = 0; } Time::Time(int h,int m){ hour = h; minute = m; } void Time::Addhour(int h){ hour = hour + h; } void Time::Addmin(int m){ minute = minute + 1; hour = minute / 60; minute = minute % 60; } Time Time::operator+(const Time& a) const{ Time P; P.minute = a.minute + minute; P.hour = a.hour + hour + minute/60; P.minute %= 60; return P; } Time Time::operator*(double a){ Time result; long total_min = minute*a + hour*a*60; result.hour = total_min / 60; result.minute = minute % 60; return result; } void Time::show() const{ cout << "minute : " << minute << " " << "hour : " << hour << endl; }
主函数
#include "cool.h" int main(){ Time cat(9,10); cat.show(); Time total_val = cat * 10; total_val.show(); return 0; }
当我们这么写的话是正确的,我们可以运行看看输出结果
但是这个时候我们可以看到这个是cat在左边,这个时候我们把它放到右边呢?
不难看到这个是错误的,这个终端直接报错,这是为什么呢?
从概念上说, 2.75 * B 应该与 B *2.75是相同,但第一个表达式不对应成员函数,因为 2.75 不是Time类 型的对象。记住,左侧的操作数应是调用对象,但2.75不是对象。因此,编译器不能使用成员函数调用来替换该表达式
解决这个难题的一种方式是,告知每个人(包括程序员自己),只能按B* 2.75这种格式编写,不能写 成2.75 * B。这是一种对服务器友好---客户警惕的解决方案,与 OOP 无关,但是不可能让客户都这么干把,这样会降低客户的体验感
所以我们该怎么办呢?
如果我们把它变成非成员函数的话,那样就访问不到隐私变量,就很头疼,除非这么写
但是这个还是这个还是运用到了之前我们在成员函数定义的*重载函数,但是可以把这个数字放到左侧了,但是这个还是差点意思,那么这个时候友元就出来了
四 友元
1 引言
我们似乎可以用非成员函数来解决这个顺序问题,使用非成员函数可以按所需的顺序获得操作数(先是 double,然后是 Time),但引发了一个新问题: 非成员函数不能直接访问类的私有数据,至少常规非成员函数不能访问。然而,有一类特殊的非成员函数 可以访问类的私有成员,它们被称为友元函数,注意友元不是成员函数
2 创建友元
我们的声明是需要在class里声明的,定义的时候不要加上作用域运算符和friend,只在class里面加上firend
头文件#ifndef __COOL_H__ #define __COOL_H__ #include<iostream> using namespace std; class Time{ private: int minute; int hour; public: Time(); Time(int h,int m); void Addhour(int h); void Addmin(int m); Time operator+(const Time& a) const; friend Time operator*(double a,const Time& b); void show() const; }; #endif
工具函数
#include"cool.h" Time::Time(){ hour = minute = 0; } Time::Time(int h,int m){ hour = h; minute = m; } void Time::Addhour(int h){ hour = hour + h; } void Time::Addmin(int m){ minute = minute + 1; hour = minute / 60; minute = minute % 60; } Time Time::operator+(const Time& a) const{ Time P; P.minute = a.minute + minute; P.hour = a.hour + hour + minute/60; P.minute %= 60; return P; } Time operator*(double a,const Time& b){ Time result; long total_min = b.minute*a + b.hour*60*a; result.hour = total_min / 60; result.minute = total_min % 60; return result; } void Time::show() const{ cout << "minute : " << minute << " " << "hour : " << hour << endl; }
主函数
#include "cool.h" int main(){ Time cat(9,10); cat.show(); Time total_val = 10 * cat; total_val.show(); return 0; }
我们可以看到上面的程序是把这个顺序颠倒了
是可以运行的,在定义的时候不要加作用域和firend,声明的时候要在class里面进行声明还要加上friend,具有访问private数据的权限
如果要为类重载运算符,并将非类的项作为其第一个操作数,则可以用友元函数来反转操作数 的顺序
3 常用的友元 重载<<运算符
我们要编写一个可以用cout来输出class的对象,那么我们就要进行重载,才可以进行输出对象最初, <<运算符是C和c++的位运算符,将值中的位左移,ostream类对该运算符进行了重载,将 其转换为一个输出工具。前面讲过,cout 是一个 ostream 对象,它是智能的,能够识别所有的 c++基本类型。 这是因为对于每种基本类型, ostream类声明中都包含了相应的重载operator<<( )定义。 也就是说,一个定义使用 int参数,一个定义使用 double 参数,等等。因此,要使 cout 能够识别 Time对象,一种方法是将一个新的函数运算符定义添加到 ostream 类声明中。 但修改 iostream 文件是个危险的主意,这样做会在标准接口上浪费时间。相反,通过Time类声明来让Time类知道如何使用 cout
4 <<第一重载版本
这讲的是什么意思呢?首先我这个就是我们在重载的时候,如果创建了一个成员函数来进行操作,根据我们上面的引言可知,我们的成员是要放到左侧的,就是这个第二个代码,但是我们会觉得很奇怪,这样设置不像是我们之前所使用的cout那样子,所以我们就使用友元函数来实现
访问哪一个成员的私有成员,就是哪一个类的友元,这里无非就是把ostream作为一个整体在使用
5 <<第二重载版本
像我们上面所说的,如果直接这么写的话是不可以进行连续输出的,但是我们在打印普通类型的变量的时候是可以连续进行输出的,如cout << a << b << endl;
像这样的操作我们上面那个函数是不可以进行这个操作的
我们可以把这个返回值变成ostream &这样就可以实现我们无限输出了,十分的简单,我们首先输出一个,饭后返回这个os,然后后面又有一个<<这样就又可以使用这个ostream的引用,就又可以进行输出了
小结:友元是可以作为一个非成员函数去看隐私值得,并且可以有效得解决掉操作数顺序得问题,我们在定义的时候不需要加上作用域和firend,在声明的时候是需要在class里面进行声明的,对于<<这个东西我们不需要在ostream进行修改直接用引用,如果要无线打印,要进行返回值的修改才可以
重载函数不可以又选择两者,即选择非成员又选择非成员,这样是不对的,当然对于类的设计,友元会相交好一些,非成员就是友元
五 类的自动类型转换
我们先来学习类有什么初始化
Stonewt a = 275; Stonewt incognito = Stonewt(275);// 创建了一个无名对象初始化incognito Stonewt wolfe(257.2);//也是可以的
有三种一个是直接运用=进行自动类型转换,还有一个创建一个无名的类的对象,然后把这个对象的内容赋值给incognito,还有一个就是利用括号,这个是隐式调用构造函数,这个我们前面学习过
然后我们现在来学习一下这个=自动类型转换是一个什么东西
头文件#ifndef __COOL_H__ #define __COOL_H__ using namespace std; class Stonewt{ private: static const int Lbs_per_stn = 14; int stone; double pds_left; double pounds; public: Stonewt(double lbs); Stonewt(int stn, double lbs); Stonewt(); void show_lbs()const; void show_stn()const; }; #endif
工具函数
#include<iostream> #include"cool.h" using namespace std; Stonewt::Stonewt(double lbs){ stone = (int)lbs / Lbs_per_stn; pds_left = (int)lbs % Lbs_per_stn + lbs - (int)lbs; pounds = lbs; } Stonewt::Stonewt(int stn, double lbs){ stone = stn; pds_left = lbs; pounds = stn * Lbs_per_stn + lbs; } Stonewt::Stonewt(){ stone = pds_left = pounds = 0; } void Stonewt::show_lbs()const{ cout << "stone = " << stone << "pds_left = " << pds_left << endl; } void Stonewt::show_stn()const{ cout << "pounds = " << pounds << endl; }
主函数
#include<iostream> #include"cool.h" using namespace std; int main(){ Stonewt a = 275; // Stonewt incognito = Stonewt(275); // 创建了一个无名对象初始化incognito Stonewt wolfe(257.2);//也是可以的 Stonewt taft(21,8); a.show_stn(); wolfe.show_lbs(); taft.show_stn(); cout << "------------------------------" << endl; a = 276.8; //自动类型转换 taft = 175; a.show_stn(); taft.show_stn(); return 0; }
我们可以看到这个=左值和右值并不匹配,所以这里肯定运用默认重载函数,其实这个就好比如,这个先去找到这个构造函数,看类型十分相互匹配,不相互匹配看可不可以强制转换
也就是说创建了一个临时的变量,然后对这个临时的变量进行初始化,然后把这个临时的变量赋值给这个a这个对象,这个就是类的自动转换
六 补充
补充 类里面的强制类型转换
当我们利用当我们进行初始化的时候,如果我们写了这么一个东西
display(422 , 2) ;
这里的第二个参数为int类型,这个时候,就会去找这个display第二个参数是int类型的函数,但是我们的程序里面只有double,这个时候就会采取强制类型转换的办法进行解决,把int转换为double类型
总结
符号重载
operator +( )重载+运算符
当我们运算符号重载函数形参的顺序和数量不一样的话,那么这两个符号重载函数是不一样的
符号重载的问题我们要知道哪一些是不可以再非成员函数进行重载的
-> = ( ) [ ] 这四个是不可以的
友元的引言
当我们定义一个符号的重载成员函数的话,这个成员函数里面的操作数有一个是普通类型的话,这个时候顺序是不可以颠倒的,只有自定义类型才可以放到前面
友元的定义和使用
友元函数是非成员函数,这个我们一定要熟记
声明是需要声明在里面的,定义的时候不可以加上作用域和friend,这样是错误的
友元是可以解决掉操作数随意顺序的问题和可以让非成员函数访问到private里面的成员
类的自动转换
这个我们要知道类是有哪几种初始化的方式
类的自动转换是利用=
当我实际开发的时候,使用这个=出现了问题,我们可以在对应的构造函数前面加上explicit这样就可以实现关闭这个方法