多种方法表示方式和类
可以用不同但等价的方式表示的量,类似于前面的货币,黄金的用各种货币表示,类非常适用于在一个对象中表现实体的不同方面,首先在一个对象中存储多种表示方式,然后编写类函数,使得当以任意一种方式赋值当时候都能自动更新所有的量,也可以如上所示,只存储一种表示方法,在需求其他表示方式的时候进行转换。类允许从本质上而不是从表达方式看一个量
为矢量类重载算术运算符
如果方法通过计算得到一个新的类对象,则应该考虑是否应该通过构造函数建立,这种方法不仅可以避免麻烦,也可以保证类对象的正确建立/
因为元素符重载是通过函数重载实现的,所以与函数重载相同,只要签名不同,就可以多次重载一个运算符。
对实现的说明
前面实现了矢量的这一实现,Vector中只存储了x,y坐标而极坐标则通过转换得出,但是我们发现公有接口并不依赖这一事实,无论我们内部使用x,y实现还是极坐标,或者使用其他任何方法实现,所有接口只要求返回这两个值,这就是OOP的目标之一—将接口和实现分离。
类的自动转换和强制类型转换
C++如何处理用户定义的类型的转换?在看这个之前先看C++是如何处理内置数据类型转换的,看如下语句
long count = 0;// int -> long
double d = 0; // int -> double
int a = 3.3; // double -> int
C++将拒绝自动转换不兼容的类型
int * p = 10; // 显然 10 并不是一个有效的地址
但是仍可完成强制转换
int * p = (int *) 10;
可以将类定义为一种基本类型或者与另外一个类相关是有意义的,使得从一种类型转换为另一种类型。在这种转换中程序员可以指定C++如何自动进行转换或者强制转换。我们不妨从磅和石头互转入手
#ifndef STONEWT_H
#define STONEWT_H
class Stonewt
{
enum class Stonewt_C{LBS_PER_STONE = 14};
double stone;
double pounds_left;
double pounds;
public:
Stonewt(int stone,double pounds);
Stonewt(double pounds);
Stonewt();
~Stonewt();
void show_lbs() const;
void show_stn() const;
};
#endif //STONEWT_H
#include "Stonewt.h"
#include <iostream>
Stonewt::Stonewt(int stone, double pounds)
{
this->stone = stone;
this->pounds_left = pounds;
this->pounds = stone * (int)Stonewt_C::LBS_PER_STONE + pounds;
}
Stonewt::Stonewt(double pounds)
{
this->pounds = pounds;
stone = ((int)pounds) / (int)Stonewt_C::LBS_PER_STONE;
this->pounds_left = int (pounds) % (int) Stonewt_C::LBS_PER_STONE
+ (pounds - int(pounds) );
}
Stonewt::Stonewt()
{
pounds_left = pounds = stone = 0;
}
Stonewt::~Stonewt()
= default;
void Stonewt::show_lbs() const
{
using std::cout;
using std::endl;
cout << pounds << " pounds" << endl;
}
void Stonewt::show_stn() const
{
using std::cout;
using std::endl;
cout << stone << " stone " << pounds_left << " pounds" << endl;
}
在如上类可以使用如下赋值来初始化
Stonewt a = 14.2;
原因在于,在C++中接受一个参数的构造函数为将类型与该参数相同的值转换为类提供了蓝图,记住只有一个参数才可以,两个参数就不行,除非为除了第一个参数后面的参数全部提供默认值
将构造函数用作自动类型转换函数似乎是一个不错的主意,但是这会导致意外的转换,在C++11新增了 e x p l i c i t explicit explicit来关闭这种隐式的自动转化,但仍然允许现实的强制转换如下
Stoenwt a = Stonewt(14.2); // 这很像是在调用构造函数 啊哈哈哈
Stonewt a = (Stonewt)14.2;
如果不禁用显示转换,则还可以用于一下用途
- 将对象初始化为double值的时候
- 将double复制给对象的时候
- 将double值传递给接受stonewt对象的时候
- 返回值被声明为Stonewt但试图返回double值的时候
当然只有当转换不具有二义性时候,编译器才会进行这种转换,如何还定义了Stonewt(long)编译器将拒绝进行转换。
转换函数
因为构造函数只使用从某种类型到类类型的转换,但是要进行相反的转换,必须使用特殊的C++运算符函数—转换函数
转换函数时用户定义的强制类型转换,可以像使用强制转换那样使用它。或者让编译器来使用它们(隐式)
如何创建转换函数 要转换为typename类型 声明如下
operator typename();
- 必须是类方法
- 转换函数不能制定返回类型
- 转换函数没有参数
例如如下
operator double();
转换函数需要通过类对象调用,参数为隐式传递的this指针。
自动类型转换
如果一个类cat a能转换为double又能转换为long那么如下语句会怎么办?
cout << a << endl;
答案是编译器将拒绝这种转换,转换存在二义性,有趣的事如果只定义其中一种转换,转换可以进行
为什么呢,因为无论是转换为double还是long这种转换都是合法的,编译器不想承担选择转换函数的责任,所以他将拒绝转换,并抛出存在二义性的错误。
在C++11中你可以将 e x p l i c i t explicit explicit关键字同样用于转换函数使其不能用于隐式转换而解决上述相同的问题
当然另外一种选择不适用转换函数,而是提供类成员函数使其返回值为想转换的类型,也能解决隐式转换带来的烦恼
应当十分谨慎的使用隐式转换函数,通常最好选择仅在显示调用时采用执行的函数
C++提供为类提供以下类型转换
- 只有一个必须提供的参数的类构造函数用于将类型与该参数相同的值转换为类类型。在构造函数声明 e x p l i c i t explicit explicit可以禁止隐式转换
- 转换函数的特殊成员函数,原型如上,要求必须是类成员,没有返回值,没有参数。如果没有 e x p l i c i t explicit explicit限制,将可以用隐式转换。
转换函数和友元函数
Stonewt加法函数可以实现如下
Stonewt Stonewt::operator+(const Stonewt & st) const
{
double pds = pounds + st.pounds;
Stonewt sum(pds);
return sum;
}
或者用友元函数实现如下
Stonewt operator+(const Stonewt & s1,const Stonesw & 2) const
{
return Stonewt{s1.pounds + s2.pounds};
}
你可以选择任意一种定义,但是不能都选!
有关于友元函数重载运算符和成员函数重载运算符与转换函数隐式使用详见C++PrimerPlus342,太过于复杂
我个人喜欢禁用转换函数的隐式转换,对于地位平等的运算使用友元函数处理,地位有区别的使用成员函数。或者使用使用成员函数重载运算来匹配成员运算符。
总结
一般来说访问类的私有成员的方法是使用类的公有接口,C++使用友元函数来避开这种限制,需要在类声明声明函数并加上关键字friend
C++允许用户重载运算符
C++允许用于定义转换函数