C++知识整理
每篇文章的总结,只需要在标题和目标上进行总结就可以了。
整理进度
1_ C++(31)中有很多没有整理的,找出来整理。
1.C++语言基础
C++类型转换:static_cast、reinterpret_cast、dynamic_cast、const_cast
- C风格的类型转换:(类型说明符)表达式,如int valueB = (int) valueA;
- reinterpret_cast:主要针对指针或引用,重新解释指针所指地址的内存。很强大,很少用,最好不用吧(我的想法)。reinterpret_cast执行什么动作是依赖于编译器的,所以不具有可移植性。
- static_cast:普通类型转换(非指针且非引用),如double转int、void*类型指针转换、有继承关系的指针之间的转换、non-const对象转const对象。【最经常使用】
- dynamic_cast:在需要保证“安全的向下转型”的情况下使用,如当转型基类指针时,无法确定此基类指针指向的是否是子类对象。dynamic_cast会拖慢运行。所以我觉得非必要时,直接使用static_cast。
- const_cast:用来移除变量的const或volatile限定符
C++ delete:delete操作相当于说明当前指针指向内存被释放了,此块内存由操作系统接管了。当前指针所指区域做了什么操作,则是一个未定义行为(未定义行为:每个编译器可能都有不同的实现,C++标准中并未有明确的定义)。
- close-on-exec:子进程默认会继承父进程的所有文件描述符,但是如果某个文件描述符设置了close-on-exec,那么从父进程中继承得到的文件描述符会在子进程(fork生成子进程)中被关闭。
- execl函数:在代码中调用其他可执行程序
void指针(void*)用法:void指针可用于存放任意对象的地址,需要类型转换以后才能使用,如*((int*)a)
C++和C中的输入输出总结、标准输入/标准输出/标准错误与重定向,>>、>、<、<<
- libevent所讲的函数:封装了epoll
- bufferevent所讲的函数:封装了listen、accept等
2.C++编译
2.1 gcc编译和gdb调试的学习
C程序编译成可执行文件后,才能有运行。我常用gcc工具将c程序编译成可执行文件。
GCC编译流程分为四个步骤: 编译预处理、编译、汇编和链接
请参考:gcc编译和gdb调试的学习
我觉得gdb对我没什么用。知道gdb是对生成的二进制文件进行调试就行。
确实有兴趣的可参考: gdb调试
注意点:
g++中链接库的指定顺序也会影响编译是否成功
gcc能编译通过的,g++不一定能编译通过
2.2 C\C++ 静态库和动态库
按照下面顺序进行学习:
静态库和动态库的简介和制作
linux C\C++动态库(共享库)编译和运行时的链接
动态链接库的隐式加载和显示加载
2.3 makefile、cmake和configure脚本
使用g++编译程序的时候,需要依赖很多库文件和头文件,当工程很大时,在命令行中使用一条gcc命令编译整个工程就会显得困难。
所以我们将gcc编译各种程序的命令放入一个文件中,这个文件命名为Makefile。
makefile使用
./configure是一种叫autoconf的构建工具自动生成的构建文件,它以shell script的形式存储,在cmake之前是c/c++的主流构建工具。近年来很多项目有从autoconf转向cmake的趋势。autoconf和cmake的共同点是会生成makefile,然后从makefile执行真正的编译构建过程。
configure生成Makefile文件全过程
cmake:
上面我们学到的是linux下Makefile的编写规则,但是不同平台有不同的Makefile文件编写规则,为了解决不同平台编写规则的不同,就需要使用cmake。在使用cmake时,需要开发者编写一种平台无关的CMakeList.txt 文件来定制整个编译流程,然后通过cmake命令就可以根据目标的平台生成所需的本地化Makefile和工程文件。
cmake入门
cmake 常用变量和常用环境变量
Cmake之深入理解find_package()的用法
cmake升级\更新-ubuntu
系统学习cmake:
【cmake】为程序添加版本号和带有使用版本号的头文件:本文章中还提到了CMake的configure_file指令的使用
CMake的configure_file指令:复制输入文件到输出文件,输出文件中将输入文件中的@VAR@或者${VAR}等变量替换成具体的值。@VAR@或者${VAR}是CMakeLists.txt中的变量。configure_file让普通文件,也能使用CMakeLists.txt中的变量。
【cmake】add_subdirectory实例,设定add_subdirectory包含的模块是否可用:此链接还没有写完。。。
按照此链接学习cmake,还没学完:https://juejin.cn/post/6844903557196414989
C++面向对象
重载和重写:重载(函数名相同但参数不同)、重写(函数名相同且参数相同)
- 虚指针与虚函数表:如果一个类拥有virtual函数或父类拥有virtual函数,则当此类创建对象时,内存中存储了数据成员和指向虚表的指针,虚表中指明该类对象应该调用哪个虚函数。我觉得同一个类创建的对象应该是共享同一个虚表。
- 虚函数调用过程:当对象调用虚函数时,会通过虚指针找到虚表,最后通过虚表找到应该调用的虚函数。
- 【注】当父类中存在虚函数,而子类中不存在虚函数,那么子类和父类公用同一个虚表。
- 多态:使用基类指针实现多态时,需要使用虚函数。
- override:告诉编译器此函数是重写父类中的虚函数,此时编译器就会查找父类中是否有相同签名的函数,如果父类不存在,编译时就会报错。
【注】函数的签名包括:函数名,参数列表,const属性。 - final:阻止类的进一步派生 或 虚函数的进一步重写。
C++之纯虚函数:拥有纯虚函数的类称为抽象类;抽象类不能实例化。
C++中虚析构函数的作用及其原理分析:当父类的析构函数不声明成虚析构函数的时候,如果父类的指针指向子类对象,此时delete掉父类的指针,只调动父类的析构函数,而不调动子类的析构函数。
- 虚函数依赖虚指针,对象还没构造,所以没有虚指针。
- 虚函数是通过父类的引用或指针调用的,而构造函数不能通过父类的指针或者引用去调用。
- 友元:让其他函数(类外函数或者其他类的成员函数)可以访问对象的private和protect类型成员
- 虚基类(不是虚函数哦):解决多继承中的间接二义性问题
多继承:子类有两个或两个以上的父类
间接二义性:首先我们知道子类继承父类中成员是将成员复制一份。当B和C都继承了A,然后D又继承了B和C。如果A中有成员a,那么D中就有两个名字相同的a。
虚基类:虚基类就是让当B和C都“虚”继承了A,如果D又继承了B和C。如果A中有成员a,那么D只有一个a。 - 直接二义性问题:多个基类中拥有同名的成员A,子类调用A时编译器无法确定调用的是哪个A。
解决方法:一般利用类的作用域分辨符解决,如Car::show()代表调用了类Car的函数show。 - 多态:假设B和C都继承于基类A,则B和C的对象都可以用基类的对象的指针a进行指向。当a调用方法x时,由于B和C类的方法x的实现是不一样,同样使用a->x得到的效果是不同。多态指的就是:使用相同的代码a->x,却可以根据a具体指向的对象而实现出不同的效果,这个不同的效果就是多态。
- 拷贝构造函数(一种特殊的构造函数):用一个对象初始化一个新建立的对象。默认拷贝构造函数的功能是把传入的对象的每个数据成员的值依次复制到新建立的对象中。
拷贝构造函数在三种情况下被调用:1)用于用一个对象初始化另一个对象 2)对象作为实参传递给形参 3)作为函数的返回值。举例如下: - 深拷贝构造函数:默认拷贝构造函数是将一个对象的所有数据成员的值,复制给另一个对象的所有数据成员。但是当数据成员中存在指针时,默认拷贝构造函数只会拷贝地址到另一个对象。这就会导致两个不同的指针指向同一个地址,两个对象中的指针指向同一个内存空间,可能会导致内存的多次释放。
这个时候就需要重写拷贝构造函数来开辟新的内存空间,从而让两个不同的指针指向的地址不同且不同地址中存储的数据是相同的。
- 右值:一般不能取地址、没有名字、临时的就是右值,用于减少拷贝,例如,如果需要获取函数的返回值,那么可以将函数的调用放在std::move()中,这样函数返回值直接转换为右值,背后会调用移动赋值运算符。
多线程
C++ 条件变量(condition_variable)、notify、wait、互斥量(mutex)、同步过程、生产者和消费者:
- 条件变量中包含如下两种函数:notify、wait
- 虚假唤醒:被唤醒,但是锁被其他线程抢走了。所以唤醒以后,还要再判断一下条件是否符合。
- 错过唤醒:需要锁,避免错过唤醒。
C++中类的普通成员函数不能作为 pthread_create的线程函数:因为普通成员函数隐含形参this
线程的任务函数可以是普通函数、类的非静态成员函数、类的静态成员函数、lambda函数、仿函数(举例):线程不能拷贝,但是可以将线程的资源所有权转移给新创建的线程对象。
C++多线程中的join, detach, joinable
- join的意思是父线程等待子线程结束
- detach的含义是父线程和子线程相互分离,即使父线程结束了,只要主线程没有结束,子线程就会继续正常运行。
- joinable:是否能被join或者detach
C++ 线程池:线程池:创建几个线程用于处理任务,这些线程暂时不销毁,从而减少线程创建和销毁所需的时间。实现线程池所需内容如下:
- 一个锁:用于线程互斥访问任务队列
- 两个条件变量:
1.当任务队列满时,此时生产者线程阻塞。当任务队列不满时,此时通知生产者线程添加任务。(如果队列可以无限大,就可以不用此条件变量)
2.当任务队为空时,此时消费者线程阻塞。当任务队列不为空时,此时通知消费者线程处理任务。 - 循环队列:用循环队列实现任务队列
C++11 std::unique_lock与std::lock_guard:std::unique_lock要比std::lock_guard更灵活但是占用空间更大且更慢、condition_variable中的wait只接收unique_lock,因为unique_lock可以在wait期间解锁mutex,并在之后重新将其锁定(这就是std::unique_lock灵活的地方)
C++模板编程
C++ 模板:模版分为类模版、函数模版、成员函数模板。
C++模板的偏特化与全特化
模板元编程与函数式:视频还没看完:https://www.bilibili.com/video/BV1ui4y1R78s/ ,这个视频可以用于作为C++进阶视频,太他妈多了。
类模板:
- 函数的形参的类型不确定:写线程池时有用到,因为线程池需要执行不同类型参数的函数。C++11实现的简单线程池、模板的使用实例
- 类模板写出的函数可以在编译器被优化:如bool类型的编译器常量作为模板的参数,那么编译器就会根据bool类型具体的值来删减代码。模板元编程与函数式
- 模板的惰性:就是模板函数没有被使用,编译器就不会去编译函数。
C++关键字
C++11的constexpr用于保证实参为编译期常量,如果constexpr中不是编译常量就会报错
C++ 宏(define)总结宏的作用:
- 替换作用:使含义更加明确或者使代码更加简洁
- 选择作用:头文件是否已经包含或者在哪个平台运行哪段代码
- 宏与其他具有替换作用的关键字的区别:说区别的时候,只要讲define只是简单地替换作用,然后讲一下其他具有替换作用的关键字的特点就行。
static:
- static 详解
静态变量:限定此变量的使用范围,并在限定访问内共享此变量。
静态函数:对于静态成员函数来说,可通过类名访问、函数内不可使用非静态成员、不隐含this。
静态成员变量必须在类外进行初始化。 - C++ static静态成员函数详解:普通成员函数隐含形参this
- C++中类的普通成员函数不能作为 pthread_create的线程函数:因为普通成员函数隐含形参this
- extern:用于变量声明,或暗示函数可能在别的源文件里定义
- extern "C":让此c++代码按照c语言的方式编译。使得“c++代码中使用c库”和“c代码中使用c++库”
volatile:直接从内存中取此变量而不是从寄存器中。
- volatile的作用: 作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。简单地说就是防止编译器对代码进行优化。
当要求使用volatile声明的变量的值的时候,系统总是重新从它所在的内存读取数据,而不是使用保存在寄存器中的备份。即使它前面的指令刚刚从该处读取过数据。 - 一般在多个线程访问同一个变量时,使用volatile修饰此变量
- 对于简单且经常调用的函数,防止栈溢出,而对编译器的一个建议,编译器不一定会执行
- 定义在类中的成员函数默认都是内联的
- inline和define区别
C++11
C++11 enable_shared_from_this:当一个类被共享智能指针 share_ptr 指向,且在类的成员函数里需要把当前类对象作为参数传给其他函数时,这时就需要传递一个指向自身的 share_ptr。
C++ primer中的C++11部分阅读:
-
- shared_ptr允许多个指针指向同一个对象,可以通过enable_shared_from_this从成员函数中返回当前对象的智能指针。
- unique_ptr则“独占”所指向的对象,但可以通过reset和release用于转移指针指向。
- weak_ptr:1.防止循环引用从而避免了内存泄漏 2.判断指向的对象是否已被销毁
- 项目实例:weak_ptr可以和shared_ptr结合使用,用于判断对象是否已经被释放了。比如网络库中有一个TcpConnection类的生命周期需要比Channel的长,在调用channel中的成员函数的时候,先使用TcpConnection的weak_ptr判断对象是否被析构掉了。
输入输出流(读写文件)
使用fopen打开文件时,如果文件不存在,就不需要执行fclose,否则会产生段错误
C++20协程
C++协程:挂起函数,恢复函数执行。
- co_await与awaiter:定义了挂起时(await_ready)、挂起后(await_suspend)、恢复后(await_resume)的需要执行的函数。使用co_await调用awaiter,从而挂起协程,并执行相应操作。
- co_return与co_yield:
- co_yield会调用promise_type中相应的函数,并挂起协程,等待协程被恢复和下一次co_yield被调用。
- co_return会调用相应函数并终止协程。
co_yield与co_await:
- co_yield是调用promise_type中相应的函数,并挂起协程。
- co_await是调用等待体(awaiter),并可能挂起协程。co_yield总是挂起,co_await可以自定义挂起时、挂起后、恢复后的行为。
- 如果需要向类中传入参数,那么我会使用co_yield。
不需要向类中传入参数,那么直接使用co_await,不用使用co_yield。
C++协程:在 C++ 当中,一个函数的返回值类型如果是符合协程的规则的类型,那么这个函数就是一个协程。符合协程的规则的类:
- 定义了
struct promise_type
,用于获取协程的返回对象、协程的初始化、异常处理、资源回收等。 - 拥有一个
std::coroutine_handle<promise_type> handle;
用于操作协程,如协程的恢复。
6.824 raft代码中,applyLoop可以直接写成协程,applyLogLoop中调用协程来处理已提交但是未应用到状态机的日志,处理完以后就将协程applyLoop挂起。
协程和线程的区别:
-
线程需要在内核态和用户态之间切换,切换操作由系统决定,线程的开销比较大。所以如果需要频繁切换线程的程序可以考虑使用协程进行代替,用于支持更高的并发。
【注】更高的并发就是指cpu在协程之间切换更快。
-
协程就是可以挂起和恢复的函数。
-
C++中使用co_yield和co_await将协程进行挂起,通过协程handle来恢复协程。在go语言中对协程的封装比较好,通过go关键字就可以启动一个并发的协程,协程之间可以通过channel传输数据。go语言:没有class、指针、goroutine与channel、select与time.After
-
可以使用事件循环+协程方式实现异步io,如python asyncio中当await处的代码完成时,它会通知事件循环继续执行挂起的协程。
C++常用库的使用
待学习:
leveldb
写好的C++使用小程序
C++与python的互相调用:
音视频开发
音视频基础知识(访问密码:1qaz)的主要内容:
- 常见的音视频压缩算法(如H264)、封装格式(如MP4)
- RGB与YUV
- MP4标准和h264格式
- 音视频的一些名词解释
- 音频编码原理
ffmpeg安装和基本使用(访问密码:1qaz)的主要内容:
- ffmpeg中各个库的功能介绍
- FFmpeg处理音视频流程
- ffmpeg常用的八类命令
网络编程
网络编程常用函数的封装 from 黑马程序员
C++进程控制:ps、top(动态显示运行中的进程)、kill、进程退出(exit() 和_exit())、等待子进程退出并清理子进程(wait() 和 waitpid())
- 不同架构的相同信号对应的数字可能是不同的,所以要让程序具有系统可移植性,最好使用信号的名称而不是编号。
- 通过调用系统命令/函数、快捷键等方式可以产生信号。
- 阻塞信号集(屏蔽信号)和未决信号集(记录产生到未处理的信号)
- 信号产生函数:kill函数、raise函数——自己给自己发信号、abort函数——给自己发送异常终止信号SIGABRT、alarm函数(闹钟)/setitimer函数——定时给自己发送SIGALRM信号
- 信号捕捉过程
- signal函数用于指定某个信号的信号处理函数
http:
-
multipart/form-data的HTTP消息文本:消息体通过 boundary 来分隔多个字段,被分隔的每个字段都有自己的小头部和小消息体