• 《C++ Primer》读书笔记—第七章 类


    声明:

    • 文中内容收集整理自《C++ Primer 中文版 (第5版)》,版权归原书所有。
    • 学习一门程序设计语言最好的方法就是练习编程

      这一章都是概念和细节,需要好好理解和思考,认真看书中的例子。

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

      2、封装实现了累的接口和实现的分离。

      3、实现数据抽象和封装,需要定义一个抽象数据类型。在抽象数据类型中,由类的设计者考虑类的实现过程,使用该类的程序员则只需要抽象思考类型做了什么,无须了解类型的工作细节。

    一、定义抽象数据类型

    1、一个设计良好的类,既要有直观且易于使用的接口,也必须具备高效的实现过程。

    2、成员函数的声明必须在类的内部,他的定义则既可以在类的内部也可以在类的外部。作为接口组成部分的非成员函数,则定义和声明都在类的外部。

    3、成员函数通过一个名为this的额外的隐式参数来访问调用它的那个对象,当我们调用一个成员函数时,用请求该函数的对象地址初始化this。

      在成员函数内部,可以直接使用调用该函数的对象的成员,而无须通过成员访问运算符来做这一点。因为this所指的就是这个对象。

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

    4、默认状态下,this的类型时指向类类型非常量版本的常量指针。不能把this绑定到一个常量对象上,也不能在一个常量对象上调用普通的成员函数。

    5、C++允许把const关键字放在成员函数的参数列表之后,此时,紧跟在参数列表后面的const表示this是一个指向常量的指针。像这样使用const的成员函数被称为常量成员函数

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

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

    6、类的成员函数的定义嵌套在类的作用域之内。

      编译器处理类的过程分两步:首先编译成员的声明,然后才轮到成员函数体(可以为空)。因此,成员函数体可以随意使用类中的其他成员而无须在意这些成员出现的顺序。

    7、无须使用隐式的this指针访问函数调用者的某个具体成员,而是需要把调用函数的对象当成一个整体来访问。???

    8、定义非成员函数的方式与其他函数一样,通常把函数的声明和定义分开。如果函数在概念上属于类但是不定义在类中,则它一般应与类声明(而非定义)放在同一个头文件中。这样,用户使用接口的任何部分都只需要引入一个文件。

    9、IO类属于不能被拷贝的类型,因此只能使用引用来传递它们。而且,读入和写入都会改变流的内容,所以两个函数接受的都是普通引用,而非对常量的引用。

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

    11、构造函数的名字和类名相同,但构造函数没有返回类型,构造函数也有一个(可能为空)的参数列表和一个(可能为空的)函数体。类可以包含多个构造函数,和其他重载函数一样,不同构造函数之间必须在参数数量和参数类型上有区别。构造函数不能被声明成const类型。当我们创建类的一个const对象时,直到构造函数完成初始化过程,对象才能真正取得其“常量”属性。因此,构造函数再const对象的构造过程中可以向其写入值。

    12、默认构造函数:类通过一个特殊的构造函数来控制默认初始化过程,这个函数叫默认构造函数。没有任何实参。如果我们没有显式的定义构造函数,那么编译器会隐式定义一个默认构造函数。(合成的默认构造函数

    13、***合成默认函数只适用于非常简单的类。对一个普通类,必须定义自己的默认构造函数。原因有三:

      一、编译器只有发现类不包含任何构造函数的情况下才会生成一个默认的构造函数。一旦我们定义了一些其他的构造函数,那么除非我们再定义一个默认的构造函数,否则类将没有默认构造函数。依据:如果一个类在某种情况下需要控制对象初始化,那么该类可能在所有情况下都需要控制。

      二、对于某些类来说,合成的默认构造函数可能执行错误的操作。如果定义在块中的内置类型或复合类型的对象被默认初始化,则他们的值是未定义的。该准则适用于默认初始化的内置类型成员。因此,含有内置类型或复合类型成员的类应该在类的内部初始化这些成员,或定义一个自己的默认构造函数,否则用户在创建类的对象时就可能得到未定义的值。

      三、有的时候编译器不能为某些类合成默认的构造函数。例如,如果类中包含一个其他类类型的成员且这个成员的类型没有默认构造函数,那么编译器无法初始化该成员。我们必须自定义默认构造函数。

    14、=default的含义:C++11中,如果我们需要默认的行为,那么可以通过在参数列表后面写上=default来要求编译器生成构造函数。其中,=default既可以和声明一起出现在类的内部,也可以作为定义出现在类的外部。和其他函数一样,如果=default出现在类的内部,则默认构造函数是内联的,如果在外部,则该成员在默认情况下不是内联的。

    15、通常情况下,构造函数应使用类内初始值,只要这样的初始值存在我们就能确保为成员赋予一个正确的值。不过,如果编译器不支持类内部初始值,则所有构造函数都应该显式地初始化每个内置类型的成员。

      构造函数不应该轻易覆盖掉类内的初始值,除非新赋的值与原值不同。

    二、访问控制与封装

    1、我们使用访问说明符(access specifiers)加强类的封装性:

      定义在public后的成员在整个程序内可被访问。public成员定义类的接口。

      定义在private说明符后的成员可以被类的成员函数访问,但不能被使用该类的代码访问,private部分封装了类的实现细节。

    2、使用class和struct定义类的区别:默认的访问权限。struct是public的,class是private的。

    3、类可以允许其他类或者函数访问它的非公有成员,方法是令其他类或者函数成为它的友元,加一条friend关键字开始声明语句即可。友元只能出现在类定义的内部,具体位置不限。

      友元不是类的成员,所以不受它所在区域访问控制级别的约束。

      最好在类定义开始或者结束前的位置集中声明友元。

      如果希望类的用户可以调用某个友元函数,则必须在友元声明之外再专门对函数进行一次声明

    三、类的其他特性

    1、类型成员必须先定义后使用,因此,类型成员通常出现在类开始的地方。

    2、一个const成员函数如果以引用的形式返回一个*this,那么它的返回类型将是一个常量引用。

      非常量版本的函数对于常量对象是不可用的,所以我们只能在一个常量对象上调用const成员函数。另一方面,虽然可以在非常量对象上调用常量版本或者非常量版本,但显然此时非常量版本比较好。

    3、一个类声明之后定义之前称为不完整类型(imcomplete type),不完整类型可以定义指向这种类型的指针或引用,也可以声明(但不能定义)以不完全类型作为参数或者返回类型的函数。

    4、创建一个类的对象之前该类必须被定义过,而不能仅仅被声明。否则编译器不知道该给分配多少存储空间。

      然而,一个类名字出现以后就被声明,因此可以允许类内包含指向它自身类型的引用或指针。

    5、如果一个类指定了友元,则友元函数可以访问此类包含非公有成员在内的所有成员。

    6、如果一个类想把一组重载函数声明成它的友元,它需要对这组函数中的每一个分别声明。

    四、类的作用域

    1、当成员函数定义在类的外部时,返回类型中使用的名字都位于类的作用域之外。此时,返回类型必须指明它是哪个类的成员。

    2、编译器处理完类中的全部声明后才会处理成员函数的定义。

    3、 类型名的定义出现在类的开始处,这样能确保所有使用该类型的成员都出现在类名的定义之后。

    4、成员函数中使用名字的解析方式:

      首先:在成员函数内查找该名字的声明,只有在函数使用之前出现的声明才考虑。

      如果在成员函数内没有找到,则在类的内部继续查找,这时类的所有成员都可以考虑。

      如果类内也没有找到该名字的声明,在成员函数定义之前的作用域内继续查找。

    5、不建议使用其他成员的名字作为某个成员函数的参数。

    五、构造函数再探

    1、如果没有在构造函数的额初始值列表中显式地初始化成员,则该成员将在构造函数体之前执行默认初始化。

    2、有时候可以忽略数据成员初始化和赋值的区别。但如果成员是const或者引用时,必须初始化。类似的,当成员属于某种类类型且该类没有定义默认构造函数时,也必须将这个成员初始化(通过初始值列表)。

    3、最好令构造函数初始值的顺序与成员声明的顺序保持一致,尽量避免使用某些成员初始化其他成员。

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

      一个委托构造函数也有一个成员初始值列表和一个函数体,成员初始值列表只有一个唯一的入口,就是类本身。

      和其他成员初始值一样,类名后面紧跟圆括号括起来的参数列表,参数列表必须与类中另外一个构造函数匹配。

      当一个构造函数委托给另一个构造函数时,受委托的构造函数的初始值列表和函数体依次执行。假如函数体包含有代码的话,先执行这些代码,然后控制权才交还给委托者的函数体。

    5、如果想定义一个使用默认构造函数进行初始化的对象,正确的方法是去掉对象名之后的空括号。

      

    1 Sales_data obj();   //定义了一个函数而非对象   不能对函数使用成员访问运算符
    2 
    3   Sales_data obj   //正确,obj是个对象而非函数

    6、转换构造函数(converting constructor):如果一个构造函数只接受一个实参,则它实际上定义了转换成此类类型的隐式转换机制。编译器只会执行一步类型转换。

    7、关键字explicit可以阻止要求隐式转换的程序上下文。关键字explicit只对一个实参的构造函数有效,需要多个实参的构造函数不能使用于执行隐式转换,所以无需对多个构造函数指定为explicit。

      当使用explicit关键字声明构造函数时,只能以直接初始化的形式使用,而且,编译器不会在自动转换过程中使用该构造函数。但可以使用这样的构造函数显式地进行强制转换:

    1 //正确:实参是一个显式构造的Sales_data对象
    2 item.combine(sales_data(null_book));
    3 //正确:static_cast可以使用explicit的构造函数
    4 item.combine(static_cast<Sales_data>(cin));

      第一个调用中,使用Sales_data的构造函数,该调用通过接受string的构造函数创建了一个临时的Sales_data对象,在第二个调用中,我们使用static_cast执行了显式的而非隐式的转换,其中,Static_cast使用istream构造函数创建了一个临时的Sales_data对象。

    9、聚合类(aggregate class)使得用户可以直接访问其成员,并且具有特殊的初始化语法形式。当一个类满足以下条件称为聚合类:

      ·所有成员都是public的

      ·没有定义任何构造函数

      ·没有类内初始值

      ·没有基类,没有virtual函数。

    10、如果初始化值列表中的元素个数少于类的成员数量,则靠后的成员被值初始化。初始值列表的元素个数不能超过类的成员变量。

    11、显式初始化类的对象的成员有三个明显缺点:

      ·要求类的所有成员都是public

      ·将正确初始化每个对象的每个成员的任务交给了类的用户而不是作者。初始化过程可能冗余或者出错

      ·添加或者删改一个成员之后,所有的初始化语句都要更新。

    12、字面值常量类: 数据成员都是字面值的聚合类。或者符合以下要求:

      ·数据成员必须是字面值类型

      ·类必须至少含有一个constexpr构造函数

      ·如果数据成员含有类内初始值,则内置类型成员的初始值必须是一条常量表达式;或者如果成员属于某种类类型,则初始值必须使用成员自己的constexpr构造函数。

      ·类必须使用析构函数的默认定义,该成员负责销毁类的对象

    13、constexpr构造函数可以声明成=default的形式,或者删除函数的形式。一般来说函数体应该为空。

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

    六、类的静态成员

    1、类的静态成员只与类有关,与对象没有关系。

    2、类的静态成员存在于任何对象之外,对象中不包含任何与静态数据成员有关的数据。也不与任何对象绑定,不包含this指针。不能被声明成const,也不能在static函数体内使用this。

    3、必须在类的外部定义和初始化静态成员。

    4、如果要在类内部初始化静态成员,则必须提供const整数类型的类内初始值,不过要求静态成员必须是字面值常量类型的constexpr。初始值必须是常量表达式

    5、静态成员可以是不完全类型,静态数据成员的类型可以就是它所属的类类型。而非静态数据成员则受到限制,只能声明成它所属类的指针或者引用。

      另一个区别就是:我们可以使用静态成员作为默认实参。 

      3.2晚:第七章都是定义性的东西,很难理解,得反复看,反复记,以后遇到实例反而才能记得住。九点就溜了,跟超哥回去打求生之路。最近研二的都在准备找工作了,旁边的师兄师姐也在做机试准备找实习,越越也在刷题准备笔试机试。一个师兄说自己阿里三道题一道都没做出来,想想我当年华为的机试还是做出三道了。感觉压力突然大了,虽然现在不迷茫的,已经知道自己该做什么,比起其他人应该算是早早起步了吧,笨鸟先飞。希望早点准备,不要到时候着急。不管跑步还是读书,都是要坚持下去。

      3.3早:老苗又任IEEE的高级会员了,从上学期末ccf的主席又升职了。我等屁民作为IEEE高级会员的亲传弟子的同学兼室友,岂不是要沾很大的光。哈哈哈哈,扯犊子,赶紧看书。

      3.4:去交大转了转,跟交大的聊一聊,就发现自己的差距。别人要工程有工程,要学术有学术。学院培养计划和个人目标都很明确,个人提高的速度非常快,实验室的项目和科研进度也进展迅速,而且奖助和保障工作也做得很到位。然而博士每天活得也很枯燥,在他眼里出学术论文就已形成套路,从无到有两三个月就搞定了。反观我们,都是三只小菜鸡,说白了就是有点文凭的高级民工。毕业了也是批量生产代码的机器。很不甘心但又没有能力改变这一切。只能默默地提升自己先。

      3.5:打了一天排位,十一连胜。从未有过的战绩啊。小黄毛疯狂虚区。晚上跟星星打晋级赛,三胜一都难于上青天。真是各种奇葩各种报复社会的都来了。连着两次还是三次晋级都失败了,也是运气。付老师让写的文章并没有很上心,反正这种文章随便写写都能发,发了也没什么卵用。中国的教育体制改革?做梦吧。

      3.6又是周一。周末两天啥都没做成,浑浑噩噩。抓紧赶进度了。

      倘若樱花常开,我们的生命常在,
      那么两厢邂逅,就不会动人情怀。

      ——东山魁夷 《一片树叶》

      我所有的自负都来自我的自卑,所有的英雄气概都来自于我内心的软弱,所有的振振有词都因为心中满是怀疑。我假装无情,其实是痛恨自己的深情。我以为人生的意义在于四处游荡流亡,其实只是掩饰至今没有找到愿意驻足的地方。

      晚:今天只看了十页。下午都没看,晚上还开会。真是烦,还能不能安心啃这块硬骨头了。

      3.7第七章结束。持续了5天,中间有两天周末。类这一部分是C++最基本的特性,比较生涩难懂,书上的例子也不多。可能还得回来重新读几次。且随疾风前行。

  • 相关阅读:
    Linux基础---开关机与帮助
    Linux磁盘管理命令
    批处理之命令补充II
    LeetCode 328. 奇偶链表(Odd Even Linked List)
    LeetCode 岛屿的最大面积(探索字节跳动)
    LeetCode 复原IP地址(探索字节跳动)
    LeetCode 简化路径(探索字节跳动)
    LeetCode 最长公共前缀(探索字节跳动)
    LeetCode 无重复字符的最长子串(探索字节跳动)
    自动机器学习超参数调整(贝叶斯优化)
  • 原文地址:https://www.cnblogs.com/zlz099/p/6515235.html
Copyright © 2020-2023  润新知