跳转至

面向对象程序设计

课程链接:程序设计与算法(三)C++面向对象程序设计_北京大学_中国大学MOOC(慕课) (icourse163.org)

笔记时间:24年寒假

1.从C到CPP

引用

  • const & 不允许通过该引用来修改对象内容
  • const T & 和T & 是不同的类型,不能用const T &来初始化T &
  • 同理,常量指针不能通过其修改指向的内容

动态内存分配

  • new出来的对象必须要delete才会消失,只能delete一次
  • 返回起始地址的指针

Tip

delete [] p;

内联、重载、缺省

  • 避免二义性

2.类和对象基础

类的成员

  • private
  • public
  • protected

  • 缺省认为是私有成员

构造函数 constructor

  • 名字与类名相同,可以有参数,不能有返回值
  • 作用是对对象进行初始化,生成对象时自动调用
  • 没写默认无参构造函数,不做任何操作,如果定义了则默认的函数不存在
  • 一个类可以有多个构造函数

Tip

注意在数组中的使用

复制构造函数

  • 只有一个参数
  • 形如X::X(X&) 或 X::X(const X &)
  • 复制构造函数有三种情况起作用
  • 当用一个对象去初始化另一个对象
  • 一个函数的参数是类A的对象,当该函数调用时
  • 返回值是类A的对象,当函数返回时

Tip

在不同编译器中可能有不同表现

类型转换构造函数

  • 目的是实现类型的自动转换
  • 只有一个参数,且不是复制构造函数,可以看做是转换构造函数

析构函数

  • 名字与类名相同,在前面加一个~没有参数和返回值,一个类最多一个析构函数
  • 在对象消亡时被自动调用,完成善后工作

3.类与对象提高

this指针

  • 作用是指向成员函数所作用的对象
  • 静态成员函数中不能使用this指针,因为静态成员函数并不具体作用于某个对象,因此静态成员函数的真实的参数的个数,就是程序中写出的参数个数

静态成员

  • 访问静态成员
  • 类::成员名
  • 对象名.成员名
  • 指针->成员名
  • 引用.成员名
  • 静态成员变量本质上是全局变量,对象不存在,静态成员变量也存在;静态成员函数是全局函数

  • 静态成员函数,不能访问非静态成员变量,不能调用非静态成员函数。(因为不能确定是哪个象的)

Tip

1. 注意对静态成员变量的初始化; 2. 注意复制构造函数。

成员对象和封闭类

  • 有成员对象的类叫封闭类
  • 任何生成封闭类对象的语句都要让编译器明白,对象中的成员对象,是如何初始化的
  • 初始化列表
  • 先执行所有对象成员的构造函数,再执行封闭类的构造函数,构造函数调用次序和在类中的说明次序一致
  • 封闭类对象消亡事,先执行封闭类的析构函数,再执行成员对象的析构函数,次序和构造函数调用次序相反

常量对象,常量成员函数、常引用

  • 常量成员函数执行期间不能修改其所 作用的对象。因此不能修改成员变量的值(静态除外),也不能调用非常量成员函数(静态除外)
  • 常量对象可以执行常量成员函数
  • 两个成员函数,名字和参数表一样,一个是常量一个不是,则为重载
  • 将对象的常引用作为函数参数,不会触发复制构造函数,也不会修改对象实参的值

友元

  • 友元函数不是成员函数,一个类的友元函数可以访问该类的私有成员
  • A是B的友元类,那么A的成员函数可以访问B的私有成员

4.运算符重载

基本概念

  • 运算符重载实质是函数重载,可以重载为普通函数或者成员函数。把函数运算符的表达式转换成对运算符函数的调用,操作数转换成运算符函数的参数。多次重载时根据实参类型决定调用函数
  • 重载为 成员函数时,参数个数是运算符目数;重载为普通函数时,参数个数是运算符目数

赋值运算符'='重载

  • 只能重载为成员函数

Tip

注意浅拷贝和深拷贝问题

运算符重载为友元函数

  • 一般情况下,将运算符重载为类的成员函数,是较好的选择。
  • 有时需要设置成普通函数,但普通函数又不能访问私有成员,所以需要把运算符重载为友元

可变长数组类的实现

  • 略(vector)

流插入运算符和流提取运算符的重载

  • 由于不能修改iostream里的对象,重载只能重载为全局函数

类型转换运算符的重载

自增自减运算符重载

class CDemo{
    private :
        int n;
    public:
        CDemo(int i=0):n(i){}
        CDemo & operator++();           //前置
        CDemo operator++(int);          //后置
        operator int () {return n;};
    friend CDemop & operator--(CDemo &);
    friend CDemo operator--(CDemo &, int);
};

CDemo & CDemo::operator++()             //前置++
{                                       
    n ++;
    return * this;
}                                       //++s即s.operator++();
CDemo CDemo::operator++(int k)          //后置++
{                                       
    CDemo tmp(*this);
    n++;
    return tmp;//返回修改前的对象
}                                       //s++即s.operator++(0);
CDemo & operator--(CDemo & d)           //前置--
{                                       
    d.n--;
    return d;
}                                       //s--即operator--(s)
CDemo operator--(CDemo & d, int)        //后置--
{                                       
    CDemo tmp(d);
    d.n--;
    return temp;
}                                       //s--即operator--(s,0)

5.继承

继承和派生的基本概念

  • 继承:在定义一个新的类B时,如果该类与某个已有的类A相似(指B拥有A的全部特点),那么就可以把A作为一个基类,而把B作为基类的一个派生类(或子类)。
  • 派生类是通过对积累进行修改和扩充的得到的,在派生类中,可以扩充新的成本员变量和成员函数
  • 派生类一经定义后,可以独立使用,不依赖于基类
  • 派生类拥有基类的全部成员函数和成员变量,在派生类的各个成员函数中,不能访问基类中的private成员
  • 写法:class 类名:public 基类名

继承关系和复合关系

  • 继承:”是“关系

  • 基类A,B是基类A的派生类

  • 逻辑上要求:一个B对象也是一个A对象

  • 复合:“有”关系

  • 类C中成员变量k,k是类D的对象,则C和D是复合关系

  • 逻辑上要求:D对象是C对象的固有属 性或组成部分

  • 人狗问题:

    class Cmaster;
    class Cdog{
        CMaster *pm;
    }
    class CMaster{
        CDog *dogs[10];
    }
    

覆盖和保护成员

  • 派生类中可以定义一个和基类成员同名的成员,叫覆盖。在派生类中访问这类成员,缺省的情况是访问派生类的成员。要在派生类中访问基类定义的同名成员时,要使用作用域符号 ::
  • protected成员:可以被 基类的成员函数,基类的友元函数 访问(同private),另外派生类的成员函数 可以访问 对象的基类的保护成员 访问

派生类的构造函数

  • 创建派生类对象时,要调用基类构造函数,来初始化派生类对象中从基类继承的成员。 总是先执行基类构造函数,再执行派生类构造函数。

  • 调用基类构造函数两种方式

  • 显式方式:在派生类的构造函数中,为积累的构造函数提供参数
  • 隐式方式:在派生类构造函数中,省略积累构造函数时,派生类的构造函数自动调用基类的默认构造函数
  • 派生类的析构函数被执行时。先执行派生类的析构函数,再调用基类的析构函数
  • 封闭派生类的构造函数
  • 先执行基类的构造函数
  • 再执行成员对象类的构造函数
  • 最后执行派生类自己的构造函数
  • 析构函数相反顺序

公有继承的赋值兼容规则

  • 派生类对象可以赋值给基类对象
  • 派生类对象可以初始化基类引用
  • 派生类对象的地址可以赋值给基类指针

继承方式不是上述public则上述三条不成立

  • 直接基类和间接基类
  • 在声明派生类时只需要列出他的直接基类
  • 构造函数执行从最顶层基类开始,一直到当前派生类,析构函数相反

多态

虚函数和多态的基本概念

  • 在类的定义中,前面有virtual关键字的成员函数就是虚函数

  • c++ class base{ virtual int get(); }; int base::get(){ }

  • virtual关键字只用在类定义的函数声明中,写函数体时不用

  • 构造函数和静态成员函数不能是虚函数

  • 多态表现形式一

  • 派生类的指针可以赋值给基类指针

  • 通过基类指针调用基类和派生类中的同名虚函数时

    1. 若该指针指向一个基类的对象,那么被调用是基类的虚函数
    2. 若该指针指向一个派生类的对象,那么被调用的是派生来的虚函数
  • 多态表现形式二

  • 派生类的对象可以赋值给基类引用

  • 通过基类引用调用基类和派生类中的同名虚函数中是

    1. 若该引用引用的是一个基类的多想,那么被调用的是基类的虚函数
    2. 若该引用引用的是一个派生类的对象,那么被调用的是派生类的虚函数
  • 在非构造函数,非析构函数的成员函数中调用虚函数,就是多态;在构造函数和析构函数中调用虚函数,不是多态:编译时就可以确定,调用的函数是自己的类或者基类中定义的函数。

多态的实现原理

  • 多态关键在于通过基类指针或引用调用一个虚函数时,编译时不确定调用的是基类还是派生类的函数,运行时才确定,这叫“动态联编”
  • 虚函数表

虚析构函数、纯虚构函数和抽象类

  • 纯虚函数:无函数体virtual void func() = 0

  • 包含纯虚函数的类叫抽象类
  • 从抽象类只能作为基类来派生新类使用,不能创建抽象类的对象
  • 抽象类的指针和引用可以指向由抽象类派生出来的类的对象
  • 在抽象类的成员函数内可以调用纯虚函数,但是在构造函数或析构函数内部不能调用纯虚函数
  • 如果一个类从抽象类派生而来,那么当且仅当他实现了基类中的所有纯虚函数,他才能成为非抽象类

输入输出和模版

输入输出流相关的类

摆了