• C++——基础学习中的一些重点笔记


    1.异常处理:

      a)在try块中,一旦发生错误,立即抛出异常,然后转入catch块中(try块中剩余的代码不会被执行)。

      b)如果throw异常不去捕获,会造成程序core dump(异常处理没有对应的代码块,同样会引起core dump)。

      c)对于不同类型的异常,可以采用不同的catch块分别对其进行处理,也可以采取统一处理(越是通用的异常处理handle,越应该放到最后)。

      d)异常在某函数中没有相应的处理,就向外继续throw(函数剩余的代码不会执行,程序会立刻退出该程序)。

    2.传引用:

      a)C语言中的传值,实际上是在函数调用时,将实参的value拷贝给形参(如果传递的类型较复杂,可能造成较大的开销)。

      b)在C++中,我们采用传引用来避免对象复制的开销(既可以修改原对象,又可以避免复制的开销)。

      c)如果一个函数的形参为A &a,这意味着该函数不仅可以改变a的值,而且期望改变a的值

      d)const引用表示常亮,是一种保护语义,而非const引用,是一种修改语义

      e)绝对不要返回局部对象的引用或指针

    3.宏函数与内联函数:

      a)宏函数:

        在预处理期间被文本展开。

        没有函数调用的开销。

        缺点是如果不被调用,编译器就无法为其检查错误(编译器根本就找不到这段代码,因为该段代码预处理期间就消失了)。

      b)内联函数:

        可以看做高级的宏函数。

        在编译期间被内联展开。

        没有函数调用的开销。

        内联函数需要编译器为其检查语法错误。

    4.函数的唯一标示与重载:

      a)C++中函数的唯一标示——函数签名,不仅包括函数的名字,还包括参数列表(不包括返回值)。

      b)构成函数重载的几点要素:

        函数名称(包含类名)。

        形参表(形参的类型与个数)。

        const属性(类的成员属性)。

    5.IO流:

      a)C++中IO有三种状态: bad 、fail 、 eof。以cin为例:

        如果输入非法数据,cin的 fail 置为1.

        如果输入结束,cin的fail与eof都置为1.

      b)IO流的修复:

        如果是文件结束,那么只需调用clear函数即可。

        如果是非法数据,除了clear,还需要清空非法输入(如果不清空,那么数据会一直停留在缓冲区中)。

        调用以下函数:

    cin.ignore(std::numeric_limits < std::streamsize > ::max(), '
    ');

    6.类:
      a)一个类分为两大部分,数据与函数(数据可以看做是对象的属性,函数可以看做是对象的行为)。

      b)C++类的成员函数,均还有一个隐式的参数,指向调用该函数的对象的指针,即this指针。

      c)构造函数也可以重载。

      d)初始化式中的语句为变量的初始化,而构造函数块中的语句为变量的赋值

      e)初始化列表中的初始化顺序,与class中变量的定义顺序有关,而与初始化列表中的声明顺序无关

      f)尽可能使用初始化列表代替函数体内的赋值

        当class中的成员只能被初始化,而不能被赋值时(例如const成员或引用成员),我们必须使用初始化列表

        当class中的成员的初始化必须由我们手工控制,而不能交给系统默认初始化时(通常原因是该成员没有默认构造函数),我们必须使用初始化列表

      g)class内不写访问标号,默认为private,而struct默认为public。这是二者唯一的区别

      h)C++中的friend声明,是一种单向关系。例如A 声明friend class B表示B是A的friend,可以访问A的private数据,但是反过来则不成立。

    7.顺序容器:

       a)用一个容器去初始化另一个容器,要求类型完全一致(容器类型相同,元素类型相同)。

      b)用迭代器范围去初始化容器,只要求迭代器元素与容器元素类型匹配即可(不要求容器类型相同)。

      c)凡是传入迭代器作为指定范围的参数,可以使用指针代替

      d)凡是放入vector中的元素,必须要求具备复制与赋值能力

      e)vector迭代器持续有效,除非:

        使用者在较小的索引位置插入或删除元素。

        由于容量的变化引起的内存重新分配。

      f)用erase删除元素记得接收返回值,同时最好使用while循环

      g)vector的几个与容量有关的函数:

        size(), 表示元素数目。

        resize(), 调整元素的数目。

        capacity(),表示可容纳数目。

        reserve(), 调整容量。

      h)vector的内存增长是按照成倍增长

      i)vector与list的区别:

        vector采用数组实现,list采用链表实现。

        vector支持随机访问,list不提供下标

        大量增加删除的操作适合使用list。

    8.map和set:

      a)pair不是容器,而是代表一个key-value键值对。

      b)map是存储pair对象的容器,只是存储方式与vector不同,map采用的是二叉排序树存储pair,一般是红黑树

      c)map使用下标访问时,如果key不存在,那么会在map中添加一个新的pair,value为默认值。

      d)map的key必须具有小于操作符operator<。

      e)使用insert插入map元素时,如果失败,则不会更新原来的值

      f)map与set的比较:

        二者均使用红黑树实现。

        key需要支持<操作。

        map侧重于key-value的快速查找。

        set侧重于查看元素是否存在。

      g)map和set中的元素都无法排序

    9.reverse迭代器:

      a)在逻辑上,rbegin指向最后一个元素,rend指向第一个元素的前一个位置。

      b)在实际实现上,rbegin指向最后一个元素的下一个位置,rend指向第一个元素。

      c)reverse迭代器的物理位置比逻辑位置增加了1.

      d)采用这种实现的好处是:将iterator转化为reverse_iterator之后的区间,与之前的区间恰好相反,但内容相同。

      e)reverse迭代器不能用于erase函数。删除的正确方式是:

    it = string::reverse_iterator(s.erase((++it).base()));


    10.深拷贝与浅拷贝:

      a)含有指针成员变量的类在复制时,有两种选择: 

        复制指针的值,这样复制完毕后,两个对象指向同一块资源,这叫做浅拷贝

        复制指针指向的资源,复制完毕后,两个对象各自拥有自己的资源,这叫做深拷贝

      b)赋值操作符,需要先释放以前持有的资源,同时必须处理自身赋值的问题

      c)复制构造函数、赋值运算符以及析构函数,称为三法则,一旦提供了其中一个,务必提供另外两个。以string为例:

        涉及到深拷贝、浅拷贝问题,所以需要提供拷贝构造函数。

        为了保持一致,赋值运算符也应该实现深拷贝。

        既然实现了深拷贝,那么必定申请了资源(内存),所以需要析构函数手工释放资源。

      d)一个空类,编译器默认提供无参构造函数、拷贝构造函数、赋值运算符以及析构函数,一共四个函数

      e)禁止一个类复制与赋值能力的方法:

        将copy函数与赋值运算符设为private。

        只声明,不实现。

      f)复制和赋值必须保证在程序的语义上具有一致性

      g)如果一个类,不需要复制与赋值,那就禁用这种能力,可以避免大量潜在的bug。

      h)如果一个类,实现了像value一样的复制和赋值能力(意味着复制和赋值后,两个对象没有任何关联,或者逻辑上看起来无任何关联),那么就称这个类的对象为值语义;如果类不能复制,或者复制后对象之间的资源归属纠缠不清,那么称为对象语义,或引用语义

     11.操作符的重载:

      a)A(int a)这样的构造函数实现了一种类型转化能力,加上explicit可以禁用这种转化。

      b)+ 或者>、< 、==之类的操作符重载最好采用friend的形式。

      c)下标操作符的重载最好提供const和非const的版本。

      d)>>操作符的重载,应该注意处理输入失败的情况。

     12.智能指针:

      a)构造函数接收堆内存

      b)析构函数释放内存

      c)必要时要禁用值语义

      d)重载*和->两个操作符

      e)智能指针是个对象,但其行为表现的像一种指针,它有三种操作符:

        .   调用的是智能指针这个对象本身。

        *  调用的是解引用出持有的对象。

        -> 调用的是持有对象内部的成员

      f)常用的智能指针:

        Boost库提供了scoped_ptr和shared_ptr。

        C++11内置了unique_ptr和shared_ptr

        C++98提供了auto_ptr(已经被废弃)。

     13.函数模板:

      a)函数模板可以看做是一种代码产生器,往里面放入具体的类型,得到具体化的函数。

      b)模板的编译分为:

        实例化之前,先检查模板本身的语法是否正确。

        根据函数调用,去实例化代码,产生具体的函数。

      c)没有函数调用,就不会去实例化模板代码,在目标文件obj中找不到模板的痕迹。

      d)一个非模板函数可以和一个同名的函数模板同时存在,构成重载,同样的两个模板函数可以因为参数不同构成重载。

      e)模板函数重载时,选择函数版本的特点:

        当条件相同时,优先选择非模板函数

        在强制类型转化,与实例化模板之间,优先选择实例化模板。

        实例化版本不可行,则去尝试普通函数的转化,

        参数是指针时,优先选择指针版本。

        总之,尽可能采用最匹配的版本

      f)在模板函数重载中,不要混合使用传值和传引用,尽可能使用传引用

      g)传值和传引用,对于参数来说,本质区别在于是否产生了局部变量; 对于返回值来说,本质区别在于返回时是否产生了临时变量。

     14.类模板:

      a)模板类也类似于代码产生器,根据用户输入的类型不同,产生不同的class。  

      b)模板类的编译:

        检查模板class的自身语法。

        根据用户输入的指定类型,去实例化一个模板(注意,不是实例化所有代码,而是仅仅实例化用户调用的部分)。

      c)模板的缺点是代码膨胀,编译速度慢,带来的好处是运行速度快。

      d)将类模板拆分为.h和.cpp文件,构建时产生了链接错误。原因在于:

        模板的调用时机和代码的实例化必须放在同一时期,

        编译.cpp时,编译器找不到任何用户调用的代码,所以得到的.o文件为空

        编译main.cpp时,编译器获取用户的调用,了解应该去实例化哪些代码,但是这些代码存在于另一模块,所以推迟到链接期间。

        链接期间,由于以上原因,需要链接的代码并没有产生。

      e)模板参数不仅可以使类型,还可以为数值,需要注意的是:数值也是类名的一部分,例如Stack<int, 5>和Stack<int, 10>不是同一个类,二者的对象无法相互赋值。

      f)在模板代码中编写: T::value_type * p;  编译器将其可能解释为乘法,为了显示的告诉编译器这是定义一个变量,需要加上typename

        typename T::value_type * p;

      g)对于非引用类型的参数,在实参演绎的过程中,会出现从数组到指针的类型转换,也称为衰退

     15.原生数据(POD):

      a)在C++中,非POD变量经过两个步骤生成:

        申请原始内存(字节数组)。

        在内存上执行构造函数。

      b)POD指的是原生数据,包括int、double等基本数据,以及包含基本数据的结构体(struct、class),但是class或者struct不能包含自定义的构造函数,不能含有虚函数,更不能包含非POD数据。

      c)对于POD数据,可以通过mencpy系列函数,直接操控内存达到目的。C语言中的数据都是原生数据

      d)POD数据仅仅申请内存就可以使用,不需要执行特殊的构造工作(可以直接使用malloc)。

      e)非POD数据只能使用new,不能使用malloc。

     16.继承:

      a)protected仅限于本类和派生类可以访问。

      b)经过public继承,父类中的private、protected、public在子类中的访问权限为:不可访问、protected、public。

      c)通过子类对象去调用函数:

        父类中的非private函数,可以由子类调用。

        子类额外编写的函数,可以正常使用。

        子类中含有与父类同名的函数,无论参数列表是否相同,调用的始终都是子类的版本(如果想执行父类的版本,必须显示指定父类的名称)。

      d)父类与子类含有同名的函数,那么通过子类对象调用函数,总是调用子类的版本,这叫做子类的函数隐藏了父类的函数

      e)子类对象中含有一个父类的无名参数

      f)构造子类对象时,首先需要调用父类的构造函数,其次是子类的构造函数,析构的顺序与之相反

      g)子类的对象可以赋值给父类的对象,其中子类多余的部分被切除,这叫做对象的切除问题。但是,父类的对象赋值给子类对象是非法的

      h)派生类的构造顺序:

        构建基类对象。

        构造成员对象。

        调用自己的构造函数。

        析构顺序与之相反

      i)子类在构造对象时,通过初始化列表,指定如何初始化父类的无名对象。而拷贝构造函数用子类去初始化父类对象,赋值运算符中则是显示调用父类的赋值运算符。

      j)public继承,塑造的是一种"is-a"的关系。在继承体系中,从上到下是一种具体化的过程,而从下到上则是抽象、泛化的过程。

      k)一个类包含另一个类,叫做"has-a"的关系,也称为类的组合。

      l)OOP的第二个性质称为继承,第三个性质动态绑定

      m)基类的指针或者引用可以指向派生类的对象。

      n)通过基类指针调用函数:

        基类中存在的函数,可以调用。

        子类额外添加的函数,不可以。

        父子类同名的函数,调用的是父类的版本。

        以上的原因是:通过基类指针调用的函数,编译器把基类指针指向的对象视为基类对象。

      o)派生类指针可以指向基类指针,这叫做"向上塑形",这是绝对安全的,因为继承体系保证了"is-a"的关系。

       然而,基类指针转化为派生类指针则需要强制转化,而且需要人为的保证安全性,"向下塑形"本质上是不安全的

      p)静态绑定:编译器在编译期间根据函数的名字和参数,决定调用哪一段代码,这叫做静态绑定,或早绑定。

       动态绑定:编译器在编译期间不确定具体的函数调用,而是把这一时机推迟到运行期间,这叫做动态绑定,或晚绑定。

      q)C++中触发动态绑定的条件:

        virtual(虚函数),基类的指针或者引用指向了派生类的对象。

      r)触发多态绑定后,virtual函数的调用不再是编译期间确定,而是到运行期间,根据基类指针指向的对象的实际类型,来确定调用哪一函数。

      s)动态绑定的执行流程:运行期间,因为触发了动态绑定,所以先去寻找对象的vptr(虚指针),根据vptr找到虚函数表(vtable),里面存储着虚函数的代码地址,根据vtable找到要执行的函数

      t)子类在继承父类的虚函数的时候,如果对函数体进行了改写,那么子类的虚函数版本会在vtable中覆盖掉父类的版本,这叫做函数的覆盖

      u)虚函数具有继承性,如果子类的同名函数,名字与参数与父类的虚函数相同,且返回值相互兼容,那么子类中的该函数也是虚函数。

      v)函数的重载、隐藏和覆盖:

        隐藏:凡是不符合函数覆盖的情形,都属于函数的隐藏。

          父类中的非虚函数,子类中的函数名字和参数与其一致。

          父类中的非虚函数,子类对其参数或返回值做了改动。

          父类中的虚函数,但是子类中对其参数做了改动,或者返回值不兼容。

        覆盖:触发多态的情形。

          父类中的虚函数,子类中的函数名字和参数与其一致,且返回值相互兼容。

      w)不要改动从父类继承而来的非virtual函数(不要触发函数的隐藏)

      x)如果父类中的某函数为虚函数,那么两个选择:

        不做任何改动,采用默认实现。

        覆盖父类的实现,提供自己的行为。

      y)virtual void run() = 0;声明了一个纯虚函数,此函数只有声明,没有实现,包含了纯虚函数的类,称为抽象类

      z)子类在继承抽象类后,必须将其中所有的纯虚函数全部实现,否则仍是一个抽象类。

        在继承体系中,应该把基类的析构函数设为virtual

    17.生产者消费者问题:

      a)互斥是一种竞争关系,同步是一种协作关系

      b)生产者消费者问题需要:

        一个互斥锁:保证对缓冲区的互斥访问。

        两个Condition:一个是生产者通知消费者取走物品,另一个则是消费者通知生产者可以放入物品。

      c)pthread_cond_wait:

        首先释放锁,等待,重新抢锁(必须在加锁的条件下才可调用该函数)。

      d)pthread_cond_signal通常用来通知资源可用

      e)pthread_cond_broadcast一次通知多个线程,通常用来通知状态的改变。滥用broadcast会导致"惊群"问题。

      f)使用pthread_cond_wait必须采用while判断,原因在于:

        如果采用if,最多判断一次。

        线程A等待数据,阻塞在full上,那么当另一个线程放入产品时,通知A去拿数据,此时另一个线程B抢到锁,直接进入临界区,取走资源.A重新抢到锁,(因为采用的是if,所以不会判断第二次)进去临界区时,已经没有资源。

        防止broadcast的干扰,如果获得一个资源,使用broadcast会唤醒所有等待的线程,那么多个线程被唤醒,但最终只有一个能拿到资源,这就是所谓的"惊群效应"

    18.线程类的封装:

      a)MutexLock和Condition利用了RAII,利用构造函数和析构函数自动完成资源的申请和释放

        RAII:智能指针将对资源的获取放在构造函数中,资源的释放置于析构函数中,这样,当智能指针管理资源时,一旦智能指针对象销毁,资源就可以自动释放,实现了资源的自动化管理,这种技术叫做"资源获取即初始化",即RAII。

      b)MutexLock、Conditio和Thread涉及到系统资源,这些类全为不可复制的

      c)线程在默认情况下是joinable(可结合状态),需要手工调用join函数,也可以设置为detachable(分离状态),线程运行完毕自动消亡。

      d)Thread类采用static函数作为pthread_create的回调函数。原因在于:

        普通成员函数含有一个隐式参数this,所以函数指针类型与(void*)(*)(void *)不匹配。

      e)Linux中的线程,本质上是一个轻量级进程,拥有自己唯一的tid,可以编写gettid获取,可以通过syscall(SYS_gettid)获取。

     19.类型转化:

      a)static_cast发生在编译期间,如果转化不通过,那么编译错误,如果编译无问题,那么转化一定成功。static_cast仍具有一定风险,尤其是向下塑形,将基类指针转化为子类指针时,指针可以转化,但是指针未必指向子类对象。

      b)dynamic_cast发生在运行期间,用于将基类指针或引用转化为派生类的指针或引用,如果成功,返回正常的指针或引用,如果失败,返回(NULL),或者抛出异常.

      c)typeid运算符能够识别类型,如果要识别的类型不是class或者不含virtual函数,那么typeid指出静态类型。如果class含有virtual函数,那么typeid在运行期间识别类型

      d)typeid和dynamic_cast称为运行时类型识别(RTTI)。

  • 相关阅读:
    解决Spring+Quartz无法自动注入bean问题
    MacBook PRO蓝牙无法搜索设备
    解决HP打印机错误:Couldn't open fifo
    SQL调优
    ALTER SEQUENCE 修改序列解决唯一约束冲突 unique constraint violated
    Linux Unix 环境变量设置实例
    DB2解除锁表
    ExtJS远程数据-本地分页
    安装mysql
    celery使用
  • 原文地址:https://www.cnblogs.com/gjn135120/p/4020086.html
Copyright © 2020-2023  润新知