【C++】继承

news2024/11/24 16:53:38

​🌠 作者:@阿亮joy.
🎆专栏:《吃透西嘎嘎》
🎇 座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根
在这里插入图片描述

目录

    • 👉继承的概念及定义👈
      • 继承的概念
      • 继承的定义
        • 1. 定义格式
        • 2. 继承基类成员访问方式的变化
    • 👉基类和派生类对象赋值转换👈
    • 👉继承中的作用域👈
    • 👉派生类的默认成员函数👈
      • 构造函数
      • 拷贝构造
      • 赋值运算符重载
      • 析构函数
      • 取地址重载
    • 👉继承与友元👈
    • 👉继承与静态成员👈
    • 👉复杂的菱形继承及菱形虚拟继承👈
      • 单继承、多继承和菱形继承
      • 菱形继承的二义性和数据冗余
      • 虚拟继承的原理
    • 👉继承的总结和反思👈
    • 👉笔试面试题👈
    • 👉总结👈

👉继承的概念及定义👈

继承的概念

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

在这里插入图片描述

#include <iostream>
using namespace std;
#include <string>

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

class Student : public Person
{
protected:
	int _stuid; // 学号
};

class Teacher : public Person
{
protected:
	int _jobid; // 工号
};
int main()
{
	Student s;
	s.Print();

	Teacher t;
	t.Print();

	return 0;
}

在这里插入图片描述
继承后,父类的 Person 的成员(成员函数+成员变量)都会变成子类的一部分。这里就体现出了 Student 和 Teacher 复用了 Person 的成员。我们使用监视窗口查看 Student 和 Teacher 对象,可以看到变量的复用,调用 Print 函数可以看到成员函数的复用。

继承的定义

1. 定义格式

从下图,我们可以看到 Person 是父类,也称作基类。Student 是子类,也称作派生类。

在这里插入图片描述
注:子类对象不一定比父类对象对象大,因为有可能子类只是改写父类的方法而已,并没有增加其自身的数据成员,则大小一样。

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

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
总结:

  1. 基类 private 成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它
  2. 基类 private 成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在 派生类中能访问,就定义为 protected。可以看出保护成员限定符是因继承才出现的
  3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式等于 min(成员在基类的访问限定符,继承方式),且 public > protected > private。
  4. 使用关键字 class 的默认的继承方式是 private,使用struct 的默认的继承方式是 public,不过最好显式写出继承方式
  5. 在实际运用中一般使用都是 public 继承,几乎很少使用 protetced 或者 private 继承,也不提倡使用protetced 或者 private继承。因为 protetced 和 private 继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。
  6. protected 和 private 修饰的成员变量和成员函数在类外都不能访问。

子类无法访问父类 private 修饰的成员

在这里插入图片描述

class 默认是 private 继承

在这里插入图片描述
私有成员变量和函数在类外都不能访问。

👉基类和派生类对象赋值转换👈

  • 派生类对象可以赋值给基类的对象 / 基类的指针 / 基类的引用,该过程不存在类型转换,也就是不会产生临时变量(临时变量具有常性)。这里有个形象的说法叫切片或者切割寓意把派生类中父类那部分切来赋值过去。
  • 基类对象不能赋值给派生类对象。
  • 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用,但是必须是基类的指针是指向派生类对象时才是安全的。基类如果是多态类型,可以使用 RTTI(RunTime Type Information) 的dynamic_cast 来进行识别后进行安全转换。
  • dynamic_cast 是将一个基类对象指针(或引用)转换到继承类指针,dynamic_cast 会根据基类指针是否真正指向继承类指针来做相应处理。

在这里插入图片描述

class Person
{
protected:
	string _name; // 姓名
	string _sex; // 性别
	int _age; // 年龄
};
class Student : public Person
{
public:
	int _No; // 学号
};

int main()
{
	// 中间不存在类型转换,不会产生临时变量
	// 子类对象可以赋值给父类对象/指针/引用
	Student s;
	Person p = s;
	Person& rp = s;
	Person* pp = &s;

	// 中间存在类型转换,会产生临时变量,临时变量具有常性
	int i = 1;
	double  d = 2.2;
	i = d;
	const int& ri = d;

	return 0;
}

在这里插入图片描述

👉继承中的作用域👈

  1. 在继承体系中基类和派生类都有独立的作用域。
  2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏, 也叫重定义。在子类成员函数中,可以使用基类::基类成员的方式显式访问。
  3. 需要注意的是:如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
  4. 注意:在实际开发中,在继承体系里面最好不要定义同名的成员
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;
};

在这里插入图片描述
注:当子类和父类存在同名成员时,默认是访问子类的成员(就近原则)。如果想要访问父类的成员,就要指定父类的作用域了。

// 题目1
class A
{
public:
	void fun()
	{
		cout << "A::func()" << endl;
	}
};
class B : public A
{
public:
	void fun(int i)
	{
		cout << "B::func(int i)->" << i << endl;
	}
};

void Test()
{
	B b;
	b.fun(10);
}

// 题目二
class A
{
public:
	void fun()
	{
		cout << "A::func()" << endl;
	}
};
class B : public A
{
public:
	void fun(int i)
	{
		cout << "B::func(int i)->" << i << endl;
	}
};

void Test()
{
	B b;
	b.fun();
}

题目一和题目二中的父类和子类的 func 函数构成隐藏(重定义),并不是构成重载。函数构成重载的前提条件是在同一作用域,而父类和子类的作用域不是同一个作用域。题目一的运行结果是B::func(int i)->10,而题目二的运行结果是编译报错。

题目一运行结果

在这里插入图片描述
题目二运行结果

在这里插入图片描述
出现这样的结果是因为父类和子类的 fun 函数形成了隐藏,如果想要调用父类的 fun 函数需要指定作用域。

在这里插入图片描述

一道选择题

关于同名隐藏的说法正确的是( )
A. 同一个类中,不能存在相同名称的成员函数。
B. 在基类和子类中,可以存在相同名称但参数列表不同的函数,他们形成重载。
C. 在基类和子类中,不能存在函数原型完全相同的函数,因为编译时会报错。
D. 成员函数可以同名,只要参数类型不同即可,成员变量不能同名,即使类型不同。

答案:D
解析:A 选项:可以存在,如函数重载。B、C 选项:基类与子类函数名字相同,参数不同,形成的是隐藏,可以共存。D 选项:成员函数在同一个类里面同名,此时构成了重载,但变量一定不能同名,故正确。

👉派生类的默认成员函数👈

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

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

简单回顾一下默认成员函数的行为,编译器生成的构造函数和析构函数对于内置类型不处理,对于自定义类型调用该类型的构造函数和析构函数;而编译器生成的拷贝构造和赋值运算符重载对于内置类型完成值拷贝,对于自定义类型调用该类型的拷贝构造和赋值运算符重载。

构造函数

派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员变量。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。 派生类对象初始化先构造基类再构造派生类。

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

拷贝构造

派生类的拷贝构造函数必须调用基类的拷贝构造完成基类成员变量的拷贝初始化。

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

赋值运算符重载

派生类的赋值运算符重载必须要调用基类的赋值运算符重载完成基类成员变量的赋值。需要特别注意的是:因为子类和父类的赋值运算符重载构成隐藏关系,所以调用基类的赋值运算符重载必须指定作用域,否则会调用派生类的赋值运算符重载,从而形成死递归,造成栈溢出。

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

在这里插入图片描述

析构函数

析构函数也和上面的三个默认成员函数一样,父类成员调用父类的析构函数处理,子类成员由子类自己处理。但是像下面的写法是错误的!!!

在这里插入图片描述

上面的写法是无法通过编译的,原因是后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同。那么编译器会对析构函数名进行特殊处理,处理成 destrutor(),所以父类析构函数不加 virtual 的情况下,子类析构函数和父类析构函数构成隐藏关系。所以我们要调用父类的析构函数,就需要指定作用域了。

指定父类的作用域调用父类的析构函数后,我们把代码运行起来,就会看到父类的析构函数的调用多了一倍。这是什么原因呢?

在这里插入图片描述

其实是因为子类的析构函数会在被调用完成后自动调用父类的析构函数清理父类成员,这样才能保证子类对象先清理子类成员再清理父类成员的顺序。所以我们不需要在子类的析构函数中调用父类的析构函数,否则有可能无法保证先析构子类后析构父类的顺序。

在这里插入图片描述

为什么要先析构子类后析构父类呢?因为父类是先于子类定义的,需要先构造父类再构造子类,而类构函数调用一般按照构造函数调用的相反顺序进行调用。但是要注意 static 对象的存在, 因为 static 改变了对象的生存周期,需要等待程序结束时才会析构释放对象。所以需要先调用子类的析构再去调用父类的析构。

在这里插入图片描述

注:多次析构可能会报错!!!

取地址重载

注意:取地址重载分为普通对象的取地址重载和 const
对象的取地址重载,这两个函数都不需要调用父类的取地址重载,而取地址重载一般也不需要自己写,编译器默认生成的就够用了。

在这里插入图片描述

在这里插入图片描述

完整代码

#include <iostream>
using namespace std;
#include <string>

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:
	Student(const char* name, int num)
		: Person(name)
		, _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)
	{
		if (this != &s)
		{
			Person::operator=(s);	// 切片
			_num = s._num;

			cout << "Student& operator=(const Student& s)" << endl;
		}
		return *this;
	}

	// 子类的析构函数和父类的析构函数构成隐藏
	// 由于多态的需要,编译器会将析构函数处理成destrutor()
	// 父类的析构函数不需要显式调用,它会被自动调用以确保先清理子类成员再处理父类成员
	~Student()
	{
		//Person::~Person();
		// 处理子类的成员
		cout << "~Student()" << endl;
	}

	// 一般不需要自己写
	//Student* operator&()
	//{
	//	return this;
	//}

protected:
	int _num; //学号
};

int main()
{
	Student s1("Joy", 19);
	cout << &s1 << endl;

	Student s2(s1);

	Student s3("Paige", 20);
	s3 = s1;

	return 0;
}

👉继承与友元👈

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

在这里插入图片描述

如果想让友元函数也能访问子类的protectedprivate的成员,那么需要在子类中声明一下友元函数。

在这里插入图片描述

注:友元函数需要慎用,可能会破号封装性!

👉继承与静态成员👈

基类定义了 static 静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个 static 成员实例。静态成员是存储在静态区的,生命周期是程序的整个运行时间!注:静态成员变量一定要在类外进行初始化。

因为静态成员只有一个,所以父类和子类访问的静态成员都是同一个静态成员,其地址是相同的!

在这里插入图片描述

因为父类对象和子类对象都要调用父类的构造函数,所以在父类的构造函数里让静态成员变量_count++,这样就能知道父类对象和子类对象的总和了。

#include <iostream>
using namespace std;
#include <string>

class Person
{
public:
	Person()
	{
		++_count;
	}
protected:
	string _name; // 姓名
public:
	static int _count; // 统计人的个数。
};
int Person::_count = 0;

class Student : public Person
{
protected:
	int _stuNum; // 学号
};

int main()
{
	Person p1;
	Person p2;
	Person p3;
	Student s1;
	Student s2;

	cout << "人数:" << Person::_count << endl;

	return 0;
}

👉复杂的菱形继承及菱形虚拟继承👈

单继承、多继承和菱形继承

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

在这里插入图片描述
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承。

在这里插入图片描述
菱形继承:菱形继承是多继承的一种特殊情况。

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

在这里插入图片描述

// 菱形继承
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; // 主修课程
};

菱形继承的二义性和数据冗余

在这里插入图片描述
二义性问题可以通过指定作用域来解决,但是这样也还是没有解决掉数据冗余的问题,同时还会调用两次父类Person的拷贝函数。

在这里插入图片描述

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

在这里插入图片描述

#include <iostream>
using namespace std;
#include <string>

// 菱形虚拟继承
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 at;
	// 菱形虚拟继承解决了二义性和数据冗余的问题
	at.Student::_name = "张三";
	at.Teacher::_name = "张老师";
	cout << at.Student::_name << endl;
	cout << at._name << endl;

	return 0;
}

在这里插入图片描述

中间腰部两个类StudentTeacher虚拟继承父类Person就能够解决掉二义性和数据冗余的问题了。

开发时,我们要尽量避免使用菱形继承。不过,标准库中也使用了菱形继承。

在这里插入图片描述

虚拟继承的原理

为了研究虚拟继承原理,我们给出了一个简化的菱形继承继承体系,再借助内存窗口观察对象成员的模型(注:监控窗口看到的模型可能不是实际的模型)。

我们通过对比菱形继承和菱形虚拟继承的,来探索菱形虚拟继承的原理。

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 关系菱形虚拟继承的原理解释:

在这里插入图片描述

现在我们知道了类型虚拟继承的模型,那么我们再深入了解一下 B 对象的模型是怎么样的以及其大小是多少。

在这里插入图片描述

菱形虚拟继承为了解决二义性和数据冗余的问题,还是付出了一些代价的。不过,这个代价还是在可接受的范围之内的。

在这里插入图片描述

👉继承的总结和反思👈

  1. 很多人说 C++ 语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度及性能上都有问题。
  2. 多继承可以认为是 C++ 的缺陷之一,很多后来的面向对象语言都没有多继承,如 Java。
  3. 继承和组合
  • public 继承是一种 is-a 的关系。也就是说每个派生类对象都是一个基类对象。
  • 组合是一种 has-a 的关系。假设 B组合了 A,每个B对象中都有一个 A 对象。
  • 优先使用对象组合,而不是类继承 。
  • 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。白箱是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响(如:基类增删或删减成员变量)。派生类和基类间的依赖关系很强,耦合度高。
  • 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以黑箱的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。
  • 实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承。比如要实现多态,就必须要继承。类之间的关系可以用继承,也可以用组合,那么就用组合。比如:栈stack的底层容器是vector / deque / list,即可以用继承的方式实现,也可以用组合的方式实现,而我们选择组合的方式来实现。因为组合的耦合度低。
  • 组合无法访问类的protected成员,而继承可以访问父类的protected成员。
  • is - a 关系:人和学生、植物和玫瑰花等等。
  • has - a 关系:轮胎和车等等。
  • 高内聚低耦合,是判断软件设计好坏的标准。这个概念主要用于程序的面向对象的设计,主要看类的内聚性是否高,耦合度是否低。目的是使程序模块的可重用性、移植性大大增强。

👉笔试面试题👈

如何定义一个无法被继承的类?

  • 父类构造函数私有,子类对象实例化时无法调用父类的构造函数(C++ 98)

在这里插入图片描述

  • 关键字 final 修饰的类,表示该类是最终类,不能被继承(C++ 11)

在这里插入图片描述


多继承中指针偏移问题?下面说法正确的是( )

  • A:p1 == p2 == p3
  • B:p1 < p2 < p3
  • C:p1 == p3 != p2
  • D:p1 != p2 != p3
class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 
{ public: int _d; };

int main() 
{
	Derive d;
	Base1* p1 = &d;
	Base2* p2 = &d;
	Derive* p3 = &d;
	return 0;
}

答案:C
解析:位于左边的父类先继承,且位于低地址!

在这里插入图片描述


  1. 什么是菱形继承?菱形继承的问题是什么?
  2. 什么是菱形虚拟继承?如何解决数据冗余和二义性的
  3. 继承和组合的区别?什么时候用继承?什么时候用组合?

👉总结👈

本篇博客主要讲解继承的概念和定义、基类和派生类对象的赋值转换、继承中的作用域、派生类的默认成员函数、继承与友元、继承与静态成员、菱形继承以及基础和组合的区别等等。那么以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️

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

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

相关文章

通俗易懂的java设计模式(6)-代理模式

1.什么是代理模式 为某个对象提供一个代理对象&#xff0c;通过这个代理对象&#xff0c;可以控制对原对象的访问。 通俗的解释&#xff1a;我们电脑桌面上的一个个快接方式&#xff0c;当我们点击这个快捷方式的时候&#xff0c;我们就间接地访问到了这个程序。 2.静态代理…

附录B:Standard Delay Format(SDF)(下)

文章目录B.4 映射实例(Mapping Examples)传播延迟(Propagation Delay)输入建立时间(Input Setup Time)输入保持时间(Input Hold Time)输入建立和保持时间(Input Setup and Hold Time)输入恢复时间(Input Recovery Time)输入撤销时间(Input Removal Time)周期(Period)脉宽(Pulse…

C#自动化物流仓库WMS系统源码

分享一套C#自动化仓库WMS管理系统源码 MF00426 需要源码学习可私信我获取。 WMS作为现代物流系统中的主要组成部分&#xff0c;是一种多层次存放货物的高架仓库系统&#xff0c;由自动控制与管理系统、货架、巷道式堆垛机、出入库输送机等设 备构成&#xff0c;能按指令自动完…

PHP多进程(二)

上篇文章我们说到父进程应该回收子进程结束之后产生的数据,这样才会不浪费系统资源。 一个程序启动之后&#xff0c;变成了一个进程&#xff0c;进程在以下情况会退出 1&#xff09;运行到最后一行语句 2) 运行时遇到return 时 3) 运行时遇到exit()函数的时候 4) 程序异常的时…

【Dash搭建可视化网站】项目13:销售数据可视化大屏制作步骤详解

销售数据可视化大屏制作步骤详解1 项目效果图2 项目架构3 文件介绍和功能完善3.1 assets文件夹介绍3.2 app.py和index.py文件完善3.3 header.py文件完善3.4 api.py文件和api.ipynb文件完善3.4.1 需求数据获取3.4.2 header.py文件中数据变更3.5 middle.py文件完善3.5.1 中间第一…

24.数组名字取地址变成数组指针,数组名和指针变量的区别

数组名字取地址变成数组指针 1.一维数组名字取地址&#xff0c;变成一维数组指针&#xff0c;加1跳一个一维数组。 int a[10]; a1 跳一个整型元素&#xff0c;是a[1]的地址 a和a1相差一个元素&#xff0c;4个字节 &a 就变成了一个一位数组指针&#xff0c;是int(*p)[10]…

结构体重点知识大盘点

0、结构体基础知识 1、结构体是一些值的集合&#xff0c;这些值被称为成员&#xff0c;它们的类型是可以不同的。&#xff08;与数组相似&#xff0c;但数组元素的类型都是相同的&#xff09;。用来描述由基本数据类型组成的复杂类型。 2、结构体也有全局的和局部的。 3、st…

Hello World with VS 17.4.4 DOT NET MAUI Note

Hello World with VS 17.4.4 DOT NET MAUI Note kagula2023-1-12 Prologue If you touched XAML, well here is a concise guide for you running the first MAUI project. Content System Requirement 【1】Microsoft Windows [Version 10.0.19044.2486] Chinese Language …

Ubuntu Centos Linux End Kernel panic-not syncing:Attempted to kill init!

原问题&#xff1a; 当前系统为 Ubuntu 解决问题步骤&#xff1a; 1、重启电脑&#xff0c;在进入选择版本时&#xff0c;选择 系统高级选项&#xff0c; 我选的是【Ubuntu高级选项】 2、进入一个又很多系统版本的界面&#xff0c;每个版本有三个选项&#xff1a;常规启动版…

如何在 ASP.NET Core 2.X 项目中通过EF Core使用MySQL数据库

目录 一、安装MySql.Data.EntityFrameworkCore 二、创建EF Core上下文类以及相关数据模型类 三、配置连接字符串 四、在Starup.cs中注册数据库服务&#xff08;配置Context类的依赖注入&#xff09; 五、通过数据迁移命令生成数据库 目前EF Core已经支持了MySQL数据库了。…

从零开始带你实现一套自己的CI/CD(四)Jenkins Pipeline流水线

目录一、简介二、Groovy2.1 HelloWorld2.2 Pipeline script from SCM三、Jenkinsfile3.1 拉取代码3.2 代码质量检测3.3 构建代码3.4 制作镜像并发布镜像仓库3.5 部署到目标服务器3.6 完整的Jenkinsfile3.7 参数配置3.8 通过参数构建四、添加邮件通知4.1 配置Jenkins邮件配置4.2…

开源飞控初探(一):无人机的历史

这章先纠正大疆带给无人机外行小白的认知。定义无人机无人机的正式英文名字是Unmanned Aerial Vehicle&#xff0c;缩写为UAV。有人无人的区分&#xff0c;是看飞机能否一直需要人为操控。最简单的场景是&#xff0c;当飞机飞出视线之外时&#xff0c;人已经很难实时根据环境来…

微服务自动化管理【docker compose】

1.什么是docker-compose Docker-Compose项目是Docker官方的开源项目&#xff0c;负责实现对Docker容器集群的快速编排 通过编写docker-compose文件可对多个服务同时进行启动/停止/更新(可定义依赖&#xff0c;按顺序启动服务) docker-compose将所管理的容器分为3层结构&#…

Yii2下PHP远程调试PHP5.6/7.2与Xdebug2.5/2.7/3.0 在PHPSTORM下的差异化表现

学习起因&#xff1a;新人学YII2不知道远程调试(远程调试和控制台调试是两件事&#xff0c;同一个原理) 因为yii2框架&#xff0c;设计复杂度非常高&#xff0c;加上php代码的弱类型语言结构&#xff0c;在代码非常复杂的情况下&#xff0c;不采用调试的方式来看源码调用栈&am…

MPLS 虚拟专线 实验

目录 1.拓扑图 2.实验思路 3.主要配置 4.测试 5.实验总结 1.拓扑图 2.实验思路 IGP路由 MPLS Domain 配置MPLS VPN PE之间的MP-BGP邻居关系 CE端与PE端的路由交换 双向重发布&#xff0c;实现路由共享 3.主要配置 R6&#xff1a; *公网环境&#xff1a; [r6]ospf 1 r…

记录robosense RS-LIDAR-16使用过程2

一、安装并使用可视化工具RSView&#xff0c;官网提供了不同版本的安装包&#xff0c;根据个人环境下载解压。本人ubuntu18系统&#xff0c;修改权限&#xff1a;chmod ax run_rsview.sh;然后运行&#xff1a;./run_rsview.sh。该软件每次启动时都要运行./run_rsviewer.sh该软件…

Acwing 1214. 波动数列

题目链接&#xff1a;1214. 波动数列 - AcWing题库 标签&#xff1a;动态规划 &#xff08;字好丑...&#xff09; AC代码&#xff1a; #include<iostream> using namespace std;int f[1005][1005];const int MOD 100000007;//返回正余数 int get_mod(int a,int b) {…

不重复的随机数问题

前言 对于随机数的运用&#xff0c;在开发中经常会用到。有时需要生成不重复的定范围定总量的随机数&#xff0c;比如1~20&#xff0c;需要打乱的1~20中的10个数&#xff0c;那到底怎么做呢? 一、不重复的随机数 我们知道&#xff0c;直接用random会有重复的数字&#xff0…

电商物流云仓的原理是什么?

以云的速度和范围获得胜利  这是一个快速转型时期&#xff0c;封锁、就地避难订单和游览限制扰乱了美国经济的各个范畴&#xff0c;对供给链运营产生了严重影响。在如此动乱的时期&#xff0c;企业正越来越多地转向云优先战略&#xff0c;以使其供给链愈加矫捷和灵敏。  战…

【NI Multisim 14.0原理图文件管理——保存/备份文件及新建电路图页文件】

目录 序言 ⛄1.保存文件 ⛄2.备份文件 ⛄3.新建电路图页文件 序言 NI Multisim最突出的特点之一就是用户界面友好。它可以使电路设计者方便、快捷地使用虚拟元器件和仪器、仪表进行电路设计和仿真。 首先启动NI Multisim 14.0&#xff0c;打开如图所示的启动界面&#xff…