网站设计资料,乐wordpress,网站域名好了下一步,做网站解析要多久目录 C/C语言C内存分区malloc/free与new/delete的区别联合体联合体大小的计算 结构体对齐为什么需要结构体内存对齐 结构体与联合体的区别左值引用与右值引用指针和引用的区别迭代器失效static关键字在C语言的作用进程地址空间的分布内联函数 三大特性构造函数不能是虚函数析构… 目录 C/C语言C内存分区malloc/free与new/delete的区别联合体联合体大小的计算 结构体对齐为什么需要结构体内存对齐 结构体与联合体的区别左值引用与右值引用指针和引用的区别迭代器失效static关键字在C语言的作用进程地址空间的分布内联函数 三大特性构造函数不能是虚函数析构函数得是虚函数多态中的虚函数表重载重写重定义动态的多态虚表指针是在什么阶段完成初始化的呢虚表又是在什么阶段生成的 网络TCP三次握手TCP四次挥手-断开连接can通信的原理 多线程互斥锁和信号量条件变量的区别功能不同使用方式不同适用场景不同 进程间的通信线程间通信 排序快排的实现逻辑挖坑法交换法 C/C语言
C内存分区
1.栈Stack由编译器自动分配和释放存放函数参数、局部变量等。栈是连续的内存区域从高地址向低地址增长。
2.堆Heap由程序员手动分配和释放存放动态分配的内存数据。堆是不连续的内存区域从低地址向高地址增长。
3.全局/静态存储区Global/Static Storage Area存放全局变量、静态变量等在程序启动时由系统自动分配在程序结束时由系统回收。全局/静态存储区是不连续的内存区域从低地址向高地址增长。
4.常量存储区Constant Storage Area存放常量字符串、const变量等不允许被修改。常量存储区在程序运行期间始终存在且只读通常位于全局/静态存储区中。
5.程序代码区Code Area存放程序的二进制代码通常位于内存的低地址处只读。
malloc/free与new/delete的区别
malloc和free是C语言中的函数用于动态内存的分配和释放。而new和delete是C中的运算符也用于动态内存的分配和释放。
以下是malloc/free和new/delete之间的区别
语法malloc和free是C语言函数使用时需要包含头文件stdlib.h并通过指针来操作内存而new和delete是C运算符直接在对象上调用不需要使用指针。类型安全malloc返回的是void指针在使用时需要进行类型转换而new可以根据类型自动分配正确大小的内存并返回特定类型的指针不需要进行类型转换。构造函数和析构函数new在分配内存后会自动调用对象的构造函数进行初始化而malloc只是分配一块内存不会调用构造函数同样delete在释放内存前会调用对象的析构函数进行清理而free只是简单地释放内存。内存管理new/delete会维护对象的类型信息和内存的分配大小便于内存管理和回收而malloc/free只负责内存的分配和释放无法自动管理对象的生命周期。异常处理new在内存分配失败时会抛出std::bad_alloc异常可以进行异常处理而malloc在内存分配失败时返回NULL需要手动检查是否分配成功。
总的来说使用C时推荐使用new/delete它们更符合面向对象的思维方式提供了更好的类型安全和内存管理。而在需要与C语言兼容或特定场景下可以使用malloc/free来进行动态内存分配和释放。
联合体
一种特殊的自定义类型 这种类型定义的变量也包含一系列的成员特征是这些成员公用同一块空间所以联合也叫共用体
联合的特点是成员是共用同一块内存空间的这样一个联合变量的大小至少是最大成员的大小因为联 合至少得有能力保存最大的那个成员
联合体大小的计算
联合的大小至少是最大成员的大小。
当最大成员大小不是最大对齐数的整数倍的时候就要对齐到最大对齐数的整数倍。
结构体对齐
第一个成员在与结构体变量偏移量为0的地址处。其他成员变量要对齐到某个数字对齐数的整数倍的地址处。结构体总大小为最大对齐数每个成员变量都有一个对齐数的整数倍。对齐数 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8·
为什么需要结构体内存对齐
以32位机器为例结构体中的变量大小不固定但是32位机器的数据总线就32位内部是以字节编址但是是以四个字节为一组进行寻址的如果内存对齐可以保证四个字节的数据可以被CPU一次读取出来但如果没有内存对齐四个字节的数据就可以分布的紧凑但是CPU需要两次才能提取到完成的数据虽然节省了空间但是降低的命中率严重拖慢CPU的执行速率。
结构体与联合体的区别
结构体struct是一种按照一定顺序排列并能够包含不同数据类型的数据集合。结构体中的每个成员变量都有自己的内存空间因此结构体的大小等于所有成员变量的大小之和。这也意味着结构体中的不同成员变量可以同时存储不同的值联合体union也是一种数据集合但它只能存储其中一个成员变量的值。不同成员变量共用同一块内存空间它们的值会互相覆盖。联合体的大小等于它的最长成员变量的大小。
左值引用与右值引用
左值引用用于绑定到具名对象或者拥有持久存储的表达式它要求被引用的对象必须是可修改的。通过左值引用可以对被引用的对象进行读写操作而且可以使用引用来修改原始对象的值。左值引用使用单个 符号表示。
cppCopy Codeint x 10;
int ref x; // 左值引用绑定到变量x
ref 20; // 修改了原始对象x的值右值引用用于绑定到临时对象、匿名对象或者即将被销毁的对象它可以延长对象的生命周期或者实现移动语义。通过右值引用可以获取临时对象的资源或者通过移动操作减少对象的拷贝开销。右值引用使用双 符号表示。
cppCopy Codeint rref 30; // 右值引用绑定到临时对象
rref 40; // 修改了临时对象的值虽然没有实际意义C11引入了移动语义和完美转发右值引用在这些新特性的实现中起到了重要的作用。通过右值引用可以区分左值和右值实现更高效的对象管理和转移提高程序的性能。
需要注意的是左值引用和右值引用在语法上是相同的但根据绑定的对象类型不同会有不同的语义和使用方式。
指针和引用的区别
指针是一个变量存储的是一个地址指向内存的一个存储单元引用是原变量的一个别名跟原来的变量实质上是同一个东西。指针可以有多级引用只能是一级指针可以在定义的时候不初始化引用必须在定义的时候初始化指针可以指向NULL引用不可以为NULL指针初始化之后可以再改变引用不可以sizeof 的运算结果不同在32位机器上指针是4个字节引用是绑定类型的大小自增运算意义不同指针加一是移动一个指向类型的字节大小而引用加一是绑定的对象加一
迭代器失效
指向意义改变变成野指针了
由于插入元素使得容器元素整体“迁移”导致存放原容器元素的空间不再有效从而使得指向原空间的迭代器失效。
由于删除元素使得某些元素次序发生变化使得原本指向某元素的迭代器不再指向希望指向的元素。
static关键字在C语言的作用
限制变量作用域虽然变量一直存在但是只能在指定作用域中使用声明静态变量在进程地址空间的静态区中只能在第一次初始化声明静态函数保证静态函数只能在当前源文件调用其它文件不可见保证安全性
进程地址空间的分布
代码段Code Segment 也称为文本段Text Segment。存放程序的机器指令或可执行代码。通常是只读的以确保程序的指令不会被修改。 数据段Data Segment 存放全局变量和静态变量。包括已初始化的数据和全局/静态变量的初始值。在程序启动时进行初始化并且可以被读写。 BSS段Block Started by Symbol 存放未初始化的全局变量和静态变量。它们在程序启动时会自动被初始化为0或者空指针。不需要保存具体的初始值因此节省了存储空间。 堆Heap 用于动态内存的分配和释放。在程序运行时可以通过函数如malloc()和free()来管理堆内存。堆是从低地址向高地址扩展的由操作系统动态分配和回收内存。 栈Stack 存放函数调用时的局部变量、函数参数、返回地址等。栈是一种自动分配和释放内存的方式遵循后进先出LIFO的原则。 内核空间Kernel Space 用于操作系统内核执行和管理。存放操作系统核心数据结构、设备驱动程序等。用户程序无法直接访问内核空间需要通过系统调用来与内核交互。
内联函数
内联函数是一种特殊的函数它与普通函数最大的不同在于在编译时会被直接嵌入到调用该函数的地方从而避免了函数调用的开销。内联函数可以帮助提升程序的运行效率。
在 C 中一个函数可以通过关键字 inline 来声明为内联函数。下面是一个内联函数
当该函数被调用时编译器将会将其内部代码复制到调用处并且优化成一个表达式或语句。
相比于普通函数在使用内联函数时需要注意以下几点
内联函数的代码必须简单且短小以便编译器能够将其嵌入到调用点中。否则如果函数体过大可能会导致代码可读性降低、代码体积增大以及编译时间增加等问题。内联函数的声明通常需要放在头文件中以便所有使用到该函数的源文件都能够获得其定义。否则在链接时可能会出现找不到函数定义的错误提示。内联函数不支持递归调用和函数指针调用因为这些调用方式无法被简单地展开。
总之内联函数可以提高程序的运行效率但需要注意函数代码的复杂度和调用方式等因素。对于一些频繁调用、简单且短小的函数可以使用内联函数来优化程序性能。
三大特性
构造函数不能是虚函数
构造函数不能是虚函数因为派生类不能继承基类的构造函数将构造函数声明为虚函数没有意义。
vbtl在构造函数调用后才建立因而构造函数不可能成为虚函数从实际含义上看在调用构造函数时还不能确定对象的真实类型由于子类会调父类的构造函数并且构造函数的作用是提供初始化在对象生命期仅仅运行一次不是对象的动态行为也没有必要成为虚函数。
析构函数得是虚函数
如果派生类中申请了内存空间并在其析构函数中对这些内存空间进行释放。假设基类中采用的是非虚析构函数当删除基类指针指向的派生类对象时就不会触发动态绑定因而只会调用基类的析构函数而不会调用派生类的析构函数。那么在这种情况下派生类中申请的空间就得不到释放从而产生内存泄漏
多态中的虚函数表
虚函数表的位置代码段
在编译时派生类就会拷贝一份基类的虚函数指针数组放进派生类为自己的虚表准备的空间中然后看看自己重写了哪个虚函数就按照其相应的位置去将元素赋为自己重写的虚函数的函数指针。
如果一个类包含了虚函数那么在创建该类的对象时就会额外地增加一个数组数组中的每一个元素都是虚函数的入口地址。不过数组和对象是分开存储的为了将对象和数组关联起来编译器还要在对象中安插一个指针指向数组的起始位置。*这里的数组就是虚函数表Virtual function table简写为vtable。
基类的虚函数在 vtable 中的索引下标是固定的不会随着继承层次的增加而改变派生类新增的虚函数放在 vtable 的最后。如果派生类有同名的虚函数遮蔽覆盖了基类的虚函数那么将使用派生类的虚函数替换基类的虚函数这样具有遮蔽关系的虚函数在 vtable 中只会出现一次。
重载重写重定义
重载同一个作用域下函数名相同参数列表不同在编译时确定-静态的多态重写覆盖两个函数分别在基类与派生类的作用域中函数必须完全一样函数都得是虚函数重定义隐藏函数名相同在同一个类中进行不涉及继承关系
动态的多态
通过基类的指针或者引用调用派生类的虚函数在运行时确定通过指向的对象的类不同而查找的虚函数表不同这样实现的动态的多态通过这个也是实现的一种接口继承。
虚表指针是在什么阶段完成初始化的呢虚表又是在什么阶段生成的
虚表在编译阶段完成虚表指针在构造函数的列表初始化时完成
虚表指针是存在于对象中的所以在对象还没生成时是不会有虚表指针的而对象完成构造后虚表指针又是正常出现了所以虚表指针是在对象在构造函数中的初始化列表中完成初始化的。
而虚表是要早于虚表指针的不然虚表指针就拿不到虚函数指针数组的首地址。而在一个程序的编译阶段会处理程序中的函数对函数进行分析所以虚表是在编译阶段生成的。当对象构造时直接将地址赋给指针即可
网络
TCP三次握手
TCP报文中有SYN表示发起一个连接FIN表示结束一个连接ACK表示对这个报文的确认有效
客户端先向服务端发起建立连接的请求报文中是不携带任何数据报文中的SYN1,表示建立连接同时生成一个随机的32位的序号这是表示后面的数据报文的序号都从则会个初始序号开始 表明客户端可以向服务端发送信息。服务端收到建立连接的报文段后也会向客户端发送一个SYN1同时随机初始化序号的报文段报文段中还要有ACK1表示确认收到客户端的请求报文段中的确认号要加1回复表示这个确认号之前的报文段都受到了期待收到这个确认号的报文段这是表示服务端可以接收与回复客户端的响应客户端收到服务端的报文后向服务端再次发送一个信息表示客户端可以收到服务端的消息。这个报文段可以携带一些数据报文其中消息确认段ACK1确认号根据上次服务端发送的序号加1
这三次握手之后可以确保TCP全双工的协议正常双方可以互相收发。
TCP四次挥手-断开连接
客户端断开连接时FIN1,表示断开连接其它的如报文序号是需要正常推进这样是在基于可靠传输协议上进行的。
服务端收到了FIN1的报文段后需要应答 ACK1确认收到了客户端的断开连接的请求。
服务端发送一个FIN1的报文段表示服务端做好了断开连接的准备然后客户端发送一个ACK1的确认报文段
表示断开连接
can通信的原理
串行通信协议具有可靠灵活实时的特点 以广播的形式发送报文当CAN总线上的某个节点需要给其他节点发送消息时会以广播的形式发送给总线上所有的节点因为总线上的节点不适用地址来进行配置CAN系统而是根据报文的开头的11位标识符决定是否要接受其他节点发来的报文面向内容的编制方案 每个节点都有自己的处理器和CAN总线接口控制器 当一个节点需要发送数据到另一个节点时自身节点的处理器需要将要发送的数据和自己的标识符传给自身的总线控制接口处于准备状态当获取到总线的使用权后将数据和标识符组装成报文将报文以一定格式发出此时其他的节点处于接收状态至于其他节点是否接收由其他节点决定是都会对某些报文进行过滤 当新增的节点仅仅是纯粹的数据接收设备时只需要该设备直接从总线上接收数据即可
多线程
互斥锁和信号量条件变量的区别
功能不同
互斥锁是一种用于保护共享资源的锁机制它确保在同一时刻只有一个线程可以访问被保护的资源。信号量是一种计数器用于控制对资源的并发访问。它可以实现进程间的同步和互斥。条件变量用于线程之间的等待和通知用于等待某个特定条件的发生。
使用方式不同
互斥锁提供了两个基本操作上锁Lock和解锁Unlock。线程在访问共享资源之前需要先获取互斥锁如果锁已经被其他线程占用那么获取操作将被阻塞。一旦线程完成对共享资源的访问就释放互斥锁以便其他线程可以获取锁并访问资源。信号量包含一个计数器和两个原子操作Pwait和Vsignal。P 操作会将计数器减一如果计数器小于零则线程被阻塞。V 操作会将计数器加一并唤醒等待的线程。线程在访问共享资源之前必须执行 P 操作而在访问完成后执行 V 操作。条件变量通常结合互斥锁一起使用。线程可以在条件变量上等待某个条件的发生并在满足条件时被唤醒。条件变量提供了 wait阻塞等待条件、signal唤醒一个等待线程和 broadcast唤醒所有等待线程等操作。
适用场景不同
互斥锁主要用于保护共享资源的互斥访问防止多个线程同时修改共享数据造成数据不一致或冲突的问题。信号量可用于控制对有限数量资源的并发访问例如限制同时访问某个文件的线程数量。条件变量通常与互斥锁一起使用用于线程间的等待和通知机制例如一个线程等待另一个线程完成某项任务后才能继续执行。
总结来说互斥锁用于提供对共享资源的互斥访问信号量用于控制资源的并发访问而条件变量则用于线程之间的等待和通知。它们各自有不同的功能和应用场景可以根据具体需求选择使用。
进程间的通信
管道Pipe管道是一种半双工的通信方式可以实现两个进程之间的通信。管道分为匿名管道和命名管道两种匿名管道只适用于有亲缘关系的进程而命名管道则可以跨越不同主机和无亲缘关系的进程进行通信。信号Signal信号是一种异步的通信方式主要用于处理进程间的同步和协作。某个进程可以向另一个进程发送信号让其响应相应的操作。共享内存Shared Memory共享内存是一种可以被多个进程同时访问的内存区域在该内存区域中的数据可以被多个进程读写从而实现了进程之间的通信和数据共享。由于多个进程可以同时访问共享内存因此必须采用同步机制如互斥锁、信号量等来保证共享内存的正确性。消息队列Message Queue消息队列是一种有一端写入消息、另一端读取消息的通信方式。消息队列不同于管道和共享内存它们都只能进行一对一的通信而消息队列可以实现一对多的通信。套接字Socket套接字是用于不同主机之间通信的一种进程间通信方式在网络编程中应用广泛。套接字提供了一种标准的接口使得进程可以通过网络发送和接收数据。
线程间通信
共享内存多个线程可以访问同一块内存区域进行数据交换。需要注意的是由于共享内存可以被多个线程同时访问因此需要使用同步机制如互斥锁、条件变量等来保证线程的正确同步和互斥。消息队列一个线程将消息放入队列另外一个线程从队列中取出消息进行处理。消息队列可以提供异步通信和缓存功能不过也需要使用同步机制来保证多个线程之间的数据安全。线程信号量一种特殊的计数器用于控制多个线程对共享资源的访问。当某个线程获取到信号量时可以继续执行特定操作而当某个线程释放信号量时其它线程可以获取到信号量并继续执行。线程信号量通常用于限制同时访问某个共享资源的线程数量。管道Pipe管道是进程间通信的方式但也可以用于线程间通信。管道提供了一个通信的通道一个线程将数据写入管道另一个线程从管道中读取数据进行处理。需要注意的是由于管道是单向的因此通常需要使用两个管道来实现双向通信。信号Signal一种基于异步事件的通信方式通常用于进程间通信但也可以用于线程间通信。当某个线程执行特定事件如段错误、中断等时可以向其它线程发送信号以触发相应的操作。
排序
冒泡排序Bubble Sort重复地比较相邻的元素将较大或较小的元素逐渐交换到右侧直到整个数组有序。时间复杂度为O(n^2)。插入排序Insertion Sort将待排序的元素依次插入已经排好序的序列中的合适位置直到整个数组有序。时间复杂度为O(n^2)但在部分有序的情况下有较好的性能。选择排序Selection Sort每次从无序区选择最小或最大的元素放到有序区的末尾直到整个数组有序。时间复杂度为O(n^2)。快速排序Quick Sort通过一趟划分将待排序序列分成两部分左边部分都小于等于枢轴元素右边部分都大于枢轴元素。然后对两部分分别进行快速排序直到整个数组有序。平均时间复杂度为O(nlogn)但在最坏情况下可能达到O(n^2)。归并排序Merge Sort将待排序序列分成两个子序列分别对子序列进行归并排序然后将两个有序的子序列合并成一个有序序列直到整个数组有序。时间复杂度为O(nlogn)但需要使用额外的存储空间。堆排序Heap Sort利用堆这种数据结构进行排序构建最大堆或最小堆然后每次取出堆顶元素并调整堆直到整个数组有序。时间复杂度为O(nlogn)且不需要额外的存储空间。希尔排序Shell Sort将待排序序列按照一定的步长进行分组对每组进行插入排序然后逐渐缩小步长直到步长为1最后进行一次完整的插入排序。时间复杂度取决于步长序列的选择一般在O(nlogn)到O(n^2)之间。
快排的实现逻辑
挖坑法
对原始数组进行三数取中进行交换然后选取首元素作为目标存放在一个临时变量中left指针指向第一个位置然后right指针从数组最后一个元素向前移动直到找到一个比目标元素小的然后放到left指向的位置right不动left指针右移寻找比目标元素大的放到right的位置。直到两个指针相遇把目标元素放在相遇的位置然后将数组从这个位置分成两个子数组开始递归的执行逻辑
交换法
三数取中选择一个目标元素存放在一个临时变量设置左右两指针left指针找到比目标元素大的就停止right指针找到比目标元素小的就停止然后左右指针的元素交换然后继续移动直到相遇把目标元素放在相遇的位置然后以这个位置为分界分词两个子数组递归执行逻辑。