C++多态

news/发布时间2024/5/18 14:20:31

文章目录

  • 多态的概念
  • 多态的定义及实现
      • 多态的构成条件
      • 虚函数
      • 虚函数重写
      • 虚函数重写的两个例外
          • 例外一
          • 例外二
          • C++11 override 和 final
      • 重载、覆盖(重写)、隐藏(重定义)的对比
  • 抽象类
      • 概念
      • 接口继承和实现继承
  • 多态的原理
      • 虚函数表
      • 多态的原理
      • 动态绑定和静态绑定
  • 单继承和多继承关系中的虚函数表
      • 单继承中的虚函数表
      • 多继承中的虚函数表
      • 菱形继承、菱形虚拟继承
  • 继承和多态常见的面试题

多态的概念

多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。

举个例子:比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人买票时是优先买票。

多态的定义及实现

多态的构成条件

多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。要实现多态必须满足这两个条件:

  1. 必须通过基类的指针或者引用调用虚函数
  2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

虚函数

虚函数:即被virtual修饰的类成员函数称为虚函数

class Person
{
public://被virtual修饰的类成员函数就是虚函数virtual void BuyTicket(){cout << "买票-全价" << endl;}
};

**注意:1.只有类的非静态成员函数前可以加virtual变成虚函数,普通函数和静态成员函数不可以
2.虚函数所加关键字virtual和虚继承所加关键字virtual是同一个关键字,但是二者没有任何关系,虚函数加virtual是为了实现多态,而虚继承加virtual是为了解决菱形继承时数据冗余和二义性问题 **

虚函数重写

虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。
例如:下面Student子类就重写了Person父类的虚函数

//父类
class Person
{
public://父类的虚函数virtual void BuyTicket(){cout << "买票-全价" << endl;}
};
//子类
class Student : public Person
{
public://子类重写了父类的虚函数virtual void BuyTicket(){cout << "买票-半价" << endl;}
};

派生类重写基类虚函数之后就构成多态,此时通过基类的指针或者引用就可以实现多态调用,此时不同类型的对象调用就能产生不同的结果,实现了函数调用的多态形态。
注意:普通调用: 看指针或者引用或者对象的类型
多态调用 看指针或者引用指向的对象类型。

例如下面:Person父类对象就调用父类的虚函数,Student子类就调用子类的虚函数实现了多态。

//父类
class Person
{
public://父类的虚函数virtual void BuyTicket(){cout << "买票-全价" << endl;}
};
//子类
class Student : public Person
{
public://子类的虚函数virtual void BuyTicket(){cout << "买票-半价" << endl;}
};void Func(Person& p)//基类的引用调用虚函数
{p.BuyTicket();
}int main()
{Person p;Student s;Func(p);//调同一函数,传什么类型对象调什么对象的虚函数,传父类就调父类Func(s);传子类就调子类的虚函数return 0;
}

结果:买票-全价
买票-半价
注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因
为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样使用,》》建议两个都加,但是父类不加一定构不成多态,子类加没有用。

虚函数重写的两个例外

例外一

协变(基类与派生类虚函数返回值类型不同),也构成多态,但是必须满足条件:派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变

如一下代码一个返回值为父类A的指针,一个返回子类B的指针,返回值有继承关系,构成虚函数重写。

//父类A
class A {};
//子类B
class B : public A{};class Person {
public://返回值为父类A的指针或引用virtual A* f() { cout << "A::f()" << endl;return new A;}
};class Student : public Person {
public://返回值为子类的指针或引用virtual B* f() {cout << "B::f()" << endl;return new B;}
};int main()
{基类Person的指针指向子类Student对象多态调用f()Person* p = new Student;p->f();//结果“B::f()”实现了多态return 0;
}
例外二

析构函数的重写(基类与派生类析构函数的名字不同)

以下代码delete 释放空间是普通调用,delete p1释放的是父类,delete p2释放的也是父类,那么p2指向的子类当中只有父类那部分成员被释放,子类成员没有被释放造成内存泄漏。要想delete p2 调用的是子类的析构函数,那么必须构成多态,父类对象调父类的析构,子类对象调子类的析构。

class Person {
public:virtual ~Person() {cout << "~Person()" << endl;}
};
class Student : public Person {
public:virtual ~Student() { cout << "~Student()" << endl; }
};
int main()
{Person* p1 = new Person;Person* p2 = new Student;delete p1;delete p2;return 0;
}

解决办法构成多态:只有派生类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函数,才能构成多态,才能保证p1和p2指向的对象正确的调用析构函数。
只需要将基类Person的析构函数前加virtual就构成重写了!

问题来了?析构函数加上virtual就构成了虚函数重写呢?
因为:如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。函数名相同,所以为什么析构函数天生构成隐藏,加上virtual后就变成了虚函数重写构成多态

C++11 override 和 final

从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此:C++11提供了override和final两个关键字,可以帮助用户检测是否重写。

要想让一个类不能被继承?
方法1:父类构造函数私有化,派生实例化不出对象

方法二:加final
—final修饰的类为最终类,不能被继承。
—final修饰虚函数,表示该虚函数不能再被重写

class Car
{
public:virtual void Drive() final {}//加上final派生类不能重写
};
class Benz :public Car
{
public:
//重写报错virtual void Drive() {cout << "Benz-舒适" << endl;}
};

override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错,重写了不报错。可以强制检查基类某个必须重写的虚函数有没有被重写成功。

class Car{
public:virtual void Drive(){}
};
class Benz :public Car {
public:virtual void Drive() override {cout << "Benz-舒适" << endl;}//不报错,重写成功
};

重载、覆盖(重写)、隐藏(重定义)的对比

在这里插入图片描述
继承派生类和基类中只有重写和重定义,重载必须在同一作用域,函数名相同就构成隐藏,如果是虚函数,且符号返回值和参数列表相同,就构成重写。
注意:重写的两个函数必须是虚函数

抽象类

概念

在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

class Car
{
public:
virtual void Drive() = 0;
};
class Benz :public Car
{
public:virtual void Drive(){cout << "Benz-舒适" << endl;}
};
class BMW :public Car
{
public:virtual void Drive(){cout << "BMW-操控" << endl;}
};
void Test()
{
Car* pBenz = new Benz;pBenz->Drive();Car* pBMW = new BMW;pBMW->Drive();
}

接口继承和实现继承

**实现继承:**普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。为了使用。
**接口继承:**虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。
**建议:**如果不实现多态,不要把函数定义成虚函数。

多态的原理

虚函数表

// 这里常考一道笔试题:sizeof(Base)是多少?
class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;
};
int main()
{Base b;cout << sizeof(b) << endl; //8return 0;
}

结果是8,为什么呢?
b对象当中除了_b成员外,实际上还有一个_vfptr放在对象的前面(有些平台可能会放到对象的最后面,这个跟平台有关)。
对象中的这个指针vfptr叫做虚函数表指针,简称虚表指针,虚表指针指向一个虚函数表,简称虚表,每一个含有虚函数的类中都至少有一个虚表指针。虚表指针指向虚表,虚表里面有什么呢?

其实虚表就是一个函数指针数组,存的是每个虚函数的地址。

下面基类Base有虚函数Func1,Func2,普通成员函数Func3,派生类Derive重写了Func1。

#include <iostream>
using namespace std;
//父类
class Base
{
public://虚函数virtual void Func1(){cout << "Base::Func1()" << endl;}//虚函数virtual void Func2(){cout << "Base::Func2()" << endl;}//普通成员函数void Func3(){cout << "Base::Func3()" << endl;}
private:int _b = 1;char _ch = 97;
};
//子类
class Derive : public Base
{
public://重写虚函数Func1virtual void Func1(){cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};
int main()
{Base b;Derive d;return 0;
}

通过调试看结果
在这里插入图片描述
所有虚函数表里面实际上存的是每个虚函数的地址,是一个指针数组,由于Func1被重写,所以子类对象dd存的Func1函数地址是自己重写了的函数地址,覆盖了原来父类Base的Func1的函数地址,所以语法上叫重写,原理上叫覆盖,而Func2没有被重写,存的依然是BB对象中Func2的函数地址。普通成员函数Func3地址没有放下来,一般情况下会在这个指针数组最后放一个nullptr。(vs是这样的,g++没放,平台原因)。

总结一下虚表,就是一个指针数组,存虚函数地址

派生类虚表生成步骤--------------

  1. 先将基类中的虚表内容拷贝一份到派生类的虚表。
  2. 如果派生类重写了基类中的某个虚函数,则用派生类自己的虚函数地址覆盖虚表中基类的虚函数地址。
  3. 派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。

虚表是什么阶段初始化的?虚函数存在哪里?虚表存在哪里?

虚表指针实际上是在构造函数初始化列表阶段进行初始化的,注意虚表当中存的是虚函数的地址不是虚函数,虚函数和普通函数一样,都是存在代码段的,只是他的地址又存到了虚表当中。另外,对象中存的不是虚表而是指向虚表的指针。
所以对象当中存虚表指针,初始化列表阶段初始化,虚表在代码段,里面存每个虚函数地址,虚函数也存在代码段

至于虚表是存在哪里的?,我们可以通过以下这段代码进行判断。

int j = 0;
int main()
{Base b;Base* p = &b;printf("vfptr:%p\n", *((int*)p)); //000FDCACint i = 0;printf("栈上地址:%p\n", &i);       //005CFE24printf("数据段地址:%p\n", &j);     //0010038Cint* k = new int;printf("堆上地址:%p\n", k);       //00A6CA00char* cp = "hello world";printf("代码段地址:%p\n", cp);    //000FDCB4return 0;
}

代码当中打印了对象b当中的虚表指针,也就是虚表的地址,可以发现虚表地址与代码段的地址非常接近,由此我们可以得出虚表实际上是存在代码段的。

所以虚表是在代码段的

多态的原理

为什么基类指针指向父类对象时就调用父类的BuyTicket,指向子类对象时就调用子类的BuyTicket呢?

#include <iostream>
using namespace std;
//父类
class Person
{
public:virtual void BuyTicket(){cout << "买票-全价" << endl;}int _p = 1;
};
//子类
class Student : public Person
{
public:virtual void BuyTicket(){cout << "买票-半价" << endl;}int _s = 2;
};
int main()
{Person Mike;Student Johnson;Johnson._p = 3; //以便观察是否完成切片Person* p1 = &Mike;Person* p2 = &Johnson;p1->BuyTicket(); //买票-全价p2->BuyTicket(); //买票-半价return 0;
}

通过调试可以发现,Mike对象和Johnson对象都有一个虚表指针,指向不同的虚表。
在这里插入图片描述
正是因为各自有各自的虚表所以,当基类指针指向不同的对象时就会到各自对象的虚表里面去找。这样就实现了不同对象调用不同的虚函数,完成了多态。

现在回想一下多态的两个条件:一必须构成虚函数重写,就像上面一样有各自的虚函数表,那么继承过来的虚函数如果重写了,子类虚表就存重写了的函数地址,没有重写,函数地址还是父类的虚函数地址,所以必须虚函数重写覆盖原来的虚函数地址,这样调用各自的才有不同效果。 二:为什么一定要用基类的指针或者引用去调用呢?

就是因为用父类的指针或者引用去调其实是一种切片行为,指向父类对象时,不切片,指向子类对象时,切片下来保留继承父类的那一部分成员和虚表。
这样就可以调用各自的虚表了。

在这里插入图片描述
那为什么用基类的对象调用就不可以呢?
**因为基类对象调用时会发生拷贝构造,构造出来的对象的虚表指针不会被拷贝过去,所以构造出来的父类的对象虚表指针还是指向父类的虚表,实现不了多态。
所以必须用基类的指针进行,切片,而不是拷贝构造。

在这里插入图片描述
总结一下
1.构成多态,基类指针指向什么对象就调用什么对象的虚函数。与对象有关。
2.不构成多态,什么类型的对象就调用什么对象的虚函数,与类型有关。

动态绑定和静态绑定

静态绑定: 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也成为静态多态,比如:函数重载。

动态绑定: 动态绑定又称为后期绑定(晚绑定),在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。

//父类
class Person
{
public:virtual void BuyTicket(){cout << "买票-全价" << endl;}
};
//子类
class Student : public Person
{
public:virtual void BuyTicket(){cout << "买票-半价" << endl;}
};
int main()
{Student Johnson;Person p = Johnson; //不构成多态p.BuyTicket();return 0;
}

不构成多态,函数调用是在编译期间完成的是静待绑定,构成多态函数调用是运行期间完成的。看汇编代码。
不构成多态调用,直接就是call函数地址是在编译期间完成的,是静态绑定。
在这里插入图片描述

int main()
{Student Johnson;Person& p = Johnson; //构成多态p.BuyTicket();return 0;
}

看一下多态调用情况-----
在这里插入图片描述
构成多态,汇编指令变多了,原因是在运行时去通过找虚表找到对应的虚函数调用。
总结:所以静态绑定是在编译时确定的,而动态绑定是在运行时确定的

单继承和多继承关系中的虚函数表

单继承中的虚函数表

通过这段代码可以观察单继承虚表模型

//基类
class Base
{
public:virtual void func1() { cout << "Base::func1()" << endl; }virtual void func2() { cout << "Base::func2()" << endl; }
private:int _a;
};
//派生类
class Derive : public Base
{
public:virtual void func1() { cout << "Derive::func1()" << endl; }virtual void func3() { cout << "Derive::func3()" << endl; }virtual void func4() { cout << "Derive::func4()" << endl; }
private:int _b;
};

在这里插入图片描述
单继承虚表模型中完成了一下动作:

  1. 继承基类的虚表内容到派生类的虚表。
  2. 对派生类重写了的虚函数地址进行覆盖,比如func1。
  3. 虚表当中新增派生类当中新的虚函数地址,比如func3和func4。
    一、使用内存监视窗口
    因为我们看不到虚表里面存的函数地址可以通过内存窗口观看。
    在这里插入图片描述
    二. 内存窗口看见的地址是否和我们说的子类的虚表和父类虚表继承过来的顺序一致,和子类自己的虚函数按照声明顺序放在后面,即和单继承模型图一致吗?可以
    通过代码打印出来。
typedef void(*VFPTR)(); //虚函数指针类型重命名
//打印虚表地址及其内容
void PrintVFT(VFPTR* ptr)
{printf("虚表地址:%p\n", ptr);for (int i = 0; ptr[i] != nullptr; i++){printf("ptr[%d]:%p-->", i, ptr[i]); //打印虚表当中的虚函数地址ptr[i](); //使用虚函数地址调用虚函数}printf("\n");
}
int main()
{Base b;PrintVFT((VFPTR*)(*(int*)&b)); //打印基类对象b的虚表地址及其内容Derive d;PrintVFT((VFPTR*)(*(int*)&d)); //打印派生类对象d的虚表地址及其内容return 0;
}

在这里插入图片描述

多继承中的虚函数表

再看看多继承虚表模型,通过一下代码观察

//基类1
class Base1
{
public:virtual void func1() { cout << "Base1::func1()" << endl; }virtual void func2() { cout << "Base1::func2()" << endl; }
private:int _b1;
};
//基类2
class Base2
{
public:virtual void func1() { cout << "Base2::func1()" << endl; }virtual void func2() { cout << "Base2::func2()" << endl; }
private:int _b2;
};
//多继承派生类
class Derive : public Base1, public Base2
{
public:virtual void func1() { cout << "Derive::func1()" << endl; }virtual void func3() { cout << "Derive::func3()" << endl; }
private:int _d1;
};

两个基类的虚表
在这里插入图片描述
派生类的虚表
在这里插入图片描述

多继承模型过程中做的步骤
1. 分别继承各个基类的虚表内容到派生类的各个虚表当中。
2. 对派生类重写了的虚函数地址进行覆盖(派生类中的各个虚表中存有该被重写虚函数地址的都需要进行覆盖),比如func1。
3. 在派生类第一个继承基类部分的虚表当中新增派生类当中新的虚函数地址,比如func3。

一. 内存窗口查看完整的虚表
在这里插入图片描述

二. 通过代码打印虚表内容
**需要注意的是:**我们在派生类第一个虚表地址的基础上,向后移sizeof(Base1)个字节即可得到第二个虚表的地址。

typedef void(*VFPTR)(); //虚函数指针类型重命名
//打印虚表地址及其内容
void PrintVFT(VFPTR* ptr)
{printf("虚表地址:%p\n", ptr);for (int i = 0; ptr[i] != nullptr; i++){printf("ptr[%d]:%p-->", i, ptr[i]); //打印虚表当中的虚函数地址ptr[i](); //使用虚函数地址调用虚函数}printf("\n");
}
int main()
{Base1 b1;Base2 b2;PrintVFT((VFPTR*)(*(int*)&b1)); //打印基类对象b1的虚表地址及其内容PrintVFT((VFPTR*)(*(int*)&b2)); //打印基类对象b2的虚表地址及其内容Derive d;PrintVFT((VFPTR*)(*(int*)&d)); //打印派生类对象d的第一个虚表地址及其内容PrintVFT((VFPTR*)(*(int*)((char*)&d + sizeof(Base1)))); //打印派生类对象d的第二个虚表地址及其内容return 0;
}

结果:
在这里插入图片描述

菱形继承、菱形虚拟继承

菱形虚拟继承模型通过以下派生类和基类观察

class A
{
public:virtual void funcA(){cout << "A::funcA()" << endl;}
private:int _a;
};
class B : virtual public A
{
public:virtual void funcA(){cout << "B::funcA()" << endl;}virtual void funcB(){cout << "B::funcB()" << endl;}
private:int _b;
};
class C : virtual public A
{
public:virtual void funcA(){cout << "C::funcA()" << endl;}virtual void funcC(){cout << "C::funcC()" << endl;}
private:int _c;
};
class D : public B, public C
{
public:virtual void funcA(){cout << "D::funcA()" << endl;}virtual void funcD(){cout << "D::funcD()" << endl;}
private:int _d;
};

在这里插入图片描述
继承关系如图,基类A当中有虚函数funcA,B有虚函数funcB,C有虚函数funcC,D有虚函数funcD,A,B,C三个类都重写了funcA。

A类对象的分布情况

A中包含一个成员变量_a, 一个虚表指针指向一个存放虚函数funcA的虚表。
在这里插入图片描述

B类对象当中的成员及其分布情况。
B类对象中存放了一个成员变量_b,一个虚表指针指向存放funcB的虚表,还有一个虚基表指针指向一个虚基表,虚基表当中存储的是两个偏移量,第一个是虚基表指针距离B虚表指针的偏移量,第二个是虚基表指针距离虚基类A的偏移量。 B类由于是虚拟继承的A类,所以B类对象当中将A类继承下来的成员放到了最后。
在这里插入图片描述

C类对象中的情况

C类对象当中的成员分布情况与B类对象当中的成员分布情况相同。C类也是虚拟继承的A类,所以C类对象当中将A类继承下来的成员放到了最后,除此之外,C类对象的成员还包括一个虚表指针、一个虚基表指针和成员变量_c,虚表指针指向的虚表当中存储的是C类虚函数funcC的地址。
虚基表当中存储的是两个偏移量,第一个是虚基表指针距离C虚表指针的偏移量,第二个是虚基表指针距离虚基类A的偏移量。

在这里插入图片描述

D类对象中的成员分布情况

D由于是菱形虚拟继承,把A继承下来的成员放到最后,还有从B,C继承下来的成员,还有D的成员,需要注意的是,D类对象当中的虚函数funcD的地址是存储到了B类的虚表当中

在这里插入图片描述
建议
实际中我们不建议设计出菱形继承及菱形虚拟继承,一方面太复杂容易出问题,另一方面使用这样的模型访问基类成员有一定的性能损耗。

继承和多态常见的面试题

  1. 以下程序输出结果是什么()
class A{public:virtual void func(int val = 1){ std::cout<<"A->"<< val <<std::endl;}virtual void test(){ func();}};class B : public A{public:void func(int val=0){ std::cout<<"B->"<< val <<std::endl; }};int main(int argc ,char* argv[]){B*p = new B;p->test();return 0;}

解析:由于基类B没有重定义test()成员函数,所以p->test()调用的是继承过来的test()即父类A的test(),但是A的成员函数test()隐藏形参this是A类型的,所以调用func()是A的指针类型即基类的指针调用func函数,同时func虚函数又满足重写,构成了多态两个条件,但是A*指向的对象是子类B类型,所以调用B的func结果是B->1,因为是多态调用,而且虚函数重写是实现重写,声明不重写,所以缺省值不是0,还是1,结果选B。如果是普通调用B的func函数,,就不是虚函数重写,是同名隐藏,就整个重定义了包括声明,所以输出B->0。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.bcls.cn/ByTm/3437.shtml

如若内容造成侵权/违法违规/事实不符,请联系编程老四网进行投诉反馈email:xxxxxxxx@qq.com,一经查实,立即删除!

相关文章

【办公类-16-10-02】“2023下学期 6个中班 自主游戏观察记录(python 排班表系列)

背景需求&#xff1a; 已经制作了本学期的中4班自主游戏观察记录表 【办公类-16-10-01】“2023下学期 中4班 自主游戏观察记录&#xff08;python 排班表系列&#xff09;-CSDN博客文章浏览阅读398次&#xff0c;点赞10次&#xff0c;收藏3次。【办公类-16-10-01】“2023下学…

springmvc+ssm+springboot房屋中介服务平台的设计与实现 i174z

本论文拟采用计算机技术设计并开发的房屋中介服务平台&#xff0c;主要是为用户提供服务。使得用户可以在系统上查看房屋出租、房屋出售、房屋求购、房屋求租&#xff0c;管理员对信息进行统一管理&#xff0c;与此同时可以筛选出符合的信息&#xff0c;给笔者提供更符合实际的…

Android全新UI框架之Jetpack Compose入门基础

Jetpack Compose是什么 如果有跨端开发经验的同学&#xff0c;理解和学习compose可能没有那么大的压力。简单地说&#xff0c;compose可以让Android的原生开发也可以使用类似rn的jsx的语法来开发UI界面。以往&#xff0c;我们开发Android原生页面的时候&#xff0c;通常是在xml…

操作系统--多线程的互斥、同步

一、概念 在进程/线程并发执行的过程中&#xff0c;进程/线程之间存在协作的关系&#xff0c;例如有互斥、同步的关系。 1.互斥 由于多线程执行操作共享变量的这段代码可能会导致竞争状态&#xff0c;因此我们将此段代码称为临界区&#xff08;critical section&#xff09;…

Linux(ACT)权限管理

文章目录 一、 ATC简介二、 案例1. 添加测试目录、用户、组&#xff0c;并将用户添加到组2. 修改目录的所有者和所属组3. 设定权限4. 为临时用户分配权限5. 验证acl权限 6. 控制组的acl权限 一、 ATC简介 ACL&#xff08;Access Control List&#xff0c;访问控制列表&#xf…

element ui 安装 简易过程 已解决

我之所以将Element归类为Vue.js&#xff0c;其主要原因是Element是&#xff08;饿了么团队&#xff09;基于MVVM框架Vue开源出来的一套前端ui组件。我最爱的就是它的布局容器&#xff01;&#xff01;&#xff01; 下面进入正题&#xff1a; 1、Element的安装 首先你需要创建…

使用LinkedList实现堆栈及Set集合特点、遍历方式、常见实现类

目录 一、使用LinkedList实现堆栈 堆栈 LinkedList实现堆栈 二、集合框架 三、Set集合 1.特点 2.遍历方式 3.常见实现类 HashSet LinkedHashSet TreeSet 一、使用LinkedList实现堆栈 堆栈 堆栈&#xff08;stack&#xff09;是一种常见的数据结构&#xff0c;一端…

抖音爬虫批量视频提取功能介绍|抖音评论提取工具

抖音爬虫是指通过编程技术从抖音平台上获取视频数据的程序。在进行抖音爬虫时&#xff0c;需要注意遵守相关法律法规和平台规定&#xff0c;以确保数据的合法获取和使用。 一般来说&#xff0c;抖音爬虫可以实现以下功能之一&#xff1a;批量视频提取。这个功能可以用于自动化地…

Android 解决后台服务麦克风无法录音问题

Android 解决后台无法录音问题 问题分析问题来源解决方案1. 修改清单文件:`AndroidManifest.xml`2. 修改启动服务方式3. 服务启动时创建前台通知并且指定前台服务类型参考文档最后我还有一句话要说我用心为你考虑黄浦江的事情,你心里想的却只有苏州河的勾当 问题分析 安卓9.…

WebRTC最新版报错解决:FileNotFoundError: LASTCHANGE.committime (二十五)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒体系统工程师系列【原创干货持续更新中……】🚀 人生格言: 人生从来没有捷径,只…

SpringCloud-Gateway解决跨域问题

Spring Cloud Gateway是一个基于Spring Framework的微服务网关&#xff0c;用于构建可扩展的分布式系统。在处理跨域问题时&#xff0c;可以通过配置网关来实现跨域资源共享&#xff08;CORS&#xff09;。要解决跨域问题&#xff0c;首先需要在网关的配置文件中添加相关的跨域…

智慧建工的魔法:数据可视化的引领之光

在智慧建工的时代&#xff0c;数据可视化成为推动建筑行业进步的强大引擎&#xff0c;其作用不可忽视。通过将复杂的建筑数据以直观、清晰的图形展示出来&#xff0c;数据可视化为建筑工程提供了前所未有的便利和创新。 首先&#xff0c;数据可视化在建筑规划和设计阶段发挥关键…

Windows安装ElasticSearch

安装ES 官方网站&#xff1a;https://www.elastic.co/cn/elasticsearch 声明&#xff1a;JDK1.8 &#xff0c;最低要求&#xff01; Java开发&#xff0c;ElasticSearch的版本和我们之后的java核心包对应 windows安装 目录结构 目录熟悉 bin 启动文件 config 配置文件log4j…

数据结构-Queue队列

一,队列的简单认识 队列也是一种线性数据结构,与栈不同的是,它只能从一端添加元素,从另一端取出元素.定义了一端,另一端也就确定了. (当然还有一个特殊的双向队列LinkedList除外,它既可以从队首添加元素,也可以移除元素,队尾也是一样的,既可以添加元素,也可以移除元素) 二,队…

算法打卡day1|数组篇|Leetcode 704.二分查找、27.移除元素

数组理论基础 数组是存放在连续内存空间上的相同类型数据的集合&#xff0c;可以方便的通过下标索引的方式获取到下标下对应的数据。 1.数组下标都是从0开始的。 2.数组内存空间的地址是连续的。 正是因为数组的在内存空间的地址是连续的&#xff0c;所以我们在删除或者增添…

如何连接ACL认证的Redis

点击上方蓝字关注我 应用程序连接开启了ACL认证的Redis时与原先的方式有差别&#xff0c;本文介绍几种连接开启ACL认证的Redis的Redis的方法。 对于RedisACL认证相关内容&#xff0c;可以参考历史文章&#xff1a; Redis权限管理体系(一&#xff09;&#xff1a;客户端名及用户…

TiDB离线部署、Tiup部署TiDB

先做tidb准备工作&#xff1a; 部署 TiDB 前的环境检查操作&#xff1a;TiDB 环境与系统配置检查 | PingCAP 文档中心 1.查看数据盘 fdisk -l &#xff08;2,3&#xff09;本人的分区已经是 ext4 文件系统不用分区&#xff0c;具体官方文档的分区&#xff1a; 4.查看数据盘…

小程序画布(二维地图线)

首先开始是想用小程序兼容openlayers的&#xff0c;但是了解到用不了&#xff0c;那就用画布来解决 实际效果如下 wxml中代码 <canvas id"trackDesignCanvas" //指定 id 的 Canvas 组件class"orbit-canvas-main" type"2d" …

鸿蒙DevEco Service开发准备与使用

DevEco低代码是一个基于Serverless和ArkUI的端云一体化低代码开发平台&#xff0c;可通过拖拽式开发&#xff0c;可视化配置构建元服务。打通HarmonyOS云侧与端侧能力&#xff0c;轻松实现HMS Core和AGC Serverless能力的调用。通过与元服务生态、HMS Core、AGC Serverless平台…

如何用GPT高效地处理文本、文献查阅、PPT编辑、编程、绘图和论文写作?

原文链接&#xff1a;如何用GPT高效地处理文本、文献查阅、PPT编辑、编程、绘图和论文写作?https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247594986&idx4&sn970f9ba75998f2dd9fa5707d1611a6cc&chksmfa82320dcdf5bb1bdf58c20686d4eb209770e68253ed90d…
推荐文章