实验环境
操作系统:win10
gcc:8.1.0
开发软件:qt5.14.2
实验内容
一、继承访问权限测试
设计类A具有public, protected, private等不同属性的成员函数或变量;
类B通过public, protected, private等不同方式继承A,在类B的成员函数中测试访问A的成员函数或变量;
在类B中添加public, protected, private等不同属性的成员函数或变量,在外部测试访问B的各个成员函数或变量;
B以private方式继承A,尝试把A中的部分public成员提升为public。
二、友元类继承测试
设计类A含有私有变量a,在类A中友元给类C;
设计类B继承A,添加私有变量b;在类C中测试访问类B的成员变量a, b;
设计类D继承C,在D的成员函数中测试访问类A的成员变量a,类B的成员变量a, b。
三、多态性综合运用
一般多态性函数:输入输出参数完全一样,在父类中添加virtual;
特殊多态性函数:输入或输出参数在子类中是父类的指针或基类的引用,在子类中对于的是子类的指针或子类的引用;
析构函数的多态性;
多继承,注意什么情况需要虚继承;
设计矢量图,运用多继承设计组合图形,要求具备创建不同类型矢量图、选择图形、移动图形、用不同颜色显示图形(表示选中与否),用vector或数组管理图形。
四、作业提交方式
通过设计实验完成上述相关功能,并在博客中记录完成过程及实验结果,尤其记录上课强调的注意点、碰到的问题及对应解决方案。
动态库与静态库
本次实验中将使用qt的框架,并且将引入和使用动态库的概念;其实本实验并不一定要了解和使用动态库,但既然课程中有所提及,这里也进行学习和使用。
首先回顾库的概念,库是写好的现有的,成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。
如上图所示,常见的代码编译为可执行程序的流程如上图所示;其中链接这一步的链接方式区别就是动态和静态的区别。
再来谈及动态和静态的区别。通俗来讲,在对调用了库的代码进行编译时;库是否被编译进程序是它们的本质区别。而从使用的区别上来讲,静态由于将库已经编译进程序内部,所以可执行程序得以独立运行;而动态库在编译时只是指定了定向的库中函数,所以使用时可执行程序无法独立运行。但反过来看;从软件开发的更新维护角度来讲,动态库更利于使用,而静态库一旦发生库的更新,需要整个程序重新编译,不利于维护。
在本次实验中,矢量图部分的实现将依赖于设计的对应动态库。
继承
在本次的实验中,会涉及到面向对象编程的一个重要概念继承。这个概念在java中同样存在,在C++中,继承的属性分为3种,public,protected,private;分别可以称作公有,保护和私有。被继承的常被称为父类(基类),而通过继承生成的类常被称为子类(派生类)。
下面将设计一个简单的小例子进行说明。其中函数部分没有特意设计,可以是简单的返回对应类别的变量。
class test1
{
public:
int public1 = 0;
void publicfunction();//通过调用本函数可以访问test1变量的三种属性
protected:
int protected1 = 0;
void protectedfunction();//通过调用本函数同样可以访问test1变量的三种属性
private:
int private1 = 0;
void privatefunction();//通过调用本函数同样可以访问test1变量的三种属性
friend void fritest1(test1 testfri);//友元函数
//C++中,一个类的函数只要可以被调用,就可以访问其所有变量。
};
//设计test2,test3,test4三个不同的类以三种不同的方式继承test1
class test2:public test1 //以公有方式继承test1
{
public:
void use_public_val();
void use_public_function();
};
class test3:protected test1 //以保护方式继承test1
{
public:
void use_public_val();
void use_public_function();
};
class test4:private test1 //以私有方式继承test1
{
public:
using test1::public1;
void use_public_val();
void use_public_function();
};
//二次设计test5,test6以public的方式继承test3和test4
class test5:public test3
{
public:
void test_use_public_function();
};
class test6:public test4
{
public:
void test_use_public_function();
};
需要注意的是,上面给出的是笔者简要写的伪代码,只是为了进行继承的相关说明。
首先从test1的例子进行public protected private 三种属性相关的说明,在一个类中;在类的内部,所有函数和变量都可以被自由访问。而在外部被引用声明时,仅有public类可以被访问,即仅有public1和publicfuction可以被使用。
接下来从test2,test3,test4三个例子进行public protected private 三种继承方式说明。首先,test2和test3与test4作为子类,可以对其父类,也就是test1的public和protected属性的变量和函数进行访问。而其中更为具体的区别,则是在于三种不同继承方式的继承类型,简单来说。
对于test2的public继承而言:
- 基类的私有成员,子类不可以访问
- 基类的保护成员,子类可以继承为自己的保护成员,在派生类可以访问,在外部不可以访问。
- 基类的公有成员,子类可以继承为自己的公有成员。在派生类可以访问,在外部也可以访问。
对于test3的protected继承而言:
- 基类公有成员,子类中继承为自己的保护成员,在派生类可以访问,在外部不可以访问
- 基类保护成员,子类中继承为自己的保护成员,在派生类可以访问,在外部不可以访问
- 基类私有成员,子类一样不可以访问基类的私有成员。
对于test4的private继承而言:
- 基类公有成员,子类中继承为自己的私有成员,在派生类可以访问,在外部不可以访问。
- 基类保护成员,子类中继承为自己的私有成员,在派生类可以访问,在外部不可以访问。
- 基类私有成员,子类一样不可以访问基类的私有成员
上述的三种不同,可以从test5和test6中的不同得到说明,举例来说,test5中的访问合法而test6中则不然。
using的使用
经过上面的说明,当然会存在部分使用场景下;需要使得某些子类得以跨权限访问父类属性,那么这个时候可以用到using;举例来说,test4中就得以跨权限访问了public1。
友元
友元同样是c++中重要的概念之一,具体的应用可以分为友元函数和友元类;对于友元函数而言,其最大的作业可以简单的概括为使得声明函数在类的外部同样得以访问本来不能访问的保护和私有对象。如下面贴上的test1中的代码。
class test1
{
public:
int public1 = 0;
void publicfunction();//通过调用本函数可以访问test1变量的三种属性
protected:
int protected1 = 0;
void protectedfunction();//通过调用本函数同样可以访问test1变量的三种属性
private:
int private1 = 0;
void privatefunction();//通过调用本函数同样可以访问test1变量的三种属性
friend void fritest1(test1 testfri);//友元函数
//C++中,一个类的函数只要可以被调用,就可以访问其所有变量。
};
则此时外部定义的fritest1函数就得以访问受限的保护和私有对象。
而对于友元类而言,若在定义了一个类test7为test1的友元类,则test7的实例同样可以访问test1的所有成员。
但此时需要注意两点,首先我们假定存在两个新的类test8和test9
其中 test8继承自test1 而 test9继承自test7
则
- test7可以访问test8中继承自test1的所有对象,但对test8本身不具备友元的特殊访问性质
- test9则已经丧失了对test1的友元的特殊访问性质
- 当然,test9就更不能对test8进行友元的访问
多态
什么是多态?在面向对象方法中,所谓多态性就是不同对象收到相同消息,产生不同的行为。在C++程序设计中,多态性是指用一个名字定义不同的函数,这些函数执行不同但又类似的操作,这样就可以用同一个函数名调用不同内容的函数。
对于多态而言简单的例子就是运算符了,例如我们使用运算符+,就可以实现整型数、浮点数、双精度类型之间的加法运算,这三种类型的加法操作其实是互不相同的,是由不同内容的函数实现的。这个例子就是使用了多态的特征。
举一个简单的例子
class test1
{
public:
virtual void showtest()
{cout << "test1" << endl;}
};
class test2 : public test1
{
public:
void showtest()
{cout << "test2" << endl;}
};
void Func(test1 &test)
{
test.showtest();
}
int main()
{
test1 a;
test2 b;
Func(a);
Func(b);
return 0;
}
上述的代码执行后,结果应为test1和test2。
这里出现了一个 virtual 的关键词的应用,其对应着多态的一个重要概念,虚函数。
虚函数指那些在类的成员函数前加virtual关键字的函数,而虚函数的重写则指的是派生类中有一个跟基类的完全相同的虚函数,我们就称子类的虚函数重写了基类的虚函数。“完全相同”是指:函数名、参数、返回值都相同。另外,虚函数的重写也叫做虚函数的覆盖。
这里要注意的是,构成多态需要满足以下两个条件:
- 调用函数的对象必须是指针或者引用。
- 被调用的函数必须是虚函数,且完成了虚函数的重写。
这里要注意的是,满足以上条件和重写时的多态函数也被称为一般多态性函数。
而相对应的,上例中的Func函数的参数使用的是对父类的引用,两者的区别如下。
void Func(test1 &test)
{
test.showtest();
}
void Func(test1 test)
{
test.showtest();
}
上面两者的区别在于前者满足了上面提到的构成多态的条件一;若改成下面的形式,则输出结果都为test1和test1。这种多态函数则被称为特殊性多态函数。这个过程实现的重要意义是确保了函数在传参过程中同样具有多态性,使得函数在调用时可以灵活的识别子类的多态函数。
多态函数还包含一种纯虚函数,即在虚函数的后面写上 = 0。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象。只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现了接口继承,当基类没有实际应用意义但派生类会被大量调用时,常常会出现纯虚函数的使用。
矢量图
本次实验中实现的矢量图部分均以qt的框架为基准,其中涉及到一个特别的QPainter库,QPainter是Qt的图形绘制系统中自带的库。其具体的使用和案例均需要专门的学习,笔者这里只是为了示例并没有深入研究,有需要的烦请自行查找相关函数和使用方法。
项目结构如上图所示。其中,shapedll为动态库,负责矢量图的实现;main为qt自带窗口程序。
由于时间原因,本次实验仅实现点和三角形两种图形的简单示例。
shapedll.h
#ifndef SHAPEDLL_H
#define SHAPEDLL_H
#include<string>
#include<math.h>
#include<QPainter>
#include<vector>
using namespace std;
#include "shapedll_global.h"
class SHAPEDLL_EXPORT Shapedll
{
public:
Shapedll();
};
class CPoint;
class CRect;
class SHAPEDLL_EXPORT CShape
{
public:
CShape();
CShape(const CShape & shape);
virtual ~CShape();
virtual double GetArea() const;
virtual bool ptIn(const CPoint& pt) const;
virtual bool InRect(const CRect& rc) const;
virtual void Draw(QPainter & painter) const;
virtual CShape* Clone() const;
virtual CShape& Move(int nOffsetX,int nOffsetY);
protected:
string m_sName;
};
class SHAPEDLL_EXPORT CPoint:virtual public CShape
{
public:
int m_nPosX;
int m_nPosY;
CPoint(){}
CPoint(int nPosX,int nPosY);
CPoint(const CPoint & pt);
virtual ~CPoint();
double GetArea() const;
bool ptIn(const CPoint& pt) const;
bool InRect(const CRect& rc) const;
void Draw(QPainter & painter) const;
CPoint* Clone() const;
CPoint& Move(int nOffsetX,int nOffsetY);
};
class SHAPEDLL_EXPORT CTriangle:virtual public CShape
{
public:
CTriangle(const CPoint& pt1,const CPoint& pt2,const CPoint& pt3);
CTriangle(const CTriangle & rc);
virtual ~CTriangle();
double GetArea() const;
bool ptIn(const CPoint& pt) const;
bool InRect(const CRect& rc) const;
void Draw(QPainter & painter) const;
CTriangle* Clone() const;
CTriangle& Move(int nOffsetX,int nOffsetY);
CPoint m_pts[3];
};
class ShapeManager
{
public:
ShapeManager();
void Add(CShape* pShape);
void Remove(CShape* pShape);
void Add(vector<CShape*> shapes);
void Remove(vector<CShape*> shapes);
void RemoveAll();
~ShapeManager();
CShape * ptIn(const CPoint & pt);
bool InRect(const CRect & rc, vector<CShape*> &shapesOut);
void Draw(QPainter & painter, vector<CShape*>&shapes);
void Draw(QPainter & painter);
void Clone(vector<CShape*>&shapesIn, vector<CShape*>&shapesOut);
void Move(int nOffsetX, int nOffsetY, vector<CShape*> &shapes);
private:
vector<CShape*> m_pShapes;
};
#endif //SHAPEDLL_H
shapedll.cpp
#include "shapedll.h"
#include<iostream>
using namespace std;
#include<QPaintEvent>
#include<QPainter>
Shapedll::Shapedll()
{
}
CShape::CShape() {
}
CShape::CShape(const CShape & shape)
{
m_sName = shape.m_sName;
}
CShape::~CShape(){
}
double CShape::GetArea() const
{
return 0;
}
bool CShape::ptIn(const CPoint & pt) const
{
return false;
}
bool CShape::InRect(const CRect & rc) const
{
return false;
}
void CShape::Draw(QPainter & painter) const
{
}
CShape* CShape::Clone() const
{
return new CShape(*this);
}
CShape& CShape::Move(int nOffsetX, int nOffsetY)
{
return *this;
}
CPoint::CPoint(int nPosX, int nPosY) {
m_nPosX = nPosX;
m_nPosY = nPosY;
}
CPoint::CPoint(const CPoint& pt) {
m_nPosX = pt.m_nPosX;
m_nPosY = pt.m_nPosY;
}
CPoint::~CPoint() {
}
double CPoint::GetArea() const
{
return 0;
}
bool CPoint::ptIn(const CPoint& pt) const
{
return false;
}
bool CPoint::InRect(const CRect& rc) const {
return rc.ptIn(*this);
}
//画图形
void CPoint::Draw(QPainter& painter) const
{
painter.drawPoint(m_nPosX, m_nPosY);
}
CPoint* CPoint::Clone() const {
return new CPoint(*this);
}
//移动点
CPoint& CPoint::Move(int nOffsetX, int nOffsetY) {
m_nPosX += nOffsetX;
m_nPosY += nOffsetY;
return *this;
}
//三角形
CTriangle::CTriangle(const CPoint& pt1, const CPoint& pt2, const CPoint& pt3) {
m_pts[0] = pt1;
m_pts[1] = pt2;
m_pts[2] = pt3;
}
//移动三角形
CTriangle& CTriangle::Move(int nOffsetX, int nOffsetY) {
for (int i = 0; i < 3; i++) {
m_pts[i].Move(nOffsetX, nOffsetY);
}
return *this;
}
CTriangle::CTriangle(const CTriangle& tri) {
for (int i = 0; i < 3; i++) {
m_pts[i] = tri.m_pts[i];
}
}
CTriangle::~CTriangle() {
}
double CTriangle::GetArea() const {
int x1, y1, x2, y2, x3, y3;
x1 = m_pts[0].m_nPosX;
y1 = m_pts[0].m_nPosY;
x2 = m_pts[1].m_nPosX;
y2 = m_pts[1].m_nPosY;
x3 = m_pts[2].m_nPosX;
y3 = m_pts[2].m_nPosY;
double bottomLine = sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2));
double verticalLine1 = abs((y1 - y2) * x3 - (x1 - x2) * y3 + (x1 - x2) * y2 - (y1 - y2) * x2);
double verticalLine2 = sqrt(pow(y1 - y2, 2) + pow(x1 - x2, 2));
double verticalLine = verticalLine1 / verticalLine2;
return (verticalLine * bottomLine) / 2.0;
}
bool CTriangle::ptIn(const CPoint& pt) const {
CTriangle c1 = CTriangle(m_pts[0], m_pts[1], pt);
CTriangle c2 = CTriangle(m_pts[1], m_pts[2], pt);
CTriangle c3 = CTriangle(m_pts[2], m_pts[0], pt);
double totalArea = c1.GetArea() + c2.GetArea() + c3.GetArea();
if (totalArea == this->GetArea())
return true;
else
return false;
}
bool CTriangle::InRect(const CRect& rc) const {
return rc.ptIn(m_pts[0]) && rc.ptIn(m_pts[1]) && rc.ptIn(m_pts[2]);
}
//画三角形
void CTriangle::Draw(QPainter & painter) const
{
painter.drawLine(m_pts[0].m_nPosX, m_pts[0].m_nPosY, m_pts[1].m_nPosX, m_pts[1].m_nPosY);
painter.drawLine(m_pts[1].m_nPosX, m_pts[1].m_nPosY, m_pts[2].m_nPosX, m_pts[2].m_nPosY);
painter.drawLine(m_pts[2].m_nPosX, m_pts[2].m_nPosY, m_pts[0].m_nPosX, m_pts[0].m_nPosY);
}
ShapeManager::ShapeManager()
{
}
void ShapeManager::Add(CShape *pShape)
{
m_pShapes.push_back(pShape);
}
void ShapeManager::Remove(CShape *pShape)
{
for(vector<CShape*>::iterator it=m_pShapes.begin();it!=m_pShapes.end();it++){
if(*it==pShape)
{
delete pShape;
m_pShapes.erase(it);
}
}
}
void ShapeManager::Add(vector<CShape*> shapes)
{
m_pShapes.insert(m_pShapes.begin(),shapes.begin(),shapes.end());
}
void ShapeManager::Remove(vector<CShape*> shapes)
{
for (vector<CShape*>::iterator it = m_pShapes.begin();it!=m_pShapes.end();it++) {
Remove(*it);
}
}
void ShapeManager::RemoveAll()
{
for (vector<CShape*>::iterator it = m_pShapes.begin();it!=m_pShapes.end();it++) {
delete *it;
}
m_pShapes.clear();
}
ShapeManager::~ShapeManager()
{
RemoveAll();
}
CShape * ShapeManager::ptIn(const CPoint& pt)
{
for (vector<CShape*>::iterator it = m_pShapes.begin();it!=m_pShapes.end();it++) {
if((*it)->ptIn(pt))
{
return *it;
}
}
return NULL;
}
bool ShapeManager::InRect(const CRect& rc,vector<CShape*> &shapesOut)
{
shapesOut.clear();
for (vector<CShape*>::iterator it = m_pShapes.begin();it!=m_pShapes.end();it++) {
if((*it)->InRect(rc))
{
shapesOut.push_back(*it);
}
}
}
void ShapeManager::Draw(QPainter& painter, vector<CShape*>& shapes)
{
for (vector<CShape*>::iterator it = shapes.begin();it!=shapes.end();it++) {
(*it)->Draw(painter);
}
}
void ShapeManager::Draw(QPainter& painter)
{
for (vector<CShape*>::iterator it = m_pShapes.begin();it!=m_pShapes.end();it++) {
(*it)->Draw(painter);
}
}
void ShapeManager::Clone(vector<CShape*>& shapesIn,vector<CShape*>& shapesOut)
{
shapesOut.clear();
for (vector<CShape*>::iterator it = shapesIn.begin();it!=shapesIn.end();it++) {
shapesOut.push_back((*it)->CLone());
}
}
void ShapeManager::Move(int nOffsetX, int nOffsetY, vector<CShape *> &shapes)
{
for(vector<CShape*>::iterator it=shapes.begin();it!=shapes.end();it++)
{
(*it)->Move(nOffsetX,nOffsetY);
}
}
具体的运行由于实验所用机器qt配置存在问题,导致完成构建后无法运行;后续的运行截图将在完成调试后补充;同时ShapeManager部分的代码任然存在问题,同样会在后续进行修改。理论上上述代码配合几行简单的demo代码可以直接运行,不会出问题。