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.h

5)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;
}
  1. 友元关系不是相互的:如果类A声明类B的一个成员函数为友元,那么只有那个特定的成员函数是类A的友元,而不是类B的所有成员函数。

  2. 友元函数不是类的成员函数:它不能通过类的实例来调用,也不能访问类的this指针(即没有this指针)。

  3. 友元可以定义在类外部:如上例所示,友元函数的定义可以放在类的外部。

  4. 友元函数可以访问类的所有成员:包括私有成员和保护成员。

  5. 友元函数破坏了封装性:虽然友元函数在某些情况下很有用,但它允许外部代码访问类的内部状态,这可能会破坏类的封装性。因此,应该谨慎使用友元函数。

  6. 友元函数可以是另一个类的成员函数:在这种情况下,整个类(而不是单个成员函数)成为友元。

  7. 友元函数可以重载:就像其他函数一样,友元函数也可以被重载。

6)虚函数与纯虚函数,即动态多态(运行时多态)

C++ 多态深度解析:理解虚函数与纯虚函数 (qq.com)

C++中的虚函数和纯虚函数都是为了实现多态性而存在的:

  1. 虚函数:在基类中声明的函数,可以在派生类中被覆盖(重写)。虚函数在基类中有一个默认的实现,但在派生类中可以重新实现,实现与基类虚函数的方法签名相同。派生类中定义虚函数时,必须使用关键字“virtual”来声明。也就是虚函数使用virtual修饰,有函数体,不会导致父类变为抽象类。

  2. 纯虚函数:在基类中声明的函数,在基类中没有实现必须在派生类中实现。纯虚函数使用“=0”来声明,例如“virtual void foo() = 0;”。纯虚函数没有默认实现,所以派生类必须实现它们。也就是纯虚函数有virtual修饰,=0,没有函数体,导致父类为抽象类,子类必须重写父类的所有纯虚函数。

区别:

  1. 虚函数可以有实现,纯虚函数没有实现。

  2. 虚函数可以在基类中有默认的实现,纯虚函数没有默认实现。

  3. 虚函数不一定要在派生类中实现,但纯虚函数必须在派生类中实现。

  4. 如果一个类中有纯虚函数,那么它就是抽象类。不能创建抽象类的对象,只能创建它的派生类对象。

虚函数是可选的,它有一个默认的实现,但可以在派生类中重新实现;

纯虚函数是必须实现的,它没有默认的实现,只有声明,必须在派生类中实现。

虚函数用于实现多态性,而纯虚函数用于定义接口

虚函数

虚函数的原理

  • 虚函数的原理 基于一个叫做虚函数表(也叫虚表)的特殊数据结构。
  • 每个含有虚函数的类 都有一个对应的虚表;
  • 每个对象实例 都包含一个 指向其类的虚表 的指针(vptr)。
  • 当调用一个对象的虚函数时,程序会通过这个指针查找虚函数表,从而找到正确的函数实现来执行,这就支持了运行时多态,即允许在运行时根据对象的实际类型来调用适当的函数版本。

虚表

  • 是类的所有对象共有的。
  • 每个类只有一个虚表,该表在编译时被创建,且所有该类的对象都包含一个指向这个共享虚表的指针(vptr)。
  • 当类的对象被创建时,每个对象的 vptr 会被初始化指向它所属类的虚表。
  • 虽然虚表是共享的,但每个对象都有自己的 vptr,确保了即使是同一个类的不同对象,如果它们属于不同的派生类,也能通过各自的 vptr 找到正确的虚函数实现。
  • 虚函数是在基类中用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 多态知道吗?讲一讲你对多态的理解

  • 多态是面向对象编程的重要特性之一,简单来说,它允许同一个函数或方法在不同的上下文中表现出不同的行为(概念)
  • 多态又分为静态多态和动态多态,静态多态是通过函数重载,运算符重载和泛型编程实现,是在编译期实现的。动态多态是通过继承和虚函数实现,是在运行期实现的。(分类)
  • 多态他是通过父类的指针或者引用,指向子类的对象,每个包含虚函数的类实例中都会有一个虚函数指针,虚函数指针指向虚函数表,虚函数表中就是我们要调用的虚函数的的地址,从而实现多态。(原理)

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.so

2)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++的编译过程

  1. 预处理:处理源代码文件中的预处理指令,如宏定义的展开,去掉注释、条件编译指令和头文件包含。
  2. 编译:将预处理后的源代码转换成汇编代码,会进行语法分析,语义分析,进行代码优化。
  3. 汇编:将汇编代码转换成机器码,生成目标文件(.obj 或 .o 文件)。
  4. 链接:将所有的目标文件和必需的库文件链接在一起形成最终的可执行文件。

3、常用的开发设计模式

1) 单例模式(Singleton)

应用场景:当只需要一个全局实例时。
现实例子:操作系统的文件系统。
优点:节省了系统资源,避免了不必要的实例化。
缺点:可测试性差,扩展性差。

单例模式指的是保证 ,一个类中整个程序中只有能有存在有一个实例,并且提供一个全局的访问点。

实现方法:通常将构造函数私有提供一个静态函数来返回唯一的实例,还会用delete操作符禁止掉拷贝构造和复值重载。即:

a) 私有化它的构造函数,以防止外界创建单例类的对象;
b) 使用类的私有静态指针变量指向类的唯一实例;
c) 使用一个公有的静态方法获取该实例。

二种形式:

懒汉式:在第一次调用的时候才会创建实例,实现延迟加载。可能线程不安全(多线程中要处理同步问题)

饿汉式:在程序启动的时候就创建实例。线程安全

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的情况;

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进行初始化 参数:vector
	vector<int> dp(w + 1, 0);//初始化dp数组,往里放进w+1个0
	vector<vector<int>> dp(m + 1, vector<int>(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的元素

map

1、什么是map?

C++ 中 map 提供的是一种键值对容器,里面的数据都是成对出现的,如下图:每一对中的第一个值称之为关键字(key),每个关键字只能在 map 中出现一次;第二个称之为该关键字的对应值。

2、常用操作

1、 插入操作

m.insert(pair<int, int>(1, 10)); //插入pair
m.insert(make_pair<int, int>(2, 20));
m[4] = 40;

2、 查找操作

map<int,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 Key&key); //通过关键字删除
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
  1. 信号量机制(Semaphore):进程通过P / V操作控制信号量,可以有效地解决同步和互斥问题;信号量机制适合需要同步的场景,如避免竞争条件和死锁。

  2. 共享存储机制(Shared Memory):多个进程能够直接读写同一块内存空间,这种机制被称为共享内存,共享内存是实现进程间通信的一种高效方法。共享存储机制适合需要高效数据传输的场景。

  3. 消息传递机制(Message Passing):消息传递机制需要操作系统内核的支持,通过消息队列 或 信箱传递数据,消息以数据块的形式传输。消息传递机制支持不同类型的数据,适用于无关进程之间的通信。</

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值