第九节 面向对象程序设计

1. 面向过程程序设计:以数据流为基础,模块化系统设计,以寻找问题解决过程为主题的设计方式;数据结构是算法的辅助

复制代码
// 面向过程的程序设计: 分析问题,解决问题
// 分析问题:将大问题拆分为子问题,分析出问题所解决的事情,问题给出的条件,以及问题解决后需要的反馈
// 解决问题:设计具体的步骤,解决问题

// 对学生成绩进行排序
// 输入:学生的成绩,学生的信息
// 输出:排序后的学生成绩
struct StudentInfo
{
    int StudentID;
    float Score;
};

// 解决问题,设计算法
// 起泡排序
void BubbleSort (StudentInfo students[], int len) {
    StudentInfo temp;
    for (int i = 0; i < len - 1; i++)
        for (int j = 0; j < len - 1 - i; j++) {
            if (students[j].Score > students[j + 1].Score) {
                temp = students[j];
                students[j] = students[j + 1];
                students[j + 1] = temp;
            }
        }
}

// 完成汉诺塔设计(将盘子从A移动到C)
// 输入:起始条件,A柱上盘子的数量n
// 输出:所有盘子移动到C柱上
// 解决问题,设计算法
// 把n-1个盘子由 A 移到 B
// 把第n个盘子由 A 移到 C
// 把n-1个盘子由 B 移到 C
struct Pillar {};
void Move(Pillar from, Pillar to);
void Hanoi(int n, Pillar A, Pillar B, Pillar C) {
    if (n == 1)
        Move(A, C);
    else {
        Hanoi(n - 1, A, C, B);
        Move(A, C);
        Hanoi(n - 1, B, A, C);
    }
}

// 设计学生管理系统
// 问题分解:学生管理、课程管理、成绩管理
// 问题分解_学生管理:学生入学,学生信息录入
// 问题分解_课程管理:创建课程,选课
// 问题分解_成绩管理:成绩录入
struct Student;
struct Course;

void CreateStudent();
void CreateCourse();
void InputStudentInformation();
void AddCourse();
void InputCourseMark();
//...


// 完成汽车自动化生产
// 问题分解:材料准备、汽车组装、汽车测试
// 问题分解_汽车测试:测试刹车、测试发动机、测试反光镜、测试水箱....
// 解决问题:测试刹车
// 输入:刹车信号
// 输出:汽车停止时间
// 解决问题:测试发送机
// 输入:油门信号
// 输出:百米百公里加速时间
// .......
复制代码

 

2. 面向对象程序设计:以对象(Object)为基础设计系统;通过为对象添加属性(变量)、功能(方法),并研究对象之间的关系,从而解决问题

复制代码
//以面向对象的设计方式,设计学生管理系统
//1. 明确程序中的对象
//2. 明确不同对象间的联系
//3. 为对象设计属性与方法

class Student
{
private:
    Course* Courses;
    //...Informations
    
public:
    void AddCourse();
    //Getter/Setters for Informations
    //...

    //Create
    Student();
    //Dispose
    ~Student();
};

class Course
{
private:
    double _mark;
    //...Course Informations
public:
    double GetMark();
    double SetMark();
    //Getter/Setters for Course Informations
    //...

    //Create
    Course();
    //Dispose
    ~Course();
};
复制代码

 

3. 类与数据抽象化

 

复制代码
#include <iostream>
#include <string>
#include <list>

using namespace std;

// 在进行面向过程的(模块化)的系统设计时,根据算法设计合适的结构体(数据集合)
// 此时,数据与函数是分离的关系
struct StudentInfo
{
    string Name;
    int Age;
    string Gender;
    string Major;
    string Department;
};

void PrintStudentInfo();
void GetStudentInfoByMajor(string major);
StudentInfo* OrderStudentByAge();

// 在进行面向对象设计时,首先要做的就是对数据进行抽象
// 在抽象的过程中,发现系统中的对象(类),及对象(类)之间的关系
// 以对象(类)及关系(类关系)为基础,为每一个对象(类)设计属性与方法

// 类与对象的区别
// 类是对象的抽象描述,如停车场中的每一辆车是一个具体的对象,统称为汽车类
// 在座的每一个学生是一个具体的对象,统称为学生类

// 以教务系统为例,进行面向对象分析
// 系统场景分析:学生会选课、查看课表、查看课程成绩;教师会录入课程成绩、教授课程
// 系统中三个基本的对象:学生、教师、课程
class Student;
class Professor;
class Course;
//...

//继续为每一个类设计属性与基本方法(和自身有关)
class Student
{
    // 学生所具有的属性
    string Name;
    string ID;
    string BirthDate;
    string Gender;
    string Address;
    string Contact;

    list<Course> Courses;
    list<string> Awards;
    list<string> Punishment;
    //...

    // 学生能够实现的功能
    Student();
    ~Student();

    // 打印成绩单
    Course* PrintCourseReport();
    // 打印在读证明
    void PrintEnrollingCertificate();
    // 添加获奖记录
    void AddAword();
    //...
};

// 继续设计不同类之间的关系,学生与教师的关系,学生与课程关系
// 在设计不同类之间的关系时,可以设定访问权限
// 权限符号包括:
// public:后续内容全部公开,任何其他类都可以访问
// protect:后续内容,根据其他类与本类的关系选择性公开
// private:后续内容对其他任何类都不公开
class Car
{
public:
    string Brand;
    string Price;
    void Move();
    void Stop();

protected:
    string MotoID; // 司机和维修人员可以查看发动机ID
    void OpenCarFrontCover(); // 司机和维修人员可以打开,其他人不可以
private:
    string DiverInfo; // 只有司机可以查看
    void DestroyCar();
};

// 重新设计,带有访问权限的学生类
class Student
{
private:
    string _name;
    string _id;
    string _birthDate;
    string _gender;
    string _address;
    string _contact;
    list<string> _awards;
    list<string> _punishment;
    list<Course> _courses;

    //...

public: // 可以通过Getter和Setter方法,为每一个属性设定读写权限
    //Getter/Setters
    string GetName(){ return _name }; // 姓名,只读
    void SetContact(string value) { _contact = value;} // 联系方式,可读写
    string GetContact() { return _contact; }
    //...

    Student();
    ~Student();

private: // 打印成绩单和在读证明的功能只有学生自己使用
    Course* PrintCourseReport();
    void PrintEnrollingCertificate();
    
public: // 班主任也可以访问
    void AddAword();
    void AddCourse(Course*);
    void IsEnrolledCourse(Course*);
    //...
};
复制代码

 

 

4. 通过重载与继承实现多态化

 

复制代码
// 考虑教师和学生类
class Student
{
public:
    string Name;
    string Age;
    string Gender;

    string Group; // 班级
};

class Professor
{
public:
    string Name;
    string Age;
    string Gender;

    string Level; // 职称
};

// 可以看到学生与老师既有相同的属性,也有不同的差异属性
// 姓名、年龄、性别是对于任何人都具有的特点,而班级只有学生才有,职称只有老师才有
// 在进行数据抽象时,可以继续进行抽象,设置基类(Base Class)

class Staff // Base Class 基类(父类)
{
public:
    string Name;
    string Age;
    string Gender;
    string ID;
};

// Child Class 子类
class Student : public Staff
{
public:
    string Group;
};

class Professor : public Staff
{
public:
    string Level;
};

// 通过继承类的方式,可以让基类在子类中表现出更多的属性
// 除了在子类中扩展属性,也可以在子类中扩展方法
// 通过子类来扩展基类的这种行为,称为面向对象程序的多态性

// 在子类中扩展方法的方式有两种
// 1. 添加新的函数
// 2. 对父类中已存在的函数进行重写,称为重载
class Staff
{
public:
    void Pay() // 在校的职工都具有接收资金的功能
    {
        // 但是不同人员支付的方式不一样,因此在父类中,可以写一个空函数
    }
};

class Student: public Staff {
public:
    void PayScholarship(); // 子类扩展新的方法,学生还可以收到奖学金

    // 与父类同名不同参数列表的函数,称为重载函数
    // 父类中的Pay()函数与这个函数共存
    void Pay(int salaryPerMonth);

    // 与父类中的函数返回值、参数列表、名称完全一样,称为重定义函数
    // 父类中的Pay()函数将被隐藏
    void Pay()
    {
        // 学生每月定期发补助
    }

    // 除了重载方法,还可以重载符号
    // 格式为:返回值 operator 符号 (参数列表)
    int operator +();
    Student operator []();
};

// 基类中有时候只提供需要实现的函数列表,同时在子类中对这些函数进行实现
// 基类中的这些函数被称为虚函数,使用virtual与override关键字实现
class Staff
{
public:
    virtual void Pay();
};

class Student: public Staff
{
public:
    // 重载函数,父类中的函数被隐藏
    void Pay() override
    {

    }
};
复制代码

 

复制代码
#include <iostream>

using namespace std;

namespace Calculate
{
    class Print { };
}

namespace Time
{
    class Print { };
}

namespace Encoding
{
    class Print { };
}

using namespace Encoding;
int main()
{
    Calculate::Print cp;
    Time::Print tp;
    Print ep; // Encoding::Print
    return 0;
}
复制代码

 

5. 通过虚函数与接口实现抽象化

 

复制代码
//传统的面向对象程序设计(C++),会出多重继承
//一个子类可能具有多个父类,这种继承方式很容易造成混乱
//现代面向对象程序设计,通常使用的是单继承类,多继承接口的方式
//接口是只有方法声明,而没有定义的类,接口也不具有属性
//C++中的接口使用虚类来实现,其他高级语言有专用关键字Interface

// 多重继承
class A {};
class B {};
class C {};
class D: public A {};
class E: public D, B {};
class F: public D, B, A{}; // 错误,D已经继承A,多重继承的混乱

// C++中的接口,直一个类中,所有的函数都是虚函数或纯虚函数,纯虚函数就是子类必须实现

// 对于接口,约定不可以有任何属性,并且使用Interface作为类名前缀
// 其他高级语言如C#,JAVA,Python等,都有专门的接口声明关键字interface
class InterfaceA {
    virtual void FuncA() = 0; // 纯虚函数,子类必须实现
    virtual void FuncB(); // 虚函数,子类选择性实现
};

// 对于接口设计方式,允许多级继承,但是约定,不可以多重继承类
class G: A, InterfaceA
{};

class H: G, InterfaceA // 接口会在H类中再次被重写,继承关系为单链,H→G→A
{};

// 有了接口的定义,在进行面向对象设计时,除了对数据进行抽象化,还可以对方法进行抽象化
// 分别对应类设计和接口设计

class Staff
{
public:
    string ID;
    string Name;
    string Gender;
};

class PaymentInterface
{
public:
    virtual void Payment() = 0;
};

//学生不需要发工资,所以不继承接口
class Student : public Staff
{
};

class Professor : public Staff, public PaymentInterface
{
public:
    void Payment() override; // 重载函数,父类中Payment()函数被隐藏
    void Payment(int award, int payment, int project); // 重载子类Payment()函数,与子类的Payment()函数共存
};
复制代码

 

6. 值类型与引用类型

 

复制代码
#include <iostream>

using namespace std;

// C++ 中的指针,引用,与值

class A {
public:
    int Value;
};

void func1(A a, int b)
{
    a.Value = 10;
    b = 10;
}

void func2(A& a, int& b)
{
    a.Value = 20;
    b = 20;
}

void func3(A* a, int* b)
{
    a->Value = 30;
    *b = 30;
}

int main()
{
    // C++ 中的值类型、引用类型与指针类型
    A a; a.Value = 5;
    int b = 5;
    cout << "a = " << a.Value << " ; b = " << b << endl;

    // 值类型在传递时,会开辟一块新的内存空间,并将数据复制过去
    // 参数中,新的变量与旧的变量表示不同的内存数据
    func1(a, b);
    cout << "a = " << a.Value << " ; b = " << b << endl;

    // 引用类型在传递时,不会开辟新的空间
    // 参数中,新的变量与旧的变量指向同一地址
    func2(a, b);
    cout << "a = " << a.Value << " ; b = " << b << endl;

    // 指针类型在传递时,不会开辟新的空间
    // 参数中,指针指向旧的变量所在的内存地址
    func3(&a, &b);
    cout << "a = " << a.Value << " ; b = " << b << endl;
    return 0;

    // 在C++中,引用和指针的意义相似,但是指针的灵活性要比引用高出很多
}


// 现代的纯面向对象语言中,不在具有指针和引用两个操作
// 如,C#,JAVA,Python,Swift等
// 系统会默认根据变量的类型,来选择参数传递时是使用值传递还是引用传递
// 默认情况下,所有的数值类型:int,float,double,bool,char,struct,enum以值传递的方式传递
// 其他所有类型,都以引用的方式进行传递

// 在现代的高级语言中,可以将值类型封装为引用类型,也可以将引用类型拆解为值类型
// 这两个操作被称为:数据的装箱和拆箱
复制代码

 

7. 其他

复制代码
#include <iostream>

using namespace std;

class SampleClass
{
public:
    static int StaticValue;
    int VariableValue;

    static void Func() {}
    void Func1() {}
};

int SampleClass::StaticValue = 0; // 静态变量初始化,必须

int main()
{
    SampleClass a, b;

    a.StaticValue = 10;
    a.VariableValue = 10;

    b.StaticValue = 20;
    b.VariableValue = 20;

    SampleClass::StaticValue = 30;
    SampleClass::Func();
    // SampleClass::Func1();
    a.Func1();

    cout << "a.StaticValue = " << a.StaticValue << " ; a.VariableValue = " << a.VariableValue << endl;
    cout << "b.StaticValue = " << b.StaticValue << " ; b.VariableValue = " << b.VariableValue << endl;
    return 0;
}
复制代码

 

复制代码
#include <iostream>

using namespace std;

// 构造函数、析构函数、拷贝构造函数
class Complex
{
    // z = a + ib
    int a;
    int b;

    // C++ 默认提供三个函数
    // 默认构造函数
    //Complex() {}
    // 默认拷贝构造函数
    /*
    Complex(const Complex &value) {
        // 拷贝所有属性的值
    }
    */
    // 默认析构函数
    //~Complex() {};

    Complex()
    {
        cout << "Constructor" << endl;
    }

    Complex(const Complex &value)
    {
        cout << "Deep Copy Constructor" << endl;
    }

    ~Complex()
    {
        cout << "Destructor" << endl;
    }
};
复制代码
复制代码
#include <iostream>

using namespace std;

// 类型兼容

class Root{
public:
    int RootValue;
};

class Child: public Root
{
    int ChildValue;
};

int main()
{
    Root root;
    Child child;
    Root* ptrRoot;
    Child* ptrchild;
    
    root = (Root)child; // 此时,ptrchild->ChildValue有值,但无法被访问
    child = root; // error
    child = (Child)root; // error
    
    ptrRoot = &root;
    ptrRoot = &child; // 此时,ptrchild->ChildValue有值,但无法被访问

    ptrchild = &child;
    ptrchild = &root; // error
    ptrchild = (Child* )&root; // 此时,ptrchild->ChildValue为随机值
    
    return 0;
}
复制代码
复制代码
// 类只有被实例化(变量)后,才具有内存空间并被赋值
// 但如果在设计类时,就需要访问类属性时,可以使用this指针
// 此外,this指针还可以用来区分同名的变量

class A
{
    int a;
    int b;
    int AddAB;

    A(int a, int b)
    {
        this->a = a;
        this->b = b;
        this->AddAB = this->a + this->b;
    }
};
复制代码

 

8. 作业

实现扫雷游戏,如下图

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @   庞兴庆  阅读(192)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示