各种进制的表示
0B00001101 0xff 076
clangd消除警告的方法
- 核心参考官方文档
- 编写项目的
.clangd
文件,然后添加需要抑制的内容,这个内容可以根据提示获取
Diagnostics:
Suppress: -Wc++17-extensions
- 头文件检查的,在
.clang-tidy
添加
Checks: -misc-definitions-in-headersChecks: -misc-definitions-in-headers
clangd找不到头文件方法
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1
- 会生成
compile_commands.json
- clangd索引会生成
.cache
文件夹
clang-format格式化方法
- 傻逼clangd-format必须制定
-style=file
才会根据.clang-format格式化,否则直接 - vim文件可以参考
https://raw.githubusercontent.com/llvm-mirror/clang/master/tools/clang-format/clang-format.py
" BEGIN CLANG-FORMAT CONFIG
" imap <C-K> <c-o> :pyf /home/zhuling/code_standards/format/clang-format.py<cr>
nmap <leader><leader>f :pyf /home/zhuling/code_standards/format/clang-format.py<cr>
function! Formatonsave()
let g:clang_format_path = "/home/zhuling/code_standards/format/clang-format"
let l:formatdiff = 1 " 保存文件时针对改动的代码进行格式化,历史代码不动
"let l:lines="all" "如果保存文件时也希望对历史代码做格式化,可取消该行的注释
pyf /home/zhuling/code_standards/format/clang-format.py
endfunction
autocmd BufWritePre *.h,*.cc,*.cpp call Formatonsave()
" END CLANG-FORMAT CONFIG
compile_commands.json生成方法
- cmake,
apt install cmake
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1
- bear,
apt install bear
如何编译的在前面加上
bear --
,类似bear -- make,bear make
- clang,
apt install clang
clang -MJ compile_commands.json source.cpp
malloc
C++多态的多种实现
- 重载。函数重载和运算符重载,编译期。
- 虚函数。子类的多态性,运行期。 在继承关系中,对于父类的方法我们也同样使用。但是正常来说,我们希望方法的行为取决于调用方法的对象,而不是指针或引用指向的对象有关。
- 模板,类模板,函数模板。编译期
智能指针
weak_ptr
- weak_ptr<T> 模板类中没有重载 * 和 -> 运算符,这也就意味着,weak_ptr 类型指针只能访问所指的堆内存,而无法修改它。
- weak_ptr主要针对shared_ptr的空悬指针和循环引用问题
- 空悬指针问题:有两个指针p1和p2,指向堆上的同一个对象Object,p1和p2位于不同的线程中。假设线程A通过p1指针将对象销毁了(尽管把p1置为了NULL),那p2就成了空悬指针。
- weak_ptr不控制对象的生命期,但是它知道对象是否还活着。如果对象还活着,那么它可以提升为有效的shared_ptr(提升操作通过lock()函数获取所管理对象的强引用指针);如果对象已经死了,提升会失败,返回一个空的shared_ptr
- 当 weak_ptr 类型指针的指向和某一 shared_ptr 指针相同时,weak_ptr 指针并不会使所指堆内存的引用计数加 1;同样,当 weak_ptr 指针被释放时,之前所指堆内存的引用计数也不会因此而减 1。也就是说,weak_ptr 类型指针并不会影响所指堆内存空间的引用计数
static作用
- 如果用 static 关键字修饰的话,该变量便会存 放在静态数据区,其生命周期会一直延续到整个程序执行结束
- 用 static 对全局变量进行修饰改变了其 作用域范围,由原来的整个工程可⻅变成了本文件可⻅
- 用 static 修饰函数,情况和修饰全局变量类似,也是改变了函数的作用 域
- 对类中的某个函数用 static 修饰,则表示该函数属于一个类而 不是属于此类的任何特定对象;如果对类中的某个变量进行 static 修饰,则表示该变量以及所 有的对象所有,存储空间中只存在一个副本,可以通过;类和对象去调用。
强制转换
dynamic_cast
- 用于指针或者引用父类转子类,如果转失败返回NULL
static_cast
- 和C的强制转换差不多
const_cast
- 用于去除和加上const
reinterpret_cast
- 完成任意指针类型向任意指针类型的转换
others
- string的substring函数第二个参数是len长度而不是pos
push_back和emplace_back区别
- push_back 接受一个对象,将其拷贝(或移动)到容器中,而 emplace_back 接受一组参数,直接在容器中构造对象。
- push_back 调用对象的拷贝构造函数(或移动构造函数),将对象拷贝(或移动)到容器中,而 emplace_back 调用对象的构造函数,在容器中直接构造对象。
- 使用 emplace_back 比 push_back 更高效,因为它避免了对象的拷贝(或移动)操作,直接在容器中构造对象。此外,emplace_back 还可以支持可变参数,可以方便地构造带有多个参数的对象。
- emplace_back 的参数必须和对象的构造函数参数匹配,否则会编译错误。而 push_back 接受的是一个对象,因此可以隐式转换类型
编译顺序
预处理
- 将所有一起编译的
.c
文件合并 - 将宏展开计算,将注释以及删除无用字符
- 将头文件内容插入到源文件(因为#include也是宏,本质还是宏展开),头文件路径的添加可以通过
-I
- 通过这个步骤的带一个文件包含了精简化后的所有的源代码(当然还是不包括库的)
编译
- 这一步将上一步得到的源代码进行编译成为汇编代码
- 这一步只会检查代码的正确性,不会检测所需要的函数是否有实现,如果函数没有定义,但是引用了会在这里报错,但是定义了,使用了,没有实现,这里还不会报错
汇编
- 汇编器会将汇编代码转换成二进制指令,同时生成与指令相关的符号表和重定位表
链接
- 这一步非常重要,链接器会解析所有目标文件中的符号,包括函数名、变量名等,并为每个符号分配一个唯一的地址。由于不同目标文件中的符号地址可能会相互引用,链接器需要对这些符号进行重定位,即将符号的地址修改为正确的地址。链接器还会将程序所依赖的库文件链接到可执行文件中,以便程序在运行时可以调用库函数。
- 定义了使用了没有实现的函数会在这里被发现,包括引用了外部的
.a静态库
但是没有通过-l
加入也是在这里发现,一般是白色的undefine
的错误,链接器会检查每个跳转的是否ok - 这一步中如果需要加入
.a(linux)或者.lib(win)
的路径,可以通过-L
,对应-l
通常就是静态库的名字
右值引用
类型
- 左值指存储在内存中、有明确存储地址(可取地址)的数据;
- 右值是指可以提供数据值的数据(不可取地址);右值又分为纯右值(prvaule),亡值(xvalue),纯右值指的是类似1,2这种纯数字,这种不能纯数字不能被修改,亡值指的是类似函数返回值,函数参数,临时变量这种临时构建,非引用返回的临时变量、运算表达式产生的临时变量、原始字面量和 lambda 表达式,这部分可以被右值引用修改
- 右值引用可以延长生命周期如下面,可以减少临时对象的构建
int&& value = 520;
class Test{
public:
Test(){
cout << "construct: my name is jerry" << endl;
}
Test(const Test& a){
cout << "copy construct: my name is tom" << endl;
}
};
Test getObj(){
return Test();
}
int main(){
int a1;
int &&a2 = a1; // error
Test& t = getObj(); // error
Test && t = getObj();
const Test& t = getObj();
return 0;
}
- 可以通过移动构造函数减少不必要的构造
std::move
- 将数据转化为右值,标记对象为临时对象
- 本身上并没有什么效率提升,仅仅只是一个转换为右值的功能,但是因为可以转换为右值,可以调用右值的构造函数,右值的构造又可以避免对象的拷贝(虽然左值也可以实现,但是左值更多是copy而不是move,因此会有const,右值就没有const),具体参考
::运算符
- 表示使用某个命名空间下的操作
- 如果前面没有命名空间表示全局的意思,全局的命名空间
宏中使用do{}while(0)原因
- 使用do{...}while(0)构造后的宏定义不会受到大括号、分号等的影响,总是会按你期望的方式调用运行
如果仅仅使用func1();func2(); 遇到使用if加单行语句就会出问题
构造函数=default的作用
- 用于标记使用默认的构造函数,如果有的构造函数和编译器默认生成的一样,字节使用=default(特别是拷贝构造函数)
C++代码计算运行时间
- 参考示例
int main() {
// "start" and "end"'s type is std::chrono::time_point
time_point<system_clock> start = system_clock::now();
{
std::this_thread::sleep_for(std::chrono::seconds(2));
}
time_point<system_clock> end = system_clock::now();
std::chrono::duration<double> elapsed = end - start;
std::cout << "Elapsed time: " << elapsed.count() << "s";
return 0;
}
奇形怪状的错误
type nofind 错误
- 一定需要考虑是不是头文件重复包含的问题 A头文件和B头文件都include了对方,导致顺序出问题了,可以考虑用extern 声明避免nofound
the vtable symbol may be undefined because the class is missing its key function
- 类中的第一个虚函数没有实现,导致虚函数表没有办法实现
warning:#pragma once in main file
- 不要编译头文件,带着头文件编译就会有这个错误,确保g++的命令接的全是.cpp的文件
multiple definition of
- 核心是因为头文件中包含了实现(理论上应该放到cpp文件中)
- 因为C++编译器是按照 .cpp文件变异成为.o文件,如果多个cpp引用同一个头文件,每个.o文件都有一份实现,最后.o链接的时候就会出问题
- 但是下面几种情况是例外的
- 内联函数的定义
- 因为内联函数直接被替换了, 不会出现函数的概念
- class/struct/union 定义的函数
- 类里面的函数实现如果放在头文件中默认变成内联的,直接替换不会出问题
- const 和 static 变量
- static限制了变量的作用域,该变量仅在引用.h的源文件中有效,也就是说.h被引用了几次这个变量就被定义了几次,且各变量之间互不影响(各变量具有不同的内存地址)。这种方法不适用于定义全局变量,因为它们不是同一个变量。
- const 默认就是static类型的变量
- 内联函数的定义
[!tip] 参考 zhuanlan.zhihu.com/p/577994847 注意头文件规则,避免链接错误:重复定义(multiple defination) - bw_0927 - 博客园 C++ multiple definition 总结 - 简书
socket broken pipe问题
- 对已经关闭的socket 执行二次写的时候会出现, 可以通过
signal(SIGPIPE, SIG_IGN);
解决
如何给random函数设置随机的seed
#ifdef __i386
__inline__ uint64_t rdtsc()
{
uint64_t x;
__asm__ volatile ("rdtsc" : "=A" (x));
return x;
}
#elif __amd64
__inline__ uint64_t rdtsc()
{
uint64_t a, d;
__asm__ volatile ("rdtsc" : "=a" (a), "=d" (d));
return (d<<32) | a;
}
#endif
- 使用嵌入式汇编来获取CPU的时间戳计数器(TSC)的值。TSC是一个递增的64位计数器,它记录了从电源启动或者最近一次复位以来的时钟周期数。64位架构中,由于TSC的值超过了32位寄存器的容量,所以需要使用两个32位寄存器来存储TSC的高位和低位,并将它们进行位移和合并操作,最后返回一个完整的64位值
sizeof和strlen区别
- sizeof计算char* 类型时候计算的是指针的长度,而不是字符串长度,strlen则看作是字符串,但是字符串不计算
\0
,因为\0
被视为字符串结束的位置 - sizeof计算char[]类似的时候计算的是字符串长度,而且包括
\0
C++协程实现
- 底层还是通过linux提供的API makecontext实现的,实现可以参考
multiple definition of问题
- 不要把全局变量以及全局方法的定义放在头文件里!!!!,这样就会出错
- 核心是因为编译器编译分开每一个cpp的时候直接他妈的展开了所有的头文件,放在一起编译就不会有问题
- 但是class的静态函数就没问题
spdlog的问题
显示一大堆nodefine的问题
[!tip] 参考 https://blog.csdn.net/CSSDCC/article/details/121854773 https://fmt.dev/latest/usage.html#usage-with-cmake
- 可以直接在makefile中添加
-lfmt
手动链接,makefile
代码规范
核心参考Google C++规范 #代码规范
- 注意头文件和对应的源文件实现要放在同一个文件夹中, 不要用愚蠢的include文件夹
命名
- 枚举enum的命名 应当和常量 或宏 一致: kEnumName 或是 ENUM_NAME.
- 宏的命名不要以下划线开头,因为这是C语言标识符保留的
- 变量使用下划线命名法+小写
- 函数和类使用驼峰法+首字母大写
- 命名空间使用全小写,不要出现下划线
- 类内部属性使用下划线命名法+下划线的后缀
player_pos_
Class
- 类应该public放在前面(因为这是直接暴露给使用者的),private放在后面
- set值方法
set_pos(Pos pos)
使用 set+属性名字 小写 - get值方法直接使用
pos()
使用小写名字 - 类内部属性使用下划线命名法+下划线的后缀
player_pos_
项目组织规范
- 参考 GitHub - chenxuan520/cppnet: Lightweight C++ network library
- 第三方引用的仓库, 如果是编译类型的, 通过 submodule 添加到 third_party , 然后编译时候一起编译, 如果是二进制类型的, 通过脚本进行下载引入即可, 不要直接放到仓库中
- hpp 和对应的 cpp 放在同一文件夹
- test 文件夹是必要的, 作为单元测试存在
错误标识
- 通过返回RC,然后设置RC到string的转移,大多数使用这种办法
- 麻烦,但是标准程度高
- 返回特定的Err结构体,类值go的error,
- 标准,但是通用型差
- 返回int标识类型,参数中携带std::string& err_msg标识错误信息,微信支付方法
- 通常小于0标识系统错误,大于0标识逻辑错误
有用的宏
[!tip] 这些宏必须经两层包装才能被使用
__LINE__ :当前行数
__func__ :当前函数名
__FILE__ :当前文件名
__COUNTER__ :计数器,会随着调用递增
Lambda 表达式捕获
- 默认是使用
=
捕捉所有的值拷贝,使用&
捕捉所有的引用拷贝 - 如果使用
[x]
默认只捕获这个值拷贝,[&x]
默认只捕获这个引用拷贝
内存泄漏检查工具 valgrind
- 安装
apt install valgrind
- 使用
valgrind --tool=memcheck --leak-check=full <二进制文件>
- 注意 这个和cmake > 内存泄漏检查sanitize 冲突不要一起使用
参考(重要)
- project-setup
- files
- https://blog.csdn.net/weixin_51609435/article/details/126571057