C++学习笔记(12)——设计类

上篇笔记总结了C++类的基本结构和使用,并尝试着写了一个实现简单计算的类,但这个类仅仅是为了表明语法规则,并不能很好的体现类存在的意义。另外,类的设计也不会像上篇笔记那样死板和简单,它还有很多复杂的规则来是我们的设计变得方便(但不一定变得简单,很可能是变得复杂)。本篇笔记将总结一些类设计中的“新方法”,并同时设计一个“有意义”的类。

一、设计一个矢量类

这里,我首先提出一个软件开发需求。这个需求是要实现一个二维矢量的运算。二维矢量的计算的核心思想是同时考虑大小和方向,它有两种表示方式,一种表示为极坐标方式,即大小和方向;一种是直角坐标方式,即将矢量分解为直角坐标下的x和y分量。因为C++内置类型中并没有矢量这个物理量,而且C++的标准库中也没有相关的库文件。针对这个需求,最好的C++解决方案便是写一个矢量类(Vector)。该类实现矢量运算,如相加、相减、扩大等。本篇笔记将围绕实现这个类总结一些类设计的方法。

二、运算符重载

就像之前在《函数探幽》中介绍函数重载一样,C++还可以对运算符进行重载。其目的就是将运算符的功能扩展到不限于基本类型的情况。比如,本篇笔记将实现的矢量类的相加,就是要将两个自定义类的对象相加。重载运算符使得用户不用关心内部如何实现的,这也是OOP的主要目标之一。

运算符重载的格式如下;将函数名换为operator运算符即可

operator运算符(形参){函数体}

以重载Vector类的加号为例,其声明和实现分别如下;

Vector operator+(const Vector & b) const;

Vector Vector::operator+(const Vector & b) const

{

return Vector(x + b.x, y + b.y);

}

这使得我们在应用时可以直接将两个矢量相加(用+号运算符):

Vector A,B,C;

C = A+B;

这里我们要理解一个问题就是,编译器实际上是将上述“+”翻译为:

C= A. operator+(B);

也就是说它有加数和被加数的区别,A作为被加数,是调用运算符重载函数的对象,而B作为加数是函数的形参。对于加法来说,A和B可以互换位置,但是如果是减法,则不可以,因为这违背了物理意义。另外,这还带来一个问题,就是扩大的算法,是将向量扩大某值,是一个double类型,重载了*运算符,A*n和n*A的表面意义应该一样,但是在具体调用的时候会被翻译为:

n. operator*(B);

这显然行不通。后面将介绍通过另一种方式解决这个问题。

再分析一个问题,如果要实现A+B+C,这种重载可以实现吗?

答案是肯定的,这就是将函数返回值设计为Vector的另一作用,A+B的运算返回值是Vector类型的副本,我们假设为tmp,后面执行tmp+C,也即:

tmp. operator+(C);

这显然是可以的。

三、运算符重载限制:

1. 重载后的运算符必须至少有一个操作数是用户定义的类型,这是为了防止程序员为标准类型重载运算符,可以确保程序正确运行。

2. 不能修改运算符的优先级,不能违反运算符原本的运算规则。例如在重载加号时,不能提供两个形参(友元除外),例如下面的这种重载方式就是不被允许的;

Time operator+(const Time &t1,const Time &t2) const;

因为加法是一种双目运算符。在重载为类的成员函数后,加法运算的第一项应该是调用函数的一个对象。所以在运算符重载时,参数表中的参数数目应该是重载运算符的操作数减1。

3. 不能创造新的运算符,例如不能定义operator**()运算符来求幂;

4. 不能重载以下运算符;

“.”成员运算符

“.*”成员指针运算符

“::”作用域运算符

“?”条件运算符

“siezof”运算符。

5. 很多运算符可以通过成员或者非成员函数进行重载,但是以下四种只能通过成员函数进行重载;

“=”赋值运算符;

“()“函数调用运算符;

“[ ]”下标运算符;

“->”通过指针方位类成员的运算符。

6. 自增运算符(++)与自减运算符(--)

由于自增和自减运算符是单目运算符,在重载时应该是没有参数的,但是又有前置与后置之分,例如++i与i++。为了区分,C++做了规定;

Class operator++()      //前置

Class operator++(int)   //后置

四、友元函数

我们已经知道,类的私有成员被限制访问,公有接口是唯一可访问途径,但是有时候这种限制太严格,在某些场景使用并不方便。我们来看上述提到的给矢量扩大的重载函数,实现A*n可以将运算符重载函数写为:

Vector Vector::operator*(double n) const

{

    return Vector(n * x, n * y);

}

但是如果要实现n*A,则上述函数不能满足要求,需要下面的运算符重载函数原型:而且因为在成员函数里已经对*运算符重载过了,下面这个函数只能定义为非成员函数,

Vector operator*(double n, const Vector & a);

{

   return Vector(n * x, n * y)

}

这种情况下,非成员函数内没有权限访问类的成员变量x和y。C++提供了友元来解决这种问题。C++中友元有三种,分别是友元函数,友元类,友元成员函数。这里单独介绍友元函数,它使得函数成为类的友元,将拥有和类的成员函数一样的权限去访问类的成员变量。

创建友元函数的第一步是声明,友元函数的声明放在类的声明中,并且在前面加上friend关键字。例如;

friend Vector operator*(double n, const Vector & a);

这意味着:

1.虽然友元在类中声明,但是它并不是成员函数,所以不能使用成员运算符来调用它,

2.但是它却跟成员运算符有相同的访问权限,即可以通过友元访问类的私有成员。

第二步是定义友元,友元可以直接在声明的时候进行定义,即内联的定义。也可以定义在类外,定义在类外时,不需要加类作用域运算符,也不需要有friend关键字。这样上述的非成员函数重载接口就可以访问x和y了。于是n*A将被翻译为:operator*(n, A);

因此,可以总结为:如果要为类重载运算符,并将非类的项作为其第一个操作数,则可以用友元函数来反转操作数的顺序。

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

我们希望为Vector类重载<<运算符,以实现直接打印某些信息。<<经常被重载,按照通常所见:

int n=0;

cout<<n;

我们希望可以直接:

Vector A;

cout<<A;

但我们知道cout是ostream的一个对象。要想让Vector类知道ostream类的对象,且该对象位于第一个操作数,按照上述总结,需要通过友元来实现重载<<运算符。我们可以如此设计友元函数:

void operator<<(ostream & os, const Vector & v)

则cout<<A被翻译为:operator<<(cout, v)。

这里分析一个问题,按照友元函数的概念,这里用到了ostream的对象和Vector的对象,则该函数有必要同时成为ostream和Vector类的友元函数。但是实际上,由于用的参数是cout,且我们一直用的是cout的整体,并没有访问ostream类的私有成员,于是不必是ostream的友元。为了保证传进来的不是cout的副本,所以这里的参数用了&引用。

下面来看另一种常见的情况:

cout<<A<<B;

也即在实现连续输出,上述的友元函数不能满足,因为cout<<A的返回值为void,将无法执行<<B的操作。通常的

cout<<”hello”<<”world”<<endl;

能够执行是因为cout的返回值不是void而是返回cout本身。于是可以按此改造友元函数:

std::ostream & operator<<(std::ostream & os, const Vector & v)

{

   os << "(x,y) = (" << v.x << ", " << v.y << ")";

   return os;

}

注意返回值是ostream &,这实现了将传入的cout返回去。

该友元函数不仅可以用以显示在控制台上的cout,同样可以应用于写入文件的ofstream.因为ofstream是ostream的子类。不用修改接口,可直接再调用的时候:

Vector A,B;

ofstream fout;

fout.open(“./Test.txt”);

fout<<A<<B;

附源码:

//vect.h 矢量类声明 实现矢量运算和显示
#ifndef VECTOR_H_	//预编译宏 避免重复引用
#define VECTOR_H_
#include <iostream>	//cout等对象的支持库

class Vector	//类声明
{
public:
    enum Mode {RECT, POL};	//枚举表达方式为直角坐标还是极坐标

//私有成员
private:				
    double x;          // 直角坐标系下的x值
    double y;          // 直角坐标系下的y值
    double mag;        // 极坐标下的矢量长度
    double ang;        // 极坐标下的矢量角度
    Mode mode;         // 表达方式 或RECT或POL

// 私有接口函数
    void set_mag();	//计算长度
    void set_ang();	//计算角度
    void set_x();	//计算x
    void set_y();	//计算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;}       // 得到x
    double yval() const {return y;}       // 得到y
    double magval() const {return mag;}   // 得到mag
    double angval() const {return ang;}   // 得到ang
    void set_polar_mode();                // 设置表达方式为极坐标
    void set_rect_mode();                 // 设置表达方式为直角坐标
// 运算符重载
    Vector operator+(const Vector & b) const;	//矢量加法
    Vector operator-(const Vector & b) const;	//矢量减法
    Vector operator-() const;					//矢量反向,求负
    Vector operator*(double n) const;			//矢量扩大
// 友元函数
    friend Vector operator*(double n, const Vector & a);//矢量扩大
    friend std::ostream & operator<<(std::ostream & os, const Vector & v);//矢量打印
};

#endif
// vect.cpp  矢量类方法定义
#include <cmath>
#include "vect.h"   
using std::sqrt;
using std::sin;
using std::cos;
using std::atan;
using std::atan2;
using std::cout;

// 弧度转度
const double Rad_to_deg = 45.0 / atan(1.0);//180/PI

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);
}

void Vector::set_x()
{
    x = mag * cos(ang);
}

void Vector::set_y()
{
    y = mag * sin(ang);
}

Vector::Vector()            
{
    x = y = mag = ang = 0.0;
    mode = RECT;
}
//默认表达方式为RECT,直角表达
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_x();
         set_y();
    }
    else
    {
         cout << "Incorrect 3rd argument to Vector() -- ";
         cout << "vector set to 0\n";
         x = y = mag = ang = 0.0;
         mode = RECT;
    }
}


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()    
{
}

void Vector::set_polar_mode()    
{
    mode = POL;
}

void Vector::set_rect_mode()     
{
    mode = RECT;
}


Vector Vector::operator+(const Vector & b) const
{
    return Vector(x + b.x, y + b.y);
}


Vector Vector::operator-(const Vector & b) const
{
    return Vector(x - b.x, y - b.y);
}


Vector Vector::operator-() const
{
    return Vector(-x, -y);
}


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


Vector operator*(double n, const Vector & a)
{
    return a * n;//这里调用了上述重载的*
}

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; 
}
// main.cpp 测试Vector类功能
#include <iostream>
#include "vect.h"
int main()
{
    using namespace std;
	//用默认构造函数创建
	Vector A;
	A.reset(3, 4);
	//用显式的构造函数直接创建,使用默认表达方式
	Vector B(6, 8);
	//用new的方式创建,使用指定的表达方式
	Vector *C= new Vector(5, 53.1, Vector::POL);
	//测试+
	Vector Sum = A+B;
	//测试-
	Vector Sub = B-A;
	//测试*
	double n = 2.0;
	Vector Mul = (*C)*n;
	Vector Mul1 = n*(*C);
	//测试求负
	Vector Ops = -(*C);
	//测试打印
	cout<<A<<" "<<B<<" "<<*C<<endl;	
	//查看计算结果
	cout<<Sum<<" "<<Sub<<" "<<Mul<<" "<<Mul1<<" "<<Ops<<endl;
	Sum.set_polar_mode();
	Sub.set_polar_mode();
	Mul.set_polar_mode();
	Mul1.set_polar_mode();
	Ops.set_polar_mode();
	cout<<Sum<<" "<<Sub<<" "<<Mul<<" "<<Mul1<<" "<<Ops<<endl;
	//清理内存
	delete C;
	//窗口停留
    cin.get();

    return 0; 
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bjtuwayne

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值