C++知识点学习笔记

zxl19 2021-09-25

我的C++知识点学习笔记。

C++ Hello World

C++和C的区别

  1. C++是面向对象的语言,C是面向过程的语言;
  2. C++引入new/delete运算符,取代了C中的malloc/free库函数;
  3. C++引入引用的概念,而C中没有;
  4. C++引入类的概念,而C中没有;
  5. C++引入函数重载的特性,而C中没有;

面向对象的三大特征

  1. 封装:封装是面向对象方法的一个重要原则,就是把对象的属性和服务结合成一个独立的系统单位,并尽可能隐蔽对象的内部细节;
  2. 继承:特殊类的对象用于其一般类的全部属性与服务,称作特殊类对一般类的继承;
  3. 多态:多态性是指在一般类中定义的属性或行为,被特殊类继承之后,可以具有不同的数据类型或表现出不同的行为;

    • 静态多态:编译时的多态,绑定工作在编译连接阶段完成,称为静态绑定,e.g. 函数重载(包括运算符重载);
    • 动态多态:运行时的多态,绑定工作在程序运行阶段完成,称为动态绑定,e.g. 虚函数;

C++关键字

const关键字

符号常量

const value_type variable_name = variable_value;
  1. 符号常量在使用之前一定要首先声明:
  2. 符号常量不能被赋值:

常对象

const class_name object_name;
  1. 常对象必须进行初始化,而且不能被更新;
  2. 在声明常对象时,把const关键字放在类型名后面也是允许的,不过人们更习惯于把const写在前面;
  3. 基本数据类型的常量也可看作一种特殊的常对象,因此,后面将不再对基本数据类型的常量和类类型的常对象加以区分;

const修饰的类成员

常成员函数
value_type function_name(variables) const;
  1. const是函数类型的一个组成部分,因此在函数的定义部分也要带const关键字;
  2. 如果将一个对象声明为常对象,则通过该常对象只能调用它的常成员函数,而不能调用其他成员函数(这就是C++从语法机制上对常对象的保护,也是常对象唯一的对外接口方式);
  3. 无论是否通过常对象调用常成员函数,在常成员函数调用期间,目的对象都被视同为常对象,因此常对象函数不能更新目的对象的数据成员,也不能针对目的对象调用该类中没有用const修饰的成员函数(这就保证了在常成员函数中不会更改目的对象的数据成员的值);
  4. const关键字可以用于对重载函数的区分,如果仅以const关键字为区分对成员函数重载,那么通过非const的对象调用该函数,两个重载的函数都可以与之匹配,这时编译器将选择最近的重载函数——不带const关键字的函数:

     T& operator[](const std::size_t i) { return data_[i]; }
     const T& operator[](const std::size_t i) const { return data_[i]; }
    
常数据成员
const value_type variable_name;
static const value_type variable_name;
  1. 类的成员数据也可以是常量;
  2. 常数据成员只能通过初始化列表来获得初值;
  3. 静态常数据成员在类外说明和初始化;

常引用

const value_type& reference_name;
  1. 常引用所引用的对象不能被更新;
  2. 如果用常引用作形参,便不会意外地发生对实参的更改;
  3. 对于在函数中无须改变其值的参数,不宜使用普通引用方式传递,因为那会使得常对象无法被传入,采用传值方式或传递常引用的方式可避免这一问题;
  4. 对于大对象来说,传值耗时较多,因此传递常引用为宜;
  5. 复制构造函数的参数一般也宜采用常引用传递;
  6. 在32位处理器上,小于4字节的数据类型建议传值,在64位处理器上,小于8字节的数据类型建议传值;

delete关键字

enum关键字

enum enum_name {enum_list};
  1. 枚举类型用于表示可取值有限的变量,方便检查数据合法性;
  2. 对枚举元素按常量处理,不能对它们赋值;
  3. 枚举元素具有默认值,默认从0开始,依次加1;
  4. 可以在声明时另行定义枚举元素的值,之后的值依次加1;
  5. 枚举值可以进行关系运算,同一类型枚举变量可互相赋值;
  6. 枚举类型可以隐式转换为整型,整型转换为枚举类型需要进行显式类型转换;

extern关键字

外部变量和外部函数

C++和C的混合编程

使用extern "C"实现C++和C的混合编程

#ifdef __cplusplus
extern "C" {
#endif

void display();

#ifdef __cplusplus
}
#endif

friend关键字

inline关键字

inline value_type function_name(variables) {
    // implementation
}
  1. 内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处;
  2. 对于一些功能简单、规模较小又使用频繁的函数,可以设计为内联函数;
  3. 函数实现在头文件中的函数建议定义为内联函数,否则有可能违反单一定义原则(One Definition Rule,ODR),造成未定义行为;

new关键字

static关键字

静态数据成员

  1. 如果某个属性为整个类所共有,不属于任何一个具体对象,则采用static关键字来声明为静态成员;
  2. 类属性是描述类的所有对象共有特征的一个数据项,对于任何对象实例,它的属性值是相同的;
  3. 静态数据成员具有静态生存期;
  4. 由于静态数据成员不属于任何一个对象,因此可以通过类名对它进行访问,一般的用法是类名::标识符
  5. 在类的定义中仅仅对静态数据成员进行引用性声明,必须在命名空间作用域的某个地方使用类名限定定义性声明,这时也可以进行初始化(在类中声明,在类外初始化);

静态函数成员

static value_type function_name(variables);
  1. 静态函数成员可以直接访问该类的静态数据和函数成员;
  2. 而访问非静态成员,必须通过对象名;

this关键字

typedef关键字

typedef type_name aliases;
  1. 用于将一个标识符声明成某个数据类型的别名,然后将这个标识符当做数据类型使用;
  2. 新类型名表中可以有多个标识符,它们之间以逗号分隔;

using关键字

virtual关键字

虚函数

虚函数是动态绑定的基础。虚函数必须是非静态的成员函数。虚函数经过派生之后,在类族中就可以实现运行过程中的多态。

一般虚函数成员
virtual value_type function_name(variables);
  1. 虚函数声明只能出现在类定义中的函数原型声明中,而不能在成员函数实现的时候;
  2. 运行过程中的多态需要满足三个条件:

    • 赋值兼容原则;
    • 虚函数;
    • 由成员函数来调用或者是通过指针、引用来访问虚函数;
  3. 虚函数一般不声明为内联函数,因为对虚函数的调用需要动态绑定,而对内联函数的处理是静态的,所以虚函数一般不能以内联函数处理。但将虚函数声明为内联函数也不会引起错误;
虚析构函数
virtual ~ClassName();

纯虚函数和抽象类

纯虚函数
virtual value_type function_name(variables) = 0;
  1. 纯虚函数是一个在基类中声明的虚函数,它在该基类中没有定义具体的操作内容,要求各派生类根据实际需要给出各自的定义;
  2. 声明为虚函数之后,基类中就可以不再给出函数的实现部分,纯虚函数的函数体由派生类给出;
  3. 基类中仍然允许对纯虚函数给出实现,但即使给出实现,也必须由派生类覆盖,否则无法实例化;
抽象类
  1. 带有纯虚函数的类是抽象类;
  2. 抽象类不能实例化;

类型转换操作符

显式类型转换:

value_type(expression);  // C++风格
(value_type)expression;  // C风格

类型转换操作符:

static_cast<value_type>(expression);
dynamic_cast<value_type>(expression);
const_cast<value_type>(expression);
reinterpret_cast<value_type>(expression);

区别辨析

结构体struct和类class

  1. 结构体是一种特殊形态的类,它和类一样,可以有自己的数据成员和函数成员,可以有自己的构造函数和析构函数,可以控制访问权限,可以继承,支持包含多态等,二者定义的语法形式也几乎一样;
  2. 结构体和类的唯一区别在于,结构体和类具有不同的默认访问控制属性:

    • 在类中,对于未指定访问控制属性的成员,其访问控制属性为私有类型private
    • 在结构体中,对于未指定任何访问控制属性的成员,其访问控制属性为公有类型public
  3. C++引入结构体是为了保持和C程序的兼容性;

结构体struct和联合体union

  1. 联合体是一种特殊形态的类,它可以有自己的数据成员和函数成员,可以有自己的构造函数和析构函数,可以控制访问权限;
  2. 与结构体一样,联合体也是从C语言继承而来的,因此它的默认访问控制属性也是公共属性的;
  3. 联合体的全部数据成员共享同一组内存单元;
  4. 联合体在存储时具有两种存储形式,具体采用哪种存储形式由处理器决定:

    • 大端模式(big endian):高位字节保存在内存中的低位地址,低位字节保存在内存中的高位地址,这种存储方式符合人的正常思维习惯;
    • 小端模式(little endian):低位字节保存在内存中的低位地址,高位字节保存在内存中的高位地址,这种存储方式有利于计算机处理;

#defineconst

constconstexpr

size_tint

typedef unsigned int size_t;    // 32位
typedef unsigned long size_t;   // 64位

int固定四个字节不同,size_t的取值范围是目标平台下最大可能的数组尺寸,一些平台下size_t的范围小于int的正数范围,又或者大于unsigned int,使用int既有可能浪费,又有可能范围不够大。

NULLnullptr

为解决NULL代指空指针存在的二义性问题,在C++11中引入nullptr关键字来代指空指针:

NULL        // 0
nullptr     // 空指针,C++11引入

C++11

关于嵌套模板中相邻的右尖括号

C++11支持嵌套模板中相邻的右尖括号写作>>

vector<vector<int>>

早期C++标准要求嵌套模板中相邻的右尖括号写作> >

vector<vector<int> >

其原因是为了避免与输入流运算符>>混淆,否则会报错:

'>>' should be '> >' within a nested template argument list.

关于autodecltype

  1. auto:让编译器在编译期就推导出变量的类型,可以通过=右侧的类型推导出变量的类型;
  2. decltype:相对于auto用于推导变量类型,而decltype则用于推导表达式类型,这里只用于编译器分析表达式的类型,表达式实际不会进行运算;

constexpr关键字

default关键字

C++11支持使用default关键字生成默认函数:

= default

delete关键字

C++11支持使用delete关键字禁用默认函数:

= delete

enum关键字

为了避免名称冲突,C++11引入了枚举类:

enum class enum_name {enum_list};

explicit关键字

C++11支持使用explicit关键字显式声明构造函数和转换操作符,必须要显式调用,避免隐式类型转换。

final关键字

C++11支持使用final关键字声明虚函数不能在派生类中被覆盖或者声明类不能被继承,否则编译器会报错。

for循环

C++11支持基于范围的for循环:

vector<int> vec;
for (int i : vec) {}
string str;
for (char c : str) {}

noexcept关键字

C++11支持使用noexcept关键字声明函数不会抛出任何异常。

override关键字

C++11支持使用override关键字声明派生类中的虚函数覆盖了基类中的虚函数。

using关键字

C++11支持使用using关键字指定别名,作用与typedef关键字相同:

typedef type_name aliases;
using alias = type_name;

关于智能指针

C++11引入智能指针来实现对于堆内存的自动管理。

使用时需要包含头文件:

#include <memory>
  1. std::auto_ptr:自动指针,C++11弃用,C++17移出;
  2. std::shared_ptr:共享指针,底层使用引用计数,存在循环引用问题;
  3. std::unique_ptr:独享指针,建议优先使用,避免循环引用,性能开销更低;
  4. std::weak_ptr:弱共享指针,不会引起引用计数变化,不单独使用,结合共享指针使用,用于解决循环引用问题;

共享指针std::shared_ptr

  1. 初始化方法,以指向int类型的共享指针为例:

     // 空共享指针,初始引用计数为0
     std::shared_ptr<int> sp1;
     std::shared_ptr<int> sp1(nullptr);
     sp1.reset(new int(10));
     // 赋值构造
     std::shared_ptr<int> sp2(new int(10));                  // 建议使用,会使用重载后的new运算符
     std::shared_ptr<int> sp2 = std::make_shared<int>(10);   // 辅助构造函数,使用默认的new运算符
     // 拷贝构造
     std::shared_ptr<int> sp3(sp2);
     std::shared_ptr<int> sp3 = sp2;
     // 移动构造
     std::shared_ptr<int> sp4(std::move(sp3));
     std::shared_ptr<int> sp4 = std::move(sp3);
    

    同一个裸指针不能赋值给多个共享指针,否则会导致双重释放(double free),造成未定义行为。

  2. 共享指针辅助构造函数make_shared()原型声明:

     template <class T, class... Args>
     shared_ptr<T> make_shared(Args&&... args);
    
  3. 共享指针辅助构造函数allocate_shared()原型声明:

     template <class T, class Alloc, class... Args>
     shared_ptr<T> allocate_shared(const Alloc& alloc, Args&&... args);
    

    使用指定的堆内存管理器分配内存空间。

  4. 常用成员函数原型声明:

     void swap(std::shared_ptr& x) noexcept;     // 交换指针内容
     void reset() noexcept;                      // 重置指针为空指针
     template <class U> void reset(U* p);        // 重置指针为指定指针,要求U类型指针能够隐式转换为T类型指针
     element_type* get() const noexcept;         // 返回裸指针
     long int use_count() const noexcept;        // 返回引用计数
     bool unique() const noexcept;               // 判断是否独享
    

独享指针std::unique_ptr

  1. 初始化方法,以指向int类型的独享指针为例:

     // 空独享指针
     std::unique_ptr<int> up1;
     std::unique_ptr<int> up1(nullptr);
     up1.reset(new int(10));
     // 赋值构造
     std::unique_ptr<int> up2(new int(10));
     std::unique_ptr<int> up2 = std::make_unique<int>(10);    // 辅助构造函数
     // 拷贝构造:禁止
     // 移动构造
     std::unique_ptr<int> up3(std::move(up2));
     std::unique_ptr<int> up3 = std::move(up2);
    
  2. 独享指针禁止拷贝构造,拷贝构造函数原型声明:

     // 使用delete关键字禁止编译器使用拷贝构造函数
     std::unique_ptr(const std::unique_ptr&) = delete;
     // 拷贝构造无法编译通过
     // std::unique_ptr<int> up4(up3);
     // std::unique_ptr<int> up4 = up3;
    
  3. 独享指针辅助构造函数make_unique()原型声明:

     // C++14引入
     template <class T, class... Args>
     unique_ptr<T> make_unique(Args&&... args);
    
     // 在C++11中的实现
     template <typename T, typename... Args>
     std::unique_ptr<T> make_unique(Args&&... args) {
         return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
     }
    
  4. 常用成员函数原型声明:

     element_type* get() const noexcept;         // 返回裸指针
     element_type* release() noexcept;           // 返回裸指针,重置指针为空指针,释放对原内存的所有权,但是不释放原内存
     void reset(element_type* p) noexcept;       // 重置指针为指定指针,释放原内存
     void swap(std::unique_ptr& x) noexcept;     // 交换指针内容
    

弱共享指针std::weak_ptr

  1. 初始化方法,以指向int类型的弱共享指针为例:

     // 空弱共享指针
     std::weak_ptr<int> wp1;
     std::weak_ptr<int> wp1(nullptr);
     wp1.reset();
     // 赋值构造:禁止
     // 拷贝构造
     std::weak_ptr<int> wp2(wp1);
     // 移动构造
     std::weak_ptr<int> wp3(std::move(wp2));
     std::weak_ptr<int> wp3 = std::move(wp2);
     // 通过共享指针构造
     std::shared_ptr<int> sp(new int(10));
     std::weak_ptr<int> wp4(sp);
    
  2. 弱共享指针禁止赋值构造,因为不拥有对内存的所有权,不影响生命周期,是观察者;
  3. 常用成员函数原型声明:

     void swap(std::weak_ptr& x) noexcept;                   // 交换指针内容
     void reset() noexcept;                                  // 重置指针为空指针
     long int use_count() const noexcept;                    // 返回引用计数
     bool expired() const noexcept;                          // 判断是否过期(指针为空,或者指向的堆内存已经被释放)
     std::shared_ptr<element_type> lock() const noexcept;    // 如果未过期,返回智能指针,如果已过期,返回空智能指针
    

    没有重载*->运算符,只能访问所指的堆内存,而无法修改它;

关于lambda表达式

lambda表达式是一种匿名函数,语法形式如下:

[capture](params) opt -> ret { body; };
  1. capture为捕获列表:

    • []:不捕获任何变量;
    • [&]:捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获);
    • [=]:捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获);
    • [=,&foo]:按值捕获外部作用域中所有变量,并按引用捕获foo变量;
    • [bar]:按值捕获bar变量,同时不捕获其他变量;
    • [this]:捕获当前类中的this指针,让lambda表达式拥有和当前类成员函数同样的访问权限,如果已经使用了&或者=,就默认添加此选项,捕获this的目的是可以在lamda表达式中使用当前类的成员函数和成员变量;
  2. params为参数列表,与普通函数的定义相同,如果不需要传参可以省略;
  3. opt为函数选项,可以省略,如果使用,参数列表的括号不能省略:

    • noexcept:函数体不会抛出任何异常;
    • throw():指定函数体可以抛出的异常类型;
  4. ret为返回值类型,如果lambda表达式内部只有一个return语句,或者没有返回值,则编译器可以自动推断出返回值类型,可以省略;
  5. body为函数体,与普通函数的定义相同;
  6. 可以使用std::functionstd::bind来存储和操作lambda表达式;

参考

  1. 《C++语言程序设计》
  2. cplusplus
  3. 校招C++大概学习到什么程度?-程序员内功修炼的回答-知乎
  4. C++教程-菜鸟教程
  5. C++入门教程-C语言中文网
  6. C++11教程-C语言中文网
  7. 终极C++避坑指南-鹅厂架构师的文章-知乎
  8. 是C++的发展进入了邪路,还是我写代码的姿势不正确?-真重的回答-知乎
  9. c++中函数参数里,是否能用const reference的地方尽量都用?-吴咏炜的回答-知乎
  10. 传值,传地址,传引用的效率区别-博客园
  11. 值传递、指针传递、引用传递:深入理解函数参数传递方式-CSDN博客
  12. extern “C”-C语言中文网
  13. inline和ODR-CSDN博客
  14. 为什么C++实现多态必须要虚函数表?-Pluveto的回答-知乎
  15. C++强制类型转换运算符(static_cast、reinterpret_cast、const_cast和dynamic_cast)-C语言中文网
  16. C++类型转换:强制类型转换和隐式类型转换-C语言中文网
  17. C++数据类型(强制)转换详解-C语言中文网
  18. C++是不是结构体都可以用类来表示?有了类,还有使用结构体的必要吗-南山烟雨珠江潮的回答-知乎
  19. 共用体的大端模式和小端模式1-CSDN博客
  20. 共用体的大端模式和小端模式2-CSDN博客
  21. C++11 constexpr:验证是否为常量表达式-C语言中文网
  22. C++11 constexpr和const的区别详解-C语言中文网
  23. size_t和int1-CSDN博客
  24. size_t和int2-CSDN博客
  25. NULL和nullptr-CSDN博客
  26. 嵌套模板中的»1-Stack Overflow
  27. 嵌套模板中的»2-Stack Overflow
  28. C++11新特性,所有知识点都在这了!-程序喵大人的文章-知乎
  29. C++11 auto和decltype关键字-C语言中文网
  30. C++返回值类型后置(跟踪返回值类型)-C语言中文网
  31. C++ =default和=delete的用法-C语言中文网
  32. C++随笔:关键字 =default 和 =delete-灵知子的文章-知乎
  33. default和delete-CSDN博客
  34. C++里写一个函数体为空的构造函数和构造函数=default这两种写法有何区别?-吴咏炜的回答-知乎
  35. C++ explicit用法详解-C语言中文网
  36. C++ explicit关键字-漠空的文章-知乎
  37. C++ override和final的用法-C语言中文网
  38. C++ noexcept的用法-C语言中文网
  39. C++11使用using定义别名(替代typedef)-C语言中文网
  40. C++中using的三种用法-算法集市的文章-知乎
  41. c++是否应避免使用普通指针,而使用智能指针(包括shared,unique,weak)?-张小方的回答-知乎
  42. 现代C++:一文读懂智能指针-FOCUS的文章-知乎
  43. C++智能指针-小小将的文章-知乎
  44. C++11智能指针丨巨巨巨详细-Phoenix Studio的文章-知乎
  45. 如何看待无脑shared_ptr的行为?-南山烟雨珠江潮的回答-知乎
  46. C++11 shared_ptr智能指针-C语言中文网
  47. C++11 shared_ptr(智能指针)详解-C语言中文网
  48. C++11 unique_ptr智能指针详解-C语言中文网
  49. C++11 weak_ptr智能指针-C语言中文网
  50. C++ std::weak_ptr智能指针详解-C语言中文网
  51. std::unique_ptr release的使用-博客园
  52. C++11 lambda匿名函数用法详解-C语言中文网
  53. C++11 lambda表达式精讲-C语言中文网
  54. C++11 Lambda表达式(匿名函数)详解-C语言中文网