C++ Primer (二)

C++ 类和标准库

C++类

1. 类中的this指针:成员函数通过一个名为this的额外隐式参数来访问调用它的对象,当调用一个成员函数时,实际是用该函数的对象地址初始化this,this是一个常量指针总是指向当前对象。

2. const成员函数:this的类型是指向类类型非常量版本的常量指针,eg:Sales_data *const 

    常量对象,常量对象的引用或指针都只能调用常量成员函数(const关键字放在成员函数参数列表后面),紧跟在参数列表后面的const表示this是一个指向常量的指针

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

    const成员函数如果以引用返回*this,那么它返回类型是常量引用

3. 编译器分两步处理类:首先编译成员的声明,然后才是成员函数体。因此,成员函数体可以随意使用类中的其他成员而无须在意这些成员出现的次序

4. 默认构造函数初始化:

    (1)如果存在类内初始值,用它来初始化成员

    (2)否则,默认初始化该成员

    只有当类没有声明任何构造函数时,编译器才会自动地生成默认构造函数

    如果类包含内置或复合类型成员,则只有当这些成员全部被赋予了类内初始值时,才适用于合成的默认构造函数,否则默认初始化后的值时未定义的

    如果类内自定义了构造函数,又想生成默认构造函数需要这样写,类名()=default,告诉编译器生成默认构造函数

5. 友元:类可以允许其他类或者函数访问它的非公有成员

    友元声明:友元需要声明两次,一次在类内声明指定了访问的权限,但它不是一个通常意义上的函数声明,如果类的用户可以调用友元需要在类的外部专门对函数进行一次声明

6. 封装:

    (1) 确保用户代码不会无意间破坏封装对象的状态

    (2) 被封装的类的具体实现细节可以随时改变,而无须调整用户级别的代码

7. 可变数据成员:mutable关键字修饰的数据成员,即使在一个const成员函数内可以被修改

8. 当提供类内初始值时,必须以符号=或花括号表示 

9. 友元:类可以把其他的类定义成友元,也可以把其他类的成员函数定义成友元

    令成员函数作为友元:必须仔细组织程序结构以满足声明和定义的彼此依赖关系

    (1)首先定义Window_mgr类,其中声明clear函数,但是不能定义它,在clear使用Screen的成员之前必须先声明Screen

    (2)接下来定义Screen,包括对于clear的友元声明

    (3)最后定义clear,此时它才可以使用Screen的成员

10. 类的编译过程,先编译成员的声明,直到类全部可见后才编译函数体

     类型名要特殊处理:内层作用域可以重新定义外层作用域中的名字,即使该名字已经在内层作用域中使用过,但在类中,如果成员使用了外层作用域中的某个名字,而该名字代表一种类型,则类不能在之后重新定义该名字。

     类型名的定义通常出现在类的开始处,这样可以确保所有使用该类型的成员都出现在类名定义之后

11. 成员定义中的普通块作用域的名字查找规则 P255:

    (1) 在成员函数内查找该名字的声明,只有在函数使用之前出现的声明才被考虑

    (2) 如果在成员函数内没有找到,则在类内继续查找

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

12. 构造函数的初始值有时必不可少:

     成员变量时const或引用类型,必须将其初始化,当成员属于某种类类型且该类没有定义默认构造函数时,也必须将这个成员初始化

     随着构造函数体一开始执行,初始化就完成了,以上必须初始化的成员唯一机会就是通过构造函数初始值列表为这些成员提供初值,而不能用赋值操作初始化

ConstRef::ConstRef(int ii):i(ii), ci(ii), ri(i) {}

     初始化列表和赋值的区别事关底层效率问题,前者直接初始化数据成员,后者则先初始化再赋值

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

14. 委托构造函数:一个委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程,参数列表必须与类中另外一个构造函数匹配,受委托的构造函数的初始值列表和函数体被依次执行,然后控制权才会交还给委托者的函数体

15. 隐式的类类型转换:如果构造函数只接受一个实参,它实际上定义了转换为此类类型的隐式转换机制。但只允许一步类类型转换 P264

     在Sales_data类中,接受string和istream的构造函数分别定义了从这两种类型向Sales_data隐式转换的规则。即在需要使用Sales_data的地方,我们可以使用string或istream作为替代,但只允许一步转换

item.combine("9-999-9999999") // 错误
// (1) 把"9-999-9999999"转换成string
// (2) 再把这个临时string转换成Sales_data

     抑制构造函数定义的隐式转换:将构造函数在类内声明为explicit即可(只针对一个实参的构造函数),类外定义时不应重复出现explicit关键字

     explicit构造函数只能用于直接初始化,因为发生隐式转换的情况是当我们执行拷贝初始化(使用=),只能用显示的直接初始化

16. 类的静态成员:类的静态成员存在于任何对象之外,对象中不包含任何与静态数据成员有关的数据。静态成员只存在一份而且它被所有实例化的对象共享

     静态成员函数也不与任何对象绑定在一起,它们不包含this指针

     (1) 使用作用域运算符直接访问静态成员

     (2) 使用类的对象、引用或指针来访问静态成员

17. 不完全类型:类声明之后定义之前是不完全类型,只知道它是一个类类型,但不清楚它到底包含哪些成员

     可以定义指向这种类型的指针或引用,也可以声明(但不能定义)以不完全类型为参数或返回类型的函数,直到类被定义后数据成员才能被声明成这种类类型 

18. 定义静态成员:它们不是由类的构造函数初始化的,必须在类的外部定义和初始化每个静态成员,一个静态数据成员只能定义一次

     通常,类的静态成员不应该在类内初始化,但可以为静态成员提供const整数类型的类内初始值,其中要求静态成员必须是字面常量类型的constexpr

     即使一个常量静态数据成员在类内被初始化,通常也应该在类外定义该成员

     静态数据成员的类型可以是它所属的类类型(不完全类型)

     静态成员可以作为默认实参,非静态成员不可以

 

C++标准库

IO库

1. istream和ostream是类型,cin和cout是对应类型的对象

   istream定义了用于读写流的基本类型

   fstream定义了读写命名文件的类型

   sstream定义了读写内存string对象的类型

   标准库使我们能忽略这些不同类型的流之间的差异是通过继承机制

2. IO对象无拷贝或赋值,不能将形参或返回类型设置为流类型,只能以引用的方式传递和返回流

3. iostate是IO库定义的一个与机器无关的类型,它提供了表达流状态的完整功能,应作为一个集合来使用

    badbit表示系统级错误,如不可恢复的读写错误,一旦它被置位,流就无法使用

    failbit表示可恢复错误,如读的期望输入与实际类型不符,可以继续使用

    当到达文件结束位置,eofbit和failbit都会被置位

    goodbit的值为0表示流未发生错误

    badbit,failbit和eofbit任一个被置位则流状态条件为失败

    good和fail是标准库定义的一组函数可以查询流的状态

4. endl(输出换行),flush(无)和ends(输出一个空字符):都会刷新缓冲区

5. 如果程序崩溃,输出缓冲区不会被刷新

    关联的流,当读写被关联的流时,关联到的流的缓冲区会被刷新

    cout<<unitbuf; // 所有输出操作后都会立即刷新缓冲区 

6. 文件流:out模式open,默认的文件模式是trunc会清空文件内容,app(append)会保留文件内容并在末尾追加

7. string流:P287

    istringstream和stringstream处理字符流的输入和输出

顺序容器

(保存相同类型对象有序集合的类型,顺序容器中的元素通过位置来访问)

1. 顺序容器类型:vector,deque,list,forward_list,array,string

   deque:双端队列,支持快速随机访问,在头尾位置插入/删除速度很快

2. 迭代器:begin与end相等时,范围为空。

                不以c开头的函数都是重载过的,就是实际上有两个名为begin的成员,一个是const成员,返回容器的const_iterator类型,另一个是非常量成员,返回容器的iterator类型,当auto与begin和end结合使用时,获得的迭代器类型依赖于容器类型。

                只有顺序容器(不包括array)的构造函数才能接受大小参数 P299

                将一个容器初始化为另一个容器的拷贝:(1)可以直接拷贝整个容器<相同的容器类型,且保存的是相同的元素类型> (2)拷贝由一个迭代器对, 指定的元素范围<不要求容器类型相同,但容器中的元素类型要相容>
3. array:大小也是array的类型中的一部分,eg:array<int, 42> // 类型为42个int的数组

              array不支持普通的容器构造函数,这些构造函数都会确定容器的大小。虽然不能对内置数组类型进行拷贝或对象赋值操作,但array可以

4. swap: 只交还两个相同类型容器的内容,两个容器中的内容将会交换

    assign:从不同但相容的类型赋值,或从容器的一个子序列赋值 P302

    除了无序关联容器外的所有容器都支持关系运算(>, >=, <, <=),只有当其元素类型定义了相应的比较运算符后,才能比较

5. emplace: 一般用于元素是类类类型,构造而不是拷贝元素,其参数必须与元素类型的构造函数相匹配。emplace成员使用这些参数在容器管理内存中直接创建对象,而push_back则会创建一个局部临时对象,并将它的拷贝压入容器中。

6. front和back:这两个操作分别返回首元素和尾元素的引用,间接方法使用解引用begin返回的迭代器获得首元素的引用。首先要提前判断容器非空

7. forward_list(单链表)的特殊处理:没有简单方法来获取一个元素的前驱,在一个forward_list中添加或删除元素的操作是通过改变给定元素之后的元素来完成的,这样可以访问到被添加或删除操作所影响的元素

    forward_list没有定义insert、emplace和erase,而是定义了名为insert_after、emplace_after和erase_after的操作,定义了before_begin,它返回一个首前的迭代器,允许我们在链表首元素之前并不存在的元素之后添加或删除元素

    添加或删除元素时,必须同时关注两个迭代器(一个指向我们必须要处理的元素,另一个指向其前驱)

8. resize改变容器大小

    添加或删除元素可能使迭代器失效,使用失效的迭代器、指针或引用是严重的运行时错误

    每次改变容器的操作之后都应该正确地重新定位迭代器

    不要缓存end返回的迭代器,因为添加或删除元素后这个迭代器会失效,未定义的,作为循环条件会导致无限循环

9. string的操作非常多,涉及构造,修改,搜索,比较,数值转换等等,其中每一个函数可能有过个重载版本 P321

10. 容器适配器:标准库定义了三个顺序容器的适配器:stack、queue和priority_queue

                     这种机制就是能使某种事物的行为看起来像一种不同的类型

       其底层有普通的顺序容器构成:默认情况下,stack和queue是基于deque实现的,priority_queue是在vector上实现的,可以在创建一个适配器时将一个命名的顺序容器作为第二个类型的参数来重载默认容器类型

泛型算法

1. 大多数算法定义在algorithm中,这些算法一般不直接操作容器,而是遍历由两个迭代器指定的一个元素范围,不涉及容器元素的添加或删除

2. 接收的范围参数分别是指向要处理的第一个元素和尾元素之后位置的迭代器

   使用范围中的元素的方式不同:是否读取元素,改变元素或是重排元素顺序

3. 只读算法:find、count、accumulate、equal

4. 写容器元素的算法:fill,fill_n,copy,replace_copy(会保留原序列不变),unique,for_each

    其中向目的位置迭代器写入数据的算法假定目的位置足够大,能容纳要写入的元素,如果容器为空,仍要用fill_n去写入将导致未定义的后果

     back_inserter(插入迭代器)会保证算法有足够元素空间来容纳输出数据,插入迭代器是一种向容器中添加元素的迭代器 P341

                         接受一个指向容器的引用,返回一个与该容器绑定的插入迭代器,当我们通过此迭代器赋值时,赋值运算符会调用push_back将一个具有给定值的元素添加到容器中

     copy的目的序列至少要包含与输入序列一样多的元素

5. 谓词:一元、二元谓词。指一个可调用的表达式,其返回结果是一个能用作条件的值。

6. lambda表达式:

    为何需要:有时我们希望进行的操作需要更多的参数,超出了算法对谓词的限制

    表示一个可调用的代码单元,一个未命名的内联函数

    find_if接受一元谓词,传递给find_if的任何函数都必须严格接受一个参数,这时如果需要输入多个参数需要考虑使用lambda

    可调用对象:函数、函数指针、重载了函数调用元素符的类、lambda表达式

    [capture list](parameter list) -> return type { function body }

    其中捕获列表是一个lambda所在函数中定义的局部变量的列表(通常为空),才能在lambda函数体中使用该变量。可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体,lambda不能有默认参数,所以调用的实参数目永远与形参数目相等

    捕获的值可以是值捕获或引用捕获,当以引用方式捕获一个变量,必须保证在lambda执行时变量时存在的

    隐式捕获:=(值),&(引用),显式和隐式混用,显式捕获的变量必须使用与隐式捕获不同的方式

    mutable关键字,能改变被捕获的变量值,这里区别于引用捕获 P352

7. 标准库bind函数:对于捕获局部变量的lambda,用函数来替换需要借助bind函数,一种函数适配器

                          它接受一个可调用函数,生成一个新的可调用对象来"适应"原对象的参数列表

                          auto newCallable = bind(callable, arg_list); // 当我们调用newCallable时,newCallable会调用callable,并传递给它arg_list中的参数

          占位符时bind函数输出对象的参数,_n占位符都定义在一个名为placeholders的命名空间,本身定义在std。using namespace std::placeholders

            可以用bind修正参数的值(绑定给定可调用对象中的参数或重新安排其顺序)

          bind的不是占位符的参数默认被拷贝到bind返回的可调用对象中,如果遇到必须引用的形式,需要借助ref函数或cref函数

9. 迭代器:插入迭代器(back_inserter, front_inserter, inserter(t,p)),流迭代器,反向迭代器,移动迭代器

              front_insert返回的迭代器一直指向插入后的首元素,这与inserter插入迭代器完全不同,inserter会将元素插入到iter原来所指向的元素之前的位置

 

posted @ 2019-12-26 23:34  demianzhang  阅读(229)  评论(0编辑  收藏  举报