前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >【笔记】《C++Primer》—— 第7章:类

【笔记】《C++Primer》—— 第7章:类

作者头像
ZifengHuang
发布2020-07-29 16:06:57
发布2020-07-29 16:06:57
6140
举报
文章被收录于专栏:未竟东方白未竟东方白

类这部分还是和之前类似,即大体上的内容都明白了,但是书上说到的很多细节问题之前却没有搞懂。这次记录也会比较长一些,但是很多内容非常有用如之前没注意到的初始值列表。

下一篇就做个第一部分的小总结吧。

7.1 定义抽象数据类型

  1. 封装的目的是使得接口与实现之间的分离,隐藏实现细节使用户无需理解细节就能使用
  2. 类在C++中既可以用struct也可以用class,其区别在于默认的访问权限
  3. 类的成员函数必须声明在类内,但是可以在外部定义
  4. 在类内定义的函数默认是隐式的内联函数
  5. 调用对象的成员函数时,实际上是将对象作为this指针指向的目标传入后执行了一个函数
  6. 也即是说,this是类的一个隐式常量指针,指向当前所用的这个实例对象,常量指针指的是我们不能改变这个指针指向的地址
  7. 可以在成员函数的参数列表后面加上const,此时的成员函数称为常量成员函数,表示此时的this是一个常量版本的常量指针。我们无法在这个函数中修改这个对象的内容
  1. 这个写法的一个用处在于我们无法在常量对象上调用普通成员函数,所以要用常量成员函数代替
  2. 编译类时,编译器先编译完成员的声明,然后再回来编译成员的函数体,因此成员函数可以无视顺序使用成员
  3. 当要返回当前对象时,将this解引用为*this即可
  4. 一般来说当一个函数概念上属于某个类但并不在类内,则将其与类的声明放在同一个文件中,如一些IO流接口函数
  5. 构造函数负责初始化对象,只要对象被创建就会执行一次构造
  6. 构造函数不可以是const,当创建const对象时,对象是在构造初始化完成后(执行构造函数体前的瞬间)获得const属性的
  7. 当没有任何构造函数时编译器会默认生成一个构造函数,初始化(值初始化,默认构造或用初始值赋值)类内的各种成员,成员当无法默认初始化时值将未定义,这个函数称为合成的默认构造函数
  8. 默认构造函数有可能出问题(遇到无法初始化的成员),所以尽量不要用它,用时要做好各种初始化准备
  9. 当有构造函数时,编译器将不会默认创建构造函数,C11此时可以用 [类名]()=default 来要求编译器生成一个默认行为
  1. 在构造函数和函数体间用冒号连接一段以逗号分隔的调用串,调用名为函数的成员,即为构造函数的初始值列表,可以很方便地在函数体是空时完成构造函数中对值的赋值且如随后将要提到的,此方法有时必不可少
  1. 拷贝构造在赋值或初始化与值传递等操作中产生,但当类需要分配对象以外的资源时,默认的拷贝构造常常失效,此时用vector类来代劳内存分配等能避免这种复杂情况

7.2 访问控制与封装

  1. 在类的成员前加访问控制符(public,protected,private),访问控制符有效范围直到下个访问控制符出现或者到达类的结尾,控制符可以出现多次
  2. private表示此部分成员只能被类自己访问
  3. public表示可以在整个程序中被访问到
  4. class和struct的区别在于class默认是private,struct默认是public
  5. 有时我们需要外部的接口函数也可以访问类内的非公有成员,可以将那些函数令为友元,即将函数在类内声明一下,并在开头加上friend关键字。
  6. 友元声明只能出现在类的内部,且此声明不受访问控制符的约束,为了清晰起见建议在类的开头或结尾集中声明
  7. 友元声明仅仅指示了权限,而不是传统的声明,所以要在类的外部再声明一次(尽管很多编译器不要求这个额外的声明,但建议还是独立声明提高可移植性),同样为了清晰也建议声明在此类的头文件中

7.3 类的其他特性

  1. 用来定义类型的成员必须先定义再使用
  2. 在成员前用mutable关键字,使得这个成员必定不会变为const,即使身处const函数之中也可以被改变
  3. 提供类内初始值必须用等号或花括号
  4. const成员函数若以引用形式返回*this,则此时返回的是常量引用
  5. 尽量避免在多处使用相同的代码,所以尽量把类似的功能写为函数并迭代组合使用
  6. 类可以声明而暂时不定义,此时成为前向声明,得到的类是不完全类型,可以作为参数,返回类型,指针等
  7. 注意友元函数没有传递性,即类似自己有自己的友元并不代表自己的友元可以得到其他友元的权限
  8. 声明某个类的成员函数为友元时,需要指定出这个函数是属于哪个类的
  9. 对于重载函数声明友元时,每一种重载都要分别声明一次
  10. 前面说到即使在类内声明了友元也要在外部提供相应的声明,这是因为我们需要友元函数在外部被使用/引用时,也处于正确的作用域之中,不过很多编译器并不强制要求这一点而是进行了优化

7.4 类的作用域

  1. 定义函数时,一旦遇到类名,定义的剩余部分就在类的作用域之内了,这里的剩余部分即后面的参数列表和函数体,不包含前面的返回部分
  2. 好好理解第一点就可以理解为什么当外部定义的函数名中出现了对其类的指示后,后面就不再需要这个指示的出现了
  1. 对于类的名字查找过程有几处不同,首先处理类的成员的声明,再当类全部可见后再编译函数体
  2. 对于声明中的所有名称都需要在使用前即可见,因此我们要把需要用到的名字在函数声明前就保证已经被声明了
  3. 然后对于声明中的类型名,则要保证类型名是嵌套唯一的,外层出现过的类型名不能再在内层重新定义
  4. 而对于函数体中的名字,首先在函数内部查找,然后在此函数所属的类内查找,最后在此函数定义前可访问到的作用域内继续查找。正因为这个顺序,当两者出现冲突时,函数内部的名的全称是this->[名],函数外的是[类名]::[名],函数外的作用域中的全称为::[名],注意此时是直接用空的双冒号即可。(前面的方括号都表示需替换的部分)
  5. 由于上一点可能出现不必要的麻烦,强烈建议不要使函数内部的名字与类内的名字重名
7.5 构造函数再探
  1. 前面说到构造函数题执行的时候const或引用的属性定义就定下了,因此若我们想要给如const对象赋初始值,必须借助构造函数初始值写法。即7.1(17)
  2. 类成员初始化的顺序是成员在类内声明的顺序而非参数列表的顺序,这点一定要注意有时容易引发大问题。因此一方面是最好保持参数顺序与声明顺序相同以方便查找,另一方面尽量避免用某些成员来初始化其他的成员以防止初始化顺序导致的问题
  1. C11增加了“委托构造函数”,即我们可以简化之前重载多个类似的构造函数的代码,我们可以在构造函数初始值列中调用非委托的构造函数了
  1. 对于委托构造函数的函数体,委托者会依次执行被委托的函数的函数体,完成初始化后才执行自己的函数体
  2. 想使用默认构造函数时,方法是初始化对象时不使用后面的调用运算符(即小括号对),如直接写Test a;
  3. 当构造函数只接受一个实参时,称转换构造函数,即定义了这种类型的隐式转换机制,在这种情况下我们对实参的输入编译器可以自动地进行一步隐式转换
  4. 要注意这里只转换一步,即如现在有构造函数Test(OurString b),和OurString(string a)时,当我们输入的a是string类来调用Test(a),则编译器会隐式将string转为OurString再调用Test,但当我们输入的a是char*的“abcde”时,由于编译器需要先把char*转string再转OurString,即需要两步转换,所以会产生错误
  5. 这种隐式类型转换有时候我们是不希望其启用的,此时我们可以及那个那个构造函数声明为explicit(显式的),它就不会进行隐式转换
  6. 多个参数的构造函数不会进行隐式转换
  7. explicit只要能类内的声明中写,类外定义时不需要写
  8. explicit关键字的构造函数只能用于直接初始化,即不能用在之后会说到的拷贝构造中
  9. 当一个类所有成员是public,没有构造函数,没有类内初始值且没有基类和虚函数时,这个类称为聚合类。纯粹的C风格的struct就是一种典型的聚合类,此时我们可以用有顺序的花括号来初始化它
  1. 数据成员都是字面值常量的聚合类或不是聚合类但其成员都是字面值,至少含有一个constexpr构造函数,成员若有类内初始值则必由字面值常量或其自己的constexpr构造且类必须使用默认的析构函数的类,称为字面值常量类
  2. constexpr构造函数由constexpr关键字引出,其函数体一般来说都是空的或只有一条返回语句,且其构造函数必须初始化类的所有成员。它的实参必须由字面值,初始值或constexpr来初始化

7.6 类的静态成员

  1. 通过在类的成员前加static来使其成为类的静态成员,与类本身联系在一起,类似于全局范围里的静态变量
  2. 类的静态成员存在于所有对象之外,我们可以直接使用作用域运算符(双冒号)来访问它,也可以正常地用对象来访问(但注意它不属于哪个对象)
  3. 成员函数可以不通过作用域运算符直接使用静态成员
  4. 注意类似explicit关键字,static关键字只出现在类的声明里,不能在外部重复这个关键字
  5. 如同其余的静态变量,静态成员只会被定义初始化一次
  6. 由于静态成员只在程序生命中定义一次,所以我们一方面最好将其定义与其他非内联函数放在一起以保证唯一次定义
  7. 静态成员类内应对常量型提供const属性的初始值或用constexpr来初始化
  8. 要注意由于静态变量不是由构造函数初始化的,一般来说我们不能在类内初始化它,而是在类内声明它然后在类外定义并初始化它
  9. 由于类内的它的初始化只是声明而已,若外部的函数没有获得类的完整声明则无法使用类内的静态初始化,所以我们应该保持一个良好习惯即即便我们已经类内初始化它,也在外部进行一次定义(但不用初始化)来保证其作用域的正常
  1. 静态成员的好处是它类似指针类型可以在类内作为非完全类型被成员声明所采用
  2. 另一个好处是静态成员可以成为函数的默认实参
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-01-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 未竟东方白 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档