【C++进阶】一、继承(总)
目录
一、继承的概念及定义
1.1 继承概念
1.2 继承定义
1.3 继承基类成员访问方式的变化
二、基类和派生类对象赋值转换
三、继承中的作用域
四、派生类的默认成员函数
五、继承与友元
六、继承与静态成员
七、菱形继承及菱形虚拟继承
7.1 继承的分类
7.2 菱形虚拟继承
7.3 菱形虚拟继承原理
八、继承总结
前言:面向对象三大特性是:封装、继承、多态,封装初阶的时候已经讲了,进阶开始讲解继承和多态和一些更复杂的结构,今天的篇章是讲解继承
一、继承的概念及定义
1.1 继承概念
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。
继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用
比如,一个学生管理系统,里面有不同的角色,如学生、老师、管理者等等,每个角色都要定义一个类:
class Student
{string _name;string _tel;string _address;int _age;// ...string _stuID; // 学号
};class Teacher
{string _name;string _tel;string _address;int _age;// ...string _wordID; // 工号
};...
不难发现其存在大量冗余的代码,有些信息是共有的,有些信息是每个角色独有的,要对这些共有的代码进行复用就要使用 “继承”,下面使用继承展示一下:
// 把大家共有的东西写进来
class Person
{
public:string _name;string _tel;string _address;string _age;
};//Student类 继承了 Person类
class Student : public Person
{string _stuID; // 学号
};
//Teacher类 继承了 Person类
class Teacher : public Person
{string _wordID; // 工号
};
Student 和 Teacher 类就是使用了继承,继承于 Person 类,下面开始进行讲解继承
1.2 继承定义
以上面的代码为例,Student类和 Teacher类继承了 Person类,Person类称为基类,也叫父类,而 Student类和 Teacher类 则称为派生类,也叫子类
要让一个子类进行继承父类,需要在子类的类的类名后加上冒号,并跟上继承方式和父类类名即可,比如上面的子类 Student
class Student : public Person
冒号右边的 public 是子类进行继承的继承方式,Person 则是父类的类名

初阶的时候已经学过,访问限定符有以下三种
- public(公有访问)
- protected(保护访问)
- private(私有访问)
public 修饰的成员变量,可以在类外面直接访问,protected 和 private 修饰的成员变量,不能在类外访问,但可以在类里面进行访问
继承方式也有三种:
- public(公有继承)
- protected(保护继承)
- private(私有继承)

这三个不仅能当访问限定符,也能当继承方式
1.3 继承基类成员访问方式的变化
基类当中被不同访问限定符修饰的成员,以不同的继承方式继承到派生类当中后,该成员最终在派生类当中的访问方式将会发生变化,如下图

(1)基类 private 成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它,如果进行访问会直接报错
测试代码
//基类
class Person
{
private:string _name;
};//派生类
class Student : public Person
{
public:void Print(){//在派生类当中访问基类的private成员,error!cout << _name << endl;}
protected:string _stuID; // 学号
};

(2)因此,基类的 private 成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就需要定义为 protected,由此可以看出,protected 限定符是因继承才出现的
(3)实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected> private
比如,基类的成员变量是 protected 访问,派生类进行继承时继承方式如果是 public 继承,那派生类对基继承,继承下来的成员变量的访问方式就是 protected,因为 public > protected> private,访问方式取小的那个(min),public > protected,min为protected
(4)使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式
//基类
class Person
{
private:string _name;
};//派生类
class Student : Person //class不写访问方式,默认为 private,但建议还是写出
{
protected:string _stuID; // 学号
};
(5)在实际运用中一般使用都是 public 继承,几乎很少使用 protetced/private 继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强
所以常用的就两个,其他基本不会用到,大佬设计的时候设计过多了,没有考虑实际会用到的

二、基类和派生类对象赋值转换
派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用
测试代码
//基类
class Person
{
protected:string _name;string _age;string _sex;
};//派生类
class Student : public Person
{
public:string _stuID; // 学号
};void Test()
{Student s;// 1.子类对象可以赋值给父类对象/指针/引用Person p = s; //派生类对象赋值给基类对象Person* pp = &s; //派生类对象赋值给基类指针Person& rp = s; //派生类对象赋值给基类引用
}
这里有个形象的说法叫切片或者切割,寓意把派生类中父类那部分切来赋值过去
派生类对象赋值给基类对象:

派生类对象赋值给基类指针:

派生类对象赋值给基类引用;

注意:基类对象不能赋值给派生类对象
//基类
class Person
{
protected:string _name;string _age;string _sex;
};//派生类
class Student : public Person
{
public:string _stuID; // 学号
};void Test()
{//2.基类对象不能赋值给派生类对象Student s;Person p; s = p;//error
}

基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用 RTTI(RunTime Type Information)的 dynamic_cast 来进行识别后进行安全转换。(这个后面再讲解,这里先了解一下)
测试代码,基类和派生类依旧是上面的
void Test()
{//3.基类的指针可以通过强制类型转换赋值给派生类的指针Student s;Person p;Person* pp;pp = &s;Student * ps1 = (Student*)pp; // 这种情况转换时可以的ps1->_stuID = 10;pp = &p;Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问ps1->_stuID = 10;
}
三、继承中的作用域
在继承体系中基类和派生类都有独立的作用域。子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
测试代码
// Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆
class Person
{
protected:string _name = "张三"; // 姓名int _num = 111111; // 身份证号
};
class Student : public Person
{
public:void Print(){cout << "姓名: " << _name << endl;cout << "学号: " << _num << endl;cout << "身份证号 :" << _num << endl;}
protected:int _num = 222; // 学号
};
void Test()
{Student s1;s1.Print();
};
运行结果

如果要使用基类里面的 _num,可以使用 基类::基类成员 显示访问,修改代码:

下面看同名函数的隐藏
测试代码
// B中的fun和A中的fun不是构成重载,因为不是在同一作用域
// B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
class A
{
public:void fun(){cout << "func()" << endl;}
};
class B : public A
{
public:void fun(int i){A::fun();cout << "func(int i)->" << i << endl;}
};
void Test()
{B b;b.fun(10);
};
运行结果

如果 fun不传参数就会报错,因为B中的 fun和A中的 fun构成隐藏,无参的 fun 调不到
void Test()
{B b;b.fun();
};

注意:需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏,所以在实际中在继承体系里面最好不要定义同名的成员
四、派生类的默认成员函数
默认成员函数,即我们不写编译器会自动生成的函数,类当中的默认成员函数有以下六个:

最后两个基本不使用,对于前四个(构造和析构,拷贝和赋值重载),普通类 默认生成的的四个成员函数:构造函数和析构函数对于内置类型不做处理,自定义类型则调用对应的构造函数和析构函数;
拷贝构造函数和赋值重载函数对于内置类型进行浅拷贝(值拷贝),对于自定义类型则调用对于的拷贝构造和赋值重载函数

对于派生类,除了内置类型和自定义类型外,还多了基类对象,对于基类对象则调用基类对应的函数完成初始化、清理、拷贝

派生类当中的默认成员函数,与普通类的默认成员函数的不同之处:
- 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用
- 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化
- 派生类的 operator= 必须要调用基类的 operator= 完成基类的复制
- 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序
- 派生类对象初始化先调用基类构造再调派生类构造
- 派生类对象析构清理先调用派生类析构再调基类的析构
- 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同(这个我们后面会讲解)。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系
(1)对于派生类的构造函数
//基类
class Person
{
public://构造函数Person(const string& name = "peter"):_name(name){cout << "Person()" << endl;}
private:string _name; //姓名
};//派生类
class Student : public Person
{
public://构造函数Student(const string& name, int id):Person(name) //调用基类的构造函数初始化基类的那一部分成员, _id(id) //初始化派生类的成员{cout << "Student()" << endl;}
private:int _id; //学号
};int main()
{Student s("zhangsan", 1111);return 0;
}

(2)对于派生类的拷贝构造函数
//基类
class Person
{
public://构造函数Person(const string& name = "peter"):_name(name){}//拷贝构造函数Person(const Person& p):_name(p._name){cout << "Person(const Person& p)" << endl;}
private:string _name; //姓名
};//派生类
class Student : public Person
{
public://构造函数Student(const string& name, int id):Person(name) , _id(id) {}//拷贝构造函数Student(const Student& s):Person(s) //调用基类的拷贝构造函数完成基类成员的拷贝构造, _id(s._id) //拷贝构造派生类的成员{cout << "Student(const Student& s)" << endl;}
private:int _id; //学号
};int main()
{Student s("zhangsan", 1111);Student s2(s);return 0;
}

(3)对于派生类的赋值重载函数
//基类
class Person
{
public://构造函数Person(const string& name = "peter"):_name(name){}//赋值运算符重载函数Person& operator=(const Person& p){cout << "Person& operator=(const Person& p)" << endl;if (this != &p){_name = p._name;}return *this;}
private:string _name; //姓名
};//派生类
class Student : public Person
{
public://构造函数Student(const string& name, int id):Person(name), _id(id){}//赋值运算符重载函数Student& operator=(const Student& s){cout << "Student& operator=(const Student& s)" << endl;if (this != &s){Person::operator=(s); //调用基类的operator=完成基类成员的赋值_id = s._id; //完成派生类成员的赋值}return *this;}
private:int _id; //学号
};int main()
{Student s1("zhangsan", 1111);Student s2("xioahong", 2222);s2 = s1;return 0;
}

(4)对于派生类的析构函数,派生类析构先子后父,派生类对象的析构清理是先调用派生类析构再调基类析构。派生类析构函数完成后会自动调用基类的析构函数,所以不需要我们显式调用
//基类
class Person
{
public://构造函数Person(const string& name = "peter"):_name(name){}//析构函数~Person(){cout << "~Person()" << endl;}
private:string _name; //姓名
};//派生类
class Student : public Person
{
public://构造函数Student(const string& name, int id):Person(name), _id(id){}//析构函数~Student(){cout << "~Student()" << endl;//派生类的析构函数会在被调用完成后自动调用基类的析构函数}
private:int _id; //学号
};int main()
{Student s1("zhangsan", 1111);return 0;
}

五、继承与友元
友元关系不能继承,也就是说基类的友元可以访问基类的私有和保护成员,但是不能访问派生类的私有和保护成员
class Student;
class Person
{
public://声明Display是Person的友元friend void Display(const Person& p, const Student& s);
protected:string _name; //姓名
};
class Student : public Person
{
protected:int _id; //学号
};
void Display(const Person& p, const Student& s)
{cout << p._name << endl; //可以访问cout << s._id << endl; //无法访问
}
int main()
{Person p;Student s;Display(p, s);return 0;
}

六、继承与静态成员
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例
测试代码
//基类
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;//4Student::_count = 0;cout << " 人数 :" << Person::_count << endl;//0
}int main()
{TestPerson();return 0;
}

七、菱形继承及菱形虚拟继承
7.1 继承的分类
单继承:一个子类只有一个直接父类时称这个继承关系为单继承

多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承

菱形继承:菱形继承是多继承的一种特殊情况

多继承本身没有问题,但多继承形成的菱形继承就有问题,从上面的菱形继承的模型构造就可以看出,菱形继承的继承方式存在数据冗余和二义性的问题
例如,对于上面菱形继承的模型,对于菱形继承 Assistant类,实例化出一个对象后,访问成员时就会出现二义性问题
//基类
class Person
{
public:string _name; // 姓名
};
//派生类
class Student : public Person
{
protected:int _num; //学号
};
//派生类
class Teacher : public Person
{
protected:int _id; // 职工编号
};
//菱形继承
class Assistant : public Student, public Teacher
{
protected:string _majorCourse; // 主修课程
};
void Test()
{// 这样会有二义性无法明确知道访问的是哪一个Assistant a;a._name = "peter";
}

Assistant 对象是多继承的 Student 和 Teacher,而 Student 和 Teacher 当中都继承了 Person,因此 Student 和 Teacher 当中都有 _name 成员,若是直接访问 Assistant 对象的 _name 成员会出现访问不明确的报错
对于此,可以显示指定访问 Assistant 哪个父类的 _name 成员,二义性解决了,但是数据冗余无法解决
void Test()
{Assistant a;// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决a.Student::_name = "xxx";a.Teacher::_name = "yyy";
}
菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在Assistant的对象中Person成员会有两份

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

测试代码
//基类
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";//编译通过
}
7.3 菱形虚拟继承原理
在此之前,我们先看虚拟继承,有A、B、C、D四个类,B、C继承A,D继承B、C,也就是菱形继承

测试的代码
//基类
class A
{
public:int _a;
};
//派生类
class B : public A
{
public:int _b;
};
//派生类
class C : public A
{
public:int _c;
};
//菱形继承
class D : public B, public C
{
public:int _d;
};
int main()
{D d;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;return 0;
}
进行调试,通过内存窗口查看,注意这里不要通过监视窗口查看,监视窗口被编译器优化过了,不好看

查看如下

从内存窗口可以看出 d对象的分布情况 ,D类对象 d当中各个成员在内存当中的分布情况如下:

通过这里就可以看出为什么菱形继承导致了数据冗余和二义性,根本原因就是 D类对象当中含有两个 _a 成员
下面,看菱形虚拟继承

测试代码
//基类
class A
{
public:int _a;
};
//派生类
class B : virtual public A
{
public:int _b;
};
//派生类
class C : virtual public A
{
public:int _c;
};
//菱形继承
class D : public B, public C
{
public:int _d;
};
int main()
{D d;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;return 0;
}
调试内存窗口查看

其中D类对象当中的 _a 成员被放到了最后,而在原来存放两个 _a 成员的位置变成了两个指针,这两个指针叫虚基表指针,它们分别指向一个虚基表。虚基表中存的偏移量,通过偏移量可以找到下面的A
虚基表中包含两个数据,第一个数据(全为0的)是为多态的虚表预留的存偏移量的位置(暂时不理会),第二个数据就是当前类对象位置距离公共虚基类的偏移量(由于VS是小端,地址需要成对的倒着读,比如 14 00 00 00 ,读的话就是0x00 00 00 14,十进制就是20)

也就是说,这两个指针经过一系列的计算,最终都可以找到成员 _a

八、继承总结
很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承,否则在复杂度及性能上都有问题。
多继承可以认为是C++的缺陷之一,很多后来的OO语言都没有多继承,如Java
继承和组合
- public 继承是一种 is-a 的关系。也就是说每个派生类对象都是一个基类对象
- 组合是一种 has-a 的关系。假设B组合了A,每个B对象中都有一个A对象
- 优先使用对象组合,而不是类继承
例如,车类和宝马类就是 is-a 的关系,它们之间适合使用继承
class Car
{
protected:string _colour; //颜色string _num; //车牌号
};
class BMW : public Car
{
public:void Drive(){cout << "this is BMW" << endl;}
};
而车和轮胎之间就是 has-a 的关系,它们之间则适合使用组合
class Tire
{
protected:string _brand; //品牌size_t _size; //尺寸
};
class Car
{
protected:string _colour; //颜色string _num; //车牌号Tire _t; //轮胎
};
若是两个类之间既可以看作is-a的关系,又可以看作has-a的关系,则优先使用组合
原因:
- 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。
- 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。
- 组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。
- 实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合
笔试面试题

上面已有解释,不再解释
----------------我是分割线---------------
文章到这里就结束了,下一篇即将更新
相关文章:
【C++进阶】一、继承(总)
目录 一、继承的概念及定义 1.1 继承概念 1.2 继承定义 1.3 继承基类成员访问方式的变化 二、基类和派生类对象赋值转换 三、继承中的作用域 四、派生类的默认成员函数 五、继承与友元 六、继承与静态成员 七、菱形继承及菱形虚拟继承 7.1 继承的分类 7.2 菱形虚拟…...
AttributeError: module ‘lib‘ has no attribute ‘OpenSSL_add_all_algorithms
pip安装crackmapexec后,运行crackmapexec 遇到报错 AttributeError: module lib has no attribute OpenSSL_add_all_algorithms 直接安装 pip3 install crackmapexec 解决 通过 python3 -m pip install --upgrade openssl 或者 python3 -m pip install openssl>22.1.…...
Python实现视频自动打码功能,避免看到羞羞的画面
前言 嗨呀嗨呀,最近重温了一档综艺节目 至于叫什么 这里就不细说了 老是看着看着就会看到一堆马赛克,由于太好奇了就找了一下原因,结果是因为某艺人塌房了…虽然但是 看综艺的时候满影响美观的 咳咳,这里我可不是来教你们如何解…...
说说Knife4j
Knife4j是一款基于Swagger2的在线API文档框架使用Knife4j, 需要 添加Knife4j的依赖当前建议使用的Knife4j版本, 只适用于Spring Boot2.6以下版本, 不含Spring Boot2.6 在主配置文件(application.yml)中开启Knife4j的增强模式必须在主配置文件中进行配置, 不要配置在个性化配置文…...
Java学习笔记-03(API阶段-2)集合
集合 我们接下来要学习的内容是Java基础中一个很重要的部分:集合 1. Collection接口 1.1 前言 Java语言的java.util包中提供了一些集合类,这些集合类又称之为容器 提到容器不难想到数组,集合类与数组最主要的不同之处是,数组的长度是固定的,集合的长度是可变的&a…...
「3」线性代数(期末复习)
🚀🚀🚀大家觉不错的话,就恳求大家点点关注,点点小爱心,指点指点🚀🚀🚀 矩阵的秩 定义4:在mxn矩阵A中,任取k行与k列(k<m,k<n),位…...
【CSDN竞赛】27期题解(Javascript)
前言 本来排名是20的,不过第一题有点输出bug,最后实际测出来又重新排名,刚好卡在第10。但是考试报告好像过了12小时就下载不到了,所以就只写题目求解的JS函数吧。 1. 幸运数字 小艺定义一个幸运数字的标准包含3条: 仅包含4或7幸…...
高压放大器在骨的逆力电研究中的应用
实验名称:高压放大器在骨的逆力电研究中的应用研究方向:生物医学测试目的:骨中的胶原和羟基磷灰石沿厚度分布不均匀,骨试样在直流电压作用下,内部出现传导电流引起试样内部温度升高,不同组分热变形不一致&a…...
思科网络部署,(0基础)入门实验,超详细
♥️作者:小刘在C站 ♥️个人主页:小刘主页 ♥️每天分享云计算网络运维课堂笔记,努力不一定有收获,但一定会有收获加油!一起努力,共赴美好人生! ♥️夕阳下,是最美的绽放࿰…...
private static final Long serialVersionUID= 1L详解
我们知道在对数据进行传输时,需要将其进行序列化,在Java中实现序列化的方式也很简单,可以直接通过实现Serializable接口。但是我们经常也会看到下面接这一行代码,private static final Long serialVersionUID 1L;这段代…...
若依前后端分离版集成nacos
根据公司要求,需要将项目集成到nacos中,当前项目是基于若依前后端分离版开发的,若依的版本为3.8.3,若依框架中整合的springBoot版本为2.5.14。Nacos核心提供两个功能:服务注册与发现,动态配置管理。 一、服…...
JAVA面试八股文一(mysql)
B-Tree和BTree区别共同点;一个节点可以有多个元素, 排好序的不同点:BTree叶子节点之间有指针,非叶子节点之间的数据都冗余了一份在叶子节点BTree是B-Tree 的升级mysql什么情况设置了索引,但无法使用a.没符合最左原则b.…...
动静态库概念及创建
注意在库中不能写main()函数。 复习gcc指令 预处理-E-> xx.i 编译 -S-> xx.s 汇编 -c-> xx.o 汇编得到的 xx.o称为目标可重定向二进制文件,此时的文件需要把第三方库链接进来才变成可执行程序。 gcc -o mymath main.c myadd.c mysub.c得到的mymath可以执…...
【H.264】码流解析 annexb vs avcc
H264码流解析及NALUAVCC和ANNEXB 前者是FLV容器、mp4 常用的。后者 是实时传输使用,所以是TS 一类的标准。VLC显示AVC1就是AVCC AVCC格式 也叫AVC1格式,MPEG-4格式,字节对齐,因此也叫Byte-Stream Format。用于mp4/flv/mkv, VideoToolbox。 – Annex-B格式 也叫MPEG-2 trans…...
【最优化方法】1-最优化方法介绍
文章目录1 最优化起源2 最优化发展3 运筹学在国外4 运筹学在国内5 什么是最优化?6 为什么要研究最优化问题?7 最优化问题8 最优化问题分类9 最优化研究内容理论算法应用1 最优化起源 中国古代优化思想–田忌赛马(公元前340年) 18世纪L.Euler࿰…...
数据结构 | 树 | 二叉树
🔥Go for it!🔥 📝个人主页:按键难防 📫 如果文章知识点有错误的地方,请指正!和大家一起学习,一起进步👀 📖系列专栏:数据结构与算法 ὒ…...
笔记:使用 unbuild 搭建 JavaScript 构建系统笔记
使用 unbuild 搭建 JavaScript 构建系统jcLee95:https://blog.csdn.net/qq_28550263?spm1001.2101.3001.5343 邮箱 :291148484163.com 简介: 本文是笔者阅读分析 elementPlus 项目时记录的。该项目用到了一个完全没有文档和资料的工具 unbu…...
【SpringBoot3.0源码】启动流程源码解析 •下
文章目录初始化DefaultBootstrapContext开启Headless模式获取监听器并启动封装命令行参数准备环境打印Banner创建上下文容器预初始化上下文容器刷新Spring容器打印启动时间发布事件执行特定的run方法上一篇《【SpringBoot3.0源码】启动流程源码解析 • 上》,主要讲解…...
QT(56)-动态链接库-windows-导出变量-导出类
1.导出变量 1.1不使用_declspec(dllimport) _declspec(dllexport) 使用_declspec(dllimport) _declspec(dllexport) 1.2win32 mydllwin32 myexe 1.3win32 mydllqt myexe 2.导出类 使用_declspec(dllimport) _declspec(dllexport) 2.1不用关键…...
TCP传输文件
传输文件和传输信息的区别: 传输信息,只是一条数据,传输文件是多条数据传输信息传输过去一般都会显示,传输文件一般不会显示,一般只是存放在文件中传输文件需要传输,文件大小和文件名称(不然不知…...
YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...
跨链模式:多链互操作架构与性能扩展方案
跨链模式:多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈:模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展(H2Cross架构): 适配层…...
微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...
自然语言处理——Transformer
自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效,它能挖掘数据中的时序信息以及语义信息,但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN,但是…...
JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
图表类系列各种样式PPT模版分享
图标图表系列PPT模版,柱状图PPT模版,线状图PPT模版,折线图PPT模版,饼状图PPT模版,雷达图PPT模版,树状图PPT模版 图表类系列各种样式PPT模版分享:图表系列PPT模板https://pan.quark.cn/s/20d40aa…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
