c++Primer简单回顾--面向对象的类

来源:互联网 发布:iphone蓝牙不支持mac 编辑:程序博客网 时间:2024/05/21 14:52

1.动态绑定
C++中使用基类的引用或指针调用一个虚函数时,将发生动态绑定。针对 公有继承。

class Quote{public:    Quote() = default;    Quote(const std::string &book, double sales_price)        : bookNo(book), price(sales_price)    {}    std::string isbn() const    {        return bookNo;    }    virtual double net_price(std::size_t n) const    {        return n * price;    }    virtual ~Quote() = default;private:    std::string bookNo;protected:    double price = 0.0;};class Bulk_quote : public Quote{public:    Bulk_quote() = default;    Bulk_quote(const std::string&, double, std::size_t, double);    double net_price(std::size_t) const override;private:    std::size_t min_qty = 0;    double discount = 0.0;};

2.虚函数
基类中任何构造函数之外的非静态函数都可以是虚函数。
关键字virtual只能出现在类内部的声明语句之前而不能用于类外部的函数定义。
成员函数如果非虚函数,其解析过程发生在编译时。

3.继承
派生类可以继承在基类中的成员,但派生类的成员函数不一定有权访问从基类继承而来的成员。
派生类的成员函数只能访问基类的 公共的,保护的成员。私有成员不能访问。

可以将基类的指针或引用绑定到派生类对象的基类部分上。这种派生类到基类的类型转换可以隐式执行。

派生类构造函数
派生类须使用基类的构造函数来初始化它的基类部分。
初始化顺序:先初始化基类部分,然后按照声明的顺序依次初始化派生类的成员。

在类名后加上 final可以令此类不能被继承。

4.虚函数覆盖和默认实参
把具有继承关系的多个类型称为多态类型
一个派生类的函数如果覆盖了某个继承来的虚函数,则它的形参列表须与被它覆盖的基类函数的一致。返回类型也是。
例外:
当类的虚函数的返回类型是类本身的指针或引用时,派生类返回类型允许为对应的派生类本身的指针或引用。

虚函数可以有默认实参,如某次调用使用了默认实参,则实参值由本次调用的静态类型决定。

5.纯虚函数和抽象基类
抽象基类
纯虚函数:

class Disc_quote : public Quote{public:    Disc_quote() = default;    Disc_quote(const std::string& book, double& price, std::size_t qty, double disc)        :Quote(book, price), quantity(qty), discount(disc)    {}    // 纯虚函数:虚函数声明后加 = 0    double net_price(std::size_t) const = 0;protected:    std::size_t quantity = 0;    double discount = 0.0;};

含有(或非经覆盖直接继承)纯虚函数的类是抽象基类。
抽象基类负责定义接口,而后续的其他类可以覆盖该接口。不能创建一个抽象基类的对象。

派生类构造函数 一般使用直接基类构造函数 初始化基类部分。

6.访问控制与继承
每个类控制自己的成员初始化过程。
每个类还控制其成员对派生类的可访问性。

// …派生类的友元 只能 通过派生类对象 来访问基类的受保护成员。成员函数内直接访问其实是通过this访问的,this代表派生类对象,故成员函数内 直接访问符合条件。

class Base{public:    void pub_mem();protect:    int prot_mem();private:    char priv_mem;};struct Pub_Derv : public Base{    int f()    {        return prot_mem;    }    // 错误:不能访问基类私有成员    char g()    {        return priv_mem;    }};struct Priv_Derv : private Base{    int f1() const    {        return prot_mem;    }};

派生访问说明符对派生类的成员 对基类成员的访问性 没什么影响。对基类成员的访问权限只与基类中的访问说明符有关。

派生访问说明符的目的是控制派生类用户对于基类成员的访问权限。

Pub_Derv d1;Priv_Derv d2;d1.pub_mem;d2.pub_mem; // 错误

派生类向基类转换的可访问性
派生类向基类的转换是否可访问由使用改转换的代码和派生访问说明符决定。
对 用户代码 公有继承时,可使用派生类向基类的转换
对 派生类的成员和友元, 派生类向基类的转换总是可用。
对 派生类的派生类的成员和友元, 在 公有和保护继承下,可使用 派生类向基类的转换。

class Base{    friend class Pal;protected:    int prot_mem;};class Sneaky : public Base{    friend void clobber(Sneaky &);    friend void clobber(Base &);private:    int j;};class Pal{public:    int f(Base b)    {        // 通过对象Base使用其保护或私有成员在 Base的友元中 或 Base类的成员函数中 是可以        return b.prot_mem;    }    int f2(Sneaky s)    {        // 错误        // 通过 Sneaky对象使用其私有 或 受保护成员,在非Sneaky 成员函数或其友元内,是不可以        return s.j;    }    int f3(Sneaky s)    {        // 通过 Sneaky对象 使用其基类部分的 私有的或受保护的成员,在 基类的友元中 是可以        return s.prot_mem;    }};class D2 : public Pal{public:    int mem(Base b)    {        // 通过Base对象使用其 保护成员,在 非Base成员函数或非Base友元内是 不可以        return b.prot_mem;    }};// 改变个别成员的可访问性class Base{public:    std::size_t size() const     {        return n;    }protected:    std::size_t n;};class Derived : private Base{public:    using Base::size;protected:    using Base::n;};

使基类的size和n成为Derived类的public和protected成员。本来应该是private的。

struct和class关键字定义的类的唯一差别是:默认的成员访问说明符不同。默认派生访问说明符也不同。

7.继承中类作用域
一个对象,引用或指针的静态类型决定了改对象的哪些成员是可见的。
p->mem()或obj.mem()执行过程:
在p或obj的静态类型对应的类中 找mem。找不到从基类中找。也找不到,报错。
找到了,进行类型检查
类型检查合法下,如果mem是虚函数,判断是否有动态绑定。没有时,进行常规调用。

使用using声明可以改变名字查找时,名字可见性。
举例:本来 类内成员函数内部名字查找 第一层为 类中。第二层 为类的基类。
在类内对基类的一些成员用using声明,可以使 在第一层查找名字时,包含using声明的名字。

struct Base{    int memfcn();};struct Derived : Base{    int memfcn(int);};Derived d;Base b;b.memfcn();d.memfcn(10);d.memfcn();// Xd.Base::memfcn();

名字查找在类型检查前,名字查找 一次在一个“层”里面查找。
先由名字查找确定匹配集合,之后再匹配集合中寻找最优匹配。在 一层中查找到名字后,便不会继续在后续层中查找。

class Base{public:    vritual int fcn();};class D1 : public Base{public:    int fcn(int);    vritual void f2();};class D2 : public Base{public:    int fcn(int);    int fcn();    void f2();};Base bobj;D1 d1obj;D2 d2obj;Base *bp1 = &bobj, *bp2 = &d1obj, *bp3 = &d2obj;bp1->fcn(); // Base::fcn()bp2->fcn(); // Base::fcn()bp3->fcn(); // D2::fcn()D1 *d1p = &d1obj;D2 *d2p = &d2obj;bp2->f2(); // xd1p->f2(); // D1::f2()d2p->f2(); // D2::f2()Base *p1 = &d2obj;D1 *p2 = &d2obj;D2 *p3 = &d2obj;p1->fcn(42); // xp2->fcn(42); // D1::fcn(int)p3->fcn(42); // D2::fcn(int)

被继承类的析构函数应该定义为虚函数。
和其他虚函数一样,析构函数的虚属性会被继承,无论后面的派生类使用合成的析构函数还是自己定义的析构函数,都将是虚析构函数。

对于派生类的析构函数 来说,它除了负责销毁派生类自己的成员外,还负责销毁派生类的直接基类,直接基类又销毁他自己的直接基类…

class B{public:    B();    B(const B&) = delete;    //...};class D : public B{    // ...};D d;D D2(d); // xD d3(std::move(d)); // x// 定义派生类的拷贝或移动构造函数class Base{    // ...    Base& operator=(const Base&);};// 正例class D : public Base{public:    // 拷贝构造,基类成员初始化由基类拷贝构造负责    D(const D& d)        : Base(d)    { // ... }    // 移动拷贝构造,基类成员初始化由基类移动拷贝构造负责    D(D&& d)        : Base(std::move(d))    { // ... }    // 赋值应该为对象内所有成员赋值    D& operator=(const D& rhs)    {        // 一般用基类函数处理基类成员相关控制。        Base::operator=(rhs);        // 为派生类成员赋值        return *this;    }    // Base::~Base被自动调用执行    ~D()    {        // 清除派生类成员    }};// 反例class D : public Base{public:    // 此时基类部分的成员将使用基类的默认构造函数进行初始化    D(const D& d)    { //... }};

如所知,派生类对象的基类部分被首先构建,当执行基类的构造函数时,该对象的派生类部分是未被初始化的状态。
执行析构函数时,先执行派生类析构函数,然后销毁派生类部分,在执行直接基类析构函数,销毁直接基类,…

在执行基类成员时,该对象处于未完成状态。

0 0