C++归纳整理

本文深入探讨了C++编程的基础,包括内联函数、函数重载、继承与多态、异常处理、STL应用以及C++11的新特性。详细解释了如何利用关键字如`inline`、`extern "C"`、`template`和`virtual`来优化代码,同时介绍了内存管理中的深复制与浅复制、动态内存分配与释放。此外,还讨论了C++中的类设计原则,如封装、构造函数与析构函数、静态成员与非静态成员、以及常量和运算符重载。最后,文章提到了C++11引入的`auto`、`nullptr`和智能指针等新特性,强调了右值引用在提高代码效率中的重要作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C++基础

  1. 内联函数

    • 关键字inline
    • 功能简单、规模较小且使用频繁的函数
    • 在编译的时候将函数嵌入在调用它的地方,可以减少参数传递和控制转移等的开销
    • 定义位于类声明中的函数会自动被认为内联函数
  2. 函数重载(多态)

    • 在同一个作用域中,两个及以上函数的函数名相同,但是参数类型或者参数数量不同,编译器将根据参数类型和参数个数自动匹配并调用,这就是函数重载。
    • 注意:编译器将类型引用和类型本身视为相同的参数。
    • 类的成员函数可以重载,不同类的同名函数不是重载
  3. extern “C”

    • 实现C++调用C语言的代码,提示编译器这部分代码按照C语言进行编译。
    • 编译不同之处:C++支持函数重载,函数编译过程中会将函数名以及参数类型加入到编译后的代码中,C语言不支持函数重载,因此编译过程中只将参数名加入到编译后的代码中。
  4. 函数模板和类模板

    • C++多态性的体现,实现高效的代码重用
    • 关键字template
    • 函数模板
      • 函数模板可以重载
      • 遇上同名普通函数时,编译器会优先选择普通函数,除非函数模板能产生更好的参数类型匹配,可以通过空模板实参列表的语法限定编译器只能通过模板匹配
    • 类模板
      • 子类从模板类继承时,需要制定基类的参数类型,如class B: public A
  5. 类的成员函数

    • 可以重载
    • 可以使用默认参数
    • 一般在头文件中声明,在源文件中定义
  6. 封装

    • 类的定义过程,将抽象得到的数据成员和函数成员形成一个统一的整体
  7. 类的访问修饰符

    • public:类本身、子类、对象
    • protect:类本身、子类
    • private:类本身
  8. 构造函数

    • 完成对象的初始化工作
    • 如果定义了一个带参数的构造函数且不是所有的参数都有默认值,那么必须定义一个不带参数的构造函数,否则调用不带参数的构造函数时会报错
    • 子类中构造函数的调用顺序:基类构造函数(按继承顺序)、派生类对象成员的构造函数(按声明顺序)、派生类构造函数
  9. 复制构造函数

    • 什么时候调用

      • 用已经定义的对象去初始化其它对象的时候
      • 函数的形参是对象类型,完成形参和实参结合的时候
      • 函数返回值是对象,函数执行完返回结果的时候
    • 什么时候需要自定义复制构造函数(完成深复制)

      • 类数据成员有指针的时候
      • 类数据成员管理资源的时候,如打开文件
      • 需要析构函数释放资源的时候
    • 禁止调用复制构造函数

      class Uncopyable {
      private:
      	Uncopyable(const Uncopyable &);	//阻止拷贝构造
      	Uncopyable &operator=(const Uncopyable &);	//阻止赋值运算符
      };
      
  10. 析构函数

    • 完成对象删除前的清理工作,对象删除前系统自动调用
    • 不可以重载
    • 可能会被继承的类的虚函数应该声明为虚函数,这样可以使用基类的指针释放子类的空间,否则会发生内存泄漏
    • 析构函数调用顺序与构造函数相反:派生类析构函数体、派生类成员对象析构函数、基类析构函数
  11. static数据成员

    • 实现同类不同对象之间的数据共享

    • 访问 类名::静态对象名

    • 需要在类外初始化,初始化时不加static

      int Martain::martain_count = 2;
      
    • 存储在静态存储区,对象的成员变量在堆或栈中生成

    • 优点:避免二义性,节省存储空间

  12. static函数成员

    • 主要用于访问静态数据成员,需要通过对象名才能访问非静态数据成员,维护不同对象之间共享的数据
    • 可以通过类名和对象名调用,但是其意义都是相同的
    • 没有this指针
  13. 动态内存分配与释放

    • new/delete:C++提供的关键字
    • malloc/free:C提供的函数,需要指定申请空间的大小
  14. 深复制和浅复制

    • 默认复制构造函数都是浅复制
    • 浅复制在遇到含有指针的对象类型的时候容易出现问题,复制后的对象和原来的对象指向同一块内存空间
      • 一个对象对内存存储数据的修改会影响到另一个对象
      • 可能会重复释放同一块内存,造成运行错误
  15. const关键字

    • 数据保护机制
    • 常数据成员只能使用初始化列表初始化
    • 常函数成员可以使用类中所有的数据成员,但是不可以修改它们,在声明和定义的时候都需要在结尾加上const关键字
    • 指向常量的指针(指针本身可变,*与变量名被分隔开),常指针(指针不能再指向其它的值了,*与变量名在一起)
    • 常对象,只能访问类的const成员
  16. 运算符重载

    • operator op(参数列表)
    • 可以重载为成员函数或者非成员函数,重载为成员函数参数个数是实际参数个数-1(对象本身就是一个参数),非成员函数(要声明为类的友元函数)是实际参数个数
    • 只能重载为成员函数的:=/[]/()/->
    • 不能重载的:类型转换操作符(const_cast synamic_cast reinterpret_cast static_cast)/typeid//.*/?:/sizeof
    • 重载后的参数至少有一个是用户自定义的类型,不能创建新的运算符、不能修改原有运算符的规则
  17. 继承

    • 公有继承、私有继承、保护继承
    • 公有继承:继承之后,父类成员访问属性在子类中不变,子类成员及子类对象可以访问父类的公有成员和保护成员,但是无法访问父类的私有成员
    • 私有继承:继承之后,父类的所有成员都变为子类的私有成员,子类成员可以访问父类的公有成员和保护成员,不可以访问父类的私有成员
      • 子类对象不可以访问父类的任何成员,子类再派生新的子类的时候,父类成员无法再被继承(已经全部变为子类的私有成员了),相当于阻断了父类的继承机制
    • 保护继承:继承之后,父类成员访问属性在子类中不变,子类成员可以访问父类的公有成员和保护成员,但是无法访问父类的私有成员,子类对象无法访问父类的成员,但是子类可以继续派生新的子类
    • 赋值兼容原则:派生类对象可以隐含转换为基类对象、派生类的对象可以初始化基类的引用、派生类指针可以隐含转换为基类指针
  18. 多态

    • 特殊多态:重载多态、强制多态;一般多态:包含多态、参数多态
    • 虚函数:基类中用virtual关键字声明的函数,可以用基类的指针调用派生类的函数,动态绑定的基础,基于虚函数表和虚表指针实现
    • 抽象类:有虚函数的类
    • 纯虚函数:只有函数声明,没有函数定义,格式是虚函数声明之后加上 =0,有纯虚函数的类是接口类,更高层次的抽象,无法实例化,必须定义析构函数
    • 多态基类必须定义虚析构函数,否则会内存泄漏
  19. STL

    • 由一些集合类及在这些类上操作的算法构成,包括容器、算法、迭代器、函数对象
    • 容器
      • 顺序容器:向量vector、双端队列dqueue、列表list
        • vector:动态数组,自动分配内存
        • list:双向链表
      • 关联容器:集合set、多重集合multiset、映射map、多重映射multimap
    • 迭代器iterator:面向对象版本的指针,遍历容器
    • map:内部实现一般为平衡二叉树
  20. 设计模式

    • 面向对象设计原则1:针对接口编程,而非针对实现编程
    • 面向对象设计原则2:优先使用对象组合,而不是类继承
    • 观察者模式:定义对象间的一对多依赖关系,当一方的对象改变状态时,所有依赖者都会被通知更新,也叫发布-订阅模式,被依赖的一方叫目标或主题(Subject),依赖方叫观察者(Observers)
  21. 异常捕获

    • 只用来处理确实可能不正常的情况
    • 构造器和析构器不应该使用异常
    • 如果try语句块无法找到与之匹配的catch语句块,则它抛出的异常将中止程序的执行
    • 使用对象作为异常,用值传递的方式抛出对象,引用传递的方式捕获对象
    • 函数的异常声明列表void func() throw (int, double, A, B, C){…}
      • 交代可能抛出的异常类型
      • 可以在声明时写,也可以在定义时写,若都写要保持一致
      • void func() throw ()表示不抛出任何类型的异常
      • 若不交代能抛出什么类型的异常,那么可以抛出任何类型的异常
      • 若抛出没有交待的异常,编译时不会出错,但在运行时, Dev C++ 编译出来的程序会出错;用 Visual Studio 2010 编译出来的程序则不会出错,异常声明列表不起实际作用
    • 标准异常类,从exception派生而来
  22. 其它

    • 系统不会初始化局部变量
    • 一个函数体中可以存在同名变量,前提是作用域不同
    • 存储在静态存储区的变量会在程序刚开始运行时会被初始化,这是唯一一次被初始化
    • 存储在静态存储区的变量:全局变量、静态变量
    • 全局变量和局部变量同名时,通过域名在函数中引用全局变量,否则引用的全是局部变量
  23. 变量在内存中的位置(自上而下)

    • 代码段:存放程序的执行代码,通常只读,程序运行前大小就已经确定了,const修饰的全局变量存储在这里

    • 数据段:生命周期是整个程序的运行周期

      已被初始化的全局变量或者静态变量

    • bbs段:未被初始化的全局变量和静态变量,被初始化为0的全局变量和静态变量

    • 堆区:存放动态分配的内存,大小不固定,动态申请后向上添加到栈区,释放后从堆中删除,程序员负责分配释放,如果不释放,程序结束后OS释放;分配小内存时使用,brk系统调用

    • 映射区:存储动态链接库等文件映射、申请大内存(malloc调用mmap函数)

    • 栈区:存储临时创建的局部变量、局部变量、函数参数、函数返回值等,编译器自动分配释放,向下有限扩展,const修饰的局部变量

  24. RTTI,运行时类型检查

    • C++多态性的体现,使用基类的指针或引用查询它指向的子类对象的实际派生类型
    • 主要体现在dynamic_cast和typeid两方面
      • dynamic_cast用于将基类的指针或者引用安全地转换为派生类的指针或引用
      • typeid用于返回指针或者引用实际指向的派生类类型
    • 虚函数表-1位置存放了指向type_info的指针
  25. 静态函数和虚函数

    • 静态函数:编译时确定运行时机
    • 虚函数:运行时动态绑定;使用虚函数表,调用时增加一次内存开销
  26. C++默认函数栈

    • 大小与OS和平台有关
    • CentOS7 64位:8M;mac OS:8M;Windows:2M;Linux:1M
  27. 隐式类型转换

    • 内置类型:低精度向高精度转换
    • “可以用 单个形参来调用 的构造函数定义了从 形参类型 到 该类类型 的一个隐式转换。”
      • 构造函数形参类型 到 类类型 的隐式转换
      • 不是只能有一个形参,可以有其它形参,但其它形参要有默认实参
      • 生成临时对象,可能存在风险,用explicit声明抑制这种隐式类型转换,强迫用户只能显式创建对象
  28. vector和list

    • 联系:都在堆上分配内存
    • 区别:
      • vector底层是数组,list底层是双向链表
      • vector分配连续内存,list内存不连续
      • vector存储空间不够时成倍增加(也不一定),list每次插入和删除都需要申请和释放内存
      • vector对中间元素进行插入或删除操作时,需要进行内存拷贝,list不需要
      • vector随机访问性能好,插入删除性能差,list随机访问性能差,插入删除性能好
      • vector顺序内存,list不是顺序内存
    • 应用
      • vector:适用于需要进行高效随机访问,对插入和删除性能要求不高的情况
      • list:适用于需要高效插入和删除,但是对随机访问性能要求不高的情况
  29. resize和reverse

    • resize:对已有元素的相关判断,resize(len),如果len大于原来的size,则补充len-resize个元素,否则不变;如果len大于数组容量(capacity),则capacity也会扩容到len
    • reverse:对vector容量的相关判断,reverse(len),如果len大于原来的reverse,则容量扩充到len,否则不变;
    • resize操作可能会影响reverse,reverse操作不会影响resize
  30. C++类内的引用数据成员和const常量都只能通过构造函数的初始化列表进行初始化。因为一旦进入构造函数函数体内部,就不再是初始化操作而是赋值操作,引用数据成员和const常量都不能进行赋值操作。

  31. struct和class

    • struct是值类型,class是引用类型;复制时,值类型的数据是值复制,引用类型的数据是引用复制
    • struct分配在栈区,class分配在堆区;栈的空间较小,但在栈中存储的数据访问效率相对较高;堆的空间较大,但在堆中存储的数据访问效率较低
    • 结构体使用完后自动解除内存分配,类实例有垃圾回收机制保证内存的回收处理
    • 结构体的默认访问和继承属性是public,类的默认访问和继承属性是private
    • 轻量级的对象(点、矩形、颜色)用结构体,大量逻辑对象用类
  32. 类成员访问权限:public、private、protected

    • public:类内成员函数、类的对象、子类的对象
    • private:类内成员函数
    • protected:类内成员函数、子类成员函数、友元函数
  33. 右值引用、移动语义和完美转发

    https://www.jianshu.com/p/d19fc8447eaa

    • 左值&右值
      • 左值:可取址,具名的持久化对象
      • 右值:不可取址,不具名的临时对象(常量、临时对象)
    • 左值引用&右值引用
      • 左值引用:T&,只能绑定左值
      • 常量左值引用:const T&,可以绑定非常量左值、常量左值和右值;只能读不能写
      • 右值引用:T&&,只能绑定右值,可以延长右值的生命期;右值引用具名之后就变成了一个左值
      • 通用引用:类型由初始化时绑定的值的类型确定;必须初始化;只有发生自动类型推断时才是通用引用
    • 移动语义
      • 避免多余复制,减少无谓的内存拷贝;有些编译器进行了优化
      • 参数是右值引用
      • 通过移动构造函数和移动赋值构造函数实现
      • std::move()可以将左值转换成右值使用;适用于生命周期很短的局部变量;用于转移资源的控制权,对含有文件句柄、内存等资源的对象更有意义;资源转移后不会立即析构,左值离开作用域之后才会析构
    • 完美转发
      • 通过一个函数将参数转交给另一个函数处理,若能继续保持原有特征,那么是完美的
      • std::forward()+通用引用实现完美转发
    • emplace_back()替换push_back()减少内存拷贝,提升性能
  34. 段错误:发生非法内存访问时

    • 野指针
    • 修改字符串常量
  35. 内存泄漏:程序未能释放掉不再使用的内存,造成内存浪费

    • 堆内存泄漏:使用new/malloc动态分配的内存没有使用delete/free释放
    • 系统资源泄漏:没有释放系统分配的资源,如Bitmap、handle、SOCKET,会导致系统效能降低,系统运行不稳定
    • 没有将基类的虚构函数定义为虚函数,基类指针指向子类对象时,子类的析构函数不会被调用,子类的资源没有被正确释放,造成内存泄漏
  36. new和malloc

    • new是C++的关键字,可以重载,malloc是C的库函数
    • new按数据类型申请内存,malloc按大小分配内存
    • new分配内存时调用构造函数,delete释放内存时调用析构函数;malloc不调用构造函数和析构函数
    • new返回指向对象的指针,malloc返回void*指针,需要再进行类型转换
    • new申请失败抛出bad_malloc异常,malloc申请失败返回NULL
    • malloc可以用realloc扩充内存空间;new没有这种机制
  37. STL的内存优化

    1. 使用allocate向内存池请求size大小的内存空间,如果需要请求的内存大小大于128bytes,直接使用malloc。、
    2. 如果需要的内存大小小于128bytes,allocate根据size找到最适合的自由链表。
    3. 如果链表不为空,返回第一个node,链表头改为第二个node。
    4. 如果链表为空,使用blockAlloc请求分配node。
      1. 如果内存池中有大于一个node的空间,分配尽可能多的node(但是最多20个),将一个node返回,其他的node添加到链表中
      2. 如果内存池只有一个node的空间,直接返回给用户。
      3. 若果如果连一个node都没有,再次向操作系统请求分配内存。
        • 分配成功,再次进行b过程。
        • 分配失败,循环各个自由链表,寻找空间。
          • 找到空间,再次进行过程b。
          • 找不到空间,抛出异常。
    5. 用户调用deallocate释放内存空间,如果要求释放的内存空间大于128bytes,直接调用free。
    6. 否则按照其大小找到合适的自由链表,并将其插入。
  38. C++ 11的新特性

    • auto关键字:编译器根据初始值自动推导出类型,不能用于函数传参和数组类型的推导
    • nullptr关键字:可以被转换为任意其它的指针类型,NULL会被宏定义为0,在重载时可能会出现问题
    • 智能指针:解决内存管理问题
    • 初始化列表:初始化列表对类进行初始化
    • 右值引用:基于右值引用实现移动语义和完美转发,消除两个对象间不必要的对象拷贝,节省运算存储资源
    • atomic原子操作用于多线程资源互斥操作
    • 新增STL容器array以及tuple
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值