如果这篇博客帮助到你,可以请我喝一杯咖啡~
CC BY 4.0 (除特别声明或转载文章外)
寄语:
不要怕犯错误,因为在解决问题中学到的知识,比生搬硬套而不犯错误时要多得多。
一 操作符重载
操作符重载(operator overloading)
计算时间——操作符重载范例
如果 t1, t2, t3, t4
都是 Time
对象,可以这样做吗:
t4 = t1 + t2 + t3;
由于 +
是右结合(从左向右)的操作符,因此上面的语句转化为:
t4 = t1.operator+(t2 + t3);
进而转化为:
t4 = t1.operator+(t2.operator+(t3));
t2 与 t3 相加后返回一个匿名临时变量然后与 t1 再进行相加。
重载限制
- 重载后的操作符必须至少有一个操作数是用户定义的类型, 这将防止用户为标准类型重载操作符。
- 使用操作符时不能违反操作符原来的句法规则。
- 不能定义新的操作符。
- 不能重载下列操作符:
sizeof
.
.*
::
?:
typeid
const_cast
等强制累心转换操作符
- 下面的操作符只能通过成员函数进行重载
=
()
[]
->
二 友元函数
三 类的自动转换和强制类型转换
explicit 关键字
class A
{
private:
int _a;
public:
// 单参构造函数
A(int a)
{
_a = a;
}
};
void test1()
{
// 隐式转换
A a = 10;
}
使用 explicit
关键字将禁用这种转换。
class A
{
private:
int _a;
public:
// 单参构造函数
explicit A(int a)
{
_a = a;
}
};
void test1()
{
// A a = 10; // Error!
A a = (A)10; // ok
}
虽然禁止了隐式类型转换,但是依然 可以显示类型转换 。
class A
{
private:
int _a;
public:
// 单参构造函数
A(int a)
{
_a = a;
}
};
void test1()
{
// 3.1 首先转换为 int 类型,然后调用 A(3)
A b = 3.1;
}
但是,如果这种转换存在二义性,比如:
A(int a)
{
_a = a;
}
// 再定义一个参数为 long 的构造函数
A(long a)
{
_a = a;
}
double 可被转换为 int 或 long ,因此调用存在二义性。
转换函数
内置类型可以转换为类类型,那么,可以做相反的转换吗?
class A
{
private:
int _a;
public:
// 单参构造函数
A(int a)
{
_a = a;
}
operator int() const
{
return _a;
}
operator double() const
{
return double(_a);
}
};
void test1()
{
// 3.1 首先转换为 int 类型,然后调用 A(3)
A b = 3.1;
int i = b; // ok
double d = b; // ok
// 存在二义性:double 和 int 都可以转换为 long
// long l = b;
cout << int(b) << endl; // ok
cout << double(b) << endl; // ok
// 存在二义性:b 可以转换为 double 或 int
// cout << b << endl;
}
提供执行自动,隐式转换的函数所存在的问题是:再用户不希望进行转换时,转换函数也可能进行转换。但是转换函数又不支持可以禁止掉隐式转换的explicit
。所以,我们可以用一个功能相同的非转换函数替换该转换函数即可:
int A_to_int()
{
return _a;
}
double A_to_double()
{
return (double)_a;
}
我们可以这样使用它们:
void test2()
{
A a(3);
int a = a.A_to_int();
double d = a.A_to_double();
cout << a << endl; // Error!
}
详举 类类型 + 内置类型
以 类类型B
+ double
类型为例
情况 1
double 类型借助单参构造函数转换为 B 类型完成相加
适用场景 :程序更加简短(需要定义的函数少),但是需要多调用构造函数,增加了时间和内存的开销
构造函数被调用三次
class B
{
private:
double _b;
public:
B(double b)
{
cout << "B(double b) " << endl;
_b = b;
}
B operator+(const B& b)
{
// 第三次构造
B ret(_b + b._b);
return ret;
}
double Show()
{
return _b;
}
};
void test3()
{
B b(3.0); // 第一次调用构造
double d = 3.6;
// d 调用 d(double) 隐式转换为 B 类型,第二次调用构造
cout << (b + d).Show() << endl;
}
需要注意的是,如果将 b + d
写成 d + b
则会出现报错。因为 d 并不是类类型。C++ 不会试图将 d 转换为 B 类型。
这时候可以添加一个友元函数。
情况 2
B 借助转换函数转换为 double 类型
适用场景: 程序较长,但是更加高效。
构造函数被调用一次
class B
{
private:
double _b;
public:
B(double b)
{
cout << "B(double b) " << endl;
_b = b;
}
operator double() const
{
return _b;
}
};
void test3()
{
B b(3);
double d = 3.6;
// b 调用转换函数隐式转换为 double
cout << (b + d) << endl;
}
需要注意的是,如果转换函数和单参构造同时存在,且有类重载了 operator+
。那么就会有上面两种转换方式,产生二义性,出现错误:
class B
{
private:
double _b;
public:
B(double b)
{
cout << "B(double b) " << endl;
_b = b;
}
operator double() const
{
return _b;
}
B operator+(const B& b)
{
// 第三次构造
B ret(_b + b._b);
return ret;
}
};
void test3()
{
B b(3);
double d = 3.6;
cout << (b + d) << endl; // Error
}
情况 3
重载 operator+
函数,使得其可以与 double 类型相加
适用场景 :经常需要 double 与 B 类型相加。(可以再定义一个友元函数)
构造函数被调用 1 次
class B
{
private:
double _b;
public:
B(double b)
{
cout << "B(double b) " << endl;
_b = b;
}
B operator+(double d)
{
return B(_b + d);
}
};
void test3()
{
B b(3);
double d = 3.6;
cout << (b + d) << endl;
}
在 main 函数之前调用 Bootstrap 函数
如果我们需要在调用 main 函数之前处理一些信息。我们可以做一个类,将要提前做的操作放入其构造函数。然后定义一个该类类型的全局变量(全局变量在 main 函数调用之前被创建)。
class A
{
public:
A()
{
func1();
func2();
...
}
};
A a;
int main(void)
{
...
}
强制转换的格式
(int)a; // C 风格
int(a) // C++ 风格