一:字面值常量
字面值常量是其值在编译时就已经被知道。
内置类型有字面值常量,比如1.2是double类型的字面值常量。那么用户自定义类型有没有字面值常量呢?答案是有,有两种方法可以使用户自定义类型具有字面值常量,下面介绍一种方法,另外一种方法会在第19章被介绍。
我们可以通过声明构造函数为constexpr函数来使该类具有字面值常量。例子代码如下:
class ex{
public:
constexpr explicit ex(int i=0):a(i){}
private:
int a;
};
声明类ex的构造函数为constexpr函数使得构造函数可以在编译时被计算。
constexpr ex e1{3}; //在编译时就被计算了,因此e1是类ex的字面值常量
ex e2{3}; //e2不是ex类的字面值常量。a constexpr function can be used for non-constant arguments, but when that is done, the result is not a constant expression.
二:类型转换:
1:可用一个实参调用的非explicit构造函数定义一个隐式类型的转换。当提供了实参类型的对象而需要一个类类型的对象时,编译器将使用该转换,这种构造函数定义了到类类型的转换。
注意:通常,除非有明显的理由想要定义隐式转换,否则单形参构造函数应该为explicit。将构造函数设置为explicit可以避免错误。并且当转换有用时,用户可以显示地构造对象。
2:除了上述说的到类类型的转换,我们还可以定义从类类型的转换。即我们可以定义转换操作符,给定类类型的对象,该操作符将产生其他类型的对象。
(1):转换操作符是一种特殊的类成员函数,它定义将类类型值转换为其它类型值。转换操作符在类定义体内声明,在保留字operator之后跟着转换的目标类型。代码例子如下:
class ex{
public:
constexpr explicit ex(int i=0):a(i){} //explicit抑制了整型int到ex类的转换。
operator int() const { return a; } //定义了从类ex到整型int的转换。
private:
int a;
};
(2):转换函数采用 operator type()的形式。这里type表示内置类型名,类类型名或由类型别名定义的名字。一般而言,不允许转换为数组或函数类型,转换为指针类型以及引用类型是可以的。
注意:转换函数必须是类成员函数,不能指定返回类型,并且形参表必须为空。并且转换函数一般不应该改变被转换的对象,因此转换操作符通常定义为const成员。
(3):我们能够声明一个转换操作符为explicit,如果这样声明的话,则这个转换操作符仅仅只能用于直接初始化的情况,不能用于复制初始化。
比如如果上述例子中的类ex的转化操作符声明为explicit。则
ex类中的转换操作符的定义:explicit operator double() const { return a; }
ex e1{3};
double a(e1); //okay,直接初始化
double a=e1; //error,这是复制初始化,声明为explicit的转换操作符不能用于复制初始化。
(4):类类型转换和标准转换
使用转换函数时,被转换的类型不必与所需要的类型完全匹配,必要时可在类类型转换之后跟上标准转换以获得想要的类型。比如我们想要把类ex一个对象的值e1与double作比较,那么编译器会先将e1转换为int类型,然后再将int类型转换为double。
(5):只能应用一个类类型转换
类类型转换之后不能再跟另一个类类型转换。如果需要多个类类型转换则代码出错。比如我有一个类EX,在类EX中我定义了一个转换为类ex的操作符。在这时候我们或许会想用类EX对象的值与整型int的值直接比较,因为类EX可以转化为类ex,类ex可以转化为int,但这种想法是行不通的,因为c++规定只能应用一个类类型转换,如果需要多个类类型转换代码将出错。
(6):转换操作符使用不当可能会引起二义性,因此我们应该限制转换操作符的数目,保证最多只有一种途径将一个类型转化为另一个类型,不要相互转化。另外当转换操作符用于没有明显映射关系的类类型和转化类型之间时,容易引起误解,在这种情况下,提供转换函数可能会令类的使用者迷惑不解,那么最好定义普通成员函数从类中获取想要的信息。