C++ 知识补充 参考链接:7.18、面试回顾C++服务端春招面试问题总汇_c++游戏服务端开发面试题-CSDN博客C++经典面试题(最全,面中率最高)-CSDN博客C++常见面试题_c++ 面试题-CSDN博客1、c++特性1) 封装、继承、多态a)封装是为了代码模块化和增加安全性保护数据成员,不让类以外的程序直接访问或者修改类的成员,只能通过其成员对应方法访问(即数据封装)隐藏方法实现的具体细节,仅仅提供接口,内容修改不影响外部调用(即方法封装)private:只能被访问:1.该类中的函数、2.其友元函数。不能被任何其他访问,该类的对象也不能访问。protected:可被访问:1.该类中的函数、2.子类的函数、3.其友元函数。但不能被该类的对象访问。public:可被访问:1.该类中的函数、2.子类的函数、3.其友元函数、4.该类的对象。b)继承的目的: 重用代码一个类B继承另一个类A,则B就继承了A中申明的成员以及函数派生的目的: 代码扩展,继承自一个类然后添加自己的属性和方法则实现代码的扩展c)多态: 接口的复用,一个接口多种实现用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数c++多态及其多态的原理多态包括静态多态(编译时多态)和动态多态(运行时多态)。静态多态:编译时多态,是早绑定;(编译时根据形参类型或运算符进行选择)。包括函数重载、运算符重载、重定义、模板。动态多态:运行时多态,是晚绑定;(在程序运行时根据对象的实际类型进行选择)通过继承和虚函数实现。2) c++class和C语言struct的区别a) C++ 中保留了C语言的 struct 关键字,并且加以扩充。b) 在C语言中,struct 只能包含成员变量,不能包含成员函数。而在C++中,struct 类似于 class,既可以包含成员变量,又可以包含成员函数。C++中的 struct 和 class 基本是通用的,唯有几个细节不同a) 使用class时,类中的成员默认都是private属性的;而使用struct时,结构体中的成员默认都是public属性的。b)class 继承默认是private 继承,而struct 继承默认是public 继承。c) class 可以用在模板参数中,而struct 不能。3) 析构函数、虚析构函数、纯虚析构函数对象在结束其生命周期之前,都会调用析构函数以完成必要的清理工作;派生类调用的析构函数顺序是“先子类,后基类”;普通析构函数:1、若delete运算符删除的是子类指针对象,则会调用子类和基类的析构函数;2、若析构函数是非虚的,即使基类指针指向的是子类对象,则delete 指针对象时,也仅调用基类的析构函数虚析构函数:(目的:通过父类指针释放整个子类空间。)1、虚析构函数用于在析构对象时调用其派生类的析构函数,确保释放所有内存空间。2、虚析构函数在基类中有默认的实现,可以在派生类中重新实现。也就是虚析构使用virtual修饰,有函数体,不会导致父类变成抽象类。3、当基类的析构函数为虚函数时,基类指针指向的是子类对象时,使用delete运算符删除指针对象,析构函能够按照“先子类,后基类”的原则完成对象清理;这样在多重继承的类中,能够保证每个类都能够得到正确的清理;比如基类和子类的缓冲区都能被释放;纯虚析构函数:纯虚析构的本质是析构函数,复杂各个类的回收工作。而且析构函数不能被继承。注意:必须为纯虚析构函数提供一个函数体。纯虚析构函数必须类外实现。也就是:尽管纯虚析构函数在基类中没有具体的实现(即没有函数体),但它必须在类的外部提供定义(一个空的函数体)虚析构函数原理剖析:构造的顺序:父类--成员--子类。析构的顺序:子类--成员--父类。class Animal { public: Animal() { cout "Animal构造函数" endl; } // 纯虚函数 virtual void speak() = 0; virtual ~Animal() { cout "Animal析构函数" endl; } }; class Dog :public Animal { public: Dog() { cout "Dog构造函数" endl; } // 子类一定会重写父类的虚函数 void speak() { cout "狗在汪汪" endl; } ~Dog() { cout "Dog析构函数" endl; } }; int main() { Animal *p = new Dog; p-speak(); delete p; return 0; }输出:Animal构造函数 Dog构造函数 狗在汪汪 Dog析构函数 Animal析构函数4)#include “filename.h”和#include filename.h的区别答:对于#include filename.h编译器从标准库开始搜索filename.h对于#include “filename.h”编译器从用户工作路径开始搜索filename.h5)friend友元函数(能访问类的私有成员和保护成员)9.9 也是讲述友元函数友元函数不是类的成员函数,但可以被声明为某个类的友元,从而能够访问该类的私有成员(private)和保护成员(protected)。声明通常放在类的内部,但定义(即函数体)可以在类的外部进行。友元函数不一定是全局函数。其可以是全局函数、其他类的成员函数,甚至是静态函数。class Box { double width; // 私有成员 public: Box(double w) : width(w) {} // 声明友元函数 friend void printWidth(const Box box); // 其他成员函数... }; // 定义友元函数 void printWidth(const Box box) { cout "Width of box : " box.width endl; } int main() { Box box(10.0); // 调用友元函数 printWidth(box); return 0; }友元关系不是相互的:如果类A声明类B的一个成员函数为友元,那么只有那个特定的成员函数是类A的友元,而不是类B的所有成员函数。友元函数不是类的成员函数:它不能通过类的实例来调用,也不能访问类的this指针(即没有this指针)。友元可以定义在类外部:如上例所示,友元函数的定义可以放在类的外部。友元函数可以访问类的所有成员:包括私有成员和保护成员。友元函数破坏了封装性:虽然友元函数在某些情况下很有用,但它允许外部代码访问类的内部状态,这可能会破坏类的封装性。因此,应该谨慎使用友元函数。友元函数可以是另一个类的成员函数:在这种情况下,整个类(而不是单个成员函数)成为友元。友元函数可以重载:就像其他函数一样,友元函数也可以被重载。6)虚函数与纯虚函数,即动态多态(运行时多态)C++ 多态深度解析:理解虚函数与纯虚函数 (qq.com)C++中的虚函数和纯虚函数都是为了实现多态性而存在的:虚函数:在基类中声明的函数,可以在派生类中被覆盖(重写)。虚函数在基类中有一个默认的实现,但在派生类中可以重新实现,实现与基类虚函数的方法签名相同。派生类中定义虚函数时,必须使用关键字“virtual”来声明。也就是虚函数使用virtual修饰,有函数体,不会导致父类变为抽象类。纯虚函数:在基类中声明的函数,在基类中没有实现,必须在派生类中实现。纯虚函数使用“=0”来声明,例如“virtual void foo() = 0;”。纯虚函数没有默认实现,所以派生类必须实现它们。也就是纯虚函数有virtual修饰,=0,没有函数体,导致父类为抽象类,子类必须重写父类的所有纯虚函数。区别:虚函数可以有实现,纯虚函数没有实现。虚函数可以在基类中有默认的实现,纯虚函数没有默认实现。虚函数不一定要在派生类中实现,但纯虚函数必须在派生类中实现。如果一个类中有纯虚函数,那么它就是抽象类。不能创建抽象类的对象,只能创建它的派生类对象。虚函数是可选的,它有一个默认的实现,但可以在派生类中重新实现;纯虚函数是必须实现的,它没有默认的实现,只有声明,必须在派生类中实现。虚函数用于实现多态性,而纯虚函数用于定义接口虚函数虚函数的原理:虚函数的原理 基于一个叫做虚函数表(也叫虚表)的特殊数据结构。每个含有虚函数的类 都有一个对应的虚表;每个对象实例 都包含一个 指向其类的虚表 的指针(vptr)。当调用一个对象的虚函数时,程序会通过这个指针查找虚函数表,从而找到正确的函数实现来执行,这就支持了运行时多态,即允许在运行时根据对象的实际类型来调用适当的函数版本。虚表:是类的所有对象共有的。每个类只有一个虚表,该表在编译时被创建,且所有该类的对象都包含一个指向这个共享虚表的指针(vptr)。当类的对象被创建时,每个对象的 vptr 会被初始化指向它所属类的虚表。虽然虚表是共享的,但每个对象都有自己的 vptr,确保了即使是同一个类的不同对象,如果它们属于不同的派生类,也能通过各自的 vptr 找到正确的虚函数实现。vtable:一般每个类一份 vprt:一般每个对象一份虚函数是在基类中用virtual关键字声明的成员函数。当通过基类指针或引用调用虚函数时,会根据对象的实际类型(即对象的动态类型)来调用相应的函数版本,这称为动态绑定或晚期绑定。虚函数允许子类覆盖基类的实现,从而实现多态性。虚函数必须有函数体,即使它只是一个空的函数体(即只包含{})。重写时:函数签名必须完全一致:包括返回类型、参数列表和const修饰符(如果有)。使用override关键字(推荐):显式表明意图,编译器会检查是否真正重写了父类的虚函数。若签名不匹配(如拼写错误),编译器会报错,避免意外隐藏父类函数。class Animal { public: virtual void speak() { cout "动物在说话" endl; } }; class Dog :public Animal { public: void speak() // 重写父类的虚函数 { cout "狗在汪汪" endl; } }; class Cat :public Animal { public: void speak() override // 用override关键字显式标记 重写父类的虚函数 { cout "猫在喵喵" endl; } }; //设计一个算法 void showAnimal(Animal *p) { p-speak(); delete p; } int main() { showAnimal(new Dog); showAnimal(new Cat); return 0; }输出:狗在汪汪 猫在喵喵纯虚函数纯虚函数是在基类中用virtual和= 0关键字声明的成员函数。纯虚函数没有函数体,只有函数声明。它要求任何继承该基类的子类都必须提供该函数的实现。含有纯虚函数的类被称为抽象类,抽象类不能被实例化。纯虚函数的主要目的是强制派生类实现特定的接口,从而允许在基类中定义接口而在派生类中实现这些接口class 类名{ public: // 纯虚函数 virtual 函数返回值类型 函数名(参数列表)=0; }; //例如 class Animal { public: virtual void speak()=0; };一旦类中有纯虚函数,那么这个类就是抽象类。抽象类不能实例化对象。抽象类必须被继承,同时子类必须重写父类的所有纯虚函数,否则子类也是抽象类。抽象类主要目的是设计类的接口。应用实例:class Animal { public: // 纯虚函数 virtual void speak() = 0; }; class Dog :public Animal { public: // 子类一定会重写父类的虚函数 void speak() { cout "狗在汪汪" endl; } }; class Cat :public Animal { public: // 子类一定会重写父类的虚函数 void speak() { cout "猫在喵喵" endl; } }; //设计一个算法 void showAnimal(Animal *p) { p-speak(); delete p; } int main() { showAnimal(new Dog); showAnimal(new Cat); return 0; }1.1 类的构造函数可以是虚函数吗? 为什么说析构函数必须声明为虚函数构造函数肯定不能是虚函数:构造函数是用来初始化对象的,在对象还没有完全构造完成之前,虚函数指针和虚表可能还没有被正确初始化。如果构造函数是虚函数,就会导致在对象未完全构造之前尝试调用虚函数,可能引发未定义行为。因此,构造函数不能是虚函数。是为了确保在删除对象时能够正确调用派生类的析构函数,避免资源泄漏:如果析构函数不被声明为虚函数,在下面场景下会出现内存泄露,比如:Base* ptr = new Derived(); delete ptr; // 会调用 ase 的析构函数由于子类对象是父类的指针,但是析构函数不是虚函数,就不构成多态,所以这调用delete释放资源的时候,会去调用父类的析构导致子类的资源没有被释放。1.2多态的理解多态是面向对象编程的重要特性之一,简单来说,它允许同一个函数或方法在不同的上下文中表现出不同的行为(概念)多态又分为静态多态和动态多态。(分类)静态多态:通过 函数重载,运算符重载和泛型编程实现,是在编译期实现的。动态多态:通过继承+虚函数+基类指针引用实现,是在运行期实现的。底层通常通过:vprt 虚函数表指针 + vtable 虚函数表 实现。多态他是通过父类的指针或者引用,指向子类的对象,每个包含虚函数的类实例中都会有一个虚函数指针,虚函数指针指向虚函数表,虚函数表中就是我们要调用的虚函数的的地址,从而实现多态。(原理)2、静态链接库和动态链接库静态库代码装载的速度快,执行速度略比动态库快;动态库更加节省内存,可执行文件体积比静态库小很多;静态库是在编译时加载,动态库是在运行时加载;a) 静态库的扩展名一般为“.a”或“.lib”;动态库的扩展名一般为“.so”或“.dll”。b) 静态库在编译时会直接整合到目标程序中,编译成功的可执行文件可独立运行;动态库在编译时不会放到连接的目标程序中,即可执行文件无法单独运行。c) 静态库的缺点:利用静态库编译成的文件比较大;升级难度没有明显优势,如果函数需要更新,需要重新编译;d) 动态函数库在编译的时候,在程序里只有一个“指向”的位置而已,也就是说当可执行文件需要使用到函数库的机制时,程序才会去读取函数库来使用;从产品功能升级角度方便升级,只要替换对应动态库即可,不必重新编译整个可执行文件。e) 综上,不能看出:从产品化的角度,发布的算法库或功能库尽量使动态库,这样方便更新和升级,不必重新编译整个可执行文件,只需新版本动态库替换掉旧动态库即可。1) 动态链接和静态链接扩展:静态链接是由链接器在链接时将库的内容加入到可执行程序中的做法。链接器是一个独立程序,将一个或多个库或目标文件(先前由编译器或汇编器生成)链接到一块生成可执行程序。静态链接是指把要调用的函数或者过程链接到可执行文件中,成为可执行文件的一部分。动态链接所调用的函数代码并没有被拷贝到应用程序的可执行文件中去,而是仅仅在其中加入了所调用函数的描述信息(往往是一些重定位信息)。仅当应用程序被装入内存开始运行时,在Windows的管理下,才在应用程序与相应的DLL之间建立链接关系。当要执行所调用DLL中的函数时,根据链接产生的重定位信息,Windows才转去执行DLL中相应的函数代码。文件类型作用使用顺序典型扩展名头文件声明接口,管理依赖优先被包含.h,.hpp源文件实现逻辑,编译为目标文件依赖头文件,生成.o/.obj.c,.cpp静态库编译时链接,嵌入到可执行文件链接阶段使用.lib,.a动态库运行时加载,共享代码链接阶段使用,运行时加载.dll,.so2)QT中引入SDK开发包的原理讲解原理简介:头文件(.h)、源文件(.c)、库文件(.lib .dll)1.头文件(,h):声明函数接口,一些函数方法名2.源文件(.c):对头文件中函数的实现源代码3. .lib库文件有两种(1)静态链接库(静态库):把程序中调用的某函数的相关模块链接在一起,然后放入内存进行执行。(2)动态链接库(导入库):把调用的函数所在文件模块(DLL)和调用函数在文件中的位置等信息链接进目标程序,程序运行时再从DLL中寻找相应函数代码进行执行。所以.lib库就是导入.dll文件用的。4. .dll库文件:含有函数的可执行代码;与.c文件的源代码的关系是:.c的源代码编译封装之后就形成.dll文件综上所述,这几个文件调用关系大致为:当我们在自己的程序中引用了一个H文件里的函数,编链器怎么知道该调用哪个.dll文件呢?这就是.lib文件的作用: 告诉链接器 调用的函数在哪个.dll模块中,函数执行代码在.dll中的什么位置,这也就是为什么需要 ”附加依赖项“ .lib文件,它起到连接的桥梁作用。.h头文件是编译时必须的,lib库是链接时需要的,dll动态链接库是运行时需要的。参考链接:QT中引入海康威视SDK开发包(原理讲解:头文件(.c)、库文件(.lib .dll))_csdn qt导入头文件与.lib-CSDN博客3)C++的编译过程预处理:处理源代码文件中的预处理指令,如宏定义的展开,去掉注释、条件编译指令和头文件包含。编译:将预处理后的源代码转换成汇编代码,会进行语法分析,语义分析,进行代码优化。汇编:将汇编代码转换成机器码,生成目标文件(.obj 或 .o 文件)。链接:将所有的目标文件和必需的库文件链接在一起形成最终的可执行文件。3、常用的开发设计模式1) 单例模式(Singleton)应用场景:当只需要一个全局实例时。现实例子:操作系统的文件系统。优点:节省了系统资源,避免了不必要的实例化。缺点:可测试性差,扩展性差。单例模式的核心:确保一个类只有一个实例,并提供全局访问点。就像全球只能有一个总指挥官,统一发号施令。即 保证一个类中整个程序中只有能有存在有一个实例,并且提供一个全局的访问点。实现方法:通常将构造函数私有,提供一个静态函数来返回唯一的实例,还会用delete操作符禁止掉拷贝构造和复值重载。即:a)私有化它的构造函数,以防止外界创建单例类的对象;私有构造函数:指挥官不能随便被创建(比如ImageCheck()是私有的),防止外部随意new出多个指挥官。b) 使用类的私有静态指针变量指向类的唯一实例;静态实例:指挥官一旦被任命(实例化),就会一直存在(静态变量),直到程序结束。c) 使用一个公有的静态方法获取该实例。二种形式:懒汉式:在第一次调用的时候才会创建实例,实现延迟加载。可能线程不安全(多线程中要处理同步问题)饿汉式:在程序启动的时候就创建实例。线程安全实现方式线程安全延迟加载代码复杂度推荐度饿汉式✅❌低⭐⭐DCLP✅✅高⭐⭐⭐Meyers' Singleton✅✅最低⭐⭐⭐⭐⭐Atomic DCLP✅✅中⭐⭐⭐⭐CRTP 模板✅✅中⭐⭐⭐⭐最终建议:日常开发中优先使用Meyers' Singleton(局部静态变量方式),它兼顾了线程安全、延迟加载和代码简洁性。class Singleton { private: static Singleton* instance; private: Singleton() {}; ~Singleton() {}; Singleton(const Singleton); Singleton operator=(const Singleton); public: static Singleton* getInstance() { if(instance == NULL) instance = new Singleton(); return instance; } }; // init static member Singleton* Singleton::instance = NULL;通过加锁避免:两个以上线程调用GetInstance(),同时监测到instance为null的情况;实用实例:class CAMERASHARED_EXPORT ImageCheck : public QObject { Q_OBJECT public: ImageCheck(); virtual ~ImageCheck(); static ImageCheck *Instance(); }; ImageCheck* ImageCheck::Instance() { //先检查静态成员变量instance是否为空。若空,说明实例尚未创建,需进入创建流程 if (instance.isNull()) { static QMutex mutex;//静态互斥锁mutex。静态变量只在第一次调用时初始化,后续调用会复用同一个锁,确保所有线程共享同一把锁 QMutexLocker locker(mutex);//在构造时锁定互斥锁,析构时自动解锁。以保证同时只有一个线程能进入临界区(即创建实例的代码块) /* 在加锁后再次检查实例是否为空。这是因为多个线程可能同时通过第一次检查,但只有一个线程能获得锁。 若无二次检查,获得锁的线程创建实例后,其他线程获得锁时可能再次创建,导致重复实例 */ if (instance.isNull()) { //创建 ImageCheck的新实例,并用智能指针管理。 //reset会释放旧实例(如果有),并接管新实例的所有权 instance.reset(new ImageCheck()); } } return instance.data();//返回智能指针管理的原始指针,供外部使用 }—— Meyers' Singleton(局部静态变量方式:C++11 推荐,最优雅)class Singleton { public: static Singleton getInstance() { static Singleton instance; // C++11 保证局部静态变量初始化线程安全 return instance; } void doSomething() { // ... } private: Singleton() {} ~Singleton() {} Singleton(const Singleton) = delete; Singleton operator=(const Singleton) = delete; };—— 完整示例(Meyers' Singleton)// logger.h #pragma once #include string #include fstream #include chrono #include ctime class Logger { public: static Logger getInstance(); void info(const std::string message); void error(const std::string message); private: Logger(); ~Logger(); Logger(const Logger) = delete; Logger operator=(const Logger) = delete; std::ofstream logFile_; std::string getCurrentTime(); }; // logger.cpp #include "logger.h" #include iostream Logger Logger::getInstance() { static Logger instance; return instance; } Logger::Logger() { logFile_.open("app.log", std::ios::app); if (!logFile_.is_open()) { std::cerr "Failed to open log file!" std::endl; } } Logger::~Logger() { if (logFile_.is_open()) { logFile_.close(); } } std::string Logger::getCurrentTime() { auto now = std::chrono::system_clock::now(); auto time_t = std::chrono::system_clock::to_time_t(now); char buffer[26]; ctime_s(buffer, sizeof(buffer), time_t); buffer[24] = '\0'; // 移除换行符 return buffer; } void Logger::info(const std::string message) { if (logFile_.is_open()) { logFile_ "[INFO][" getCurrentTime() "] " message std::endl; } } void Logger::error(const std::string message) { if (logFile_.is_open()) { logFile_ "[ERROR][" getCurrentTime() "] " message std::endl; } }2) 工厂模式(Factory)应用场景:在创建对象时需要根据条件返回不同类型的实例。现实例子:汽车制造工厂。优点:封装了对象的创建过程,降低了耦合性。缺点:当需要添加新的产品类型时,需要修改工厂类。工厂模式:子类对象用相同的父类模块方法,不同的子类分别实现模块方法中的抽象方法,从而实例化不同的子类对象(父类提供抽象方法,继承了的子类自己各自慢慢写怎么叫,怎么飞)工厂模式是一种创建型设计模式,它的主要目的是将对象的创建过程与使用过程分离,通过定义一个工厂类或方法来负责对象的创建,从而减少代码的耦合性,使代码更易于维护和扩展。工厂模式有常见的三种模式:简单工厂模式,工厂方法模式,抽象工厂模式。简单工厂模式:提供一个静态的方法来创建对象,根据传入的参数来决定返回那种类的实例。工厂方法模式:提供一个接口或者一个抽象类,由子类决定实例化哪种具体的类抽象工厂模式:创建一系列相关或者相依赖的对象,通常用于复杂对象。抽象工厂模式(Abstract Factory):应用场景:需要创建一组相关或依赖的对象。现实例子:游戏中的角色创建。优点:隐藏了具体实现,客户端可以通过抽象接口操纵对象。缺点:增加新产品族时不容易,需要修改抽象工厂接口。3) 策略模式和工厂模式区别它们的用途不一样。简单工厂模式是创建型模式,它的作用是创建对象。策略模式是行为型模式,作用是在许多行为中选择一种行为,关注的是行为的多样性。(简单来说,工厂模式就是英语考试的完形填空题(自己考虑填什么词、句子),策略模式就是信息匹配题(提前写好选择项,当你有多个填空时,直接选就好了,没有适合的就再多写几个选择项))4、指针与引用值传递、引用传递、指针传递指针和引用的区别:1指针有自己的一块空间(指针所指向的地址需要存放数据的时候需要申请空间),而引用只是一个别名;(本质)2使用sizeof看一个指针的大小是4(32位下),而引用则是被引用对象的大小; (大小)3指针可以被初始化为NULL,而引用必须被初始化且必须是一个已有对象 的引用;(初始化)4作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引 用的修改都会改变引用所指向的对象;5可以有const指针,但是没有const引用;6指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能被改变;补充:7、指针可以有多级指针(**p),而引用至多一级;8、指针和引用使用++运算符的意义不一样;9、如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄露。引用与指针有什么区别?1) 引用必须被初始化,指针不必。2) 引用初始化以后不能被改变,指针可以改变所指的对象。3) 不存在指向空值的引用,但是存在指向空值的指针。5、内存的分配方式有几种?C++中内存分配方式为前三种。一个C、C++程序编译时内存分为5大存储区:堆区、栈区、全局/静态区、文字常量(字符常量)区、程序代码区。1、静态存储区域分配,内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。速度快、不容易出错,因为有系统会善后。例如全局变量、静态变量2、在栈上创建,在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。3、在堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多,频繁地分配和释放不同大小的堆空间将会产生堆内碎块。4、文字常量区,常量字符串就是放在这里的,程序结束后由系统释放。5、程序代码区,存放函数体的二进制代码。5.1 描述内存分配方式以及它们的区别?1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。3)从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。6、STL容器Vector(后面的类型莫忘)1、什么是vector?向量(vector)是一个封装了动态大小数组的顺序容器(Sequence Container)。跟任意其它类型容器一样,它能够存放各种类型的对象。可以简单的认为,向量是一个能够存放任意类型的动态数组。2、容器特性1.顺序序列顺序容器中的元素按照严格的线性顺序排序。可以通过元素在序列中的位置访问对应的元素。2.动态数组支持对序列中的任意元素进行快速直接访问,甚至可以通过指针算述进行该操作。操供了在序列末尾相对快速地添加/删除元素的操作。3.能够感知内存分配器的(Allocator-aware)容器使用一个内存分配器对象来动态地处理它的存储需求。3、常用操作1、 构造vector v1; //默认构造 无参数 int arr[] = { 20,10,23,90 }; vector v2(arr,arr+sizeof(arr);//使用数组对vector进行初始化 参数:起始地址、长度 vector v3(v2.begin(), v2.end());//使用向量对vector进行初始化 参数:向量起始地址、向量末地址 vector v4(v3); //使用其他向量对vector进行初始化 参数:vectorvectorint dp(w + 1, 0);//初始化dp数组,往里放进w+1个0 vectorvectorint dp(m + 1, vectorint(n + 1, 0));//初始化2、 赋值操作Vector v2; V2.assaign(v1.begin(), v1.end()); //使用向量对vector进行赋值 参数:起始地址、末地址 bright_point.assign(bright_point.begin() + splitIdx, bright_point.end());//截取下标为splitIdx到末尾的部分重新赋值给其本身 vector v3; v3 = v2;//=号操作符重载 int arr1[] = { 200,100,300,400 }; vector v4(arr1, arr1 + sizeof(arr1) / sizeof(int)); v4.swap(v1); //使用swap函数3、 常用函数resize(int n,element) ;将容器的大小设为n,扩容后的每个元素的值都为element v1[i]、v1.at(i)、v1.front()、v1.back():取值操作 v1.insert(v1.begin(), 2, 1000); 在指定位置loc前插入num个值为val的元素map1、什么是map?C++ 中 map 提供的是一种键值对容器,里面的数据都是成对出现的,如下图:每一对中的第一个值称之为关键字(key),每个关键字只能在 map 中出现一次;第二个称之为该关键字的对应值。2、常用操作1、插入操作m.insert(pairint, int(1, 10)); //插入pair m.insert(make_pairint, int(2, 20)); m[4] = 40;2、查找操作mapint,int ::iterator pos = m.find(3); if (pos != m.end()) // true为元素存在,false元素不存在3、删除操作iterator erase(iterator it) ;//通过一个条目对象删除 iterator erase(iterator first,iterator last); //删除一个范围 size_type erase(const Keykey); //通过关键字删除 clear();//就相当于enumMap.erase(enumMap.begin(),enumMap.end());7、多线程7.1、多线程和多进程的区别1)进程是资源分配的最小单位,线程是CPU调度的最小单位2)组成:核函数 =(1个线程网络 + 1个线程块);1个线程网络 =(多个线程块);线程块 =(多个线程)。7.2、Qt程序主界面运行卡断如何解决原因:显示数据和处理数据放在同一个线程中处理做法:将读取处理数据和显示数据两个步骤分离,这属于异步操作(同步:等待数据处理接口的返回值)异步:线程间相互排斥的使用临界资源的现象,就叫互斥。同步:线程之间的关系不是相互排斥临界资源的关系,而是相互依赖的关系。7.3、多线程通线(共享内存和消息传递)1、volatile关键字来实现线程间通信是使用共享内存的思想。程序中运用的方式大致意思是多个线程同时监听一个变量,当这个变量发生变化的时候,线程能偶感知来执行相应的业务。2、QT中常用信号与槽,来实现跨线程通信。跨线程的信号与槽使用自定义类型时,使用qRegisterMetaType对自定义类型进行注册。7.3.1 共享内存1)是一种进程间通信(IPC)机制。2)共享内存就是允许两个或多个进程共享一定的存储区(物理内存区域)。是最快的 IPC 方法之一,因为它避免了数据的复制,直接允许进程访问同一内存地址空间。可以通过操作系统提供的API来实现。在 Linux 系统中,可以使用shmget(),shmat(),shmdt(), 和shmctl()使用共享内存时,通常需要解决同步问题,以确保进程不会同时写入共享内存造成数据损坏。可以使用互斥锁、信号量或其他同步机制来保护共享内存的访问。具体请参考7.4、Qt信号与槽的链接方式1、Q::DirectConnection:槽函数会在信号发送的时候直接被调用(本质:槽函数运行于信号发送者所在的线程),多线程环境下比较危险,可能会造成崩溃。2、Qt::QueuedConnection:槽函数在控制回到接收者所在线程的事件循环时被调用(本质:槽函数运行于信号接收者所在的线程的事件循环时被调用),多线程环境下一般用这个。7.5、多进程通信(进程间通信)六种进程间通信方式_进程间的通信方式-CSDN博客每个进程的用户地址空间都是独立的,一般而言是不能互相访问的,但内核空间是每个进程都共享的,所以进程之间要通信必须通过内核。链接:拼多多二面,面试官冷笑:'共享内存为什么最快?' 我提到系统调用次数为零时,他突然打开了ipcs命令。。面试题:"进程间有哪些通信方式?"—— 简要回答unsetunset信号量机制(Semaphore):进程通过P / V操作控制信号量,可以有效地解决同步和互斥问题;信号量机制适合需要同步的场景,如避免竞争条件和死锁。共享存储机制(Shared Memory):多个进程能够直接读写同一块内存空间,这种机制被称为共享内存,共享内存是实现进程间通信的一种高效方法。共享存储机制适合需要高效数据传输的场景。消息传递机制(Message Passing):消息传递机制需要操作系统内核的支持,通过消息队列或信箱传递数据,消息以数据块的形式传输。消息传递机制支持不同类型的数据,适用于无关进程之间的通信。管道通信机制(Pipeline):管道通信机制适合进程间的简单数据传输,其又可细分为两类——①匿名管道:半双工通信,适用于父子进程或兄弟进程;②命名管道:具有文件名,适用于无关进程。套接字机制(Socket):Socket(套接字)是一种网络通信机制,可以在不同主机上的进程之间进行通信;Socket机制适合分布式系统和网络通信。—— 详细回答unsetunset信号量机制(Semaphore):信号量是一种同步通信机制,用于控制多个进程对共享资源的访问;信号量支持两种原子操作:P()(等待)和V()(信号)。共享存储机制(Shared Memory):共享存储机制又可细分为两种方式——①共享数据结构的通信方式:进程间共享同一个数据结构,编程人员需要考虑进程间的同步互斥问题,传输效率低;②共享存储区的通信方式:在计算机系统中,操作系统会分配特定的共享内存区域供多个进程同时访问。这种机制允许不同进程高效地进行数据交换。当某个进程需要访问共享内存时,首先需要完成虚拟地址空间到共享内存区域的映射操作,然后向操作系统提交访问请求。消息传递机制(Message Passing):进程之间可以调用由操作系统实现的通信命令来传递消息;实现消息传递即分别实现Send和Receive两个原语,用于消息的发送和接收。消息传递机制又可细分为两类——①直接通信方式:发送进程直接将消息发送给接收进程,将信息挂载到接收进程的消息队列上,接收进程从自己的消息队列中获取信息;②间接通信方式:操作系统额外创建一片空间用于存储各种消息,类似一个信箱,发送进程和接收进程依靠这个中间实体实现信息的发送和接收,这种方式也被称为“信箱通信模式”,在计算机网络中被广泛运用。管道通信机制(Pipeline):管道机制又可细分为两种类型——①匿名管道:即我们平时所说的“管道”通信,它使用一种共享文件来实现进程间通信,这种共享文件被称为“管道” 或 “pipe文件”,可以连接一个读进程和一个写进程。写进程以字节流的形式,将数据送入管道,读进程通过管道将数据读出。匿名管道作为一种半双工通信机制,其数据传输具有单向性特征,主要应用于具有亲缘关系的进程间通信,包括父子进程或兄弟进程之间的交互。在UNIX/Linux操作系统中,开发者可以通过调用pipe()函数来建立匿名管道。②命名管道(Named Pipe/FIFO):在UNIX/Linux操作系统中,存在一种具备特定文件名的特殊通信管道,该管道能够实现不同进程间的数据交互。通过调用mkfifo()这一系统函数,可以创建此类被命名的管道。套接字机制(Socket):Socket是一种网络通信机制,支持不同主机上的进程之间进行通信。网络通信功能可通过Socket实现,特别适合分布式架构的应用场景。从通信模式来看,既可采用基于连接的TCP协议,也可选择无连接的UDP协议进行数据传输。7.5.1 管道(最简单的通信方式)1)管道传输数据是单向的。2)管道就是内核里面的一串缓存。(进程写入的数据都是缓存在内核中)3)通信方式效率低,不适合进程间频繁地交换数据。4)通信数据都遵循先进先出原则,不支持 lseek 之类的文件定位操作。ps:匿名管道通信的数据是无格式的流并且大小受限。匿名管道:通信范围是存在父子关系的进程命名管道:可以在不相关的进程间也能相互通信7.5.2 消息队列(解决了管道通信方式效率低的弊端)1)消息队列是保存在内核中的消息链表2)不适合比较大数据的传输3)通信过程中,存在 用户态与内核态之间 的数据拷贝开销不足:一是通信不及时,二是附件也有大小限制(消息体都是固定大小的存储块)。7.5.3 共享内存(解决了消息队列通信过程存在用户态与内核态之间的消息拷贝过程的弊端)共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中。(直接分配一个共享空间,每个进程都可以直接访问)弊端:如果多个进程同时修改同一个共享内存,很有可能就冲突了。例如两个进程都同时写一个地址,那先写的那个进程会发现内容被别人覆盖了。(多进程竞争共享资源,而造成的数据错乱)==》需要保护机制,使得共享的资源,在任意时刻只能被一个进程访问。(信号量能解决)7.5.4 信号量(解决了共享内存中多进程竞争共享资源,而造成的数据错乱的弊端)信号量作为保护机制,使得共享的资源,在任意时刻只能被一个进程访问。1)信号量其实是一个整型的计数器,主要用于实现进程间的互斥与同步,而不是用于缓存进程间通信的数据。2)信号量不仅可以实现访问的互斥性,还可以实现进程间的同步,如下:信号初始化为1,就代表着是互斥信号量,它可以保证共享内存在任何时刻只有一个进程在访问,这就很好的保护了共享内存。信号初始化为0,就代表着是同步信号量,它可以保证进程 A 应在进程 B 之前执行。信号量表示资源的数量,控制信号量的方式有两种原子操作:一个是P 操作,这个操作会把信号量减去