<C++> 继承

1.继承的概念和定义

继承是面向对象其中的一个核心概念之一,继承允许一个类(称为子类或派生类)从另一个类(称为父类或基类)继承属性和行为,以便在子类中重用已有的代码并添加新的功能。通过继承,可以建立类之间的层次关系,使代码更加模块化、可维护和可扩展。

继承可以通过以下方式来定义:

class BaseClass {// 父类的成员变量和成员函数声明
};class SubClass : [访问权限] BaseClass {// 子类的成员变量和成员函数声明
};

其中,BaseClass 是父类的名称,SubClass 是子类的名称。[访问权限] 是继承关系的访问权限修饰符,可以是 publicprotectedprivate,分别对应公有继承、保护继承和私有继承。使用不同的访问权限修饰符会影响子类对父类成员的访问权限。

以下代码中Student类和Teacher类就继承了Person类。

//父类
class Person {
public:void Print() {cout << "name:" << _name << endl;cout << "age:" << _age << endl;}protected:string _name = "张三";//姓名int _age = 18;        //年龄
};
//子类
class Student : public Person {
protected:int _stuid;//学号
};
//子类
class Teacher : public Person {
protected:int _jobid;//工号
};

在这里插入图片描述

继承后,父类Person的成员,包括成员函数和成员变量,都会变成子类的一部分,也就是说,子类Student和Teacher复用了父类Person的成员。

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

继承的访问权限修饰符(public、protected、private)会影响派生类对基类成员的访问方式。以下是继承基类成员访问方式的变化:

  • 公有继承
    • 公有成员:在派生类中仍然保持公有权限。可以在派生类内部和外部直接访问。
    • 受保护成员:在派生类中变为保护权限。可以在派生类内部访问,但在外部无法直接访问。
    • 私有成员:无法在派生类中直接访问。
  • 保护继承
    • 公有成员和受保护成员:在派生类中变为保护权限。可以在派生类内部访问,但在外部无法直接访问。
    • 私有成员:无法在派生类中直接访问。
  • 私有继承
    • 公有成员、受保护成员:在派生类中变为私有权限。可以在派生类内部访问,但在外部无法直接访问。
    • 私有成员:无法在派生类中直接访问。

总结:

  1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
  2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。 这也是protected和private的唯一区别
  3. 实际我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。
  4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
  5. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。

示例:

#include <iostream>class Animal {
public:void eat() {std::cout << "Animal is eating." << std::endl;}protected:void sleep() {std::cout << "Animal is sleeping." << std::endl;}private:void move() {std::cout << "Animal is moving." << std::endl;}
};class PublicInheritance : public Animal {
public:void accessBase() {eat();  // 可访问基类的公有成员 -> public成员sleep();// 可访问基类的保护成员 -> protected成员move(); // err 不可访问基类的私有成员 -> private成员}
};class ProtectedInheritance : protected Animal {
public://保护继承从基类继承下来的公有和保护都为保护,私有不变void accessBase() {eat();  // 可访问基类的公有成员 -> protected成员sleep();// 可访问基类的受保护成员 -> protected成员move(); // err  不可访问基类的私有成员 -> private成员}
};class PrivateInheritance : private Animal {
public://私有继承成员都变为私有成员void accessBase() {eat();  // 可访问基类的私有成员   -> private成员sleep();// 可访问基类的私有成员   -> private成员move(); // 不可访问基类的私有成员 -> private成员}
};//保护和私有继承在继续派生观察区别
class Protected : public ProtectedInheritance {
public://void access() {eat();  //可以访问基类的保护成员sleep();//可以访问基类的保护成员move(); //不可以访问基类的私有成员}
};class Privated : public PrivateInheritance {
public:void access() {eat();  //err 不可以访问基类的私有sleep();//err 不可以访问基类的私有move(); //err 不可以访问基类的私有}
};

3.基类和派生类对象赋值转换

派生类对象可以赋值给基类的对象 / 基类的指针 / 基类的引用

例如,对于以下基类及其派生类。

//基类
class Person {
protected:string _name;//姓名string _sex; //性别int _age;    //年龄
};
//派生类
class Student : public Person {
protected:int _stuid;//学号
};int main(){Student s;Person p = s;     //派生类对象赋值给基类对象Person* ptr = &s; //派生类对象赋值给基类指针Person& ref = s;  //派生类对象赋值给基类引用return 0;
}

对于这种做法,有个形象的说法叫做切片/切割,寓意把派生类中基类那部分切来赋值过去。

派生类对象赋值给基类对象图示:

在这里插入图片描述

赋值给指针和引用同理

示例:

#include <iostream>
using namespace std;
//基类
class Person {
public:string _name;//姓名string _sex; //性别int _age;    //年龄
};
//派生类
class Student : public Person {
public:int _stuid;//学号
};int main() {Student s;Person p = s;//派生类对象赋值给基类对象cout << p._name << endl;cout << s._stuid << endl;cout << p._stuid << endl;//err 只能访问基类的成员return 0;
}

基类对象不能赋值给派生类对象。

示例:

int main() {Student s;Person p = s;//派生类对象赋值给基类对象s = p;  //err 基类不能赋值给派生类return 0;
}

基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI的dynamic_cast来进行识别后进行安全转换。

示例:

int main() {Student s;Person p = s;//派生类对象赋值给基类对象Person *pp = &s;Student *st = (Student *) pp;//这种情况转换时可以的st->_stuid = 10;pp = &p;Student *st2 = (Student *) pp;// 这种情况转换时虽然可以,但是会存在越界访问的问题st2->_stuid = 10;return 0;
}

4.继承中的隐藏

在继承体系中的基类和派生类都有独立的作用域。若子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。

例如,对于以下代码,访问成员num时将访问到子类当中的num。

#include <iostream>
#include <string>
using namespace std;
//父类
class Person {
protected:int _num = 111;
};
//子类
class Student : public Person {
public:void fun() {cout << _num << endl;}protected:int _num = 999;
};int main() {Student s;s.fun();//999return 0;
}

若此时我们就是要访问父类当中的_num成员,我们可以使用作用域限定符进行指定访问。

class Student : public Person {
public:void fun() {cout << Person::_num << endl;//指定访问父类当中的_num成员}protected:int _num = 999;
};

例如,对于以下代码,调用成员函数fun时将直接调用子类当中的fun,若想调用父类当中的fun,则需使用作用域限定符指定类域。

#include <iostream>
#include <string>
using namespace std;
//父类
class Person {
public:void fun(int x) {cout << x << endl;}
};
//子类
class Student : public Person {
public:void fun(double x) {cout << x << endl;}
};int main() {Student s;s.fun(3.14);      //直接调用子类当中的成员函数funs.Person::fun(20);//指定调用父类当中的成员函数funreturn 0;
}

代码当中,父类中的fun和子类中的fun不是构成函数重载,因为函数重载要求两个函数在同一作用域,而此时这两个fun函数并不在同一作用域。

成员函数的隐藏,只需要函数名相同就构成隐藏

在实际中在继承体系里面最好不要定义同名的成员。

5.派生类的默认成员函数

默认成员函数,即我们不写编译器会自动生成的函数,类当中的默认成员函数有以下六个:

在这里插入图片描述

下面我们看看派生类当中的默认成员函数,与普通类的默认成员函数的不同之处。

例如,我们以下面这个Person类为基类。

class Person {
public://构造函数Person(const string &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;}private:string _name;//姓名
};

我们用该基类派生出Student类,Student类当中的默认成员函数的基本逻辑如下:

class Student : public Person {
public://构造函数Student(const string &name, int id): Person(name)//调用基类的构造函数初始化基类的那一部分成员,_id(id)//初始化派生类的成员{cout << "Student()" << endl;}//拷贝构造函数Student(const Student &s): Person(s)//调用基类的拷贝构造函数完成基类成员的拷贝构造,_id(s._id)//拷贝构造派生类的成员{cout << "Student(const Student& s)" << endl;}//赋值运算符重载函数Student &operator=(const Student &s) {cout << "Student& operator=(const Student& s)" << endl;if (this != &s) {Person::operator=(s);//调用基类的operator=完成基类成员的赋值_id = s._id;         //完成派生类成员的赋值}return *this;}//析构函数~Student() {cout << "~Student()" << endl;//派生类的析构函数会在被调用完成后自动调用基类的析构函数}private:int _id;//学号
};

派生类与普通类的默认成员函数的不同之处概括为以下几点:

  1. 派生类的构造函数被调用时,会自动调用基类的构造函数初始化基类的那一部分成员,如果基类当中没有默认的构造函数,则必须在派生类构造函数的初始化列表当中显示调用基类的构造函数。

  2. 派生类的拷贝构造函数必须调用基类的拷贝构造函数完成基类成员的拷贝构造。

  3. 派生类的赋值运算符重载函数必须调用基类的赋值运算符重载函数完成基类成员的赋值。

  4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。

  5. 派生类对象初始化时,会先调用基类的构造函数再调用派生类的构造函数。

  6. 派生类对象在析构时,会先调用派生类的析构函数再调用基类的析构函数。

在编写派生类的默认成员函数时,需要注意以下几点:

  1. 派生类和基类的赋值运算符重载函数因为函数名相同构成隐藏,因此在派生类当中调用基类的赋值运算符重载函数时,需要使用作用域限定符进行指定调用。
  2. 由于多态的某些原因,任何类的析构函数名都会被统一处理为destructor();。因此,派生类和基类的析构函数也会因为函数名相同构成隐藏,若是我们需要在某处调用基类的析构函数,那么就要使用作用域限定符进行指定调用。
  3. 在派生类的拷贝构造函数和operator=当中调用基类的拷贝构造函数和operator=的传参方式是一个切片行为,都是将派生类对象直接赋值给基类的引用。

在这里插入图片描述

说明一下:

  • 基类的构造函数、拷贝构造函数、赋值运算符重载函数我们都可以在派生类当中自行进行调用,而基类的析构函数是当派生类的析构函数被调用后由编译器自动调用的,我们若是自行调用基类的析构函数就会导致基类被析构多次的问题

  • 我们知道,创建派生类对象时是先创建的基类成员再创建的派生类成员,编译器为了保证析构时先析构派生类成员再析构基类成员的顺序析构,所以编译器会在派生类的析构函数被调用后自动调用基类的析构函数

6.继承和友元

友元关系不能继承,也就是说基类的友元可以访问基类的私有和保护成员,但是不能访问派生类的私有和保护成员。

例如,以下代码中Display函数是基类Person的友元,当时Display函数不是派生类Student的友元,即Display函数无法访问派生类Student当中的私有和保护成员。

#include <iostream>
#include <string>
using namespace std;
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;
}

若想让Display函数也能够访问派生类Student的私有和保护成员,只能在派生类Student当中进行友元声明。

class Student : public Person {
public://声明Display是Student的友元friend void Display(const Person &p, const Student &s);protected:int _id;//学号
};

7.继承与静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子 类,都只有一个static成员实例 。

示例:

#include <iostream>
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;// 研究科目
};int main() {Student s1;Student s2;Student s3;Graduate s4;cout << " 人数 :" << Person ::_count << endl;   //4Student ::_count = 0;cout << " 人数 :" << Person ::_count << endl;  //0return 0;
}

8.继承的方式

单继承:一个子类只有一个直接父类时称这个继承关系为单继承

在这里插入图片描述

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

在这里插入图片描述

菱形继承:菱形继承是多继承的一种特殊情况。
在这里插入图片描述

菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。 在Assistant的对象中Person成员会有两份。

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成员。

// 需要显示指定访问哪个父类的成员可以解决二义性问题
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";

虽然该方法可以解决二义性的问题,但仍然不能解决数据冗余的问题。因为在Assistant的对象在Person成员始终会存在两份。

在这里插入图片描述

菱形虚拟继承

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

在这里插入图片描述

代码如下:

#include <iostream>
#include <string>
using namespace std;
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;//主修课程
};int main() {Assistant a;a._name = "peter";//无二义性return 0;
}

此时就可以直接访问Assistant对象的name成员了,并且之后就算我们指定访问Assistant的Student父类和Teacher父类的name成员,访问到的都是同一个结果,解决了二义性的问题。

cout << a.Student::_name << endl; //peter
cout << a.Teacher::_name << endl; //peter

而我们打印Assistant的Student父类和Teacher父类的_name成员的地址时,显示的也是同一个地址,解决了数据冗余的问题。

cout << &a.Student::_name << endl; //0136F74C
cout << &a.Teacher::_name << endl; //0136F74C

菱形虚拟继承原理

为了研究虚拟继承原理,我们给出了一个简化的菱形继承继承体系,再借助内存窗口观察对象成员的模型。

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.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;return 0;
}

下图是菱形继承的内存对象成员模型:这里可以看到数据冗余

在这里插入图片描述

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

为什么D中B和C部分要去找属于自己的A?那么大家看看当下面的赋值发生时,d是不是要去找出B/C成员中的A才能赋值过去?

D d;
B b = d;
C c = d;

下面是上面的Person关系菱形虚拟继承的原理解释:
在这里插入图片描述

9.继承和组合

组合(Composition) 是另一种对象关系,其中一个类包含另一个类的实例作为其成员变量。这种关系允许你在一个类中使用另一个类的功能,但不会继承其接口。组合关系更加灵活,可以在运行时动态选择成员对象,同时避免了多继承可能引发的问题。

继承是一种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的关系,则优先使用组合。

原因如下:

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

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

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

相关文章

CVE-2023-36874 Windows错误报告服务本地权限提升漏洞分析

CVE-2023-36874 Windows错误报告服务本地权限提升漏洞分析 漏洞简介 Windows错误报告服务在提交错误报告前会创建wermgr.exe进程&#xff0c;而攻击者使用特殊手法欺骗系统创建伪造的wermgr.exe进程&#xff0c;从而以system权限执行代码。 影响版本 Windows10 1507 * Wind…

Python-pyqt不同窗口数据传输【使用静态函数】

文章目录 前言程序1&#xff1a;caogao1.py输入数据界面程序2&#xff1a;caogao2.py接收数据界面 程序3 &#xff1a;将输入数据界面和接收数据界面组合成一个总界面讲解 总结 前言 在编写pyqt 页面时有时候需要不同页面进行数据传输。本文讲解静态函数方法。直接看示例。 程…

FPGA VR摄像机-拍摄和拼接立体 360 度视频

本文介绍的是 FPGA VR 相机的第二个版本&#xff0c;第一个版本是下面这样&#xff1a; 第一版地址&#xff1a; ❝ https://hackaday.io/project/26974-vr-camera-fpga-stereoscopic-3d-360-camera ❞ 本文主要介绍第二版本&#xff0c;第二版本的 VR 摄像机&#xff0c;能够以…

Kotlin学习之密封类

Kotlin中的密封类: kotlin中的密封类&#xff0c;用关键词Sealed修饰&#xff0c;且还有一个规定&#xff1a;Sealed类的子类应该是Sealed类的嵌套类&#xff0c;或者应该在与Sealed类相同的文件中声明。 当我们想定义一个有相同父类&#xff0c;但是有不同子类的时候&#xf…

胡歌深夜发文:我对不起好多人

胡歌的微博又上了热搜。 8月29日01:18分&#xff0c;胡歌微博发文称&#xff1a;“我尽量保持冷静&#xff0c;我对不起好多人&#xff0c;我希望对得起这短暂的一生”&#xff0c;并配了一张自己胡子拉碴的图&#xff0c;右眼的伤疤清晰可见。 不少网友留言称“哥你又喝多了吗…

基于MATLAB的径向基函数插值(RBF插值)(一维、二维、三维)

基于MATLAB的径向基函数插值&#xff08;RBF插值&#xff09;&#xff08;一维、二维、三维&#xff09; 0 前言1 RBF思路2 1维RBF函数2.1 参数说明2.1.1 核函数选择2.1.2 作用半径2.1.3 多项式拟合2.1.4 误差项&#xff08;光滑项&#xff09; 3 2维RBF函数4 3维RBF函数 惯例声…

深入了解Nginx:高性能的开源Web服务器与反向代理

一、Nginx是什么 Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器&#xff0c;也可以作为负载均衡器和HTTP缓存服务器使用。它采用事件驱动、异步非阻塞的处理方式&#xff0c;能够处理大量并发连接和高流量负载&#xff…

【Selenium】chromedriver最新版本与Chrome自动更新版本不匹配问题

文章目录 一、查看chromedriver版本二、降级Chrome浏览器且禁止自动更新三、chromedriver下载配置四、测试 一、查看chromedriver版本 使用Selenium时需要下载chromedriver 1、首先查看我的Chrome浏览器版本已自动更新到116&#xff1a; 2、查找与之对应的chromedriver版本&am…

Elasticsearch Split和shrink API

背景&#xff1a; 尝试解决如下问题&#xff1a;单分片存在过多文档&#xff0c;超过lucene限制 分析 1.一般为日志数据或者OLAP数据&#xff0c;直接删除索引重建 2.尝试保留索引&#xff0c;生成新索引 - 数据写入新索引&#xff0c;查询时候包含 old_index,new_index 3.…

【【萌新的STM32学习-19-蜂鸣器实验】】

萌新的STM32学习-19-蜂鸣器实验 STM32在正点原子的视频中并未讲述关于蜂鸣器的实验&#xff0c;我们自己探究一下作为简单的HAL库入门 蜂鸣器每隔 300ms 响或者停一次。LED0 每隔 300ms 亮或者灭一次。LED0 亮的时候蜂鸣器不叫&#xff0c;而 LED0 熄灭的时候&#xff0c;蜂鸣…

ospf不规则区域划分和数据库表

华子目录 ospf不规则区域1.远离骨干的非骨干区域2.不连续骨干 不规则区域解决方案1.tunnel-点到点GRE2.ospf虚链路3.多进程双向重发布&#xff08;推荐&#xff09; ospf的数据库表 ospf不规则区域 1.远离骨干的非骨干区域 图示 2.不连续骨干 图示 不规则区域解决方案 …

二叉搜索树(C++)

二叉搜索树 概念二叉搜索树的应用二叉搜索树的实现K模型基本结构和函数声明接口实现①find——查找关键码②Insert——插入关键码③Erase——删除关键码&#xff08;重点&#xff09;时间复杂度 源码&#xff08;整体&#xff09;非递归递归 KV模型 在使用C语言写数据结构阶段时…

Leetcode:【448. 找到所有数组中消失的数字】题解

题目 给你一个含 n 个整数的数组 nums &#xff0c;其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字&#xff0c;并以数组的形式返回结果。 难度&#xff1a;简单 题目链接&#xff1a;448. 找到所有数组中消失的数字 示例1 输入&…

Mybatis1.1 环境准备

1.1 环境准备 数据库表&#xff08;tb_brand&#xff09;及数据准备实体类 Brand编写测试用例安装 MyBatisX 插件 数据库表&#xff08;tb_brand&#xff09;及数据准备 -- 删除tb_brand表 drop table if exists tb_brand; -- 创建tb_brand表 create table tb_brand (-- id 主…

C语言每日一练------Day(6)

本专栏为c语言练习专栏&#xff0c;适合刚刚学完c语言的初学者。本专栏每天会不定时更新&#xff0c;通过每天练习&#xff0c;进一步对c语言的重难点知识进行更深入的学习。 今日练习题关键字&#xff1a;整数转换 异或 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn…

第62步 深度学习图像识别:多分类建模(Pytorch)

基于WIN10的64位系统演示 一、写在前面 上期我们基于TensorFlow环境做了图像识别的多分类任务建模。 本期以健康组、肺结核组、COVID-19组、细菌性&#xff08;病毒性&#xff09;肺炎组为数据集&#xff0c;基于Pytorch环境&#xff0c;构建SqueezeNet多分类模型&#xff0…

【安装包】JDK 17安装教程

软件下载 软件&#xff1a;JDK版本&#xff1a;17语言&#xff1a;简体中文大小&#xff1a;151.24M安装环境&#xff1a;Win11/Win10/Win8/Win7硬件要求&#xff1a;CPU2.0GHz 内存4G(或更高&#xff09;下载通道①百度网盘丨64位下载链接&#xff1a;https://pan.baidu.com/…

【爬虫】5.5 Selenium 爬取Ajax网页数据

目录 AJAX 简介 任务目标 创建Ajax网站 创建服务器程序 编写爬虫程序 AJAX 简介 AJAX&#xff08;Asynchronous JavaScript And XML&#xff0c;异步 JavaScript 及 XML&#xff09; Asynchronous 一种创建交互式、快速动态网页应用的网页开发技术通过在后台与服务器进行…

软考:中级软件设计师:邮件加密系统,网络安全保障,网络威胁与攻击,防火墙技术

软考&#xff1a;中级软件设计师:邮件加密系统 提示&#xff1a;系列被面试官问的问题&#xff0c;我自己当时不会&#xff0c;所以下来自己复盘一下&#xff0c;认真学习和总结&#xff0c;以应对未来更多的可能性 关于互联网大厂的笔试面试&#xff0c;都是需要细心准备的 &…

【Linux-Day8- 进程替换和信号】

进程替换和信号 问题引入 我们发现 终端输入的任意命令的父进程都是bash,这是因为Linux系统是用fork()复制出子进程&#xff0c;然后在子进程中调用替换函数进行进程替换&#xff0c;实现相关命令。 &#xff08;1&#xff09; exec 系列替换过程&#xff1a;pcb 使用以前的只…