【C++进阶】继承详解

news2024/12/23 15:21:50

在这里插入图片描述

文章目录

  • 前言
  • 一、继承的概念及定义
    • 1.概念
    • 2.继承定义
      • 定义格式
      • 继承关系和访问限定
      • 继承基类成员访问方式的变化
  • 二、基类和派生类对象赋值转换
  • 三、继承中的作用域
  • 四、派生类的默认成员函数
  • 五、继承与友元
  • 六、继承与静态成员
  • 七、复杂的菱形继承及菱形虚拟继承
    • 1.单继承与多继承
    • 2.菱形继承的问题
    • 3.虚拟继承
    • 4.虚拟继承的模型及原理
  • 八、继承的总结和反思
    • 继承反思
    • 继承与组合

前言

万物皆对象,对象是具体的世界事物,面向对象的三大特征封装,继承多态封装,封装说明一个类行为和属性与其他类的关系,低耦合,高内聚;继承是父类和子类的关系,多态说的是类与类的关系。
前边我们对封装有了一个系统的认识,今天我们来学习继承的相关内容。

一、继承的概念及定义

1.概念

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保
持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象
程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继
承是类设计层次的复用

概念看起来不太好懂,我来举一个例子,让大家了解到继承是怎么样的。比如在一个学校管理系统里边,有很多不同的角色,例如老师,学生,保安,宿管阿姨,每实现一个角色都得去实现一个类,但是这些类里边有很多属性是相同的,例如这些角色都是人,他们都有人的属性,例如姓名,年龄,电话号等等,所以当我们需要把这些共有的属性实现为一个类,在实现每个不同的角色时进行继承,就可以节省空间,也可以使代码简洁。

class person
{
public:
	char* _name;
	int _age;
};
//进行继承
class student : public person
{
public:
	int _studentId;
};

class teacher : public person
{
public:
	int _workId;
};

为了验证上述的代码,我们来调试观察一下:
在这里插入图片描述
我们发现在学生类的老师类中也有person类的属性,这也就是继承成功了。

2.继承定义

定义格式

在上述的例子中,我们把person类称为基类,也叫父类,把继承父类的类称为子类,或者派生类,但是大家切记,子类和父类是对应的,派生类和基类对应的。
在继承时应该遵循下图的格式:
在这里插入图片描述

继承关系和访问限定

C++的继承关系和访问限定比较复杂,这是因为C++是早期开始面向对象的语言,所以在设计上难免有不妥当的地方,但是语言必须向前兼容,所以C++的继承关系和访问限定复杂就留了下来,下边来看他们:
在这里插入图片描述
在这里插入图片描述

继承基类成员访问方式的变化

由于在一个类中,访问限定有三种,分别是public,protected,private
而一个类在继承父类时,又有三种继承方式,分别是public,protested,private
所以按道理来说,在继承之后,子类中应该有九种情况要分别讨论:
在这里插入图片描述
如果让我们背过这几种关系也并不难,但是千万不能死记硬背,而是去寻找规律的记忆他们。
通过观察,我们应该可以得到以下的结论:
1.基类中的私有成员,在继承到派生类之后,都在派生类中不可见。这里的不可见是指基类的私
有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面
都不能去访问它。
2.我们认为权限的大小为public>protected>provate,有了这样的公式,我们发现当类成员的访问限定符与在子类中的继承方式相遇时,最后在子类中的访问关系取他们之中权限较小的一个,例如基类的public成员使用pretected方式继承,最终派生类中的成员的访问属性就是protected的。
3.class类和struct结构体如果没有写出继承方式,默认class与默认权限一样,都是private,struct默认继承方式和默认权限相同,都是public,但是最好写出继承方式。

代码示例:
上边的例子,当class不显式的给定继承关系时,会默认采用私有继承,但是私有继承的成员在子类中是不可见的,所以下边访问他们就报错。
在这里插入图片描述
4.在实际的使用中,并不会经常用到private/protected继承,而大多数只会使用公有继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里
面使用,实际中扩展维护性不强。

二、基类和派生类对象赋值转换

1.派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片 或者切割。寓意把派生类中父类那部分切来赋值过去。
2.基类对象不能赋值给派生类对象
3.基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类
的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(RunTime Type Information)的dynamic_cast 来进行识别后进行安全转换。(这些东西后续的多态章节会讲到)。

可能在看完文字描述之后大家还不是很清楚,我们来通过图片看一下什么叫做切片:
在这里插入图片描述
当我们使用person继承得到一个student类时,在公有继承的情况下,我们会发现,子类成员有了父类成员所有的属性,除过私有属性,这时,我们就可以通过赋值转换,也叫切片技术来使用子类给父类赋值。

class Person
{
protected:
	string _name; // 姓名
	string _sex; //性别
	int _age; // 年龄
};
class Student : public Person
{
public:
	int _No; // 学号
};
void Test()
{
	Student sobj;
	// 1.子类对象可以赋值给父类对象/指针/引用
	Person pobj = sobj;
	Person* pp = &sobj;
	Person& rp = sobj;

	//2.基类对象不能赋值给派生类对象
	//sobj = pobj;

	// 3.基类的指针可以通过强制类型转换赋值给派生类的指针
	pp = &sobj;
	Student* ps1 = (Student*)pp; // 这种情况转换时可以的。
	ps1->_No = 10;

	pp = &pobj;
	Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题
	ps2->_No = 10;
}

通过上述的代码我们发现:
1.子类对象可以给父类对象赋值,也可以是指针和引用。
2.而父类不能给子类赋值,这点应该很容易想清楚,父类成员数量一定是小于等于子类的,所以不能对子类赋值。
3.父类指针可以通过强转赋值给子类的指针,但是可以会越界,其实指针只是一段地址,即使他们的地址相同,但是指针类型的不同意味着他们能往后看到空间的大小不同,理解这一点是很重要的。

三、继承中的作用域

1.父类和子类都有自己的作用域。
2.如果父类和子类有成员变量名是相同的,也是可以存在的,但是我们直接访问,访问到的是子类中的成员,如果想访问到父类继承下来的成员,必须指定作用域
3.函数变量在继承时,只要函数名相同就会构成隐藏,并不会判断函数的参数是否相同。

我们先通过一段程序来验证一下,在父类和子类中有相同的成员变量,直接访问就是子类的变量。

class Person
{
protected:
	string _name = "小李迪"; // 姓名
	int _num = 111; 	   // 身份证号
};

class Student : public Person
{
public:
	void Print()
	{
		cout << " 姓名:" << _name << endl;
		cout << " 学号:" << _num << endl; // 999
		cout << " 身份证号:" << Person::_num << endl; // 111
	}
protected:
	int _num = 999; // 学号
};

在这里插入图片描述
接下来继续通过程序来验证一下函数名相同时的情况:

class A
{
public:
	void fun()
	{
		cout << "func()" << endl;
	}
};

class B : public A
{
public:
	void fun(int i)
	{
		cout << "func(int i)->" << i << endl;
	}
};
int main()
{
	B b;
	b.fun(10);
	b.A::fun();
	return 0;
}

在这里插入图片描述
我们先来思考一下问题,这两个函数名相同的函数构成函数重载吗?答案肯定不构成,因为函数重载的前提是在同一作用域,而这里分为父类和子类两个作用域。此处的两个同名函数构成隐藏。

四、派生类的默认成员函数

在前边的类与对象章节学习过,C++类中有六个默认成员函数,但是最常用的是前四个默认成员函数,也是我们要重点学习的。
我们可以认为,子类继承时,对父类的操作都必须把父类成员看成一个整体,会调用父类的相关函数,而子类成员调用子类相关函数,所以可以把自己看做是合成形成的。
在这里插入图片描述
在这里插入图片描述

  1. 默认的构造函数对内置类型不做处理,自定义类型回去调用他们的构造函数,而对于从父类继承的变量,会回去调用他们父类的构造函数。派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
  2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
  3. 派生类的operator=必须要调用基类的operator=完成基类的复制。
  4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能
    保证派生类对象先清理派生类成员再清理基类成员的顺序。
  5. 派生类对象初始化先调用基类构造再调派生类构造。
  6. 派生类对象析构清理先调用派生类析构再调基类的析构。
  7. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同(这个我们后面会讲
    解)。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加
    virtual的情况下,子类析构函数和父类析构函数构成隐藏关系。

(1)派生类的构造函数

class A
{
public:
	A(const string& name)
		//:_name(name)
	{}
	string _name;
};
class B : public A 
{
public:
	B(int age = 18)
		//:A("tmt")
		:_age(age)
	{

	}
	int _age;
};

int main()
{
	B b;
	return 0;
}

在这里插入图片描述
如果父类没有默认构造函数,就必须在子类显式在初始化列表显式调用。

class A
{
public:
	A(const string& name)
		:_name(name)
	{}
	string _name;
};
class B : public A 
{
public:
	B(int age = 18,string name = "tmt")
		:A(name)
		,_age(age)
	{

	}
	int _age;
};

int main()
{
	B b;
	return 0;
}

在这里插入图片描述

在这里插入图片描述
(2)子类的析构函数
子类函数的析构函数,对于自定义类型去调用它的析构函数,对于内置类型不做处理,对于继承的成员回去调用父类的析构函数。但是不用去显式调用父类的析构函数,为了保证父类成员先构造先析构,我们不能去主动析构父类成员,而是在子类的析构函数后自动回去调用父类析构函数。
并且,还有一个细节,就是父子类的析构函数会构成隐藏,虽然我们看到函数名称不同,但是编译器会将他们都处理成constructer,所以构成隐藏,调用时必须加上域作用符。

class Person
{
public:
	//提供了默认构造函数
	Person(const char* name = "peter")
		: _name(name)
	{
		cout << "Person()" << endl;
	}
	//拷贝构造
	Person(const Person& p)
		: _name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
	}
	//赋值重载
	Person& operator=(const Person& p)
	{
		cout << "Person operator=(const Person& p)" << endl;
		if (this != &p)
			_name = p._name;

		return *this;
	}
	//析构函数
	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name; // 姓名
};
//公有继承
class Student : public Person
{
public:
	Student(const char* name ,int num)
		: Person(name)
		, _num(num)
	{
		cout << "Student()" << endl;
	}
	~Student()
	{
		cout << "~Student()" << endl;
	}
protected:
	int _num; //学号
};
void Test()
{
	Student s1("张三",18);
	Student s3("王五",17);
}

在这里插入图片描述
(3)子类的拷贝构造函数

class Person
{
public:
	//提供了默认构造函数
	Person(const char* name = "peter")
		: _name(name)
	{
		cout << "Person()" << endl;
	}
	//拷贝构造
	Person(const Person& p)
		: _name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
	}
	//赋值重载
	Person& operator=(const Person& p)
	{
		cout << "Person operator=(const Person& p)" << endl;
		if (this != &p)
			_name = p._name;

		return *this;
	}
	//析构函数
	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name; // 姓名
};
//公有继承
class Student : public Person
{
public:
	Student(const char* name ,int num)
		: Person(name)
		, _num(num)
	{
		cout << "Student()" << endl;
	}
	Student(const Student& s)
		: Person(s)
		, _num(s._num)
	{
		cout << "Student(const Student& s)" << endl;
	}
	~Student()
	{
		cout << "~Student()" << endl;
	}
protected:
	int _num; //学号
};
void Test()
{
	Student s1("张三",18);
	Student s2(s1);
}

在这里插入图片描述
(4)子类的复制重载函数

class Person
{
public:
	//提供了默认构造函数
	Person(const char* name = "peter")
		: _name(name)
	{
		cout << "Person()" << endl;
	}
	//拷贝构造
	Person(const Person& p)
		: _name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
	}
	//赋值重载
	Person& operator=(const Person& p)
	{
		cout << "Person operator=(const Person& p)" << endl;
		if (this != &p)
			_name = p._name;

		return *this;
	}
	//析构函数
	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name; // 姓名
};
//公有继承
class Student : public Person
{
public:
	Student(const char* name ,int num)
		: Person(name)
		, _num(num)
	{
		cout << "Student()" << endl;
	}
	Student& operator = (const Student& s)
	{
		cout << "Student& operator= (const Student& s)" << endl;
		if (this != &s)
		{
			Person::operator =(s);
			_num = s._num;
		}
		return *this;
	}
	~Student()
	{
		cout << "~Student()" << endl;
	}
protected:
	int _num; //学号
};
void Test()
{
	Student s1("张三",18);
	Student s2("李四",19);
	s2 = s1;
}

在这里插入图片描述
在这里插入图片描述

五、继承与友元

友元不能继承,也就是说基类的友元函数内部不能访问子类的私有和保护成员。

//后边会使用,所以先声明
class Student;
class Person
{
public:
	friend void Display(const Person& p, const Student& s);
protected:
	string _name; // 姓名
};
class Student : public Person
{
protected:
	int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{
	cout << p._name << endl;
	cout << s._stuNum << endl;
}
int main()
{
	Student s;
	Person p;
	Display(p, s);
}

在这里插入图片描述

六、继承与静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子
类,都只有一个static成员实例 。也就是说,即使子类继承了父类的静态成员,静态成员也只有一份,存在静态区,不属于任何一个对象。

下边通过一段程序来验证一下,当我们通过student类来修改人数时,我们来观察person类的count是否会发生变化:

class Person
{
public:
	//调用一次构造,人数加一
	Person() { ++_count; }
protected:
	string _name; // 姓名
public:
	static int _count; // 统计人的个数。
};
int Person::_count = 0;
class Student : public Person
{
protected:
	int _stuNum; // 学号
};
class Graduate : public Student
{
protected:
 string _seminarCourse; // 研究科目
};
void TestPerson()
{
	Student s1;
	Student s2;
	Student s3;
	Graduate s4;
	cout << " 人数 :" << Person::_count << endl;
	Student::_count = 0;
	cout << " 人数 :" << Person::_count << endl;
}

在这里插入图片描述

七、复杂的菱形继承及菱形虚拟继承

1.单继承与多继承

单继承:
一个子类只有一个直接父类的继承方式,叫做单继承。
在这里插入图片描述

多继承:
友两个或两个以上的直接父类的继承方式,叫做多继承。
在这里插入图片描述
菱形继承:
是多继承的一种特殊情况,继承图形好像一个菱形。
在这里插入图片描述

2.菱形继承的问题

但是菱形继承势必会出现许多的问题,当student和teacher类继承了person类的成员后,assistant类再来继承他们,这是person类中的内容就会被继承两份,这肯定会造成代码冗余和二义性的问题。
在这里插入图片描述
为了解决二义性的问题,我们可以通过指定作用域来进行对重复部分的赋值和查看:

	// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
	a.Student::_name = "xxx";
	a.Teacher::_name = "yyy";

但是代码冗余的问题还是没有解决,所以我们要引入虚拟继承。

3.虚拟继承

使用virtual关键字就可以实现虚拟继承,在继承方式前加上virtual关键字,这个继承就成为虚拟继承,但是切记在菱形继承的问题中,应该是在父类为同一个类的两个子类继承时变为虚拟继承。

虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和
Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地
方去使用。

class Person
{
public :
 string _name ; // 姓名
};
class Student : virtual public Person
{
protected :
 int _num ; //学号
};
class Teacher : virtual public Person
{
protected :
 int _id ; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected :
 string _majorCourse ; // 主修课程
};
void Test ()
{
 Assistant a ;
 a._name = "peter";
}

此时再去直接访问就不会报错了,因为只生成一份公有的成员,不论指定哪一个作用域,都是同一份。

4.虚拟继承的模型及原理

我们通过以下的程序通过内存和监视窗口来探究一下,虚继承的模型。

class A
{
public:
	int _a;
};
// class B : public A
class B : virtual public A
{
public:
	int _b;
};
// class C : public A
class C : virtual public A
{
public:
	int _c;
};
class D : public B, public C
{
public:
	int _d;
};
int main()
{
	D d;
	d._a = 1;
	d._a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	return 0;
}

在这里插入图片描述
在这里插入图片描述
通过调试,我们可以很清楚的看到,_a这个共有的成员,首先被存储在了最后的位置,然后再去按顺序存储其他的变量,但是在内存界面,有两个看不懂的东西是什么,其实那是地址,地址存储什么呢?我们再来看看:
在这里插入图片描述
在这里插入图片描述
十六进制的14就是十进制的20,而十六进制的C就是十进制的12,那么他们代表什么意思呢?其实是代表了他们与公有成员的距离。


所以虚拟继承的类,有以下的模型:
在这里插入图片描述
有个上边模型的理解,再来理解每个类的大小,就很容易了:
在这里插入图片描述
在这里插入图片描述
由于虚拟继承的类会存储一个地址,该地址存储有关虚基类的相关内容,所以大小会发生变化。


下图是菱形虚拟继承的内存对象成员模型:这里可以分析出D对象中将A放到的了对象组成的最下
面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?这里是通过了B和C的两个指针,指
向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量
可以找到下面的A。
在这里插入图片描述

八、继承的总结和反思

继承反思

  1. 很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱
    形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设
    计出菱形继承。否则在复杂度及性能上都有问题。
  2. 多继承可以认为是C++的缺陷之一,很多后来的OO语言都没有多继承,如Java。

继承与组合

public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。

例如:我们可以说玫瑰花是一种植物,玫瑰花一定继承了植物的相关属性,他们是一种is-a的关系,还有人和动物也是is-a的关系,但是生活中也有很多has-a的关系,例如车和轮胎的关系就是,车里边有轮胎。
总结:

优先使用对象组合,而不是类继承 。
继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称
为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的
内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很
大的影响。派生类和基类间的依赖关系很强,耦合度高。
对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象
来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复
用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。
组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被
封装。
实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有
些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用
继承,可以用组合,就用组合。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Lychee图床】本地电脑搭建私人图床,公网远程访问

文章目录 1.前言2. Lychee网站搭建2.1. Lychee下载和安装2.2 Lychee网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4.公网访问测试5.结语 转发自CSDN风浪越大%鱼越贵的文章&#xff1a;Lychee图床 - 本地配置属于自己的相册管理系统并远…

快速入手光学字符识别控件Aspose.OCR!如何从PDF中提取文本

Aspose.OCR是一款字符识别组件&#xff0c;它使得开发人员可以添加OCR功能到他们的ASP.NET Web应用程序、web服务和windows应用程序中。它提供了一个简单的类集用于控制字符识别。Aspose.OCR目的是为那些需要在他们自己的应用程序中使用图像&#xff08;BMP和TIFF&#xff09;的…

软件设计师计算机系统知识点笔记大总结

数据寄存器是一个中转站 指令寄存器 ir 保存暂存指令&#xff08;操作码加地址吗等于指令&#xff09; 地址寄存器 保存当前cpu所访问的内存单元地址 程序计数器 保存的是下一条指令的地址 状态寄存器 标志运算的结果 类似 0&#xff08;&#xff09;状态寄存器是运算器中的部件…

Ansible Automation Platform - 用 API 访问 Ansible Automation Platform

《OpenShift / RHEL / DevSecOps 汇总目录》 说明&#xff1a;本文已经在支持 Ansible Automation Platform 2.3 的环境中验证 文章目录 AAP 的 API 索引Host 主机列表 API通过 API 操作 Ansible Automation Platform 说明&#xff1a;以下命令中 mycontroller-aap.apps-crc.t…

论文阅读-17-Deep Long-Tailed Learning: A Survey---3.3 Module Improvement

文章目录 1. Representation Learning1.1 Metric learning(1) large margin local embedding LMLE①##### ②##### ③ (2) Range loss(3) CRL(4) KCL(5) Hybrid(6) PaCo(7) DRO-LT 1.2 Sequential training(1) HFL(2) Unequal-training 1.3 Prototype learning(1) OLTR(2) IEM …

PPT背景图片怎么设置?4个详细教程在这!

案例&#xff1a;PPT背景图片怎么设置&#xff1f; 【因为论文答辩&#xff0c;最近需要制作PPT&#xff0c;昨晚之后感觉有点单调&#xff0c;我想设置一个背景图片&#xff0c;让我的PPT看起来更有风格&#xff0c;请问大家是怎么设置PPT背景图片的呢&#xff1f;】 PPT背景…

容器中的operator[]注意事项

首先看一张表格&#xff0c;支持operator[]的容器包括string、array、vector、deque、map、unordered_map&#xff0c;顺序容器和关联容器的operator[]不太一致。 string中的operator[] 在pos < size()时返回到位于指定位置pos的字符的引用&#xff0c;或在pos size()时…

信息安全数学基础笔记

三个数学难题: 群的定义: 满足乘法结合律&#xff0c;有单位元&#xff0c;逆元即为群&#xff0c;如果同时满足交换律则为交换群 满足乘法结合律&#xff0c;有单位元即为半群&#xff0c;如果同时满足交换律则为交换半群 希尔密码: 其中加密矩阵为n阶一般线性群&#xff0c;…

视频与音频一键同步嘴型数字人ai工具分享

在ai发展的今天,各种虚拟主播工具层出不穷,我们在选择ai工具的适合往往陷入一个使用陷阱。 比如, 看演示视频效果非常不错,自己去用却跟智障一样的。出现这种情况,我们首先不去评价这个工具的好用,我们得分析别人使用的前置条件。 大部分前置条件都是大量的训练数据, …

进程通信和信号量

1.进程通信 管道&#xff1a;包括无名管道(pipe)和命名管道(named pipe),无名管道可用于具有父进程和子进程之间的通信。命名管道除具有管道所具有的所有功能外&#xff0c;它还允许无亲缘关系进程间的通信。消息队列&#xff1a;进程可以向队列中添加消息&#xff0c;其他进程…

代码量原地缩减50%,这个Java工具类库太香了

Guava是google公司开发的一款Java类库扩展工具包&#xff0c;内含了丰富的API&#xff0c;涵盖了集合、缓存、并发、I/O等多个方面。使用这些API一方面可以简化我们代码&#xff0c;使代码更为优雅&#xff0c;另一方面它补充了很多jdk中没有的功能&#xff0c;能让我们开发中更…

长沙银行财报启示录:“生态引擎”如何突破“低维竞争”?

众多行业当中&#xff0c;银行业一直被当做“经济晴雨表”。 今年以来&#xff0c;中国经济开启回暖模式。尤其是在前不久的五一假期&#xff0c;各地消费回暖&#xff0c;释放出经济持续向好的积极信号。此时&#xff0c;也正是各家银行发布成绩单的财报季&#xff0c;能发现…

OPC UA Client接口库

OPC UA库秉承简单、易用、可靠的设计理念&#xff0c;只需少量接口即可实现所需功能&#xff0c;同时使用者无需考虑多线程&#xff0c;时序等问题 1. 拷贝代码文件 将 \JngOpcUaClient\JngOpcUaClient\Input\ 文件夹拷贝到项目中&#xff0c;添加到项目。 2. 拷贝dll库及Secu…

ES+Redis+MySQL,高可用架构设计方案

ES高可用方案 1. ES双中心主备集群架构 全平台所有体系的会员总量是十多亿。在这么大的数据体量下&#xff0c;业务线的查询维度也比较复杂。有的业务线基于手机号&#xff0c;有的基于微信unionid&#xff0c;也有的基于卡号等查询会员信息。这么大的数据量&#xff0c;又有…

Java基础(40)反射机制

Java反射机制是 Java 语言的一个重要特性。先了解两个概念&#xff0c;编译期和运行期。编译期是指把源码交给编译器编译成计算机可以执行的文件的过程。在 Java 中也就是把 Java 代码编成 class 文件的过程。编译期只是做了一些翻译功能&#xff0c;并没有把代码放在内存中运行…

平平无奇的Python为什么能一跃成为世界排名第一的语言

一、前言 本文将结合个人经历为各位同学客观的分析是否有学习Python的必要、Python适合谁学、为什么要学&#xff0c;希望能够给看到此文章的同学一点建议&#xff0c;树立学习目标&#xff0c;让学习有结果。 读完后&#xff0c;相信你一定能够有所收获。 二、简述个人经历…

Word控件Spire.Doc 【文本框】教程(4):如何将图像插入文本框

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下&#xff0c;轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具&#xff0c;专注于创建、编辑、转…

swift闭包底层本质

swift学习笔记 闭包底层原理 1、函数赋值给一个变量 func getFn(_ a: Int) -> Int {return a 1 }let funcVar getFn当在把一个函数赋值给一个变量funcVar的时候&#xff0c;funcVar变量会存储两个东西 funcVar总共占用16个字节前八个字节存储着getFn的函数地址后八个字…

浅聊一下NTP

浅聊一下NTP 仅了解&#xff0c;没实践过NTP 文章目录 浅聊一下NTP1.什么是NTP2.基本原理3.工作模式1.单播服务器/客户端模式2.对等体模式3.广播模式4.组播模式 4.NTP数据报文 1.什么是NTP 网络时间协议NTP&#xff08;Network Time Protocol&#xff09;是TCP/IP协议族里面…

PowerShell系列(三):PowerShell发展历程梳理

目录 1、PowerShell 1.0 版本特性 2、PowerShell 2.0 版本特性 3、PowerShell 3.0 版本特性 4、PowerShell 4.0 版本特性 5、PowerShell 5.0 版本特性 6、PowerShell 5.1 版本特性 7、PowerShell6.0 Core 版本特性 8、PowerShell7.0 Core 版本特性 今天给大家聊…