跳转到内容
新建笔记

C++ 语言混合片段

(1) ptr:Pointer,只针对额缩写;nullptr:空指针;

(2)指针大小固定,32位系统,4字节;64位,8字节;

(3)指针的读法:类型 *变量名; 的声明时,其中的 变量名 就是指针,它被用来指向指定类型的数据。通常会说 “变量名 是一个指向 类型 的指针”。

(4)语法糖:指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,到那时方便程序员使用,可以理解为加语法糖后和没加前编译的结果一样,为了方便写代码。

// c++的语法糖比如:引用
int a = 1;
int *p = a;
*p = 2;
int &b = a;
b = 3;

在许多编程语言中,三元运算符是一种简洁的方式来编写条件语句。例如,在C语言和JavaScript中:

int max = (a > b) ? a : b;

这行代码的意思是:如果a > b为真,则max等于a,否则等于b。这可以看作是以下代码的语法糖:

int max;
if (a > b) {
max = a;
} else {
max = b;
}

语法糖使得代码更加简洁和易于理解,但它们背后通常有更复杂的实现,编译器或解释器会将这些语法糖转换为更基本的代码结构。

(5) 小写字母:97-122 ; 大写字母:65-90

(6) sizeof是用来计算栈大小,不涉及全局区,故类的静态成员大小sizeof不涉及

(7)

1 类与结构一样,都有字节对齐的问题 2 类中普通的函数不占用类的大小 3 类中虚函数占用一个地址位宽(4或8字节),且不论有多少个虚函数一共只占这么多 4 类中常量不占用类大小: const static / enum{aa=2,bb=4}; 5 类中static修饰的变量不占用类大小,因为修饰后存储在静态区域 6 子类的大小等于子类新增的加上父类的大小

(8)引用只能一层,不能多层引用

(9) 基类的指针指向派生类的对象:基类指针调用其虚成员函数,则会调用其真正指向对象的成员函数,而不是基类中定义的成员函数,若不是虚函数,则不管基类指针指向的是哪个派生对象,调用的都是基类中定义的那个函数

(10)如果两个比特不同,则结果为1;如果两个比特相同,则结果为0

(11)当结构体的成员自然对齐规则(由其类型决定的内存对齐要求)不能满足时,编译器通常会自动插入填充(padding),以确保每个成员都按其对齐要求放置。

(12)数组下标是从0开始的,不管是几维。

(13)16进制字母部分不分大小写的 。

(14)在继承中派生类的对象调用构造函数的顺序,应该是先调用基类的构造函数,然后是成员中的对象对应类的构造函数,最后是派生类自己的构造函数(先父母,再客人,后自己。)

(15)用父类指针指向子类对象时,在父类的成员函数前加virtual,调用时调用的才是子类本身的那个成员函数,否则调用的还是父类的成员函数。

(16)指针和引用的区别:

指针和引用是两种不同的语言特性,它们都可以用来间接地访问另一个变量。不过,它们有一些关键的区别: 1)声明和使用:

  • 指针是一个变量,其值为另一个变量的地址。你需要使用&运算符来获取地址,并使用*运算符来访问或修改指向的值。
    int x = 10;
    int* p = &x; // p是一个指针,指向x的地址
    *p = 20; // 通过指针修改x的值
  • 引用是另一个变量的别名,一旦被初始化后,就不能改变引用的绑定(即它不能再开始引用另一个变量)。引用在声明时必须初始化,并且不需要使用特殊的符号来访问绑定的值。
    int x = 10;
    int& r = x; // r是对x的引用
    r = 20; // 直接修改x的值,因为r是x的别名

2)空值:

  • 指针可以有nullptr或空值,表示它不指向任何东西。
  • 引用必须引用某些东西,不能有空引用。

3)重新赋值:

  • 指针可以被重新赋值以指向另一个不同的地址。
  • 引用一旦被初始化后,就不能重新绑定到另一个变量;它始终引用最初被绑定的变量。

4)内存地址:

  • 指针本身是一个对象,占用内存,并有自己的内存地址。
  • 引用不是一个真实的对象;它没有自己的内存地址,它只是被引用对象的一个别名。

5)使用场景:

  • 指针通常用在动态内存分配、数组操作、低级编程(如操作系统或嵌入式系统编程)中。
  • 引用则用在传递参数给函数(特别是操作符重载)、防止对象复制产生的额外开销等场景。

6)数组和算术操作:

  • 指针支持算术运算,例如指针增加(指向数组中的下一个元素)。
  • 引用不支持算术运算,它们只是单个对象的别名。

dai buchong

(18)函数定义先后

在C++中,函数定义的先后顺序确实可能对程序的行为产生影响,尤其是在以下情况下:

1)前向声明与定义:如果你在一个文件中先使用了一个函数,然后在之后的地方定义了这个函数,你需要在首次使用之前提供函数的前向声明。否则,编译器在编译时不知道函数的签名,可能会导致编译错误。

2)模板函数:模板函数的定义必须在它们被使用之前可见,因为模板的定义需要在编译时展开。如果模板函数的定义在调用之后,模板实例化将无法正确进行。

3)内联函数:对于内联函数,定义必须在它被调用之前出现,因为编译器需要在调用点展开内联函数。如果内联函数的定义在调用之后,编译器无法在调用点展开它,这可能导致链接错误或运行时错误。

(19)头文件守卫

假设我们有一个项目,其中包含一个名为 MyMath.h 的头文件,该文件提供了一些数学函数的声明。这里是 MyMath.h 的内容:

MyMath.h
#ifndef MYMATH_H
#define MYMATH_H
// 这里声明一些数学函数
int add(int a, int b);
int subtract(int a, int b);
#endif // MYMATH_H

在这个例子中,#ifndef MYMATH_H 检查 MYMATH_H 宏是否未定义。如果未定义,#define MYMATH_H 将定义这个宏,然后编译 #ifndef 和 #endif 之间的代码,即头文件的其余部分。

现在,假设我们有两个源文件 main.cpp 和 math_functions.cpp,它们都需要使用 MyMath.h 中的函数:

main.cpp
#include "MyMath.h"
int main() {
int result = add(3, 4);
return 0;
}
// math_functions.cpp
#include "MyMath.h"
int add(int a, int b) {
return a + b;
}

当这两个源文件被编译时,会发生什么情况:

  1. 预处理阶段:编译器首先处理 #include 指令,将 MyMath.h 的内容插入到 main.cpp 和 math_functions.cpp 中。
  2. 头文件保护:当编译器处理 main.cpp 中的 MyMath.h 时,它检查 MYMATH_H 宏是否已定义。由于这是 MyMath.h 的第一次包含,宏未定义,所以编译器继续处理 MyMath.h 的内容,并定义了 MYMATH_H 宏。
  3. 避免重复包含:随后,当编译器处理 math_functions.cpp 中的 MyMath.h 时,它再次检查 MYMATH_H 宏。这次,宏已经定义了(因为在编译 main.cpp 时已经定义),所以 #ifndef MYMATH_H 的条件失败,编译器跳过 MyMath.h 的内容,不会导致重复定义或编译错误。
  4. 编译源文件:编译器继续编译两个源文件中的代码,包括 add 函数的定义和 main 函数中的 add 函数调用。 通过这种方式,头文件保护确保了即使头文件被多次包含,它的内容也只会被编译一次,避免了潜在的编译错误和不一致性。这是 C 和 C++ 项目中管理头文件依赖关系的一个标准做法。

(20)构造函数会在main 完成

#include "threadSeg_Yolo_RT.h"
std::string outpath = "F:/code/projects/threadSeg_Yolo_RT/assets/out";
prc::PROCESS yolo_process(outpath);
int main(){}

在C++中,全局或命名空间作用域的变量(例如 yolo_process)是在程序的编译阶段定义的,但它们的初始化是在程序的运行时执行的。也就是说,yolo_process 的类型 prc::PROCESS 的构造函数会在 main 函数执行之前被调用,以初始化这个对象。

具体来说,当你的程序开始执行时,操作系统会加载程序的代码和数据到内存中,然后开始执行 main 函数。在 main 函数执行之前,程序会进行一系列的初始化步骤,包括:

  1. 初始化全局变量和静态变量。
  2. 调用这些变量的构造函数(如果有的话)。

(21)静态、动态库

特性.lib 文件.dll 文件
全称LibraryDynamic Link Library
用途- 静态库:包含编译后的对象文件集合。
- 导入库:包含用于链接动态链接库的接口。
包含可由多个程序共享的函数和资源的二进制代码。
链接方式静态链接:在编译时将库的内容复制到最终可执行文件中。动态链接:在程序运行时加载库,函数地址在加载时解析。
分发通常需要与应用程序一起分发,因为它们是程序的一部分。可以被多个应用程序共享,不需要每个应用程序都包含一个副本。
更新和维护更新静态库可能需要重新编译所有使用它的程序。更新DLL可能只需要替换文件,而不需要重新编译使用它的程序。
依赖性静态库在编译时解决所有依赖性。DLL在运行时解决依赖性,可能引入运行时错误。
性能由于所有代码都包含在最终可执行文件中,可能提供更快的启动时间和更好的性能。启动时间可能较慢,因为需要在运行时加载和解析DLL。
灵活性较低,因为修改库需要重新编译使用它的所有程序。较高,因为DLL可以独立于应用程序更新和替换。
使用场景- 适用于不经常更改的代码库。
- 适用于需要高性能的应用程序。
- 适用于需要高度可维护性和灵活性的应用程序。
- 适用于跨多个应用程序共享代码和资源。

(22)查看属性和方法

在C++中,没有直接等同于Python的dir()函数的功能。Python的dir()函数可以列出一个对象的所有属性和方法,这是因为Python是一种动态类型的语言,可以在运行时获取对象的信息。然而,C++是一种静态类型的语言,对象的属性和方法在编译时就已经确定,无法在运行时获取。

(23)默认参数放在后面

在C++中,当函数参数提供默认值时,这些带有默认值的参数必须位于函数参数列表的末尾。这是因为如果默认参数位于非默认参数之前,它将导致函数重载解析的歧义。

(24)对对象this问题

如果有一个A类的实例ta和一个C类的实例tc,并且ta.b指向tc,那么当调用ta.fa()时,fa方法内部调用b->fc(),此时this在fc方法中指向的是tc,因为fc是被b所指向的对象调用的。简单来说,this总是指向调用方法的那个对象实例,无论这个方法是如何被调用的。在本例中,即使fc是通过A类的一个实例间接调用的,this在C类的fc方法中仍然指向C类的实例tc。

(25)

  • 在 C++ 中,如果你在类的定义外部提供成员函数的实现(即在类定义的 {} 外部),你通常不需要使用 override 关键字。
  • 在 C++ 中,noexcept 是一个关键字,用来指示一个函数不会抛出异常。当你在函数声明的末尾添加 noexcept,你告诉编译器这个函数保证不会抛出任何异常。在重写成员函数时,如果基类版本的成员函数也声明为 noexcept,那么派生类的重写版本也应该声明为 noexcept,以保证接口的一致性。

(26)对象指针

VIDEOTHREAD * __videoThread;

__videoThread 被定义为 VIDEOTHREAD 类型的指针,意味着它存储的是 VIDEOTHREAD 类型对象的内存地址。当你看到 -> 运算符时,它用于访问指针指向的对象的成员。 -> 运算符的工作原理:

  1. __videoThread 存储了一个对象的内存地址。
  2. 当你使用 __videoThread->someMemberFunction(); 这样的表达式时,-> 运算符首先会解引用 __videoThread 指针,得到它所指向的对象。
  3. 然后,它会访问该对象的 someMemberFunction 成员函数。

(27)ASCII

Bin(二进制)Oct(八进制)Dec(十进制)Hex(十六进制)缩写/字符解释
0000 00000000x00NUL(null)空字符
0000 00010110x01SOH(start of headline)标题开始
0000 00100220x02STX (start of text)正文开始
0000 00110330x03ETX (end of text)正文结束
0000 01000440x04EOT (end of transmission)传输结束
0000 01010550x05ENQ (enquiry)请求
0000 01100660x06ACK (acknowledge)收到通知
0000 01110770x07BEL (bell)响铃
0000 100001080x08BS (backspace)退格
0000 100101190x09HT (horizontal tab)水平制表符
0000 1010012100x0ALF (NL line feed, new line)换行键
0000 1011013110x0BVT (vertical tab)垂直制表符
0000 1100014120x0CFF (NP form feed, new page)换页键
0000 1101015130x0DCR (carriage return)回车键
0000 1110016140x0ESO (shift out)不用切换
0000 1111017150x0FSI (shift in)启用切换
0001 0000020160x10DLE (data link escape)数据链路转义
0001 0001021170x11DC1 (device control 1)设备控制1
0001 0010022180x12DC2 (device control 2)设备控制2
0001 0011023190x13DC3 (device control 3)设备控制3
0001 0100024200x14DC4 (device control 4)设备控制4
0001 0101025210x15NAK (negative acknowledge)拒绝接收
0001 0110026220x16SYN (synchronous idle)同步空闲
0001 0111027230x17ETB (end of trans. block)结束传输块
0001 1000030240x18CAN (cancel)取消
0001 1001031250x19EM (end of medium)媒介结束
0001 1010032260x1ASUB (substitute)代替
0001 1011033270x1BESC (escape)换码(溢出)
0001 1100034280x1CFS (file separator)文件分隔符
0001 1101035290x1DGS (group separator)分组符
0001 1110036300x1ERS (record separator)记录分隔符
0001 1111037310x1FUS (unit separator)单元分隔符
0010 0000040320x20(space)空格
0010 0001041330x21!叹号
0010 0010042340x22”双引号
0010 0011043350x23#井号
0010 0100044360x24$美元符
0010 0101045370x25%百分号
0010 0110046380x26&和号
0010 0111047390x27’单引号
0010 1000050400x28(开括号
0010 1001051410x29)闭括号
0010 1010052420x2A*星号
0010 1011053430x2B+加号
0010 1100054440x2C,逗号
0010 1101055450x2D-减号/破折号
0010 1110056460x2E.句号
0010 1111057470x2F/斜杠
0011 0000060480x300字符0
0011 0001061490x311字符1
0011 0010062500x322字符2
0011 0011063510x333字符3
0011 0100064520x344字符4
0011 0101065530x355字符5
0011 0110066540x366字符6
0011 0111067550x377字符7
0011 1000070560x388字符8
0011 1001071570x399字符9
0011 1010072580x3A:冒号
0011 1011073590x3B;分号
0011 1100074600x3C<小于
0011 1101075610x3D=等号
0011 1110076620x3E>大于
0011 1111077630x3F?问号
0100 00000100640x40@电子邮件符号
0100 00010101650x41A大写字母A
0100 00100102660x42B大写字母B
0100 00110103670x43C大写字母C
0100 01000104680x44D大写字母D
0100 01010105690x45E大写字母E
0100 01100106700x46F大写字母F
0100 01110107710x47G大写字母G
0100 10000110720x48H大写字母H
0100 10010111730x49I大写字母I
010010100112740x4AJ大写字母J
0100 10110113750x4BK大写字母K
0100 11000114760x4CL大写字母L
0100 11010115770x4DM大写字母M
0100 11100116780x4EN大写字母N
0100 11110117790x4FO大写字母O
0101 00000120800x50P大写字母P
0101 00010121810x51Q大写字母Q
0101 00100122820x52R大写字母R
0101 00110123830x53S大写字母S
0101 01000124840x54T大写字母T
0101 01010125850x55U大写字母U
0101 01100126860x56V大写字母V
0101 01110127870x57W大写字母W
0101 10000130880x58X大写字母X
0101 10010131890x59Y大写字母Y
0101 10100132900x5AZ大写字母Z
0101 10110133910x5B[开方括号
0101 11000134920x5C\反斜杠
0101 11010135930x5D]闭方括号
0101 11100136940x5E^脱字符
0101 11110137950x5F_下划线
0110 00000140960x60`开单引号
0110 00010141970x61a小写字母a
0110 00100142980x62b小写字母b
0110 00110143990x63c小写字母c
0110 010001441000x64d小写字母d
0110 010101451010x65e小写字母e
0110 011001461020x66f小写字母f
0110 011101471030x67g小写字母g
0110 100001501040x68h小写字母h
0110 100101511050x69i小写字母i
0110 101001521060x6Aj小写字母j
0110 101101531070x6Bk小写字母k
0110 110001541080x6Cl小写字母l
0110 110101551090x6Dm小写字母m
0110 111001561100x6En小写字母n
0110 111101571110x6Fo小写字母o
0111 000001601120x70p小写字母p
0111 000101611130x71q小写字母q
0111 001001621140x72r小写字母r
0111 001101631150x73s小写字母s
0111 010001641160x74t小写字母t
0111 010101651170x75u小写字母u
0111 011001661180x76v小写字母v
0111 011101671190x77w小写字母w
0111 100001701200x78x小写字母x
0111 100101711210x79y小写字母y
0111 101001721220x7Az小写字母z
0111 101101731230x7B{开花括号
0111 110001741240x7C|垂线
0111 110101751250x7D}闭花括号
0111 111001761260x7E~波浪号
0111 111101771270x7FDEL (delete)删除

(28)‘’表示字符常量,“”表示字符串常量。

(29)main 函数不是必须

main函数和其他函数的汇编结果相同,只是好多程序默认将main函数设置为程序的入口,可以手动更改程序入口:

终端窗口
# 指定程序起点
gcc -nostartfiles -e<functionName> test.c

(30)变量是内存地址的别名

CPU对内存的寻址方式是间接寻址,全局变量内存地址唯一,栈变量(局部变量)不唯一。

# a = 1;
mov DWORD PTR [rip + 0x2f18], 0x1 #40402c <a>

(31)this 指针 成员函数和普通函数汇编结果相同,只是this指针作为成员函数的默认参数而被隐藏

(32)构造函数 构造函数和普通函数的汇编相同(有this指针),但是派生类的构造函数中其汇编里包含了一个调用(call)基类的构造函数语句。如果基类有虚函数,那么基类构造函数汇编中又新增仅包含其自身的虚函数表的地址,派生类的构造函数汇编中也同样新增仅包含其自身类(不含基类)的虚函数表的地址。

(32)CPU对多态无感 基类指针可以指向派生类对象,反过来不行,派生类指针不可以指向基类对象

  • 多态常会用基类指针指向派生类对象;
  • 多态会用派生类的结构特点复用基类的成员;
  • 多态会用虚函数来拓展派生类的特性;
  • 很多语言都有多态(java, c++, python)。
// 基类A,派生类B
A* P = &b; // ✔,相当于降级成基类使用
B* p = &a; // ✖,不能升级成派生类使用
B* p = (B*)&a; // ✔,即使强转换编译通过,但是派生类部分的内容没有分配内存,即使编译通过,调用相关部分时会出现异常
int* p = (int*)&b; // 所以无感

(32)指针变量|数组指针|野指针 指针变量和普通变量的读写的汇编结果相同,指针变量的区别是含*操作。指针变量存的是变量的内存地址,直接赋值而不是赋地址,会造成野指针

int* p;
*p = 2; // 野指针

万物皆指针:

  • 指针操作(*,->)不是指针变量特有的,普通变量甚至立即数都可进行指针操作
  • 万物皆有地址,万物皆有指针
  • 指针可以读写任意内存地址,除了指针管理单元(MMU),知道了私有变量的地址,就可用指针读写
int* p = &a;
*p = 1; // ✔
*(int*)&a = 1; // ✔
*(int*)0x1234 = 1 // ✔ a address is 0x1234;
// e.g. 2 类A的对象a
...
a.x = 1;
(&a)->x = 1;

(33)参数传递|传值|传引用 C++中的引用实际上是对指针的语法糖,它不允许修改引用本身的地址,而指针则可以。此外,C++中的引用在声明后必须被初始化,并且一旦被初始化后不能指向另一个变量。而指针可以在任何时候指向任何地址,包括NULL。

1)传值(Pass by Value):

  • 传值是指将实际参数的值复制到一个临时的变量(通常是函数的局部变量)中,然后将其传递给函数。
  • 函数接收的是原始值的副本,对参数的修改不会影响到原始数据。
  • 这种方式适用于简单数据类型,如整数、浮点数、字符等。

2)传引用(Pass by Reference):

  • 传引用是指将实际参数的内存地址传递给函数,函数通过这个地址直接访问和修改原始数据。
  • 函数接收的是原始数据的引用,对参数的修改会影响到原始数据。
  • 这种方式适用于复杂数据类型,如数组、结构体、类对象等,可以避免复制大的数据结构。

3)传指针(Pass by Pointer):

  • 传指针与传引用类似,也是将实际参数的内存地址传递给函数,但传递的是地址的副本。
  • 函数通过这个地址的副本来访问和修改原始数据,对参数的修改同样会影响到原始数据。
  • 传指针通常用于C语言,它提供了更多的灵活性,如可以传递NULL指针,或者在函数内部修改指针的值。

传值示例:

void modifyValue(int x) {
x = 10; // 修改x的值,但不影响传入的原始值
}

传引用示例:

void modifyReference(int &x)
{ // 使用引用作为函数参数
x = 10; // 修改x的值,影响传入的原始值
}
// e.g. 2
void foo(int *x)
{
*x = 10; // 通过指针修改了实参
}
int main()
{
int a = 5;
foo(&a);
// a 被修改为 10
}

传指针示例:

void modifyPointer(int *x)
{ // 使用指针作为函数参数
*x = 10; // 通过指针修改x指向的值,影响传入的原始值
}
// e.g. 2
void foo(int *x)
{
*x = 10; // 通过指针修改了实参
}
int main()
{
int a = 5;
foo(&a);
// a 被修改为 10
}

(34)指针的妙用 1)处理TCP/IP layers时 接受网卡的原始数据: TCP/IP Layer: Phthsic → Data link → Internet → Transport → Application

RawData = Ethernet header + IP header + TCP header + Data

0偏移时候处理以太网数据,偏移sizeof(ethernet_header)处理IP header的数据。

2)芯片制造商会把控制I/O引脚的寄存器与内存统一编地,并提供手册标识出寄存器对应的地址,以便操作寄存器:

void turn_on()
{
unsigned int * REG_IO = 0XFF008800;
*REG_IO = 1;
}
3)对数据流的多样性解析
对于一段stream,使用不同类型的指针会解析出不同类型的数据
```c++
unsigned char* data = raw_data;
//if parse data with struct, the raw_data will be parsed into corresponding struct.
struct A{
int a;
int b;
};
struct A *data = raw_data;
<font color =red><b> (35)数组越界不报错情况</b></font>
这发生在数组向下低端的情况,即数组向下低端的地址,对应的内存是未被任何使用的。想高端地址溢出不行。
```c++
lofng a[2];
a[1] = 1;
a[0] = 2;
a[-1] = 3; // 向下偏移
a[-2] = 3;

(35)std::string::npos std::string::npos 是 C++ 标准库中 std::string 类的一个静态常量,表示字符串搜索函数没有找到指定的子字符串。npos 的值是 size_t 类型的最大值,通常是一个非常大的数字,用来表示“没有找到”的情况。

if (iRecvResult == 0 || std::string(data, data.size()).find("END_OF_IMAGE") != std::string::npos)

(36)子串和子序列

子串和子序列是两个不同的概念,它们在字符序列中的含义如下:

  1. 子串(Substring):

    • 子串是指在原始字符串中连续出现的一系列字符。
    • 子串在原始字符串中占据连续的位置,不能跳跃。
    • 例如,在字符串 “pwwkew” 中,“pww”、“wwk” 和 “kew” 都是子串。
  2. 子序列(Subsequence):

    • 子序列是原始字符串中字符的序列,但不一定连续。
    • 子序列中的字符可以不连续出现在原始字符串中,它们的位置可以是跳跃的。
    • 例如,在字符串 “pwwkew” 中,“pwke” 是一个子序列,因为它包含了 “pwwkew” 中的字符 ‘p’、‘w’、‘k’ 和 ‘e’,但它们在原始字符串中不是连续的。

在你给出的例子中,“wke” 是 “pwwkew” 的一个子串,因为它的字符在原始字符串中是连续的。而 “pwke” 虽然包含了相同的字符,但它不是子串,因为它的字符在原始字符串中不是连续的,但它可以是子序列。