c++复习日记1 类的继承与派生

8月16日,回顾一下c++继承与派生的知识。

继承是面向对象程序设计中软件重用的关键技术。类与类间的关系主要分has-a、uses-a、is-a三种,其中is-a有传递性,此种机制即为继承。

派生类对基类成员的访问权限即类的继承中的“访问控制”可以总结成”公不变,私全私,保全保”,当然都不包括基类的私有成员。但是,派生类并非不创建从基类继承的私有数据成员(派生类可以通过所继承的该基类的成员函数,来访问基类私有数据成员)。除此之外,c++中还提供了一种访问调节机制,可以通过声明基类名::成员,保持私有继承时的基类公有数据成员在派生类中仍为公有数据成员。但声明数据成员时不可以带类型,也不可以在派生类中降低或者升高数据成员的可访问性

c++允许派生类的成员函数与基类的同名,并且派生类中访问时会自动屏蔽基类的同名数据成员。如果是成员函数同名,则在派生类中相当于重载这个函数。由调用形式指示this指针的不同类型,来调用不同版本的成员函数。

c++中,可以在派生类中初始化基类数据成员,用到了构造函数的执行顺序,这里不再赘述。

c++中继承还有多继承和虚继承等方式。多继承顾名思义,一个派生类可以继承多个基类。虚继承是为了避免继承了同一基类A的两个派生类B和C,又作为基类被另一个类D多继承时,A的成员在D中被划分为从B继承和从C继承所产生的二义性。虚继承可以通过把DAG图的会点定义为虚基类,即在B和C继承A时,用class B::virtual public A的格式实现。

下面来看一个经典的类的继承例子:

//Point.h
#ifndef POINT_H
#define POINT_H
class Point
{
    friend ostream & operator<<(ostream &, const Point &);  //friend友元函数对操作符<<进行重载
public:
    Point(int = 0, int = 0);  //带默认参数的构造函数
    void setPoint(int, int);  //对点坐标数据赋值
    int getX()const
    {
        return x;
    }
    int getY() const
    {
        return y;
    };
protected:
    int x, y;
};
Point::Point(int a, int b)
{
    setPoint(a, b);
}
void Point::setPoint(int a, int b)
{
    x = a;y = b;
}
//重载插入运算符,输出对象数据
ostream & operator<<(ostream& output, const Point& p)
{
    output << '[' << p.x << "," << p.y << "]";
    return output;
}
#endif

//Circle.h
#ifndef CIRCLE_H
#define CIRCLE_H
class Circle :public Point
{
    friend ostream& operator <<(ostream&, const Circle&);
public:
    Circle(double r = 0, int x = 0, int y = 0);   //构造函数
    void setRadius(double);   //置半径值
    double getRadius() const;    //返回半径
    double area() const;   //返回面积
protected:
    double radius;    //数据成员,半径
};
Circle::Circle(double r, int a, int b) :Point(a, b) { setRadius(r); };  //带参数构造函数初始化,首先调用基类构造函数
void Circle::setRadius(double r) { radius = r; };
double Circle::getRadius() const { return radius; };
double Circle::area() const { return 3.14 * radius * radius; };
ostream& operator <<(ostream& output, const Circle& c)
{
    output << "Center =" << '[' << c.x << ',' << c.y << "]" << ":Radius =" << setiosflags(ios::fixed | ios::showpoint) << setprecision(2) << c.radius;
    return output;
}
#endif // !CIRCLE_H
​

//Cylinder.h
#ifndef CYLINDER_H
#define CYLINDER_H
class Cylinder :public Circle
{
    friend ostream& operator<<(ostream&, const Cylinder&);
public:
    Cylinder(double h = 0.0, double r = 0.0, int x = 0, int y = 0);
    void setHeight(double);
    double getHeight() const;
    double area() const;
    double volume() const;
protected:
    double height;
};
Cylinder::Cylinder(double h, double r, int x, int y) :Circle(r, x, y) { setHeight(h); };
void Cylinder::setHeight(double h) { height = h; };
double Cylinder::getHeight()const { return height; };
double Cylinder::area() const { return 2 * Circle::area() + 3.14 * radius * height; };
double Cylinder::volume() const { return Circle::area() * height; };
ostream& operator<<(ostream& output, const Cylinder& cy)
{
    output << "Center =" << '[' << cy.x << "," << cy.y << "]" << ":Radius =" << setiosflags(ios::fixed | ios::showpoint) << setprecision(2) << cy.radius << ":Height =" << cy.height << endl;
    return output;
}
#endif // !CYLINDER_H
 

main.cpp包含了以上三个头文件,实现对给定圆心平面坐标、半径与高,计算圆柱体体积等。

//main.cpp
#include <iostream>
#include<iomanip>
using namespace std;
#include "Point.h"
#include "Circle.h"
#include"Cylinder.h"
int main()
{
    Point p(72, 115);
    cout << "The initial location of p is" << p << endl;
    p.setPoint(10, 0);
    cout << "\n The new location of p is" << p << endl;
    Circle c(2.5, 37, 43);
    cout << "\n The initial location and radius of circle is" << c << "\n Area is" << c.area() << "\n";
    c.setRadius(4.25);
    c.setPoint(2, 2);
    cout << "\n The new location and radius of circle is" << c << "\n Area is" << c.area() << "\n";
    Cylinder cy1(5.7, 2.5, 12, 23);
    cout << "\n The initial location ,radius and height of cylinder is" << cy1 << "\n Area is" << cy1.area() << "\n"<<"\n volume is"<<cy1.volume()<<"\n";
    return 0;
}

输出结果:

The initial location of p is[72,115]
​
 The new location of p is[10,0]
​
 The initial location and radius of circle isCenter =[37,43]:Radius =2.50
 Area is19.62
​
 The new location and radius of circle isCenter =[2,2]:Radius =4.25
 Area is56.72
​
 The initial location ,radius and height of cylinder isCenter =[12,23]:Radius =2.50:Height =5.70
​
 Area is84.00
​
 volume is111.86

在本例中,Point类定义了私有数据成员坐标x,y,并提供通过公有成员函数getX()和getY()作为访问接口。对x,y赋值可以通过创建对象时的初始化对象调用带参数的构造函数,或者用setPoint()函数。重载了运算符"<<",输出对象时直接输出对象的坐标。前几天也复习了运算符重载,将在后面记录和讨论。

Circle类由Point类派生,定义了受保护的数据成员半径radius,并提供了公有成员函数接口可以访问。还定义了面积计算函数area()。Circle类构造函数有三个参数,通过调用基类Point的带参构造函数初始化x,y坐标。

Cylinder类由Circle类派生,定义了受保护的数据成员height,并拥有从Circle和Point继承的x,y和radius。构造函数含默认参数,通过调用Circle类的构造函数直接初始化x,y和radius三个数据成员。类内函数完成对圆柱表面积和体积的计算(计算面积时,area()与Circle类中的为同名函数,但开辟的是不同的储存空间,在派生类中也会屏蔽基类同名函数)。最后再次重载"<<"运算符,覆盖掉基类重载版本,输出一系列圆柱属性。


上面用到了对运算符”<<“的重载,下面来回顾一下c++中运算符重载。

顾名思义,重载运算符就是让标准库函数的运算符按照用户的意愿去重新加载,但原有的语义一般不变。重载运算符格式为:

类型 类名::operator op(参数表)
{
    //用户自定义的操作
}

用于类的运算符通常都要重载,但c++也提供两个算符的默认重载版本:

"="默认重载为对象数据成员的复制

"&"默认重载为返回任何类对象的地址

通常重载运算符用于成员函数和友元函数,因为普通函数重载访问非公有类成员时需通过public接口提供的函数实现,增加程序开销。用友元函数重载运算符时,左右操作数都由参数传递,可以有效解决运算符左右操作数类型不同导致的问题。来看看下面的复数运算例子:

/*友元函数重载运算符---复数运算*/
/*如果用友元函数重载运算符,左右操作数都由参数传递,则c++可以通过构造函数实现数据的隐式转换*/
#include <iostream>
using namespace std;
class Complex
{
public:
    Complex(double r = 0, double i = 0);
    Complex(int a)
    {
        Real = a;
        Image = 0;
    }
    void print() const;
    friend Complex operator+(const Complex& c1, const Complex& c2); //如果以友元函数重载,则可以使用应用参数作为修改对象
    friend Complex operator-(const Complex& c1, const Complex& c2);
    friend Complex operator-(const Complex& c1);
private:
    double Real ,Image;
};
Complex::Complex(double r, double i)
{
    Real = r;Image = i;
}
Complex operator+(const Complex& c1, const Complex& c2)
{
    double r = c1.Real + c2.Real;
    double i = c1.Image + c2.Image;
    return Complex(r, i);
}
Complex operator-(const Complex& c1, const Complex& c2)
{
    double r = c1.Real - c2.Real;
    double i = c1.Image - c2.Image;
    return Complex(r, i);
}
Complex operator-(const Complex& c)
{
    return Complex(-c.Real ,-c.Image);
}
void Complex::print() const
{
    cout << "REAL=" << Real << "IMAGE=" << Image<<endl;
}
int main()
{
    Complex c1(1, 2), c2(3, 3);
    Complex c;
    c = c1 - c2;
    c.print();
    c = c1 + c2;
    c.print();
    c = c2 + 2;
    c.print();
    c = 2 + c2;
    c.print();
    c = -c1;
    c.print();
}
​

运行结果:

REAL=-2IMAGE=-1
REAL=4IMAGE=5
REAL=5IMAGE=3
REAL=5IMAGE=3
REAL=-1IMAGE=-2

如果将

friend Complex operator+(const Complex& c1, const Complex& c2);

这一句换成

Complex operator+(Complex);

则下面的”c2+2“被解释为c2.operator+(25),但"2+c2"则被解释为25.operator+(c2),25不是Complex类对象,无法驱动函数进行计算。定义为友元函数就可以解决这个问题。

再来看一个经典的运算符重载例子,也是上面继承例程中出现过的"<<"的重载:

/*经典重载运算符"<<"与">>"---Vector类*/
#include <iostream>
using namespace std;
class Vector
{
public:
    Vector(int = 1);     //默认长度构造函数
    Vector(const int*, int);   //使用数组参数构造函数
    Vector(const Vector&);   //拷贝构造函数
    ~Vector();    //析构函数
    //重载运算符
    int& operator[](int i)const;
    int operator()()const;
    Vector& operator=(const Vector&);
    bool operator==(const Vector&)const;
    bool operator!=(const Vector&)const;
    friend Vector operator+(const Vector&, const Vector&);
    friend ostream& operator<<(ostream& output, const Vector&);
    friend istream& operator>>(istream& input, Vector&);
private:
    int* v;
    int len;
};
//构造指定长度向量,初始化数据元素为0
Vector::Vector(int size)
{
    if (size < 0 || size>100)
    {
        cout << "error";
        exit(0);
    }
    v = new int[size];
    for (int i = 0; i < size; i++)
    {
        v[i] = 0;
        len = size;
    }
}
//用整型数组构造向量
Vector::Vector(const int* B,int size)
{
    if (size < 0 || size>100)
    {
        cout << "error";
        exit(0);
    }
    v = new int[size];
    len = size;
    for (int i = 0;i < size;i++)
    {
        v[i] = B[i];
    }
}
//用已有对象复制构造向量
Vector::Vector(const Vector& c)
{
    len = c();
    v = new int[len];
    for (int i = 0;i < len;i++)
    {
        v[i] = c[i];
    }
}
//析构函数
Vector::~Vector()
{
    delete[] v;
    len = 0;
}
//返回向量元素
int &Vector::operator[](int i) const
{
    if (i >= 0 && i <= 100) return v[i];
    else cout << "out of range" << endl;
    exit(0);
}
//返回向量长度
int Vector::operator()()const
{
    return len;
}
//向量赋值
Vector& Vector::operator=(const Vector &B)
{
    if (len == B())
    {
        for (int i = 0;i < len;i++)
        {
            v[i] = B.v[i];
            return *this;
        }
    }
    else
    {
        cout << "operate= fail\n";
        exit(0);
    }
}
//判断两个向量相等
bool Vector::operator==(const Vector& B) const
{
    if (len == B.len)
    {
        for (int i = 0;i < len;i++)
        {
            if(v[i] != B.v[i])
              return false;
        }
    }
    else
        return false;
    return true;
}
//判断两向量不等
bool Vector::operator!=(const Vector& B) const
{
    return!(*this == B);   //调用(*this).operator==(B)
}
//向量相加
Vector operator+(const Vector& A, const Vector& B)
{
    int size = A();
    int* T = new int[size];
    if (size == B())
    {
        for (int i = 0; i < size; i++)
            T[i] = A.v[i] + B.v[i];
        return Vector(T, size);   //用数组构造返回对象
    }
    else
    {
        cout << "operator+ fail\n";
        exit(0);
    }
}
//输出向量
ostream& operator<<(ostream& output, const Vector& A)
{
    for (int i = 0;i < A.len;i++)
        output << A.v[i] << " ";
    return output;
}
//输入向量
istream& operator>>(istream& input, Vector& A)
{
    for (int i = 0;i < A();i++)
        input >> A.v[i];
    return input;
}
int main()
{
    int k;
    cout << "Input the length and Vector:\n";
    cin >> k;
    Vector A(k), B(k), C(k);     //构造指定长度向量
    cout << "Input the element of Vector A:\n";
    cin >> A;         //调用operatoe>>(cin,A)
    cout << "Input the element of Vector B:\n";
    cin >> B;          //调用operatoe<<(cin,B)
    if (A == B)       //调用A.operator==(B)
    {
        for (int i = 0;i < A();i++)
            C[i] = A[i] * 2;    //调用C.operator[](i)和A.operator[](i)
    }
    else C = A + B;      //调用operator+(A,B)和C.operator=(A+B)
    cout << "[" << A << "]\n+[" << B << "]\n=[" << C << "]" << endl;
}

对于这个例子,关键分析重载插入和提取运算符。

istream& operator>>(istream& input, Vector& A)
{
    for (int i = 0;i < A();i++)
        input >> A.v[i];
    return input;
}

主函数中调用cin>>A,解释为operator>>(cin,A),第一个引用是返回对istream的引用,用于连续进行插入操作。第二个引用是istream类对象的引用,用>>把数据传到input里。第三个引用将Vector类对象A地址直接传入,如果省去&就会复制A的参数传入,增加了程序开销。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值