C++核心编程—笔记

C++笔记

  1. C++概述

    • C++两大编程思想
      • 面向对象:三大特性
        • 封装
        • 继承
        • 多态
      • 泛型编程
  2. 双冒号作用域运算符

    • ::如果前面没有任何作用域,代表使用全局作用域
  3. 命名空间

    • 用途:解决命名冲突

    • 可以存放变量、函数、结构体、类 …

    • 必须声明在全局作用域下

    • 可以嵌套命名空间

    • 是开放的,可以随时向命名空间下添加新的成员,同名命名空间会合并

    • 可以匿名

      namespace{
          int a = 100; //相当于在变量前加了一个关键字 static
      }
      
    • 可以起别名

      namespace veryLongName{
          int a = 100;
      }
      void test(){
          namespace veryShortName = NameveryLongName;
          cout << veryShortName::a << endl;
      }
      
  4. using

    • using声明:当就近原则和using声明同时出现时,需要避免二义性
    • using编译指令:如果有就近原则,那么优先使用就近原则;存在多个并且有同名出现,需要加作用域区分
  5. C++对C语言增强

    • 全局变量检测增强

    • 函数检测增强

      • 声明函数返回值
      • 形参类型 检测
      • 返回值检测增强
      • 调用时参数传参个数检测增强
    • 类型检测增强 malloc 返回void* C++必须强转

    • struct增强

      • C++可以放入函数
      • 使用时C++可以省略关键字struct
    • 三目运算符

      • C语言返回值
      • C++返回变量
    • const增强

      • 全局const修饰变量 受到常量区保护,即使语法通过,运行也会报错(C和C++效果一样)

      • 局部const修饰的变量可以间接修改成功(C可修改,C++不可修改)

        {
        	const int b = 100;
        	int *p = &b;
        	*p = 200;
        }
        
      • C++中const修饰变量可以用来初始化数组 int arr[b];

      • 连接属性

        • C默认外部链接属性
        • C++默认内部链接属性
    • const分配内存

      • 对const修饰变量取地址时会分配内存
      • const前加external关键字后也会分配内存
      • 使用变量来初始化const修饰变量
      • 对于自定义数据类型,也会分配内存
  6. 引用

    • 给一个内存空间起别名

    • 一旦初始化后不可修改指向了

      • 本质是指针常量,所以必须初始化,并且初始化后不可修改
    • 先定义数组类型,在定义引用

      int arr[10];
      int (&Arr)[10] = arr;
      
  7. 参数传递方式

    • 值传递
    • 地址传递
    • 引用传递
  8. 引用注意事项

    • 不要返回局部变量的引用
    • 如果函数返回值是引用,那么这个函数可以作为左值进行运算
  9. 指针的引用

    • 通过引用技术 可以简化指针
  10. 常量的引用

    • 类型前加const

      int & ref = 10; //错误
      const & ref = 10; //编译会优化,类似于: int temp = 10; const int & int ref = temp;
      
    • 使用场景:修饰函数中形参,防止误操作

    • class + 名 { 成员 }
    • 类构成:成员函数和成员属性
    • 作用域:public、protected、private
      • public: 类内可以访问 类外也可以访问
      • protected:类内可以访问 类外不可以访问(子类可以访问)
      • private: 类内可以访问 类外不可以访问(子类不可以访问)
  11. 内联函数

    • 内联函数引出–宏函数
      • 必须保证运算完整性,加括号
      • 即使加括号,有些情况依然和预期结果不符合
    • 函数的声明和实现必须同时加inline
    • 内部成员函数 默认前面加了inline关键字
    • 内联限制(编译器决定是否为内联)
      • 不能存在任何形式的循环语句
      • 不能存在过多的条件判断语句
      • 函数体不能过大
      • 不能对函数进行取地址操作
  12. 函数默认参数

    • 在形参后面=默认值
    • 所有默认参数在参数列表最右边
    • 函数的声明和实现只能有一个有默认参数
  13. 占位参数

    • 函数参数列表中只写类型,调用时必须要传入参数
    • 占位参数也可以有默认参数
  14. 函数重载

    • 满足函数重载条件
      • 作用域必须相同
      • 函数名称相同
      • 函数的参数类型、个数或顺序不同
      • 返回值不可以作为重载的条件
    • 引用的重载函数
      • 加const和不加const也可以作为重载条件
    • 函数重载碰到函数参数默认值时,避免二义性
  15. extern “C”

    • 函数重载原理,编译器在底层会将函数名字做两次修饰,方便内部访问函数

    • 在C++下运行C语言文件

      #ifdef __cplusplus
      extern "C" {
      #endif
      
          ...
          
      #ifdef __cplusplus
      }
      #endif
      
  16. 类的封装

    • C语言的封装
      • 缺点:C语言下没有做类型转换的检测;将属性和行为分离
    • C++封装
      • 将属性和行为作为一个整体,来表现生活中的事和物
      • 将成员加权限控制
    • struct和class区别在于默认权限
      • struct:public
      • class:private
  17. 构造和析构

    • 构造
      • 没有返回值,也不写void,函数名与类型相同
      • 可以有参数,可以发生重载
      • 由编译器自动调用,不需要手动调用,而且编译器只会调用一次
    • 析构
      • 没有返回值,也不写void,函数名与类型相同 在函数名前加~
      • 不可以有参数,不可以发生重载
      • 由编译器自动调用,不需要手动调用,而且编译器只会调用一次
    • 构造函数分类
      • 按照参数进行分类
        • 有参构造:Person(int age){ }
        • 无参构造(默认构造):Person( ){ }
      • 按照类型分类
        • 普通构造
        • 拷贝构造:Person(const Person &p){ }
    • 构造函数调用规则
      • 系统默认提供三个函数:默认构造函数、析构函数、拷贝构造函数
      • 如果提供了有参构造函数,那么系统就不会提供默认构造函数,但依然提供拷贝构造函数
      • 如果提供了拷贝构造函数,那么系统就不会提供其他的普通构造函数了
  18. 深拷贝和浅拷贝

    • 系统提供拷贝构造函数只会做简单的值拷贝
    • 如果类中有属性开辟到堆区,那么在释放的时候,由于浅拷贝问题导致堆区内容会重复释放,程序崩溃
  19. 初始化列表

    • 构造函数名( ) : 属性 ( 值 ) , 属性 ( 值 ) …
  20. 类对象作为类成员

    • 当其他类作为本来成员,先构造其他类对象,再构造自身,释放顺序与构造相反
  21. explicit关键字

    • 防止隐式类型转换方式来初始化化对象
  22. new和delete运算符

    • malloc和new、free和delete区别
      • new、delete是运算符,malloc、free是库函数
      • malloc返回void *, new返回的是new出来的对象指针
      • malloc需要判断是否开辟成功,而new内部做了操作(内部会malloc数据在堆区,判断内存是否分配成功,调用构造函数)
      • malloc不会调用构造函数, 而new调用构造函数
      • malloc对应释放是free,new对应释放是delete
    • 注意
      • 不要用void *去接受new出来的对象,原因是不能够释放
      • 用new在堆区创建数组,类中必须要存在默认构造函数,否则无法创建
      • 如果是数组释放的时候要在delete后加 [ ]
  23. 静态成员

    • 静态变量

      • 数据共享
      • 在编译阶段分配内存
      • 在类内声明、类外进行初始化
      • 访问方式:对象访问、类名访问
      • 也有访问权限
    • 静态函数

      • 访问方式:对象访问、类名访问
      • 静态成员函数不可以访问非静态成员变量
      • 静态成员函数可以访问静态成员变量,因为都是共享数据
      • 非静态成员函数可以访问静态成员变量和非静态成员变量
      • 也有访问权限
  24. 单利模式

    • 主席类:一个类中只有唯一的一个实例对象
    • 数据共享,而且只许拿到一个主席的对象的指针即可
  25. 成员变量和成员函数存储

    • 成员属性算在类大小中
    • 成员函数、静态成员变量、静态成员函数并不算在类大小中
  26. this指针

    • this指针指向的是被调用的成员函数所属的对象
    • *this对象本体
    • this解决名称冲突
  27. 空指针访问成员函数

    • 如果是一个空指针
    • 可以访问没有this的一些成员函数
  28. 常函数和常对象

    • this指针本质

      • 指针常量 type * const this;
      • 指针的指向不可以修改,指针指向的值可以修改
    • 常函数:

      • 成员函数后面加const,不可以修改成员属性
    • 常对象:

      • 不可以修改内部属性

      • 常对象只能调用常函数,不能调用普通成员函数

    • 如果特殊要修改常函数或常对象:在属性前加mutable修饰

  29. 友元

    • 全局函数作为友元函数
      • 全局函数作为本类友元函数,可以访问私有内容
        在类内部写入:friend + 函数声明
    • 类作为友元类
      • 告诉编译器一个类是本类的好朋友,那么他就可以访问到里面私有内容
      • 在类内部写入:friend class 类名
    • 成员函数作为友元函数
      • 告诉编译器一个类中的成员函数是本类的好朋友,那么他就可以访问到里面私有内容
  30. 加号运算符重载

    • 对于内置数据类型,编译器知道该如何进行运算
    • 但是对于自定义数据类型,编译器并不知道该如何运算
    • 利用运算符重载可以解决问题
    • 实现两个类数据类型相加
      • 分别利用成员函数和全局函数来实现
      • 成员函数本质 p1.operator+(p2)
      • 全局函数本质operator+(p1,p2)
      • 简化p1+p2
  31. 左移运算符重载

    • 对于内置数据类型,编译器知道如何cout进行<<运算输出
    • 对于自定义数据类型,无法输出
    • 重载左移
      • 利用成员函数:失败,原因不能让cout在左侧
      • 利用全局函数:ostream& operator<<(ostream& cout, class_name &p)
  32. 递增运算符重载

    • 前置++
      • 返回引用,class_name & operator++( )
    • 后置++
      • 返回值,class_name operator++(int )
    • 前置++的效率略高于后置++,原因是前置不会调用拷贝构造函数
  33. 智能指针(指针运算符重载)

    • 智能指针:用来托管堆区创建的对象的释放
    • 如果想让sp对象创建当做一个指针去对待,需要重载-> *
  34. 赋值运算符重载

    • 系统会默认给一个类添加4个函数:默认构造、拷贝构造、析构、operator=
    • 由于系统提供的operator会进行简单的值拷贝,导致如果属性中有堆区的数据,会进行重复释放
    • 解决方案:需要重载operator=
    • 返回值 class_name & operator=(const class_name & p)
  35. [ ]重载

    • 案例

      int& class_name::operator[](int index){
          return this->pAddress[index];
      }
      
  36. 关系运算符重载

    • 重载 == 和 !=
    • 实现两个自定义数据类型对比操作
  37. 不要重载 && 和 ||

    • 原因:无法实现短路规则
    • 总结 ( )、[ ]、->、= 只能卸载成员函数中进行重载
    • << 和 >> 通常写在全局函数配合友元进行重载
  38. 继承

    • 基本语法

      • 语法:class 子类:继承方式 父类
      • 父类:基类
      • 子类:派生类
    • 继承好处:可以减少重复代码出现

    • 继承方式

      • 公共继承
        • 父类中public到子类中public
        • 父类中protected到子类中protected
        • 父类中private到子类中private
      • 保护继承
        • 父类中public到子类中protected
        • 父类中protected到子类中protected
        • 父类中private到子类中 访问不到
      • 私有继承
        • 父类中public到子类中private
        • 父类中protected到子类中private
        • 父类中private到子类中 访问不到
    • 构造和析构

      • 先调用父类构造,在调用子类构造,析构顺序和构造相反
      • 可以利用初始化列表方法显示制定出调用父类的哪个构造函数
    • 同名成员处理

      • 如果子类和父类拥有同名成员,优先调用子类成员,可以通过作用域调用父类的成员
      • 同名成员函数,子类会隐藏掉父类中的所有版本,如果想调用父类中的其他版本,加上作用域即可
    • 同名静态成员处理

      • 如果子类和父类拥有同名成员,优先调用子类成员,可以通过作用域调用父类的成员
      • 同名成员函数,子类会隐藏掉父类中的所有版本,如果想调用父类中的其他版本,加上作用域即可
      • 访问方式有两种
        • 通过对象进行访问
        • 通过类名进行访问
    • 多继承

      • class 子类:继承方式 父类1, 继承方式 父类2
      • 当两个父类中有同名的成员被子类继承后,调用时候需要加上作用域区分
    • 菱形继承(Animal父类,Sheep和Tuo同时继承Animal,SheepTuo进行多继承,父类 Sheep和Tuo)

      • 问题1:访问父类中的数据,需要加作用域区分具体数据
      • 问题2:由于菱形继承导致继承的数据有一份浪费
      • 解决方案:利用虚继承 virtual Animal类属于虚基类
      • 在Sheep和Tuo类中继承的内容为vbptr 虚基类
        • v:virtual
        • b:base
        • ptr:pointer
      • vbptr指针 指向虚基类表 vbtable
      • vbtable中有偏移量,通过偏移量可以找到唯一的一份数据
  39. 多态

    • 静态多态 – 函数重载、运算符重载
    • 动态多态 – 父子之间继承 + 虚函数
    • 动态多态满足条件
      • 父类中有虚函数
      • 子类重写父类的虚函数
      • 父类的指针或引用指向子类的对象
    • 重写:子类重写新实现父类中的虚函数,必须返回值、函数名、参数一致才成为重写
    • 子类在做重写的时候,可以不加关键字virtual
    • 多态原理
      • 当父类中存在虚函数后,内部发生结构变化
      • 多了指针 vfptr 虚函数表指针 – 指向 虚函数表 vftable
      • 虚函数表内部记录着虚函数的地址
      • 当子类发生重写后,会修改子类中的虚函数表中的函数地址,但是并不会影响父类中的虚函数表
    • 多态好处
      • 对扩展提高
      • 组织性强
      • 可读性强
    • 如果父类中有虚函数,子类并没有重写父类的虚函数,那么这样的代码毫无意义(没有用到多态带来的好处,反而内部结构变的复杂了)
  40. 抽象类和纯虚函数

    • 纯虚函数 语法:virtual void func( ) = 0;
    • 如果一个类中有纯虚函数,那么这个类就无法实例化对象
    • 有纯虚函数的类,也称为 抽象类
    • 如果子类继承了抽象类,那么子类必须重写父类的纯虚函数,否则子类也属于抽象类
  41. 虚析构和纯虚析构

    • 如果子类中有属性创建在堆区,那么多态情况下,不会调用子类的析构代码,导致内存泄漏
    • 解决方案:利用虚析构或者纯虚析构
    • 虚析构:在析构前加 virtual
    • 纯虚析构 virtual ~函数名() = 0
    • 纯虚析构类内声明、类外必须要实现
    • 如果一个类中有了纯虚析构函数,那么这个类也属于抽象类
  42. 向上和向下类型转换

    • 父类转子类 向下类型转换 不安全
    • 子类转父类 向上转换安全
    • 如果发生多态,总是安全的
  43. 重载 重写 重定义

    • 重载 函数重载
      • 同一个作用域 函数名相同
      • 参数 个数 类型 顺序不同满足条件
      • 返回值不可以作为重载条件
    • 重写
      • 继承关系
      • 父类中有虚函数
      • 子类可以重写父类中的函数,返回值、函数名、参数类别都一致
    • 重定义
      • 继承关系
      • 非虚函数 子类重新定义 父类中同名的成员函数
  44. 函数模板

    • template 告诉编译器 T是万能数据类型,下面紧跟的函数或者类中出现 T类型,不要报错
    • 调用模板函数
      • 自动类型推到,必须让编译器推导出一致 T类型才可以使用模板
      • 显示指定类型,显示的告诉编译类型 T的类型
    • 模板使用的时候必须要告诉编译器 T是什么类型,否则无法使用
  45. 普通函数和函数模板的区别和调用规则

    • 区别
      • 普通函数可以隐式类型转换
      • 函数模板如果是自动类型推到的使用,是不可以发生隐式类型转换
    • 调用规则
      • 如果函数模板和普通函数都可以实现调用,那么优先调用普通函数
      • 可以通过空参数列表语法来强制调用函数模板
      • 函数模板也可以发生函数重载
      • 如果函数模板可以产生更好的匹配,那么优先使用的是函数模板
  46. 模板机制

    • 编译器并不是把函数模板处理成能够处理任何类型的函数
    • 函数模板通过具体类型产生不同的函数(产生了 模板函数)
    • 编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译
  47. 模板局限性以及利用具体化技术解决

    • 模板并不是真正的通用代码,对于一些自定义的数据类型,模板有时不能实现效果
    • 可以通过具体化实现对自定义数据类型进行操作
    • template<> bool myCompare(Person & a, Person & b)
  48. 类模板

    • template下面紧跟的是个类,那么这个类成为类模板
    • 类模板和函数模板区别
      • 类模板使用时不可以用自动类型推导,必须显示指定类型
      • 类模板中的类型可以有默认参数
    • 泛型编程 – 体现在模板技术 – 特点 将类型参数化
    • 类模板中的成员函数创建时机
      • 类模板中的成员,并不是一开始创建出来的,而是在运行阶段才创建出来
    • 类模板作为参数
      • 指定传入类型
      • 参数模板化
      • 整个类模板化
      • 查看T类型名称:typeid(T).name()
    • 类模板遇到继承问题及解决
      • 如果父类是一个类模板,子类在做继承时,必须指定出父类中T的类型,否则无法给父类中的T分配内存
    • 类模板的份文件编写问题以及解决
      • 类模板不建议分文件编写,因为成员函数创建时机运行阶段,使用时必须要包含.cpp才可以
      • 解决方法:将类中成员函数的声明和实现都写到一个文件汇总,并且将文件后缀名改为.hpp即可(约定俗成)
    • 类模板遇到友元函数
      • 全局函数做友元的类内实现
      • 全局函数做友元的类外实现
        • 模板函数声明 模板函数实现
  49. 类型转换

    • 静态类型转换 static_cast
      • 语法:static_cast<目标类型>(原对象)
      • 对于内置数据类型可以转换
      • 对于自定义数据类型,必须是父子之前的指针或者引用可以转换
    • 动态类型转换 dynamic_cast
      • 语法:dynamic_cast<目标类型>(原对象)
      • 对于自定义数据类型
        • 父转子 不安全 转换失败
        • 子转父 安全 转换成功
        • 如果发生多态,那么总是安全的都可以转成功
    • 常量类型转换 const_cast
      • 只能对 指针 或者引用之间使用
    • 重新解释类型转化 reinterpret_cast
      • 不建议使用 不安全
  50. 异常处理

    • 基本语法
      • 三个关键字:try throw catch
      • try:试图执行一段可能会出现异常的代码
      • throw:出现异常后抛出异常关键字 throw+类型
      • catch:捕获异常 catch(类型)
      • 如果捕获到的异常不想处理,想继续向上抛出 throw
      • 异常必须要有人处理,如果没有处理,程序会自动调用terminate函数,使用程序中断
    • 可以抛出一个自定义类型的异常 myException
    • 栈解旋:从try代码开始,到throw抛出异常,所有栈上的对象都被释放掉,释放的顺序和构造顺序是相额反的,这个过程称为栈解旋
    • 异常的接口声明
      • 如果只允许函数体中抛出某种异常,可以使用异常的接口声明
      • void func(类型),如果throw(空),代表不允许抛出异常
    • 异常变量声明周期
      • MyException e会调用拷贝构造
      • MyException &e引用方式 接受 建议使用这种方式 节省开销
      • MyException *e指针方式 接受 抛出&MyException();匿名对象,对象被释放掉,不可以再操作了
      • MyException *e指针方式 接受 抛出 new MyException();堆区创建的对象,记得手动释放 delete e;
      • 建议使用 引用的方式 去接受对象
    • 异常的多态使用
      • 提供基类异常类BaseException 纯虚函数 virtual void printError() = 0;
      • 提供两个子类 继承 异常基类 重写父类中的纯虚函数
      • 测试 利用父类引用去接受子类对象 实现多态
      • 抛出哪种异常就打印哪种异常printError函数
    • 使用系统标准异常
      • 标准异常头文件 #include
      • 使用系统异常类 out_of_range(char *)
      • 捕获 catch(exception &e) {cout << e.what();}
  51. 标准输入和输出

    • 标准输入
      • cin.get() 缓冲区中读取一个字符
      • cin.get(两个参数) 不读取换行符
      • cin.getline() 读取换行并且扔掉
      • cin.ignore() 忽略 (N) N代表忽略字符数
      • cin.peek() 偷窥 偷看1个字符然后放回去
      • cin.putback() 返回 把字符放回缓冲区
      • cin.fail() 缓冲区的标志位 0:正常 1:异常
      • cin.clear() cin.sync() 重置标志位 并且刷新缓冲区
    • 标准输出
      • out.put() out.write() 利用成员函数输出内容
      • 通过流成员函数
        • int number = 99;
        • cout.width(20); 预留20空间
        • cout.fill("*"); 填充
        • cout.setf(ios::left); 左对齐
        • cout.unsetf(ios::dec); 卸载十进制
        • cout.setf(ios::hex); 安装十六进制
        • cout.setf(ios::showbase); 设置显示进制 基数
        • cout.unsetf(ios::hex); 卸载十六进制
        • cout.setf(ios::oct); 安装八进制
        • cout << number << endl;
      • 使用控制符 头文件#include
        • int number = 99;
        • cout << setw(20); 设置宽度
        • ​ << setfill("~") 填充
        • ​ << setiosflag(ios::showbase) 显示进制基数
        • ​ << setiosflag(ios::left) 设置左对齐
        • ​ << hex 安装十六进制
        • ​ << number
        • ​ << endl;
  52. 文件读写操作

    • 读写头文件 #include
    • 写 ofstream ofs
      • ofs.open 指定打开方式 ios::out | ios::trunk
      • 判断是否打开成功 ofs.is_open
      • ofs << “aaa”;
      • 关闭流对象 ofs.close();
      • ifstream ifs
      • 指定打开方式 ios::in
      • 判断是否打开成功 is_open
      • 三种方式 可以读文件中的数据
      • 关闭流对象 ifs.close();

你可能感兴趣的:(C++)