【C++学习】继承
🐱作者:一只大喵咪1201
🐱专栏:《C++学习》
🔥格言:你只管努力,剩下的交给时间!
C++是面向对象的编程语言,它有很多的特性,但是最重要的就是封装,继承,多态三大特性,封装本喵就不介绍了,前面我们一直都在使用,这里本喵来详细介绍继承。
继承
- 🙀继承的概念和定义
- 😸继承关系和访问限定符
- 🙀基类和派生类的赋值转换
- 😸特性
- 🙀继承中的作用域
- 🙀派生类的默认成员函数
- 😸构造函数
- 😸拷贝构造函数
- 😸赋值运算符重载函数
- 😸析构函数
- 🙀继承与友元
- 🙀继承与静态成员
- 🙀菱形继承
- 😸多继承
- 😸虚拟继承
- 🙀继承和组合
- 🙀总结
🙀继承的概念和定义
- 继承:是面向对象程序设计使代码可以复用的最重要手段,它运行程序员在保持原有类特性的基础上进程扩展。
- 继承是类设计层次的复用。
如上图,这几个类都是在描述人扮演的不同角色,分别是学生,老师,其他职业的人。
每一个角色在描述的时候都有自己特有的属性,学生有学号班级,老师有工号和所教科目,其他职业的人也有工号和具体的岗位。
- 但是不同的角色之间也有共同的属性,比如姓名,性别,年龄。
- 将那些无论扮演什么角色都必须有的属性看作是人共有的属性。
此时,在使用不同类型的类描述不同的角色时,都需要复用到人的属性。为了方便,创建一个Person的类型来描述人,在描述不同角色创建新的类时,只需要增加Person这个成员即可,然后再添加各自的属性。
下面3个蓝色框中的类复用红色框Person这个类的过程就叫做继承。
具体到代码上来看:
class Person
{
public:void Print(){cout << _name << endl;cout << _age << endl;}
protected:string _name;//姓名int _age;//年龄
};//继承
class Student : public Person
{
protected:int _stuid;//学号
};
Student类继承了Person类。
- Student:称为基类,也叫做父类。
- Person:称为派生类,也叫做子类。
- public:是继承方式。
在创建了Student对象后,该对象中不仅有class Student中的_stuid成员,还有class Person中的_name,_age成员,以及class Person中的Print()成员函数。
- 基类中的一切都被派生类继承了下来。
😸继承关系和访问限定符
访问限定符之前本喵讲解过,继承方式也是有这三种。使用不同继承方式继承不同权限的基类成员,排列组合后共有9中继承结果。
类成员\继承方式 | public继承 | protected继承 | private继承 |
---|---|---|---|
基类的public成员 | 派生类的public成员 | 派生类的protected成员 | 派生类的private成员 |
基类的protected成员 | 派生类的protected成员 | 派生类的protected成员 | 派生类的private成员 |
基类的private成员 | 在派生类中不可见 | 在派生类中不可见 | 在派生类中不可见 |
看起来非常复杂,但是有规律可循。
基类中的private成员:
- Person中是private成员。
- 在Student继承Person的时候,分别采用public,protected,private三种继承方式。
在调试窗口中可以看到,每个派生类对象中是有基类成员的,但是基类成员的前面有个小锁(途中可能看不清),表示派生类中的基类成员无法访问。
派生类对象在访问基类成员的时候,编译器会报不可访问的错误。
基类中的private成员无论以什么方式继承都是不可见的。
- 这里的不可见是指:基类的成员仍然被继承到了派生类中,但是在语法上不让派生类去访问。
- 无论是在派生类的内部访问还是外部访问,都是不可以的。
基类中的protceted成员:
- Person中是protected成员。
- 在Student继承Person的时候,分别采用public,protected,private三种继承方式。
在调试窗口中可以看到,每个派生类中是有基类成员的,而且没有小锁,说明此时在派生类中的基类成员是可见的,也就是可以访问的。
- 用派生类的成员函数是可以访问基类中的成员的。
- 在派生类的外部访问基类成员会报错。
无论什么继承方式,在派生类的内部是可以访问基类中的保护成员的,但是在派生类的外部不能访问基类的保护成员。
基类中的public成员:
- Person中是public成员。
- 在Student继承Person的时候,分别采用public,protected,private三种继承方式。
在调试窗口中可以看到,派生类中有基类的成员,并且没有小锁,说明是可见的。
- 派生类内部都可以访问基类的成员。
- 派生类外部:
共有继承的公有成员:派生类外部可以访问基类成员。
保护继承的公有成员:派生类外部不可以访问基类成员。
私有继承的公有成员:派生类外部不可以访问基类成员。
无论哪种继承方式,派生类内部都可以访问基类公有成员,保护继承和私有继承,派生类只能在内部访问基类成员。
规律总结:
在刚学习类和对象的时候,本喵说过将访问限定符private和protected暂时看成是一样的,现在就可以介绍他俩的区别了。
三个访问限定符的访问权限大小关系如下:
- 访问权限:public > protected > private
继承关系和访问限定符的组合可以分为两大类:
- 基类中是private成员:无论哪种继承方式,基类成员对于派生类都是不可见的,不可访问。
- 基类中的其他权限成员:继承方式和基类成员访问限定符二者取权限小的作为派生类中成员的权限。
例如,基类中是protected成员,使用public继承方式,那么继承下来的基类成员在派生类中的访问权限就是protected。而此时protected就和private是一样了,在类的内部可以访问,在类的外部无法访问。
再例如,基类中的public成员,使用private继承方式,那么继承下来的基类成员在派生类中的访问限定符就是private。
可以看出,protected访问限定符就是因为继承才出现的。所以,protected和private的区别在继承中才得以体现。
- 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承。
因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。
- 继承方式可以省略不写,采用默认继承方式:
- class定义的类,内部如果不写访问限定符,成员的默认权限是private。
- class定义的派生类,如果不写继承方式,默认的继承方式也是private。
- struct定义的类,内部如果不写访问限定符,所有成员的默认访问权限是public。
- struct定义的派生类,如果不写继承方式,默认的继承方式也是public。
不过,最好还是显示写出继承方式。
🙀基类和派生类的赋值转换
- 派生类对象可以赋值给基类的对象/基类的指针/基类的引用。
class Person
{
public:void Print(){}
protected:string _name;//姓名string _sex;//性别int _age;//年龄
};class Student : public Person
{
public:int _No;
};
基类和派生类如上。
可以看到,派生类对象赋值给基类的对象/基类的指针/基类的引用全部都可以。
派生类赋值给基类的原理如上图所示。派生类中基类有的变量保留,其余的舍弃,_No就是派生类特有的,所以在赋值的时候会舍弃。
- 这里有个形象的说法叫切片或者切割。寓意把派生类中基类那部分切来赋值过去。
😸特性
- 派生类对象赋值给基类对象不存在类型转换。
double类型的变量赋值给int&就会报错,这是因为:
- 类型转换时,会产生中间变量
- double值会先赋给int类型的临时变量,再将临时变量的值赋给int类型的变量。
上面代码中,int&变量是临时变量的引用,而临时变量具有常性,此时相当于权限放大了,所以会报错。
在引用变量前加const就可以解决这个问题。
而派生类对象赋值给基类的引用时就没有这个问题,可以之间赋值,所有说派生类对象赋值给基类对象不存在类型转换。
没有类型转换可以很大程度上节省系统的开销。
注意: 基类对象不能赋值给派生类对象。
🙀继承中的作用域
class Person
{
protected:string _name = "张三";int _num = 150;
};class Student : public Person
{
public:void Printf(){cout << "姓名:" << _name << endl;cout << "身份证号:" << _num << endl;cout << "学号:" << _num << endl;}
protected:int _num = 370;
};
基类和派生类中都有变量_num,在派生类的成员函数中,既想打印基类中的_num,也想打印派生类中的_num。
- 派生类对象中,既有自己的_num,也有基类中的_num。
- 在使用的时候,直接使用_num时,发现使用的是派生类中的_num。
- 派生类中的_num和基类中的_num属于不同作用域。
- 默认情况下,使用的是派生类中的_num。
当基类和派生类中的成员变量名相同时,基类中的成员变量会被隐藏,也叫做重定义。
- 要想使用被隐藏的基类中成员变量,需要在变量前加上域名和域作用限定符(显示访问)。
基类和派生类中各有一个成员函数,而且成员函数名相同。此时这俩个函数之间构成的关系是隐藏/重定义,而不是函数重载。
- 函数重载的前提是:同名函数在同一个作用域中。
此时基类和派生类中的同名函数显然不在同一个作用域,所以不能构成重载,同样是隐藏关系。
默认情况下同样调用的是派生类中的成员函数。
要想使用基类中被隐藏的成员函数,也是需要加域名和域作用限定符(显示访问)。
注意:
- 如果是成员函数的隐藏,只需要函数名相同就构成隐藏,因为作用域不同,不构成重载。
- 在继承体系里面最好不要定义同名的成员。
🙀派生类的默认成员函数
先回顾一下普通类的默认成员函数:
派生类同样只看四个默认成员函数。
😸构造函数
class Person
{
public:Person(const char* name = "张三"):_name(name){cout << "Person(const char* name = \"张三\")" << endl;}
protected:string _name;
};class Student : public Person
{
protected:int _num;//学号
};
Person有显示定义的默认构造函数,Student没有显示定义的默认构造函数。
在创建派生类对象的时候,发现基类的默认构造函数被调用了。
在派生类中显示定义默认构造函数,在创建派生类对象的时候,发现先调用了基类的默认构造函数,再调用了派生类的默认构造函数。
我们知道,派生类中继承了基类中的成员,那么能不能在派生类的构造函数中去初始化基类的成员呢?
此时就报错了,说明派生类的构造函数不能直接去初始化基类的成员。
但是可以在派生类的构造函数中显示调用基类的构造函数来初始化基类。
当基类中没有默认构造函数时,必须在派生类的构造函数中显示调用基类的构造函数进行初始化。
结论:
- 派生类对象在创建的时候,先调用基类的构造函数来初始化基类在派生类中的成员,再调用派生类的构造函数初始化自己的成员。
- 派生类只能通过显示调用基类的构造函数来控制基类初始化,不能直接去初始化从基类中继承下来的成员。
- 如果基类中没有默认构造函数,派生类必须在构造函数中显示调用基类的构造函数,并且传值。
派生类相比于普通类的构造函数,多了一步对基类成员的处理。
😸拷贝构造函数
class Person
{
public:Person(){}//默认构造函数//拷贝构造函数Person(const Person& p):_name(p._name){cout << "Person(const Person& p)" << endl;}
protected:string _name;
};class Student : public Person
{
public:Student(){}//默认构造函数//拷贝构造函数Student(const Student& s):_num(s._num), Person(s){cout << "Student(const Student& s)" << endl;}
protected:int _num;//学号
};
拷贝构造函数是构造函数的重载,所以它们的特性几乎是一样的。
- 派生类的拷贝构造函数先调用基类的拷贝构造函数。
- 派生类的拷贝构造函数不能直接处理基类的成员,必须显示调用基类的拷贝构造函数。
- 派生类的拷贝构造函数在显示调用基类的拷贝构造函数时,传的值是派生类对象。
- 基类的拷贝构造函数的形参是基类对象。
- 类型不同,但是没有发生类型转换,而是发生了切割。
😸赋值运算符重载函数
class Person
{
public:Person& operator=(const Person& p){cout << "Person& operator==(const Person& p)" << endl;if (this != &p){_name = p._name;}return *this;}
protected:string _name;
};class Student : public Person
{
public:Student& operator=(const Student& s){cout << "Student& operator=(const Student& s)" << endl;if (this != &s){Person::operator=(s);_num = s._num;}return *this;}
protected:int _num;//学号
};
派生类的赋值运算符重载函数中,调用基类的赋值运算符重载函数赋值从基类继承下来的那部分成员,然后再初始化自己的、这里同样发生了派生对象给基类对象赋值时的切割现象。
- 由于基类的运算符重载函数是在派生类的运算符重载函数内部调用的,所以在给派生类对象赋值时,会先调用派生类的,在派生类运算符重载函数中再调用基类的。
- 同样不可以在派生类的运算符重载函数中自行处理基类的成员,必须使用基类的运算符重载函数去处理。
基类和派生类的运算符重载函数构造了隐藏/重定义。
在派生类中调用基类的operator=()时,必须指明作用域,否则会默认调用派生类的,此时就会造成栈溢出。
😸析构函数
按照之前几个默认成员函数的做法,在派生类的析构函数中显示调用基类的析构函数,但是发现基类的析构函数一共调用了两次。
这显然是不行的,一块动态空间只能被释放一次。
class Person
{
public:~Person(){cout << "~Person()" << endl;}
protected:string _name;
};class Student : public Person
{
public:~Student(){cout << "~Student()" << endl;}
protected:int _num;//学号
};
在派生类的析构函数中没有显示调用基类的析构函数,发现父类和子类的析构函数各调用了一次。
- 析构函数第一怪:派生类析构函数会自动调用基类析构函数,这是必然发生的,所以无需显示调用基类的析构函数。
- 先调用派生类的析构函数,再调用基类的析构函数。
- 这样做是为了保证先清理派生类成员再清理基类成员。
- 析构函数第二怪:派生类析构函数和基类析构函数构成隐藏关系。(由于多态关系需求,所有析构函数都会特殊处理成destruct函数名,以后会讲解)。
派生类相比于普通类的四类默认成员函数,多了一步对基类成员的处理,而且只能通过基类的默认成员函数去处理,不能由派生类自行处理。
上图表示了派生类和基类对象的行为。
🙀继承与友元
定义一个Display函数,它是基类的友元函数,可以访问基类内部的保护成员。
- 由于基类中的友元声明中包含派生类,但是编译器只会向上寻找,所以必须在友元声明之前加上派生类的声明。
- 否则会报Student未声明的错误。
在调用基类的友元函数访问派生类中的成员时报错了,不让访问。因为:友元关系不继承。
- 若想让基类中的友元也成为派生类中的友元,需要在派生类中也进行友元声明。
注意: 一般不建议使用友元,因为它会破坏类的封装。
🙀继承与静态成员
class Person
{
public:int _Pnum = 1;static int _cout;
};int Person::_cout = 0;class Student : public Person
{
public:int _Snum = 1;//学号
};
基类中有一个int类型的变量,有一个static变量,派生类中自己有一个int类型的变量。
- 创建两个对象,一个基类对象,一个派生类对象。
- 派生类会继承基类中的成员,所以基类对象和派生类对象中都有成员变量_Pnum。
两个对象创建后,分别将基类对象和派生类对象中的_Pnum打印出来,发现它们的值是一样的。
- 将基类对象中的_Pnum加1,然后打印出来,发现值加1。
- 将派生类对象中的_Pnum加1,然后打印出来,发现值加1。
- 基类对象和派生类对象中的_Pnum中的值是相互独立的,也就是有两个值。
- 派生类同样会继承基类中的静态变量。
- 基类对象中对静态变量加1,发现值加1.
- 派生类对象中对静态变量加1,发现值相对于刚创建时加了2.
也就是说,基类对象对静态变量的值加1,同样作用到了派生类对象中的静态变量上。
- 它们两个对象中的静态变量不是独立的。
在基类对象和派生类对象中的静态变量是同一个变量。
因为静态变量同样存放在数据段,基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。
🙀菱形继承
😸多继承
首先需要知道的是单继承:一个子类只有一个直接父类时称这个继承关系为单继承。
如上图,虽然最后的子类中有不止一个父类中的成员,但是每个子类都只有一个父类。
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承。
如上图,子类只有一个,但是父类有两个,这种就属于多继承。
- 多继承中,一个子类可以有多个父类。
但是由于多继承的存在,就会引起菱形继承的问题。
- Student继承自Person,Teacher也继承自Person。
- Assistant继承自Student和Teacher。
class Person
{
public:string _name;//姓名
};class Student : public Person
{
public:int _num;//学号
};class Teacher : public Person
{
public:int _id;//工号
};class Assistant : public Student, public Teacher
{
public:string _majorCourse;
};
在设计上,菱形继承完全是合理的,学生和老师属于人,所以继承人的属性,而助教既是老师也是学生,所以继承老师和学生的属性。
但是菱形继承会存在问题。
从上面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在Assistant的对象中Person成员会有两份。
数据冗余:
可以看到,Assistant中有两个_name成员变量,一个是继承自Student的,一个是继承自Teaher的,但是都是继承自Person的。
- 也就是相同的值存在了两份,实际上只有一个就够用了。
二义性:
如上图所示,在访问_name的时候,编译器也不知道你要访问的是Student作用域中的还是Teacher作用域中的,所以就造成了二义性。
二义性的解决办法之一:
可以通过指定作用域的方法来解决二义性的问题,如上图所示,但是并不符合实际情况,一个人虽然有多种角色,但是名字怎么会有两个甚至多个呢?
😸虚拟继承
虚拟继承就是专门用来解决菱形继承导致的数据冗余和二义性问题的。
- 在腰部位置使用虚拟继承(virtual)。
哪里造成了菱形继承的问题就在哪里使用虚拟继承,毫无疑问,是在腰部位置造成的,所以腰部的两个派生类都使用虚拟继承。
下面本喵来给大家分析一下,虚拟继承是如果解决数据冗余和二义性问题的。
为了方便分析,我们创建几个简单的类:
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;
};
上面代码菱形继承的关系如上图所示。
数据冗余和二义性的解决:
首先我们来看,不使用虚拟继承时的内存模型:
在D对象创建后,通过内存窗口来看它内部的成员分别情况,如上图所示。
- 最外边的蓝色框是整个d对象,它一共有5个int类型的变量。
- 中间的红色框中的成员是从B中继承下来的,有两个int类型的变量。
- 中间的律师框中的成员是从C中继承下来的,有两个int类型的变量。
- 红色框和绿色框中橘色细框中的变量都是从A中继承下来的。
此时可以清除的看到,d对象中有两个_a变量,分布在B域和C域中。
再看使用菱形继承后的内存模型:
- 最外部的蓝色框是整个d对象,他一共有6个四字节的数据。
- 中间的红色框中的成员是从B继承下来的,有2个四字节的数据。
- 中间的绿色框中的成员是从C继承下来的,有2个四字节的数据。
- 最下边的红色细框中的变量是从A继承下来的。
先不管多了什么东西,单看从A中继承下来的变量,发现只有一个了。从原理的两个变成了一个,解决了数据冗余的问题。
菱形继承中原本冗余的成员最后只有一个,而且放在最终派生类对象中的最后位置。
此时数据冗余和二义性是解决了,因为派生类对象中只有一个从A继承下来的成员了,但是相比原来不用虚拟继承多出来4个字节不说,还将原本是成员所在位置变成了奇奇怪怪的东西。
- B中黄色框中地址中的数据是一个地址,在新内存窗口中输入该地址,得到一个新的黄色框。
- C中紫色框中地址中的数据是一个地址,在新内存窗口中输入该地址,得到一个新的紫色框。
- 本喵用的机器是小端存储方式,按照小端模式得到d对象中存放的两个地址。
在两个新内存窗口中看到的两个新的框被称为虚基表。
黄色虚基表:
- 虚基表中,第一个int类型的数据存放的是0,具体什么意义在多态的时候再讲。
- 虚基表中第二个int类型的数据存放的是0x14,它是一个偏移量。
再看d对象的内存模型:
- 从B继承下来的成员,起始地址是0x0055FA90。
- 从A继承下来的成员,它的地址是0x0055FAA4。
这两个地址之间相差0x14,也就是20。
- 当使用d.B::_a来访问A继承下来的成员时,就从B继承下来的成员的起始地址处,根据偏移量去访问具体的_a。
紫色虚基表:
- 虚基表中第二个int类型的数据存放的是0x0c,同样是一个偏移量。
再看d对象的内存模型:
- 从C继承下来的成员,起始地址是0x0055FA98。
- 从A继承下来的成员,它的地址是0x0055FAA4。
这两个地址之间相差0x0c,也就是12。
- 当使用d.C::_a来访问A继承下来的成员时,就从C继承下来的成员的起始地址处,根据偏移量去访问具体的_a。
再看派生类对象的内存窗口:
B区域和A的偏移量是20,C区域和A的偏移量是12。
从汇编中也可看到,使用d.B::_a和d.C::_a步骤都比直接访问其他成员多,因为这两种方式虽然访问的都是一个地址,但是需要根据偏移量去计算。
由于使用了虚拟继承,所以B对象和C对象同样采用有虚基表的结构,将从A继承下来的成员放在最后,原本的位置存放对应虚基表的地址,虚基表中存放偏移量。
虚基表存在的原因:
现在有个疑问,为什么要根据偏移量来找从A中继承下来的那个成员?B对象C对象,或者是D对象,它们自己肯定会知道自己成员的位置啊。
- B的指针拿到的是对象b的地址时,解引用访问_a,此时只是在自己内部寻找,不用偏移量也可以理解。
- B的指针拿到的是对象d的地址时,此时会发生切片,但是d中的_a仍然会保留下来,但是此时站在B指针的角度来看,它根部不知道_a在哪里,因为这是d对象安排的。
- 所以此时就需要通过虚基表获取_a距离B的偏移量来访问_a。
所以虚基表还是很有必要的。这部分只要了解就好。
🙀继承和组合
组合我们之前其实一直都在使用,就是在一个类中,它的成员变量是其他类。继承和组合都是一种类设计层面的复用方式。
此时Student中同样有Person的成员变量,但是不是通过继承得到的,而是通过组合得到的。
- public继承是一种is-a的关系。
如上图中,学生是人,所以Student和Person就用继承关系比较好。
- 组合是一种has-a的关系。
如上图中,头上有眼睛,而不能说成头是眼睛,所以此时用组合更合适。
比较:
-
继承方式:派生类和基类耦合度比较高。基类中的成员变量发生改变后,对派生类的影响较大,除去private成员外,其他成员的改变都会影响派生类。
-
组合方式:最终的类和被组合的类耦合度比较低。被组合类中成员变量发生改变后,对最终类的影响较小,只有public成员改变,才会影响最终的类。
- 继承方式中,派生类中可以看到基类的所有成员,只是基类的private成员不可以访问,所以被称为白箱复用。
- 组合方式中,最终类只能看到被组合类的public成员,其他成员和细节都看不到,也无法访问,所以被称为黑箱复用。
结论: 在继承和组合都可以使用的情况下,尽量使用组合而不用继承。
🙀总结
很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度及性能上都有问题。掌握好继承的各种特性,对于后面的多态非常有帮助。
相关文章:

【C++学习】继承
🐱作者:一只大喵咪1201 🐱专栏:《C学习》 🔥格言:你只管努力,剩下的交给时间! C是面向对象的编程语言,它有很多的特性,但是最重要的就是封装,继承…...

【03173】2020年8月高等教育自学考试-软件开发工具
一、单项选择题:1. 区别于一般软件,对软件开发工具而言,下列各项最重要的性能是 A. 效率 B. 响应速度C. 资源消耗 D. 使用方便2. 在软件开发过程的信息需求中,属于跨开发周期的信息是A. 有关系统环境的需求信息 B. 有关软件设计的…...

Java中的String类
String类1.String类1.1 特性1.2 面试题1.3 常用方法1.4 String与其他类型之间的转换2. StringBuilder类、StringBuffer类:可变字符序列1.String类 1.1 特性 String类为final类,不可被继承,代表不可变的字符序列; 实现了Serializ…...

【java】笔试强训Day3【在字符串中找出连续最长的数字串与数组中出现次数超过一半的数字】
目录 ⛳选择题 1.以下代码运行输出的是 2.以下程序的输出结果为 3.下面关于构造方法的说法不正确的是 ( ) 4.在异常处理中,以下描述不正确的有( ) 5.下列描述中,错误的是( ) 6.…...

一文7个步骤从0到1教你搭建Selenium 自动化测试环境
【导语】Selenium是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中,就像真正的用户在操作一样。支持自动录制动作和自动生成 .Net、Java、Perl等不同语言的测试脚本。本文详细介绍了搭建自动化测试环境所需的工具,让你学习自动化测试不…...

Oracle目录应急清理
Oracle目录应急清理清理错误位置的归档日志清理30天前的监听告警日志清理监听日志清理30天以前的trace文件清理30天以前的审计日志清理错误位置的归档日志 检查$ORACLE_HOME/dbs下是否有归档文件: ls $ORACLE_HOME/dbs/arch* | wc -l检查和修改归档位置࿱…...

使用 OBS 进行区域录制
1. OBS 与区域录屏 实际上 OBS 的使用场景可谓是与区域录屏格格不入的。 虽然我们依旧有一些办法在 OBS 中达到区域录屏的目的,但其操作实在过于繁琐,还不如直接使用 QQ 或者 Windows 最新的自带截屏录屏来进行区域录屏来的方便实在。 但若非常强烈的…...

aws eks 配置授权额外的用户和角色访问集群
参考资料 https://github.com/kubernetes-sigs/aws-iam-authenticator#full-configuration-formathttps://docs.amazonaws.cn/zh_cn/eks/latest/userguide/add-user-role.html 众所周知,aws eks使用 Authenticator 或者 aws 命令来进行账户级别的用户和角色的授权…...

MagicalCoder可视化开发平台:轻松搭建业务系统,为企业创造更多价值
让软件应用开发变得轻松起来,一起探索MagicalCoder可视化开发工具的魔力!你是否为编程世界的各种挑战感到头痛?想要以更高效、简单的方式开发出专业级的项目?MagicalCoder低代码工具正是你苦心寻找的产品!它是一款专为…...

8个不能错过的程序员必备网站,惊艳到我了!!!
程序员是一个需要不断学习的职业,不少朋友每天来逛CSDN、掘金等网站,但一直都抱着“收藏从未停止,学习从未开始”的态度,别骗自己了兄弟。在编程体系中,有很多不错的小工具,可以极大得提升我们的开发效率。…...

Mybatis(二):实现“增删改查”
Mybatis(二):实现“增删改查”前言一、MyBatis的增删改查1、添加2、修改3、删除4、查询4.1 查询一个实体4.1 查询集合二、MyBatis获取参数值的两种方式(重点)1、单个字面量类型的参数2、多个字面量类型的参数3、map集合…...

Faster RCNN 对血液细胞目标检测
目录 1. 介绍 2. 工具函数介绍 utils 2.1 xml 文件的读取 get_label_from_xml 2.2 绘制边界框 draw_bounding_box...

【数据结构】Java实现栈
目录 1. 概念 2. 栈的使用 3. 自己动手实现栈(使用动态数组实现栈) 1. 创建一个MyStack类 2. push入栈 3. pop出栈 4. 查看栈顶元素 5. 判断栈是否为空与获取栈长 6. toString方法 4. 整体实现 4.1 MyStack类 4.2 Test类 4.3 测试结果 1.…...

【数据结构】排序
作者:✿✿ xxxflower. ✿✿ 博客主页:xxxflower的博客 专栏:【数据结构】篇 语录:⭐每一个不曾起舞的日子,都是对生命的辜负。⭐ 文章目录1.排序1.1排序的概念1.2常见的排序算法2.常见排序算法2.1插入排序2.1.1直接插入…...

过拟合、验证集、交叉验证
过拟合 简单描述:训练集误差小,测试集误差大,模型评估指标的方差(variance)较大; 判断方式: 1、观察 train set 和 test set 的误差随着训练样本数量的变化曲线。 2、通过training accuracy 和…...

原力计划来了【协作共赢 成就未来】
catalogue🌟 写在前面🌟 新星计划持续上新🌟 原力计划方向🌟 原力计划拥抱优质🌟 AIGC🌟 参加新星计划还是原力计划🌟 创作成就未来🌟 写在最后🌟 写在前面 哈喽&#x…...

一文了解Jackson注解@JsonFormat及失效解决
背景 项目中使用WRITE_DATES_AS_TIMESTAMPS: true转换日期格式为时间戳未生效。如下: spring:jackson:time-zone: Asia/Shanghaiserialization:WRITE_DATES_AS_TIMESTAMPS: true尝试是否关于时间的注解是否会生效,使用JsonForma和JsonFiled均失效。 常…...

webpack——使用、分析打包代码
世上本无nodejs js最初是在前端浏览器上运行的语言,js代码一旦脱离了浏览器环境,就无法被运行。直到nodejs的出现,我们在电脑上配置了node环境,就可以让js代码脱离浏览器,在node环境中运行。 浏览器不支持模块化 nodej…...

libvirt零知识学习5 —— libvirt源码编译安装(3)
接前一篇文章libvirt零知识学习4 —— libvirt源码编译安装(2) 在上篇文章及上上篇文章中构建libvirt的时候遇到了一个问题“ERROR: Problem encountered: YAJL 2 is required to build QEMU driver”。上篇文章讲到即使安装了相应的YAJL库仍然不能解决问…...

Nmap 的使用教程
Nmap是一个网络侦测和安全审计工具。它可以用于发现网络上的主机和服务,并提供广泛的信息,其中包括操作系统类型和版本、应用程序和服务的详细信息等。在本文中,我们将介绍如何使用Nmap扫描网络主机,识别开放端口以及进行操作系统…...

async与await异步编程
ECMA2017中新加入了两个关键字async与await 简单来说它们是基于promise之上的的语法糖,可以让异步操作更加地简单明了 首先我们需要用async关键字,将函数标记为异步函数 async function f() {} f()异步函数就是指:返回值为promise对象的函…...

移动应用架构设计:如何转变开发流程
移动应用架构设计:如何转变开发流程 2023 年掌握移动应用程序架构的指南(附案例研究) 如果他们要解决这个问题,开发人员需要了解移动架构设计的最佳实践,使他们能够构建用户喜欢的优化应用程序。其中一些做法包括使用…...

NX二次开发 图层函数总结
简介: NX二次开发 图层相关的总结。 函数: uc5007()uc5008()uc5009()UF_LAYER_ask_category_info()获取图层类别的信息UF_LAYER_ask_category_tag()根据图层分类名称查询其图层分类标识UF_LAYER_ask_status()UF_LAYER_ask_work_layer()UF_LAYER_create…...

windows微服务部署
windows部署一.nginx部署1.nginx 官网下载2. 配置nginx3.配置nigix 防止nigix刷新404不生效二.配置redis部署成服务1.在系统配置中 配置为系统变量2.打开快捷登录服务管理#3. 开启redis三.windows部署jar包一.nginx部署 1.nginx 官网下载 地址 官网地址 安装 windows版本 可安…...

Java四种内部类(看这一篇就够了)
🎉🎉🎉点进来你就是我的人了 博主主页:🙈🙈🙈戳一戳,欢迎大佬指点!人生格言:当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友一起加油喔🦾&am…...

蓝桥杯刷题第二十天
第一题:纸张尺寸问题描述在 ISO 国际标准中定义了 A0 纸张的大小为 1189mm 841mm, 将 A0 纸 沿长边对折后为 A1 纸, 大小为 841mm 594mm, 在对折的过程中长度直接取 下整 (实际裁剪时可能有损耗)。将 A1 纸沿长边对折后为 A2 纸, 依此类推。输入纸张的名称, 请输出…...

如何通过命令行查看CentOS版本信息和linux系统信息
1.如何查看已安装的CentOS版本信息: 1.cat /proc/version 2.uname -a 3.uname -r 4.cat /etc/centos-release 5.lsb_release -a 6.hostnamectl1. 第一种方式输出的结果是: Linux version 3.10.0-1127.el7.x86_64 (mockbuildkbuilder.bsys.centos.org) …...

oracle查询表空间大小以及每个表所占空间的大小
1、查询数据库中所有的表空间以及表空间所占空间的大小,直接执行语句就可以了: select tablespace_name, sum(bytes)/1024/1024 from dba_data_files group by tablespace_name; 2、查看表空间物理文件的名称及大小 select tablespace_name, file_id, …...

C语言通讯录应用程序:从设计到实现
hello,这期给大家带来C语言实现静态通讯录,主要也是建立起创建大项目的思维,与往期这两篇博客有点类似 C语言实现三子棋 C语言实现扫雷 文章目录🤓通讯录介绍😶🌫️效果演示🤠主题框架头文件测试文件函数…...

银河麒麟v10sp2安装nginx
nginx官网下载:http://nginx.org/download/ 银河麒麟系统请先检查yum源是否配置,若没有配置请参考:https://qdhhkj.blog.csdn.net/article/details/129680789 一、安装 1、yum安装依赖 yum install gcc gcc-c make unzip pcre pcre-devel …...