[C++ Primer] 类

类的基本思想是数据抽象和封装。数据抽象是一种依赖于接口和实现分离的编程(以及设计)技术。类的接口包括用户所能执行的操作;类的实现则包括类的数据成员、负责接口实现的函数体以及定义类所需的各种私有函数。

定义抽象数据类型

  1. 定义在类内部的函数是隐式的inline函数。

  2. 常量成员函数 (常函数)

    this的目的总是指向“这个”对象,所以this是一个常量指针,不允许改变this中保存的地址。Sales_data * const this

    若this指向的是常量对象,则将const关键字放在成员函数的参数列表之后,像这样使用const的成员函数称为常量成员函数。

    可以把函数体想象成如下的形式:

    // 伪代码,说明隐式的this指针如何使用
    // 下面代码非法的:因为不能显式定义自己的this指针
    // 谨记此处的this是一个指向常量的指针,因为isbn是一个常量成员
    std::string Sales_data::isbn(const Sales_data *const this)
    {
        return this->isbn;
    }
    

    因为this是指向常量的指针,所以常量成员函数不能改变调用它的对象的内容。

    常量对象,以及常量对象的引用或指针都只能调用常量成员函数。

  3. 构造函数

    每个类都分别定义了它的对象被初始化的方式,类通过一个或几个特殊的成员函数来控制其对象的初始化过程,这些函数叫做构造函数。构造函数的任务是初始化类对象的数据成员,无论何时只要类对象被创建,就会执行构造函数。

    构造函数初始值列表:负责为新创建的对象的一个或几个数据成员赋初值。

    Sales_data(const std::string &s): bookNo(s) { }
    Slaes_data(const std::string &s, unsigned n, double p): bookNo(s), units_sold(n), revenue(p*n) { }
    

访问控制与封装

  1. 使用访问说明符加强类的封装性:

    • 定义在public说明符之后的成员在整个程序内可被访问,public成员定义类的接口。
    • 定义在private说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问,private部分封装了(即隐藏了)类的实现细节。
  2. 使用class和struct定义类的唯一区别就是默认的访问权限,class默认private,struct默认public。

  3. 类可以允许其他类或者函数访问它的非公有成员,方法是另其他类或者函数成为它的友元

  4. 封装的两个重要优点:

    • 确保用户代码不会无意间破坏封装对象的状态。
    • 被封装的类的具体实现细节可以随时改变,而无需调整用户级别的代码。
  5. 可变数据成员:在变量的声明前加上mutable关键字,一个可变数据成员永远不会是const,即使它是const对象的成员。因此,一个const成员函数可以改变一个可变成员的值。

  6. 类内初始值必须提供使用=或者花括号括起来直接初始化的形式。

类的其他特性

  1. 友元关系不存在传递性,每个类负责控制自己的友元类或友元函数。

类的作用域

  1. 对于定义在类内部的成员函数来说,解析其中名字的方式分两步处理:

    • 首先,编译成员的声明;
    • 直到类全部可见后才编译函数体。

    这种两阶段的方式处理可以简化类代码的组织形式。因为成员函数体直到整个类可见后才会被处理,所以它能使用类中定义的任何名字。

构造函数再探

  1. 如果成员是const、引用,或者属于某种未提供默认构造函数的类类型,必须通过构造函数初始值列表为这些成员提供初值。

  2. 构造函数初始化列表时成员的初始化顺序与它们在类定义中的顺序一致:第一个先被初始化,然后第二个,以此类推。

    最好另构造函数初始值的顺序与成员声明的顺序保持一致。而且如果可能,尽量避免使用某些成员初始化其他成员。

  3. 委托构造函数

    使一个委托构造函数用它所属类的其他构造函数执行它自己的初始化过程,或者说它把它自己的一些(或者全部)职责委托给了其他构造函数。

    在委托构造函数内,成员初始值列表只有一个唯一的入口,即类名本身。

    calss Sales_data{
    public:
        // 非委托构造函数使用对应的实参初始化成员
        Sales_data(std::string s, unsigned cnt, double price):
        	bookNo(s), units_sold(cnt), revenue(cnt*price) { }
        // 其他构造函数全都委托给另一个构造函数
        Sales_data(): Sales_data("",0,0){}
        Sales_data(std::string s): Sales_data(s,0,0) {}
        Sales_data(std::istream &is): Sales_data() {read(is,*this)}
        // ...
    }
    
  4. 能通过一个实参调用的构造函数定义了一条从构造函数的参数类型向类类型隐式转换的规则。

    编译器只会自动执行一步类型转换。

  5. 抑制构造函数定义的隐式转换

    将构造函数声明为explicit加以阻止

    关键字explicit只对一个实参的构造函数有效,需要多个实参的构造函数不能用于执行隐式转换,所以无须将这些构造函数指定为explicit的。只能在类内声明构造函数时使用explicit关键字,在类外部定义时不应重复。

    explicit构造函数只能用于直接初始化:

    string null_book = "9999-9999-9999";
    Sales_data item1(null_book); // 正确: 直接初始化
    
  6. 标准库中含有显示构造函数的类

    • 接受一个单参数的const char*的string构造函数,不是explicit的。
    • 接受一个容量参数的vector构造函数是explicit的。
  7. 聚合类

    一个类满足如下条件,则是聚合的:

    • 所有成员都是public的
    • 没有定义构造函数
    • 没有类内初始值
    • 没有基类,也没有virtual函数。

    下面的类是一个聚合类:

    struct Data{
        int ival;
        string s;
    }
    

    可以提供花括号括起来的成员初始值列表,并用它初始化聚合类的数据成员:

    Data val1 = {0, "Anna"};
    

    初始值顺序必须与声明的顺序一致。若初始值列表的元素个数少于类的成员数量,则靠后的成员将被值初始化。

  8. 字面值常量类

    数据成员都是字面值类型的聚合类是字面值常量类。如果一个类不是聚合类,但满足下列要求,则它也是一个字面值常量类:

    • 数据成员都必须是字面值类型。
    • 类必须至少含有一个constexpr构造函数。
    • 若一个数据成员含有类内初始值,则内置类型成员的初始值必须是一条常量表达式;或者如果是某种类类型,则初始值必须使用成员自己的constexpr构造函数。
    • 类必须使用析构函数的默认定义,该成员负责销毁类的对象。

    constexpr函数

    尽管构造函数不能是const的,但是字面值常量类的构造函数可以是constexpr函数。

    constexpr构造函数可以声明成=default的形式,也可以是删除函数的形式。否则,constexpr构造函数就必须既满足构造函数的要求(不能包含返回语句),又符合constexpr函数的要求(唯一可执行语句就是返回语句)。综合这两点,constexpr构造函数体一般为空。可通过前置关键字constexpr声明一个构造函数:

    class Debug{
    public:
        constexpr Debug(bool b = true): hw(b), io(b), other(b) {}
        constexpr Debug(bool h, bool i, bool o): hw(h), io(i), other(o) {}
        constexpr bool any() {return hw || io || other;}
        void set_io(bool b) {io = b;}
        void set_hw(bool b) {hw = b;}
        void set_other(bool b) {other = b;}
    private:
        bool hw;		// 硬件错误,而非io错误
        bool io;		// IO错误
        bol other;		// 其他错误
    }
    

    constexpr构造函数必须初始化所有数据成员,初始值或者使用constexpr构造函数,或者是一条常量表达式。

    constexpr构造函数用于生成constexpr对象 或者 constexpr函数的参数或返回类型

    constexpr Debug io_sub(false, true, false);				// 调式IO
    if (io_sub.any())										// 等价于if(ture)
        cerr << "print apporiate error messages" << endl;
    constexpr Debug prod(false);							// 无调试
    if (prod.any())											// 等价于if(false)
        cerr << "print an error message" << endl;
    

类的静态成员

  1. 类的静态成员存在于任何对象之外,静态成员函数也不与任何对象绑定在一起,不包含this指针。静态成员函数不能声明成const的。
  2. 和类的所有成员一样,当我们指向类外部的静态成员时,必须指明成员所属的类名。static关键字只出现在类内部的声明语句中。在类的外部定义静态成员时,不能重复static关键字。
posted @   Invinc-Z  阅读(17)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示