【C++进阶】一、继承(总)

news2024/11/30 10:32:19

目录

一、继承的概念及定义

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 则是父类的类名

初阶的时候已经学过,访问限定符有以下三种

  1.  public(公有访问)
  2. protected(保护访问)
  3. private(私有访问) 

        public 修饰的成员变量,可以在类外面直接访问,protected 和 private 修饰的成员变量,不能在类外访问,但可以在类里面进行访问

继承方式也有三种:

  1.  public(公有继承)
  2. protected(保护继承)
  3. 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();
};

注意:需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏,所以在实际中在继承体系里面最好不要定义同名的成员

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

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

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

        拷贝构造函数和赋值重载函数对于内置类型进行浅拷贝(值拷贝),对于自定义类型则调用对于的拷贝构造和赋值重载函数

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

派生类当中的默认成员函数,与普通类的默认成员函数的不同之处:

  1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用
  2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化
  3. 派生类的 operator= 必须要调用基类的 operator= 完成基类的复制
  4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序
  5. 派生类对象初始化先调用基类构造再调派生类构造
  6. 派生类对象析构清理先调用派生类析构再调基类的析构
  7. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同(这个我们后面会讲解)。那么编译器会对析构函数名进行特殊处理,处理成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;//4
	Student::_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

继承和组合

  1. public 继承是一种 is-a 的关系。也就是说每个派生类对象都是一个基类对象
  2. 组合是一种 has-a 的关系。假设B组合了A,每个B对象中都有一个A对象
  3. 优先使用对象组合,而不是类继承 

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

笔试面试题

上面已有解释,不再解释

----------------我是分割线---------------

文章到这里就结束了,下一篇即将更新

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

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

相关文章

【SAP Abap】X-DOC:SE11 - 创建配置表并分配事务码

SE11 - 创建配置表并分配事务码1、创建自定义表2、创建表维护功能3、功能验证4、设置事务码5、带出字段默认值&#xff08;1&#xff09;方法一&#xff1a;表维护事件&#xff08;2&#xff09;方法二&#xff1a;屏幕事件1、创建自定义表 SE11&#xff0c;创建自定义表&…

结构体的三种定义方法、结构体类型名(可选标志符)什么时候可以省略

结构体的三种定义方法 一、单独定义&#xff1a; 先定义结构体类型&#xff0c;再定义变量   定义结构体的格式如下&#xff1a;    struct 结构体名 {    若干数据项&#xff1b;    } &#xff1b;   其中&#xff0c;struct为关键字&#xff1b; 结构体名是用户定…

golang 入门教程:迷你 Twitter 后端

请记住&#xff0c;这个项目主要是为了稍微熟悉下Golang&#xff0c;您可以复制架构&#xff0c;但该项目缺少适当的 ORM&#xff0c;没有适当的身份验证或授权&#xff0c;我完全无视中间件&#xff0c;也没有测试。 我将在其自己的部分中讨论所有这些问题&#xff0c;但重要的…

利用NGROK将本地网站发布为一个公开网站

一般与第三方服务集成时&#xff0c;需要提供https的回调URL&#xff0c;本地开发阶段可以利用NGROK将本地网站发布为公开的https网站。https://ngrok.com/downloadWindow下载地址&#xff1a;https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-windows-amd64.zip以Window…

echarts问题汇总

因为工作中经常使用echarts&#xff0c;做数据可视化相关需求&#xff0c;需要实现各种各样图表的需求。 有遇到过很多问题&#xff0c;一些网上不太好搜索到解决方案的&#xff0c;一直想总结一下解决过程。方便自己查阅&#xff0c;也方便别人参考。 一&#xff1a;echarts…

【C++】set/multiset、map/multimap的使用

目录 一、关联式容器 二、set的介绍 1、接口count与容器multiset 2、接口lower_bound和upper_bound 三、map的介绍 1、接口insert 2、接口insert和operator[]和at 3、容器multimap 四、map和set相关OJ 1、前K个高频单词 2、两个数组的交集 一、关联式容器 vector、…

【LeetCode】环形链表 II [M](链表)

142. 环形链表 II - 力扣&#xff08;LeetCode&#xff09; 一、题目 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链…

Python网络爬虫 学习笔记(1)requests库爬虫

文章目录Requests库网络爬虫requests.get()的基本使用框架requests.get()的带异常处理使用框架&#xff08;重点&#xff09;requests库的其他方法和HTTP协议&#xff08;非重点&#xff09;requests.get()的可选参数网络爬虫引发的问题&#xff08;非重点&#xff09;常见问题…

【C/C++】Windows下VS创建Linux项目

如果不想在Linux下用vim编写代码&#xff0c;可以在Windows下使用VS远程连接Linux&#xff08;Linux下是不支持安装使用VS的&#xff09;&#xff0c;将VS上编写的代码通过 SSH协议 推送到Linux下&#xff0c;注意文件编写是在Windows上进行的&#xff0c;编译是在Linux下进行的…

【Hello Linux】Linux工具介绍 (make/makefile git)

作者&#xff1a;小萌新 专栏&#xff1a;Linux 作者简介&#xff1a;大二学生 希望能和大家一起进步&#xff01; 本篇博客简介&#xff1a;介绍Linux的常用工具make/makefile git Linux项目自动化构建工具 – make/Makefile 背景 会不会写Makefile 从侧面说明了一个人是否具…

Spring Cloud微服务网关Gateway组件

目录 网关简介 什么是Spring Cloud Gateway Spring Cloud Gateway 功能特征 核心概念 工作原理 Spring Cloud Gateway快速开始 环境搭建 集成Nacos 路由断言工厂&#xff08;Route Predicate Factories&#xff09;配置 自定义路由断言工厂 过滤器工厂&#xff08; …

window 配置深度学习环境GPU

CUDA 11.6 CUDNN Anaconda pytorch 参考网址&#xff1a;https://zhuanlan.zhihu.com/p/460806048 阿里巴巴开源镜像站-OPSX镜像站-阿里云开发者社区 (aliyun.com) 电脑信息 RTX 2060 GPU0 1. CUDA 11.6 1.1 确认信息 C:\Users\thzn>nvidia-smi &#xff08;CUDA Versi…

秒杀项目之消息推送

目录一、创建消费者二、创建订单链路配置2.1 定义RabbitMQ配置类2.2 创建RabbitmqOrderConfig配置类三、如何实现RabbitMQ重复投递机制3.1 开启发送者消息确认模式3.2 消费发送确认3.2.1 创建ConfirmCallBack确认模式3.2.2 创建ReturnCallBack退回模式3.3 创建生产者3.4 创建消…

金三银四必看软件测试面试题,上百家公司面试都是这些套路

自我介绍说一下测试用例嗯&#xff0c;做测试&#xff0c;好多时间是在琢磨分析测试用例怎么去写&#xff0c;这个每个公司规范可能不太一样&#xff0c;但是大致思想是一致的。都是想要通过测试用例&#xff0c;把每一个分析到位&#xff0c;进行测试。就拿我上家公司来说吧&a…

数据库(2)--加深对统计查询的理解,熟练使用聚合函数

一、内容要求 利用sql建立学生信息数据库&#xff0c;并定义以下基本表&#xff1a; 学生&#xff08;学号&#xff0c;年龄&#xff0c;性别&#xff0c;系号&#xff09; 课程&#xff08;课号&#xff0c;课名&#xff0c;学分&#xff0c;学时&#xff09; 选课&#xff0…

融云入围「2022 云办公平台 TOP50」,进入「中国协同办公产业图谱」

2 月 10 日&#xff0c;中国科学院旗下《互联网周刊》颁布“2022 云办公平台 TOP50”&#xff0c;融云荣登榜单。 2 月 13 日&#xff0c;艾瑞咨询发布《2023 年中国协同办公行业研究报告》&#xff08;下简称《报告》&#xff09;&#xff0c;对协同办公行业的供需动态和迭代方…

黑马】后台管理-项目优化和上线

一。项目优化优化1&#xff0c;加载进度条显示安装一个运行依赖&#xff0c;nprogress然后导包&#xff0c;调用对象展示和隐藏在main中基于拦截器实现展示进度条和隐藏进度条的效果如果触发请求拦截器&#xff0c;证明发起请求&#xff0c;希望展示进度条&#xff0c;如果触发…

消防应急照明和疏散指示系统——集中控制型系统的设计与应用

安科瑞 李亚俊 V:Acrel8757 摘要&#xff1a;伴随着建筑领域的良好发展&#xff0c;建筑工程建设越来越复杂&#xff0c;相应的消防配套设施也越来越先进&#xff0c;火灾发生时&#xff0c;人在燃烧产生的噪音和烟气中会产生恐惧、不安等不良的心理状态&#xff0c;进而影响他…

NLP篇章2:理解Transformer

Transformer编码&#xff0c;解码大的结构的理解 编码部分&#xff0c;每一个的小编码器的输入是前一个小编码器的输出&#xff0c; 而每一个小解码器的输入不光是它的前一个解码器的输出&#xff0c;还包括了整个编码部分的输出。 self-attention 自注意力机制 顾名思义就是…

【数据库】 MySQL备份恢复

目录 MySQL日志管理 一&#xff0c; MySQL日志类型 二&#xff0c;错误日志 三&#xff0c; 通用查询日志 四&#xff0c; 慢查询日志 五&#xff0c;二进制日志 1&#xff0c;开启日志 2&#xff0c;二进制日志的管理 3&#xff0c;日志查看 5&#xff0c;二进制日志还原数据…