文章目录
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
的友元,这并不意味着A
是f
的友元或者f
可以访问A
的所有实例的私有成员。 - 友元关系不具有传递性。如果
B
是A
的友元,C
是B
的友元,那么C
不一定是A
的友元。 - 友元关系也不具有继承性。如果
B
是A
的友元,那么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,从而可以位于<<运算符的左侧。
对比两种写法:
-
返回类型:
- 标准做法返回
std::ostream&
,支持链式调用。 - 非标准做法返回
void
,不支持链式调用。
- 标准做法返回
-
链式调用:
- 使用标准做法,你可以这样写:
std::cout << t1 << t2 << std::endl;
。 - 使用返回
void
的做法,你无法链式调用,每次输出后都需要重新写std::cout
,如:std::cout << t1; std::cout << t2; std::cout << std::endl;
。
- 使用标准做法,你可以这样写:
-
代码风格和可读性:
- 标准做法更加符合C++的常规,代码更加清晰和易于理解。
- 返回
void
的做法可能会导致代码冗长和混乱。
-
扩展性和灵活性:
- 标准做法支持所有基于
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)
自动类型转换,也称为隐式类型转换,是由编译器自动完成的,不需要程序员显式地指定。这种转换通常发生在以下几种情况:
- 基本数据类型之间的转换:例如,从
int
到float
的转换是自动的,因为float
类型能够表示int
类型的所有值,同时提供更高的精度。 - 派生类到基类的转换:在面向对象编程中,派生类的对象可以隐式地转换为基类对象(如果基类有虚函数,则这种转换是安全的,否则可能导致切片问题)。
- 算术运算符中的类型转换:当操作数的类型不同时,编译器会尝试将它们转换为一种公共类型,然后执行运算。
自动类型转换通常是安全的,但也可能导致数据丢失(如从float
转换到int
)或精度降低(如从double
转换到float
)。
强制类型转换(Explicit Conversion)
强制类型转换,也称为显式类型转换,需要程序员明确地指示编译器将一种类型的值转换为另一种类型。在C++中,有几种不同的方式可以实现强制类型转换:
-
C风格的强制类型转换:
(type)value
,这是最原始的强制类型转换方式,但不够安全,因为它不会进行类型检查。 -
C++风格的静态转换(static_cast):
static_cast<type>(value)
,它用于基本的类型转换,例如基本数据类型的转换、派生类到基类的转换(安全的)、以及基类指针(或引用)到派生类指针(或引用)的向上转换(不安全,需要确保转换是合法的)。 -
C++风格的动态转换(dynamic_cast):
dynamic_cast<type*>(expr)
,主要用于类的层次结构中的向下转换(基类指针或引用到派生类指针或引用的转换)。它在运行时检查转换的安全性,如果转换不合法,则转换失败(对于指针类型,返回nullptr
;对于引用类型,抛出std::bad_cast
异常)。 -
C++风格的常量转换(const_cast):
const_cast<type>(expr)
,用于修改类型的const或volatile属性。例如,它可以将一个指向常量对象的指针转换为指向非常量对象的指针,以便能够修改该对象。 -
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 ‘int’
34 | 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》