前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【笔记】《C++Primer》—— 第四部分:高级主题(完)

【笔记】《C++Primer》—— 第四部分:高级主题(完)

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

这篇是第四部分的总结,大致上就是回看了最后的3篇笔记并且重新翻翻书梳理了一下,内容基本都是从前面的章节复制来的。

至此书的正文都完了,上一篇结尾说想要总结每一章的小结自己看了看觉得书里的小结已经很精炼了,再总结就没东西了,因而这个系列就到此为止吧。

之后我会继续之前《程序员面试金典》的内容,这个系列更新可能会比较慢,然后会小小总结一下吴恩达的机器学习,再然后就会开一个神秘而我期待已久的新坑了XD

17 标准库特殊设施

  • tuple是类似pair的类型,可以简单地保存类型不同的数量任意的对象,定义在头文件tuple中
  • 用tuple<type1,type2,…,type3>来定义一个tuple,然后必须使用直接初始化法,对象参数可以输入对应类型需要放入的成员
  • 用get函数来取得tuple的元素
  • tuple的常见用途是从一个函数返回多个值
  • bitset类型可以很好地处理位运算问题,比直接使用位操作符清晰方便很多。类似array,定义的时候模板参数是这个bitset的位数
  • 整型值作为初始值,没有内容的部分会置0,超长的部分会截断
  • regex的核心是判断是否匹配的函数regex_match,搜索第一个匹配串的函数regex_search,用新输入的结果替换匹配到的串的函数regex_replace和用来匹配的迭代器适配器sregex_iterator
  • 默认情况下regex使用的是ECMAScript正则语言
  • 匹配的方法通常是构造一个string类型的匹配模式,然后用这个模式构造一个正则表达式regex,接着定义一个smatch类型用来保存匹配的结果,准备好string类型的匹配文本,最后选用适合的regex函数来匹配
  • 正则表达式出现错误时会以regex_error的异常抛出,所以使用时需要try-catch
  • 正则表达式的编译是非常慢的过程,所以应该避免创建不必要的表达式
  • 标准库还定义了一系列用来在替换过程中控制匹配和格式的标志,但是使用的时候我们要在std命名空间中的regex_constants命名空间中使用
  • 17.3有一些正则表达式的语法项
  • C++中我们应该使用随机数库来生成更好的随机数,随机数库包含了生成随机unsigned整数序列的随机数引擎和利用引擎生成符合特定分布随机数的随机数分布器
  • 当我们想要从一个分布和一个范围中生成随机数时,我们应该使用随机数分布器,常用的随机数分布器就是uniform_int_distribution均匀整数分布器和uniform_real_distribution均匀实数分布器,初始化分布器的时候模板参数是目标分布的最大值和最小值,实例化完成后我们调用时给分布器传递随机数引擎作为参数即可,注意需要直接传递引擎因为分布器可能在内部需要多次调用引擎
  • 新标准库还可以生成非均匀分布的随机数,最典型的是正态分布normal_distribution和伯努利分布bernoulli_distribution
  • 分布器一样需要保证好引擎的种子,而且为了保持分布的正常,分布对象需要保存状态,也就是要在循环外定义
  • 标准库定义了一组修改流状态的操作符,操作符是函数或者对象,在输入输出的时候将其传入可以改变接下来的格式状态,大多数操作符都是成对的,一个设置一个复原,且操作符分为两大类,一类控制输出的数值的格式,一类控制补白等格式
  • 很多操作符会永久修改当前IO流的状态直到复原设置,因此使用了特殊格式的操作符后最好尽快复原防止之后格式不如人意
  • setprecision(n)操作符可以改变流输出浮点值时的小数位数,也可以用cout.precision(n)控制,默认情况下浮点值按照6位数字(总位数)打印,没有小数点则不打印小数点,非常大或非常小的数以科学计数法表示
  • setw(n)可以控制输出的补白,也就是控制输出的内容需要在第几位的地方右对齐,默认使用空格将内容前推到右对齐第n位为止,然后可以用setfill(c)改变填充用的字符,用left和right改变对齐的方向
  • 未格式化IO操作允许我们将一个流当作一个无解释的字节序列处理,最常用的就是读取一个字符的get函数和输出一个字符的put函数
  • 一些操作可以进行多字节的未格式化IO,但是要注意操作越多犯错的机会也就越多,get,getline,read,write都是多字节的操作,ignore函数可以忽略流中的一定数目的字符
  • 其中get和getline最大的区别是get会将分隔符保留为流的下一个字符,getline则读取并抛弃分隔符

18 用于大型程序的工具

  • 异常处理的流程是:在C++中我们throw了一个表达式后会rised一个异常,然后调用链中与类型匹配的最近的handler会处理这个异常,被抛出的异常中携带的信息会协助处理部分进行处理
  • 要注意每次在try框内throw的时候,throw后面的剩余语句将不会再执行,程序的控制权会转移到成功catch的模块内,这个catch可能在同个函数中也可能是在外层调用链的嵌套中
  • 因此要注意出现异常的时候函数可能会提早退出,而且一旦开始异常处理,这段调用链中创建的局部对象会被销毁,因此throw有点类似于return, 因此我们最好将其放在某部分的最后一条语句中
  • 和return相同,我们也不该抛出指向局部对象的指针
  • 在构造函数的初始值列表冒号后面用try-catch将整个列表和函数体包住,这样就可以处理构造函数开始执行后发生的所有异常了
  • 要注意构造函数开始执行后的异常,如果是参数初始化过程中发生的异常则需要调用者自己在上下文中处理
  • catch的匹配顺序是从上往下的,因此我们应该像逻辑表达式中的短路计算一样,将匹配范围最小的,也就是最特殊的匹配放在最上面,以免被范围更大的catch捕获异常忽略掉
  • catch只允许最基础的转换,包括常量改变,派生向基类,数组转指针,函数转指针四种,其他的类型转换都不支持
  • 空的throw可以将异常重新抛出,这个throw只能出现在catch或catch调用的函数内
  • catch(…)可以捕获所有类型的异常,但是此时由于没有异常对象的名字所以我们一般进行一些对现状的处理操作就重新抛出
  • 承诺noexcept即不会抛出异常,这样可以让编译器进行一些特殊的优化操作
  • 标准库准备了一系列标准exception类,包含了基础的操作函数和what虚成员,what可以返回const char*说明异常信息,这个信息在对应exception的构造函数中输入。我们一般应用时是通过继承标准exception来构造自己的异常库进行各种处理的
  • 命名空间要解决的问题是大型程序中名字相互冲突的问题,通过让不同程序的名称放在不同的命名空间中,然后通过命名空间来特指所需要的名称来减少名称冲突
  • 每个命名空间都是一个作用域,一个命名空间由关键字namespace和命名空间的名字开始,然后用一个花括号括住需要需要放置的名字,和类不同命名空间的花括号外不需要分号结尾
  • 命名空间可以分布式定义,也就是可以被定义在不同的文件中
  • 别把#include放在命名空间内部,因为这代表我们要把头文件的所有名称都放入这个命名空间
  • 全局作用域实际上是一个无名命名空间,我们用::XXX来特指
  • 如果namespace后面不加名字直接定义命名空间的话,此时称为未命名命名空间,在这里面定义的变量有静态的生命周期,在第一次使用时创建,然后直到程序结束才销毁
  • using声明可以一次引入一个成员,生命周期从声明开始到声明所处的作用域结束为止
  • using指示可直接using一个命名空间,将这个命名空间里的所有名字都提到using语句所在的层级中,如果有些不能存在于局部作用域中的名称还会继续往外层升级
  • 不应该滥用using指示,这很容易导致我们一开始想要避免的名称冲突问题重新出现
  • using声明的是一个名称,是不能有参数的,所以using会给函数重载带来很多麻烦
  • 多重继承通常概念上就是某个类有多个平级或者难以定级的属性,多重继承也只能继承已经定义过的类,不能是final的,而且一个基类在列表中只能出现一次
  • 构造多重继承的对象和构造单继承的对象类似,自己决定好参数要传递到哪里。要注意的是基类的构造顺序是与派生列表中基类的出现顺序一致,与派生类参数顺序无关
  • 多继承的时候,名称查找会在所有直接基类中同时进行,单个继承链上才有顺序,此时如果名字在多个基类中被同时找到,则名字会有二义性,因此最好我们调用基类函数时也要特指
  • 可以将某个类在继承的时候声明为虚基类,方法是在继承的派生列表中对应项前面加上virtual,这样处理后无论这个目标类被间接继承多少次,这个基类成员都只会出现一次,此时的派生称为虚派生
  • 编译器是先按顺序初始化所有的虚基类,然后再按顺序初始化非虚基类,初始化的时候按照从底往上,同级的时候从列表左往右的顺序初始化

19 特殊工具与技术

  • 准确来说实际上我们并不能重载new和delete。当我们调用new时,实际上我们先调用了一个称为operator new的标准库函数分配了一块足够大的未命名的内存,然后将目标元素构造在这块内存中,完成后返回这块内存的头指针。delete也是类似的过程,不过和new相反,实际上会先调用析构函数将指针区域内的对象析构,然后调用一个称为operator delete的标准库函数释放内存空间。因此我们真正能重载的是构造函数析构函数和两个operator函数,通常我们说的重载new和delete就是指重载两个operator函数
  • 当我们调用new的时候,size_t参数是要分配的对象的字节数,当我们调用new[]时,参数则是数组所有元素的字节和。注意new和delete,new[]和delete[]不要混用,否则容易造成段错误,因为这两个操作符的应用过程有些不同,new[]会将元素数量存到内存区域的头四个字节中,delete[]会读取那四个字节才来进行正确的析构
  • 自定义new时,void *operator new(size_t, void*);函数不能被重新定义,这是标准库专用的
  • 没有使用定位new时,默认会调用void *operator new(size_t, void*);,如果我们使用定位new,默认情况下根据参数的不同能调用下面的四个函数,用起来将会和allocator的construct类似,可以在指定的内存地址分配想要的对象
  • 运行时类型识别(RTTI)应在我们想使用基类对象的指针或引用来执行某个派生类的非虚函数时使用,包括typeid可以返回表达式的类型,dynamic_cast将基类的指针或引用强制转为派生类的指针或引用
  • typeid(e)会返回一个常量对象type_info的引用,这个type_info在typeinfo头文件中,我们可以在这个对象中读取到目标e的类型
  • typeid也会决定表达式是否会被求值,只有类型含有虚函数时才会对表达式进行求值
  • dynamic_cast有模板参数,是目标要转换的类型,通常情况下应该有虚函数,是指针,左值引用或右值引用,运算符参数是需要被转换的目标
  • C原本只有一种枚举类型:不限定作用域的枚举。C11加入了限定作用域的枚举
  • 不限定作用域的枚举类的名称是可选,如果这个enum未命名,则必须在定义该enum时就定义它们的成员。限定作用域的枚举类在定义时需要加class/struct,即如enum class TypeName{mem1, mem2, mem3}; 限定作用域的枚举类的成员由于作用域在枚举类的内部,所以必须通过访问符才能得到,避免了名称冲突的问题
  • 限定作用域的枚举成员默认类型是int,不限定作用域的枚举成员则没有默认类型,我们只能知道其类型足够容纳其初始值。C11让我们可以给枚举类型附加类型声明 enum TypeName: memberType {mem1, mem2, mem3}; 指定枚举类型的类型
  • 类成员指针给了我们一种指向类的非静态成员的方法
  • 成员指针的特点是我们声明的时候需要给目标加上访问符::,例如 string Screen::*p = &Screen::data; 这句话我们定义了一个类型是string的Screen类成员指针,指针指向Screen类的data成员。整个写法可能有些繁琐,C11支持用auto或decltype直接解决 auto p = &Screen::data;
  • 成员指针的好处是我们可以将类的成员作为参数或返回值了,但是当我们要访问成员指针时,需要用.*或->*来在具体对象上访问成员。直观的理解就是当我们用解引用符*对成员指针解引用时,我们得到的是对应类的成员类型,我们还需要对应某个具体的对象用点或箭头获取对象中的这个成员类型的真正的值
  • 我们调用成员函数的方法和使用成员类差不多,区别是标志着函数名的括号仍然不可少,这是因为调用运算符的优先级太高了,会和指向成员运算符相混。string result = (screenObj.*fun)(10);
  • 成员指针的一大用处是存放为函数表
  • 嵌套类的特点是其名字在外层类之外就不可见了,需要用作用域符来访问
  • 嵌套类和外层类之间没有权限特权,完全可以当作一个独立的类使用
  • 在嵌套类的外层类完成真正的定义之前,嵌套类都不算是一个完全类型
  • union和struct可对照着看,union的特性是任意时刻只允许其中一个成员有值,然后同样可以和struct一样有丰富的成员,当作一个简单的类来使用,默认的访问控制符是public
  • union只允许一个成员有值的特性让其存储空间仅仅是可以容纳其最大成员的大小,union常常用来表示一组互斥值
  • 对union的一个值进行赋值会让其他的成员变为未定义的状态,因此一般来说我们通过在外层再包装一个类管理
  • 局部类是定义在函数内部的类,局部类的定义只在定义它的作用域中可见
  • 局部类的特点是其所有成员都必须在类内完成定义
  • 同样局部类和函数之间没有权限特权
  • 局部类内的嵌套类本质也属于一个局部类,所以嵌套类自身的成员必须定义在嵌套类内部
  • 不可移植的特性是指那些机器相关的特性,当我们把含有这种特性的程序转移到另一台机器上时,一般需要修改程序来适配
  • 类可以将其数据成员定义为位域(bit-field),一个位域中含有一定数量的二进制位数据,定义方式是Bit name: bitCount; bitCount就是其包含的二进制位数,使用位运算符来操作
  • 可以通过volatile限定符来声明某个变量是不需要编译器进行优化的
  • volatile的用法和const很相似,只起到修饰作用,与const并不冲突
  • C++使用链接指示来指出那些非C++的函数
  • 链接指示也就是在函数的声明前写 extern "Lang",其中Lang是目标语言的代号,例如C语言是C,当需要指示多个函数时可以用大括号把函数都括在一起,这称为多重声明
  • 多重声明可以包括头文件,而且链接指示可以嵌套
  • 我们也可以对一个有C++定义的函数标记链接指示,这样会使得这个函数可以被目标语言调用
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-06-11,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

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