【CPP】类与继承

news2024/11/15 20:03:46

14 类与继承

  • 在前面我们提到过继承的一些概念,现在我们来回顾一下

打个比方:在CS2中我们把玩家定义为一个类

  • class 玩家:
    • 血量:100
    • 阵营(未分配)
    • 服饰(未分配)
    • 位置(未分配)
    • 武器(未分配)
    • 是否允许携带C4(未分配)
    • 是否拥有C4(未分配)
  • 当对局创建时,会新生成两个类,这两个类继承自"class 玩家"
    • 反恐精英
      • 血量:100(不变)
      • 阵营:反恐精英
      • 服饰:反恐精英
      • 位置(未分配)
      • 武器(未分配)
      • 是否允许携带C4:否
      • 是否拥有C4:否
    • 恐怖分子
      • 血量:100(不变)
      • 阵营:恐怖分子
      • 服饰:恐怖分子
      • 位置(未分配)
      • 武器(未分配)
      • 是否允许携带C4:是
      • 是否拥有C4:是
  • 当用户oldkingnana加入对局时,系统会生成一个类随机继承自反恐精英或者是恐怖分子(我们假设 类:oldkingnana 继承自了反恐精英)
    • oldkingnana
      • 血量:100(不变)
      • 阵营:反恐精英
      • 服饰:反恐精英
      • 位置:反恐精英出生点
      • 武器:野牛(假设用户买了把野牛)
      • 是否允许携带C4:否
      • 是否拥有C4:否
  • 在以上的例子中我们称反恐精英为玩家的子类,玩家为反恐精英的父类,反恐精英继承自玩家这个父类

  • 使用继承这个操作好处多多

    • 因为存在继承这个操作,因此我们不需要重新定义反恐精英或者恐怖分子这样的类,两者相同的部分(血量)全都包括在其 父类:玩家 中,能提高开发效率
    • 当然不仅仅是属性的部分,部分方法也可以修改自父类,如 恐怖分子 只有安装C4这个方法,而在 反恐精英 中就被替换成了拆除C4这种方法
14.1 类继承的语法粗看
class player
{
public:
	void move()
	{
		cout << "move()" << endl;
	}

protected:
	int health;
	int weapon;
};

//此处是类的继承
//class [子类名] : [继承优先级] [父类名]
class team1 : public player
{
public:
	void using_C4()
	{
		cout << "using_C4()" << endl;
	}

private:
	bool C4;
};
  • 子类包含父类的所有成员,只是优先级方面需要着重考虑,及重点研究[继承优先级]
以public优先级继承以protected优先级继承以private优先级继承
父类public标签的成员在子类中为public在子类中为protected在子类中为private
父类protected标签的成员在子类中为protected在子类中为protected在子类中为private
父类private标签的成员在子类中不可访问在子类中不可访问在子类中不可访问
  • 一般情况下常用的也就标记的三个,一般使用public继承就行了,很少会用其他方式继承,况且其他的写多了可读性也会变差,增加debug难度
14.2 简单了解继承在代码中的具体实现方式
14.2.1 普通类作为父类
  • 回到上面CS的例子,我们可以简单写一个类出来
class player
{
public:
	void move()
	{
		cout << "move()" << endl;
	}

protected:
	int health;
	int weapon;
};

//以下就是类"team1",继承自"player"
class team1 : public player
{
public:
	void using_C4()
	{
		cout << "using_C4()" << endl;
	}

private:
	bool C4;
};

int main()
{
	team1 t1;

	return 0;
}
  • (编译通过)
14.2.2 模板类作为父类
template<class T>
class A
{
public:
	A()
		:_a(1)
	{
		cout << "A()" << endl;
	}

	void func1(T& val)
	{
		cout << val << endl;
	}

private:
	int _a;
};

template<class T>
class B : public A<T>
{
public:
	B()
		:_b(2)
	{}

	void func2()
	{
		//子类使用父类的成员函数的时候需要声明类域,具体原因下面会提到
		A<T>::func1(_b);
		//A<T>::func1(1.1f);
	}

private:
	int _b;
};
  • 关于声明类域的原因:
    • 模板在实例化的时候只会按需实例化,比方说上面我想实例化类B,使用B b;来实例化,但却只是实例化了其构造函数,func2()其实并没有被实例化,而实例化B必定会实例化A,但实例化A也只是实例化了构造函数,此时我想在B的成员函数中调用函数func1(),但编译器不知道func1()A的成员函数(因为func1()压根就没有实例化啊),所以找不到
    • 如果未来开发中遇到了"继承链"的情况,而每个"祖先节点"似乎都有同名的成员函数,此时调用的时候就不知道应该用哪个成员函数,所以子类调用"祖先类"的成员函数的时候,一定要指定类域
14.2.3 模板类的继承的应用
  • 例如我们可以很简单地就实现一个栈(没写完)
template<class T>
class myStack : public vector<T>
{
public:
	void push(const T& val)
	{
		vector<T>::push_back(val);
	}
	void pop()
	{
		vector<T>::pop_back();
	}
	void top()
	{
		return vector<T>::back();
	}
};
14.3 父类和子类对象的赋值兼容转换
  • 简单来讲就是子类对象可以被允许赋值给父类对象,包括父类对象类型的引用和指针
class person
{
public:
	person()
		: _num(111), _name("zhangsan"), _sex("male"), _age(18)
	{
	}

protected:
	int _num;
	string _sex;
	int _age;

public:
	string _name;
};

class student : public person
{
public:
	student()
		: _school_id("12345")
	{
	}
protected:
	string _school_id;
};

int main()
{
	student st;

	//这里复制过去的是属于父类对象的一部分,属于子类对象的就没有赋值过去
	person p1 = st;
	//指向子类对象中属于父类对象的那一部分
	person* p2 = &st;
	//引用子类对象中属于父类对象的那一部分
	person& p3 = st;

	//p3引用的是st的一部分,所以修改p3也会导致st被修改
	p3._name = "lisi";
	cout << st._name << endl;
	//输出"lisi"

	return 0;
}

请添加图片描述

  • Ps:父类不可以赋值给子类,这会导致子类有成员变量不确定,语法上是不允许的,不过有方法让父类指针/引用赋值给子类指针/引用
14.4 作用域的隐藏规则
  • 父类和子类的作用域都是独立的
  • 当父类和子类中同时存在同名的成员变量的时候,父类的成员变量将会被隐藏,如果需要调用的话就得指定类域
  • 如果父类和子类中存在相同名称的成员函数的时候,父类的成员函数会被隐藏,并不会构成重载
  • 最好不要触发作用于的隐藏规则
class person
{
public:
	person()
		: _num(111), _name("zhangsan"), _sex("male"), _age(18)
	{
	}

	void func()
	{
		cout << "person::func()" << endl;
	}

protected:
	int _num;
	string _sex;
	int _age;

public:
	string _name;
};

class student : public person
{
public:
	student()
		: _school_id("12345"), _num(666), _name("wangwu")
	{
	}

	void func(int x)
	{
		cout << "student::func()" << endl;
	}

protected:
	string _school_id;
	int _num;
public:
	string _name;
};

int main()
{
	student st;

	cout << st._name << endl;
	cout << st.person::_name << endl;

	st.func(1);
	st.person::func();
	//对于隐藏的成员函数的错误使用方式:
	//(因为person的func被隐藏了,所以需要指定类域来调用)
	//st.func();

	return 0;
}
  • 注意,这里这两个func()构成隐藏而不是重载!!!
14.5 继承与其他默认成员函数
  • Ps:阅读本小节时,我么们可以将父类当作一个整体看待
14.5.1 关于默认构造函数
class person
{
public:
	person(int num = 777, string name = "zhangsan", string sex = "male", int age = 18)
		: _num(num), _name(name), _sex(sex), _age(age)
	{
	}

	void func()
	{
		cout << "person::func()" << endl;
	}

	person(const person& p)
		: _num(p._num), _name(p._name), _sex(p._sex), _age(p._age)
	{
	}

protected:
	int _num;
	string _sex;
	int _age;

public:
	string _name;
};

class student : public person
{
public:
	student(string school_id = "12345", int num = 666, string name = "wangwu", int age = 18, string sex = "male")
		: _school_id(school_id), _num(1), _name(name), person(num, name, sex, age)
		//子类的默认构造中,需要单独对person进行构造,如果不这么做,person就会调用自己的默认构造函数
		//如果person没有默认构造函数,就会报错
	{
		cout << person::_num << endl;
	}

	void func(int x)
	{
		cout << "student::func()" << endl;
	}

	student(const student& st)
		:_school_id(st._school_id), _num(st._num), _name(st._name), person(st)
	{
	}

protected:
	string _school_id;
	int _num;
public:
	string _name;
};

int main()
{
	student st("778899", 444, "oldking");

	student st1(st);

	return 0;
}

//输出: 444
  • 以下是student子类的默认构造不单独对父类进行构造的情况
class person
{
public:
	person(int num = 777, string name = "zhangsan", string sex = "male", int age = 18)
		: _num(num), _name(name), _sex(sex), _age(age)
	{
	}

	void func()
	{
		cout << "person::func()" << endl;
	}

protected:
	int _num;
	string _sex;
	int _age;

public:
	string _name;
};

class student : public person
{
public:
	student(string school_id = "12345", string name = "wangwu", int age = 18, string sex = "male")
		: _school_id(school_id), _num(1), _name(name)
	{
		cout << person::_num << endl;
	}

	void func(int x)
	{
		cout << "student::func()" << endl;
	}

protected:
	string _school_id;
	int _num;
public:
	string _name;
};

int main()
{
	student st("778899", 444, "oldking");

	return 0;
}

//输出: 777
14.5.2 关于拷贝构造
class person
{
public:
	person(int num = 777, string name = "zhangsan", string sex = "male", int age = 18)
		: _num(num), _name(name), _sex(sex), _age(age)
	{
	}

	void func()
	{
		cout << "person::func()" << endl;
	}

	person(const person& p)
		: _num(p._num), _name(p._name), _sex(p._sex), _age(p._age)
	{
	}

protected:
	int _num;
	string _sex;
	int _age;

public:
	string _name;
};

class student : public person
{
public:
	student(string school_id = "12345", int num = 666, string name = "wangwu", int age = 18, string sex = "male")
		: _school_id(school_id), _num(1), _name(name), person(num, name, sex, age)
	{
	}

	void func(int x)
	{
		cout << "student::func()" << endl;
	}

	student(const student& st)
		:_school_id(st._school_id), _num(st._num), _name(st._name), person(st)
		//拷贝构造同样需要对person单独进行拷贝/构造,如果是进行拷贝的话,可以像上面这样使用"切片",编译器自动把st中person的部分拷贝过来
	{
		cout << person::_num << endl;
	}

protected:
	string _school_id;
	int _num;
public:
	string _name;
};

int main()
{
	student st("778899", 444, "oldking");

	student st1(st);

	return 0;
}

//输出: 444
  • 如果不单独对person进行拷贝的话,就会调用person的默认构造函数
class person
{
public:
	person(int num = 777, string name = "zhangsan", string sex = "male", int age = 18)
		: _num(num), _name(name), _sex(sex), _age(age)
	{
	}

	void func()
	{
		cout << "person::func()" << endl;
	}

	person(const person& p)
		: _num(p._num), _name(p._name), _sex(p._sex), _age(p._age)
	{
	}

protected:
	int _num;
	string _sex;
	int _age;

public:
	string _name;
};

class student : public person
{
public:
	student(string school_id = "12345", int num = 666, string name = "wangwu", int age = 18, string sex = "male")
		: _school_id(school_id), _num(1), _name(name), person(num, name, sex, age)
	{
	}

	void func(int x)
	{
		cout << "student::func()" << endl;
	}

	student(const student& st)
		:_school_id(st._school_id), _num(st._num), _name(st._name)
	{
		cout << person::_num << endl;
	}

protected:
	string _school_id;
	int _num;
public:
	string _name;
};

int main()
{
	student st("778899", 444, "oldking");

	student st1(st);

	return 0;
}

//输出: 777
14.5.3 关于赋值重载
class person
{
public:
	person(int num = 777, string name = "zhangsan", string sex = "male", int age = 18)
		: _num(num), _name(name), _sex(sex), _age(age)
	{
	}

	void func()
	{
		cout << "person::func()" << endl;
	}

	person(const person& p)
		: _num(p._num), _name(p._name), _sex(p._sex), _age(p._age)
	{
	}

	person& operator=(const person& p)
	{
		if (this != &p)
		{
			_num = p._num;
			_sex = p._sex;
			_age = p._age;
			_name = p._name;
		}
	}

protected:
	int _num;
	string _sex;
	int _age;

public:
	string _name;
};

class student : public person
{
public:
	student(string school_id = "12345", int num = 666, string name = "wangwu", int age = 18, string sex = "male")
		: _school_id(school_id), _num(1), _name(name), person(num, name, sex, age)
	{
		//cout << person::_num << endl;
	}

	void func(int x)
	{
		cout << "student::func()" << endl;
	}

	student(const student& st)
		:_school_id(st._school_id), _num(st._num), _name(st._name), person(st)
	{
		cout << person::_num << endl;
	}

	student& operator=(const student& st)
	{
		if (this != &st)
		{
			//这里一定要指定类域,否则会因为构成隐藏而造成递归
			person::operator=(st);
			_school_id = st._school_id;
			_num = st._num;
			_name = st._name;
		}
	}

protected:
	string _school_id;
	int _num;
public:
	string _name;
};
14.5.4 关于析构函数
class person
{
public:
	person(int num = 777, string name = "zhangsan", string sex = "male", int age = 18)
		: _num(num), _name(name), _sex(sex), _age(age)
	{
	}

	void func()
	{
		cout << "person::func()" << endl;
	}

	person(const person& p)
		: _num(p._num), _name(p._name), _sex(p._sex), _age(p._age)
	{
	}

	person& operator=(const person& p)
	{
		if (this != &p)
		{
			_num = p._num;
			_sex = p._sex;
			_age = p._age;
			_name = p._name;
		}
	}

	~person()
	{}

protected:
	int _num;
	string _sex;
	int _age;

public:
	string _name;
};

class student : public person
{
public:
	student(string school_id = "12345", int num = 666, string name = "wangwu", int age = 18, string sex = "male")
		: _school_id(school_id), _num(1), _name(name), person(num, name, sex, age)
	{
		//cout << person::_num << endl;
	}

	void func(int x)
	{
		cout << "student::func()" << endl;
	}

	student(const student& st)
		:_school_id(st._school_id), _num(st._num), _name(st._name), person(st)
	{
		cout << person::_num << endl;
	}

	student& operator=(const student& st)
	{
		if (this != &st)
		{
			person::operator=(st);
			_school_id = st._school_id;
			_num = st._num;
			_name = st._name;
		}
	}

	//子类的析构会自动调用父类的析构,我们不需要管父类
	//这是因为父类先被创建,子类后被创建,为了保证先析构子类再析构父类做出的规定
	//(并且,假设我们在子类的析构显式调用父类的析构,还是需要指定类域,因为编译器默认将析构的名字统一规定成了destructor,两个析构构成隐藏)
	~student()
	{
	}

protected:
	string _school_id;
	int _num;
public:
	string _name;
};

int main()
{
	student st("778899", 444, "oldking");

	student st1(st);

	return 0;
}
14.6 不能被继承的类
  • 如果一个类的默认构造函数被标记为private,因为子类实例化的时候需要调用父类的默认构造函数,但因为private的原因其默认构造函数在子类不可见,所以在实例化的时候会导致报错
class Person
{
protected:
	string _name;
	int _age;

private:
	Person()
		:_name("oldking"), _age(18)
	{
	}
};

请添加图片描述

  • CPP11之后,可能是觉得这样的方式不够明显,且子类定义了不会报错,还要到实例化的时候才会报错,就很麻烦,所以新增了关键字final
class Person final
{
public:
	Person()
	:_name("oldking"), _age(18)
	{
	}

protected:
	string _name;
	int _age;
};

请添加图片描述

14.7 继承与友元
  • 友元关系不会被继承,只会保留在父类,被标记的友元函数有访问子类中父类的那部分中的私有/保护成员的权限
//声明类型 
class Student;

class Person
{
public:
	friend void func(const Person& p, const Student& st);

	Person()
		:_name("oldking"), _age(18)
	{
	}

protected:
	string _name;
	int _age;
};

class Student : public Person
{
public:
	Student()
		:_id_number("8877665544")
	{}

protected:
	string _id_number;
};

void func(const Person& p, const Student& st)
{
	cout << p._age << endl;
	cout << st._age << endl;
	
	//下面这行使用会报错
	//cout << st._id_number << endl;
}

int main()
{
	Person p;
	Student st;

	func(st, st);

	return 0;
}

//输出:18
//     18

请添加图片描述

14.8 继承与静态成员
  • 在继承中,静态成员实际只存在一份,由子类和父类共享
class Person
{
public:
	Person()
		:_name("oldking"), _age(18)
	{
	}

	static string _sex;

protected:
	string _name;
	int _age;
};

string Person::_sex = "woman";

class Student : public Person
{
protected:
	string _id_number;
};

int main()
{
	Person p;
	Student st;

	cout << Person::_sex << endl;
	cout << Student::_sex << endl;

	cout << p._sex << endl;
	cout << st._sex << endl;

	return 0;
}

请添加图片描述

14.9 继承模型
14.9.1 单继承
  • 简单来说单继承就是只有一个"继承链"的继承方式,类似于链表,每个类的父节点都是唯一的

  • 类似于 person->student->monitor(班长)

14.9.2 多继承
  • 多继承就相对来讲复杂一些,多继承的出现是为了更好地模拟/描述现实世界,因为现实世界往往是复杂的,各种对象间都存在继承关系,类似于电子产品中的笔记本电脑和手机,它们都属于移动计算机的一种,他们都是移动计算机的子类,但有一个子类同时继承了笔记本电脑和手机,即平板电脑,同时具备有手机和电脑的特性
class Computer
{
protected:
	string _cpu;
	string _screen;
	//...
};

class Phone : public Computer
{
protected:
	string _touch_panel; //触控屏
	//...
};

class Laptop : public Computer
{
protected:
	string _touch_pad; //触控板
	//...
};

class Pad : public Phone, public Laptop
{
};
  • CPP支持多继承,但事实上多继承其实是CPP的一个大坑,像是Java就直接禁用了多继承

  • 这个多继承坑就坑在,他会产生菱形继承,咱来先看看什么是菱形继承

  • 比方说,刚刚我们举的平板电脑的例子就是经典的菱形继承
    请添加图片描述

  • 我们来分析一下菱形继承会导致什么问题

  • 计算机:

    • 计算机的特性
  • 笔记本电脑:

    • 计算机的特性
    • 笔记本额外有的特性
  • 手机:

    • 计算机的特性
    • 手机额外有的特性
  • 平板电脑:

    • 计算机的特性
    • 计算机的特性
    • 手机额外有的特性
    • 笔记本额外有的特性
  • 发现了吗,平板电脑因为既继承了笔记本电脑,又继承了手机,所以其中包含了两个计算机的特性,这就产生了数据冗余和二义性的问题

  • 数据冗余:多了一份重复的数据,空间上会有浪费

  • 二义性,如果我们想访问的数据是重复的那部分数据,编译器就不知道应该访问哪一份的

请添加图片描述

  • 以下是正确使用方式
class Pad : public Phone, public Laptop
{
public:
	void func()
	{
		cout << Phone::_cpu << endl;
	}
};
  • 总的来说,多继承最好别用,非常容易搞混,菱形继承千万别用,弊大于利,极不推荐用,能不用就不用
14.9.3 虚继承
  • 回到上面的例子,如果我实在想用菱形继承怎么办,我们可以加上虚继承

  • 虚继承可以理解为是一个标记,虚继承会标记当前类中的父类,如果当前类的子类是多继承就会影响到子类,将当前类的子类中重复的当前类的父类部分合二为一,所以说虚继承关系到3个具有父子关系的类

class Computer
{
protected:
	string _cpu;
	string _screen;
	//...
};

class Phone : virtual public Computer
{
protected:
	string _touch_panel; //触控屏
	//...
};

class Laptop : virtual public Computer
{
protected:
	string _touch_pad; //触控板
	//...
};

class Pad : public Phone, public Laptop
{
public:
	void func()
	{
		cout << &_cpu << endl;

		cout << &(Phone::_cpu) << endl;
		cout << &(Laptop::_cpu) << endl;
	}
};

int main()
{
	Pad p;
	p.func();

	return 0;
}
  • 可以看到实际在类平板电脑中只创建了一个计算机类
    请添加图片描述

  • 实际开发中的思路(可能?)是这样的

    1. 定义了类计算机,然后从计算机继承下来了手机和笔记本电脑
    2. 考虑到手机和平板电脑可能会作为多继承的父类
    3. 思考到如果作为多继承的父类的话可能会造成类计算机重复存在,会产生数据冗余和二义性
    4. 于是将手机和平板电脑的继承方式改为虚继承
  • 当然,报错了再加virtual也行

  • 假设有以下这种情况

请添加图片描述

  • 此时,重复的数据应该是计算机的这部分数据,所以计算机向笔记本电脑和手机的继承方式应该是虚继承,标记"笔记本电脑"中的"计算机"和"手机"中的"计算机",实际对Windows平板电脑没有影响,影响的是Win掌机
14.9.4 多继承下的子/父类地址关系
  • 我们简单看一看以下代码
class A
{
protected:
	int _a;
};

class B
{
protected:
	int _b;
};

class C : public A, public B
{
protected:
	int _c;
};


int main()
{
	C c;

	A* pa = &c;
	B* pb = &c;
	C* pc = &c;

	cout << pa << endl;
	cout << pb << endl;
	cout << pc << endl;

	return 0;
}
  • pc指向的一定是c的起始地址这毋庸置疑,但papb指向哪里这是有讲究的

  • c中,我们知道父类相当于作为一个独立的对象去存放,构造c真正前,首先要对父类进行构造,于是我们能看到pa指向的位置和pc相同,其实是因为A优先被构造,后面构造的是B,于是我们能看到B紧接着就在A后面

请添加图片描述

  • 所以说,在多继承中,父类会优先被构造且放在当前类的最前面
14.10 继承与组合
14.10.1 黑箱与白箱
  • 黑箱:简单来说,黑箱就是封装好各种设计细节,只保留关键接口给使用者,使用者无需关心底层的设计细节,拿来就用,比方说我们设计一个my_Stack,调用了stl提供的list来实现,我们压根就不需要关心这个链表具体是什么类型,只管用就完事了,我们只需要知道,用它提供的接口可以完成my_Stack的封装,封装完之后依旧仅外露一部分接口,保持黑箱
  • 白箱:白箱就是各种底层细节全部展现给使用者,使用者根据自己的使用情况,想怎么调用就可以怎么调用,甚至能对底层细节进行深度修改
14.10.2 黑箱与白箱的优劣
  • 黑箱:

    • 因为不会暴露底层细节,使用者也不会修改和使用底层细节,所以如果想要修改黑箱其中的细节,使用者无需更改代码,实现的效果依旧不变
  • 白箱:

    • 白箱因为暴露了底层细节,使用者也可以随意调用,就会导致如果对白箱的底层细节做出了修改,因为使用者使用了底层的内容,导致使用者也需要对代码进行修改,是一件非常得不偿失的事
14.10.3 has-a & is-a
  • 两个类的关系可以用has-ais-a来表达

  • has-a:即包含关系,我们可以说一台宝马汽车有4个轮子,这四个轮子就可以作为自定义类放进宝马汽车中

  • is-a:即继承关系,我们可以说宝马汽车是一台车,继承自一台车,但不能说宝马里有个车对吧

14.10.4 继承&组合与黑箱&白箱
  • 不难看出,继承应该对应着白箱,因为父类一般不会设计private标签,所有的底层全都是开放的,导致子类可以很轻松地使用到父类底层的所有东西

  • 而组合就是将一到多个自定义类封装到当前这个大类中,你看不到这几个类的实现细节,只管用就完事了,也就对应着黑箱

  • 所以说,一般来说,如果两个类的关系可以用has-a来表达,就尽量用has-a来表达,尽可能用组合描述事物,除非这个事物用继承来描述更加合适

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

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

相关文章

【Linux庖丁解牛】—Linux基本指令(上)!

&#x1f308;个人主页&#xff1a;秋风起&#xff0c;再归来~&#x1f525;系列专栏&#xff1a; Linux庖丁解牛 &#x1f516;克心守己&#xff0c;律己则安 目录 1、 pwd命令 2、ls 指令 3、cd 指令 4、Linux下的根目录 5、touch指令 6、 stat指令 7、mkdi…

LabVIEW提高开发效率技巧----采用并行任务提高性能

在复杂的LabVIEW开发项目中&#xff0c;合理利用并行任务可以显著提高系统的整体性能和响应速度。并行编程是一种强大的技术手段&#xff0c;尤其适用于实时控制、数据采集以及多任务处理等场景。LabVIEW的数据流编程模型天然支持并行任务的执行&#xff0c;结合多核处理器的硬…

OrCAD使用,快捷键,全选更改封装,导出PCB网表

1 模块名称 2 快捷键使用 H: 镜像水平 V&#xff1a;镜像垂直 R: 旋转 I: 放大 O&#xff1a; 放小 P&#xff1a;放置元器件 W&#xff1a; 步线 B&#xff1a; 总线&#xff08;无电气属性&#xff09; E: 总线连接符&#xff08;和BUS一起用&#xff09…

【网络通信基础与实践第四讲】用户数据报协议UDP和传输控制协议TCP

一、UDP的主要特点 1、UDP是无连接的&#xff0c;减少了开销和发送数据之前的时延 2、UDP使用尽最大努力交付&#xff0c;但是不保证可靠交付 3、UDP是面向报文的。从应用层到运输层再到IP层都只是添加一个相应的首部即可 4、UDP没有拥塞机制&#xff0c;源主机以恒定的速率…

基于JAVA+SpringBoot+Vue的学生干部管理系统

基于JAVASpringBootVue的学生干部管理系统 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末附源码下载链接&#x1f345; 哈…

力扣题解2376

大家好&#xff0c;欢迎来到无限大的频道。 今日继续给大家带来力扣题解。 题目描述&#xff08;困难&#xff09;&#xff1a; 统计特殊整数 如果一个正整数每一个数位都是 互不相同 的&#xff0c;我们称它是 特殊整数 。 给你一个 正 整数 n &#xff0c;请你返回区间 …

【Python报错已解决】SyntaxError invalid syntax

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 专栏介绍 在软件开发和日常使用中&#xff0c;BUG是不可避免的。本专栏致力于为广大开发者和技术爱好者提供一个关于BUG解决的经…

锐尔15注册机 锐尔文档扫描影像处理系统15功能介绍

锐尔文档扫描影像处理系统是一款全中文操作界面的文件、档案扫描及影像优化处理软件&#xff0c;是目前国内档案数字化行业里专业且优秀的影像优化处理软件。 无论是从纸质文件制作高质量的影像文件&#xff0c;或是检查已经制作好的影像文件&#xff0c;锐尔文档扫描影像处理…

Generative Models from the perspective of Continual Learning【小白读论文】

摘要&#xff1a; 本文在持续学习情况下评估各种生成模型。 本文研究了几种模型如何学习和遗忘&#xff0c;并考虑了各种持续学习策略&#xff1a;回放、正则化、生成重放和微调。 我们使用两个定量指标来估计生成质量和记忆能力。 我们在三个常用的持续学习基准&#xff08;MN…

RabbitMQ08_保证消息可靠性

保证消息可靠性 一、生产者可靠性1、生产者重连机制&#xff08;防止网络波动&#xff09;2、生产者确认机制Publisher Return 确认机制Publisher Confirm 确认机制 二、MQ 可靠性1、数据持久化交换机、队列持久化消息持久化 2、Lazy Queue 惰性队列 三、消费者可靠性1、消费者…

新媒体运营

一、新媒体运营的概念 1.新媒体 2.新媒体运营的五大方向 用户运营 产品运营 。。。 二、新媒体的岗位职责及要求 三、新媒体平台

【redis-01】redis基本数据类型和使用场景

redis系列整体栏目 内容链接地址【一】redis基本数据类型和使用场景https://zhenghuisheng.blog.csdn.net/article/details/142406325 redis基本数据类型和使用场景 一&#xff0c;redis基本数据类型和使用场景1&#xff0c;String数据类型2&#xff0c;Hash数据类型3&#xff…

嵌入式linux系统中rk3588芯片引脚基本操作

第一:开发板中linux系统对应设备节点 进入用户 LED 设备文件夹: 1cd /sys/class/leds/usr_led该目录下的文件分别为 brightness、device、max_brightness、power、subsystem、trigger 和 uevent,需要注意的是 brightness、max_brightness 以及 trigger 文件,这三个文件都是…

共享单车轨迹数据分析:以厦门市共享单车数据为例(五)

先来聊聊啥是共享单车电子围栏&#xff1f; 共享单车电子围栏是一种基于地理位置技术的虚拟边界&#xff0c;用于管理和规范共享单车的停放和使用。这种技术通过在地图上划定特定区域&#xff0c;帮助用户了解哪些地方可以停车&#xff0c;哪些地方不能停车&#xff0c;从而减…

网关登录校验(2)----网关如何将用户信息传递给微服务

1.微服务获取用户信息 现在&#xff0c;网关已经可以完成登录校验并获取登录用户身份信息。但是当网关将请求转发到微服务时&#xff0c;微服务又该如何获取用户身份呢&#xff1f; 由于网关发送请求到微服务依然采用的是Http请求&#xff0c;因此我们可以将用户信息以请求头…

【数据结构】假设二叉树采用二叉链表存储,编写一棵二又树中序遍历的非递归算法。

编程题: 假设二叉树采用二叉链表存储,编写一棵二又树中序遍历的非递归算法。 分析: 算法描述: 非递归中序遍历二叉树的算法使用栈来辅助实现。首先,从根节点开始,沿着左子树不断向下, 将每个节点压入栈中。当到达最左端节点后,开始出栈并访问节点,接着转向右子树,重…

【Kubernetes知识点】HPA如何控制不同的资源实现自动扩缩容?

【Kubernetes知识点】HPA如何控制不同的资源实现自动扩缩容&#xff1f; 目录 1 概念 1.1 什么是HPA1.2 Deployment 与 HPA 的关系 1.2.1 工作原理 1.3 StatefulSet 与 HPA 的关系 1.3.1 工作原理 2 实验案例&#xff1a;HPA 控制 StatefulSet 进行扩缩容 2.1 部署一个有状态…

7--SpringBoot-后端开发、原理详解(面试高频提问点)

目录 SpringBoot原理 起步依赖 自动配置 配置优先级 Bean设置 获取Bean 第三方Bean SpringBoot原理 内容偏向于底层的原理分析 基于Spring框架进行项目的开发有两个不足的地方&#xff1a; 在pom.xml中依赖配置比较繁琐&#xff0c;在项目开发时&#xff0c;需要自己去找…

使用Anaconda安装pyTorch

1.Anaconda简介 Anaconda 是一个流行的 Python 数据科学和机器学习平台&#xff0c;它简化了包管理和部署&#xff0c;使得安装、运行和升级包及其依赖变得非常容易。Anaconda 通过其内置的 Conda 包和环境管理器&#xff0c;提供了一个强大的环境&#xff0c;用于科学计算&…

OpenAI GPT o1技术报告阅读(3)-英文阅读及理解

✨继续阅读报告&#xff1a;使用大模型来学习推理(Reason) 原文链接&#xff1a;https://openai.com/index/learning-to-reason-with-llms/ 这次我们继续看一个英文阅读理解的案例。 原问题&#xff1a; The following passage is the draft of an excerpt from a contempora…