以下为个人学习笔记整理。参考书籍《C++ Primer Plus》
# 使用类
# 运算符重载
C++ 允许给运算符赋予多种含义, C++ 支持自定义运算符的含义。
// op: +、-、*、[] ... | |
operator op(arguemrnt-list)  | 
# 计算时间:重载运算符示例
tm.h 头文件:
class Time {  | |
private:  | |
int hours;  | |
int minutes;  | |
public:  | |
Time();  | |
Time(int h, int m);  | |
Time operator +(const Time& t) const;  | |
void show() const;  | |
};  | 
tm.cpp 源文件:
#include "tm.h" | |
#include <iostream> | |
Time::Time() {  | |
this->hours = this->minutes = 0;  | |
} | |
Time::Time(int h, int m) {  | |
this->hours = h;  | |
this->minutes = m;  | |
} | |
Time Time::operator+(const Time& t) const {  | |
	Time sum; | |
sum.minutes = this->minutes + t.minutes;  | |
sum.hours = this->hours + t.hours + sum.minutes / 60;  | |
sum.minutes %= 60;  | |
return sum;  | |
} | |
void Time::show() const {  | |
std::cout << "minues:" << this->minutes << ",hours:" << this->hours << std::endl;  | |
} | 
使用:
Time t_1{ 20,20 };  | |
Time t_2{ 10,10 };  | |
Time sum_t = t_1 + t_2;  | |
Time sum_z = t_1.operator+(t_2); // 两者等效,因为 operator+ 和 + 都被视作 Time 类的成员函数  | 
# 重载限制
- 重载后的运算符必须至少有一个操作数是用户自定义的类型。因此用户不能为标准类型进行重载。
 - 重载运算符时,不能对运算符本身的句法规则进行修改。例如求模运算符(%),不能只有一个操作数。并且不能修改运算符的优先级。
 - 不能创建新的运算符。例如不能定义 
**表示求幂,operator **() // is fail。 - 如下运算符不支持重载:
sizeof:sizeof运算符。.:成员运算符。.*:成员指针运算符。:::作用域解析运算符。?::条件运算符。typeid:一个 RTTI 运算符。const_cast:强制类型转换运算符。dynamtic_cast:强制类型转换运算符。reinterpret_cast:强制类型转换运算符。static_cast:强制类型转换运算符。
 
# 可重载运算符:

# 只能通过成员函数进行重载的运算符:
=:赋值运算符。():函数调用运算符。[]:下标运算符。->:通过指针访问成员运算符。
# 友元
通常情况下,公有类方法是类对象提供外界访问的唯一途径,但这有时候过于严苛,因此加入友元的概念,进一步对访问控制进行划分。
- 友元函数。
 - 友元类。
 - 友元成员函数。
 
通过让函数称为类的友元,可以赋予该函数与类的成员函数相同的访问权限。
# 为何需要友元?
当为类重载二元运算符时。例如乘除操作,往往会涉及到多个不同类型的对象,这时候,友元便派上了用场。
Type_A = Type_A * Type_Int; // 如果在 Type_A 中定义了该操作,那么这段代码有校  | |
Type_A = Type_Int * Type_A; // 但是如果调换两者的位置关系,那么将会报错  | 
常见的解决办法:
- 规定写法,只能是 
Type_A * Type_Int;,这显然不是一个好的选择。 - 把运算符设置为一个非成员函数,这会导致没办法通过参数对象访问其私有成员。
 
# 创建友元函数
创建友元函数的第一步是将函数原型放在类声明中,并且加上关键字 friend 修饰。
friend Time operator * (int c, const Time& t);  | |
Time operator*(double m, const Time& t) {  | |
	Time dot; | |
long total_mintues = t.hours * m * 60 + t.minutes * m;  | |
dot.hours = total_mintues / 60;  | |
dot.minutes = total_mintues % 60;  | |
return dot;  | |
} | 
友元函数的两个特性:
- 虽然 
operator *()函数的声明是在类声明内,但此次,该函数已经不能视作成员函数,因此不能用time.operator *()的方式进行调用。 - 虽然 
operator *()函数不是成员函数,但是其访问权限和成员函数一致。 
此时的友元函数其实更像一个非成员函数,所以可以用如下方式调用:
Time t{1,1};  | |
Time result = operator *(1.1, t);  | 
# 常用的友元:重载 << 运算符
最常见的输出方式就是通过 cout << xxx 的形式,在控制台打印对象的信息。那么如果通过重载 << 运算符,使得 Time 类也能够被输出呢。
# op.1 使用友元函数
由于 cout 和 time 是两个不同类型的对象,且需要访问 time 的私有成员,因此必须把函数声明为友元函数。
由于该友元函数不需要访问 ostream 类的私有成员,所以可以不必设置为 ostream 的友元函数。
// .h | |
friend void operator <<(std::ostream& os, const Time& t);  | |
// .cpp | |
void operator<<(std::ostream& os, const Time& t) {  | |
os << t.hours << " hours," << t.minutes << ",minutes";  | |
} | 
# op.2 void 返回值存在的问题
上面的友元函数在某些特定的情况下没办法正常工作:
Time t; | |
cout << "is string" << t << "is string"; // 执行到第二个 is string 的时候程序会出错,因为重载后的运算结果是一个 void 而不是 ostream 对象。  | 
因此我们需要对函数进行一些调整:
// .h | |
friend std::ostream& operator <<(std::ostream& os, const Time& t);  | |
// .cpp | |
std::ostream& operator<<(std::ostream& os, const Time& t) {  | |
os << t.hours << " hours," << t.minutes << ",minutes";  | |
return os;  | |
} | 
friend关键字只能在函数的原型中使用,切忌用在函数的定义上。
# 重载运算符:作为成员函数还是非成员函数
有些情况下,成员函数是唯一的选择。而在其他情况下,两者并无区别,但对于代码健壮的角度来说,非成员函数是更好的选择。
# 类的自动转换和强制类型转换
通常情况下,将一个标准类型变量赋值给另一中标准类型变量时,如果两种类型兼容,那么将自动发生类型转换:
long count = 8; // int -> long  | |
double time = 11; // int -> double  | |
int side = 3.33; // double -> int  | 
当然也不是所有的类型都可以这么玩,还是有挺多例外的,但可以通过强制类型转换强行「矫正」:
int *p = 10;// fail  | |
int *p = (int *) 10; // ok  | 
# 实现自动类型转换的方式 —— 单个参数的构造函数:
class Data{  | |
private:  | |
int data;  | |
public:  | |
Data(double d);  | |
Data(float d);  | |
Data(int *d);  | |
};  | |
Data d = 1.1;  | |
Data d = 1.1f;  | |
int *p;  | |
Data d = p;  | 
# 关键字 explicit
某些特殊情况下,单一参数的构造函数所支持的这种类似自动类型转换的功能,可能并不是我们所需要的,这时候可以通过 explicit 关键字对构造函数进行限定,从而达到屏蔽该项功能的作用。但任然可以通过显式的类型转换实现。
explicit Data(int *d);  | |
int *p;  | |
Data d = p; // is fail  | |
Data d_1 = Data(p); // is ok  | 
# 何时会发生自动类型转换
- 将 
Data对象初始化为double值时。Data d = 1.1; - 将 都变了值赋给 
Data对象时。Data d; d = 1.1; - 将 
double值传递给接受Data参数的函数。void func(Data d); func(1.1); - 返回值为 
Data的函数试图返回double值时。Data func(); return 1.1; - 在上述任意情况下,可以转化为 
double类型的对象,都可以满足以上的自动转换,从而成为Data对象。 
# 强制类型转换 —— 转换函数
- 转换函数必须时类方法。
 - 转换函数不能指定返回值类型。
 - 转换函数不能有参数。
 
转换函数的定义:
operator typeName();  | |
// example | |
operator int();  | |
operator double;  | 
转换函数需要在类中进行声明和定义:
// .h | |
class Data{  | |
private:  | |
int data;  | |
public:  | |
Data(double d);  | |
Data(float d);  | |
Data(int *d);  | |
	// covert func | |
operator int() const;  | |
operator double() const;  | |
};  | |
// .cpp | |
Data::operator int() const{  | |
return data;  | |
} | |
Data::operator double() const{  | |
return double(data);  | |
} | 
隐式转换函数的使用应该小心且谨慎。
# 转换函数和友元函数
转换函数和友元函数往往会造成某些误解:
- 如果定义了类型 Data 和 double 类型的转换。
 - 并且定义了 Data 类型和 double 类型的加法操作。
 
这时如果执行如下代码将会有二义性的问题:
Data d{1};  | |
double dou = 1.1;  | |
Data dd = d + dou; // double + double or Data + double ???  | |
// 到底是两个 double 相加呢, 还是一个 Data 和 一个 double 相加,这是一个值得思考的问题。 | |
// 实际的结果是 两个 double 类型进行加法运算。 |