C++之继承

news2025/1/15 21:02:51

文章目录

  • 一、继承的基本理解
    • 1.继承的概念
    • 2.继承的定义
  • 二、基类和派生类对象赋值转换
  • 三、继承中的作用域
  • 四、派生类的默认成员函数
  • 五、继承与友元
  • 六、继承与静态成员
  • 七、复杂的菱形继承及菱形虚拟继承
    • 1.继承关系
    • 2.菱形继承存在数据冗余和二义性的问题
    • 3.虚拟继承可以解决菱形继承数据冗余和二义性的问题
    • 4.虚拟继承解决菱形继承数据冗余和二义性的原理
  • 八、继承的总结和反思

一、继承的基本理解

1.继承的概念

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。

继承是类设计层次的复用

简单图示:

在这里插入图片描述

一个简单例子:

// 父类
class Person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
	}

protected:
	string _name = "Peter";  // 姓名
	int _age = 20;           // 年龄
};

// 继承后父类Person的成员(成员函数+成员变量)都会变成子类的一部分
// 这里体现出了Student和Teacher复用了Person的成员。
// 可以通过监视窗口查看Student和Teacher对象
// 可以看到变量的复用。调用Print可以看到成员函数的复用。

// 子类
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;
}

查看监视窗口:
在这里插入图片描述

2.继承的定义

定义格式:
在这里插入图片描述

继承关系和访问限定符:

在这里插入图片描述


派生类继承基类成员后,基类成员在派生类中的访问方式:

  • 基类的私有成员在派生类中都是不可见的。
  • 基类的其他成员在子类的访问方式 == min(基类成员的访问限定符,派生类的继承方式),public > protected > private 。

用一张表来描述,就是:

基类成员/派生类的继承方式public 继承protected 继承private 继承
基类的 public 成员派生类的 public 成员派生类的 protected 成员派生类的 private 成员
基类的 protected 成员派生类的 protected 成员派生类的 protected 成员派生类的 private 成员
基类的 private 成员在派生类中不可见在派生类中不可见在派生类中不可见

继承影响的是继承下来的基类成员,跟子类成员无关,不要混淆。

说明:

  1. 基类 private 成员在派生类中无论以什么方式继承都是不可见的。这里的不可见,是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制了派生类对象不管是在类内还是类外都不能去访问它。换言之,如果父类有一个成员,不想让子类使用,可以在父类中把该成员定义为 private 。
  2. 基类 private 成员在派生类中是不能被访问。如果基类成员不想在类外被直接访问,但需要在派生类中能访问,就定义为 protected 。可以看出 protected 成员限定符是因继承才出现的。
  3. ① 关于继承方式,在实际运用中一般都是使用 public 继承,几乎不使用 protected/private 继承,也不提倡使用 protected/private 继承,因为 protected/private 继承下来的成员都只能在派生类的类内使用,实际中扩展维护性不强。
    ② 关于访问限定符,在实际运用中一般都是使用 public/protected ,几乎不使用 private 。
    换言之,实际中最常见的是,父类成员使用 public/protected 修饰,子类的继承方式使用 public 继承。9 种关系里面只用了 2 种。
  4. 使用 class 时默认的继承方式是 private ,使用 struct 时默认的继承方式是 public ,不过最好还是显示地写出继承方式,这样更直观。

二、基类和派生类对象赋值转换

  • 派生类对象可以赋值给基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割,寓意把派生类中父类那部分切来赋值过去,仅限于 public 继承

  • 基类对象不能赋值给派生类对象。
    基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用,但这很危险,存在越界访问的风险,不要这么用。这里基类如果是多态类型,可以使用 RTTI(Run-Time Type Identification)的 dynamic_cast 来进行识别后进行安全转换。

测试代码:

class Person
{
protected:
	string _name;  // 姓名
	string _sex;   // 性别
	int _age;      // 年龄
};

class Student : public Person
{
public:
	int _No;  // 学号
};

int main()
{
	Person p;
	Student s;

	// 子类对象可以赋值给父类的对象/父类的指针/父类的引用
	// 赋值兼容 -> 切割/切片
	// 这里不存在类型转换,是语法天然支持的行为
	p = s;
	Person* pp = &s;
	Person& rp = s;

	// 父类对象不能赋值给子类对象
	//s = p;  s = (Student)p;  // 怎样都不行
	// 父类的指针或者引用可以通过强制类型转换赋值给子类的指针或者引用
	// 但这很危险,存在越界访问的风险,不要这么用
	Student* ps = (Student*)&p;
	//ps->_No = 1;  
	Student& rs = (Student&)p;
	//rs._No = 2;   

	return 0;
}

将上述代码图示:在这里插入图片描述

三、继承中的作用域

  • 在继承体系中基类和派生类都有独立的作用域
  • 子类和父类中若有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏 / 重定义(成员函数的隐藏,只需要函数名相同就构成隐藏,跟参数无关)。若要在子类中访问被隐藏的父类同名成员,需要指明父类类域显示访问。
  • 实际中,在继承体系里面最好不要定义同名的成员。

说明:
 ① 类的成员,包括成员变量和成员函数。
 ② 构成隐藏 / 重定义之后,到底是操作父类还是子类的成员,本质上还是要看作用域,跟同名的局部变量和全局变量类似,都是就近原则。

测试代码1:

// Student的_num和Person的_num构成隐藏/重定义关系
// 可以看出这样的代码虽然能跑,但是非常容易混淆
class Person
{
protected:
	string _name = "小李子";  // 姓名
	int _num = 111;          // 身份证号
};

class Student : public Person
{
public:
	void Print()
	{
		cout << "姓名:" << _name << endl;
		cout << _num << endl;          // 就近原则
		cout << Person::_num << endl;  // 若要访问父类同名成员,需要指明类域
	}

protected:
	int _num = 999;  // 学号
};

int a = 0;

int main()
{
	int a = 1;
	cout << a << endl;    // 此处访问的是局部变量a,因为就近原则 
	cout << ::a << endl;  // 要想访问全局变量a,需指明作用域

	Student s;
	s.Print();

	return 0;
}

测试代码2:

// B中的func和A中的func不构成函数重载,因为不是在同一作用域
// B中的func和A中的func构成隐藏,成员函数满足函数名相同就构成隐藏
class A
{
public:
	void func()
	{
		cout << "func()" << endl;
	}

	void f()
	{
		cout << "f()" << endl;
	}
};

class B : public A
{
public:
	void func(int i)
	{
		cout << "func(int i)->" << i << endl;
	}
};

int main()
{
	B b;
	b.func(1);
	//b.func();   // 编译报错,因为父类A的func被隐藏了
	b.A::func();  // 若想调用被隐藏的父类同名成员函数,必须指明类域
	b.f();

	return 0;
}

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

先抛开继承不谈,类的构造函数和析构函数的行为分别是:
 ① 构造函数:先根据成员变量的声明次序在初始化列表中顺序完成成员变量的初始化,再执行函数体内的语句。
 ② 析构函数:先执行函数体内的语句,再根据成员变量的声明次序逆序完成成员变量的析构。

也就是说,类的构造和析构保证符合栈的后进先出

加入继承后,可以把继承下来的父类理解成子类的一个自定义类型成员变量,且在成员变量中的声明次序是顺序第一位。

测试代码1:

class Person
{
public:
	Person(const char* 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;
	}

protected:
	string _name;  // 姓名
};

class Student : public Person
{
public:

protected:
	int _num = 1;  // 学号
	string _s = "hello world";
};

// 派生类的重点的四个默认成员函数,我们不写,编译器默认生成的:

// 我们不写默认生成的派生类的构造和析构:
// a.父类继承下来的(调用父类默认构造和析构处理)   b.自己的(按普通类处理)
// 我们不写默认生成的派生类的拷贝构造和operator=:
// a.父类继承下来的(调用父类拷贝构造和operator=) b.自己的(按普通类处理)

// 总结:继承下来的调用父类处理,自己的按普通类处理

int main()
{
	Student s1;
	Student s2(s1);
	Student s3;
	s1 = s3;

	return 0;
}

测试代码2:

class Person
{
public:
	Person(const char* name)
		: _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;
		//delete[] _ptr;
	}

protected:
	string _name;  // 姓名
	//int* _ptr = new int[10];
};

class Student : public Person
{
public:
	Student(const char* name = "张三", int num = 4)
		:Person(name)
		, _num(num)
	{}

	// s2(s1)
	// 其实可以不写,默认生成的就可以了
	Student(const Student& s)
		:Person(s)  // 传参时将子类对象赋值给父类的引用,即切片
		,_num(s._num)
	{}

	// s2 = s1
	// 其实可以不写,默认生成的就可以了
	Student& operator=(const Student& s)
	{
		if (this != &s)
		{
			Person::operator=(s);  // 传参时将子类对象赋值给父类的引用,即切片
			_num = s._num;
		}

		return *this;
	}

	// 析构函数名字会被统一处理成destructor()
	// 那么子类的析构函数跟父类的析构函数就构成隐藏
	// 所以,要调用父类的析构函数,需要指明父类类域
	~Student()
	{
		//Person::~Person();
		//delete[] _ptr;
	}
	// 但是子类析构函数结束时,会自动调用父类的析构函数(保证符合栈的后进先出)
	// 所以我们自己实现子类析构函数时,不需要显式调用父类析构函数
	// 否则会调用两次父类析构函数,可能导致运行出错

protected:
	int _num;  // 学号
	//string _s = "hello world";
	//int* _ptr = new int[10];
};

// 什么情况下必须自己写子类的默认成员函数?
// 1.父类没有默认构造,需要显式写构造
// 2.若子类有资源需要释放,就需要显式写析构
// 3.若子类存在浅拷贝问题,就需要自己实现拷贝构造和赋值重载解决浅拷贝问题

// 若必须写子类的默认成员函数,如何写?
// 1.父类成员,调用父类的对应构造、拷贝构造、operator=和析构处理
// 2.自己成员,按普通类处理

// 总结:继承下来的调用父类处理,自己的按普通类处理

int main()
{
	Student s1;
	Student s2(s1);
	Student s3("Jack", 19);
	s1 = s3;

	return 0;
}

说明:

  1. 派生类对象初始化:一定是先调用基类构造再调用派生类构造。
  2. 派生类对象析构:一定是先调用派生类析构再调用基类析构。
  3. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同。那么编译器会对析构函数名统一进行特殊处理,处理成destructor(),所以父类析构函数不加 virtual 关键字的情况下,子类析构函数和父类析构函数构成隐藏关系。

五、继承与友元

友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。

测试代码:

class Student;

class Person
{
	friend void Display(const Person& p, const Student& s);
public:

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;
}

六、继承与静态成员

若基类定义了 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;  // 研究科目
};

int main()
{
	Person p;
	Student s;
	Graduate g;

	cout << Person::_count << endl;
	cout << Student::_count << endl;
	cout << Graduate::_count << endl;

	cout << &Person::_count << endl;
	cout << &Student::_count << endl;
	cout << &Graduate::_count << endl;

	return 0;
}

七、复杂的菱形继承及菱形虚拟继承

1.继承关系

  • 单继承:一个子类只有一个直接父类时称这个继承关系为单继承。
    在这里插入图片描述

  • 多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承。
    在这里插入图片描述

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

2.菱形继承存在数据冗余和二义性的问题

菱形继承存在数据冗余二义性的问题。

比如下面的对象成员模型构造:
在这里插入图片描述
在 Assistant 对象中 Person 成员会有两份,若要访问 _name ,是访问从 Student 继承过来的还是访问从 Teacher 继承过来的呢?所以说 Assistant 存在数据冗余和二义性的问题。

class Person
{
public:
	string _name;  // 姓名
	int _a[10000];
};

class Student : public Person
{
public:
	int _num;  // 学号
};

class Teacher : public Person
{
public:
	int _id;  // 职工编号
};

class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse;  // 主修课程
};

int main()
{
	Assistant a;
	a._num = 1;
	a._id = 2;
	
	//a._name = "张三";  // 编译报错,对_name访问不明确
	//指明父类类域,可以解决二义性的问题
	a.Student::_name = "小张";
	a.Teacher::_name = "张老师";

	//但是数据冗余的问题无法解决,万一数据很大就会浪费掉很多空间
	cout << sizeof(a) << endl;
	
	return 0;
}

3.虚拟继承可以解决菱形继承数据冗余和二义性的问题

虚拟继承可以解决菱形继承数据冗余和二义性的问题。

比如上面的继承关系,在 Student 和 Teacher 继承 Person 时使用虚拟继承,即可解决问题。

class Person
{
public:
	string _name;  // 姓名
	int _a[10000];
};

class Student : virtual public Person   // 虚拟继承 
{
public:
	int _num;  // 学号
};

class Teacher : virtual public Person   // 虚拟继承
{
public:
	int _id;  // 职工编号
};

class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse;  // 主修课程
};

int main()
{
	Assistant a;
	// 虚拟继承可以解决菱形继承的数据冗余和二义性的问题
	a.Student::_name = "小张";
	a.Teacher::_name = "张老师";
	a._name = "张三";

	cout << sizeof(a) << endl;

	return 0;
}

4.虚拟继承解决菱形继承数据冗余和二义性的原理

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

测试代码1:不使用虚拟继承。

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;
}

下图是菱形继承的内存对象成员模型:这里可以看到数据冗余。在这里插入图片描述


测试代码2:使用虚拟继承。

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;
	d._a = 0;
	
	return 0;
}

下图是菱形虚拟继承的内存对象成员模型:这里可以分析出 D 对象中将 A 放到了对象组成的最下面,这个 A 是 B 和 C 的公共基类,那么 B 和 C 如何去找到公共的 A 呢?这里是通过了 B 和 C 里的两个指针,各指向一张表。这两个指针叫虚基表指针,这两张表叫虚基表。虚基表中存有偏移量,通过偏移量可以找到 A 。
在这里插入图片描述
A 是被虚拟继承的基类,称之为虚基类。B 或 C 需要找 A ,就要通过虚基表中的偏移量进行计算。

八、继承的总结和反思

  1. 很多人说 C++ 的语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,最好不要设计出菱形继承。
  2. 多继承可以认为是 C++ 的缺陷之一,后来的很多语言都没有多继承,如 Java 。
  3. 继承和组合
    ① public 继承是一种 is-a 的关系。也就是说每个派生类对象都是一个基类对象。
    组合是一种 has-a 的关系。假设 B 组合了 A ,每个 B 对象中都有一个 A 对象。
    继承和组合都能达到复用的目的。
    优先使用对象组合,而不是类继承
    ③ 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。
    ④ 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。
    ⑤ 实际尽量多去用组合,因为组合的耦合度低,代码维护性好。有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,也可以用组合,优先用组合。
// BMW和Benz与Car构成is-a的关系
class Car 
{
protected:
	string _colour = "白色";    // 颜色
	string _num = "粤A XXX00";  // 车牌号
};

class BMW : public Car 
{
public:
	void Drive() { cout << "好开-操控" << endl; }
};

class Benz : public Car 
{
public:
	void Drive() { cout << "好坐-舒适" << endl; }
};
// Car和Tire构成has-a的关系
class Tire 
{
protected:
	string _brand = "Michelin";  // 品牌
	size_t _size = 17;           // 尺寸
};

class Car 
{
protected:
	string _colour = "白色";     // 颜色
	string _num = "粤A XXX00";  // 车牌号
	Tire _t;                   // 轮胎
};

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

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

相关文章

前端优化原理篇(生命周期)

1&#xff0c; 性能评估模型 对于前端的性能的评判 主要是以下四个方面&#xff1a; 2&#xff0c;性能测量工具 1&#xff0c;浏览器的performarce功能 指路可看链接 2&#xff0c;lighthouse工具 3&#xff0c;生命周期 网站 页面的整个生命周期&#xff0c;通俗的讲&a…

移动端App 页面秒开优化总结

前言 App优化&#xff0c;是一个工作、面试或KPI都绕不开的话题&#xff0c;如何让用户使用流畅呢&#xff1f;今天谨以此篇文章总结一下过去两个月我在工作中的优化事项到底有那些&#xff0c;优化方面还算小白&#xff0c;有不对的地方还望指出海涵, 该文章主要通过讲述Nati…

CSS入门三、盒子模型

零、文章目录 文章地址 个人博客-CSDN地址&#xff1a;https://blog.csdn.net/liyou123456789个人博客-GiteePages&#xff1a;https://bluecusliyou.gitee.io/techlearn 代码仓库地址 Gitee&#xff1a;https://gitee.com/bluecusliyou/TechLearnGithub&#xff1a;https:…

力扣sql基础篇(四)

力扣sql基础篇(四) 1 每位学生的最高成绩 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 1.2 示例sql语句 # rank()函数间隔排序 若前两个字段值相同且都是并列的第一名,那么后面的一个非连续数字就是第三名 如 1 1 3 SELECT e.student_id,e.course_id,e.grade FROM…

ISO12233分辨率测试卡分类及功能说明

概述相机图像分辨率的测试&#xff0c;依据的标准是ISO 12233. 目前分为 ISO12233:2000 ,ISO12233:4000, ISO12233:2014.目前很多厂家已经开始使用新的ISO标准&#xff0c;淘汰了十几年前的“落伍”标准&#xff0c;而更新成了ISO12233&#xff1a;2014。新的分辨率测试标板是由…

代码随想录算法训练营第四期第五十六天 | 583. 两个字符串的删除操作、72. 编辑距离、编辑距离总结篇

583. 两个字符串的删除操作 # 给定两个单词word1和word2&#xff0c;返回使得word1和word2相同所需的最小步数。 # 每步可以删除任意一个字符串中的一个字符。 # # 示例 1&#xff1a; # 输入: word1 "sea", word2 "eat" # 输出: 2 # 解释: 第一步将 &…

安顺控股冲刺A股上市:拟募资6.5亿元,九成收入来自天然气销售

近日&#xff0c;安顺控股股份有限公司&#xff08;下称“安顺控股”&#xff09;递交招股书&#xff0c;准备在上海证券交易所主板上市。本次冲刺上市&#xff0c;安顺控股计划募资6.50亿元&#xff0c;将用于溧阳市城镇燃气高压管网二期项目、溧阳市美丽乡村天然气利用项目一…

openstack私有网络

1.前情回顾 目前环境使用的是Provider网络现在需要将其修改为Self-service网络&#xff0c;类似于公有云的vpc网络 2.流程 1.控制节点配置修改 1.修改/etc/neutron/neutron.conf的[DEFAULT]区域 # 原来的配置 # core_plugin ml2 # service_plugins # # 修改后的配置 c…

GitHub Enterprise Server 存在授权不当漏洞(CVE-2022-46258)

漏洞描述 GitHub Enterprise Server 是一个面向开源及私有软件项目的托管平台&#xff0c;GitHub scope 用于限制 OAuth token 的访问范围。 在 GitHub Enterprise Server 中&#xff0c;除非提交位于同一存储库的不同分支中且和 Workflow files 内容相同的 Workflow 文件 &a…

【学习】Reptile、梯度下降的LSTM、Siamese Network、原型网络、匹配网络、关系网络

文章目录ReptileRNNLSTM梯度下降的LSTM基于度量的方法Siamese NetworkN-way Few/One-shot Learning原型网络匹配网络关系网络虚拟数据的少量学习Train Test as RNNReptile RNN LSTM RNN的变形 加入门 梯度下降的LSTM GD看似像简化的LSTM 可以让机器自动学习这些zf和zi …

sql的where使用运算后的列后报错

sql的where语句中如果使用了经过运算处理后的某个列的话会报错&#xff0c;例如&#xff1a;上面红框部分就是经过运算后的列&#xff0c;但这个语法是错误的&#xff0c;但如果想通过运算后的某个列来筛选条件&#xff0c;应该怎么办&#xff1f;可以使用嵌套查询&#xff1a;…

SpringBoot实践(三十八):自定义spring-boot-starter

目录 自动配置原理 自定义starter包 导入springboot的自动配置依赖 测试业务代码 spring.factories配置 ​编辑 本地包上传 使用自定义starter依赖 测试和配置 自动配置原理 基于springBoot的starter机制能够让我们在使用外部包时候非常方便&#xff0c;只需要引入该组…

PCB学习笔记—3D PCB封装的创建

放置3D元件体&#xff1a;常规的、自定义、圆柱形、球体。第一个是厚度&#xff0c;第二个是焊盘和芯片的悬浮高度。一般电阻的高度设置成0.6mm就够了&#xff0c;电容1.25mm&#xff0c;悬浮高度为0。按键&#xff1a;放置3D元件体&#xff0c;Tab键&#xff0c;选择常规&…

快过年静不下心?不如刷刷《剑指offer》静一静(第七天)

跟着博主一起刷题 这里使用的是题库&#xff1a; https://leetcode.cn/problem-list/xb9nqhhg/?page1 目录剑指 Offer 49. 丑数剑指 Offer 51. 数组中的逆序对剑指 Offer 55 - I. 二叉树的深度剑指 Offer 49. 丑数 剑指 Offer 49. 丑数 我一开始的思路是&#xff0c;把数字1~无…

机器学习HW15元学习

文章目录一、简介Task: Few-shot Classification实验1、simple2、medium3、strong4、boss三、代码模型构建准备工作一、简介 任务对象是Omniglot数据集上的few-shot classification任务&#xff0c;内容是利用元学习找到好的初始化参数。 Task: Few-shot Classification The…

在VSCode中使用Compaq Visual Fortran编译运行Frotran程序

本片文章主要是为了使用VSCode编译运行带QuickWin的老版本Fortran代码。 一、准备工作 安装VSCode和Compaq Visual Fortran6.6。 二、配置Fortran工程 用VSCode打开保存有Frotran代码的文件夹 建立.vscode文件夹&#xff0c;建立launch.json和task.json文件&#xff0c;分…

二、TCP/IP---Ethernet和IP协议

TCP/ip协议栈 OSI模型TCP/IP协议栈应用层&#xff0c;表示层&#xff0c;会话层应用层传输层主机到主机层&#xff08;传输层&#xff09;网络层网络层数据链路层&#xff0c;物理层网络接入层 Ethernet协议 以太网&#xff0c;实现链路层的数据传输和地址封装&#xff08;MA…

马蹄集 三角形坐标

三角形坐标 难度&#xff1a;青铜 ○时间限制&#xff1a;1秒 巴占用内存&#xff1a;64M 输入三角形三个顶点A,B,C的坐标(x,y),根据公式计算并输出三 角形面积。 S1/2*X1y2X2y33y1-X1y3-X2y1-x3y2 #include <bits/stdc.h> using namespace std; int main(){double x[4],…

Win10应用商店无法加载错误0x80072F7D怎么办?

Win10应用商店无法加载错误0x80072F7D怎么办&#xff1f;有用户开启电脑的Win10软件商店想要获取软件的时候&#xff0c;发现软件页面无法进行正常的加载&#xff0c;里面的内容显示为错误代码0x80072F7D。那么这个情况怎么去进行解决呢&#xff1f;一起来看看详细的解决方法分…

PMP证书到期了,有必要续吗?

我觉得续证是有需要的&#xff0c;毕竟证书有用的地方很多。 下面我们将从两方面分享&#xff1a; 1. PMP 证书在国内的含金量怎么样&#xff1f; 2. HR 如何看待 PMP 证书&#xff1f; 说到 PMP 证书的含金量&#xff0c;相信这个问题是所有人都关心的。对于如何来评判 PMP…