C++程序设计 —— 实验二:继承和多态

目录

一、继承的知识总结

1.1 继承的概念

1.2 基类和派生类

1.3 访问控制和继承

1.4 继承类型

1.5 继承的优点

二、继承访问权限测试

2.1 设计基类

2.2 继承基类

2.3 外部测试

2.4 调整权限

三、友元类与继承测试

3.1 友元类声明

3.2 友元关系与继承

3.3 友元的总结

四、多态的知识总结

4.1 一般多态性函数

4.2 纯虚函数

4.3 特殊多态性函数

4.3 多继承

4.4 析构函数的多态性

五、多态性综合运用


一、继承的知识总结

1.1 继承的概念

        面向对象程序设计中最重要的一个概念是继承。继承(inheritance)是面向对象程序设计使代码可以复用的重要的手段,它允许我们在保持原有类特性的基础上进行扩展,增加功能。

        当创建一个类时,不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类

        继承代表了 is a 关系。例如,哺乳动物是动物,狗是哺乳动物,因此,狗是动物,等等。如下图表示了基类Person和派生类Student的关系:

  代码如下:

// 基类
class Person{
public:
    Person();                    // 缺省构造函数
    string m_strName;            // 姓名
    string m_strSex;             // 性别
    void setSex(string strSex){  // set方法,修改性别
        m_strSex = strSex;
    }
    void getName(){              // get方法,获取姓名
        return m_strName;
    }
};

// 派生类
class Student:public Person{
public:
    Student();                   // 构造函数
    string m_strStuID;           // 学生学号
    string getStuID(){           // get方法,获取学生学号
        return m_strStuID;
    }
};

1.2 基类和派生类

        一个类可以派生自多个类,这意味着,它可以从多个基类继承数据和函数。定义一个派生类,我们使用一个类派生列表来指定基类。类派生列表以一个或多个基类命名,形式如下:

class 派生类类名: 访问修饰符 基类类名

访问修饰符是 public、protected 或 private 其中的一个,如果未使用访问修饰符,则默认访问修饰符为 private。

基类 PersonStudent是它的派生类,如下所示:

class Person{
public:
    string m_strName; //姓名
protected:
    string m_strSex;  //性别
private:
    int m_nAge;       //年龄
};

class Student:public Person{
public:
    int m_nNo;        //学号
};

1.3 访问控制和继承

        派生类可以访问基类中所有的非私有成员。因此基类成员如果不想被派生类的成员函数访问,则应在基类中声明为 private。

我们可以根据访问权限总结出不同的访问类型,如下所示:

访问publicprotectedprivate
同一个类yesyesyes
派生类yesyesno
外部的类yesnono

一个派生类继承了所有的基类方法,但下列情况除外:

  • 基类的构造函数、析构函数和拷贝构造函数。
  • 基类的重载运算符。
  • 基类的友元函数。

1.4 继承类型

        当一个类派生自基类,该基类可以被继承为 public、protected 或 private 几种类型。继承类型是通过上面讲解的访问修饰符 access-specifier 来指定的。

        我们几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:

  • 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有保护成员来访问。
  • 保护继承(protected): 当一个类派生自保护基类时,基类的公有保护成员将成为派生类的保护成员。
  • 私有继承(private):当一个类派生自私有基类时,基类的公有保护成员将成为派生类的私有成员。

1.5 继承的优点

        继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,而继承便是类设计层次的复用。这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行效率的效果。

二、继承访问权限测试

2.1 设计基类

设计Person类具有public, protected, private等不同属性的成员函数或变量:

// 基类
class Person{
public:
    Person(){};                      //无参构造函数
    Person(string name){             //有参构造函数
        m_strName = name;
    }
    ~Person(){};                     //析构函数
    string m_strName;                //姓名
    void setSex(string strSex){      //set方法,设置性别
        m_strSex = strSex;
    }
    void setName(string name){       //set方法,设置姓名
        m_strName = name;
    }
protected:
    string m_strSex;                 //性别
private:
    int m_nAge;                      //年龄
};

2.2 继承基类

Student类通过public, protected,private等不同方式继承Person类,在Student类的成员函数中测试访问Person类的成员函数或变量:

// 派生类 通过public继承
class public_Student:public Person{
public:
    int m_nNo;                       //学号
    void Test(){
        m_strName = "李小红";        //可访问基类Person的公共型变量
        m_strSex = "女";             //可访问基类Person的保护型变量
        m_nAge = 20;                 //不可访问基类Person的私有型变量
    }
    void setSex(string strSex = ""){ //set方法,设置性别 
        m_strName = "";              //Person基类的m_strSex是protected型的,在派生类Student中有权访问,但外部不能访问
        m_strSex = strSex;           //因此set方法,是提供修改性别的函数
    }
    string getSex(){                 //get方法,获取性别
        return m_strSex;             //同理,派生类可访问父类的protected型的变量,即可以返回m_strSex
    }
};
// 派生类 通过protected继承
class protected_Student:protected Person{
public:
    int m_nNo;                       //学号
    void Test(){
        m_strName = "李小红";        //派生类Student通过protected继承基类Person,公共型变量被降为保护型,在派生类中依然可以访问
        m_strSex = "女";             //可访问基类Person的保护型变量
        m_nAge = 20;                 //不可访问基类Person的私有型变量
    }
    void setSex(string strSex = ""){ //set方法,设置性别
        m_strName = "";             
        m_strSex = strSex;           
    }
    string getSex(){                 //get方法,获取性别
        return m_strSex;             
    }
};
// 派生类 通过private继承
class private_Student:private Person{
public:
    int m_nNo;                       //学号
    void Test(){
        m_strName = "李小红";        //派生类Student通过private继承基类Person,公共型变量被降为私有型,在派生类中不可以访问
        m_strSex = "女";             //不可访问
        m_nAge = 20;                 //不可访问基类Person的私有型变量
    }
    void setSex(string strSex = ""){   
        m_strName = "";              //private继承,不可访问m_strName 
        m_strSex = strSex;           //private继承,不可访问m_strSex
    }
    string getSex(){   
        return m_strSex;             //private继承,不可访问m_strSex          
    }
};

2.3 外部测试

Student类中添加public,protected,private等不同属性的成员函数或变量,在外部测试访问Student类的各个成员函数或变量:

// 派生类
class Student:public Person{
public:
    int m_nNo;                       //学号
    void setSex(string strSex = ""){ //set方法,设置性别 
        m_strName = "";              //Person基类的m_strSex是protected型的,在派生类Student中有权访问,但外部不能访问
        m_strSex = strSex;           //因此set方法,是提供修改性别的函数
    }
    string getSex(){                 //get方法,获取性别
        return m_strSex;             //同理,派生类可访问父类的protected型的变量,即可以返回m_strSex
    }
    double showScore(){              //set方法,录入成绩
        return m_dScore;
    }
protected:
    string m_strCourse;              //课程
private:
    double m_dScore;                 //成绩  
};
// 测试
#include <iostream>
#include "person.h"
using namespace std;
int main()
{
    Student stu;
    stu.setSex("女生");
    stu.setName("李小红");
    cout<<stu.m_strName<<" "<<stu.getSex();
    //cout<<stu.m_nAge;    //私有型变量不能访问
    //cout<<stu.m_strSex;  //保护型变量不能访问
}

   运行结果:

  

2.4 调整权限

Student类以private方式继承Person类,尝试把Person类中的部分public成员提升为public:

// 派生类
class Student:private Person{
public:
    int m_nNo;                       //学号
    using Person::m_strName;         //通过using调整权限,派生类被降为private的变量可以提升为public
    void setSex(string strSex = ""){ //set方法,设置性别
        m_strSex = strSex;
    }
    string getSex(){                 //get方法,获取性别
        return m_strSex;
    }
    void Test(){
        m_strSex = "男";              //可访问
        m_strName = "李小明";         //可访问
    }
protected:
        using Person::m_strSex;      //通过using调整权限,派生类被降为private的变量可以提升为protected
};

三、友元类与继承测试

        友元关系不能继承。基类的友元对派生类的成员没有特殊访问权限。如果基类被授予友元关系,则只有基类具有特殊访问权限,该基类的派生类不能访问授予友元关系的类。

3.1 友元类声明

设计类ABase含有私有变量m_a,在类ABase中友元给类CFrnd:

// 基类ABase
class ABase { 
private:
	int m_a;            //私有变量m_a
public: 
	ABase(int x=0){ 
        m_a = x; 
    } 
	friend class CFrnd; //声明友元类
}; 
 

3.2 友元关系与继承

①设计类BDrived继承ABase,添加私有变量m_b,在类CFrnd中测试访问类BDrived的成员变量m_a,m_b:

// 派生类BDrived  通过public继承ABase
class BDrived:public ABase { 
private:
	int m_b;                 //私有变量m_b
public: 
	BDrived(int x):ABase(x){
        m_b = x;
    } 
};

 基类的友元类不可访问基类的派生类的私有成员——“朋友不可以访问我儿子的私有物”:

// 类CFrnd 
class CFrnd {
public: 
	void Test1(ABase& a) { 
        cout <<a.m_a <<endl;  //类CFrnd是类ABase的友元类,所以可以访问
    } 
	void Test2(BDrived& b) 
	{
		cout << b.m_a<<endl;  
		//cout <<b.m_b<<endl; //m_c是BDrived的私有成员,类CFrnd不可访问,编译错误
	} 
}; 

②设计类DFrnd继承CFrnd,在DFrnd的成员函数中测试访问类ABase的成员变量m_a,类BDrived的成员变量m_a,m_b:

基类的友元类的派生类不能访问基类——“朋友的儿子不是我的朋友”:

基类的友元类的派生类不能访问基类的派生类——“朋友的儿子不是我儿子的朋友”:

class DFrnd:public CFrnd {
public: 
	void Test1(ABase& a) { 
        //cout <<a.m_a <<endl; //编译错误,类CFrnd是类ABase的友元类,但不是类CFrnd的派生类DFrnd的友元类,所以不可以访问
    } 
	void Test2(BDrived& b) 
	{
		//cout <<b.m_a<<endl;  //编译错误,类ABase的派生类BDrived不是友元类CFrnd的派生类DFrnd的友元类,所以不可以访问
		//cout <<b.m_b<<endl;  //编译错误,m_c是BDrived的私有成员,类DFrnd不可访问
	} 
};

3.3 友元的总结

  • 类 B 是类 A 的友元类,则A可访问B中所有成员函数和变量。
  • 友元类不能继承,类 B 是类 A 的友元类,类A的派生类不是类B的友元类——朋友的儿子不是我的朋友
  • 友元的关系是单向的而不是双向的。如果声明了类 B 是类 A 的友元类,不等于类 A 是类 B 的友元类,类 A 中的成员函数不能访问类 B 中的 private 成员。
  • 友元的关系不能传递。如果类 B 是类 A 的友元类,类 C 是类 B 的友元类,不等于类 C 是类 A 的友元类——朋友的朋友不会是朋友

四、多态的知识总结

多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。C++多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。

多态的实现条件:

  • 必须是公有继承;
  • 必须是通过基类的指针或引用,指向派生类对象访问派生类方法;
  • 基类的方法必须是虚函数,且完成了虚函数的重写。

4.1 一般多态性函数

 虚函数对于多态具有决定性的作用,有虚函数才能构成多态。

  • 只需要在虚函数的声明处加上 virtual 关键字,函数定义处可以加也可以不加。
  • 为了方便,可以只将基类中的函数声明为虚函数,这样所有派生类中具有遮蔽(覆盖)关系的同名函数都将自动成为虚函数。
  • 当在基类中定义了虚函数时,如果派生类没有定义新的函数来遮蔽此函数,那么将使用基类的虚函数。
  • 只有派生类的虚函数遮蔽基类的虚函数(函数原型相同)才能构成多态(通过基类指针访问派生类函数)。例如基类虚函数的原型为virtual void func();派生类虚函数的原型为virtual void func(int);当基类指针 p 指向派生类对象时,语句p -> func(100),将会出错;而语句p -> func(),将调用基类的函数。
  • 构造函数不能是虚函数。对于基类的构造函数,它仅仅是在派生类构造函数中被调用,这种机制不同于继承。也就是说,派生类不继承基类的构造函数,将构造函数声明为虚函数没有什么意义。
  • 析构函数可以声明为虚函数,而且有时候必须要声明为虚函数。
#include <iostream>
using namespace std;

// 基类Shape ———— 形状
class Shape {
   protected:
      int width, height;    //长宽
   public:
      Shape(int a=0, int b=0){
         width = a;
         height = b;
      }
      virtual int area(){  //虚函数,virtual 关键字
         return 0;
      } 
};

// 派生类Rectangle ———— 矩形
class Rectangle: public Shape{
   public:
      Rectangle(int a=0, int b=0):Shape(a, b){ }
      int area(){                      //虚函数的重写
         return (width * height);      //矩形面积
      }
};

// 派生类Triangle ———— 三角形
class Triangle: public Shape{
   public:
      Triangle(int a=0, int b=0):Shape(a, b){ }
      int area(){                      //虚函数的重写
         return (width * height / 2);  //三角形面积
      }
};
int main(){
    Shape shape(0,0);
    Rectangle rectangle(10,7);
    Triangle  triangle(10,10);

    cout<<"矩形面积为:"<<rectangle.area()<<endl;         //调用矩形的求面积函数
    cout<<"三角形形面积为:"<<triangle.area()<<endl;      //调用三角形的求面积函数
    return 0;
}

  

        Virtual是C++ 面向对象机制中很重要的一个关键字。基类中加了Virtual关键字的函数就是虚拟函数(例如函数area),于是在基类的派生类中就可以通过重写虚拟函数来实现对基类虚拟函数的覆盖。当基类指针指向派生类的对象时,对指针的area函数的调用实际上是调用了派生类的print函数而不是基类的print函数。这是面向对象中的多态性的体现。

4.2 纯虚函数

纯虚函数没有函数体,只有函数声明,在虚函数声明的结尾加上=0,表明此函数为纯虚函数

包含纯虚函数的类称为抽象类(Abstract Class)。之所以说它抽象,是因为它无法实例化,也就是无法创建对象。原因很明显,纯虚函数没有函数体,不是完整的函数,无法调用,也无法为其分配内存空间。

抽象类通常是作为基类,让派生类去实现纯虚函数。派生类必须实现纯虚函数才能被实例化。

4.3 特殊多态性函数

输入输出参数在子类中是父类的指针或基类的引用,在子类中对于的是子类的指针或子类的引用:

// 基类Shape ———— 形状
class Shape {
   protected:
      int width, height;
   public:
      Shape(int a=0, int b=0){
         width = a;
         height = b;
      }
      virtual int area(){ //虚函数
          return 0;
      }
      void showArea(Shape* shape){     //通过指针来访问虚函数
         cout<<"面积为:"<<shape->area()<<endl;
      }
      void showArea(Shape& shape){     //通过引用来访问虚函数
         cout<<"面积为:"<<shape.area()<<endl;
      }
};

// 派生类Rectangle ———— 矩形
class Rectangle: public Shape{
   public:
      Rectangle(int a=0, int b=0):Shape(a, b){}//派生类要使用基类的构造函数,须要在构造函数中显式声明
      int area(){                      //虚函数的重写
         return (width * height);      //矩形面积
      }

};

// 派生类Triangle ———— 三角形
class Triangle: public Shape{
   public:
      Triangle(int a=0, int b=0):Shape(a, b){ }
      int area(){                      //虚函数的重写
         return (width * height / 2);  //三角形面积
      }
};
int main(){
    Rectangle rectangle(10,7);      //定义rectangle类
    Triangle  triangle(10,10);      //定义triangle类

    rectangle.showArea(&rectangle); //调用方法,测试特殊多态函数
    triangle.showArea(triangle);    
    return 0;
}

  


4.3 多继承

什么情况需要虚继承:虚继承是解决 C++ 多重继承问题的一种手段,从不同途径继承来的同一基类,会在子类中存在多份拷贝。这将存在两个问题:第一,浪费存储空间;第二,存在二义性问题。针对这种情况,C++ 提供虚基类的方法,使得在继承间接共同基类时只保留一份成员。

// 基类CAnimal
class CAnimal{
public:
    CAnimal(){}
    CAnimal(int nLeg){
        m_nLegs = nLeg;
    }
    virtual void Move(){
        cout<<"Animal move."<<endl;
    }
protected:
    int m_nLegs;
};

// 派生类CCat
class CCat:virtual public CAnimal
{
public:
    CCat(){}
    CCat(int nLegs):CAnimal(nLegs){}
    void Move(){
        cout<<"Cat move."<<endl;
    }
};

// 派生类CEagle
class CEagle:virtual public CAnimal
{
public:
    CEagle(){}
    CEagle(int nLegs):CAnimal(nLegs){}
    void Move(){
        cout<<"Eagle move."<<endl;
    }
};

// 派生类COwl,多继承
class COwl : public CCat, public CEagle
{
public:
    COwl(int nLegs):CCat(){}
    void Move(){
        cout<<"COwl move."<<endl;
    }
};

 

补充:关于继承和构造函数

若一个类提供构造函数,则该类就不提供默认的构造函数。
派生类会默认调用基类的无参构造函数。
若基类只有有参的构造函数,而派生类只有无参的构造函数(且不存在默认的参数)会报错。

4.4 析构函数的多态性

用基类指针指向子类的对象,当delete只调用父类的析构函数,不会执行子类的析构函数

解决办法: 把父类中的析构函数写成虚函数,即增加关键字virtual,建虚表

// 基类CAnimal
class CAnimal{
public:
    CAnimal(){}
    CAnimal(int nLeg){
        m_nLegs = nLeg;
    }
    virtual ~CAnimal(){
         cout<<"Animal: ~CAnimal()"<<endl;
    }
    virtual void Move(){
        cout<<"Animal move."<<endl;
    }
protected:
    int m_nLegs;
};

// 派生类CCat
class CCat:virtual public CAnimal
{
public:
    CCat(){}
    CCat(int nLegs):CAnimal(nLegs){}
    ~CCat(){
        cout<<"Cat: ~CCat()"<<endl;
    }
    void Move(){
        cout<<"Cat move."<<endl;
    }
};

// 派生类CEagle
class CEagle:virtual public CAnimal
{
public:
    CEagle(){}
    CEagle(int nLegs):CAnimal(nLegs){}
    ~CEagle(){
        cout<<"CEagle: ~CEagle()"<<endl;
    }
    void Move(){
        cout<<"Eagle move."<<endl;
    }
};

// 派生类COwl,多继承
class COwl : public CCat, public CEagle
{
public:
    COwl(int nLegs):CCat(){}
    ~COwl(){
        cout<<"COwl: ~COwl()"<<endl;
    }
    void Move(){
        cout<<"COwl move."<<endl;
    }
};

测试:

int main(){
    COwl owl(2);
    return 0;
}

 可以看出析构函数和构造函数的顺序是向反的,析构函数的调用是从子类到父类。

五、多态性综合运用

设计矢量图,运用多继承设计组合图形,要求具备创建不同类型矢量图、选择图形、移动图形、用不同颜色显示图形(表示选中与否),用vector或数组管理图形。

实现三种以上图形类,其中至少有一种是组合图形
实现创建不同图形、绘制图形、选择图形、移动图形、复制图形
选中的图形请用不同颜色表示
// ShapeDll ———— shapedll.h

#ifndef SHAPEDLL_H
#define SHAPEDLL_H

#include "ShapeDll_global.h"
#include <iostream>
#include <QPainter>
#include <math.h>
#include <vector>
using namespace std;

class SHAPEDLL_EXPORT ShapeDll{
public:
    ShapeDll();
};

class CPoint;      //点
class CRectangle;  //矩形
class Triangle;    //三角形

// 基类CShape ———— 形状
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 CRectangle& rc) const;   //判断是否在矩形内
    virtual void Draw(QPainter& painter) const;        //绘制图形
    virtual CShape* Clone() const;                     //复制图形
    virtual CShape& Move(int nOffsetX, int nOffsetY);  //移动图形
protected:
    string m_strName;               //名称
};

// 派生类CPoint ———— 点
class SHAPEDLL_EXPORT CPoint:public CShape{
public:
    int m_nPosX;      //x坐标
    int m_nPosY;      //y坐标
    CPoint(){}                               //无参构造函数
    CPoint(int nPosX, int nPosY);            //有参构造函数
    CPoint(const CPoint& pt);                //拷贝构造函数
    virtual ~CPoint();                       //析构函数
    double GetArea() const;                  //获取面积
    bool ptIn(const CPoint& pt) const;       //判断是否在点内
    bool inRect(const CRectangle& rc) const; //判断是否在矩形内
    CPoint* Clone() const;                   //复制图形
    CPoint& Move(int nOffsetX, int nOffsetY);//移动图形
    void Draw(QPainter& painter) const;      //绘制图形
};

// 派生类CRectangle ———— 矩形
class SHAPEDLL_EXPORT CRectangle:public CShape{
public:
    CPoint m_ptLT;                                    //左上坐标
    CPoint m_ptBR;                                    //右下坐标
    CRectangle(const CPoint& pt1, const CPoint& pt2); //有参构造函数
    CRectangle(const CRectangle& rc);                 //拷贝构造函数
    virtual ~CRectangle();                            //析构函数
    double GetArea() const;                           //获取面积
    bool ptIn(const CPoint& pt) const;                //判断点是否在内
    bool inRect(const CRectangle& rc) const;          //判断是否在矩形内
    void Draw(QPainter& painter) const;               //绘制图形
    CRectangle* Clone() const;                        //复制图形
    CRectangle& Move(int nOffsetX, int nOffsetY);     //移动图形
};

// 派生类Triangle ———— 三角形
class SHAPEDLL_EXPORT Triangle:public CShape{
public:
    CPoint m_pt[3];                              //三角形的三个顶点坐标
    Triangle(const CPoint& pt1, const CPoint& pt2, const CPoint& pt3);//有参构造函数
    Triangle(const Triangle& rc);                //拷贝构造函数
    virtual ~Triangle();                         //析构函数
    double GetArea() const;                      //获取面积
    bool ptIn(const CPoint& pt) const;           //判断点是否在内
    bool inRect(const CRectangle& rc) const;     //判断是否在矩形内
    void Draw(QPainter& painter) const;          //绘制图形
    Triangle* Clone() const;                     //复制图形
    Triangle& Move(int nOffsetX, int nOffsetY);  //移动图形
};

// 派生类CRectPoint ———— 矩形加点
class SHAPEDLL_EXPORT CRectPoint:public CRectangle, public CPoint{
public:
    CRectPoint(const CPoint& pt1, const CPoint& pt2);
    CRectPoint(const CRectPoint& rc);
    virtual ~CRectPoint();
    double GetArea() const;
    bool ptIn(const CPoint& pt) const;
    bool inRect(const CRectangle& rc) const;
    void Draw(QPainter& painter) const;
    CRectPoint* Clone() const;
    CRectPoint& Move(int nOffsetX, int nOffsetY);
};

// 图形管理器
class ShapeManager{
public:
    ShapeManager(){}
    ~ShapeManager();
    void AddShape(CShape* pShape);          //添加图形
    void DelShape(CShape* pShape);          //删除图形
    void AddShape(vector<CShape*> pShapes); //批量添加图形
    void DelShape(vector<CShape*> pShapes); //批量删除图形
    void DelALL();                          //清空

    CShape* ptIn(const CPoint& pt) const;
    bool InRect(const CRectangle& rc, vector<CShape*> pShapes);       
    void Draw(QPainter& painter, vector<CShape*> pShapes);              //批量绘制
    void Clone(vector<CShape*> pShapesIn, vector<CShape*> pShapesOut);  //批量复制
    void Move(int nOffsetX, int nOffsetY, vector<CShape*> pShapesIn);   //批量移动
private:
    vector<CShape*> m_pShapes;
};

#endif // SHAPEDLL_H
// ShapeDll ———— shapedll.cpp

#include "shapedll.h"

ShapeDll::ShapeDll(){}


// 图形
CShape::CShape():m_strName(NULL){}
CShape::CShape(const CShape& shape):m_strName(shape.m_strName){
    *this = shape;
}
CShape::~CShape(){}
// 面积为0
double CShape::GetArea() const{
    return 0;
}
bool CShape::ptIn(const CPoint& pt) const{
    return false;
}
bool CShape::inRect(const CRectangle& rc) const{
    return false;
}
CShape* CShape::Clone() const{
    return NULL;
}
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(){
    m_nPosX = 0;
    m_nPosY = 0;
}
double CPoint::GetArea() const{
    return 0;
}
bool CPoint::ptIn(const CPoint& pt) const{  //判断鼠标点命中
    return ((pt.m_nPosX-m_nPosX)*(pt.m_nPosX-m_nPosX) + (pt.m_nPosY-m_nPosY)*(pt.m_nPosY-m_nPosY) < 4);
}
bool CPoint::inRect(const CRectangle& rc) const{   //点是否在矩形内
    return (m_nPosX>rc.m_ptLT.m_nPosX && m_nPosX<rc.m_ptBR.m_nPosX && m_nPosY<rc.m_ptLT.m_nPosY && m_nPosY>rc.m_ptBR.m_nPosX);
}
CPoint* CPoint::Clone() const{
    return new CPoint(*this);
    //return new CPoint(m_nPosX, m_nPosY);
}
CPoint& CPoint::Move(int nOffsetX, int nOffsetY){
    m_nPosX += nOffsetX;
    m_nPosY += nOffsetY;
    return *this;
}

void CPoint::Draw(QPainter& painter) const{
    painter.drawPoint(m_nPosX, m_nPosY);
}

// 矩形
CRectangle::CRectangle(const CPoint& pt1, const CPoint& pt2){
    m_ptLT = pt1;   
    m_ptBR = pt2;
}
CRectangle::CRectangle(const CRectangle& rc){
    m_ptLT = rc.m_ptLT;
    m_ptBR = rc.m_ptBR;
}
CRectangle::~CRectangle(){
    m_ptLT.m_nPosX = m_ptLT.m_nPosY = 0;
    m_ptBR.m_nPosX = m_ptBR.m_nPosY = 0;
}
double CRectangle::GetArea() const{
    return (m_ptBR.m_nPosX - m_ptLT.m_nPosX)*(m_ptLT.m_nPosY - m_ptBR.m_nPosY);
}
bool CRectangle::ptIn(const CPoint& pt) const{
    return (pt.m_nPosX>m_ptLT.m_nPosX && pt.m_nPosX<m_ptBR.m_nPosX && pt.m_nPosY<m_ptLT.m_nPosY && pt.m_nPosY>m_ptBR.m_nPosX);
}

bool CRectangle::inRect(const CRectangle& rc) const{
     return (rc.m_ptLT.m_nPosX>m_ptLT.m_nPosX && rc.m_ptBR.m_nPosX<m_ptBR.m_nPosX && rc.m_ptLT.m_nPosY<m_ptLT.m_nPosY && rc.m_ptBR.m_nPosY>m_ptBR.m_nPosX);
}
void CRectangle::Draw(QPainter& painter) const{
    painter.drawRect(m_ptLT.m_nPosX, m_ptLT.m_nPosY, m_ptBR.m_nPosX-m_ptLT.m_nPosX, m_ptLT.m_nPosY-m_ptBR.m_nPosY);
}
CRectangle* CRectangle::Clone() const{
    return new CRectangle(*this);
}
CRectangle& CRectangle::Move(int nOffsetX, int nOffsetY){
    m_ptLT.m_nPosX += nOffsetX;
    m_ptLT.m_nPosY += nOffsetY;
    m_ptBR.m_nPosX += nOffsetX;
    m_ptBR.m_nPosY += nOffsetY;
    return *this;
}

// 三角形
Triangle::Triangle(const CPoint& pt1, const CPoint& pt2, const CPoint& pt3){
    m_pt[0] = pt1;
    m_pt[1] = pt2;
    m_pt[2] = pt3;
}
Triangle::Triangle(const Triangle& rc){
    m_pt[0] = rc.m_pt[0];
    m_pt[1] = rc.m_pt[1];
    m_pt[2] = rc.m_pt[2];
}
Triangle::~Triangle(){
    m_pt[0].m_nPosX = m_pt[0].m_nPosY = 0;
    m_pt[1].m_nPosX = m_pt[1].m_nPosY = 0;
    m_pt[2].m_nPosX = m_pt[2].m_nPosY = 0;
}
//海伦公式计算三角形面积
double TriangleArea(const CPoint& pt1, const CPoint& pt2, const CPoint& pt3){
    double AB, BC, AC, P;
    AB = sqrt(pow(pt2.m_nPosX - pt1.m_nPosX,2)+ pow(pt2.m_nPosY - pt1.m_nPosY,2));
    AC = sqrt(pow(pt3.m_nPosX - pt1.m_nPosX,2)+ pow(pt3.m_nPosY - pt1.m_nPosY,2));
    BC = sqrt(pow(pt3.m_nPosX - pt2.m_nPosX,2)+ pow(pt3.m_nPosY - pt2.m_nPosY,2));
    P = (AB + AC + BC) / 2;
    return sqrt(P*(P-AB)*(P-AC)*(P-BC));
}

double Triangle::GetArea() const{
    return  TriangleArea(m_pt[0], m_pt[1], m_pt[2]);
}
bool Triangle::ptIn(const CPoint& pt) const{
    float S1, S2, S3, Ssum;
        S1 = TriangleArea(m_pt[0], m_pt[1], pt);
        S2 = TriangleArea(m_pt[0], m_pt[2], pt);
        S3 = TriangleArea(m_pt[1], m_pt[2], pt);
        Ssum = TriangleArea(m_pt[0], m_pt[1], m_pt[2]);
        if (0.0001 > fabs(Ssum - S1 - S2 - S3))//注意绝对值
            return true;
        else
            return false;
}
bool Triangle::inRect(const CRectangle& rc) const{

}
void Triangle::Draw(QPainter& painter) const{
    QPolygon polygon;
    polygon.setPoints(m_pt[0].m_nPosX, m_pt[0].m_nPosY,m_pt[1].m_nPosX, m_pt[1].m_nPosY,m_pt[2].m_nPosX, m_pt[2].m_nPosY);
    painter.drawPolygon(polygon);
}
Triangle* Triangle::Clone() const{
    return new Triangle(*this);
}
Triangle& Triangle::Move(int nOffsetX, int nOffsetY){
    m_pt[0].m_nPosX += nOffsetX;
    m_pt[0].m_nPosY += nOffsetY;
    m_pt[1].m_nPosX += nOffsetX;
    m_pt[1].m_nPosY += nOffsetY;
    m_pt[2].m_nPosX += nOffsetX;
    m_pt[2].m_nPosY += nOffsetY;
    return *this;
}

// 矩形加点
CRectPoint::CRectPoint(const CPoint& pt1, const CPoint& pt2):
    CRectangle(pt1, pt2),
    CPoint((pt1.m_nPosX + pt1.m_nPosX)/2,(pt1.m_nPosY + pt1.m_nPosY)/2){
}

CRectPoint::CRectPoint(const CRectPoint& rc):
    CRectangle(rc.m_ptLT,rc.m_ptBR),
    CPoint(rc.m_nPosX, rc.m_ptBR.m_nPosY){
}

CRectPoint::~CRectPoint(){
    m_nPosX = 0;
    m_nPosY = 0;
    m_ptLT.m_nPosX = m_ptLT.m_nPosY = 0;
    m_ptBR.m_nPosX = m_ptBR.m_nPosY = 0;
}
double CRectPoint::GetArea() const{
    return CRectangle::GetArea();
}
bool CRectPoint::ptIn(const CPoint& pt) const{
    return CRectangle::ptIn(pt);
}
bool CRectPoint::inRect(const CRectangle& rc) const{
    return CRectangle::inRect(rc);
}
void CRectPoint::Draw(QPainter& painter) const{
    CRectangle::Draw(painter);
    CPoint::Draw(painter);
}
CRectPoint* CRectPoint::Clone() const{
    return new CRectPoint(*this);
}
CRectPoint& CRectPoint::Move(int nOffsetX, int nOffsetY){
    CRectangle::Move(nOffsetX,nOffsetY);
    CPoint::Move(nOffsetX,nOffsetY);
}

// 图形管理器
ShapeManager::~ShapeManager(){
    delete this;
}

// 增添
void ShapeManager::AddShape(CShape* pShape){
    m_pShapes.push_back(pShape);
}

// 删除
void ShapeManager::DelShape(CShape* pShape){
    for(vector<CShape*>::iterator it = m_pShapes.begin();it != m_pShapes.end();it++){
        if((*it) == pShape){
            delete pShape;
            m_pShapes.erase(it);
            break;
        }
    }
}
//批量添加图形
void ShapeManager::AddShape(vector<CShape*> pShapes){
    for(vector<CShape*>::iterator it= pShapes.begin();it != pShapes.end();it++){
       AddShape(*it);
    }
    
} 
//批量删除图形   
void ShapeManager::DelShape(vector<CShape*> pShapes){ 
    for(vector<CShape*>::iterator it = pShapes.begin();it != pShapes.end();it++){
        DelShape(*it);
    }
}
//清空
void ShapeManager::DelALL(){                         
    for(vector<CShape*>::iterator it = m_pShapes.begin();it != m_pShapes.end();it++){
            m_pShapes.erase(it);
            delete *it;
    }
}
 
}
//判断是否在矩形内
bool ShapeManager::InRect(const CRectangle& rc, vector<CShape*> pShapes){
    for(vector<CShape*>::iterator it = pShapes.begin();it != pShapes.end();it++){
        if(!(*it)->inRect(rc)){
            return false;
            break;
        }
    }
    return true;
}
//批量绘制       
void ShapeManager::Draw(QPainter& painter, vector<CShape*> pShapes){             
    for(vector<CShape*>::iterator it = pShapes.begin();it != pShapes.end();it++){
        (*it)->Draw(painter);
    }
}    
//批量复制
void ShapeManager::Clone(vector<CShape*> pShapesIn, vector<CShape*> pShapesOut){
    for(vector<CShape*>::iterator it = pShapesIn.begin();it != pShapesIn.end();it++){
        pShapesOut.push_back((*it)->Clone());
    } 
}
//批量移动
void ShapeManager::Move(int nOffsetX, int nOffsetY, vector<CShape*> pShapesIn){
    for(vector<CShape*>::iterator it = pShapesIn.begin();it != pShapesIn.end();it++){
        (*it)->Move(nOffsetX,nOffsetY);
    } 
}
// 简单实现绘图功能

// MainApp ———— mainwindow.h
#include <QMainWindow>
#include <QtGui>
#include <QActionGroup>
#include <QAction>
#include "shape.h"
#include "paintwidget.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
signals:
    void changeCurrentShape(Shape::Code newShape);
private slots: 
    void drawLineActionTriggered();  //画直线
    void drawRectActionTriggered();  //画矩形
    void drawTricActionTriggered();  //画三角形
    void drawRePtActionTriggered();  //画矩形点
private:
    Ui::MainWindow *ui;
};

//MainApp ———— mainwindow.cpp
#include "mainwindow.h"
#include "paintwidget.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    QToolBar *toolBar = this->addToolBar("Tools");  //创建工具栏

    QActionGroup *group = new QActionGroup(this);
    QAction *actionLine = group->addAction("直线");   //画直线动作
    QAction *actionRect = group->addAction("矩形");   //矩形线动作
    QAction *actionTrai = group->addAction("三角形"); //三角形线动作
    QAction *actionRePt = group->addAction("矩形点"); //矩形点线动作
    ui->menubar->addAction(actionLine);    //加入菜单栏
    ui->menubar->addAction(actionRect);
    ui->menubar->addAction(actionTrai);
    ui->menubar->addAction(actionRePt);

    actionLine->setCheckable(true);
    actionLine->setChecked(true);
    actionRect->setCheckable(true);
    actionRect->setChecked(true);
    actionTrai->setCheckable(true);
    actionTrai->setChecked(true);
    actionRePt->setCheckable(true);
    actionRePt->setChecked(true);

    // 创建画板
    PaintWidget *paintWidget = new PaintWidget(this); 
    setCentralWidget(paintWidget);

    connect(actionLine, SIGNAL(triggered()),this, SLOT(drawLineActionTriggered()));
    connect(actionRect, SIGNAL(triggered()),this, SLOT(drawRectActionTriggered()));
    connect(this, SIGNAL(changeCurrentShape(Shape::Code)),paintWidget, SLOT(setCurrentShape(Shape::Code)));
}

MainWindow::~MainWindow(){
    delete ui;
}

void MainWindow::drawLineActionTriggered(){
        emit changeCurrentShape(Shape::Line);
}

void MainWindow::drawRectActionTriggered(){
        emit changeCurrentShape(Shape::Rect);
}

void MainWindow::drawTricActionTriggered(){
    emit changeCurrentShape(CShape::Tria);
}

void MainWindow::drawRePtActionTriggered(){
    emit changeCurrentShape(CShape::RePt);
}
//MainApp ———— paintwidget.h
#include <QMainWindow>
#include "shape.h"
class PaintWidget : public QWidget{
        Q_OBJECT
public:
        PaintWidget(QWidget *parent = 0);

public slots:
        void setCurrentShape(Shape::Code s){
            if(s != currShapeCode) {
                    currShapeCode = s;
            }
        }
protected:
        void paintEvent(QPaintEvent *event);         //绘画事件 
        void mousePressEvent(QMouseEvent *event);    //鼠标按压事件
        void mouseMoveEvent(QMouseEvent *event);     //鼠标移动事件
        void mouseReleaseEvent(QMouseEvent *event);  //鼠标释放事件
private:
        Shape::Code currShapeCode;  
        Shape *shape;               //图形
        bool perm;                  //标志
        QList<Shape*> shapeList;    //列表
};

//MainApp ———— paintwidget.cpp
#include "paintwidget.h"

// 构造函数
PaintWidget::PaintWidget(QWidget *parent)
        : QWidget(parent), currShapeCode(Shape::Line), shape(NULL), perm(false)
{
        setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
}

// 绘制
void PaintWidget::paintEvent(QPaintEvent *event){
        QPainter painter(this);
        painter.setBrush(Qt::white);
        painter.drawRect(0, 0, size().width(), size().height());
        foreach(Shape * shape, shapeList) {
                shape->paint(painter);
        }
        if(shape) {
                shape->paint(painter);
        }
}

// 鼠标按压
void PaintWidget::mousePressEvent(QMouseEvent *event){
        switch(currShapeCode){
        case Shape::Line:
                {
                        shape = new Line;
                        break;
                }
        case Shape::Rect:
                {
                        shape = new Rect;
                        break;
                }
        }
        if(shape != NULL) {
                perm = false;
                shapeList<<shape;
                shape->setStart(event->pos());
                shape->setEnd(event->pos());
        }
}

// 鼠标移动
void PaintWidget::mouseMoveEvent(QMouseEvent *event){
        if(shape && !perm) {
                shape->setEnd(event->pos());
                update();
        }
}

// 鼠标释放
void PaintWidget::mouseReleaseEvent(QMouseEvent *event){
        perm = true;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

DreamWendy

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

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

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

打赏作者

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

抵扣说明:

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

余额充值