【C++】继承最全解析(什么是继承?继承有什么用?)
目录
一、前言
二、什么是继承 ?
💢继承的概念💢
💢继承的定义💢
🥝定义格式
🍇继承权限
三、基类与派生类对象的赋值转换
四、继承的作用域
五、派生类中的默认成员函数
💢默认成员函数的调用 💢
🔥构造函数与析构函数🔥
🔥拷贝构造🔥
🔥赋值运算符重载 🔥
💢显示成员函数的调用 💢
🔥构造函数 🔥
🔥拷贝构造 🔥
🔥赋值运算符重载 🔥
🔥析构函数🔥
六、继承与友元
七、继承与静态成员
八、菱形继承
💧 单继承💧
💧 多继承💧
💧 菱形继承💧
🍍概念
🍉现象
九、继承和组合
十、继承的总结和反思
十一、共勉
一、前言
继承是 面向对象三大特性之一(封装、继承、多态),所有的面向对象(OO)语言都具备这三个基本特征,封装相关概念已经在《类和对象》系列中介绍过了,今天主要学习的是继承,即如何在父类的基础之上,构建出各种功能更加丰富的子

二、什么是继承 ?
什么是继承?是继承 -- 遗产 还是继承 -- 花呗?答案都不是,先来看看官方解释:
继承(inheritance)机制是 ----面向对象程序设计使代码可以复用的重要的手段,它允许程序员在保持原有基类(父类)特性的基础上进行扩展,增加功能,这样产生新的类,称为派生类(子类)
💢继承的概念💢

继承相关概念:
- 被继承对象:父类 / 基类 (
base) - 继承方:子类 / 派生类 (
derived)
继承的本质 就是 ------------ 复用代码
举个例子 : 假设我现在要设计一个校园管理系统,那么肯定会设计很多角色类,比如学生、老师、保安、保洁等等之类的。
设计好以后,我们发现,有些数据和方法是每个角色都有的,而有些则是每个角色独有的。
为了复用代码、提高开发效率,可以从各种角色中选出共同点,组成 基类,比如每个 人 都有姓名、年龄、联系方式等基本信息,而 教职工 与 学生 的区别就在于 管理与被管理,因此可以在 基类 的基础上加一些特殊信息如教职工号 表示 教职工,加上 学号 表示学生,其他细分角色设计也是如此
这样就可以通过 继承 的方式,复用 基类 的代码,划分出各种 子类

像上面共同拥有的数据和方法我们可以重新设计一个类Person ,然后让 Student 和 Teacher 去继承它,如下:
// 大众类 --- 基础属性
class Person
{
public:Person(string name = string(), string tell = string(), int age = int()):_name(name),_tell(tell),_age(age){}void Print(){cout << "我的名字是 :" << _name << endl;cout << "我的电话是 :" << _tell << endl;cout << "我的年龄是 :" << _age << endl;}
protected:string _name; // 姓名string _tell; // 电话int _age; // 年龄
};// 学生类 --- 派生/子属性
class Student : public Person
{
public:Student(int stuId = 1578):Person("XAS","123456789",26),_stuId(stuId){cout << "我是一个学生" << endl;cout << "以下是我的个人信息 :" << endl;cout << endl;cout << "我的学号为 :" << _stuId << endl;}
protected:int _stuId; // 学号
};// 老师类 --- 派生/子属性
class Teacher : public Person
{
public:Teacher(int workId = 2024916):Person("xas","987654321",26),_workId(workId){cout << "我是一个老师" << endl;cout << "以下是我的个人信息 :" << endl;cout << endl;cout << "我的工号为 :" << _workId << endl;}
protected:int _workId; // 工号
};int main()
{Student s;s.Print();cout << "---------------------" << endl;cout << "---------------------" << endl;Teacher t;t.Print();return 0;
}
继承后,父类的 Person 的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了Student 和 Teacher 复用了 Person


💢继承的定义💢
了解完 继承相关概念 后,就可以开始学习 使用继承 了

🥝定义格式
格式如下: Person 是 父类,也称作基类; Student 是 子类,也称作派生类。
格式为 :子类 : 继承方式 父类,比如 class a : public b 就表示 a 继承了 b,并且还是 公有继承

注:Java 中的继承符号为 extern,而 C++ 中为 :
🍇继承权限
继承有权限的概念,分别为:公有继承(public)、保护继承(protected)、私有继承(private)
没错,与 类 中的访问 限定修饰符 一样,不过这些符号在这里表示 继承权限
简单回顾下各种限定符的用途
- 公有 public:公开的,任何人都可以访问
- 保护 protected:保护的,只有当前类和子类可以访问
- 私有 private:私有的,只允许当前类进行访问
权限大小:公有 > 保护 > 私有
保护 protected 比较特殊,只有在 继承 中才能体现它的价值,否则与 私有 作用一样

此时我们发现 ---- 访问权限:三种 继承权限:三种

根据排列组合,可以列出以下多种搭配方案
| 父类成员 / 继承权限 | public | protected | private |
父类的 public 成员 | 外部可见,子类中可见 | 外部不可见,子类中可见 | 外部不可见,子类中可见 |
父类的 protected 成员 | 外部不可见,子类中可见 | 外部不可见,子类中可见 | 外部不可见,子类中可见 |
父类的 private 成员 | 都不可见 | 都不可见 | 都不可见 |
注:所谓的--外部--其实就是 子类对象
总结:
- 无论是哪种继承方式,父类中的
private成员始终不可被 [子类 / 外部] 访问;当外部试图访问父类成员时,依据min(父类成员权限, 子类继承权限),只有最终权限为public时,外部才能访问 - 在实际运用中一般使用都是 public 继承,几乎很少使用 protetced/private 继承,也不提倡使用 protetced/private继承,因为 protetced/private 继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。
如何证明呢? 通过一下的代码,我们来验证上面的结论!!
// 父类
class A
{
public:int _a;
protected:int _b;
private:int _c;
};// 子类
class B : public A
{
public:B(){cout << _a << endl;cout << _b << endl;cout << _c << endl;}
};int main()
{// 外部(子类对象)B b; b._a;
}
- public 继承

- protected 继承

- private 继承

之所以说
C++的继承机制设计复杂了,是因为protected和private继承时的效果一样
其实 C++ 中搞这么多种情况(9种)完全没必要,实际使用中,最常见到的组合为 public : public 和 protected : public
如何优雅的使用好 ---- 继承权限 ?
对于只想自己类中查看的成员,设为 private,对于想共享给子类使用的成员,设为 protected,其他成员都可以设为 public
比如在张三家中,张三家的房子面积允许公开,家庭存款只限家庭成员共享,而个人隐私数据则可以设为私有
class Home
{
public:int area = 500; //500 平米的大房子
};class Father : public Home
{
protected:int money = 50000; //存款五万
private:int privateMoney = 100; //私房钱,怎能公开?
};class Zhangsan : public Father
{
public:Zhangsan(){cout << "我是张三" << endl;cout << "我知道我家房子有 " << area << " 平方米" << endl;cout << "我也知道我家存款有 " << money << endl;cout << "但我不知道我爸爸的私房钱有多少" << endl;}
};class Xiaoming
{
public:Xiaoming(){cout << "我是小明" << endl;cout << "我只知道张三家房子有 " << Home().area << " 平方米" << endl;cout << "其他情况我一概不知" << endl;}
};int main()
{Zhangsan z;cout << "================" << endl;Xiaoming x;return 0;
}

三、基类与派生类对象的赋值转换
在继承中,允许将 子类 对象直接赋值给 父类,但不允许 父类 对象赋值给 子类
- 这其实很好理解,儿子以后可以当父亲,父亲还可以当儿子吗?
并且这种 赋值 是非常自然的,编译器直接处理,不需要调用 赋值重载 等函数

派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。
(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;return 0;
}
可以看到,当父类对象是一个指针的时候,照样可以赋值过去:

子类对象赋值给父类指针切片图:

(3) 子类对象可以赋值给父类引用
//基类
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& rp = s;return 0;
}
可以看到,当父类对象是一个引用的时候,也可以赋值过去:

子类对象赋值给父类引用切换图片:

(4) 父类对象不能赋值给子类对象
//基类
class Person
{
public:string _name; // 姓名string _sex; // 性别int _age; // 年龄
};//派生类
class Student : public Person
{
public:int _id;
};int main()
{Student s;Person p;s = p;return 0;
}
编译会报错:

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

代码示例: Student 的 _num 和 Person的 _num 构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆。
// 基类
class Person
{
protected:string _name = "Edison"; // 姓名int _num = 555; // 身份证号
};// 派生类
class Student : public Person
{
public:void Print(){cout << "姓名:" << _name << endl;cout << "学号:" << _num << endl;}
protected:int _num = 888; // 学号
};int main()
{Student s1;s1.Print();return 0;
}
运行可以看到,访问的是子类中的_num (类似于局部优先的原则)

那么如果我想访问父类中的_num 呢 ? 可以使用基类 :: 基类成员显示的去访问 :
// 基类
class Person
{
protected:string _name = "Edison"; // 姓名int _num = 555; // 身份证号
};// 派生类
class Student : public Person
{
public:void Print(){cout << "姓名:" << _name << endl;cout << " 身份证号:" << Person::_num << endl;}
protected:int _num = 888; // 学号
};int main()
{Student s1;s1.Print();return 0;
}
可以看到,此时就是访问的父类中的 _num

还有一点需要注意的是 : 如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
// 基类
class A
{
public:void fun(){cout << "A::func()" << endl;}
};// 派生类
class B : public A
{
public:void fun(int i){cout << "B::func()" << endl;cout << "func(int i)->" << i << endl;}
};int main()
{B b;b.fun(10);return 0;
}
可以看到,默认是去调用子类的 fun() 函数,因为成员函数满足函数名相同就构成隐藏。

如果想调用父类的 fun() 还是需要指定作用域
// 基类
class A
{
public:void fun(){cout << "A::func()" << endl;}
};// 派生类
class B : public A
{
public:void fun(int i){cout << "B::func()" << endl;cout << "func(int i)->" << i << endl;}
};int main()
{B b;b.A::fun();return 0;
}
运行可以看到,此时就是调用父类中的 fun()

注意 :B 中的 fun 和 A 中的 fun 不是构成函数重载,而是隐藏 !函数重载的要求是在同一作用域里面!!
另外,在实际中在继承体系里面最好不要定义同名的成员。
五、派生类中的默认成员函数
派生类(子类)也是 类,同样会生成 六个默认成员函数(用户未定义的情况下)
不同于单一的 类,子类 是在 父类 的基础之上创建的,因此它在进行相关操作时,需要为 父类 进行考虑

这里我们只以下面的两个类为基础,讨论四类默认成员函数:构造函数、拷贝构造、赋值运算符重载、析构函数
class Person
{
public:Person(const std::string name = std::string(), const int age = 18, const int sex = 1): _name(name), _age(age), _sex(sex){std::cout << "Person()" << std::endl;}Person(const Person& p): _name(p._name), _age(p._age), _sex(p._sex){std::cout << "Person(const Person& p)" << std::endl;}Person& operator= (const Person& p){std::cout << "operator= (const Person& p)" << std::endl;if (this != &p){_name = p._name;}return *this;}~Person(){std::cout << "~Person()" << std::endl;}
protected:std::string _name;int _age = 18;int _sex = 1;
};class Student : public Person
{
public:
protected:long long _st_id;
};
💢默认成员函数的调用 💢
🔥构造函数与析构函数🔥
int main()
{Student st;return 0;
}
output:
Person() // s1 的构造
~Person() // s1 的析构
说明了,派生类的默认构造函数和默认析构函数都会 -----自动调用基类的构造和析构
🔥拷贝构造🔥
int main()
{Student st1;Student st2(st1);return 0;
}
output:
Person() // s1 的构造函数
Person(const Person& p) // s2 拷贝构造
~Person() // s2 的析构函数
~Person() // s1 的析构函数
说明了,派生类的默认拷贝构造会自动调用基类的拷贝构造
🔥赋值运算符重载 🔥
int main()
{Student st1, st2;st1 = st2;return 0;
}
output:
Person() // s1 的拷贝构造
Person() // s2 的拷贝构造
operator= (const Person& p) // 赋值重载
~Person() // s2 的析构函数
~Person() // s1 的析构函数
说明了,派生类的默认赋值运算符重载会自动调用基类的赋值运算符重载
实际上,我们可以将派生类的成员分成三部分:基类成员、内置类型成员、自定义类型成员
-
继承相较于我们以前学的类和对象,可以说就是多了基类那一部分
-
当调用派生类的默认成员函数时,对于基类成员都会调用对应基类的默认成员函数来处理
💢显示成员函数的调用 💢
🔥构造函数 🔥
当实现派生类的构造函数时,就算不显示调用基类的构造,系统也会自动调用基类的构造:
class Student : public Person
{
public:Student(long long st_id = 111): _st_id(st_id){std::cout << "Student()" << std::endl;}
protected:long long _st_id;
};
int main()
{Student st;return 0;
}
output:
Person()
Student()
~Person()
如果需要显示的调用基类的构造函数,应该这样写:
Student(long long st_id = 111): _st_id(st_id), Person("xas", 18)
{std::cout << "Student()" << std::endl;
}
特别注意:
Person("xas", 18)如果放在初始化列表,那就使显式调用基类的构造函数;如果放在函数体内,那就使创建一个基类的匿名对象
🔥拷贝构造 🔥
当实现派生类的拷贝构造时,如果没有显式调用基类的拷贝构造,那么系统就会自动调用基类的构造
class Student : public Person
{
public:Student(long long st_id = 111): _st_id(st_id), Person("xas", 18){std::cout << "Student()" << std::endl;}Student(const Student& s): _st_id(s._st_id){std::cout << "Student(const Student& s)" << std::endl;}
protected:long long _st_id;
};
int main()
{Student st1;Student st2(st1);return 0;
}
output:
Person()
Student()
Person() //系统自动调用了基类的构造
Student(const Student& s)
~Person()
~Person()
也可以像显示调用构造函数一样显式调用基类的拷贝构造:
Student(const Student& s): Person(s), _st_id(s._st_id)
{std::cout << "Student(const Student& s)" << std::endl;
}
🔥赋值运算符重载 🔥
在实现派生类的赋值运算符重载时,如果没有显式调用基类的赋值运算符重载,系统也不会自动调用基类的赋值运算符重载
class Student : public Person
{
public:Student(long long st_id = 111): _st_id(st_id), Person("xas", 18){std::cout << "Student()" << std::endl;}Student& operator= (const Student& s){std::cout << "operator= (const Student& s)" << std::endl;if (this != &s){_st_id = s._st_id;}return *this;}
protected:long long _st_id;
};
int main()
{Student st1, st2;st1 = st2;return 0;
}
output:
Person()
Student()
Person()
Student()
operator= (const Student& s)
~Person()
~Person()
因此,在实现派生类的赋值运算符重载时,必须显示调用基类的赋值运算符重载:
Student& operator= (const Student& s)
{std::cout << "operator= (const Student& s)" << std::endl;if (this != &s){Person::operator=(s); //由于基类和派生类的赋值运算符重载构成隐藏,因此要用 :: 指定类域_st_id = s._st_id;}return *this;
}
🔥析构函数🔥
在实现派生类的析构函数时,不要显式调用基类的析构函数,系统会在派生类的析构完成后自动调用基类的析构
class Student : public Person
{
public:Student(long long st_id = 111): _st_id(st_id), Person("xas", 18){std::cout << "Student()" << std::endl;}~Student(){std::cout << "~Student()" << std::endl;}
protected:long long _st_id;
};int main()
{Student st1;return 0;
}
output:
Person()
Student()
~Student()
~Person()
- 和前面的默认成员函数不同,在实现派生类的析构时,基类的析构不能显式调用
- 这是因为,如果显示调用了基类的析构,就会导致基类成员的资源先被清理,如果此时派生类成员还访问了基类成员指向的资源就,就会导致野指针问题
- 因此,必须保证析构顺序为先子后父,保证数据访问的安全
六、继承与友元
友元关系不能继承,也就是说 基类友元不能访问子类私有和保护成员,只能访问自己的私有和保护成员。
下面代码中,Display 函数是基类 Person 的友元,但是 Display 函数不是派生类 Student 的友元,也就是说 Display 函数无法访
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;
}
可以看到运行会报错:

如果想让 Display 函数也能够访问派生类Student 的私有和保护成员,只需要在派生类Student 当中进行友元声明。
class Student;class Person
{
public:friend void Display(const Person& p, const Student& s); // 声明Display是Person的友元
protected:string _name; // 姓名
};class Student : public Person
{
public:friend void Display(const Person& p, const Student& s); // 声明Display是Student的友元
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 成员实例。
下面代码中,在基类 Person 当中定义了静态成员变量 _ count, 派生类 Student 和 Graduate 继承了Person, 但是,在整个继承体
// 基类
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;Person s;cout << " 人数 :" << Person::_count << endl;cout << " 人数 :" << Student::_count << endl;cout << " 人数 :" << s4._count << endl;return 0;
}
我们定义了5个对象,那么每定义一个对象都会去调用一次++ _count ,打印以后可以看到,这几个对象里面的 _count 都是一样的:

同时,我们还可以打印一下地址,可以看到也是同-个:

总结: 关于父类中的静态成员,子类继续下来以后都是同一个,类似于“传家宝"。
八、菱形继承
💧 单继承💧
一个子类只有一个直接父类时称这个继承关系为单继承

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

💧 菱形继承💧
C++支持多继承,即支持一个子类继承多个父类,使其基础信息更为丰富,但凡事都有双面性,多继承 在带来巨大便捷性的同时,也带来了个巨大的坑:菱形继承问题
🍍概念
首先
C++允许出现多继承的情况,如下图所示

这样看很正常是吧,但如果出现以下这种 重复继承 的情况,就比较麻烦了

此时 普通人X 会纠结于使用哪一个 不用吃饭 的属性!这对于编译器来说,是一件无法处理的事
🍉现象
将上述概念转化为代码,观察实际现象
注:多继承时,只需要在 父类 之后,添加
,号,继续增加想要继承的父类
class Person
{
public:string _name; //姓名
};//本科生
class Undergraduate : public Person
{};//研究生
class Postgraduate : public Person
{};//毕业生
class Graduate : public Undergraduate, public Postgraduate
{};int main()
{Graduate g1;g1._name = "zhangsan";return 0;
}
无法编译!

原因分析:
Undergraduate 中继承了 Person 的 _name,Postgraduate 也继承了 Person 的 _name
Graduate 多继承 Undergraduate 、Postgraduate 后,同时拥有了两个 _name,使用时,无法区分!

通过监视窗口查看信息:

解决方法:
想要解决二义性很简单,通过 :: 限制访问域即可
Graduate g1;
g1.Undergraduate::_name = "zhangsan";
cout << g1.Undergraduate::_name << endl;

但这没有从本质上解决问题!而且还没有解决数据冗余问题
真正的解决方法:虚继承
注:虚继承是专门用来解决 菱形继承 问题的,与多态中的虚函数没有直接关系
虚继承:在菱形继承的腰部继承父类时,加上
virtual关键字修饰被继承的父类
class Person
{
public:string _name; //姓名
};//本科生
class Undergraduate : virtual public Person
{};//研究生
class Postgraduate : virtual public Person
{};//毕业生
class Graduate : public Undergraduate, public Postgraduate
{};int main()
{Graduate g1;g1._name = "zhangsan";cout << g1._name << endl;return 0;
}
此时可以解决 菱形继承 的 数据冗余 和 二义性 问题

虚继承是如何解决菱形继承问题的?
- 利用 虚基表 将冗余的数据存储起来,此时冗余的数据合并为一份
- 原来存储 冗余数据 的位置,现在用来存储 虚基表指针
此时无论这个 冗余 的数据存储在何处,都能通过 基地址 + 偏移量 的方式进行访问
九、继承和组合
public 继承是一种 is-a 的关系。也就是说每个派生类对象都是一个基类对象。
而 组合 是一种 has-a 的关系。假设 B 组合了 A,每个 B对象中都有一个 A对象。
举个例子: 轿车和奔驰就构成 is-a 的关系,所以可以使用继承。
// 车类
class Car
{
protected:string _colour = "黑色"; // 颜色string _num = "川A66688"; // 车牌号
};// 奔驰
class Benz : public Car
{
public:void Drive(){cout << "好开-操控" << endl;}
};
再举个例子:汽车和轮胎之间就是 has-a 的关系,它们之间则适合使用组合。
// 轮胎
class Tire {
protected:string _brand = "Michelin"; // 品牌size_t _size = 17; // 尺寸};// 汽车
class Car {
protected:string _colour = "黑色"; // 颜色string _num = "川A66688"; // 车牌号Tire _t; // 轮胎
};
实际项目中,更推荐使用 组合 的方式,这样可以做到 解耦,避免因父类的改动而直接影响到子类
- 公有继承:
is-a—> 高耦合,可以直接使用父类成员- 组合:
has-a—> 低耦合,可以间接使用父类成员
十、继承的总结和反思
1. 很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度及性能上都有问题。
2. 多继承可以认为是C++的缺陷之一,很多后来的面向对象语言都没有多继承,如Java。
十一、共勉
以下就是我对 【C++】继承 的理解,如果有不懂和发现问题的小伙伴,请在评论区说出来哦,同时我还会继续更新对 【C++】多态 的理解,请持续关注我哦!!!

相关文章:
【C++】继承最全解析(什么是继承?继承有什么用?)
目录 一、前言 二、什么是继承 ? 💢继承的概念💢 💢继承的定义💢 🥝定义格式 🍇继承权限 三、基类与派生类对象的赋值转换 四、继承的作用域 五、派生类中的默认成员函数 💢…...
STM32-外部中断浅析
本篇解释了STM32中断原理 MCU为什么需要中断 中断,是嵌入式系统中很重要的一个功能,在系统运行过程中,当出现需要立刻处理的情况时,暂停当前任务,转而处理紧急任务,处理完毕后,恢复之前的任务…...
Spring-Data-Elasticsearch
简介 Spring Data for Elasticsearch 是 Spring Data 项目的一部分,该项目旨在为新数据存储提供熟悉且一致的基于 Spring 的编程模型,同时保留特定于存储的特性和功能。 Spring Data Elasticsearch 项目提供了与 Elasticsearch 搜索引擎的集成。Spring…...
代码随想录二刷7.22|977.有序数组的平方
暴力解法: ——如果想暴力解决这个问题的话,可以像题目那样,先将每一个元素平方,然后再排序 双指针: ——从题目中找到的信息:这是一个非递减顺序的整数数组,从例子中,可以容易看…...
redis介绍与布署
redis remote dictionary server(远程字典服务器) 是一个开源的,使用c语言编写的非关系型数据库,支持内存运行并持久化,采用key-value的存储形式。 单进程模型意味着可以在一台服务器上启动多个redis进程,…...
PMON的解读和开发
提示:龙芯2K1000PMON相关记录 文章目录 1 PMON的发展和编译环境PMONPMON2000 2 PMON2000的目录结构3 Targets目录的组成4 PMON编译环境的建立5 PMON2000的框架6 异常向量表7 Pmon的空间分配8 PMON的汇编部分(starto.S或sbdreset.S)的解读Start.SC代码部分dbginit 9 …...
初识c++(构造函数,析构函数,拷贝构造函数,赋值运算符重载)
一、类的默认函数 默认成员函数就是用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数。 #include<iostream> using namespace std; class Date { public:Date(){_year 1;_month 1;_day 1;cout << _year << "/" <&…...
CANoe:为什么两个VLAN接口不能设置同一个网络的IP地址呢?
经常玩CANoe的人应该配置过TCP/IP Stack中网络节点的网卡信息,基本的信息包含:MAC地址、IP地址、子网掩码、默认网关、MTU值、IPv6地址。 如果你想让发送出去的报文携带VLAN tag,可以在网卡上添加VLAN tag信息。 此时你就能得到两个新的网卡V…...
SpringBoot新手快速入门系列教程七:基于一个低配centoos服务器,如何通过宝塔面板部署一个SpringBoot项目
1,如何打包一个项目 通过IDEA自带的命令行,执行 ./gradlew clean build 2,检查生成的JAR文件 进入 build/libs 目录,你应该会看到一个类似 helloredis-0.0.1-SNAPSHOT.jar 的文件。 3:运行生成的JAR文件 你可以在…...
性能测试的流程(企业真实流程详解)(二)
性能测试的流程 1.需求分析以及需求确定(指标值,场景,环境,人员) 一般提出需求的人员有:客户,产品经理,项目组领导等 2.性能测试计划和方案制定 基准测试: 负觋测试: 压力测试: 稳定性测试: 其他:配置测试…...
使用sklearn的基本流程
scikit-learn,通常简称为 sklearn,是一个开源的Python库,是基于 Python 编程语言的一个非常流行的机器学习库。它建立在 NumPy 和 SciPy 这两个科学计算库之上,并与 Matplotlib 配合使用,为数据预处理、模型训练、评估…...
力扣题解(乘积为正数的最长子数组长度)
1567. 乘积为正数的最长子数组长度 已解答 中等 给你一个整数数组 nums ,请你求出乘积为正数的最长子数组的长度。 一个数组的子数组是由原数组中零个或者更多个连续数字组成的数组。 请你返回乘积为正数的最长子数组长度。 本题要求乘积为正数,而整…...
PPTP、L2TP、IPSec、IPS 有什么区别?
随着互联网的发展,保护网络通信的安全越来越重要。PPTP、L2TP、IPSec、IPS是常见的网络安全协议和技术,在保护网络通信安全方面发挥着不同的作用和特点。下面介绍PPTP、L2TP、IPSec、IPS之间的区别。 点对点隧道协议(PPTP)是一种用…...
SpringBoot注解--11--@JSONField @JsonProperty
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 一个问题:后端实体类isXXX开头的属性,传到前端后自动去掉is解决方法: JsonProperty和JSONField1.简介2.注解的区别2.1 底层框架不…...
C语言 | Leetcode C语言题解之第221题最大正方形
题目: 题解: int maximalSquare(char** matrix, int matrixSize, int* matrixColSize){int dp[301][301]{0};int wid0;if(matrixSize0&&matrixColSize[0]0){return 0;}for(int i0;i<matrixSize;i){for(int j0;j<matrixColSize[0];j){if(m…...
AI数据服务如何驱使AI商业化,实现在各行业落地融合
AI技术经历数十载的发展,正在加速向各行各业渗透。近年来深度学习加速了人工智能技术的商业化落地的同时,也带来了大量AI算法训练需求,推动AI数据服务市场的快速增长。作为AI快速发展的“基石”,AI数据服务AI数据服务如何驱使AI在…...
户用光伏项目开发流程
1、收集业主信息 管理业主基本信息,包括但不限于联系方式、地址、房屋信息等。 2、业主开卡 每户都需要办理银行卡,用于电费结算和划转。 3、合同签约 业主开卡完成之后,平台方发起签约(支持线上签约),…...
C++ 函数返回值是引用类型使用场景
目录 1、希望返回函数内部的局部静态变量或全局变量 2、希望通过函数返回一个对象的成员变量 3、希望实现链式操作 4、避免对象的拷贝 5、需要注意的事项 在C中,函数的返回值可以是引用类型的情况主要有以下几种: 1、希望返回函数内部的局部静态变…...
CUDA原子操作
代码 #include <cuda_runtime.h> #include <stdio.h>__global__ void atomicAddAndGet(int *result, int *valueToAdd) {// 原子加法int addedValue atomicAdd(result, *valueToAdd);// 通过原子操作后读取值,确保是加法后的值addedValue *valueToAd…...
08.C2W3.Auto-complete and Language Models
往期文章请点这里 目录 N-Grams: OverviewN-grams and ProbabilitiesN-gramsSequence notationUnigram probabilityBigram probabilityTrigram ProbabilityN -gram probabilityQuiz Sequence ProbabilitiesProbability of a sequenceSequence probability shortcomingsApproxi…...
RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...
04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...
云原生安全实战:API网关Kong的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关(API Gateway) API网关是微服务架构中的核心组件,负责统一管理所有API的流量入口。它像一座…...
HubSpot推出与ChatGPT的深度集成引发兴奋与担忧
上周三,HubSpot宣布已构建与ChatGPT的深度集成,这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋,但同时也存在一些关于数据安全的担忧。 许多网络声音声称,这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...
Python 高效图像帧提取与视频编码:实战指南
Python 高效图像帧提取与视频编码:实战指南 在音视频处理领域,图像帧提取与视频编码是基础但极具挑战性的任务。Python 结合强大的第三方库(如 OpenCV、FFmpeg、PyAV),可以高效处理视频流,实现快速帧提取、压缩编码等关键功能。本文将深入介绍如何优化这些流程,提高处理…...
