【C++】---继承

news2025/1/15 13:10:12

【C++】---继承

  • 一、继承的概念及定义
    • 1、继承的概念
    • 2、定义语法格式
    • 3、继承基类成员访问方式的变化
  • 二、基类 和 派生类 的对象之间的赋值转换
    • 1、赋值规则
    • 2、切片
      • (1)子类对象 赋值 给 父类对象
      • (2)子类对象 赋值 给 父类指针
      • (3)子类对象 赋值 给 父类引用
  • 三、继承中的作用域
    • 1、父类和子类都有独立的作用域
    • 2、隐藏的概念
    • 3、如果是成员函数的隐藏,只需要函数名相同 就构成 隐藏
    • 4、继承中最好不要定义同名成员
  • 四、派生类的默认成员函数
    • 1、规则①
    • 2、规则②
    • 3、规则③
    • 4、规则④
  • 五、继承和友元
  • 六、继承和静态成员
  • 七、菱形继承
    • 1、单继承
    • 2、多继承
    • 3、菱形继承
  • 八、菱形虚拟继承
    • 1、虚继承
    • 2、虚继承的解决数据冗余 和 二义性的原理
  • 九、继承和组合

一、继承的概念及定义

1、继承的概念

继承的概念:

继承机制是面向对象程序设计当中使代码复用的重要手段,它允许程序员,在保持原有类特性的基础上,进行拓展,增加功能,产生新的类,这个新的类就叫做:派生类。

继承的出现,呈现出面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。

以前我们所接触到的复用都是函数的复用,而继承的出现则是体现了:类设计层次的复用。
在这里插入图片描述

2、定义语法格式

在这里插入图片描述

#include<iostream>
using namespace std;


// 基类
class Person
{
public:
	void Print()
	{
		cout << "name->" << _name << endl;
		cout << "age->" << _age << endl;
	}
protected:
	string _name = "peter";
	int _age = 18;
};

// 派生类:stu
class Student :public Person
{
protected:
	int stu_ID;
};
// 派生类:tea
class Teacher :public Person
{
protected:
	int job_ID;
};

int main()
{
	Student s;
	Teacher t;

	s.Print();
	t.Print();

	return  0;
}

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

在这里插入图片描述
不需要记住总结上面的表
我给大家总结两点,只需要记住下面两点规律,一切都好说:

(1)基类private成员在派生类中无论以什么方式继承都是不可见的

(2)基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),访问权限,由大到小:public>protected>private。(Min(成员在基类的访问限定符,继承方式):意思是 取 两者访问权限较小的!)


注意:基类private成员在派生类中无论以什么方式继承都是不可见的

关于什么叫做不可见:

假如将父类成员改为private,那么子类继承了父类的私有成员,内存上,子类对象有这个成员,但是语法上不允许访问
可以理解为父类就是你的父亲,而父类的私有成员就是你父亲藏的私房钱。子类就是你自己,不可见的意思,就是说:你知道你父亲藏有私房钱,但是你找不到你见不到!

二、基类 和 派生类 的对象之间的赋值转换

1、赋值规则

(1)派生类对象可以赋值给:基类对象 / 基类的指针 / 基类的引用。这里有一个形象的说法叫做切割或者切片,就是将派生类中父类那一部分切割下来,赋值过去。(子可以给父)

(2)基类对象不能赋值给派生类对象。(父不能给子)

(3)基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(RunTimeType Information)的dynamic_cast 来进行识别后进行安全转换。(ps:这个我们后面再讲解,这里先了解一下)

2、切片

(1)子类对象 赋值 给 父类对象

子可以赋值给父,但 父不可以赋值给子:
在这里插入图片描述

// 基类
class Person
{
public:
	void Print()
	{
		cout << "name->" << _name << endl;
		cout << "age->" << _age << endl;
	}

	string _name = "peter";
	int _age = 18;
};

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

int main()
{
	Student s;// s是一个 子类对象
	Person p;// p是一个 父类对象

	s._name = "yjl";
	s._age = 20;
	s.stu_ID = 20221071;

	p = s;

	s = p;
	
	return  0;
}

在这里插入图片描述

(2)子类对象 赋值 给 父类指针

在这里插入图片描述

① 子类对象可以赋值给父类指针


// 基类
class Person
{
public:
	void Print()
	{
		cout << "name->" << _name << endl;
		cout << "age->" << _age << endl;
	}

	string _name = "peter";
	int _age = 18;
};

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

int main()
{
	Student s;// s是一个 子类对象
	//Person p;// p是一个 父类对象

	Person* p;// p是一个 父类对象指针

	s._name = "yjl";
	s._age = 20;
	s.stu_ID = 20221071;

	p = &s;

	
	return  0;
}

在这里插入图片描述
② 若父类指针要赋值给子类指针,必须将父类指针强转为子类指针:

int main()
{
 
	//父类指针赋值给子类指针
	Person p;//父类对象
	Student *s;//子类指针
	
	p._name = "yjl";
	p._sex = "male";
	p._age = 6;
 
	Person* pp = &p;//父类指针
 
	s = (Student*)pp;//将父类指针强转为子类型指针后,再赋值给子类型指针
 
	return 0;
}

③ 指向父类对象的父类指针,强转为子类指针后赋值给子类指针可能会造成访问越界

int main()
{
	//指向父类对象的父类指针,强转为子类指针后赋值给子类指针可能会造成访问越界
	Person p;
	
	p._name = "yjl";
	p._sex = "male";
	p._age = 7;
	//s._No = 21530;
 
	Person* pp = &p;
 
	Student* ss = (Student*)pp;
	//ss->_No = 6;访问越界造成程序崩掉
 
	return 0;
}

(3)子类对象 赋值 给 父类引用

// 基类
class Person
{
public:
	void Print()
	{
		cout << "name->" << _name << endl;
		cout << "age->" << _age << endl;
	}

	string _name = "peter";
	int _age = 18;
};

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

int main()
{
	Student s;// s是一个 子类对象
	//Person p;// p是一个 父类对象

	//Person* p;// p是一个 父类对象指针

	

	s._name = "yjl";
	s._age = 20;
	s.stu_ID = 20221071;

	Person& p=s;// p是一个 父类对象引用

	
	return  0;
}

在这里插入图片描述

三、继承中的作用域

1、父类和子类都有独立的作用域

2、隐藏的概念

(1)子类和父类中有同名成员,子类成员将屏蔽父类,对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)

(2)通过代码来演示:隐藏!

class Person
{
public:
	string _name = "yjl";
	int _age = 20;

	void Print1()
	{
		cout << "name->" << _name << endl;
		cout << "age->" << _age << endl;
	}
};

class Student :public Person
{
public:
	int _age = 100;

	void Print2()
	{
		cout << "age->" << _age << endl;
	}
};

int main()
{
	Person p;
	Student s;

	s.Print2();
	return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

// 父类
class Person
{
public:
	string _name = "yjl";
	int _age1 = 20;

	void Print()
	{
		cout << "name->" << _name << endl;
		cout << "age->" << _age1 << endl;
	}
};
// 子类
class Student :public Person
{
public:
	int _age2 = 100;

	void Print(int count)
	{
		while (count)
		{
			cout << "age->" << _age2 << endl;
			count--;
		}
	}
};
int main()
{
	Person p;
	Student s;
	// 这里的s.Print();与父类的Print  构成函数重载×××,构成函数隐藏√√√
	// 父子类函数构成隐藏,只需要满足一点即可(函数名相同)
	s.Print();// 这里认为你用子类对象调用的是子类的打印函数,而不是你想要调用的父类打印函数。
	return 0;
}

在这里插入图片描述

4、继承中最好不要定义同名成员

四、派生类的默认成员函数

(1)什么叫做默认成员函数?

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

(2)派生类的默认成员函数和普通类的默认成员函数的不同区别:

  1. 派生类的构造函数必须要调用基类的构造函数来初始化基类的那一部分成员。如果基类没有写默认的构造函数,那么就必须要在派生类的初始化列表里面对基类的构造函数进行显示调用。(构造:先父后子)
  2. 派生类的拷贝构造函数必须要调用基类的拷贝构造函数,来进行对基类成员的拷贝构造。
  3. 派生类的operator=必须要调用基类的operator=完成基类的赋值
  4. 派生类的析构函数,会在被调用完成之后,自动调用基类的析构函数来清理基类的成员。因为这样才能保证析构函数作用的时候是先析构子类的成员,最后再析构父类的成员的顺序(析构:先子后父)
  5. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同(这个我们后面会讲解)。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加显示调用的情况下,子类析构函数和父类析构函数构成隐藏关系。

总之:派生类=基类+派生类成员(①把基类看作是一个自定义类型的整体->无论是构造机构,拷贝构造,基类就调用自己的默认写的!②派生类成员看作一个普通的类!)

// 父类
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()
	{
		cout << "~Person()" << endl;
	}
	// 赋值运算符重载
	Person& operator=(const Person& p)
	{
		cout << "Person& operator=()" << endl;
		if (this != &p)
		{
			_name = p._name;
		}
		return *this;
	}
protected:
	string _name;
};

// 子类
class Student :public Person
{
public:
	// 构造
	Student(const char* name,int num)
		:Person()
		,_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=" << endl;
		if (this != &s)
		{
			Person::operator=(s);
			_num = s._num;
		}
		return *this;
	}
	// 析构
	~Student()
	{
		cout << "~Student()" << endl;
	}
protected:
	int _num;// 学号
};


void test()
{
	Person p1;

	Student s1("yjl", 2022);
	//Student s2("yjl666", 2024);

	//Student s3(s1);

	//Person p2(s3);

}

int main()
{
	test();

	return 0;
}

1、规则①

规则①:子类的构造函数必须调用父类的构造函数初始化父类的那一部分成员。如果父类没有默认的构造函数,则必须在子类构造函数的初始化列表阶段显式调用。

(1)回忆一下什么是默认构造函数?

  1. 我们没有写编译器,自动生成的。
  2. 我们自己写了无参的构造函数。
  3. 我们自己写了的,带参的全缺省构造函数。

(2)父类里面没有写默认的构造函数和拷贝构造函数:
在这里插入图片描述
当父类没有写默认的构造函数的时候,显式调用父类默认构造函数,是把当作一个整体去调父类的构造函数!
(3)父类里面写了默认的构造函数:

在这里插入图片描述
总结:
(1)父类有默认构造函数,子类只需要在构造函数中初始化子类自己的成员
(2)父类没有默认构造函数,子类要在构造函数初始化列表显式调用父类的构造函数,再初始化子类自己的成员

2、规则②

规则②:子类的拷贝构造函数必须调用父类的拷贝构造完成父类的拷贝初始化。

子类的拷贝构造函数该如何写呢?

调用父类拷贝构造函数时,将子类对象作为参数传给父类拷贝构造函数,这时会发生切片行为,将子类对象中父类成员部分切片赋值给父类成员,完成父类拷贝构造;再将子类自己的成员初始化为拷贝对象的子类成员值:

3、规则③

规则③ 子类的operator=必须要调用父类的operator=完成父类的赋值

如果子类的赋值运算符重载函数里只调用子类的operator=,那么会发生栈溢出:

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

void test()
{
	//Person p1;

	Student s1("yjl", 2022);
	Student s2("yjl666", 2024);

	s1 = s2;// 赋值的时候,会发生:栈溢出

	//Person p2(s3);

}

在这里插入图片描述
这样写才对:

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

4、规则④

规则④ 子类的析构函数会在被调用完成后自动调用父类的析构函数清理父类成员。因为这样才能保证子类对象先清理子类成员再清理父类成员的顺序
1、
(1)析构顺序:先子后父!

(2)构造顺序:先父后子!

在这里插入图片描述
2、注意:由于多态的缘故,对析构的统一处理

// 对析构的统一处理:
	~Student()
	{
		//Person::~Person();
		~Person();
		cout << "~Student()" << endl;
	}

在这里插入图片描述
这是由于父类的析构函数和子类的析构函数构成隐藏,奇不奇怪?

按照隐藏的定义,父类和子类的同名函数才会构成隐藏,为什么父类和子类的析构函数不同名也会构成隐藏呢?

这是因为多态的缘故,任何类的析构函数名都会被统一处理成destructor( )。所以父类和子类析构函数同名,会构成隐藏。解决方法就是加父类作用域,指定访问的是父类的析构函数就可以了:

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

在这里插入图片描述

五、继承和友元

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

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;
}
void main()
{
 Person p;
 Student s;
 Display(p, s);
}

六、继承和静态成员

如果在父类里面定义了一个static静态成员,那么在整个继承体系当中,无论父类派生出多少个子类,整个体系只有这一个静态成员!

下面这些代码是对静态成员变量的诠释,在整个继承体系中,只要有static静态成员变量,那么它就是同一个!

#include<iostream>

using namespace std;

class Person
{
public:
	Person() { _count++; }
protected:
	string _name;
public:
	static int _count;
};

int Person::_count = 0;

class Student : public Person
{
protected:
	int _ID;
};

class Graduate : public Student
{
protected:
	string _course;
};


void TestPerson()
{
	Student s1;
	Student s2;
	Student s3;

	Graduate s4;// 在上面的继承体系中,它是单继承。
	// 创建子类对象的时候必须要先构造父类对象,所以进行到此处_count等于4。
	cout << "人数:" << Person::_count << endl;
	Student::_count = 0;// 因为已经说明了_count是一个静态的,在整个继承体系中它就是一个整体就是一个
	// 对_count修改后他就是0了。
	cout << "人数:" << Person::_count << endl;
}

int main()
{
	TestPerson();
	return 0;
}

在这里插入图片描述

七、菱形继承

1、单继承

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

在这里插入图片描述

2、多继承

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

在这里插入图片描述

3、菱形继承

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

在这里插入图片描述

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

void Test()
{
	Assistant a;
	a._name = "peter";// 这样会有二义性无法明确知道访问的是哪一个_name
	// 因为,a访问的_name没有指明是student继承person的_name,还是teacher继承person的_name。

	// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
	a.Student::_name = "xxx";
	a.Teacher::_name = "xxx";
}

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

在这里插入图片描述

八、菱形虚拟继承

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

1、虚继承

虚继承的语法格式:在两个直接父类的继承方式访问限定符前加vitual:

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

void Test()
{
	Assistant a;
	a._name = "peter";

	/*a.Student::_name = "xxx";
	a.Teacher::_name = "xxx";*/
}

int main()
{
	Test();
	return 0;
}

在这里插入图片描述

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

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

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

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

下图是菱形继承的内存对象成员模型:这里可以看到数据冗余:
在这里插入图片描述

下图是菱形虚拟继承的内存对象成员模型:这里可以分析出D对象中将A放到的了对象组成的最下面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A。

在这里插入图片描述
下面是上面的Person关系菱形虚拟继承的原理解释:

在这里插入图片描述

九、继承和组合

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

(1)public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。

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

(3)优先使用对象组合,而不是类继承 :链接:优先使用对象组合,而不是类继承

(4)实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用
继承,可以用组合,就用组合。

// Car和BMW Car和Benz构成is-a的关系
   class Car{
   protected:
   string _colour = "白色"; // 颜色
   string _num = "陕ABIT00"; // 车牌号
   };
   
   class BMW : public Car{
   public:
   void Drive() {cout << "好开-操控" << endl;}
   };
   
   class Benz : public Car{
   public:
   void Drive() {cout << "好坐-舒适" << endl;}
   };
   
   // Tire和Car构成has-a的关系
   
   class Tire{
   protected:
       string _brand = "Michelin";  // 品牌
       size_t _size = 17;         // 尺寸
   
   };
   
   class Car{
   protected:
   string _colour = "白色"; // 颜色
   string _num = "陕ABIT00"; // 车牌号
    Tire _t; // 轮胎
   };

好了,今天的分享就到这里了
如果对你有帮助,记得点赞👍+关注哦!
我的主页还有其他文章,欢迎学习指点。关注我,让我们一起学习,一起成长吧!

在这里插入图片描述

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

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

相关文章

QT:QT与操作系统

文章目录 信号槽与事件QT多线程概述原理完成倒计时程序 UDP回显服务器服务端客户端 信号槽与事件 在之前的信号槽中&#xff0c;已经有了一个基本的认识&#xff0c;那么对于QT中事件的理解其实就非常的类似&#xff0c;当用户进行某种操作的时候&#xff0c;就会触发事件&…

pytest教程-41-钩子函数-pytest_runtest_teardown

领取资料&#xff0c;咨询答疑&#xff0c;请➕wei: June__Go 上一小节我们学习了pytest_runtest_call钩子函数的使用方法&#xff0c;本小节我们讲解一下pytest_runtest_teardown钩子函数的使用方法。 pytest_runtest_teardown 钩子函数在每个测试用例执行完成后被调用&…

【前端】-【前端文件操作与文件上传】-【前端接受后端传输文件指南】

目录 前端文件操作与文件上传前端接受后端传输文件指南 前端文件操作与文件上传 一、前端文件上传有两种思路&#xff1a; 二进制blob传输&#xff1a;典型案例是formData传输&#xff0c;相当于用formData搭载二进制的blob传给后端base64传输&#xff1a;转为base64传输&…

【linux kernel】linux内核hid触摸源码hid-multitouch.c剖析

文章目录 一、内核中通用hid触摸驱动二、probe过程剖析(1)hid_parse()函数(2)hid_hw_start()函数(3)hid_connect()函数三、hid-multitouch.c应用场景一、内核中通用hid触摸驱动 在linux内核中,为HID触摸面板实现了一个通用的驱动程序,位于/drivers/hid/hid-multitouch.c文件…

连续活跃天数统计

连续活跃天数统计 需求说明 什么是连续出现&#xff1f; 假设有如下日期信息&#xff1a; 20230401,20230402,20230403,20230405,20230406,20230407,20230410,20230411 则&#xff1a; 20230401-20230403 为一次连续出现&#xff0c;连续出现天数为 3 20230405-20230407 为一次…

【Qt QML】ComboBox组件

ComboBox 是一个组合的按钮和弹出列表。它提供了一种以最小的屏幕空间呈现选项列表给用户的方式。ComboBox 使用数据模型填充。数据模型通常是一个 JavaScript 数组、一个 ListModel 或一个整数&#xff0c;但也支持其他类型的数据模型。 下面是一个简单的使用方式。 import …

关于Anaconda常用的命令

常用命令 查看当前环境下的环境&#xff1a;conda env list查看当前conda的版本&#xff1b;conda --version conda create -n your_env_name pythonX.X&#xff08;2.7、3.6等)命令创建python版本为X.X。名字为your_env_name的虚拟环境。your_env_name文件可以在Anaconda安装…

专题五_位运算(3)

目录 137. 只出现一次的数字 II 解析 题解 面试题 17.19. 消失的两个数字 解析 题解 137. 只出现一次的数字 II 137. 只出现一次的数字 II - 力扣&#xff08;LeetCode&#xff09; 解析 注意这里指的是比特位上的01来进行统计的 题解 class Solution { public:int sin…

二叉排序树(二叉搜索树)BST增删改查操作

一、定义 二叉搜索树&#xff08;Binary Search Tree&#xff0c;BST&#xff09;是一种常用的二叉树数据结构&#xff0c;具有以下特点&#xff1a; 1. **排序性质**&#xff1a;对于树中的每个节点&#xff0c;其左子树中的所有节点值都小于该节点的值&#xff0c;而右子树…

类和对象(中篇)(未完结)

文章目录 类的6个默认成员函数构造函数析构函数 类的6个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类。 class Date {};空类中真的什么都没有吗&#xff1f;并不是&#xff0c;任何类在什么都不写时&#xff0c;编译器会自动生成以下6个默认成员函数。 默…

《QT实用小工具·六十一》带动画的三角形指示箭头

1、概述 源码放在文章末尾 该项目实现了一个带动画效果的三角形指示箭头&#xff0c;项目demo演示如下所示&#xff1a; 用法 interestingindicate.h interestingindicate.cpp 放到工程中&#xff0c;直接使用即可。 注意&#xff1a;建议绝对布局&#xff0c;手动指定 wid…

分红76.39亿,分红率再创新高,成长活力无限的伊利带来丰厚回报

伊利47万股东&#xff0c;又等来了一个好消息。 4月29日&#xff0c;伊利股份发布2023年报&#xff0c;实现营业总收入1261.79亿元&#xff0c;归母净利润104.29亿元&#xff0c;双创历史新高&#xff0c;实现连续31年稳健增长。 在递交亮眼成绩单的同时&#xff0c;乳业巨头伊…

Linux系统编程--网络编程

一、OSI网络七层模型 OSI模型将整个网络通信过程分解为七个层次&#xff0c;每个层次都为网络通信提供了特定的功能。以下是OSI模型的七个层次&#xff0c;从上到下依次是&#xff1a; 应用层&#xff08;Application Layer&#xff09;&#xff1a;为应用软件提供网络服务&am…

MySQL部署系列-centos离线安装MySQL

MySQL部署系列-centos离线安装MySQL 文章目录 MySQL部署系列-centos离线安装MySQL1. 查看是否已经安装 Mysql3. 下载官方 Mysql 包3. 下载之后上传到服务器4. 创建用户组5. 创建数据目录并赋予权限6. 修改配置文件 vim /etc/my.cnf7. 初始化数据库(数据库安装)8. 加入到系统服务…

多个开源的js补环境框架测试

原文链接&#xff1a;https://mp.weixin.qq.com/s/uEMFGpE5bqmTvzSgX2twvA 前言 在做js逆向时肯定会遇到补环境的情况&#xff0c;看到github开源了好几个补环境用的框架&#xff0c;这篇文章做个测试&#xff0c;看看哪个比较好用。 https://github.com/pysunday/sdenvhttp…

word格式技巧

文章目录 论文格式技巧论文交叉引用怎么弄论文的页码怎么弄 论文格式技巧 论文交叉引用怎么弄 1.取消文献原有的编号 2.定义新编号 3.具体编号设置 4.在引用的地方插入&#xff0c;具体引用选项卡–>交叉引用–>选择后插入 2. 4. 论文的页码怎么弄 假设我们有这样一…

探索DeepSeek平台:新一代MoE模型的深度体验

简介 DeepSeek是一个创新的人工智能平台&#xff0c;它最近推出了其最新版本的模型——DeepSeek-V2 MoE&#xff08;Mixture of Experts&#xff09;。这个平台不仅提供了一个交互式的聊天界面&#xff0c;还提供了API接口&#xff0c;让用户可以更深入地体验和利用这一先进的…

scala速通(精简版)

1.变量和常量 var name [:VariableType] value // variable val name [:ConstantType] value // constant1.声明变量时&#xff0c;类型可以省略 2.类型定义后就不能修改言 3.变量声明必须有初始值 4.变量&#xff0c;常量分别用var&#xff0c;val声明修饰 2.标识符命名…

构建自己的docker镜像node.js

学习资源&#xff1a; 构建自己的 Docker 镜像_哔哩哔哩_bilibili 针对其中的一些比较困难的点写篇文章。 以下是对app.js的注释&#xff1a; // 使用 Koa 框架搭建 Node.js 应用的示例代码// 这两行代码引入了 koa 模块&#xff0c;并创建了一个新的 Koa 应用实例&#xf…

vue2项目升级到vue3经历分享4

后端重构&#xff0c;如果接口做好抽象封装&#xff0c;只需要考虑jar之间的兼容性问题&#xff0c;jdk版本不变&#xff0c;基本不用做太大的调整&#xff0c;但是前端就不一样&#xff0c;除了vue框架本身&#xff0c;css的调整&#xff0c;改起来更是让人头疼。前面写了vue2…