《C++ Primer Plus》《11、使用类》

0、前言

本章介绍运算符重载,它允许将C++运算符用于类对象。然后也介绍了友元这个机制,使得非成员函数可以访问私有数据,也介绍了C++对类执行自动类型转换。

1、运算符重载

运算符重载是一种形式的C++多态;运算符重载将重载的概念扩展到运算符上,允许赋予C++运算符多种含义。例如*运算符用于地址,得到存储这个地址中的值,但是将他用于两个数字时,得到的是他们的乘积。
C++允许将运算符重载扩展到用户定义的类型。例如,允许使用+将2个对象相加,编译器将根据操作数的数目和类型决定使用哪种加法定义。这样的方法使得代码看起来更加的自然,例如将2个数组相加,一般是采用这样的for循环来实现:

for(int i = 0;i < 20;i++)
	evening[i] = sam[i] + janet[i];   //add element by element

但是如果,C++中定义了一个数组的类,并重载+运算符

evening = sam + janet;

这种简单的加法表示隐藏了内部机理,并强调了实质。

2、计算时间:一个运算符重载示例

时间由时,分构成;例如上午花了2小时35分钟,下午花费了2小时40分钟,问总共花了多少时间。这个示例与加法的概念很吻合,但是相加的单位与内置类型不匹配。现在才用一个方法来处理加法的Time类。

//mytime0.h --Time class before operating overloading
#ifndef MYTIME0_H
#define MYTIME0_H_
class Time{
private:
	int hours;
	int minutes;
public:
	Time();
	Time(int h,int m = 0);
	void AddMin(int m);
	void AddHr(int h);
	void Reset(int h = 0,int m = 0);
	Time Sum(const Time&t) const;
	void Show() const ;
};
#endif 

Time类提供了用于调整,重新设置时间,显示时间,将两个时间相加的方法,下面是这些方法的实现。

//mytime0.cpp --implementing Time methods
#include<iostream>
#include"mytime0.h"
using namespace std;
Time::Time(){
	hours = minutes = 0;
}
Time::Time(int h,int m){
	hours = h;
	minutes = m;
}
Time::AddMin(int m){
	minutes += m;
	hours += minutes/60;
	minutes %= 60;
}
Time::AddHr(int h){
	hours += h;
}
void Time::Reset(int h,int m){
	hours = h;
	minutes = m;
}
Time Time::Sum(const Time &t) const{
	Time sum;
	sum.miutes = minutes + t.minutes;
	sum.hours = hours + t.hours + sum.minutes/60;
	sum.minutes %= 60;
	return sum;
}
void Time::Show() const{
	std::cout << hours << " hours," <<minutes << " minutes" << endl;
}

注意看sum的函数的代码,参数类型是引用,但是返回类型不是引用。将参数声明为引用是为了提高效率,传递引用,速度更快,使用的内存也更少。
但是返回值不能是引用,因为函数会创建一个新的Time对象来表示另外两个Time对象的和。
最后,对Time类中计算时间的总和。

//
//
#include<iostream>
#include "mytime0.h"
int main(){
	using namespace std;
	Time planning;
	Time coding(2,40);
	Time fixing(5,55);
	Time total;

	cout << "planning time = ";
	planning.Show();
	cout << endl;

	cout << "coding time = ";
	coding.Show();
	cout << endl;
	
	cout << "fixing time = ";
	fixing.Show();
	cout << endl;
	
	total = coding.Sum(fixing);
	cout << "coding.Sum(fixing) == ";
	total.Show();
	cout << endl;

	return 0;
}

输出如下:

planning time = 0 hours,0 minutes

coding time = 2 hours,40 minutes

fixing time = 5 hours,55 minutes

coding.Sum(fixing) == 8 hours,35 minutes

2.1 添加加法运算符

上述方法并不是重载运算符,只是创建了add的相关方法。将Time类转换为重载的加法运算,只要将Sum()的名称改为operator+()即可。

//mytime1.h --Time class before operating overloading
#ifndef MYTIME1_H_
#define MYTIME1_H_
class Time{
private:
	int hours;
	int minutes;
public:
	Time();
	Time(int h,int m = 0);
	void AddMin(int m);
	void AddHr(int h);
	void Reset(int h = 0,int m = 0);
	Time operator+(const Time & t) const;
	void Show() const ;
};
#endif 
//mytime1.cpp --implementing Time methods
//mytime1.cpp --implementing Time methods
#include<iostream>
#include"mytime1.h"
using namespace std;
Time::Time(){
	hours = minutes = 0;
}
Time::Time(int h,int m){
	hours = h;
	minutes = m;
}
void Time::AddMin(int m){
	minutes += m;
	hours += minutes/60;
	minutes %= 60;
}
void Time::AddHr(int h){
	hours += h;
}
void Time::Reset(int h,int m){
	hours = h;
	minutes = m;
}
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;
}
void Time::Show() const{
	std::cout << hours << " hours," <<minutes << " minutes" << endl;
}
//usetime1.cpp  --using the second draft of the Time class
//compile usetime1.cpp and mytime1.cpp togrther
#include<iostream>
#include"mytime1.h"
int main(){
	using namespace std;
	Time planning;
	Time coding(2,40);
	Time fixing(5,55);
	Time total;

	cout << "planning time = ";
	planning.Show();
	cout << endl;

	cout << "coding time = ";
	coding.Show();
	cout << endl;

	cout << "fixing time = ";
	fixing.Show();
	cout << endl;

	total = coding + fixing;
	cout << "coding + fixing = ";
	total.Show();
	cout <<endl;

	Time morefixing(3,28);
	morefixing.Show();
	cout << endl;
	total = morefixing + total;
	cout << "morefixing + total ";
	total.Show();
	cout <<endl;
}

operator+()的函数名称可以使用函数或运算符表示来调用他。
输出如下:

planning time = 0 hours,0 minutes

coding time = 2 hours,40 minutes

fixing time = 5 hours,55 minutes

coding + fixing = 8 hours,35 minutes

3 hours,28 minutes

morefixing + total 12 hours,3 minutes

2.2 重载限制

2.3 其他重载运算符

还有一些其他操作对Time类来说是有意义的,例如将两个时间乘于一个因子,或者两个时间相减;即创建operator-()和operator*()
以下是全新的文件:

//mytime2.h --Time class after operator overloading
#ifndef MYTIME2_H
#define MYTIME2_H
class Time{
private:
	int hours;
	int minutes;
public:
	Time();
	Time(int h,int m = 0);
	void AddMin(int m);
	void AddHr(int h);
	void Reset(int h = 0,int m = 0);
	Time operator+(const Time & t) const;
	Time operator-(const Time & t) const;
	Time operator*(const Time & t) const;
	void Show() const ;
};
#endif

//mytime2.cpp --implementing Time methods
#include <iostream>
#include "mytime2.h"
using namespace std;
Time::Time(){
	hours = minutes = 0;
}
Time::Time(int vh,int m){
	hours = h;
	minutes = m;
}
void Time::AddMin(int m){
	minutes += m;
	hours += minutes/60;
	minutes %= 60;
}
void Time::AddHr(int h){
	hours += h;
}
void Time::Reset(int h,int m){
	hours = h;
	minutes = m;
}

Time Time::opertaor+(const Time& t) const
{
	Time sum;
	sum.minutes = minutes + t.minutes;
	sum.hours = hours + t.hours + sum.minutes/60;
	sum.minutes % = 60;
	return sum;
}

Time Time::opertaor-(const Time& t) const
{
	Time diff;
	int tot1,tot2;
	tot1 = t.minutes + 60 *t.hours;
	tot2 = minutes + 60*hours;
	diff.minutes = (tot2 - tot1)%60;
	diff.hours = (tot2 - tot1)/60;
	return diff;
}

Time Time::opertaor*(double mult) const
{
	Time result;
	long totalminutes = hours*mult*60+minutes*mult;
	result.hours = totalminutes/60;
	result.minutes = totalminutes%60;
	return result;
}

void Time::Show() const{
	std::cout << hours << " hours," <<minutes << " minutes" << endl;
}


#include<iostream>
#include "mytime2.h"
int main(){
	using namespace std;
	Time wedding(4,35);
	Time waxing(2,47);
	Time total;
	Time diff;
	Time adjusted;

	cout << "wedding time = ";
	wedding.Show();
	cout << endl;

	cout << "waxing time = ";
	waxing.Show();
	cout << endl;
	
	cout << "total work time = ";
	total = wedding + waxing;
	total.Show();
	cout << endl;
		
	cout << "diff work time = ";
	diff = wedding - waxing;
	diff.Show();
	cout << endl;
		
	cout << "adjusted work time = ";
	adjusted = total * 1.5;
	adjusted.Show();
	cout << endl;
	return 0;
}

实际输出:

wedding time = 4 hours,35 minutes

waxing time = 2 hours,47 minutes

total work time = 7 hours,22 minutes

diff work time = 1 hours,48 minutes

adjusted work time = 11 hours,3 minutes

3、友元

C++控制对类对象私有部分的访问;正常情况下,公有类方法提供唯一的访问途径,但有时候这种限制太严格,不适应特定的问题。C++为了解决这种方法提供了另外一种形式的访问权限:友元。
友元有三种:
(1)友元函数
(2)友元类
(3)友元成员函数
友元函数是C++编程语言中的一种特殊函数,具有访问类中私有成员的权限,尽管它不是类的成员函数。以下是对友元函数的详细介绍:

一、定义与特点

  • 定义:友元函数是在一个类中声明的非成员函数,但在类的内部通过friend关键字声明该函数为友元,这意味着该函数可以访问该类的私有成员(包括私有变量和私有函数)。
  • 特点
    • 友元函数不是类的成员函数,因此它不能通过对象来调用(即不能使用.->操作符),而是像普通函数一样被调用。
    • 友元函数可以访问类的所有成员,包括私有成员和保护成员。
    • 友元函数可以在类定义的任何地方声明,不受类访问限定符(public、protected、private)的限制。
    • 一个函数可以是多个类的友元函数。

二、使用场景

友元函数的主要作用是允许外部函数或类访问另一个类的私有成员,从而实现对类的细粒度控制。具体使用场景包括:

  • 重载操作符:当需要重载类的操作符(如<<>>+-等)时,友元函数可以访问私有成员,实现合适的操作。
  • 提高性能:在某些情况下,使用友元函数可以直接访问类的私有成员,避免了通过公共接口(如getter和setter)访问成员的开销,从而提高程序的执行效率。
  • 访问多个类的私有数据:用友元函数可以访问两个或多个类的私有数据,使得人们更容易理解程序的逻辑关系。

三、优缺点

  • 优点
    • 提高了程序的灵活性和扩展性。
    • 在某些情况下可以提高程序的执行效率。
    • 使得类的设计更加符合实际需求。
  • 缺点
    • 破坏了类的封装性,降低了代码的安全性和可维护性。
    • 当程序变得复杂时,友元函数的使用可能会导致代码难以理解和维护。

五、注意事项

  • 友元函数不是类的成员函数,因此不能用const修饰。
  • 友元关系是指定的,不是获取的。如果让类B成为类A的友元类,类A必须显式声明类B为自己的友元类。
  • 友元关系不具有继承性,即声明为友元类的类的子类并不自动成为友元类。
  • 友元关系不满足对称性和传递性。即如果类A是类B的友元类,并不意味着类B也是类A的友元类;同样,如果类A是类B的友元类,类B是类C的友元类,并不意味着类A是类C的友元类。

3.1 创建友元

创建一个友元函数的过程相对简单,但需要遵循一定的步骤。以下是一个详细的指南,介绍如何在C++中创建一个友元函数:

1. 确定友元函数的需求

首先,你需要确定为什么需要一个友元函数。通常,这是因为你想要一个非成员函数能够访问类的私有或保护成员。

2. 声明友元函数

在类的定义中,使用friend关键字声明你想要作为友元的函数。这个声明可以出现在类的任何部分,包括私有或保护部分,因为它不会改变函数的访问权限,只是授予它访问类成员的权限。

class MyClass {
private:
    int value;
public:
    MyClass(int v) : value(v) {}
    friend void display(const MyClass& obj); // 声明友元函数
};

3. 定义友元函数

在类的外部定义友元函数。这个函数现在可以访问类的所有成员,包括私有和保护成员。

void display(const MyClass& obj) {
    std::cout << "Value: " << obj.value << std::endl;
}

4. 使用友元函数

现在你可以像使用任何其他函数一样使用友元函数了。它可以直接访问类的私有成员。

int main() {
    MyClass obj(10);
    display(obj); // 输出: Value: 10
    return 0;
}

注意事项

  • 友元函数不是类的成员函数,因此它不能访问类的this指针。
  • 友元关系不是相互的。如果你声明了函数f是类A的友元,这并不意味着Af的友元或者f可以访问A的所有实例的私有成员。
  • 友元关系不具有传递性。如果BA的友元,CB的友元,那么C不一定是A的友元。
  • 友元关系也不具有继承性。如果BA的友元,那么B的子类不一定是A的友元。

通过遵循这些步骤和注意事项,你可以在C++程序中有效地创建和使用友元函数。

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

代码示例:

//mytime3.h --Time class with friends
#ifndef MYTIME3_H
#define MYTIME3_H
class Time{
private:
	int hours;
	int minutes;
public:
	Time();
	Time(int h,int m = 0);
	void AddMin(int m);
	void AddHr(int h);
	void Reset(int h = 0,int m = 0);
	Time operator+(const Time & t) const;
	Time operator-(const Time & t) const;
	Time operator*(double n) const;
    friend Time operator*(double m,const Time &t){
        return t*m ;}
    friend std::ostream & operator<<(std::ostream &os,const Time& t);
};
#endif

//mytime3.cpp --implementing Time methods
#include <iostream>
#include "mytime3.h"
using namespace std;
Time::Time(){
	hours = minutes = 0;
}
Time::Time(int h,int m){
	hours = h;
	minutes = m;
}
void Time::AddMin(int m){
	minutes += m;
	hours += minutes/60;
	minutes %= 60;
}
void Time::AddHr(int h){
	hours += h;
}
void Time::Reset(int h,int m){
	hours = h;
	minutes = m;
}
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;
}

Time Time::operator-(const Time& t) const
{
	Time diff;
	int tot1,tot2;
	tot1 = t.minutes + 60 *t.hours;
	tot2 = minutes + 60*hours;
	diff.minutes = (tot2 - tot1)%60;
	diff.hours = (tot2 - tot1)/60;
	return diff;
}

Time Time::operator*(double mult) const
{
	Time result;
	long totalminutes = hours*mult*60+minutes*mult;
	result.hours = totalminutes/60;
	result.minutes = totalminutes%60;
	return result;
}

std::ostream & operator<<(std::ostream &os,const Time& t){
    os << t.hours << "hours, "<<t.minutes << " minutes";
    return os;
}

#include<iostream>
#include "mytime3.h"
int main(){
	using namespace std;
	Time aida(3,35);
	Time tosca(2,47);
	Time temp;

	cout << "aida and tosca ";
	cout << aida << " ;" << tosca <<endl;
    temp = aida + tosca;
    cout << "aida + tosca: " << temp <<endl;
	return 0;
}

如果对<<进行重载,书中给出了2个版本:
版本1:

void operator <<(ostream & os,const Time t){
	os << t.hours << " hours," <<t.minutes << " minutes ."
}

版本2:

std::ostream & operator<<(std::ostream &os,const Time& t){
    os << t.hours << "hours, "<<t.minutes << " minutes";
    return os;
}

函数版本1有一个问题,他不能链式地进行调用,<<运算符要求左边是一个ostream对象,因此ostream类将operator<<()函数返回一个指向ostream对象的引用;具体的说是返回一个指向调用对象的引用。因此(cout << x)本身就是一个ostream对象cout,从而可以位于<<运算符的左侧。

对比两种写法:

  1. 返回类型

    • 标准做法返回std::ostream&,支持链式调用。
    • 非标准做法返回void,不支持链式调用。
  2. 链式调用

    • 使用标准做法,你可以这样写:std::cout << t1 << t2 << std::endl;
    • 使用返回void的做法,你无法链式调用,每次输出后都需要重新写std::cout,如:std::cout << t1; std::cout << t2; std::cout << std::endl;
  3. 代码风格和可读性

    • 标准做法更加符合C++的常规,代码更加清晰和易于理解。
    • 返回void的做法可能会导致代码冗长和混乱。
  4. 扩展性和灵活性

    • 标准做法支持所有基于std::ostream的输出,包括文件输出、字符串流等。
    • 返回void的做法可能在这些场景下不够灵活。

因此,强烈建议按照标准做法来重载<<运算符,即返回一个对std::ostream的引用。这样做不仅更加符合C++的常规,而且代码更加清晰、易于理解和维护。

4、重载运算符

对于很多运算符来说,可以使用成员函数和非成员函数(友元函数)实现运算符重载;

//成员函数
Time operator+(const Time & t) const;
//非成员函数
friend Time operator+(const Time & t1,const Time &t2);

加法运算符需要两个操作数,对于成员函数来说。一个操作数通过this指针隐式传递,另一个操作数作为函数参数显示传递;而对于友元版本来说,两个操作数都作为参数来传递。

5、再谈重载:一个矢量类

矢量之前大家都接触过,它是一个既有大小,又有方向的变量;书中介绍了一种使用了运算符重载和友元的类设计——一个表示矢量的类。
代码示例:

//vect.h --vector class with <<,mode state
#ifndef VECTOR_H_
#define VECTOR_H_
#include <iostream>
namespace VECTOR
{
	class VECTOR
	{
		public:
			enum Mode {RECT,POL};
		private:
			double x;
			double y;
			double mag;
			double ang;
			Mode mode;
			//private methods for setting value
			void set_mag();
			void set_ang();
			void set_x();
			void set_y();
		public:
			Vector();
			Vector(double n1,double n2,Mode form = RECT};
			void reset(double n1,double n2,Mode form = RECT};
			~Vector();
			double xval() const {return x;}
			double yval() const {return y;}
			double magval() const {return mag;}
			double angval() const {return ang;}
			void polar_mode();
			void rect_mode();
			//operator overloading
			Vector operator+(const Vector & b) const;
			Vector operator-(const Vector & b) const;
			Vector operator-() const;
			Vector operator*(double n) const;
			//friends
			friend Vector operator*(double n,const Vector& a);
			friend std::ostream & operator<<(std::ostream & os,const Vector & v);
	};
} //end of namespace VECTOR
#endif
//vect.cpp == methods for the Vector class
#include<cmath>	
#include"vector.h" //includes<iostream>
using std::sqrt;
using std::sin;
using std::cos;
using std::atan;
using std::atan2;
using std::cout;
using std::endl;

namespace VECTOR
{
	//comput degrees in one radian 
	const double Rad_to_deg = 45.0 / atan(1.0);
	//should be about 57.2957795130823

	//private methods
	//calculates magnitude from x and y 
	void Vector::set_mag()
	{
		mag = sqrt(x * x+y * y);
	}
	void Vector::set_ang()
	{
		if (x == 0.0 && y == 0.0)
		{
			ang = 0.0;
		}
		else
		{
			ang = atan2(y, x);
		}
	}
	//set x from polar coorinate 
	void Vector::set_x()
	{
		x = mag * cos(ang);
	}
	//set y from polar coorinate
	void Vector::set_y()
	{
		y = mag * sin(ang);
	}
	//public methods 
	//default constructor
	Vector::Vector()
	{
		y = x = mag = ang = 0.0;
		mode = RECT;
	}

	//construct vector from rectangular coordinates if form is r 
	//(the default) or else from polay coordinates if form is p
	Vector::Vector(double n1, double n2, Mode form)
	{
		mode = form;
		if (form == RECT)
		{
			x = n1;
			y = n2;
			set_mag();
			set_ang();
		}
		else if (form == POL)
		{
			mag = n1;
			ang = n2 / Rad_to_deg;
			set_mag();
			set_ang();
		}
		else
		{
			cout << "Incorrect 3rd argument to Vector() -- ";
			cout << "vector set to 0\n";
			x = y = mag = ang = 0.0;
			mode = RECT;
		}
	}
	//reset vector from rectangular coorinates if form is 
	//RECT(the default) or else from polar coorinates if 
	//form is POL
	void Vector::reset(double n1, double n2, Mode form)
	{
		mode = form;
		if (form == RECT)
		{
			x = n1;
			y = n2;
			set_mag();
			set_ang();
		}
		else if (form == POL)
		{
			mag = n1;
			ang = n2 / Rad_to_deg;
			set_x();
			set_y();
		}
		else
		{
			cout << "Incorrect 3rd argument to Vector() -- ";
			cout << "vector set to 0\n";
			x = y = mag = ang = 0.0;
			mode = RECT;
		}
	}
	Vector::~Vector()//destructor
	{

	}

	void Vector::polar_mode()//set to polay mode 
	{
		mode = POL;
	}

	void Vector::rect_mode() // set to rectangular mode
	{
		mode = RECT;
	}

	//operator overloading 
	//add two Vectors 
	Vector Vector::operator+(const Vector& b)const
	{
		return Vector(x + b.x, y + b.y);
	}
	//sub vector b from a 
	Vector Vector::operator-(const Vector& b)const
	{
		return Vector(x - b.x, y - b.y);
	}

	//reverse sign of Vector
	Vector Vector::operator-()const
	{
		return Vector(-x, -y);
	}

	//multyply vector by n 
	Vector Vector::operator*(double n)const
	{
		return Vector(n*x,n*y);
	}

	//friend methods
	//multiply n by Vector a 
	Vector operator*(double n, const Vector& a)
	{
		return a * n;
	}
	
	//display rectangular coorinates if mode is RECT
	//else display polar coordinates if mode is POL
	std::ostream& operator<<(std::ostream& os, const Vector& v)
	{
		if (v.mode == Vector::RECT)
		{
			os << "(x,y) = (" << v.x << ", " << v.y << ")";
		}
		else if (v.mode == Vector::POL)
		{
			os << "(m,a) = (" << v.mag << ", " << v.ang * Rad_to_deg << ")";
		}
		else
			os << "Vector object mode is invalid";
		return os;
	}
}//end namespace VECTOR
#if 1
#include <iostream>
#include<cstdlib>	//rand,srand() prototypes
#include<ctime>//time()prototype
#include"vector.h"
int main()
{
	using namespace std;
	using VECTOR::Vector;
	srand(time(0));//seed random-number generator 
	double direction;
	Vector step;
	Vector result(0.0, 0.0);
	unsigned long steps = 0;
	double target;
	double dstep;
	cout << "Enter target distance(q to quit):";
	while (cin >> target)
	{
		cout << "Enter step length: ";
		if (!(cin >> dstep))
		{
			break;
		}
		while (result.magVal() < target)
		{
			direction = rand() % 360;
			step.reset(dstep, direction, Vector::POL);
			result = result + step;
			steps++;
			//cout << "steps++ " << steps << ",magVal() = " << result.magVal() << endl;
		}
		cout << "After " << steps << " steps,the subject "
			"has the following location:\n";
		cout << result << endl;
		result.polar_mode();
		cout << " or\n" << result << endl;
		cout << "Average outward distance per step = "
			<< result.magVal() / steps << endl;
		steps = 0;
		result.reset(0.0, 0.0);
		cout << "Enter target distance(q to quit): ";
	}
	cout << "Bye!\n";
	cin.clear();
	while (cin.get() != '\n')
	{
		continue;
	}
	return 0;
}
#endif

6、类的自动转换和强制类型转换

简单介绍一下2种转换:
在C++中,类型转换是一个重要的概念,它允许你将一种类型的值转换为另一种类型。类型转换可以大致分为两类:强制类型转换(Explicit Conversion)和自动类型转换(Implicit Conversion 或 Automatic Conversion)。

自动类型转换(Implicit Conversion 或 Automatic Conversion)

自动类型转换,也称为隐式类型转换,是由编译器自动完成的,不需要程序员显式地指定。这种转换通常发生在以下几种情况:

  1. 基本数据类型之间的转换:例如,从intfloat的转换是自动的,因为float类型能够表示int类型的所有值,同时提供更高的精度。
  2. 派生类到基类的转换:在面向对象编程中,派生类的对象可以隐式地转换为基类对象(如果基类有虚函数,则这种转换是安全的,否则可能导致切片问题)。
  3. 算术运算符中的类型转换:当操作数的类型不同时,编译器会尝试将它们转换为一种公共类型,然后执行运算。

自动类型转换通常是安全的,但也可能导致数据丢失(如从float转换到int)或精度降低(如从double转换到float)。

强制类型转换(Explicit Conversion)

强制类型转换,也称为显式类型转换,需要程序员明确地指示编译器将一种类型的值转换为另一种类型。在C++中,有几种不同的方式可以实现强制类型转换:

  1. C风格的强制类型转换(type)value,这是最原始的强制类型转换方式,但不够安全,因为它不会进行类型检查。

  2. C++风格的静态转换(static_cast)static_cast<type>(value),它用于基本的类型转换,例如基本数据类型的转换、派生类到基类的转换(安全的)、以及基类指针(或引用)到派生类指针(或引用)的向上转换(不安全,需要确保转换是合法的)。

  3. C++风格的动态转换(dynamic_cast)dynamic_cast<type*>(expr),主要用于类的层次结构中的向下转换(基类指针或引用到派生类指针或引用的转换)。它在运行时检查转换的安全性,如果转换不合法,则转换失败(对于指针类型,返回nullptr;对于引用类型,抛出std::bad_cast异常)。

  4. C++风格的常量转换(const_cast)const_cast<type>(expr),用于修改类型的const或volatile属性。例如,它可以将一个指向常量对象的指针转换为指向非常量对象的指针,以便能够修改该对象。

  5. C++风格的重新解释转换(reinterpret_cast)reinterpret_cast<type>(expr),它提供了一种非常低级的转换方式,基本上可以转换任何指针(或引用)类型到任何其他指针(或引用)类型,还可以用于指针和足够大的整数类型之间的转换。这种转换基本上只是告诉编译器:“我知道我在做什么,请按我说的做。”但它几乎不保证转换后的值是有意义的或安全的。

总的来说,自动类型转换是隐式的、由编译器自动完成的,而强制类型转换需要程序员显式地指定,并且提供了更多的控制选项和灵活性。在使用强制类型转换时,需要特别小心,以确保类型转换的安全性和正确性。

举书中的编程示例:

//stonewt.h. --definition for Stonewt class
#ifndef  STONEWT_H_
#define  STONEWT_H_
class Stonewt{
private:
	enum{Lbs_per_stn = 14};
	int stone;
	double pds_left;
	double pounds;
public:
	Stonewt(double lbs);
	Stonewt(int stn,double lbs);
	Stonewt();
	~Stonewt();
	void show_lbs() const;
	void show_stn() const;
};
#endif
#include "stonewt.h"
#include <iostream>
using namespace std;

Stonewt::Stonewt(double lbs)
{
	stone = int(lbs) / Lbs_per_stn; //整数除法
	pds_left = int(lbs) % Lbs_per_stn + lbs - int(lbs);
	pounds = lbs;
}

Stonewt::Stonewt(int stn, double lbs)
{
	stone = stn;
	pds_left = lbs;
	pounds = stn * Lbs_per_stn + lbs;
}

Stonewt::Stonewt() //默认构造函数
{
	stone = pounds = pds_left = 0;
}

Stonewt::~Stonewt() //析构函数
{
}

//以英石和磅为单位显示
void Stonewt::show_stn() const
{
	cout << stone << " stone, " << pds_left << " pounds\n";
}

//以磅为单位显示
void Stonewt::show_lbs() const
{
	cout << pounds << " pounds\n";
}

#include <iostream>
#include "stonewt.h"
using namespace std;

void display(const Stonewt &st, int n);

int main()
{
	Stonewt incognito = 275; //构造函数初始化
	Stonewt wolfe(285.7); //Stonewt wolfe = 285.7
	Stonewt taft(21, 8);

	cout << "The celebrity weighted ";
	incognito.show_stn();
	cout << "The detective weighted ";
	wolfe.show_stn();
	cout << "The President weighted ";
	taft.show_lbs();
	incognito = 276.8; //利用构造函数转换
	taft = 325; //taft = Stonewt(325)
	cout << "After dinner, the celecbrity weighed ";
	incognito.show_stn();
	cout << "After dinner, the President weighed ";
	taft.show_lbs();
	display(taft, 2);
	cout << "The wrestler weighed even more.\n";
	display(422, 2);
	cout << "No stone left unearned\n";
	return 0;
}

void display(const Stonewt & st, int n)
{
	for (int i = 0; i < n; i++)
	{
		cout << "Wow! ";
		st.show_stn();
	}
}

6.1 转换函数

//stonewt1.h
//conversion functions
operator int() const;
operator double() const;
//stonewt1.cpp
//conversion functions
Stonewt::operator int() const{
	return int(pounds+0.5);
}
Stonewt::operator double() const{
	return pounds;
}

//main.cpp
	//add convert functions
	Stonewt poppins(9,2.8);
	double p_wt = poppins;
	cout << "Convert to double" << p_wt << std::endl;
	cout << "Convert to int" << int(poppins) << std::endl;

输出结果如下:

Convert to double 128.8
Convert to int 129

但是如果我们去掉转换函数,便会提示没有这样的类型转换

main.cpp: In function ‘int main():
main.cpp:32:23: error: cannot convert ‘Stonewt’ to ‘double’ in initialization
   32 |         double p_wt = poppins;
      |                       ^~~~~~~
      |                       |
      |                       Stonewt
main.cpp:34:38: error: invalid cast from type ‘Stonewt’ to type ‘int34 |         cout << "Convert to int " << int(poppins) << std::endl;

但是这种方法也有弊端,你不想转换的时候,他也会进行隐式转换,为避免不必要的转换,可以做显示转换。
方式一:

//stonewt1.h
//conversion functions
explicit operator int() const;
explicit operator double() const;

方法二:

//stonewt1.cpp
//conversion functions
int Stonewt:: Stone_to_Int() const{
	return int(pounds+0.5);
}
double Stonewt::Stone_to_Double() const{
	return pounds;
}
//main.cpp
	//add convert functions
	Stonewt poppins(9,2.8);
	int p_wt = poppins.Stone_to_Int();
	cout << "Convert to int" << p_wt << std::endl;

6.2 转换函数和友元函数

对加法运算符进行重载,有2种方法:1)成员函数,2)友元函数

Stonewt Stonewt::operator+(const Stonewt & st) const{
	double pds = pounds + st.pounds;
	Stonewt sum(pds);
	return sum;
}
Stonewt operator+(const Stonewt& st1,const Stonewt& st2){
	double pds = st1.pounds + st2.pounds;
	Stonewt sum(pds);
	return sum;
}

实现加法的选择:将double量与Stonewt量相加
1)友元函数
让构造函数,将double类型的参数转换为Stonewt类型的参数

operator+(const Stonewt &,const Stonewt &)

2)运算符重载

Stonewt operator+(double x);
friend Stonewt operator+(double x,Stonewt & s);
Stonewt jen(9,120);
Stonewt ken = 176.0;
Stonewt total;
//way 1:
total1 = jen + ken;  //Stonewt + double
//way 2:
total2 = ken + jen;  //double + Stonewt

方式一:依赖于隐式转换,程序更加简单;缺点:每次需要转换时,都调用转换构造函数,这会增加时间和内存开销。
方式二:程序较为复杂,但是运行速度更快。

7、总结

虽然友元函数提供了一定的便利,但是在实际项目里,尽可能避免友元函数的使用。其次隐式转换函数比较方便,但是不要依赖这种隐式函数。自己根据需要,进行灵活使用。

8、参考

8.1 《C++ Primer Plus》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值