《C++ Primer Plus》第十一章 使用类 Shepard-Wang

寄语:

不要怕犯错误,因为在解决问题中学到的知识,比生搬硬套而不犯错误时要多得多。

一 操作符重载

操作符重载(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 再进行相加。

重载限制

  1. 重载后的操作符必须至少有一个操作数是用户定义的类型, 这将防止用户为标准类型重载操作符。
  2. 使用操作符时不能违反操作符原来的句法规则。
  3. 不能定义新的操作符。
  4. 不能重载下列操作符:
    • sizeof
    • .
    • .*
    • ::
    • ?:
    • typeid
    • const_cast 等强制累心转换操作符
  5. 下面的操作符只能通过成员函数进行重载
    • =
    • ()
    • []
    • ->

二 友元函数

练习代码

三 类的自动转换和强制类型转换

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++ 风格