【C++】—— 继承(上)
【C++】—— 继承(上)
- 1 继承的概念与定义
- 1.1 继承的概念
- 1.2 继承定义
- 1.2.1 定义格式
- 1.2.2 继承父类成员访问方式的变化
- 1.3 继承类模板
- 2 父类和子类对象赋值兼容转换
- 3 继承中的作用域
- 3.1 隐藏规则
- 3.2 例题
- 4 子类的默认成员函数
- 4.1 构造函数
- 4.1.1 父类有默认构造
- 4.1.2 父类没有默认构造
- 4.2 拷贝构造
- 4.2.1 不需要自己显式写
- 4.2.2 自己显式写
- 4.3 赋值重载
- 4.4 析构函数
- 4.4.1 重载
- 4.4.2 顺序
- 4.5 实现不能被继承的类
- 4.5.1 法一:设为私有
- 4.5.2 法二:final
- 4.6 总结
1 继承的概念与定义
1.1 继承的概念
继承(inheritance)机制是面向对象设计使代码可以复用的最重要的手段,它允许我们在保存原有特性的基础上进行扩展,增加方法(成员函数)和属性(成员变量),这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的函数层次的复用,继承是类设计层次的复用。
下面我们通过一个例子来初步感受一下继承:
class Student
{public :// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证void identity(){// ...} // 学习void study(){// ...}
protected:string _name = "peter"; // 姓名string _address; // 地址string _tel; // 电话int _age = 18; // 年龄int _stuid; // 学号
};class Teacher
{
public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证void identity(){// ...} // 授课void teaching(){//...}
protected:string _name = "张三"; // 姓名int _age = 18; // 年龄string _address; // 地址string _tel; // 电话string _title; // 职称
};
上面,我们看到没有继承之前我们设计了两个类 Student
和 Teacher
, S t u d e n t Student Student 和 T e a c h e r Teacher Teacher 都有 姓名 / 地址 / 电话 / 年龄 等成员变量,都有 i d e n t i t y identity identity ⾝份认证的成员函数,设计到两个类里面就是冗余的。当然他们也有⼀些独有的成员变量和函数,比如老师独有成员变量是职称,学生的独有成员变量是学号;学⽣的独有成员函数是学习,⽼师的独有成员函数是授课。
既然 S t u d e n t Student Student 和 T e a c h e r Teacher Teacher 两个类的设计有些冗余,那我们能不能把公共的信息提取出来呢?
下面我们公共的成员都放到 Person 中,Student 和 Teacher 都继承 Person,就可以复⽤这些成员,就不需要重复定义了,省去了很多麻烦。
class Person
{
public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证void identity(){cout << "void identity()" << _name << endl;}
protected:string _name = "张三"; // 姓名string _address; // 地址string _tel; // 电话int _age = 18; // 年龄
};class Studen : public Person
{
public:// 学习void study(){// ...}
protected:int _stuid; // 学号
};class Teacher : public Person
{
public:// 授课void teaching(){//...}
protected:string title; //职称
};
虽然 S t u d e n t Student Student类 的成员变量看起来只有int _stuid;
,但它继承了Person类,它还有string _name
、string _address;
等等成员变量。成员函数也不止void study()
,还有void identity()
1.2 继承定义
1.2.1 定义格式
下面我们看到 Person
是父类,也称作基类。Student
是子类,也称作派生类。(因为翻译的原因,所以既叫父类/子类,也叫基类/派生类)
继承方式与访问限定符一样,都有三个:公有、保护、私有
1.2.2 继承父类成员访问方式的变化
类成员/继承方式 | p u b l i c public public 继承 | p r o t e c t e d protected protected 继承 | p r e v a t e prevate prevate 继承 |
---|---|---|---|
基类的 p u b l i c public public 成员 | 派生类的public成员 | 派生类的 p r o t e c t e d protected protected 成员 | 派生类的 p r i v a t e private private 成员 |
基类的 p r o t e c t e d protected protected 成员 | 派生类的 p r o t e c t e d protected protected 成员 | 派生类的 p r o t e c t e d protected protected 成员 | 派生类的 p r i v a t e private private 成员 |
基类的 p r i v a t e private private 成员 | 在派生类中不可见 | 在派生类中不可见 | 在派生类中不可见 |
-
父类的
private成员
在子类中无论以什么方式继承都是不可见的。这里的不可见是指父类的私有成员还是被继承到了子类对象中,但是语法上限制子类对象不管在类里面还是类外我们都不能去访问它- 子类想访问父类的 p r i v a t e private private 成员虽然不能直接访问,但能间接访问。虽然在子类中是不能访问,但在父类中并没有相关限制,只用父类提供相关访问 p r i v a t e private private 成员变量的成员函数,子类调用其函数就能间接访问。
-
父类 p r i v a t e private private 成员在子类中是不能被访问的,如果父类成员不想在类外直接被访问,但需要在子类中能访问,就定义为
protected
。可以看出保护成员限定符是因继承才出现的。 -
实际上面的表格我们进行一下总结会发现,父类的私有成员在子类都是不可见。父类其他成员在子类的访问方式为: M i n Min Min(成员在父类的访问限定符, 继承方式), p u b l i c public public > p r o t e c t e d protected protected > p r i v a t e private private。
-
使用关键字
class
时默认的继承方式是private
,使用struct
时默认的继承方式是public
,不过最好显式的写出继承方式class Student:Person //默认为private继承
、struct Student:Person //默认为public继承
-
在实际运用中一般使用的都是 p u b l i c public public 继承,几乎很少使用 p r o t e c t e d protected protected / p r i v a t e private private 继承,也不提倡使用 p r o t e c t e d protected protected / p r i v a t e private private 继承,因为 p r o t e c t e d protected protected / p r i v a t e private private 继承下来的成员都只能在子类的类里面使用,实际中扩展维护性不强。这里可以认为是 C++ 过度设计了。
看起来上面的规则很复杂,实际实践过程中是很简单的,一般都是:父类我们就用公有和保护,继承方式我们就用公有。其他方式都很少使用。
1.3 继承类模板
上述都是一些普通类的继承,那如果我们想继承类模板又该怎样呢?
之前,我们模拟实现栈使用的适配器模式,其实还有一种方法:继承
namespace ganyu
{template<class T>class stack : public std::vector<T>{public:void push(const T& x){push_back(x);}void pop(){vector<T>::pop_back();}const T& top(){return vector<T>::back();}bool empty(){return vector<T>::empty();}};
}
当基类是类模板时,需要指定类域去访问
,否则会编译报错。(普通类的继承不存在这个问题)
int main()
{ganyu::stack<int> st;st.push(1);st.push(2);st.push(3);while (!st.empty()){cout << st.top() << " ";st.pop();}return 0;
}
为什么编译报错呢?这与按需实例化有关系
ganyu::stack<int> st;
这句代码实例化栈,将 T T T 实例化成 i n t int int,也间接将 v e c t o r vector vector 实例化(严格来说只实例化了栈的构造函数)。但我们将 v e c t o r vector vector 实例化时不会把 v e c t o r vector vector 中所有的成员函数都实例化,我们调用谁才实例化谁。
我们调用 p u s h push push 函数时,编译器去找 p u s h push push_ b a c k back back 函数,在子类和父类中都找不到,因为还没有实例化。所以我们要指定类域去访问
,表示调用的是 v e c t o r vector vector< T T T> 中的 p u s h push push_ b a c k back back,此时编译器看到 T T T 已经被实例化成 i n t int int 了,就会将 v e c t o r vector vector< T T T> 中的 p u s h push push_ b a c k back back 实例化出一份 i n t int int 版本的出来。
我们可以结合 #define
,能灵活更改 s t a c k stack stack 的底层容器,达到类似适配器模式的效果
#define CONTAINER vectornamespace ganyu
{template<class T>class stack : public std::CONTAINER<T>{public:void push(const T& x){CONTAINER<T>::push_back(x);}void pop(){CONTAINER<T>::pop_back();}const T& top(){return CONTAINER<T>::back();}bool empty(){return CONTAINER<T>::empty();}};
}
2 父类和子类对象赋值兼容转换
- p u b l i c public public继承的前提下,子类对象可以赋值给父类的对象 / 父类的指针 / 父类的引用。这里有个形象的说法叫切片或者切割。寓意把子类中父类那部分切割开来赋值给父类对象/指针/引用
- 但反过来就不成立:父类对象不能赋值给子类对象
例如:现在有一个 S t u d e n t Student Student 对象, S t u d e n t Student Student 对象可以赋值给父类对象 P e r s o n Person Person,当然,指针和引用也是可以的;但反过来就不成立(总不能无中生有出一个 _ N o No No 成员吧)。
class Person
{
protected :string _name; // 姓名string _sex; // 性别int _age; // 年龄
};class Student : public Person
{
public :int _No; // 学号
};int main()
{Student sobj;// 1.派⽣类对象可以赋值给基类的对象/指针/引⽤Person pobj = sobj;Person* pp = &sobj;Person& rp = sobj;//2.基类对象不能赋值给派⽣类对象,这⾥会编译报错//sobj = pobj;return 0;
}
这里并没有发生类型转换。
虽然我们前面讲过不同类型的对象之间进行赋值,支持的是类型转换
int i = 0;
double d = i;
将 i i i 赋值给 d d d 走的就是类型转换,中间会生成一个临时对象。
但是切片并不是类型转换,中间并没有产生临时变量,这是一种特殊处理。
- 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(Run-Time Type Information)的 dynamic_cast 来进行识别后进行安全转换。(ps:这个我们后面再单独专门介绍,这里先提⼀下)
3 继承中的作用域
3.1 隐藏规则
- 在继承体系中父类和子类都有独立的作用域
- 子类和父类中有同名成员,
子类成员
将屏蔽父类
的同名成员的直接访问,这种情况叫 隐藏。(在子类成员函数中,可以使用父类::父类成员 显式访问
)
class Person
{
protected :string _name = "小帅"; // 姓名int _num = 111; // ⾝份证号
};
class Student : public Person
{
public :void Print(){cout << " 姓名:" << _name << endl;cout << " 身份证号:" << Person::_num << endl;//指定父类的类域进行访问cout << " 学号:" << _num << endl;}
protected:int _num = 999; // 学号
};int main()
{Student s1;s1.Print();return 0;
}
运行结果:
- 如果是成员函数的隐藏,
只需要函数名相同就构成隐藏
- 注意:在实际中在继承体系里面最好不要定义重名的成员或函数
3.2 例题
class A
{public :void fun(){cout << "func()" << endl;}
};
class B : public A
{public :void fun(int i){cout << "func(int i)" << i << endl;}
};
int main()
{B b;b.fun(10);b.fun();return 0;
};
-
A A A 和 B B B 类中的两个 f u n c func func函数 构成什么关系()
A. 重载 B. 隐藏 C.没关系 -
下面程序的编译运行结果是什么()
A. 编译报错 B. 运行报错 C. 正常运行
- 第一题:第一眼看上去,他们构成重载关系:函数名相同,参数类型不同。但如果选 A 就错了,这题选B。别忘了,只有在同一作用域的函数才构成函数重载,而隐藏是父类和子类中的函数名相同就构成隐藏
- 第二题:选A,因为子类和父类的 f u n c func func函数 构成
隐藏
,除非指定父类的作用域去调用,否则同名成员或函数是不会去父类中查找的。b.fun();
没有传递参数,编译报错。
4 子类的默认成员函数
6 个默认成员函数,意思是我们不写,编译器会给我们自动生成。父类的默认成员函数与普通类没有任何差别,但在派生类中,这几个成员函数是如何生成的呢?
4.1 构造函数
- 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显式调用。
4.1.1 父类有默认构造
class Person
{
public:Person(const char* name = "peter"): _name(name){cout << "Person()" << endl;}protected:string _name; // 姓名
};class Student : public Person
{
public:protected:int _num; //学号string _sex; //性别
};
首先,我们来回忆一下普通类的默认生成的构造函数的行为:
- 对
内置类型
:默认生成的构造函数是不确定的- 对
自定义类型
:会调用它的默认构造函数
现在,比起之前多出来一部分:
父类成员
- 我们把继承的父类成员看成一个整体对象,子类的默认构造会自动调用父类的默认构造完成父类成员的初始化
4.1.2 父类没有默认构造
class Person
{
public:Person(const char* name, double height): _name(name),_height(height){cout << "Person()" << endl;}protected:string _name; // 姓名double _height; //身高
};class Student : public Person
{
public:protected:int _num; //学号string _sex; //性别
};
现在,父类没有默认构造,派生类还能默认生成构造函数吗?.
可见,默认生成的只能调用默认构造。这时,就需要我们在子类显式写一个构造函数了
Student(const char* name, double height, int num, const char* sex):_name(name),_height(height),_num(num),_sex(sex)
{}
这样写可不可以呢?
不可以。编译器不允许直接去初始化父类的成员,子类要求必须调用父类的构造函数来初始化父类的成员,要把父类当成一个整体。
显示调用父类方法如下:
Student(const char* name, double height, int num, const char* sex):Person(name, height),_num(num),_sex(sex)
{}int main()
{Student s1("张三", 1.80, 1, "男");Student s2("李四", 1.70, 2, "未知");return 0;
}
有点像调用一个匿名对象一样。
4.2 拷贝构造
派生类的拷贝构造函数必须调用基类的拷贝构造完成基本的拷贝初始化
对默认生成的拷贝构造,其行为也像上述构造函数一样分成三类
内置类型
:完成浅拷贝自定义类型
:调用其拷贝构造父类整体
:调用父类的拷贝构造
4.2.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;}protected:string _name; // 姓名
};class Student : public Person
{
public://构造函数Student(const char* name, int num, const char* sex):Person(name), _num(num), _sex(sex){}//未写拷贝构造//···protected:int _num; //学号string _sex; //性别
};int main()
{Student s1("张三", 1, "男");Student s2 = s1;return 0;
}
严格来说, S t u d e n t Student Student类是不用我们自己写拷贝构造的,默认生成的拷贝构造已经完成了我们的需求。前面,我们通过学习知道拷贝构造、赋值重载、析构是一体的。一个不需要写,三个都不需要写;一个要写,三个都要写。因此 S t u d e n t Student Student类 的赋值重载和析构函数都不需要自己写
如果有需要深拷贝的资源,才需要自己实现
4.2.2 自己显式写
那假设 S t u d e n t Student Student 类中有指向的资源,需要我们自己写拷贝构造,又该怎么写呢?
class Student : public Person
{
public:protected:int _num; //学号string _sex; //性别int* _ptr = new int[10];//假设有指向的资源
};
Student (const Student& s):_num(s._num),_sex(s._sex),//显示调用父类的拷贝构造
{//深拷贝memcpy(_ptr, s._ptr, sizeof(int) * 10);
}
如何显式调用父类的拷贝构造呢?
调用父类的拷贝构造,需要传递父类的对象,但现在没有父类的对象,咋办呢?
这时,我们就可以运用前面学习的赋值兼容转换
Student(const Student& s):_num(s._num),_sex(s._sex),Person(s)
{//深拷贝memcpy(_ptr, s._ptr, sizeof(int) * 10);
}
Person(s)
: s s s 是子类对象的引用,要拷贝父类那一部分,需要将父类那一部分拿出来, 怎么拿出来呢?我把子类对象传给父类的引用,这时父类的引用,引用的是子类对象中切割出来的父类的那一部分。
这里有个小细节,走初始化列表时,编译器会先走Person(s)
,在走_num(s._num)
和_sex(s._sex)
这是因为初始化列表初始化的顺序与成员在列表中的顺序无关,只与声明的顺序有关。
所以继承以后,它将父类对象当成一个整体,而父类对象是最先被声明的
那如果不在初始化列表显示初始化父类呢?
Student(const Student& s):_num(s._num), _sex(s._sex)
{//深拷贝
}
我们说过,所有成员都会走初始化列表,父类 P e r s o n Person Person 没有显示调用,也会走初始化列表。但此时编译器会调用 P e r s o n Person Person 的默认构造,虽然编译能通过,但很可能不符合你的需求;如果 P e r s o n Person Person 没有默认构造,那么编译报错
4.3 赋值重载
和拷贝构造一样, S t u d e n t Student Student 类严格来说不需要写赋值。
但如果我们需要显式写要怎么写呢
- 派生类的 o p e r a t o r operator operator= 必须要调用基类的 o p e r a t o r operator operator =。
Student& operator=(const Student& s)
{if (this != &s){operator=(s);_num = s._num;_sex = s._sex;}return *this;
}
复制拷贝与拷贝构造是类似的,都是传递子类对象的引用给父类即可。
但是,如果运行程序会发现:程序陷入死循环
。
为什么呢?
子类中的同名函数与父类的构成了隐藏!
operator=(s);
其实一直调的是子类的 o p e r a t o r operator operator=,因此程序陷入死循环
因此我们要指定调用定父类的 o p e r a t o r operator operator=。
Student& operator=(const Student& s)
{if (this != &s){Person::operator=(s);_num = s._num;_sex = s._sex;}return *this;
}
总结:派生类的 o p e r a t o r operator operator= 必须要调用基类的 o p e r a t o r operator operator= 完成基类的赋值。需要注意的是派生类的 o p e r a t o r operator operator= 屏蔽了基类的 o p e r a t o r operator operator=,所以显式调用基类的 operator=,需要指定基类作用域
4.4 析构函数
首先,严格来说 S t u d e n t Student Student 并不需要我们显式写析构函数
那如果有需要显式释放的资源,析构函数又该怎么写呢?
4.4.1 重载
我们还是以 S t u d e n t Student Student类 为例
首先,如果显式实现析构函数,_ n u m num num 和 _ s e x sex sex 是不用管的。因为int _num
是内置类型,而 string _sex
会自己调用其析构。我们只需要管父类即可
~Student()
{~Person();
}
但这样会报错
析构是可以显示调用的,但为什么这里调不动呢?
这里有个小知识点:子类的析构会和父类的析构构成隐藏关系。
因为一些特殊的原因,析构函数的函数名会被特殊处理成 d e s t r u c t o r destructor destructor(),所以父类的析构函数和子类的析构函数构成隐藏关系。实际上并没有什么 ~ S t u d e n t Student Student() 和 ~ P e r s o n Person Person(),只有 d e s t r u c t o r destructor destructor()。
所以我们要指定类域调用
~Student()
{Person::~Person();
}
4.4.2 顺序
我们来尝试调用一下析构函数
class Person
{
public://成员函数//···~Person(){cout << "~Person()" << endl;}protected:string _name; // 姓名
};class Student : public Person
{
public://成员函数//···~Student(){Person::~Person();}protected:int _num; //学号string _sex; //性别
};int main()
{Student s1("张三", 1, "男");Student s2("李四", 2, "未知");return 0;
}
运行结果:
大家有没有发现析构函数调的有点多啊,我一个就两个对象,你怎么就调用 4 次析构函数了呢?
像构造、赋值重载等,我们显式写的都需要显式调用父类的对应函数,但析构不需要显式调用。调用了子类析构函数之后,系统会自动调用父类的析构(这点与自定义类型的成员很像)。
为什么要这样的。这样可以保证析构顺序是先子后父。后定义的先析构,而对象构造时,是先构造(初始化)父类,在初始化子类;析构是就需要先析构子类,在析构父类。如果显式调用就不能保证先子后父,而是取决于实现的人。
4.5 实现不能被继承的类
要实现一个不能被继承的类,有两种方法
4.5.1 法一:设为私有
将父类的构造函数设置为私有
class Base
{
public :void func5() { cout << "Base::func5" << endl; }
protected:int a = 1;
private:// C++98的⽅法Base(){}
};class Derive :public Base
{void func4() { cout << "Derive::func4" << endl; }protected:int b = 2;
};
为什么呢?因为子类的构造函数,不论是自动生成还是我们自己显式实现,都必须调用父类的构造函数。但是父类的 p r i v a t e private private成员在子类中是不可见的,因此子类调不到父类的构造函数。
但是这种方式不够明显,如果不调用子类的对象编译器是不会报错的
4.5.2 法二:final
C++11中新增了一个关键字: f i n a l final final
用 f i n a l final final 修饰一个类,表示该类是最终类,无法再被继承。
这种方式更直观一些,不管子类定不定义,直接报错
class Base final
{
public :Base(){}void func5() { cout << "Base::func5" << endl; }
protected:int a = 1;
};class Derive :public Base
{void func4() { cout << "Derive::func4" << endl; }
protected:int b = 2;
};
4.6 总结
- 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表显示调用
- 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化
- 派生类的 o p e r a t o r operator operator= 必须要调用基类的 o p e r a t o r operator operator= 完成基类的复制。需要注意的是派生类的 o p e r a t o r operator operator= 隐藏了基类的 o p e r a t o r operator operator=,所以显示调用基类的 operator=,需要指定基类作用域
- 派生类的析构函数会在被调用完成之后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员在清理基类成员的顺序
- 派生类对象初始化先调用基类的构造再调派生类的构造
- 派生类对象析构清理先调用派生类析构再调基类的析构
- 因为多态中一些场景析构函数需要构成重写,重写的条件之一是函数名相同。那么编译器会对析构函数进行特殊处理 ,处理成 d e s t r u c t o r destructor destructor(),所以基类析构函数不加 virtual 的情况下,派生类析构函数和基类析构函数构成隐藏关系
- 大多数情况下,派生类中拷贝构造、赋值、析构都是不需要自己写的;如果需要,那这个继承的设计太过复杂,可以考虑重新设计。
好啦,本期关于继承的知识就介绍到这里啦,希望本期博客能对你有所帮助。同时,如果有错误的地方请多多指正,让我们在 C++ 的学习路上一起进步!
相关文章:

【C++】—— 继承(上)
【C】—— 继承(上) 1 继承的概念与定义1.1 继承的概念1.2 继承定义1.2.1 定义格式1.2.2 继承父类成员访问方式的变化 1.3 继承类模板 2 父类和子类对象赋值兼容转换3 继承中的作用域3.1 隐藏规则3.2 例题 4 子类的默认成员函数4.1 构造函数4.1.1 父类有…...

【2024保研经验帖】东南大学计算机学院夏令营
前言 背景:末211,专业计算机科学与技术,rk前5%,无科研,只有几个竞赛 东南大学计算机学院夏令营需要老师推荐,一个老师的推荐名额感觉应该挺多的,因为学硕和专硕都进了两百多人,总共…...

dz论坛可可积分商城插件价值399元
界面简洁美观大方,适合各类站点。支持多用户商城,可让商家入驻站点发布商品,亦可站长自己发布商品。支持向商家抽佣抽成功能,可设置商家在成交商品后按一定比例扣除抽成,达到网站盈利目的采用队列技术处理,…...

python的extend和append
在Python中,list的append和extend方法都是用来向列表添加元素的,但它们之间有一些关键的区别: append方法: append方法用于将一个对象添加到列表的末尾。无论添加的对象是什么类型(整数、字符串、列表等)&a…...

贪心算法相关知识
目录 基础 定义 工作原理 步骤一:分解问题 步骤二:确定贪心策略 步骤三:求解子问题 步骤四:合并结果 适用场景 活动安排问题 找零问题 哈夫曼编码 局限性 高级 与动态规划的对比 决策方式 最优性保证 时间复杂度和…...

济南比较出名的人物颜廷利:全球最具影响力的思想家起名大师
颜廷利教授是一位在思想、哲学、教育、易学、国学、心理学、命名学等多个领域具有深远影响的学者。他被誉为“世界点赞第一人”,在国内外享有极高的声誉,被认为是现代易经三大泰斗之首。山东目前比较厉害的名人颜廷利教授的学术成就和影响力横跨哲学、思…...

第100+27步 ChatGPT学习:概率校准 Temperature Scaling
基于Python 3.9版本演示 一、写在前面 最近看了一篇在Lancet子刊《eClinicalMedicine》上发表的机器学习分类的文章:《Development of a novel dementia risk prediction model in the general population: A large, longitudinal, population-based machine-learn…...

Python知识点:如何应用Python工具,使用NLTK进行语言模型构建
开篇,先说一个好消息,截止到2025年1月1日前,翻到文末找到我,赠送定制版的开题报告和任务书,先到先得!过期不候! 如何使用NLTK进行语言模型构建 在自然语言处理(NLP)中&a…...

深入浅出MySQL
深入浅出MySQL 以下内容参考自 《MySQL是怎样运行的:从根儿上理解MySQL》一书,强烈推荐 存储引擎 对于不同的表可以设置不同的存储引擎 CREATE TABLE tableName (xxxx ) ENGINE 引擎名称; # 修改 ALTER TABLE tableName ENGINE xxx; 编码格式 my…...

【WRF工具】cmip6-to-wrfinterm工具概述:生成WRF中间文件
cmip6-to-wrfinterm工具概述 cmip6-to-wrfinterm工具安装cmip6-to-wrfinterm工具使用快速启动(Quick start)情景1:MPI-ESM-1-2-HR(默认):情景2:BCMM情景3:EC-Earth3 更改使用&#x…...

大厂面试真题:阿里经典双重检测DCL对象半初始化问题
阿里面试题中提到的双重检测DCL(Double-Checked Locking)对象半初始化问题,是Java多线程编程中一个经典的问题。以下是对这一问题的详细解析: 一、双重检测锁(DCL)概述 双重检测锁是一种用于实现单例模式…...

20款奔驰CLS300升级原厂抬头显示HUD 23P智能辅助驾驶 触摸屏人机交互系统
以下是为您生成的一份关于 18 款奔驰 CLS 老款改新款的改装文案: 18 款奔驰 CLS 老款改新款:科技升级,畅享极致驾驶体验 在汽车改装的世界里,每一次的升级都是对卓越的追求。今天,让我们一同探索 18 款奔驰 CLS 老款改…...

GoogleNet原理与实战
在2014年的ImageNet图像识别挑战赛中,一个名叫GoogLeNet 的网络架构大放异彩。以前流行的网络使用小到11,大到77的卷积核。本文的一个观点是,有时使用不同大小的卷积核组合是有利的。 回到他那个图里面你会发现,这里的一个通过我们最大的池化…...

MongoDB 数据库服务搭建(单机)
下载地址 下载测试数据 作者:程序那点事儿 日期:2023/02/15 02:16 进入下载页,选择版本后,右键Download复制连接地址 下载安装包 wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-5.0.14.tgz …...

基于springboot+小程序的智慧物业平台管理系统(物业1)
👉文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 智慧物业平台管理系统按照操作主体分为管理员和用户。 1、管理员的功能包括报修管理、投诉管理管理、车位管理、车位订单管理、字典管理、房屋管理、公告管理、缴费管理、维修指派管理、…...

[SpringBoot] 苍穹外卖--面试题总结--上
前言 1--苍穹外卖-SpringBoot项目介绍及环境搭建 详解-CSDN博客 2--苍穹外卖-SpringBoot项目中员工管理 详解(一)-CSDN博客 3--苍穹外卖-SpringBoot项目中员工管理 详解(二)-CSDN博客 4--苍穹外码-SpringBoot项目中分类管理 详…...

[C#]使用onnxruntime部署yolov11-onnx实例分割模型
【官方框架地址】 https://github.com/ultralytics/ultralytics.git 【算法介绍】 在C#中使用ONNX Runtime部署YOLOv11-ONNX实例分割模型,涉及到模型的加载、数据预处理、模型推理和后处理几个关键步骤。 首先,需要确保已经安装了ONNX Runtime的NuGe…...

Polars的Config
Config Config 内容使用示例设置并行执行设置日志详细程度指定null值设置推断schema的行数启用低内存模式获取当前配置选项的值 在Polars的Python API中,Config部分提供了配置选项,允许用户自定义Polars的行为。以下是一些可配置的选项及其使用示例&…...

【面试官】 多态连环问
以下是一些关于封装的常见面试题及答案: 封装 1. 什么是封装? 答案:封装是面向对象编程的三大特性之一,它是将数据和操作数据的方法绑定在一起,并且通过访问修饰符限制对数据的直接访问,只提供特定的方法来…...

Vue 路由设置
为了防止遗忘,记录一下用Vue写前端配置路由时的过程,方便后续再需要用到时回忆。 一、举个例子 假如需要实现这样的界面逻辑: 在HomePage中有一组选项卡按钮用于导航到子页面,而子页面Page1中有一个按钮,其响应事件是…...

力扣110:判断二叉树是否为平衡二叉树
利用二叉树遍历的思想编写一个判断二叉树,是否为平衡二叉树 示例 : 输入:root [3,9,20,null,null,15,7] 输出:true思想: 代码: int getDepth(struct TreeNode* node) {//如果结点不存在,返回…...

Chromium 中JavaScript Fetch API接口c++代码实现(一)
Fetch API主要暴露了三个接口一个方法。 三个接口 Request(资源请求)Response(请求的响应)Headers(Request/Response头部信息)一个方法 fetch()(获取资源调用的方法更多介绍参考 Fetch API - Web API | MDN (mozilla.org) 一、 来看一段前端代码 <!DOCTYPE html> <h…...

ARM(5)内存管理单元MMU
一、虚拟地址和物理地址 首先,计算机系统的内存被组成一个由M个连续的字节大小组成的数组。每字节都会有一个唯一的物理地址。CPU访问内存最简单的方式就是使用物理地址。如下图: 图 1 物理地址,物理寻址 而现在都是采用的都是虚拟寻址的方法。CPU生成一…...

文件上传漏洞原理
原理:\n应用中存在上传功能,但是上传的文件没有经过严格的合法性检验或者检验函数存在缺陷,导致可以上传木马文件到服务器,并且能够执行其中的恶意代码。\n\n危害:\n服务器的网页篡改,网站被挂马࿰…...

Web安全 - 安全防御工具和体系构建
文章目录 安全标准和框架1. 国内安全标准:等级保护制度(等保)2. 国际安全标准:ISO27000系列3. NIST安全框架:IDPRR方法4. COBIT与ITIL框架 防火墙防火墙的基本作用防火墙的三种主要类型防火墙的防护能力防火墙的盲区 W…...

服务器数据恢复—raid磁盘故障导致数据库文件损坏的数据恢复案例
服务器存储数据恢复环境&故障: 存储中有一组由3块SAS硬盘组建的raid。上层win server操作系统层面划分了3个分区,数据库存放在D分区,备份存放在E分区。 RAID中一块硬盘的指示灯亮红色,D分区无法识别;E分区可识别&a…...

requests 中data=xxx、json=xxx、params=xxx 分别什么时候用
如果是要做爬虫模拟一个页面提交,看原页面是post还是get,以及Content-Type是什么。 GET 请求 使用 paramsxxx,查询参数会被编码到 URL 中。POST 请求,Content-Type为 application/x-www-form-urlencoded的,使用 dataxx…...

毕设 大数据抖音短视频数据分析与可视化(源码)
文章目录 0 前言1 课题背景2 数据清洗3 数据可视化地区-用户观看时间分界线每周观看观看路径发布地点视频时长整体点赞、完播 4 进阶分析相关性分析留存率 5 深度分析客户价值判断 0 前言 🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕…...

【SQL】深入理解SQL:从基础概念到常用命令
目录 1. SQL基础概念1.1 数据库与表1.2 行与列1.3 数据库与表结构示意图 2. 常用SQL命令3. DML 命令3.1 SELECT语句3.2 INSERT语句3.3 UPDATE语句3.4 DELETE语句 4. DDL 命令3.4.1 CREATE 命令3.4.2 ALTER 命令3.4.3 DROP 命令 5. DCL 命令3.6.1 GRANT 命令3.6.2 REVOKE 命令 学…...

一文看懂计算机中的大小端(Endianess)
文章目录 前言一、什么是大小端二、如何判断大小端三、大小端的转换3.1 使用标准库函数3.2 手动实现大小端转换 前言 本文主要探讨计算机中大小端的相关概念以及如何进行大小端的判断和转换等。 一、什么是大小端 大小端(Endianess)是指计算机系统在存…...