欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

《C++ Primer Plus 第六版》学习笔记:第十一章 使用类

程序员文章站 2023-12-22 23:32:04
...

因为写博客是为了记录一下自己的学习成果方便日后查看,可能有的地方只有看过原版书才能知道我在说什么,这一系列博客也许不适合用来预习。

“不要害怕犯错误,因为在解决问题的过程中学到的知识,比生搬硬套而不犯错误时多得多(然而不要认为所有的错误都会让人增长见识)。”

“学习C++的难点之一时需要记住大量的东西,但在拥有丰富的实践经验之前,根本不可能全部记住这些东西。”

“不要在第一次学习时就试图使用所有的特性。”

本章将进一步讨论类的特征,重点是类设计技术,而不是通用原理。

11.1 运算符重载

11.1.1 运算符重载示例

//运算符重载函数定义:
Time Time::operator+(const Time & t) const
{
	Time sum;
	sum.minutes = minutes + t.minutes;
	sum.hours = hours + t.hours + sum.minutes / 60;
	sum.minutes %= 60;
	return sum;
}
//运算符重载调用:
total = coding.operator+(fixing);	//function notation
total = coding + fixing;	//operator notation
//可以将两个以上的对象相加:
t4 = t1 + t2 + t3;
t4 = t1.operator+(t2.operator+(t3));

对函数参数和返回值是否使用引用的讨论:

//请看如下代码:
Time Time::Sum(const Time & t) const
{
	Time sum;
	...
	return sum;
}

一般来说,重载运算符参数和返回值类型应该相同,但时函数参数应是引用,但返回类型不是引用

  • 将参数声明为引用的目的是提高效率(速度更快,使用的内存更少);
  • 然而,如果返回类型为引用,则引用的将是sum对象。但由于sum对象是局部变量,在函数结束时将被删除,因此引用将指向一个不存在的对象。使用返回类型Time意味着程序将在删除sum之前构造它的拷贝,调用函数将得到它的拷贝。

警告:不要返回指向局部变量或临时对象的引用。函数执行完毕后,局部变量和临时对象将消失,引用将指向不存在的数据。

11.1.2 重载限制

  • 重载后的运算符必须至少有一个操作数是用户定义的类型,这将防止用户为标准类型重载运算符;
  • 使用运算符时不能违反运算符原来的句法规则;
  • 不能创建新运算符;
  • 可重载和不可重载运算符(略)。

11.2 友元

友元有三种:友元函数、友元类、 友元成员函数,下面主要介绍友元函数。通过让函数成为类的友元,可以赋予该函数与类的成员函数相同的访问权限。
简单地说为什么使用友元:

//请看如下代码,我们运用了重载技术:
A = B * 2.75;
//将被转化为下面的成员函数调用
A = B.operator*(2.75);
//但下面的语句又如何呢?
A = 2.75 * B;	//不匹配任何一个成员函数,编译不通过

可以利用非成员函数来解决这个问题,但是非成员函数不能访问私有成员变量。为了在为类重载运算符的过程中,使得反转操作数后依然能进行正常操作,我们引入友元函数。

11.2.1 创建友元

//第一步:将其原型放在类声明中,并在原型声明前加上关键字friend:
friend Time operator*(double m, const Time & t);

//第二步:编写函数定义。
Time operator*(double m, const Time & t)
{
	Time result;
	...
	return result;
}
//实际上,按下面的方式对定义进行修改,可以将这个友元函数编写为非友元函数:
Time operator*(double m, const Time & t)
{
	return t * m;
}

注意:

  1. 虽然operator*()函数是在类声明中声明的,但它不是成员函数,因此不能使用成员运算符来调用,并且定义时不能用Time::限定符;
  2. 虽然operator*()函数不是成员函数,但它与成员函数的访问权限相同;
  3. 不能再在函数定义时加限定符friend。
//有了上述语句
A = 2.75 * B;
//将转化为如下语句:
A = operator*(2.75,B); 

11.2.2 常用的友元:重载<<运算符

实例:

//定义如下:
ostream & operate<<(ostream & os, const Time & time)
{
	os << ...;	//display object contents
	return os;
}
//返回值为ostream&,意味着该函数返回ostream对象的引用,可以进行拼接输出。

注意:operate<<()之间访问Time对象的私有成员,所以它必须是Time类的友元。但由于它并不直接访问ostream对象的私有成员,所以并不一定必须是ostream类的友元。这很好,意味着不必修改ostream的定义。

11.3 重载运算符:作为成员函数还是非成员函数

//merber verson:
Time operator+(const Time & t) const;	

//nonmember verson:
friend Time operator+(cosnt Time & t1, const & t2);	

注意:非成员版本的重载运算符所需的形参数目与运算符使用的操作数相同;而成员版本所需的参数数目少一个,因为其中的一个操作数是被隐式地传递的调用对象。

此后,编译器将

T1 = T2 + T3;

转换为下面两个的任何一个:

T1 = T2.operator+(T3);	//member function
T1 = operator+(T2, T3);	//nonmember function

定义运算符时,必须选择其中的一种格式,而不能同时选择这两种格式。因为这两种格式都与同一个表达式匹配,同时定义这两种格式将被视为二义性错误,导致编译错误。

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

总之,C++为类提供了下面的类型转换:

  • 只有一个参数的类构造函数用于将类型于该参数相同的值转换为类类型。例如将int值赋给Stonewt对象时,接受int参数的Stonewt类构造函数将自动被调用。然而,在构造函数中使用explicit可防止隐式转换,而只允许显示转换。
  • 被称为转换函数的特殊类成员运算符,用于将类对象转换为其他类型。转换函数时类成员,没有返回类型、没有参数、名为operaor type Name(),其中,typeName是对象将被转换的类型。将类对象赋给typeName变量或将其强制转换为typeName类型时,该转换函数将自动被调用。

上一篇:

下一篇: