C++的继承基础和虚继承原理
1.继承概念
“继承”是面向对象语言的三大特性之一(封装、继承、多态)。
继承(inheritance
)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性基础上进行扩展,增加功能,这样产生新的类,称为“派生类/子类”。
继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。之前我们接触的复用都是函数复用,继承是类设计层次的复用。
2.继承使用
继承发生在“基类/父类”和“派生类/子类”之间,语法形式就是:
class 父类名
{//方法//属性
};
class 子类名 : 继承方式 父类
{//...
};
那这个继承方式又是什么呢?在字面上和我们之前看到的public
、protected
、private
是一样的,但是意义有所不同。继承方式和访问限定符可以相互组合,在父子类中有以下组合:
类成员/继承方式 | public 继承 | protected 继承 | private 继承 |
---|---|---|---|
子类的public 成员 | 子类中仍是public 成员 | 子类中变成protected 成员 | 子类中变成private 成员 |
子类的protected 成员 | 子类中仍是protected 成员 | 子类中仍是protected 成员 | 子类中变成private 成员 |
子类的private 成员 | 子类中不可见,该成员为父类私有 | 子类中不可见,该成员为父类私有 | 子类中不可见,该成员为父类私有 |
实际上规律也简单:公有>保护>私有
,在两个关键字中取小即可,有点类似权限缩小的感觉,当然:您可以选择调用父类的函数来无视这些限定(也就是间接访问)。
下面我们就以“人Person
”父类来生成子类“学生Student
”和“老师Teacher
”:
class Person//父类
{
public: void Print() { cout << "name:" << _name << endl; cout << "age:" << _age << endl; }
protected: string _name = "peter";//姓名 int _age = 18;//年龄
};
//继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分class Student : public Person//继承子类
{
protected: int _stuid;//学号
};
class Teacher : public Person
{
protected:int _jobid;//工号
};int main()
{Student s;Teacher t;s.Print();t.Print();return 0;
}
注意
1
:实际应用中继承方式大多使用public
,还有类内的访问限定符也大多使用public
和protected
。其他的继承方式和类内的访问限定符基本上都很少用,并且有很多时候都不推荐使用。注意
2
:如果不写继承方式,那么默认继承方式是private
(如果是结构体struct
就默认public
),但是最好还是显式写出继承方式为好。注意
3
:父类的private
成员在子类中无论以什么方式继承都是不可见的。这里的不可见是指父类的私有成员被继承到了子类对象中,但是语法上限制子类不管在类里还是类外都不能去访问它。#include <iostream> #include <string> using namespace std; class Person//父类 { public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;} private:string _name = "peter";//姓名 int _age = 18;//年龄 }; //继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分class Student : public Person//继承子类 {/*void function(){_name = "student";//失败_age = 12;//失败}*/ };//不可以直接在类内使用从父类继承来的private变量,包括子类创建出来的对象(不可见),但是依旧可以被Print()使用 class Teacher : public Person {/*void function(){_name = "teacher";//失败_age = 20;//失败}*/ };//不可以直接在类内使用从父类继承来的private变量,包括子类创建出来的对象(不可见),但是依旧可以被Print()使用int main() {Student s;Teacher t;/* 失败s._name;s._age;t._name;t._age;*/s.Print();t.Print();return 0; }
注意
4
:由于父类的private
成员在子类中是不能被访问,如果子类成员不想在类外直接被访问,但需要在子类中被访问,就使用限定符为protected
即可。从这点也可以看出,保护成员限定符是因为继承才出现的。#include <iostream> #include <string> using namespace std; class Person//父类 { public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;} protected:string _name = "peter";//姓名 int _age = 18;//年龄 };class Student : public Person//继承子类 { public:void function(){_name = "student";//成功_age = 12;//失败} }; class Teacher : public Person { public:void function(){_name = "teacher";//成功_age = 20;//失败} };int main() {Student s;Teacher t;/* 失败语句s._name = "student";s._age = 12;t._name = "teacher";t._age = 20;*/s.function();t.function();s.Print();t.Print();return 0; }
3.切片赋值
公有类继承的子类可以直接赋值给父类,这个过程形象的说法叫做“切割/切片”,也有叫“赋值兼容转化”的。这个过程是纯天然的,没有隐式类型转化的发生(没有临时变量的产生,类似int a = 1; int b = 10; a = b
的过程),而这种“切片”的现象实际上在指针和引用上也有体现。
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "peter";int _age = 18;
};class Student : public Person
{
protected:int _stuid;
};class Teacher : public Person
{
protected:int _jobid;
};int main()
{//1.普通类型double a = 1.1;int b = a;//发生类型转化const int& ra = a;//中间会产生临时变量int* pa = (int*)&a;//2.子类切片给父类Student std;Person per = std;//没有发生类型转化,只是将子类中的成员变量拷贝给父类Person& rper = std;//没有产生临时变量,并且使用这一特性就好像给子类做了“切片”,rp本身还是子类,但是却只引用/切出了子类中继承自父类的那一部分成员(因此无需加上const)Person* pper = &std;//同理指针也是做了一些“切片”,指针解引用只能看到子类从父类哪里继承的那一部分成员//3.父类赋值给子类//std = per;//不允许//Student& rstd = per;//不允许//Student* pstd = &per;//不允许/* 这里我还暂时理解不了Student& rstd = (Student)per;//不允许pper = &std;Student* pstd = (Student*)pper;//允许pper = &per;pstd = (Student*)pper;//允许,但是有可能会越界*/return 0;
}
那反过来可以么?一般是不可以的,父类不可以直接赋值给子类,因为容易造成子类的属性缺失,有可能会出现问题。
补充:父类的指针和引用可以通过“强制类型转化”赋值给子类的指针和引用,但是必须是父类的指针是指向子类对象时才是安全的(这里的父类如果是多态的,就可以使用
RTTI(Run-Time Type Information)
的dynamic cast
来进行识别,然后进行安全转换,这点我们以后再来细说)。
4.隐藏父类
-
在继承中父类和子类都有自己独立的作用域
-
子类和父类有同名成员,但是子类将屏蔽父对类同名对象的直接访问,这种情况就叫“隐藏/重定义”
-
函数也有类似“隐藏”的情况,注意需要和“重载区分(在同一个作用域)”,只需要函数名相同就可以达到“隐藏父类成员函数”
-
尽可能不使用同名的成员构成隐藏,不然有的时候会给自己挖坑…
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "peter";int _age = 18;
};class Student : public Person
{
protected:string _name = "student";//父类的_name被子类Student隐藏了int _stuid;
public:void Print()//父类的Print()被子类Student隐藏了{cout << "name:" << _name << endl;cout << "age:" << _age << endl;//_age没有被子类隐藏,依旧打印从父类继承过来的_age}
};class Teacher : public Person
{
protected:string _name = "teacher";//父类的_name被子类Teacher隐藏了int _jobid;
public:void Print()//父类的Print()被子类Teacher隐藏了{cout << "name:" << _name << endl;cout << "age:" << _age << endl;//_age没有被子类隐藏,依旧打印从父类继承过来的_age}
};int main()
{Student s;s.Print();Teacher t;t.Print();return 0;
}
但是想要直接访问父类中被子类隐藏的成员怎么办呢?使用作用域解析运算符即可,因为父类的成员只是被隐藏而不是不存在了。
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "peter";int _age = 18;
};class Student : public Person
{
protected:string _name = "student";//父类的_name被子类Student隐藏了int _stuid;
public:void Print()//父类的Print()被子类Student隐藏了{cout << "name:" << _name << endl;cout << "age:" << _age << endl;//_age没有被子类隐藏,依旧打印父类的_age}
};class Teacher : public Person
{
protected:string _name = "teacher";//父类的_name被子类Teacher隐藏了int _jobid;
public:void Print()//父类的Print()被子类Teacher隐藏了{cout << "name:" << _name << endl;cout << "age:" << _age << endl;//_age没有被子类隐藏,依旧打印父类的_age}
};int main()
{Student s;s.Print();Teacher t;t.Print();s.Person::Print();t.Person::Print();return 0;
}
5.子类成员
-
子类的构造函数必须调用父类的构造函数初始化继承自父类的那一部分成员,而不能自己在子类中直接初始化。
如果父类有默认构造函数,哪怕用户不写也会在子类构造函数的初始化列表处,自动调用父类的默认构造函数。
如果父类没有默认构造函数,则用户必须在子类构造函数的初始化列表阶段显示调用。因此,子类对象初始化先调用父类构造函数再调用子类构造函数。
-
子类的析构函数会在被调用完成后自动调用基类的析构函数清理父类成员。因为这样才能 保证派生类对象先清理派生类成员再清理基类成员的顺序,避免出现:用户自己调用父类的析构函数,父类成员先被释放了,但是子类依旧可以使用父类的成员,造成越界访问的现象。
因此在子类的析构函数内用户不能自己显式调用父类的析构函数,否则会重复调用。
另外,编译器会对析构函数名进行特殊处理,处理成
destrutor()
,所以父类析构函数不加virtual
的情况下,子类析构函数和父类析构函数构成隐藏关系。后续一些场景中,析构函数需要构成重写(重写的条件之一是函数名相同) -
子类的拷贝构造函数必须调用父类的拷贝构造完成继承自父类的那一部分成员的拷贝初始化。但是父类的拷贝构造函数需要传递一个父类对象过去,如何把继承于父类的成员变量拿出来作为父类对象传给父类的拷贝构造函数呢?使用切片即可,这就是切片的实际应用
-
子类的
operator=
必须要调用父类的operator=
完成继承自父类的那一部分成员的复制,同理也是使用切片注意:子类是没有办法直接隐藏父类的特殊成员函数的,这也体现了成员函数的特殊性。当然,这除去了析构函数的特殊情况。
-
使用父类的成员函数最好一定要带上父类和作用域解析运算符,否则就有可能因为隐藏而出现自己调用自己的情况,就会变成无穷递归(例如:下述代码中子类的
Person::Print()
调用)。
#include <iostream>
#include <string>
using namespace std;
//1.父类Person
class Person
{
public:Person(string name, int age) : _name(name), _age(age)//不是默认的构造函数{cout << "Person(string nume, int age) : _name(\"peter\"), _age(18)" << endl;}~Person(){cout << "~Person()" << endl;}Person(Person& p)//不是默认的拷贝构造函数{cout << "Person(Person& p)" << endl;_name = p._name;_age = p._age;}Person& operator=(const Person& p)//不是默认的赋值重载函数{cout << "Person operator=(const Person& p)" << endl;if (this != &p){_name = p._name;_age = p._age;}return *this;}void Print(){cout << "Print()" << endl;cout << "name:" << _name << " age:" << _age << endl;}
protected:string _name;int _age;
};//2.子类Student
class Student : public Person
{
public:Student(string name, int age, int stuid = 00000000) : Person(name, age), _stuid(stuid)//这里调用显示调用父类的构造函数(并且没有明确指定父类),初始化子类中从父类继承过来的成员,然后才初始化子类自己的成员{cout << "Student(string name, int age, int stuid = 00000000) : Person(name, age), _stuid(stuid)" << endl;}~Student(){cout << "~Student()" << endl;//无需自己调用父类函数的析构函数,编译器会自己帮助我们调用}Student(Student& s) : Person(s), _stuid(s._stuid)//这里使用了切片显式调用父类的拷贝构造函数(可以选择不指定父类),初学者推荐一直显式调用,有助于代码理解和可读性。{cout << "Student(Student& s) : Person(s), _stuid(s._stuid)" << endl;}Student& operator=(const Student& p){cout << "Student& operator=(const Student& p)" << endl;if (this != &p){Person::operator=(p);_stuid = p._stuid;}return *this;}void Print()//构成成员函数隐藏了{//Print();//这就变成自己调用自己了,此时就不可以不明确父类了Person::Print();//指明父类cout << "stuid:" << _stuid << endl;}
protected:int _stuid;
};
int main()
{Student s1("limou", 18, 22130800);Student s2(s1);Student s3("daimou", 22, 22109988);s1 = s3;s1.Print();s2.Print();s3.Print();return 0;
}
6.不可继承
6.1.友元关系
友元关系是不会被继承的,也就是说,如果父类有一个友元函数,那么这个友元函数只可以使用父类的成员变量,而不可以使用从父类继承过来的子类的内部成员变量。
#include <iostream>
#include <string>
using namespace std;class Student;//这句是声明,告诉编译器有一个类的存在,让下面在Person内的函数可以通过编译
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()
{Person p;Student s;Display(p, s);return 0;
}
这种情况下只能再定义一个友元关系给子类。
#include <iostream>
#include <string>
using namespace std;class Student;//这句是声明,告诉编译器有一个类的存在,让下面在Person内的函数可以通过编译
class Person
{
public:friend void Display(const Person& p, const Student& s);
protected:string _name;//姓名
};
class Student : public Person
{friend void Display(const Person& p, const Student& s);
protected:int _stuNum;//学号
};
void Display(const Person& p, const Student& s)
{cout << p._name << endl;cout << s._stuNum << endl;//该语句不正确
}int main()
{Person p;Student s;Display(p, s);return 0;
}
6.2 .静态成员
静态成员变量属于整个类,不仅仅属于父类,也属于子类,因此不能说子类继承静态成员变量,这么说是不准确的。
#include <iostream>
#include <string>
using namespace std;
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;Graduate s3;Graduate s4;cout << "人数:" << Student::_count << endl;Person::_count = 0;cout << "人数:" << Graduate::_count << endl;
}
int main()
{TestPerson();return 0;
}
也就是说父类中使用的静态成员变量和子类的静态成员变量是一样的(不过这其实也侧面说明,从父类中的成员变量和子类中的成员变量只是拷贝关系,各自的地址是不一样的)。
7.虚拟继承
7.1.不可能棱形
我之前提到的继承都是“单个父类继承给多个子类”的“单继承”,但是C++
在开发之初为了更加符合现实中“多个父类继承个单个子类”的现象,设计了“多继承”。
但是没有想到因此出现了一个新的问题:棱形继承,具有“二义性”和”数据冗余“的两大缺陷。棱形继承是多继承的一种特殊情况,其内容为:”假设有一个父类,其有两个子类,而两个子类又通过多继承得出一个子子类,这个子子类创建出来的对象会从两个子类继承,那么会出现相同的成员,也就是二义性(不知道使用的是哪一个子类的成员)和数据冗余(有可能出现重复相同的成员变量)“。
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:Person(): _name("peter"), _age(18){cout << "Person()" << endl;}
public:string _name;int _age;
};class Student : public Person
{
public:Student():Person(), _stuid(22103){}
public:int _stuid;
};class Teacher : public Person
{
public:Teacher():Person(), _jobid(88903){}
protected:int _jobid;
};
class Graduate : public Student, public Teacher//多继承
{
protected:int _graid;
};int main()
{Graduate g;//cout << g._name << endl;//运行失败,具有二义性cout << g.Student::_name << endl;//运行成功,成功解决二义性cout << g.Teacher::_name << endl;//运行成功,成功解决二义性return 0;
}
二义性我们尚且可以通过作用域解析运算符来解决,但是数据冗余还需要使用virtual
关键字才能解决。该关键字加在两个子类的继承方式处,但是也因此变得很复杂…
补充:因此多继承会让代码变得复杂,所以
Java
直接移除了多继承的功能。
7.2.虚继承操作
为什么数据冗余使用virtual
关键字就能解决呢?我们来查看一下底层。
这里我们最好不要查看编译器的调试窗口,而是使用内存窗口(因为调试窗口为了方便展示又可能做了一些优化,比如:使用内联函数,有些编译器的调试器会让您觉得这是在调用函数)。
7.2.1.不加关键字
#include <iostream>
#include <string>
using namespace std;
class A//父类A
{
public:int _a;
};class B : public A//子类B
{
public:int _b;
};class C : public A//子类C
{
public:int _c;
};class D : public B, public C//多继承后的子子类D
{
public:int _d;
};int main()
{D d;d.B::_a = 1;d.C::_a = 2;d._b = 3;//或者d.B::_b = 3;d._c = 4;//或者d.C::_c = 4;d._d = 5;return 0;
}
可以看到,每一个变量都存储在不同的地址上。
7.2.2.加上关键字
#include <iostream>
#include <string>
using namespace std;
class A//父类A
{
public:int _a;
};class B : virtual public A//子类B
{
public:int _b;
};class C : virtual public A//子类C
{
public:int _c;
};class D : public B, public C//多继承后的子子类D
{
public:int _d;
};int main()
{D d;d.B::_a = 1;d.C::_a = 2;d._b = 3;//或者d.B::_b = 3;d._c = 4;//或者d.C::_c = 4;d._d = 5;return 0;
}
因此我们成功解决了二义性和数据冗余的问题,需要注意的是,虚拟继承最好不要在其他地方去使用。
7.3.虚继承原理
通过上面的调试我们发现,在解决二义性的时候,好像D
从B
、C
、A
处继承的_a
成员是在一个地址上的变量?这个应该怎么理解呢?让我们通过调试来理解虚继承的原理。
将B
和C
和D
中同属于A
的部分变成一个公共部分,在继承自B
和C
的部分中多出两个指针,各自指向一块表空间,这些表内存储了指针偏移量信息。利用指针偏移量和表指针的地址即可找到一块公共空间,消除二义性。
那为什么是指针指向一个偏移量表,而不是直接使用指针指向公共空间呢?这些后面多态再来讨论,指针固然能解决这个问题,但是使用偏移量还能再进一步解决多态的问题…
这样子子类在使用从两个子类继承过来的变量的时候,实际上是同一个变量,解决了冗余性和二义性。
既然棱形继承这么复杂,那应该会很少使用吧?实际上您有可能天天用这,iostream
类就是通过istream
类和ostream
类棱形继承来的…
注意:上述两图来源于:Reference - C++ Reference (cplusplus.com)和输入/输出库 - cppreference.com,感兴趣您可以去查看一下。
下面有一道结合了虚拟继承的面试题目供您思考一下,让您更加了解虚继承和初始化列表的初始化顺序。
#include <iostream>
#include <string>
using namespace std;
class A
{
public:A(const char* s){cout << s << endl;}~A() {}
};class B : virtual public A
{
public:B(const char* s1, const char* s2): A(s1){cout << s2 << endl;}
};class C : virtual public A
{
public:C(const char* s1, const char* s2): A(s1){cout << s2 << endl;}
};
class D : public B, public C//多继承
{
public:D(const char* s1, const char* s2, const char* s3, const char* s4): B(s1, s2), C(s1, s3), A(s1){cout << s4 << endl;}
};int main()
{D* p = new D("class A", "class B", "class C", "class D");//按照初始化列表的根据谁先定义先初始化的原则://先调用A(s1),然后调用B(s1, s2),然后调用C(s1, s3),然后打印"class D"//也就是先调用A(s1),然后打印"class B",然后调用A(s1),然后打印"class C",然后调用A(s1),然后打印"class D"//由于在D中从B和C中继承过来的A的成员是公共部分,因此只会调用一次构造函数A(s1),并且这个构造函数A(s1)是在D中被调用的//于是得到:A、B、C、Ddelete p;return 0;
}
8.继承/组合
继承和组合都是一种复用,以下是继承和组合的区别(不过这里只讨论公有方式的继承):
//继承is-a
class A1
{};
class B1 : public A1
{};//组合has-a
class A2
{};
class B2
{
private:A2 _a;
};
那么这个is-a
和has-a
,是什么呢?一个是“是一个(也就是继承)”的关系,一个是“拥有一个(也就是组合)”的关系。
从“关联关系/耦合度”上来看继承会更高,B1
可以直接使用A1
的所有成员,但是B2
只能使用A2
的函数成员,间接调用其他的A2
成员(因此对象组合也是一种黑箱/黑盒风格)。
但在软件设计中有一种设计理念就是“低耦合、高内聚”,因此在实践中,应该尽可能去用组合,这样的代码维护性更好(具体还要看实际)。
- 如果是为了实现多态,那就必须使用继承(多态我们后面再谈)
- 如果描述类之间的关系时,继承和组合都可以,那么应该尽量使用组合
相关文章:
C++的继承基础和虚继承原理
1.继承概念 “继承”是面向对象语言的三大特性之一(封装、继承、多态)。 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性基础上进行扩展,增加功能&…...
第三章:最新版零基础学习 PYTHON 教程(第十三节 - Python 运算符—Python 中的运算符函数 - 套装2)
Python 中的运算符函数 - 套装1 本文将讨论更多功能。 1. setitem(ob, pos, val):- 该函数用于在容器中的 特定位置分配值。操作 – ob[pos] = val 2. delitem(ob, pos):- 该函数用于删除容器中 特定位置的值。 操作 – del ob[pos] 3. getitem(ob, pos)&#x...
Linux网络编程:详解https协议
目录 一. https协议概述 二. 中间人截获 三. 常见的加密方法 3.1 对称加密 3.2 非对称加密 四. 数据摘要和数据签名的概念 五. https不同加密方式的安全性的探究 5.1 使用对称加密 5.2 使用非对称加密 5.3 非对称加密和对称加密配合使用 六. CA认证 七. 总结 一.…...
LLVM IR 文档 专门解释 LLVM IR
https://llvm.org/docs/LangRef.html#phi-instruction...
免费服务器搭建网盘教程,给电脑挂载500G磁盘
免费服务器搭建网盘教程,给电脑挂载500G磁盘 请勿注册下载,注册下载是空白文件,使用免登录下载 免费搭建网盘教程,给电脑挂载500G磁盘 其他按照下载教程操作教程代码: 下载下来的文件pancn 文件拖到您创建的容器 手机的话点击…...
【Java】微服务——Nacos配置管理(统一配置管理热更新配置共享Nacos集群搭建)
目录 1.统一配置管理1.1.在nacos中添加配置文件1.2.从微服务拉取配置1.3总结 2.配置热更新2.1.方式一2.2.方式二2.3总结 3.配置共享1)添加一个环境共享配置2)在user-service中读取共享配置3)运行两个UserApplication,使用不同的pr…...
QT基础入门——信号和槽机制(二)
前言: 在Qt中,有一种回调技术的替代方法:那就是信号和槽机制。当特定事件发生时,会发出一个信号。Qt的小部件中有许多预定义的信号,但我们可以将小部件子类化,向它们添加自定义的信号。槽是响应特定信号的…...
黑豹程序员-架构师学习路线图-百科:JavaScript-网页三剑客
文章目录 1、为什么需要JavaScript2、发展历史3、什么是JavaScript3.1、JavaScript介绍3.2、JavaScript内部结构3.3、主要功能 4、TypeScript 1、为什么需要JavaScript 前面我们已经了解了网页三剑客的HTML和CSS,已经明确了它们的职责。 HTML负责页面的展现&#x…...
三、互联网技术——IP子网划分
文章目录 一、IP地址基础1.1 IP地址分类1.2 网络掩码/子网掩码 二、子网划分VLSM2.1 为什么要进行子网划分2.2 怎么进行子网划分2.3 子网划分原理2.4 例题一2.5 例题二2.6 例题三2.6 例题四2.7 例题五2.8 例题六2.9 例题七2.10 例题八 三、无类域间路由CIDR3.1 例题一3.2 例题二…...
TinyWebServer学习笔记-log
为什么服务器要有一个日志系统? 故障排查和调试: 在服务器运行期间,可能会发生各种问题和故障,例如程序崩溃、性能下降、异常请求等。日志记录了服务器的运行状态、错误信息和各种操作,这些日志可以用来快速定位和排查…...
【kubernetes】CRI OCI
1 OCI OCI(Open Container Initiative):由Linux基金会主导,主要包含容器镜像规范和容器运行时规范: Image Specification(image-spec)Runtime Specification(runtime-spec)runC image-spec定义了镜像的格式,镜像的格式有以下几…...
竞赛 机器视觉opencv答题卡识别系统
0 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 答题卡识别系统 - opencv python 图像识别 该项目较为新颖,适合作为竞赛课题方向,学长非常推荐! 🥇学长这里给一个题目综合评分(每项满分5分…...
Youtube视频下载工具分享-油管视频,音乐,字幕下载方法汇总
YouTube视频下载方法简介 互联网上存在很多 YouTube 下载工具,但我们经常会发现自己收藏的工具没过多久就会失效,我们为大家整理的这几种方法,是存在时间较久并且亲测可用的。后续如果这些工具失效或者有更好的工具,我们也会分享…...
【算法练习Day11】滑动窗口最大值前 K 个高频元素
📝个人主页:Sherry的成长之路 🏠学习社区:Sherry的成长之路(个人社区) 📖专栏链接:练题 🎯长路漫漫浩浩,万事皆有期待 文章目录 滑动窗口最大值前 K 个高频…...
华为云HECS云服务器docker环境下安装nginx
前提:有一台华为云服务器。 华为云HECS云服务器,安装docker环境,查看如下文章。 华为云HECS安装docker-CSDN博客 一、拉取镜像 下载最新版Nginx镜像 (其实此命令就等同于 : docker pull nginx:latest ) docker pull nginx查看镜像 dock…...
GET 和 POST的区别
GET 和 POST 是 HTTP 请求的两种基本方法,要说它们的区别,接触过 WEB 开发的人都能说出一二。 最直观的区别就是 GET 把参数包含在 URL 中,POST 通过 request body 传递参数。 你可能自己写过无数个 GET 和 POST 请求,或者已经看…...
机器学习(监督学习)笔记
目录 总览笔记内容线性回归梯度下降特征缩放多输出线性回归 逻辑回归二分类与逻辑回归分类任务的性能指标(召回率,精度,F1分数等)支持向量机SVMK近邻朴素贝叶斯分类器朴素贝叶斯分类器进阶多分类逻辑回归二分类神经网络多分类神经…...
科普rabbitmq,rocketmq,kafka三者的架构比较
对比 架构对比 从架构可以看出三者有些类似,但是在细节上有很多不同。下面我们就从它们的各个组件,介绍它们: RabbitMQ,是一种开源的消息队列中间件。下面是RabbitMQ中与其相关的几个概念: 1.生产者(P…...
加密货币交易技巧——地利(二)
EMA指标 针对资金体量大的代币,做现货交易或低倍合约,可参考以下指标: 1.指标介绍:EMA,移动平均线指标,这里只分享中长线用法,非常实用且准确率超高 2.适用群体:适用于现货或低倍…...
服务网关Gateway_微服务中的应用
没有服务网关 问题: 地址太多安全性管理问题 为什么要使用服务网关 网关是微服务架构中不可或缺的部分。使用网关后,客户端和微服务之间的网络结构如下。 注意: 网关统一向外部系统(如访问者、服务)提供REST API。在Sp…...
2G大小的GPU对深度学习的加速效果如何?
训练数据情况 总共42776张224*224*3张图片 Found 42776 files belonging to 9 classes. Using 12833 files for training. 模型参数情况 Total params: 10,917,385 Trainable params: 10,860,745 Non-trainable params: 56,640 batch-size:12 GPU信息 NVIDIA GeForce GT 7…...
intel 一些偏门汇编指令总结
intel 汇编手册下载链接:https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html LDS指令: 手册中可以找到 位于 3-588 根据手册内容猜测:lds r16 m16:16 的作用,是把位于 [m16:16] 内存地址的数…...
python 多个proto文件import引用时出现ModuleNotFoundError错误
问题描述 my_proto文件夹里有两个proto文件,book.proto想要引用person.proto文件中的Person,如下 book.proto syntax "proto2";import "person.proto"; // 导入person.proto文件message Book {optional string name 1;optional …...
C语言图书管理系统
一、 系统概述 图书管理系统是一个用C语言编写的软件系统,旨在帮助图书馆或图书机构管理其图书馆藏书和读者信息。该系统提供了一套完整的功能,包括图书录入、借阅管理、归还管理、读者管理、图书查询、统计报表等。 二、 系统功能 2.1 图书录入 管理…...
归并排序及其非递归实现
个人主页:Lei宝啊 愿所有美好如期而遇 目录 归并排序递归实现 归并排序非递归实现 归并排序递归实现 图示: 代码: 先分再归并,像是后序一般。 //归并排序 void MergeSort(int* arr, int left, int right) {int* temp (int…...
【kubernetes】kubernetes中的Controller
1 什么是Controller? kubernetes采用了声明式API,与声明式API相对应的是命令式API: 声明式API:用户只需要告诉期望达到的结果,系统自动去完成用户的期望命令式API:用户需要关注过程,通过命令一…...
RabbitMQ-死信队列
接上文 RabbitMQ-java使用消息队列 1 死信队列简介 死信队列模式实际上本质是一个死信交换机绑定的死信队列,当正常队列的消息被判定为死信时,会被发送到对应的死信交换机,然后再通过交换机发送到死信队列中,死信队列也有对应的消…...
ElasticSearch - 基于 DSL 、JavaRestClient 实现数据聚合
目录 一、数据聚合 1.1、基本概念 1.1.1、聚合分类 1.1.2、特点 1.2、DSL 实现 Bucket 聚合 1.2.1、Bucket 聚合基础语法 1.2.2、Bucket 聚合结果排序 1.2.3、Bucket 聚合限定范围 1.3、DSL 实现 Metrics 聚合 1.4、基于 JavaRestClient 实现聚合 1.4.1、组装请求 …...
什么是数学建模(mooc笔记)
什么是数学建模 前提:我们数学建模国赛计划选择C题,故希望老师的教学中侧重与C题相关性大的模型及其思想进行培训。之后的学习内容中希望涉及以下知识点: logistic回归相关知识点。如:用法、适用、限制范围等。精学数学建模中常…...
基于SpringBoot的流浪动物管理系
基于SpringBoot的流浪动物管理系的设计与实现,前后端分离 开发语言:Java数据库:MySQL技术:SpringBootMyBatisVue工具:IDEA/Ecilpse、Navicat、Maven 系统展示 首页 后台登陆界面 管理员界面 摘要 基于Spring Boot的…...
误给传销公司做网站算犯罪吗/营销策划培训
1.网络层使用的中间设备叫路由器,其传输单元是____。 (1) 分组;数据报;Packet;数据包 2.网络层向其上一层运输层可以提供的两种服务为____和虚电路。 (1) 数据报;数据报服务;无连接的数据报服务;无连接的数…...
做动态网站的素材怎么收集/百度集团股份有限公司
align"absmiddle"//图标绝对居中方式line-height: 25px; //行高vertical-align:middle// 图片文字垂直居中a:link,定义正常链接的样式;a:visited,定义已访问过链接的样式;a:hover,定义鼠标悬浮在链接上时的样…...
wordpress插件开发教程/提升关键词
让我们先由2个问题引出今天的话题,第一,为什么选择做测试?第二,做测试的发展又如何?第一个问题,你为什么要选择做测试,我敢说十个人有九个不会说实话,什么测试能够让我开阔视野啦&am…...
大型网站建设公司 北京/广告接单网站
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼n2;//换方下子}else{chess[i][j]2;setfillcolor(BLACK); setfillstyle(BLACK);fillellipse(m.x-7,m.y-7,m.x7,m.y7);if(Game_Over1(chess,2)1||Game_Over2(chess,2)1||Game_Over3(chess,2)1) {setcolor(BLACK);outtextxy(200,200,&…...
网站建设应考虑哪些方面的问题/长沙大型网站建设公司
现象: 现在大多数java开发者使用的IDE都是eclipse,而且一般在一个workspace中,会有很多个项目,有些是open的,有些是关闭的。有时候eclipse在使用过程中会莫名其妙的异常中断关闭,再打开eclipse,…...
重庆建网站推广价格/电商推广平台有哪些
题目描述: 1、2、3… …n这n(0<n<1000000000)个数中有多少个数可以被正整数b整除。 输入输入包含多组数据每组数据占一行,每行给出两个正整数n、b。输出输出每组数据相应的结果。样例输入2 1 5 3 10 4样例输出2 1 2 #include<stdio.h>int ma…...