【C++】深入浅出之继承
目录
- 继承的概念及定义
- 继承的定义
- 继承方式和访问限定符
- protected与private的区别
- 默认继承方式
- 继承类模板
- 基类和派生类对象赋值兼容转换
- 继承中的作⽤域(隐藏关系)
- 相关面试题⭐
- 派生类的默认成员函数⭐
- 构造函数
- 拷贝构造
- 赋值重载
- 析构函数
- 继承与友元
- 继承与静态成员
- 继承的方式
- 菱形继承的缺陷及解决方案
- 相关面试题 ⭐
- 继承与组合
继承的概念及定义
继承的概念
继承(inheritance)机制是面向对象程序设计使代码可以复用的重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称为派生类。
继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,而继承便是类设计层次的复用。
例如,以下代码中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的成员。
继承的定义
定义格式
继承的定义格式如下

- 说明: 在继承当中,父类也称为基类,子类是由基类派生而来的,所以子类又称为派生类。
继承方式和访问限定符
我们知道,访问限定符有以下三种:
- public访问
- protected访问
- private访问
而继承的方式也有一样的三种:
- public继承
- protected继承
- private继承
继承基类成员访问方式的变化
基类当中被不同访问限定符修饰的成员,以不同的继承方式继承到派生类当中后,该成员最终在派生类当中的访问方式将会发生变化。

稍作观察,实际上基类成员访问方式的变化规则也不是无迹可寻的,我们可以认为三种访问限定符的权限大小为:public > protected > private,基类成员访问方式的变化规则如下:
- 在基类当中的访问方式为public或protected的成员,在派生类当中的访问方式变为:Min(成员在基类的访问方式,继承方式)。
- 在基类当中的访问方式为private的成员,在派生类当中都是不可见的。
在派生类中不可见是什么意思
- 这句话的意思是,我们无法在派生类当中访问基类的private成员。例如,虽然Student类继承了Person类,但是我们无法在Student类当中访问Person类当中的private成员_name。t
//基类
class Person
{
private:string _name = "张三"; //姓名
};
//派生类
class Student : public Person
{
public:void Print(){//在派生类当中访问基类的private成员,error!cout << _name << endl; }
protected:int _stuid; //学号
};
- 也就是说,基类的private成员无论以什么方式继承,在派生类中都是不可见的,这里的不可见是指基类的私有成员虽然被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
注意: 在实际运用中一般使用的都是public继承,几乎很少使用protected和private继承,也不提倡使用protected和private继承,因为使用protected和private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。
protected与private的区别
在前面的类和对象章节中,我们说过我们暂时认为private修饰和protected修饰的成员变量和函数的效果一样,只能在类里面使用,不能在类外使用。
- 但是继承这里就会体现出protected和private的区别
- 基类private成员在派⽣类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派⽣类
中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
#include<iostream>
using namespace std;
class A
{
public:int _a = 1;
protected:int _b = 2;
};class B : public A
{
public:void func(){cout << _a << endl;cout << _b << endl;}
};
int main()
{B().func();return 0;
}

- 可以看到我们B类中的成员函数成功访问了继承下来的基类protected成员
默认继承方式
- 在使用继承的时候也可以不指定继承方式,使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public。
- 例如,在关键字为class的派生类当中,所继承的基类成员_name的访问方式变为private。
//基类
class Person
{
public:string _name = "张三"; //姓名
};
//派生类
class Student : Person //默认为private继承
{
protected:int _stuid; //学号
};
- 而在关键字为struct的派生类当中,所继承的基类成员_name的访问方式仍为public。
//基类
class Person
{
public:string _name = "张三"; //姓名
};
//派生类
struct Student : Person //默认为public继承
{
protected:int _stuid; //学号
};
继承类模板

- 为啥指定类域?
1基类是类模板的时候,虽然说stack实例化的时候,确实实例化vector这个类模板
2.但是模板是按需实例化,派生类里面的成员函数push想要复用基类的push_back,这个时候并没有实例化push_back的vector类型,所以指定类域。 - 接下来我们看一下和这个类似的问题
template<class Container>
void Print(const Container& c)
{Container::const_iterator it = c.begin();while (it != c.end()){cout << *it << " ";++it;}cout << endl;
}int main()
{vector<int> v1 = { 1,2,3,4 };list<int> l1 = { 10,20,30,40 };Print(v1);Print(l1);return 0;
}

- 为啥这里无法编译?
1.这里面指定类域,编译器认为,可能是静态成员变量,可能是内嵌类型(内部类)
2.所以就不能确定到底是类型还是其他两个,这时候编译器就把决定权交给程序员,typename表示是类型,或者用auto表示自动推到类型表示是类型。

基类和派生类对象赋值兼容转换
- 派生类对象可以赋值给基类的对象、基类的指针以及基类的引用,因为在这个过程中,会发生基类和派生类对象之间的赋值转换。
例如,对于以下基类及其派生类。
//基类
class Person
{
protected:string _name; //姓名string _sex; //性别int _age; //年龄
};
//派生类
class Student : public Person
{
protected:int _stuid; //学号
};
Student s;
Person p = s; //派生类对象赋值给基类对象
Person* ptr = &s; //派生类对象赋值给基类指针
Person& ref = s; //派生类对象赋值给基类引用
对于这种做法,有个形象的说法叫做切片/切割,寓意把派生类中基类那部分切来赋值过去。
派生类对象赋值给基类对象图示:

- 注意:注意: 基类对象不能赋值给派生类对象,基类的指针可以通过强制类型转换赋值给派生类的指针,但是此时基类的指针必须是指向派生类的对象才是安全的。(后面的章节会讲解)
继承中的作⽤域(隐藏关系)
- 在继承体系中的基类和派生类都有独立的作用域。若子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。
- 例如,对于以下代码,访问成员_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;
}- List item
- 若此时我们就是要访问父类当中的_num成员,我们可以使用作用域限定符进行指定访问。
void fun()
{cout << Person::_num << endl; //指定访问父类当中的_num成员
}
需要注意的是,如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
- 例如,对于以下代码,调用成员函数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不是构成函数重载,因为函数重载要求两个函数在同一作用域t,而此时这两个fun函数并不在同一作用域。为了避免类似问题,实际在继承体系当中最好不要定义同名的成员。注意在实际中在继承体系⾥⾯最好不要定义同名的成员。
相关面试题⭐
#include <iostream>
class A
{
public:void fun(){std::cout << "func()" << std::endl;}
};
class B : public A
{
public:void fun(int i){std::cout << "func(int i)" << i << std::endl;}
};int main()
{B b;b.fun(10);b.fun(); return 0;
}

- 根据我们上面隐藏的定义域,基类和派生类不同的作用域,成员的名字相同,就构成隐藏,所以选B
- 这里可不敢选A,重载是要求同一个作用域

- B类对象的fun函数和A类对象的fun函数构成隐藏,所以b类对象只能调用到他自己的fun函数,b类对象的fun函数是一个有参的函数,这里调用无参就会编译错误。所以选A
派生类的默认成员函数⭐
- 默认成员函数,即我们不写编译器会自动生成的函数,类当中的默认成员函数有以下六个:

构造函数
- 如果我们默认不写:子类的默认构造对于子类成员和内置类型(有缺省值就用,没有就是一个随机值)和自定义类型成员(去调用他的默认构造) + 父类成员(必须去调用父类的默认构造)
- 那要是我们的父类成员没有默认构造或者我们的子类默认构造需要初始化呢?(默认构造是一个无参的)
- 很明显这就不行了,所以我们要自己写一个构造函数
class Person
{
public:Person(const char* name): _name(name){cout << "Person()" << endl;}protected:string _name; // 姓名
};class Student : public Person
{
public:Student(int num, const char* address, const char* name):_num(num),_address(address),Person(name){cout << "Student()" << endl;}
protected:int _num; //学号string _address;//int* _ptr = new int[10];
};
- 这里对于父类成员name当成一个整体对象,就要像一个匿名对象一样去调用父类的构造
拷贝构造
- 那如果拷贝构造我们默认不写
- 拷贝构造:默认子类的拷贝构造对于子类成员和内置类型(值拷贝)和自定义类型(这个类型拷贝构造) + 父类成员(必须调用父类拷贝构造)
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;}
protected:string _name; // 姓名
};class Student : public Person
{
public:Student(int num, const char* address, const char* name):_num(num),_address(address),Person(name){cout << "Student()" << endl;}Student(const Student& s): Person(s), _num(s._num),_address(s._address){cout << "Student(const Student& s)" << endl;}protected:int _num; //学号string _address;//int* _ptr = new int[10];
};
- 对于这个我们父类和基类成员我们默认不写拷贝构造也是可以的,对于num内置类型值拷贝足够,_address自定义类型调用他的默认拷贝构造(库里面)完成深拷贝,对于父类调用他的拷贝构造name也是会去调用string的拷贝构造完成深拷贝。所以是可以的
- 但是如果说我们在派生类中开辟空间
- int* _ptr = new int[10];
- 这个时候值拷贝就不行了,这是一个内置类型的指针,如果只浅拷贝过去就会导致两个指针指向同一块空间释放空间释放两次和一个指针修改内容会影响另一个指针(因为他们同一块空间)
- 这里基类的拷贝构造也是一样的像匿名对象一样,整体调用
那有的人说,父类的拷贝构造应该传父类的对象,这里传派生类会有问题
Person(const Person& p): _name(p._name){cout << "Person(const Person& p)" << endl;}
- 那我们来看看父类的拷贝构造,这里是不是一个父类的引用,我们前面说了,父类的指针/引用接受子类的对象,会发生切片,把子类中的父类那部分切出来给父类的指针/引用。所以这里不用担心传子类对象会有问题
赋值重载
- 对于拷贝构造如果没有开辟内存空间,我们默认不写都可以,赋值重载也是
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;}protected:string _name; // 姓名
};class Student : public Person
{
public:Student(int num, const char* address, const char* name):_num(num),_address(address),Person(name){cout << "Student()" << endl;}Student(const Student& s): Person(s), _num(s._num),_address(s._address){cout << "Student(const Student& s)" << endl;}Student& operator = (const Student& s){cout << "Student& operator= (const Student& s)" << endl;if (this != &s){_num = s._num;_address = s._address;operator=(s);}return *this;}protected:int _num; //学号string _address;//int* _ptr = new int[10];
};
- 这里也是一样,父类的赋值重载的形参引用会对子类对象切片
- 那我们来测试一下
Student s1(18, "重庆", "张三");
Student s2(18, "四川", "李四");
s2 = s1;

- 怎么回事啊?怎么一直调用i子类的赋值重载呢?
- 这里就要提到我们隐藏的概念,派生类和基类成员同名,那么派生类的成员会屏蔽基类的成员去调用派生类的
- 这里的基类和派生类的赋值重载是不是都叫operator=?对吧
- 所以这里我们调用基类的构造要指定基类的类域
Student& operator = (const Student& s){cout << "Student& operator= (const Student& s)" << endl;if (this != &s){_num = s._num;_address = s._address;Person::operator=(s);}return *this;}
析构函数
- 析构:派生类析构对于子类成员和内置类型(不处理)和自定义类型(调用他的析构) + 父类成员(调用他的析构)
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;}// destructor()~Person(){cout << "~Person()" << endl;}protected:string _name; // 姓名
};class Student : public Person
{
public:Student(int num, const char* address, const char* name):_num(num),_address(address),Person(name){cout << "Student()" << endl;}Student(const Student& s): Person(s), _num(s._num),_address(s._address){cout << "Student(const Student& s)" << endl;}Student& operator = (const Student& s){cout << "Student& operator= (const Student& s)" << endl;if (this != &s){_num = s._num;_address = s._address;Person::operator=(s);}return *this;}// destructor()~Student(){// 不需要写,子类析构函数结束后,会自动调用父类析构//Person::~Person();cout << "~Student()" << endl;// delete[] _ptr;}protected:int _num; //学号string _address;//int* _ptr = new int[10];
};
- 因为多态中⼀些场景析构函数需要构成重写,重写的条件之⼀是函数名相同(这个我们多态章节会讲
解)。那么编译器会对析构函数名进⾏特殊处理,全部处理成destructor(),所以基类析构函数不加
virtual的情况下,派⽣类析构函数和基类析构函数构成隐藏关系。 - 那么构成隐藏关系我们就要指定类域来调用

- 但是这样不对啊,为啥调用了2次基类的析构函数,这是由于编译器规定对于派生类如果显示写析构函数不需要自己去调用父类的析构,编译器会自动调用,因为编译器要保证先析构派生类,再析构基类。
总结一下:
- 派生类的构造函数被调用时,会自动调用基类的构造函数初始化基类的那一部分成员,如果基类当中没有默认的构造函数,则必须在派生类构造函数的初始化列表当中显示调用基类的构造函数。
- 派生类的拷贝构造函数必须调用基类的拷贝构造函数完成基类成员的拷贝构造。
- 派生类的赋值运算符重载函数必须调用基类的赋值运算符重载函数完成基类成员的赋值。
- 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。
- 派生类对象初始化时,会先调用基类的构造函数再调用派生类的构造函数。
- 派生类对象在析构时,会先调用派生类的析构函数再调用基类的析构函数
继承与友元
- 友元关系不能继承,也就是说基类的友元可以访问基类的私有和保护成员,但是不能访问派生类的私有和保护成员。
- 感性理解:你爸爸的朋友和你是朋友,但是跟你不是朋友
例如,以下代码中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; //学号
};
继承与静态成员
若基类当中定义了一个static静态成员变量,则在整个继承体系里面只有一个该静态成员t。无论派生出多少个子类,都只有一个static成员实例。
例如,在基类Person当中定义了静态成员变量_count,尽管Person又继承了派生类Student和Graduate,但在整个继承体系里面只有一个该静态成员。
我们若是在基类Person的构造函数和拷贝构造函数当中设置_count进行自增,那么我们就可以随时通过_count来获取该时刻已经实例化的Person、Student以及Graduate对象的总个数。
#include <iostream>
#include <string>
using namespace std;
//基类
class Person
{
public:Person() { _count++; }Person(const Person& p) {_count++;}
protected:string _name; //姓名
public:static int _count; //统计人的个数。
};
int Person::_count = 0; //静态成员变量在类外进行初始化
//派生类
class Student : public Person
{
protected:int _stuNum; //学号
};
//派生类
class Graduate : public Person
{
protected:string _seminarCourse; //研究科目
};
int main()
{Student s1;Student s2(s1);Student s3;Graduate s4;cout << Person::_count << endl; //4cout << Student::_count << endl; //4return 0;
}
继承的方式
- 单继承:一个子类只有一个直接父类时称这个继承关系为单继承。

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

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

菱形继承的缺陷及解决方案
从菱形继承的模型构造就可以看出,菱形继承的继承方式存在数据冗余和二义性的问题。
例如,对于以上菱形继承的模型,当我们实例化出一个Assistant对象后,访问成员时就会出现二义性问题。
#include <iostream>
#include <string>
using namespace std;
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; //主修课程
};
int main()
{Assistant a;a._name = "peter"; //二义性:无法明确知道要访问哪一个_namereturn 0;
}
- Assistant对象是多继承的Student和Teacher,而Student和Teacher当中都继承了Person,因此Student和Teacher当中都有_name成员,若是直接访问Assistant对象的_name成员会出现访问不明确的报错。
- 那怎么解决?
方法一: - 对于此,我们可以显示指定访问Assistant哪个父类的_name成员。
//显示指定访问哪个父类的成员
a.Student::_name = "张同学";
a.Teacher::_name = "张老师";
- 虽然该方法可以解决二义性的问题,但仍然不能解决数据冗余的问题。因为在Assistant的对象在Person成员始终会存在两份。

方法二: - 为了解决菱形继承的二义性和数据冗余问题,出现了虚拟继承。如前面说到的菱形继承关系,在Student和Teacher继承Person是使用虚拟继承(virtual关键字)为啥在哪里用,因为他们两个继承出现了二义性和冗余,即可解决问题。

#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
相关面试题 ⭐

- 1.d这个对象空间模型的原则是:先声明的在前面,对于继承的类也是,base1这个基类比base2这个基类先继承给Derive这个类。P3是对象指针,肯定指向这个对象的开始
2.根据这里的把派生类的对象切片给基类指针,基类指向派生类中基类部分,那么我们前面说了base1先继承给派生类,那么p1也在对象的开始(和p3相同),p2是base2后继承给派生类,那么和p1和p3位置不同选C

继承与组合

- 继承已经详细说了来说说组合
通过将对象包含在其他对象中来实现类之间的关联关系。组合常用于构建复杂对象,使得一个类(复合类)可以包含一个或多个其他类(组件类)的实例。
// 引擎类
class Engine
{
public:void start() {std::cout << "Engine started." << std::endl;}
};// 车轮类
class Wheel
{
public:void roll() {std::cout << "Wheel rolling." << std::endl;}
};// 汽车类,它包含引擎和车轮
class Car
{
private:Engine engine; // 组合成员:引擎Wheel wheels[4]; // 组合成员:车轮数组public:void start() {engine.start(); // 使用组合成员的方法for (int i = 0; i < 4; ++i) {wheels[i].roll(); // 使用组合成员的方法}std::cout << "Car started." << std::endl;}
};
- 组合的使用细节
组合成员可以在构造函数的初始化列表中进行初始化。
1、组合成员通常被声明为私有(private),以确保封装性。通过公共(public)方法或保护(protected)方法来访问这些成员。
2.组合:表示“强”拥有关系,组合对象的生命周期由包含它的对象管理。当包含对象销毁时,组合对象也随之销毁。
3.组合是一种通过在一个类中包含其他类的实例来实现类之间关系的设计原则。它具有灵活性、封装性和低耦合等优点,适用于构建复杂对象和实现代码复用。
- 1.Is - a(青菜是蔬菜)就是public继承,这时候我的派生类把public继承过来,那么派生类对基类的复用就是透明的,因为public可以看到派生类里面的具体细节是怎么样的。
2.对象组合has-a(汽车有轮胎)就是的内部细节是不可见的,那么被组合的类就不会被外面的类所影响。
耦合度:
1.两个类之间的依赖联系大,一个类会很大影响另一个类。耦合度低则反之。就像去爬山,如果我们20人一组,如果有一个人没来或者一个人出事,那么我们的整体计划就会被打乱。如果分成多组,2人一组,3人一组,那么我们1个人出事只会影响 2个人或更少,耦合度就低。
相关文章:
【C++】深入浅出之继承
目录 继承的概念及定义继承的定义继承方式和访问限定符protected与private的区别 默认继承方式继承类模板基类和派生类对象赋值兼容转换继承中的作⽤域(隐藏关系)相关面试题⭐ 派生类的默认成员函数⭐构造函数拷贝构造赋值重载析构函数 继承与友元继承与静态成员继承的方式菱形…...
在 macOS 上切换默认 Java 版本
下载javasdk 打开android studio -> setting -> build.execution,dep -> build tools -> gradle -> Gradle JDK -> download JDK… 点击下载,就下载到了 ~/Library/Java/JavaVirtualMachines/ 安装 jenv brew install jenv将 jenv 集成到 Shell …...
【安卓开发】【Android Studio】Menu(菜单栏)的使用及常见问题
一、菜单栏选项 在项目中添加顶部菜单栏的方法: 在res目录下新建menu文件夹,在该文件夹下新建用于菜单栏的xml文件: 举例说明菜单栏的写法,只添加一个选项元素: <?xml version"1.0" encoding"ut…...
2025.04.17【Stacked area】| 生信数据可视化:堆叠区域图深度解析
文章目录 生信数据可视化:堆叠区域图深度解析堆叠面积图简介为什么使用堆叠面积图如何使用R语言创建堆叠面积图安装和加载ggplot2包创建堆叠面积图的基本步骤示例代码 解读堆叠面积图堆叠面积图的局限性实际应用案例示例:基因表达量随时间变化 结论 生信…...
【NLP】 22. NLP 现代教程:Transformer的训练与应用全景解读
🧠 NLP 现代教程:Transformer的训练与应用全景解读 一、Transformer的使用方式(Training and Use) 如何使用Transformer模型? Transformer 模型最初的使用方式有两种主要方向: 类似 RNN 编码-解码器的架…...
jenkins凭据管理(配置github密钥)
凭据分类 Jenkins可以保存下面几种凭证: Secret text:例如 API Token(例如GitHub的个人access token)。Username with password:指的是登录GitHub的用户名和密码,可以作为单独的组件处理,也可以…...
数据结构|排序算法(三)选择排序 堆排序 归并排序
一、选择排序 1.算法思想 选择排序(Selection Sort)是一种简单直观的排序算法,其基本思想是:每次都从待排序部分中选出最小的一个数据和待排序的第一个数据交换。 将待排序序列分为已排序和未排序两部分,初始时已排…...
MAC Mini M4 上测试Detectron2 图像识别库
断断续续地做图像识别的应用,使用过各种图像识别算法,一开始使用openCV 做教室学生计数的程序。以后又使用YOLO 做医学伤口检测程序。最近,开始使用meta 公司的Detectron2.打算做OCR 文档结构分析 Detectron2 的开发者是 Meta 的 Facebook AI…...
OpenCv高阶(四)——角点检测
一、角点检测 在计算机视觉中,角点检测是识别图像中局部区域(角点)的关键技术,这些区域通常是两条或多条边缘的交点,具有丰富的结构信息,常用于图像匹配、跟踪、三维重建等任务。 Harris角点检测算法是一…...
TOA与AOA联合定位的高精度算法,三维、4个基站的情况,MATLAB例程,附完整代码
本代码实现了三维空间内目标的高精度定位,结合到达角(AOA) 和到达时间(TOA) 两种测量方法,通过4个基站的协同观测,利用最小二乘法解算目标位置。代码支持噪声模拟、误差分析及三维可视化,适用于无人机导航、室内定位等场景。订阅专栏后可获得完整代码 文章目录 运行结果…...
如何在 Ubuntu 22.04 上安装、配置、使用 Nginx
如何在 Ubuntu 22.04 上安装、配置、使用 Nginx?-阿里云开发者社区 更新应用 sudo apt updatesudo apt upgrade检查必要依赖并安装 sudo apt install -y curl gnupg2 ca-certificates lsb-release安装nginx sudo apt install -y nginx# 启动nginx sudo systemct…...
揭秘大数据 | 23、软件定义网络
软件定义网络将网络的边缘从硬件交换机推进到了服务器里面,将服务器和虚拟机的所有部署、管理的职能从原来的系统管理员网络管理员的模式变成了纯系统管理员的模式,让服务器的业务部署变得简单,不再依赖于形态和功能各异的硬件交换机…...
Elastic 9.0/8.18:BBQ、EDOT 和 LLM 可观察性、攻击发现、自动导入以及 ES|QL JOIN
作者:来自 Elastic Brian Bergholm 今天,我们很高兴地宣布 Elastic 9.0 和 8.18 的正式发布! 如果你觉得 8.x 版本系列已经很令人印象深刻,包含了 ANN、TSDB、ELSER、ES|QL、LTR、BBQ、logsdb 索引模式等功能,那你一定…...
当 AI 有了 “万能插头” 和 “通用语言”:MCP 与 A2A 如何重构智能体生态
目录 一、MCP:让 AI 拥有 “万能工具插头” 1.1 从 “手工对接” 到 “即插即用” 1.2 架构解密:AI 如何 “指挥” 工具干活 1.3 安全优势:数据不出门,操作可追溯 二、A2A:让智能体学会 “跨语言协作” 2.1 从 “…...
中间件--ClickHouse-3--列式存储和行式存储理解
在数据库存储中,列式存储(Columnar Storage)与行式存储(Row-based Storage)是两种不同的数据组织方式,它们各自适用于不同类型的应用场景。 1、行式存储(MySQL) 存储方式ÿ…...
【golang/jsonrpc】go-ethereum中json rpc初步使用(websocket版本)
说在前面 操作系统:win11 wsl2go-ethereum版本:1.15.8 关于json-rpc 官网 server 定义方法type CalculatorService struct{}func (s *CalculatorService) Add(a, b int) int {return a b }func (s *CalculatorService) Div(a, b int) (int, error) {…...
逻辑回归 (Logistic Regression)
文章目录 逻辑回归 (Logistic Regression)问题的引出Sigmoid function逻辑回归的解释决策边界 (Decision boundary)逻辑回归的代价函数机器学习中代价函数的设计1. 代价函数的来源(1)从概率模型推导而来(统计学习视角)(…...
燕山大学计算机网络之Java实现TCP数据包结构设计与收发
觉得博主写的好,给博主点点免费的关注吧! 目录 摘要.................................................................................................................... 4 前言.............................................................…...
如何使用SpringApplicationRunListener在Spring Boot 应用的不同生命周期阶段插入自定义逻辑
目录 一、引言二、核心方法概述三、加载机制四、使用场景五、扩展 - 如何在测试的不同阶段插入逻辑5.1 TestExecutionListener & AbstractTestExecutionListener5.1.1 主要功能5.1.2 生命周期方法 5.2 如何集成TestExecutionListener5.3 总结 一、引言 SpringApplicationR…...
P10413 [蓝桥杯 2023 国 A] 圆上的连线
题意: 给定一个圆,圆上有 n2023 个点从 1 到 n 依次编号。 问有多少种不同的连线方式,使得完全没有连线相交。当两个方案连线的数量不同或任何一个点连接的点在另一个方案中编号不同时,两个方案视为不同。 答案可能很大&#x…...
JavaEE——线程安全
目录 前言1.线程安全的定义2.线程安全问题产生的原因2.1 多个线程修改一个变量2.2 修改操作不是原子的2.3 内存可见性引起的线程安全问题 3.解决线程安全问题的方法3.1 通过synchronized关键字加锁3.2 使用volatile关键字 总结 前言 在使用多线程的时候,难免会出现…...
Redis Hash 介绍
Redis Hash 介绍 从基础命令、内部编码和使用场景三个维度分析如下: 一、基础命令 Redis Hash 提供了丰富的操作命令,适用于字段(field)级别的增删改查: 设置与修改 HSET:设置单个字段值(HSET…...
[redis进阶一]redis的持久化(2)AOF篇章
目录 一 为什么有了RDB持久化机制还要有AOF呢 板书介绍具体原因: 编辑二 详细讲解AOF机制 (1)AOF的基本使用 1)板书如下 2)开启AOF机制: 3) AOF工作流程 (2)AOF是否会影响到redis性能 编辑 (3)AOF缓冲区刷新策略 (4)AOF的重写机制 板书如下: 为什么要有这个重写机…...
【Linux我做主】探秘gcc/g++和动静态库
TOC Linux编译器gcc/g的使用 github地址 有梦想的电信狗 前言 在软件开发的世界中,编译器如同匠人的工具,将人类可读的代码转化为机器执行的指令。 对于Linux开发者而言,gcc和g是构建C/C程序的核心工具链,掌握它们的原理和使…...
Linux `init 0` 相关命令的完整使用指南
Linux init 0 相关命令的完整使用指南—目录 一、init 系统简介二、init 0 的含义与作用三、不同 Init 系统下的 init 0 行为1. SysVinit(如 CentOS 6、Debian 7)2. systemd(如 CentOS 7、Ubuntu 16.04)3. Upstart(如 …...
【英语语法】基本句型
目录 前言一:主谓二:主谓宾三:主系表四:主谓双宾五:主谓宾补 前言 英语基本句型是语法体系的基石,以下是英语五大基本句型。 一:主谓 结构:主语 不及物动词 例句: T…...
Vue3中发送请求时,如何解决重复请求发送问题?
文章目录 前言一、问题演示二、使用步骤1.One组件2.Two组件封装工具函数处理请求 总结 前言 在开发过程中,重复请求发送问题可能会导致数据不一致、服务器压力增加或用户操作异常。以下是解决重复请求问题的常见方法和最佳实践: 一、问题演示 我们看着…...
信息学奥赛一本通 1622:Goldbach’s Conjecture | 洛谷 UVA543 Goldbach‘s Conjecture
【题目链接】 ybt 1622:Goldbach’s Conjecture 洛谷 UVA543 Goldbach’s Conjecture 【题目考点】 1. 筛法求质数表 埃筛线性筛(欧拉筛) 知识点讲解见信息学奥赛一本通 2040:【例5.7】筛选法找质数 【解题思路】 首先使用埃…...
在极狐GitLab 身份验证中如何使用 OIDC?
极狐GitLab 是 GitLab 在中国的发行版,关于中文参考文档和资料有: 极狐GitLab 中文文档极狐GitLab 中文论坛极狐GitLab 官网 使用 OpenID Connect 作为认证提供者 (BASIC SELF) 您可以使用极狐GitLab 作为客户端应用程序,与 OpenID Connec…...
计算机视觉与深度学习 | 基于YOLOv8与光流法的目标检测与跟踪(Python代码)
===================================================== github:https://github.com/MichaelBeechan CSDN:https://blog.csdn.net/u011344545 ===================================================== 目标检测与跟踪 关键实现逻辑检测-跟踪协作机制特征点选择策略运动…...
