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的参数传入,增加了程序开销。
|