C++好难(8):C++中的继承

news2024/9/23 7:18:06

目录

1.继承的概念及定义

🍉继承的概念

🍉 继承的定义:

🍒格式定义:

 🍒继承关系和访问限定符

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

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

3.继承中的作用域:

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

5.继承与友元

6.继承与静态成员

 7.复杂的菱形继承记菱形虚拟继承

🍉单继承

🍉多继承

 🍉菱形继承:

 🍉菱形继承问题:

🍉虚拟继承:

🍉菱形继承的原理:

🍒二义性和冗余的原因:

🍒虚拟继承的原理

 8.继承的总结

🍉组合与继承

🍒组合是什么?

🍒组合与继承的关系

9.有关继承的考点和问题:


1.继承的概念及定义

🍉继承的概念

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。

继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。

举个例子:假如我们现在需要设计一个校园管理系统,那么我们肯定会设计很多不同的角色类来方便我们进行管理,比如学生类、老师类、辅导员类、保安类等等。

设计好之后,我们会发现,有些数据和方法是每个角色都有的,而有些则是每个角色独有的。

如上设计中,姓名、电话是每个人都会有的。而学生有专业和学生ID、老师有老师的ID

这时我们就可以设计一个Person类,将姓名和电话都放进去,用学生和老师去继承它。

#include<iostream>
#include<string>
using namespace std;

//基类/父类
class person
{
public:
	void print()
	{
		cout << "name: " << _name << endl;
		cout << "name: " << _number << endl;
	}
protected:
	string _name;	//姓名
	int _number;	//电话
};
class student :public person
{
private:
	string _major;	//学生专业
	int _stID;		//学生ID
};	

class teacher :public person
{
private:
	int _teaID;		//老师ID
};

继承后父类的成员,_name,_number,print()都会变为子类的一部分(成员函数+成员变量)这里体现出了 student teacher 复用了 person 的成员

🍉 继承的定义:

🍒格式定义:

如下格式:person 是父类,也称基类;student 是子类,也称派生类

 🍒继承关系和访问限定符

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

对于上面的表格,我们不用去硬记,只要记住:继承是按访问限定符最小的来取的

总结:

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

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

  • (1)派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用
//基类
class Person
{
public:
	string _name; // 姓名
	string _sex; // 性别
	int _age; // 年龄
};

//派生类
class Student : public Person
{
public:
	int _id;
};

int main()
{
	Person p;
	Student s;

	s._name = "张三";
	s._sex = "男";
	s._age = 20;
	s._id = 8888;

	p = s; // 子类对象赋值给父类对象

	return 0;
}

通过调试可以看到,在子类定义的元素都传给了父类,但由于父类没有 _id 这个变量,所以就不会发生传递:

这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去,如图:

  • (2)父类对象可以通过  指针  或  引用  接受子类对象
//基类
class Person
{
public:
	string _name; // 姓名
	string _sex; // 性别
	int _age; // 年龄
};

//派生类
class Student : public Person
{
public:
	int _id;
};


int main()
{
	Student s;

	s._name = "张三";
	s._sex = "男";
	s._age = 20;
	s._id = 8888;
    
    Person& p = s    // 通过引用赋值
	Person* p = &s;  // 通过指针赋值

	return 0;
}

  • (3)基类对象不能赋值给派生类对象
//基类
class Person
{
public:
	string _name; // 姓名
	string _sex; // 性别
	int _age; // 年龄
};

//派生类
class Student : public Person
{
public:
	int _id;
};

int main()
{
	Person p;
	Student s;

	p._name = "张三";
	p._sex = "男";
	p._age = 20;

	s = p; 

	return 0;
}

  • (4)基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。
//基类
class Person
{
public:
	string _name = "Edison"; // 姓名
	string _sex = "男"; // 性别
	int _age = 20; // 年龄
};

//派生类
class Student : public Person
{
public:
	int _id;
};

int main()
{
	Student s;
	
	Person* pp = &s;
	Student* ps1 = (Student*)pp;
	ps1->_id = 10;

	return 0;
}

 这种情况是可以赋值的

但是必须是基类的指针是指向派生类对象时才是安全的。

这里基类如果是多态类型,
可以使用  RTTI(Run-Time Type Information) 的 dynamic_cast 来进行识别后进行安全转换。

3.继承中的作用域:

  • 1. 在继承体系中基类和派生类都有独立的作用域。
  • 2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。
  • 3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
  • 4. 注意在实际中在继承体系里面最好不要定义同名的成员。

代码示例:下面代码中的 Student_num Person _num 构成隐藏关系

// 基类
class Person
{
protected:
	string _name = "moyu"; // 姓名
	int _num = 369; // 身份证号
};

// 派生类
class Student : public Person
{
public:
	void Print()
	{
		cout << "姓名:" << _name << endl;
		cout << "学号:" << _num << endl;
	}
protected:
	int _num = 963; // 学号
};

int main()
{
	Student s1;
	s1.Print();

	return 0;
}

运行可以看到,访问的是子列中的 _num ,这时因为局部优先原则

那如果我要访问父类的 _num呢?这时就可以用到作用域限定符::去显示的访问

// 基类
class Person
{
protected:
	string _name = "moyu"; // 姓名
	int _num = 369; // 身份证号
};

// 派生类
class Student : public Person
{
public:
	void Print()
	{
		cout << "姓名:" << _name << endl;
		cout << "学号:" << Person::_num << endl;
	}
protected:
	int _num = 963; // 学号
};

int main()
{
	Student s1;
	s1.Print();

	return 0;
}

需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏

只需要函数名相同就可以!!!

// 基类
class A
{
public:
	void fun()
	{
		cout << "A::func()" << endl;
	}
};
// 派生类
class B : public A
{
public:
	void fun(int i)
	{
		cout << "B::func()" << endl;
	}
};

int main()
{
	B b;
	b.fun(1);

	A a;
	a.fun();

	return 0;
}

如果要调用父类的,还是需要指定作用域

b.A::fun();

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

派生类的默认成员函数共有6个,“默认”的意思就是我们不写,编译器也会自动生成一个,如图:

这几个成员函数的生成规则如下:

  • 1)派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。
    如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
  • 2)派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
  • 3)派生类的operator=必须要调用基类的operator=完成基类的复制。
  • 4)派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。
    因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
  • 5)派生类对象初始化先调用基类构造再调派生类构造。
  • 6)派生类对象析构清理先调用派生类析构再调基类的析构。

基类成员函数:

// 基类
class Person
{
public:
    // 构造函数
    Person(const char* name = "moyu")
        : _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:
    // 构造函数
    Student(const char* name, int num)
        : Person(name) // 父类的成员必须去调父类的构造函数去初始化
        , _num(num)
    {
        cout << "Student()" << endl;
    }

    // 拷贝构造
    Student(const Student& s)
        : Person(s)
        , _num(s._num)
    {
        cout << "Student(const Student& s)" << endl;
    }

    // 赋值重载
    Student& operator = (const Student& s)
    {
        cout << "Student& operator= (const Student& s)" << endl;
        if (this != &s)
        {
            Person::operator =(s);
            _num = s._num;
        }
        return *this;
    }

    // 析构函数
    ~Student()
    {
        cout << "~Student()" << endl;
    }
protected:
    int _num; //学号
};

注意:子类析构函数不需要去调用父类的析构,因为父子类的析构函数存在隐藏关系!

因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同。

那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加 virtual的情况下,子类析构函数和父类析构函数构成隐藏关系。

5.继承与友元

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

class Student;

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

6.继承与静态成员

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

大家可以看看下面的代码:

// 基类
class Person
{
public:
	Person() 
	{ 
		++_count; 
	}
	static int _count; // 统计人的个数。

//protected:
	string _name; // 姓名
};

// 静态成员在类外面定义
int Person::_count = 0;

// 派生类
class Student : public Person
{
protected:
	int _stuNum; // 学号
};

int main()
{
	Person p;
	Student s;

	cout << &(p._name) << endl;
	cout << &(s._name) << endl;

	cout << endl;

	cout << &(p._count) << endl;
	cout << &(s._count) << endl;

	cout << endl;

	cout << " 人数 :" << Person::_count << endl;
	Student::_count = 0;
	cout << " 人数 :" << Person::_count << endl;

	return 0;
}

通过地址可以清晰的看到,父类成员和子类成员不是同一个,而在被 static 定义的静态成员都始终是同一个

 7.复杂的菱形继承记菱形虚拟继承

🍉单继承

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

🍉多继承

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

 🍉菱形继承:

菱形继承是多继承的一种特殊情况

 

 🍉菱形继承问题:

菱形继承会产生数据冗余和数据的二义性问题。

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

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


class Student :  public Person
{
protected:
	int _num; //学号
};
class Teacher :  public Person
{
protected:
	int _id; // 职工编号
};


//形成菱形继承
class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修课程
};



int main()
{
	Assistant a;
    // 这样会有二义性无法明确知道访问的是哪一个
	a._name = "moyu";    // 出错

	// 显示指定访问哪个父类的成员
	a.Student::_name = "a";
	a.Teacher::_name = "b";


	return 0;
}

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

通过查看其地址就可以看出:

🍉虚拟继承:

虚拟继承用来解决菱形继承的二义性和数据冗余的问题

如图的继承关系,如下代码,在 Student 和 Teacher 继承 Person 时使用虚拟继承,即可解决问题。需要注意:虚拟继承不要再其他地方使用

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 = "moyu";

	return 0;
}

我们现在可以打印地址看看:

 可以看到,此时不管是继承 Student 的 _name 还是继承 Teacher 的 _name 都是指向同一块区域,是同一个变量

🍉菱形继承的原理:

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

🍒二义性和冗余的原因:

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 对象中有两个 _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;
}

也可以看看下图更清楚的解释

 

 8.继承的总结

  • 很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度及性能上都有问题。
  • 多继承可以认为是C++的缺陷之一,很多后来的面向对象的语言都没有多继承,如Java。
     

🍉组合与继承

🍒组合是什么?

class A
{};

class B 
{
private:
	C _c;
};

上面的代码就是一个组合,也就是再一个类里面创建另一个类的对象

🍒组合与继承的关系

继承的父类与子类是一种   is - a   的关系。也就是说每个子类对象都是一个父类对象。

组合是一种   has - a   的关系。假设 B 组合了 A ,每个 B 对象中给都有一个 A 对象。

举例说明:

跑车 和 兰博基尼 就构成   is - a 的关系,所以可以使用继承

// 跑车
class Scar
{
protected:
	string _colour = "黑色"; // 颜色
	string _num = "AE86"; // 车牌号
};

// 兰博基尼
class lbjn : public Scar
{
public:
	void Drive()
	{
		cout << "帅" << endl;
	}
};

汽车 和 轮胎 之间的关系就是   has - a    的关系,他们之间更适用于组合关系

// 轮胎
class Tire 
{
protected:
	string _brand = "Michelin"; // 品牌
	size_t _size = 17; // 尺寸

};

// 汽车
class Car 
{
protected:
	string _colour = "黑色"; // 颜色
	string _num = "AE86"; // 车牌号
	Tire _t; // 轮胎
};

注意:如果两者的关系既适用于组合   has - a    的关系,有适用于继承   is - a 的关系,那么建议使用对象的组合,而不是类的继承

为什么呢?原因如下:

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

注意:模块与模块之间的关系应该遵循:低耦合,高内聚!

9.有关继承的考点和问题:

1. 什么是菱形继承?菱形继承的问题是什么?

2. 什么是菱形虚拟继承?如何解决数据冗余和二义性的?

3. 继承和组合的区别?什么时候用继承?什么时候用组合?

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

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

相关文章

分隔链表(大小链表的连接)

题目&#xff1a; 给你一个链表的头节点 head 和一个特定值 x &#xff0c;请你对链表进行分隔&#xff0c;使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。 你应当 保留 两个分区中每个节点的初始相对位置。 输入&#xff1a;head [1,4,3,2,5,2], x 3 输出&…

前端Vue自定义加载loading组件 通过设置gif实现loading动画 可用于页面请求前loading

随着技术的发展&#xff0c;开发的复杂度也越来越高&#xff0c;传统开发方式将一个系统做成了整块应用&#xff0c;经常出现的情况就是一个小小的改动或者一个小功能的增加可能会引起整体逻辑的修改&#xff0c;造成牵一发而动全身。 通过组件化开发&#xff0c;可以有效实现…

Altermanager安装和使用

1、Altermanager使用 1.1 什么是Alertmanager Alertmanager 与 Prometheus 是相互分离的两个组件&#xff0c;Prometheus 服务器根据报警规则将警报发送给 Alertmanager&#xff0c;然后 Alertmanager 将 silencing、inhibition、aggregation 等消息通过电子邮件、dingtalk …

【每日算法】【219. 存在重复元素 II】

☀️博客主页&#xff1a;CSDN博客主页 &#x1f4a8;本文由 我是小狼君 原创&#xff0c;首发于 CSDN&#x1f4a2; &#x1f525;学习专栏推荐&#xff1a;面试汇总 ❗️游戏框架专栏推荐&#xff1a;游戏实用框架专栏 ⛅️点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd;&…

LabVIEW-Solidworks联合仿真

介绍 NI Softmotion介绍 LabVIEW NI SoftMotion 采用高级功能块API (应用程序接口) 便于编写运动程序&#xff0c;该功能块API基于由PLCopen 定义的 Motion Control Library 。SoftMotion 包括用于直线、弧线和轮廓运动的功能块及用于进行电子传动、电子凸轮等高级运行的功能块…

Oracle 的删除

前言 最近在工作中需要将一台电脑上的 Oracle &#xff0c;删除 Oracle 与删除 MySQL 不太一样&#xff0c;删除 Oracle 还需要删除注册表中的内容&#xff0c;不然重新安装的时候会报错。 关闭服务 win r 开启运行窗口&#xff0c;输入 services.msc 打开服务列表。 将 O…

【Redis故障排查】「连接失败问题排查和解决」带你深入分析一下Redis阻塞原因以及问题排查方案指南

Redis阻塞原因以及问题排查 尽管我们在日常工作中经常使用Redis作为数据库的缓存&#xff0c;以大大减轻数据库压力并提升用户体验&#xff0c;但Redis也可能出现阻塞情况&#xff0c;导致整个系统变慢&#xff0c;进而影响用户体验。 因此&#xff0c;在面对Redis阻塞的情况…

傅里叶变换通俗理解,附python代码

傅里叶变换可以简单理解为用一系列三角函数去拟合一个目标函数。为什么可以用三角函数拟合&#xff1f;因为三角函数 是一组正交基。 先来回顾一下正交的概念&#xff0c;在二维平面坐标系中&#xff0c;与这两个单位向量&#xff0c;正交&#xff08;内积为0&#xff0c;相互…

单机模型并行最佳实践

单机模型并行最佳实践 模型并行在分布式训练技术中被广泛使用。 先前的帖子已经解释了如何使用DataParallel在多个 GPU 上训练神经网络&#xff1b; 此功能将相同的模型复制到所有 GPU&#xff0c;其中每个 GPU 消耗输入数据的不同分区。 尽管它可以极大地加快训练过程&#x…

MySQL的基本操作及实用示例(面试真题老实用了)

一、MySQL基本操作 常用语句 MySQL是一个常用的关系型数据库管理系统&#xff0c;可以用于存储和管理数据。下面是MySQL的一些基本操作&#xff1a; 1. 连接到MySQL服务器&#xff1a; mysql -u username -p 其中&#xff0c;username是你的用户名&#xff0c;执行该命令后会…

【软件分析/静态分析】chapter5 课程07 过程间分析(Interprocedural Analysis)

&#x1f517; 课程链接&#xff1a;李樾老师和谭天老师的&#xff1a; 南京大学《软件分析》课程07&#xff08;Interprocedural Analysis&#xff09;_哔哩哔哩_bilibili 目录 第五章 过程间分析 5.1 为什么需要过程间分析 5.2 Call Graph 5.2.1 调用图的概念 5.2.2 调…

2023 年 GitHub 上最火的 Java 面试宝典正式上线

笔记特点&#xff1a;条理清晰&#xff0c;含图像化表示更加易懂。 内容概要&#xff1a;包括 Java 集合、JVM、多线程、并发编程、设计模式、Spring 全家桶、Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、MongoDB、Redis、MySQL、RabbitMQ、Kafka、Linux、Ne…

Vue实现拖拽鼠标圈选、划区域、框选组件sgRectSelect:矩形区域选中checkbox,并回调相关选中、取消选中的操作

边框线虚线动画效果请参阅边框虚线滚动动画特效_虚线滚动效果_你挚爱的强哥的博客-CSDN博客【代码】边框虚线滚动动画特效。_虚线滚动效果https://blog.csdn.net/qq_37860634/article/details/130507289 碰撞检测原理请前往 原生JS完成“一对一、一对多”矩形DIV碰撞检测、碰撞…

车载以太网 - SomeIP - 协议用例 - RPC

目录 RPC Protocol specification 1、Cleint和Server端应该为一个服务实例的所有的Methodsevents使用一个TCP连接

AtCoder Regular Contest 163 C. Harmonic Mean(构造 补写法)

题目 t(t<500)组case&#xff0c; 给定一个数n(n<500)&#xff0c;构造一个长为n的数组 思路来源 官方题解 题解 注意到 ... 右边累加&#xff0c;等于1-最后一项&#xff0c;可以把最后一项挪到左边 所以&#xff0c; 1. 当n没有在前面的序列里出现过时&#xf…

centos7.6安装mysql

卸载mariadb 解决安装mysql与mariadb冲突问题&#xff08;卸载干净mariadb&#xff09;_何妨徐行的博客-CSDN博客 安装rpm包前可能需要的命令&#xff1a; yum install openssl-devel用于管理rpm包的工具 yum install lrzsz -y 文件传输缺乏rz 下载安装包 去mysql官网 把…

<Linux开发>驱动开发 -之- Linux I2C 驱动

&#xff1c;Linux开发&#xff1e;驱动开发 -之- Linux I2C 驱动 交叉编译环境搭建&#xff1a; &#xff1c;Linux开发&#xff1e; linux开发工具-之-交叉编译环境搭建 uboot移植可参考以下&#xff1a; &#xff1c;Linux开发&#xff1e; -之-系统移植 uboot移植过程详细…

[MMDetection]VOC数据格式转为COCO数据格式

以下脚本可以根据创建VOC格式数据集转换为COCO数据集 其中文件组织格式如下 VOC2007 ------Annotations ------***********.xml ------***********.xml -------ImageSets ------train.txt ------test.txt -------JPEGImages ------***********.jpg ------***********.jpg CO…

【Spring】SpringBoot参数验证10个技巧

Yan-英杰的主页 悟已往之不谏 知来者之可追 C程序员&#xff0c;2024届电子信息研究生 目录 前言 1.使用验证注解 2 使用自定义验证注解 3 在服务器端验证 4 提供有意义的错误信息 5 将 i18n 用于错误消息 6 使用分组验证 7 对复杂逻辑使用跨域验证 8 对验证错误使…

通过smtp发送邮件及执行异常解决

在日常中遇到了需要实现一个发送邮件的需求&#xff0c;完成之后记录下实现方法及自己遇到的一些问题及解决办法。 常用SMTP服务相关地址及端口 一、通过javax.mail实现发送邮件 1.引入相关坐标 <!-- 发送邮件--><dependency><groupId>org.projec…