学习日记-24/7/26
对QT控件使用差很多,不全面。
不太会看文档,自己对于新方法无法适应。
内存泄漏的现象,如何检查代码中的内存泄露?
已分配的内存空间在使用完毕后未被释放,导致可用内存减少并最终可能导致系统性能下降或程序崩溃。
方法一:代码审查和静态分析工具(自己查)
方法二:动态分析工具(Valgrind)
方法三:内存泄漏检测库(gcc -fsanitize=address -g your_program.c -o your_program)(在代码中包含#include <mcheck.h>并在main函数开始处调用mtrace())
方法四:手动跟踪:日志记录、智能指针(自动管理内存分配和释放)
-
动态多态(运行时多态)/ 静态多态(编译时多态)
静态:函数重载、模板(编译的时候就绑定)
动态:虚函数实现(运行的时候绑定) -
虚函数 虚表指针 虚表
虚函数:基类中声明的函数,使用virtual关键字。它允许派生类重写该函数,从而在运行时决定调用哪一个版本的函数。通过虚函数可以实现多态性。
虚表指针:为了实现运行时的多态性,编译器会在每个包含虚函数的类中插入一个隐藏的成员指针,称为虚表指针(vptr)。这个指针指向该类的虚表(vtable)。
虚表:是一个函数指针表,它存储每个虚函数的地址。每个包含虚函数的类都有一个虚表。顺序与它们在类中的声明顺序一致。
工作原理:
当一个包含虚函数的对象创建时,vptr被初始化为指向该对象所属类的虚表。
在调用虚函数时,通过vptr查找虚表中相应的函数指针,并调用实际的函数实现。
- 每个对象实例包含一个指向该虚函数表的指针
- 抽象类是包含至少一个纯虚函数的类
-
抽象类
定义方法/接口,提供默认行为,促进代码复用。
不能实例化!! -
回调函数
回调函数是指通过将函数指针作为参数传递给另一个函数,从而在特定事件或任务完成时调用该函数。
在C++中,回调函数可以通过函数指针、函数对象(仿函数)、以及lambda表达式来实现。
lambda表达式是一种简洁的方式来定义匿名函数。
什么叫协议:指定一个能够交流的标准。
协议(Protocol)在计算机科学和电信领域中,是指一组规则或标准,这些规则或标准定义了计算机之间如何进行通信或数据交换。协议规定了数据格式、传输方式、错误检测和纠正方法等内容,以确保在不同设备或系统之间进行可靠和高效的通信。
什么叫tcp:
TCP,全称是传输控制协议(Transmission Control Protocol),是Internet协议族的一部分,用于管理数据在网络中的传输。它是一种面向连接的协议,提供可靠、顺序的字节流传输。TCP在网络通信中扮演着重要的角色,确保数据能够从一个网络设备传输到另一个网络设备,且数据不会丢失、重复或失序。
守护进程详细内容,怎么调试
std::move
C++11 委托构造函数,继承构造函数
默认构造函数
初始化构造函数(有参数)
拷贝构造函数
移动构造函数(move和右值引用) :接受一个右值引用参数,通过“移动”资源来初始化新对象,而不是复制。
委托构造函数 :一个构造函数调用另一个构造函数来简化代码和减少重复。
转换构造函数 :可以接受单个参数并实现从该参数类型向类类型转换的构造函数。
copy
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
class MyClass {
public:
int x;
MyClass(int val) : x(val) {
// 转换构造函数
std::cout << "Conversion constructor called with value: " << x << std::endl;
}
};
int main() {
MyClass obj = 10; // 调用转换构造函数
return 0;
}
可变参数模板(variadic templates)是C++11引入的一种强大特性,允许定义能够接受可变数量参数的模板。
列表初始化 {} 👈就是用这个直接初始化
直接列表初始化:使用构造函数直接进行初始化。
拷贝列表初始化:用于初始化非类类型的对象,语法上更类似于传统的拷贝初始化。
作用域枚举(不会发生隐式转换)
在C++11及以后,引入了作用域枚举(scoped enumeration),也称为强类型枚举(strongly-typed enumeration),用于避免传统枚举(unscoped enumeration)的一些问题,特别是隐式转换的问题。
(可以显示转换)比如通过 static_cast
(c); 强类型:作用域枚举不会隐式转换为整数或其他类型。
命名空间隔离:枚举成员名在枚举类型的作用域内,避免了命名冲突。
显式指定基础类型:可以显式指定枚举的基础类型。
右值引用
左值是指可以被取地址的对象,表示某个内存位置。左值有持久的存储,通常代表变量。
右值是指不能被取地址的临时对象或常量,表示某个值本身而不是内存位置。
右值引用是对右值的引用,使用
&&
符号表示。右值引用只能绑定到右值。
尾置返回类型
auto functionName(parameters) -> returnType {
//函数体
}
copy
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
// C++11中引入
// 使用尾置返回类型
auto add(int a, int b) -> int
// 模板函数,返回参数类型的乘积
template <typename T1, typename T2>
auto multiply(T1 a, T2 b) -> decltype(a * b)
// 使用尾置返回类型和 decltype 推导返回类型
template <typename Container>
auto get_first_element(Container& container) -> decltype(container[0])
final 和 override
override
当在父类中使用了虚函数时候,你可能需要在某个子类中对这个虚函数进行重写,以下方法都可以:
xxx
如果不使用override,当你手一抖,将foo()写成了f00()会怎么样呢?结果是编译器并不会报错,因为它并不知道你的目的是重写虚函数,而是把它当成了新的函数。如果这个虚函数很重要的话,那就会对整个程序不利。所以,override的作用就出来了,它指定了子类的这个虚函数是重写的父类的,如果你名字不小心打错了的话,编译器是不会编译通过的。
final
当不希望某个类被继承,或不希望某个虚函数被重写,可以在类名和虚函数后添加final关键字,添加
final关键字后被继承或重写,编译器会报错。
进程和线程的概念
进程 是操作系统进行资源分配和调度的基本单位,是程序的一次执行过程。它拥有独立的内存地址空间和系统资源。
线程 是进程中的一个执行流,是 CPU 调度和分派的基本单位,它可以与同属一个进程的其他线程共享进程所拥有的全部资源。线程通常被用于执行代码,它们比进程更轻量级,创建和管理的开销小。
为什么要使用线程?
- 效率提升:多线程可以并行处理多个任务,提升程序的执行效率。
- 资源共享:线程间可以共享大部分进程资源,相较于进程间通信,线程间通信更简单、快速。
- 响应速度提升:在多线程应用中,一个线程的长时间执行不会阻塞其他线程,提高应用的响应速度。
多线程环境中的死锁
死锁 是指多个线程在运行过程中因为竞争资源而造成的一种阻塞的现象,若无外力作用,这些线程都将无法向前推进。发生死锁主要有四个必要条件:
- 互斥条件:资源不能被多个线程共享。
- 保持和等待:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
- 非抢占条件:线程已获得的资源在未使用完之前,不能被其他线程强行抢占。
- 循环等待条件:存在一种线程资源的循环等待链,形成闭环。
线程安全
线程安全 指的是多线程环境中,程序或者代码在并发的情况下,能够保证操作的正确性,不会因为多线程的调度和资源竞争导致数据出错。
单例模式
单例模式 是一种设计模式,确保一个类只有一个实例,并提供一个全局访问点。单例模式常用于管理共享资源,如数据库连接或文件系统的操作。
实现单例模式的常见方法包括:
- 懒汉模式:在第一次调用时初始化实例。
- 饿汉模式:在程序启动时创建实例。
在多线程环境下实现单例需要注意线程安全问题,常见的线程安全实现方法是使用同步机制,例如在获取实例的方法上添加同步锁。
进程的通信方式
进程间通信(IPC)是在不同进程之间交换数据和信号的机制。主要的进程通信方式包括:
- 管道(Pipe):允许一个进程与另一个有亲缘关系的进程进行通信。
- 命名管道(FIFO):与普通管道类似,但它们可以在无关的进程之间进行通信。
- 信号(Signal):用于处理异步事件。
- 消息队列:消息的链表,存储在内核中,并由消息队列标识符标识。
- 共享内存:允许多个进程访问同一块内存空间。
- 套接字(Socket):支持不同主机上的进程之间的通信。
OSI七层模型
OSI(Open Systems Interconnection)模型 是一个参考模型,用于理解和设计网络的工作流程,分为七层:
- 物理层:处理物理设备间的原始数据传输。
- 数据链路层:处理点对点的帧传输。
- 网络层:处理数据包从源到目的地的传输。
- 传输层:提供端到端的通信服务。
- 会话层:管理网络中的会话。
- 表示层:确保数据在网络中流动的方式可以被发送方和接收方理解。
- 应用层:为应用软件提供服务。
TCP三次握手和四次挥手
TCP三次握手(建立连接):
- 客户端发送一个带SYN标志的数据包到服务器。
- 服务器回应一个带SYN/ACK标志的数据包以确认接收。
- 客户端发送ACK包回服务器,确认连接建立。
TCP四次挥手(断开连接):
- 客户端或服务器发起关闭,发送FIN包。
- 接收方确认这个FIN包,回送一个ACK包。
- 接收方发送一个FIN包,表示同意关闭连接。
- 发起关闭的一方确认FIN包,回送ACK包,完成断开。
四次挥手过程详细说明:
1、客户端发送断开TCP连接请求的报文,其中报文中包含seq序列号,是由发送端随机生成的,并且还将报文中的FIN字段置为1,表示需要断开TCP连接。(FIN=1,seq=x,x由客户端随机生成);
2、服务端会回复客户端发送的TCP断开请求报文,其包含seq序列号,是由回复端随机生成的,而且会产生ACK字段,ACK字段数值是在客户端发过来的seq序列号基础上加1进行回复,以便客户端收到信息时,知晓自己的TCP断开请求已经得到验证。(FIN=1,ACK=x+1,seq=y,y由服务端随机生成);
3、服务端在回复完客户端的TCP断开请求后,不会马上进行TCP连接的断开,服务端会先确保断开前,所有传输到A的数据是否已经传输完毕,一旦确认传输数据完毕,就会将回复报文的FIN字段置1,并且产生随机seq序列号。(FIN=1,ACK=x+1,seq=z,z由服务端随机生成);
4、客户端收到服务端的TCP断开请求后,会回复服务端的断开请求,包含随机生成的seq字段和ACK字段,ACK字段会在服务端的TCP断开请求的seq基础上加1,从而完成服务端请求的验证回复。(FIN=1,ACK=z+1,seq=h,h为客户端随机生成)
至此TCP断开的4次挥手过程完毕。
右值引用
右值引用是C++11中引入的一个特性,它允许开发者引用临时对象。右值引用使用 &&
符号标记,与传统的左值引用(使用 &
)不同。它主要用于实现移动语义和完美转发。通过移动语义,可以将资源(如动态分配的内存)从一个对象转移到另一个对象,这样可以避免不必要的拷贝,提高性能。
什么情况下会应用右值引用
右值引用是C++11引入的一项功能,它主要用于两个场景:提升性能和实现移动语义以及完美转发。以下是使用右值引用的一些具体情景:
1. 移动语义
右值引用允许开发者区分哪些对象是可以安全地“移动”的。这种特性尤其有用于大对象或资源管理密集型的对象(如大型数组、字符串、文件句柄、网络连接等)。使用移动语义可以避免昂贵的深拷贝操作,而是通过转移所有权(如指针和句柄)从一个对象到另一个对象来显著提高性能。
copy
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
std::vector<int> createHugeVector() {
std::vector<int> v(1000000, 42); // 创建一个大型vector
return v; // 返回vector,触发移动构造,而非拷贝构造
}
void useVector() {
std::vector<int> myVec = createHugeVector(); // 使用移动构造
}
在上面的例子中,当从函数 createHugeVector
返回 vector
时,使用移动构造函数而不是拷贝构造函数,因为返回的是一个临时对象(右值)。
2. 完美转发
右值引用结合模板和 std::forward
函数可以用于实现完美转发,这允许函数模板精确地保持其接收到的实参的类型(包括其值类别,即左值或右值)。
copy
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
template<typename T>
void forwarder(T&& arg) {
someFunction(std::forward<T>(arg)); // 转发arg到someFunction
}
void someFunction(int& x) {
std::cout << "Lvalue function\n";
}
void someFunction(int&& x) {
std::cout << "Rvalue function\n";
}
int main() {
int x = 10;
forwarder(x); // 调用的是someFunction(int&)
forwarder(5); // 调用的是someFunction(int&&)
}
这里,forwarder
函数模板可以完美地转发它的参数到 someFunction
,根据实参的原始类型(左值还是右值)选择合适的函数版本。
3. 资源所有权的管理
右值引用常用于智能指针,如 std::unique_ptr
,它需要在对象间转移所有权时使用移动语义。
copy
- 1
- 2
std::unique_ptr<int> source = std::make_unique<int>(10);
std::unique_ptr<int> destination = std::move(source); // 明确转移所有权
在此例中,使用 std::move
将 source
中的所有权转移到 destination
。这是必须的,因为 std::unique_ptr
不允许拷贝。
右值引用和相关技术提供了强大的工具来优化程序性能,特别是在处理大型数据或资源密集型对象时。这些技术也使得C++程序更加高效和灵活。
深拷贝与浅拷贝
- 浅拷贝:仅复制对象的指针,不复制指针所指向的数据。两个对象的指针指向同一块内存区域,修改一个对象的数据可能会影响到另一个对象。
- 深拷贝:复制对象及其所有的数据。通常需要显式地在拷贝构造函数和赋值运算符中实现。深拷贝确保复制的对象拥有一份独立的数据副本。
构造的列表初始化和括号内初始化
在C++11及以后的版本中,初始化的方式有所扩展,包括列表初始化和传统的构造函数初始化:
- 列表初始化(使用花括号
{}
):提供了一种统一的初始化语法,用于初始化任何类型的对象,包括数组、容器和基本数据类型。列表初始化还防止了数据丢失,因为如果初始化时发生了窄化转换,编译器将报错。 - 括号内初始化(使用圆括号
()
):这是传统C++的初始化方式,适用于大多数类型,但在某些情况下可能会与函数声明产生歧义。
内联函数
内联函数是一种优化技术,用于减小函数调用的开销。通过在函数声明前加上 inline
关键字,编译器被建议在每个调用点上将函数体直接展开,而不是进行函数调用。这通常用于小规模的、频繁调用的函数。
模板
模板是C++中支持泛型编程的一种机制,它允许程序员编写与类型无关的代码。模板可以用于创建泛型函数或数据结构。有两种基本形式的模板:
- 函数模板:用于创建可接受任何数据类型的函数。
- 类模板:用于创建可以存储任何数据类型的类。
本文作者:iuk11
本文链接:https://www.cnblogs.com/iuk11/p/18325646
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报