C++继承机制:从基础到避坑详细解说

news2025/3/25 1:43:14

目录

1.继承的概念及定义

1.1继承的概念

1.2 继承定义

1.2.1定义格式

1.2.2继承关系和访问限定符

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

总结:

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

3.继承中的作用域

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

​编辑

默认构造与传参构造

拷贝构造:

显示深拷贝的写法:

赋值

显式调用赋值:

析构函数

​编辑

5.继承和友元:

6.继承与静态成员

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

虚拟继承

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

8.继承的总结和反思


总结:C++继承机制详解

1. 继承基础
  • 概念:继承是面向对象的重要特性,允许派生类在复用基类特性的基础上进行扩展,形成层次结构。

  • 访问权限

    • public继承:基类public→派生类publicprotectedprotected

    • protected/private继承:基类成员在派生类中的访问权限会被进一步限制。

    • 基类private成员对所有继承方式不可见,需通过基类提供的接口访问。

  • 默认继承方式

    • class默认private继承,struct默认public继承(但建议显式声明)。

2. 对象赋值与转换
  • 切片(切割):派生类对象可赋值给基类对象、指针或引用(基类仅保留派生类中与自身匹配的部分)。

  • 反向不成立:基类对象不能直接赋值给派生类对象(需强制类型转换,且需确保安全)。

3. 作用域与成员隐藏
  • 独立作用域:基类与派生类作用域独立,同名成员会引发隐藏(重定义)

  • 解决方法:通过基类::成员名显式访问基类成员。

  • 函数隐藏:仅函数名相同即构成隐藏(与参数无关)。

4. 派生类默认成员函数
  • 构造与析构

    • 派生类构造函数需显式调用基类构造函数(若基类无默认构造)。

    • 析构顺序:先派生类后基类(编译器自动调用基类析构,无需显式)。

  • 拷贝与赋值

    • 默认调用基类的拷贝构造和赋值运算符,需显式处理深拷贝问题。

    • 赋值运算符需避免递归调用(通过基类::operator=)。

5. 继承与友元/静态成员
  • 友元不可继承:基类友元无法访问派生类私有/保护成员。

  • 静态成员共享:基类静态成员在整个继承体系中唯一,所有派生类共用。

6. 菱形继承与虚拟继承
  • 问题:菱形继承导致数据冗余二义性(如Assistant对象包含两份Person成员)。

  • 解决:使用虚拟继承virtual),使公共基类(如Person)在派生类中仅保留一份。

  • 原理:通过虚基表指针和偏移量定位唯一基类成员,解决冗余和二义性。

7. 继承 vs. 组合
  • 继承(is-a):适合逻辑上的“派生关系”(如学生是人)。

    • 缺点:高耦合,破坏封装,基类改动影响派生类。

  • 组合(has-a):适合“包含关系”(如汽车有轮胎)。

    • 优点:低耦合,易于维护,优先使用。

8. 关键总结
  • 避免菱形继承:复杂且性能开销大,优先用组合替代多继承。

  • 虚拟继承慎用:仅解决菱形继承问题,其他场景不推荐。

  • 多继承限制:C++支持但易引发问题,Java等语言已弃用。

核心思想:合理使用继承,优先选择组合;理解作用域、权限和对象模型,避免设计复杂继承结构。

1.继承的概念及定义

1.1继承的概念

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

#include<iostream>
using namespace std;


class Person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
	}
protected:
	string _name = "peter"; // 姓名
private:
	int _age = 18; // 年龄
};

class Student : public Person 
{
public:
	void func()
	{
		cout << _name << endl;
		//不能直接访问父类的私有成员,就像不能直接使用爸爸的私房钱一样
		//cout << _age << endl;

		//可以间接使用父类的函数访问到父类的私有成员
		Print();
	}
protected:
	int _stuid; // 学号
};

class Teacher : public Person
{
protected:
	int _jobid; // 工号
};


int main()
{
	Student s;
	s.Print();
	//s._name += 'x';
	return 0;
}

1.2 继承定义

1.2.1定义格式

下面我们看到Person是父类,也称作基类。Student是子类,也称作派生类。

1.2.2继承关系和访问限定符
 

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

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

总结:

1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。

我们不能直接访问父类的私有成员:

2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。

3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected

> private。

比如说上面的代码,Print是基类的公有成员函数,派生类Student类是公有继承,因此对于Student类来说Print仍然是一个可直接访问到的公有函数:这个代码是完全可以运行的。

Student s;
s.Print();

4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过

最好显示的写出继承方式。· 

5. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡

使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里

面使用,实际中扩展维护性不强。

补充:

struct默认继承方式和访问限定符都是公有

class默认继承方式和访问限定符都是私有

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

  • 派生类对象 可以赋值 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。
  • 基类对象不能赋值给派生类对象。
  • 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(RunTime Type Information)的dynamic_cast 来进行识别后进行安全转换。(ps:这个我们后面再讲解,这里先了解一下)
	int i = 1;
	double t = i;

	const string& s = "11111";
    不相关的类型不能转换
    //string str = i;
    //强行转换也不行
    //string str = (string)i;

相关类型支持隐式类型转换,单参数构造函数支持隐式类型转换,类型转换中间会产生临时变量,临时变量具有常性,因此给引用(引用的是临时变量)的时候必须加const。相关的类型,不可以隐式类型转换,也不可以强转。(指针是地址本质就是整形,因此可以和整形转换)

公有继承的时候,子类能转为父类

保护和私有继承的时候,子类不能转为父类:

public继承:每个子类对象都是一个特殊的父类对象:is a关系

为什么叫切割/切片 : 将子类中和父类一样的部分切出来依次赋值(拷贝)给父类

什么叫做赋值兼容(编译器进行了特殊处理):中间不会产生临时对象

证明:

	//子类能转为父类
	Student st;
	Person p = st;
	//赋值兼容
	Person& ref = st;
	Person* ref = &st;

如果是给予一个引用,ref就变成子类对象当中切割出来的父类那一部分的别名。

如果是指针,ptr就指向了子类对象当中切割出来的父类对象的一部分

 

由此可以知道,父类转换成子类是不可以的,因为子类有的父类没有(指针和引用在一些特殊情况可以)

局部域、全局域、命名空间域、类域

局部域和全局域会影响访问(同一个域类不能有同名成员(特殊情况:同名函数构成重载,但函数不构成重载的时候不能同名))和生命周期,类域和命名空间域不影响生命周期只影响访问

3.继承中的作用域

类域的细分:

  •  在继承体系中基类派生类都有独立的作用域。(可以有同名成员)
  • 子类和父类中有同名成员子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类 成员 显示访问)

想访问父类的需要加域访问限定符,指定类域就可以了

  • 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
  • 注意在实际中在继承体系里面最好不要定义同名的成员。

请看下题:以下程序的结果是什么?

A. 编译报错        B. 运行报错        C. 两个func构成重载        D. 两个func构成隐藏

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

解答:

B中的fun和A中的fun不是构成重载,因为不是在同一作用域
B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。

这样就是错误的

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

    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; //学号
    	string _str;
    
    	//将父类成员当成一个整体(或者一个整体自定义类型)
    	//将自己的成员当成一个整体
    	//a、内置类型
    	//b、自定义类型
    };
    
    
    int main()
    {
    	Student s;
    
    	return 0;
    }

    默认构造与传参构造

    对于派生类,没有写默认构造函数,那在实例化对象的时候是怎么实例化的?

    走的基类的默认构造函数

    在派生类写了默认构造函数:仍然会报错

    因为需要将父类成员当成一个整体,不允许单独对父类的成员进行初始化,需要像匿名对象一样来调用(父类有几个成员就一起传过去):

    class Student : public Person
    {
    public:
    	Student(int num, const char* str, const char* name)
    		:Person(name)
    		,_num()
    		,_str()
    	{}
    
    protected:
    	int _num; //学号
    	string _str;
    };

    父类 + 自己,父类的调用父类构造函数初始化Person(name),这里就等于去初始化,显式的去调用父类的构造,父类的部分只能由父类初始化。

    可以看一个不是派生类的感受一下:派生类就相当于将父类当成一个整体(不能单独初始化)

    拷贝构造:

    如果派生类不写默认拷贝构造会发生什么?调用父类的拷贝构造

    	Student s1(1, "vv", "pupu");
    
    	Student s2(s1);

    运行结果:对内置类型采用值拷贝,自定义类型采用他的拷贝构造,父类会去调用父类的拷贝构造。

    派生类什么时候需要显式的写他自己的拷贝构造?

    如果他存在深拷贝:(有个指针指向某个资源)

    显示深拷贝的写法:

    	Student(const Student& s)
    		:Person(s)
    		,_num(s._num)
    		,_str(s._str)
    	{}

    为什么直接传s就可以?

    父类的拷贝构造:

    Person(const Person& p)
    	: _name(p._name)
    {
    	cout << "Person(const Person& p)" << endl;
    }

    赋值

    没有写派生类的赋值构造函数怎么办?调用父类的赋值函数

    显式调用赋值:

    Student& operator=(const Student& s)
    {
    	if (this != &s)//先判断是否是自己给自己赋值
    	{
    		operator=(s); //显示调用运算符重载
    		_num = s._num;
    		_str = s._str;
    	}
    	return *this;
    }

    上面的写法会引起栈溢出: 

    派生类的赋值,和父类的赋值构成隐藏(同名函数),因此会调用自己的赋值,因此这里就死循环了,导致了栈溢出

    指定作用域:

    	Student& operator = (const Student& s)
    	{
    		if (this != &s)//先判断是否是自己给自己赋值
    		{
    			Person::operator=(s); //显示调用运算符重载,发生了栈溢出,派生类的赋值,和父类的赋值构成隐藏(同名函数)
    			_num = s._num;
    			_str = s._str;
    		}
    		return *this;
    	}

    析构函数

    一个类的析构函数是否能显示调用:可以

    	Person p("wakak");
    	p.~Person();

    派生类如何显式调用:

    	~Student()
    	{
    		~Person();
    		cout << "~Student()" << endl;
    	}

    这是因为:子类的析构也会隐藏父类,因为后续多态的需要,析构函数名字会被统一处理成destructor(析构函数),因此解决办法同赋值:

    	~Student()
    	{
    		Person::~Person();
    		cout << "~Student()" << endl;
    	}

    但是此时却是这样的(在Student构造函数代码中添加了打印Student()): 析构函数多了一次

    当子类析构调用结束了以后,又自动调用了一次父类的析构,因为析构函数是自动调用的。

    构造需要先保证先父后子,析构需要保证先子后父

    更重要的原因:

    因为在子类的析构函数里面还需要还会访问到父类的成员,不能一上来就析构父类就访问不到了:

    ~Student()
    {
    	Person::~Person();
    	cout << _name << endl;
    
    	cout << "~Student()" << endl;
    }

    显示写无法保证先子后父

    为了析构顺序是先子后父,子类析构函数结束后,会自动调用父类析构

    运行结果:

    总结:

    6个默认成员函数,“默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,这几个成员函数是如何生成的呢?

    1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用
    2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
    3. 派生类的operator=必须要调用基类的operator=完成基类的复制。
    4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
    5. 派生类对象初始化先调用基类构造再调派生类构造。
    6. 派生类对象析构清理先调用派生类析构再调基类的析构。
    7. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同(这个我们后面会讲解)。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数加
    8. 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; //这里的_stuNum是子类的保护成员,基类的友元函数不能访问子类的保护和私有成员
    }
    void main()
    {
    	Person p;
    	Student s;
    	Display(p, s);
    }

    解决方法:

    6.继承与静态成员

    基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例 。基类和派生类共用静态成员

    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 Student
    {
    protected:
    	string _seminarCourse; // 研究科目
    };
    int main()
    {
    	Student s1;
    	Student s2;
    	Student s3;
    	Graduate s4;
    	cout << &Person::_count << endl;
    	Student::_count = 0;
    	cout << &Student::_count << endl;
    }
    

    派生类以及Person一共实例化了多少个:

    	Person() { ++_count; }
    
    	Person(const Person& p) { ++_count; }

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


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

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

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

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

    会导致的问题:比如说刚才的想计算派生类以及Person一共实例化了多少个,这里实例化一次Assistant就会使count++两次

    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; // 主修课程
    };
    void Test()
    {
    	// 这样会有二义性无法明确知道访问的是哪一个
    	Assistant a;
    	a._name = "peter";
    	// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
    	a.Student::_name = "xxx";
    	a.Teacher::_name = "yyy";
    }

    虚拟继承

    虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地方去使用。

    virtual加在直接继承公共基类的派生类:

    相当于只有一份a,Student和Teacher共用

    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; // 主修课程
    };
    void Test()
    {
    	Assistant a;
    	a._name = "peter";
    }

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

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

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

    使用内存窗口来观察原理:

    1.菱形继承对象模型

    2.菱形虚拟继承对象模型

    int main()
    {
    	D d;
    	d.B::_a = 1;
    	d.C::_a = 2;
    
    	d._a = 0;
    	d._b = 3;
    	d._c = 4;
    	d._d = 5;
    	return 0;
    }

    a不再在BC内部,BC多出来的地址(指针)存0,且多出来地址的下一个指针所存的为他们到A的偏移量。因此需要找到A就用自己的地址再加偏移量。(为了切片用A)

    注意pc指向的是中间位置:

    对象模型当中,在内存中的存储是按照声明的顺序。

    ostream也是一个菱形继承。

    总结:实践中可以设计多继承,但是切记不要设计菱形继承,因为太复杂,容易出各种问题。

    虚拟继承后,对象模型都需要保持一致,无论这个指针指向哪,都是使用这个指针找到偏移量,找到a的位置。

    8.继承的总结和反思

    1. 很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承一定不要设计出菱形继承。否则在复杂度及性能上都有问题。

    2. 多继承可以认为是C++的缺陷之一,很多后来的许多语言都没有多继承,如Java

    3. 继承和组合

    • public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
    • 组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。
    • 优先使用对象组合,而不是类继承 。
    • 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。

    内部细节可见:白,内部细节不可见:黑

            高耦合(模块(类与类之间)之间的关系很紧密,关联很密切,要改动一个地方,所有地方都需要改变,需要重构)

            如何开发设计软件更好:低耦合(方便维护),高内聚(当前类里面的成员关系紧密)

    • 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。
    • 实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合

    简单来说怎么选择使用继承还是组合:

    • 适合is - a 的关系就用继承
    • 适合has - a的关系就用组合
    • 优先使用组合

    经典的is - a问题:

    • 人 --- 学生: 学生是人,而不是学生有一个人
    • 动物 --- 狗: 狗是动物,而不是狗里有一个动物

    经典的has - a问题:

    • 轮胎 --- 汽车:汽车有轮胎,而不是汽车是轮胎

    两者都可以使用:

    • 链表 --- 栈: 栈里有链表,栈是特殊的链表

    1.将A作为B的成员变量(组合也相当于一种复用

    class A
    {
    public:
    	int _a;
    };
    //组合:将A作为B的成员变量
    class B 
    {
    public:
    	A _aa;
    	int _b;
    };
    

    9.笔试面试题

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

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

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

    4.C++有多继承?为什么?JAVA为什么没有

    5.多继承问题是什么?本身没啥问题,有多继承就一定会可能写出菱形继承

    结语:

           随着这篇关于题目解析的博客接近尾声,我衷心希望我所分享的内容能为你带来一些启发和帮助。学习和理解的过程往往充满挑战,但正是这些挑战让我们不断成长和进步。我在准备这篇文章时,也深刻体会到了学习与分享的乐趣。  

             在此,我要特别感谢每一位阅读到这里的你。是你的关注和支持,给予了我持续写作和分享的动力。我深知,无论我在某个领域有多少见解,都离不开大家的鼓励与指正。因此,如果你在阅读过程中有任何疑问、建议或是发现了文章中的不足之处,都欢迎你慷慨赐教。               

            你的每一条反馈都是我前进路上的宝贵财富。同时,我也非常期待能够得到你的点赞、收藏,关注这将是对我莫大的支持和鼓励。当然,我更期待的是能够持续为你带来有价值的内容,让我们在知识的道路上共同前行。

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

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

    相关文章

    MySQL数据库精研之旅第二期:库操作的深度探索

    专栏&#xff1a;MySQL数据库成长记 个人主页&#xff1a;手握风云 目录 一、查看数据库 二、创建数据库 2.1. 语法 2.2. 示例 三、字符集编码和校验(排序)规则 3.1. 查看数据库支持的字符集编码 3.2. 查看数据库支持的排序规则 3.3. 不同的字串集与排序规则对数据库的…

    git_version_control_proper_practice

    git_version_control_proper_practice version control&#xff0c;版本控制的方法之一就是打tag 因为多人协作的项目团队&#xff0c;commit很多&#xff0c;所以需要给重要的commit打tag&#xff0c;方便checkout&#xff0c;检出这个tag 参考行业的实践方式。如图git、linux…

    计算机组成原理和计算机网络常见单位分类及换算

    计算机组成原理&#xff08;主要用于存储、内存、缓存等&#xff09; 计算机网络&#xff08;主要用于传输速率&#xff09; 直观对比

    【第二十八周】:Temporal Segment Networks:用于视频动作识别的时间分段网络

    TSN 摘要Abstract文章信息引言方法时间分段采样分段聚合输入模态聚合函数多尺度时序窗口集成&#xff08;M-TWI&#xff09;训练 代码实现实验结果总结 摘要 本篇博客介绍了时间分段网络&#xff08;Temporal Segment Network, TSN&#xff09;&#xff0c;这是一种针对视频动…

    扩展域并查集

    什么叫扩展域并查集 1 和 2是敌人&#xff0c;那么就把1好12链接起来&#xff1a;表示1和2是敌人 2和11链接起来也是这个道理 然后2 和3使敌人同理。 最后12连接了1 和 3&#xff0c;表名1 和 3 是 2 的敌人&#xff0c;1和3 就是朋友 1.P1892 [BalticOI 2003] 团伙 - 洛谷 #in…

    【C#语言】C#同步与异步编程深度解析:让程序学会“一心多用“

    文章目录 ⭐前言⭐一、同步编程&#xff1a;单线程的线性世界&#x1f31f;1、寻找合适的对象✨1) &#x1f31f;7、设计应支持变化 ⭐二、异步编程&#xff1a;多任务的协奏曲⭐三、async/await工作原理揭秘⭐四、最佳实践与性能陷阱⭐五、异步编程适用场景⭐六、性能对比实测…

    动态规划入门详解

    动态规划&#xff08;Dynamic Programming&#xff0c;简称DP&#xff09;是一种算法思想&#xff0c;它将问题分解为更小的子问题&#xff0c;然后将子问题的解存起来&#xff0c;避免重复计算。 所以动态规划中每一个状态都是由上一个状态推导出来的&#xff0c;这一点就区别…

    SOFABoot-09-模块隔离

    前言 大家好&#xff0c;我是老马。 sofastack 其实出来很久了&#xff0c;第一次应该是在 2022 年左右开始关注&#xff0c;但是一直没有深入研究。 最近想学习一下 SOFA 对于生态的设计和思考。 sofaboot 系列 SOFABoot-00-sofaboot 概览 SOFABoot-01-蚂蚁金服开源的 s…

    基于基于eFish-SBC-RK3576工控板的智慧城市边缘网关

    此方案充分挖掘eFish-SBC-RK3576的硬件潜力&#xff0c;可快速复制到智慧园区、交通枢纽等场景。 方案亮点 ‌接口高密度‌&#xff1a;单板集成5GWiFi多路工业接口&#xff0c;减少扩展复杂度。‌AIoT融合‌&#xff1a;边缘端完成传感器数据聚合与AI推理&#xff0c;降低云端…

    CSS基础知识一览

    持续维护 选择器 display 常用属性 浮动 弹性布局

    【免费】2000-2019年各省地方财政房产税数据

    2000-2019年各省地方财政房产税数据 1、时间&#xff1a;2000-2019年 2、来源&#xff1a;国家统计局、统计年鉴 3、指标&#xff1a;行政区划代码、地区、年份、地方财政房产税 4、范围&#xff1a;31省 5、指标说明&#xff1a;房产税是对个人和单位拥有的房产征收的一种…

    车载以太网网络测试-21【传输层-DOIP协议-4】

    目录 1 摘要2 DoIP entity status request/response&#xff08;0x4001、0x4002&#xff09;2.1 使用场景2.2 报文结构2.2.1 0x4001&#xff1a;DoIP entity status request2.2.2 0x4002&#xff1a;DoIP entity status response 3 Diagnostic power mode information request/…

    Spring AI Alibaba ChatModel使用

    一、对话模型&#xff08;Chat Model&#xff09;简介 1、对话模型&#xff08;Chat Model&#xff09; 对话模型&#xff08;Chat Model&#xff09;接收一系列消息&#xff08;Message&#xff09;作为输入&#xff0c;与模型 LLM 服务进行交互&#xff0c;并接收返回的聊天…

    基于FPGA频率、幅度、相位可调的任意函数发生器(DDS)实现

    基于FPGA实现频率、幅度、相位可调的DDS 1 摘要 直接数字合成器( DDS ) 是一种通过生成数字形式的时变信号并进行数模转换来产生模拟波形(通常为正弦波)的方法,它通过数字方式直接合成信号,而不是通过模拟信号生成技术。DDS主要被应用于信号生成、通信系统中的本振、函…

    k8s高可用集群安装

    一、安装负载均衡器 k8s负载均衡器 官方指南 1、准备三台机器 节点名称IPmaster-1192.168.1.11master-2192.168.1.12master-3192.168.1.13 2、在这三台机器分别安装haproxy和keepalived作为负载均衡器 # 安装haproxy sudo dnf install haproxy -y# 安装Keepalived sudo yum …

    3DMAX曲线生成器插件CurveGenerator使用方法

    1. 脚本功能简介 3DMAX曲线生成器插件CurveGenerator是一个用于 3ds Max 的样条线生成工具&#xff0c;用户可以通过简单的UI界面输入参数&#xff0c;快速生成多条样条线。每条样条线的高度值随机生成&#xff0c;且可以自定义以下参数&#xff1a; 顶点数量&#xff1a;每条…

    六十天前端强化训练之第二十六天之Vue Router 动态路由参数大师级详解

    欢迎来到编程星辰海的博客讲解 看完可以给一个免费的三连吗&#xff0c;谢谢大佬&#xff01; 目录 一、知识讲解 1. Vue Router 核心概念 2. 动态路由参数原理 3. 参数传递方案对比 二、核心代码示例 1. 完整路由配置 2. 参数接收组件 3. 导航操作示例 三、实现效果示…

    Model Context Protocol:下一代AI系统集成范式革命

    在2023年全球AI工程化报告中,开发者面临的核心痛点排名前三的分别是:模型与业务系统集成复杂度(58%)、上下文管理碎片化(42%)、工具调用标准化缺失(37%)。传统API集成模式在对接大语言模型时暴露明显短板:RESTful接口无法承载动态上下文,GraphQL缺乏工具编排能力,gR…

    Java多线程与高并发专题——Future 是什么?

    引入 在上一篇Callable 和 Runnable 的不同&#xff1f;的最后&#xff0c;我们有提到和 Callable 配合的有一个 Future 类&#xff0c;通过 Future 可以了解任务执行情况&#xff0c;或者取消任务的执行&#xff0c;还可获取任务执行的结果&#xff0c;这些功能都是 Runnable…

    DeepSeek本地搭建

    1. 软件下载安装 Miniconda Miniconda下载地址 选择对应的版本下载&#xff0c;此处下载如下版本 Python 3.10 conda 25.1.1 安装完成后&#xff0c;配置环境变量&#xff0c;打开cmd命令窗口验证 Python Python的版本为 3.10 PyTorch PyTorch下载地址 后面通过命令下…