C++笔记:OOP三大特性之继承

文章目录

  • 一、继承的概念和定义
    • 1.1 概念
    • 1.2 定义格式
    • 1.3 继承关系和访问限定符
  • 二、基类和派生类对象赋值兼容转换
    • 2.1 类型转换存在临时对象的意义
    • 2.2 赋值兼容转换不会产生临时变量
  • 三、继承中的作用域
  • 四、派生类中的默认成员函数
    • 4.1 构造
    • 4.2 拷贝构造
    • 4.3 赋值重载
    • 4.4 析构
  • 五、继承与友元
  • 六、继承与静态成员
  • 七、复杂的菱形继承和菱形虚拟继承
    • 7.1 单继承与多继承
    • 7.2 菱形继承与虚继承
    • 7.3 虚继承的原理
  • 八、继承的总结和反思

一、继承的概念和定义

1.1 概念

假设现在要去设计一个学生管理系统,从数据库设计的角度来看,系统的整体是由一个个实体和它们之间的关系组成的,典型的三大实体有学生(Student)、教师(Teacher)、学校领导(Leader)。

在面向对象的编程中,通常会使用类来表示这些实体,转换成代码得到如下三个类:
在这里插入图片描述

但是通过观察却发现,有一部分属性和方法是这三个类都有的,仔细一想也可以理解,因为像姓名、年龄、家庭住址、联系电话都是一个人应该具有的基本属性,但是同样的东西却定义多份,这会导致代码冗余。

为了解决代码冗余问题,C++之父Bjarne Stroustrup提出了一种机制,称为 “继承”。这种机制允许将多个类共有的特征提取出来放到一个称为 “父类” 或 “基类” 的类中。然后,其他类可以从这个父类继承这些共有特征,并在自己定义自己独有的成员。这些继承了共有特征的类被称为 “子类” 或 “派生类”。当需要使用父类中的特征时,子类可以直接从父类中获取。

class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "peter"; // 姓名int _age = 18; // 年龄
};class Student : public Person
{
public:void SetInfo(){_name = "张三";_age = 55;}
protected:int _stuid; // 学号
};class Teacher : public Person{
protected:int _jobid; // 工号
};int main()
{Person p;Student s;Teacher t;p.Print();s.Print();t.Print();return 0;
}

在这里插入图片描述

【说明】

  • Person 类是父类,Student 类和 Teacher 类是子类。
  • 子类继承了父类之后,子类对象(st)能够使用父类的的成员和方法。

1.2 定义格式

类之间继承的格式如下:
在这里插入图片描述

1.3 继承关系和访问限定符

继承方式有三种:public(公有)继承、protected(保护)继承、private(私有)继承。

访问限定符也有三种:public(公有)访问、protected(保护)访问、private(私有)访问。

基类中不同访问限定符修饰的成员在派生类中的访问权限会发生变化,总结之后会得到如下的一张表:

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

【总结】

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

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

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

  4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。

  5. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。

在这里插入图片描述

二、基类和派生类对象赋值兼容转换

2.1 类型转换存在临时对象的意义

int main()
{int i = 97;char ch = 'a';if (ch == i)cout << "等于" << endl;elsecout << "不等于" << endl;return 0;
}

在这段代码中,如果问chi是否相等,答案是肯定相等,但是chi却不是直接进行比较的。

chi在比较过程中,编译器会调用cmp指令,这个指令要求比较对象双方的类型是一致的,但是显然chi的类型不一样,所以ch会发生整型提升,然后产生一个int类型的临时对象,然后临时对象和i进行比较,这样的处理方式不会影响chi本身。

2.2 赋值兼容转换不会产生临时变量

  1. 派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。C++特殊规定,这个赋值过程稿中不会产生临时对象,而是将派生类对象中的基类对象切割出来进行赋值。

在这里插入图片描述

  1. 基类对象不能赋值给派生类对象。
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.基类对象不能赋值给派生类对象// error C2679: 二元“=”: 没有找到接受“Person”类型的右操作数的运算符(或没有可接受的转换)sobj = pobj;return 0;
}

三、继承中的作用域

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

在这里插入图片描述

  1. Student 类的 _num 和 Person 类的 _num 构成隐藏关系,由于就近原则直接访问只会访问到 Student 类的 _num,如果想要访问 Person 类的 _num 要加Person::指定类域.
  2. 两个类中的Print由于同名也构成隐藏关系,默认只会访问Student类的Print方法,访问Person类的Print方法同样需要指定类域。

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

一个类有 6 个默认成员函数,“ 默认 ” 的意思就是指我们不写,编译器会变我们自动生成一个(类的默认构造其实有三个,不过这里默认都是编译器自动生成的),这部分的内容主要是,默认成员成员函数的行为是怎样的,以及当基类没有默认成员函数时该如何显式调用。

事先说明,这里只涉及 6 个默认成员函数中的 4 个。

4.1 构造

  1. 与一般类对象相比,派生类对象多存储了基类部分的成员,由于派生类和基类是独立的两部分,所以派生类对象中的基类部分的成员应当调用基类的构造函数去初始化,而派生类对象中派生类部分的成员则调用派生类自己的构造函数去初始化
  2. 如果派生类没有实现构造函数,编译器会自动生成一个默认的构造函数,该函数对内置类型成员不做处理,对自定义类型成员会去调用它的默认构造函数,对基类部分成员会去调用基类的默认构造函数。
  3. 如果基类没有默认构造函数,那么需要通过初始化列表显式调用基类的构造函数,调用的方式是直接调用,如 Person(name)
  4. 由于基类比派生类先定义,所以初始化列表会先调用基类构造函数再初始化其余成员。
// 基类
class Person
{
public:Person(const char* name = "peter"): _name(name){cout << "Person()" << endl;}
protected:string _name; // 姓名
};// 派生类
class Student : public Person
{
public:Student(const char* name, int num): Person(name)	// 1. 父类成员调用父类构造,子类成员调用子类构造;// 2. 父类无默认构造直接显式调用父类构造;// 3. 父类比子类先定义,初始化列表顺序为先父后子, _num(num){cout << "Student()" << endl;}
protected:int _num; //学号
};int main()
{Student s1("jack", 18);return 0;
}

在这里插入图片描述

4.2 拷贝构造

  1. 与默认构造函数的行为类似,我们不实现派生类的拷贝构造函数,编译器会自动生成一个默认的拷贝构造函数,该函数对内置类型成员会进行字节序的值拷贝,对自定义类型成员会去调用它的拷贝构造,对基类部分成员会去调用基类的默认拷贝构造函数。
  2. 如果是一个深拷贝的类就需要自己去显式实现拷贝构造函数,对于派生类类成员用对象的派生类部分的成员完成拷贝,对于基类要用派生类对象中基类的那一部分来进行拷贝,函数传参的过程中通过赋值兼容转换将派生类对象中的基类部分切割出来。
  3. 在派生类调用基类的拷贝构造函数要通过初始化列表,调用方式是直接调用,如:Person(s)
// 基类
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): Person(name)	// 1. 父类成员调用父类构造,子类成员调用子类构造;// 2. 父类无默认构造直接显式调用父类构造;// 3. 父类比子类先定义,初始化列表顺序为先父后子, _num(num){cout << "Student()" << endl;}Student(const Student& s): Person(s)		// 1. 与构造类似,拷贝构造也是类名显式调用// 2. 一般的类不需要写,默认生成的拷贝构造对自定义类型完成值拷贝,对自定义类型去调用它的拷贝构造,这里的父类同样也是去调用父类的拷贝构造//	  如果是一个深拷贝的类就需要自己去写拷贝构造,自己写拷贝构造该怎么实现//	  对于子类成员用子类部分的成员拷贝//	  对于父类要用子类对象中父类的那一部分来进行拷贝,可以该怎么把子类对象中父类的那一部分切出来?//    赋值兼容转换!Student对象作为参数传给Person(),传参会进行切片,编译器把子类对象中父类的那一部分切出来形成一个新的Person对象形参, _num(s._num){cout << "Student(const Student& s)" << endl;}protected:int _num; //学号
};int main()
{Student s1("jack", 18);Student s2(s1);return 0;
}

在这里插入图片描述

4.3 赋值重载

  1. 我们不显式实现派生类中的赋值重载,编译器自动生成的赋值重载对内置类型进行字节序的值拷贝,对自定义类型调用它的赋值重载,对派生类中的基类部分会去调用基类中的赋值重载。
  2. 由于基类中的赋值重载和派生类中的赋值重载同名,两个函数构成隐藏关系,调用基类的赋值重载需要指定类域。
// 基类
class Person
{
public:Person(const char* name = "peter"): _name(name){cout << "Person()" << endl;}Person& operator=(const Person& p){cout << "Person operator=(const Person& p)" << endl;if (this != &p)_name = p._name;return *this;}
protected:string _name; // 姓名
};// 派生类
class Student : public Person
{
public:Student(const char* name, int num): Person(name)	// 1. 父类成员调用父类构造,子类成员调用子类构造;// 2. 父类无默认构造直接显式调用父类构造;// 3. 父类比子类先定义,初始化列表顺序为先父后子, _num(num){cout << "Student()" << endl;}Student& operator = (const Student& s){cout << "Student& operator= (const Student& s)" << endl;// 如果不是自己给自己赋值if (this != &s){// 父类的调用父类的来完成赋值Person::operator =(s);// 子类的用子类成员变量完成赋值_num = s._num;}// 最后返回自己的引用return *this;}
protected:int _num; //学号
};int main()
{Student s1("jack", 18);Student s3("rose", 17);s1 = s3;return 0;
}

在这里插入图片描述

4.4 析构

特殊规定:

  1. 派生类析构函数和基类析构函数构成隐藏关系,这是因为多态的原因,析构函数都会被特殊处理,函数名会被处理成destructor()
  2. 为了保证析构顺序是先派生类后基类,基类析构会在派生类析构后自动调用,这是因为先基类后派生类是有安全隐患,可能基类的资源已经被清理了但是由于某些原因,派生类的析构函数又去访问基类的资源,存在野指针的风险。
class Person
{
public:Person(const char* name = "peter"): _name(name){cout << "Person()" << endl;}~Person(){cout << "~Person()" << endl;}
protected:string _name; // 姓名
};class Student : public Person
{
public:Student(const char* name, int num): Person(name)	// 1. 父类成员调用父类构造,子类成员调用子类构造;// 2. 父类无默认构造直接显式调用父类构造;// 3. 父类比子类先定义,初始化列表顺序为先父后子, _num(num){cout << "Student()" << endl;}~Student(){cout << "~Student()" << endl;}
protected:int _num; //学号
};int main()
{Student s1("jack", 18);return 0;
}

在这里插入图片描述

五、继承与友元

继承指的是继承基类的成员,友元不是基类的成员,所以派生类无法继承基类的友元关系。

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)
{// Display是Person类的友元,能够访问Person类的_namecout << p._name << endl;// 友元关系不能继承,所以Display不是Student的友元,Display无法访问_stuNum// 如果Display也想访问Student的_stuNum,得在Student类内加上友元声明// error:cout << s._stuNum << endl;
}void main()
{Person p;Student s;Display(p, s);
}

六、继承与静态成员

  1. 静态成员存储在静态区,派生类继承的是访问权,而不是在派生类又生成一份。
  2. 在整个继承体系中,无论派生出多少个子类,都只有这一个static成员实例。
  3. 指定该继承体系中的任一类域都可以访问到static成员。
class Person
{
public:Person() { ++_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 << " 人数 :" << Person::_count << endl;// static成员只会存在一份cout << " Person::_count 地址 :" << &Person::_count << endl;cout << " Student::_count 地址 :" << &Student::_count << endl;return 0;
}

在这里插入图片描述

七、复杂的菱形继承和菱形虚拟继承

7.1 单继承与多继承

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

在这里插入图片描述

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

在这里插入图片描述

7.2 菱形继承与虚继承

菱形继承是多继承的一种特殊情况。
在这里插入图片描述
菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在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; // 主修课程
};
void Test()
{// 这样会有二义性无法明确知道访问的是哪一个Assistant a;a._name = "peter";	// error C2385: 对“_name”的访问不明确// 显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决a.Student::_name = "xxx";a.Teacher::_name = "yyy";
}

虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在 Student 类和 Teacher 类继承 Person 类时使用虚拟继承(添加virtual关键字),即可解决问题。需要注意的是,虚拟继承不要在其他地方去使用,换言之,需要注意virtual关键字在菱形继承的哪些类里添加。

class Person
{
public:string _name; // 姓名
};
// 直接基类是Person使用虚继承
class Student : virtual public Person
{
protected:int _num; //学号
};
// 直接基类是Person使用虚继承
class Teacher : virtual public Person
{
protected:int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:string _majorCourse; // 主修课程
};
int main()
{Assistant a;a._name = "peter";return 0;
}

7.3 虚继承的原理

语法上解决菱形继承只需要使用虚继承即可,但是底层上的解决方法是怎样的?

接下来就用一个简化的菱形继承继承体系并借助内存窗口观察对象成员的模型,来研究虚拟继承原理。
注意:以下探究过程在VS2019上进行,别的编译器使用的解决方案可能在细节上略有差异

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. 原本D类对象d中存在两份的成员_a,被抽出来放到的了对象组成的最下面,此时这个成员_a同时属于B类和C类且只存在一份,解决了数据冗余。
  2. B类和C类中原本存储_a的位置分别被替换成了一个指针,通过指针能够找到一张表,这两个指针被称为虚基表指针,这两张表被称之为虚基表,虚基表中存储的是偏移量,通过偏移量能够找到成员_a

在这里插入图片描述

现有下面两句代码,pb指针该如何找到_a呢?

B* pb = &d;
pb->_a++;

第一,pb指针是一个指向 B 类对象的指针,B* pb = &d;的赋值过程会发生兼容转换,将对象d中的B类部分的指针返回给pb指针,即pb指针的内容是0x00FAF8D0

第二,pb->_a这个操作将会去读取B类中_a存储的内容,由于虚继承的缘故,读取到的是一个虚基表指针,即0x00c07be8,编译器通过虚基表指针找到虚基表,通过虚基表可以直到,现在_a相对于pb指针(0x00FAF8D0)的偏移量是0x14

最后,编译器会在0x00FAF8D0 + 0x14 的地址处找到_a的位置,然后完成++操作。

上面的模型是对象d的模型,其实B类、C类的模型也发生了改变,B类、C类对象访问成员_a时也需要通过虚基表找偏移量,以下面这几句代码为例:

B bb;
B* pb = &bb;
pb->_a++;

在这里插入图片描述

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

八、继承的总结和反思

  1. 很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就可能存在菱形继承,有了菱形继承就有得涉及菱形虚拟继承,而它的底层实现就很复杂。因此,一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度及性能上都有问题。
  2. 多继承可以认为是C++的缺陷之一,很多后来的 OOP 语言都没有多继承,如 Java。
  3. 继承被称为 “ 白箱复用 ”,术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。
  4. 组合被称为 “ 黑箱复用 ”,因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。
    在这里插入图片描述
  5. 继承和组合的选择
    • public继承是一种 is-a 的关系。 比如说Person类和Student类,学生是一个人。
    • 组合是一种 has-a 的关系。假设B组合了A,每个B对象中都有一个A对象。
    • 实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合。
// 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.mzph.cn/news/695908.shtml

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

相关文章

C++如何避免float误差?

C如何避免float误差&#xff1f; 在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「c的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01; …

WP----Look 我看的见你,你却看不见我 tips:sql injection tips2: mysql 字符集

0x00 题目连接打开后&#xff0c;是空白的&#xff0c;源代码也是空白的 这种情况就抓包&#xff0c;看请求包和相应包里面是否有提示 相应包中存在很特别的响应头X-HT: verify 可能是参数&#xff0c;传递任意参数过去 0x01 传递5个长度的参数时&#xff0c;返回 说明参数的…

美国纽约时代广场纳斯达克大屏投放-大舍传媒

美国纽约时代广场纳斯达克大屏投放-大舍传媒 引言 对于大舍传媒来说&#xff0c;能够在美国纽约时代广场纳斯达克大屏投放广告是一个里程碑式的时刻。这不仅仅代表着大舍传媒在全球范围内的知名度与实力&#xff0c;也标志着该公司在国际市场上取得了巨大的进展。纽约时代广场…

基于docker安装HDFS

1.docker一键安装见 docker一键安装 2.拉取镜像 sudo docker pull kiwenlau/hadoop:1.03.下载启动脚本 git clone https://github.com/kiwenlau/hadoop-cluster-docker4.创建网桥 由于 Hadoop 的 master 节点需要与 slave 节点通信&#xff0c;需要在各个主机节点配置节点…

cuda加速:memory coalescing,Bank Conflicts

cuda加速&#xff1a;memory coalescing 1.memory coalescing2.Shared Memory Bank Conflicts参考文献 1.memory coalescing 参考【1】中给出的定义&#xff1a;一个warp中&#xff0c;thread 0到thread 31访问连续的内存空间&#xff0c;则这些线程的访问被合并为一次访问。 …

git使用过的命令记录

目录 git add .git commit --amendgit push -f origin HEAD:mastergit checkout .git stash想把某个pr的修改应用到本地git pull 将远程仓库的最新代码更新到本地git 撤销&#xff0c;放弃本地修改参考文档 git add . 将本地修改提交到暂存区 git commit --amend 如果本地有…

Cesium 问题:加载 gltf 格式的模型之后太小,如何让相机视角拉近

文章目录 问题分析问题 刚加载的模型太小,如何拉近视角放大 分析 在这里有两种方式进行拉近视角, 一种是点击复位进行视角拉近一种是刚加载就直接拉近视角// 模型三加载 this.damModel = new Cesium.Entity({name: "gltf模型",position:</

什么是智慧公厕?智慧公厕建设的好处

智慧公厕是一种融合物联网、互联网、通信技术、大数据、云计算、自动化控制等信息化技术的新型公共厕所&#xff0c;通过传感器数据获取和分析优化业务流程&#xff0c;为公共厕所的监测、管理、控制提供全方位支持&#xff0c;实现公共厕所的环境监测与调控、厕位占用监测与引…

9906在线式户外多通道光伏组件评测系统

01 9906在线式户外多通道光伏组件评测系统 产品综述&#xff1a; 在线式户外多通道光伏组件评测系统是一款支持实时多通道光伏组件测试的评测系统&#xff0c;提供真6路电子负载&#xff0c;提供多至6通道的实时测试能力&#xff0c;并可根据用户需求订制通道数量。评测系统…

java使用poi简单操作excel

文章目录 引言插入行/列合并单元格以及设置居中换行 引言 编程开发中&#xff0c;我们可能会接到某些需求&#xff0c;例如导出某某某列表数据&#xff0c;或者做一份报表&#xff0c;这时候就需要我们的poi出场了&#xff0c;至于一些规则数据的导出&#xff0c;直接使用easy…

vue项目设置的端口号运行后会自动加一问题解决

vue项目设置的端口号运行后会自动加一问题解决 主要原因是之前运行项目后没有完全的关闭服务&#xff0c;导致再次运行项目端口号被占用&#xff0c;自动加一&#xff01; 问题解决 打开任务管理器&#xff0c;在进程中找到node相关进程&#xff0c;右键结束任务

Typescript初体验

Typescript Typescript 官网地址: https://www.typescriptlang.org/zh/ 使用 nvm 来管理 node 版本: https://github.com/nvm-sh/nvm 装 Typescript: npm install -g typescript使用 tsc 全局命令&#xff1a; // 查看 tsc 版本 tsc -v // 编译 ts 文件 tsc fileName.ts1.…

利用数字人技术“活”化吉祥物,打造“科技+”数融盛会

在第十四届全国冬季运动会上&#xff0c;吉祥物“蒙古彩娃”安达和塞努不再同于往常静态的吉祥物形象&#xff0c;而是升级为生动活泼、能够与观众实时互动的数字人形象&#xff0c;活跃于赛事宣传、场馆介绍等多个场景&#xff0c;为本届冰雪盛会注入数字活力&#xff0c;提升…

RoboRAVE世界机器人大会亚洲分会toio™专项挑战顺利收官,toio™实现“双首次”突破

2024年2月21日&#xff0c;北京——2024 RoboRAVE国际教育机器人大会&#xff08;下称RoboRAVE&#xff09;亚洲分会toio™专项挑战在北京科技大学成功举办。现场云集来自华北、华南、华东等全国各地40组杰出学生队伍&#xff0c;展开热烈的机器人编程限时挑战。本届toio™专项…

并发编程入门指南

文章目录 并发编程进程和线程的区别并发和并行的区别创建线程的方式线程之间的状态&#xff0c;状态之间的转换新建三个线程&#xff0c;如何保证按顺序执行wait方法和sleep的区别如何停止一个正在运行的线程synchronized关键字底层原理Monitor属于重量级锁&#xff0c;了解过锁…

day04_流程语句_if_while_for

今日内容 1.if 2.switch 3.while,do-while,for 零、复习 1算术运算符中除法特性 整数相除不保留小数 10/33 2什么作用,i和i什么区别 让数据自增1i,是在后先使用后自增i,是在前先自增后使用 3&& 和 & 有相同点和不同点 相同点: 两边判断式子,一错就错全对才对不同点…

一文了解web开发基础知识【HTML、CSS、JavaScript】

文章目录 1 前言1.1 HTML1.2 CSS1.3 JavaScript1.4 理解HTML、CSS、JavaScript之间的关系 2 HTML2.1 什么是网页2.2 什么是HTML2.2.1 文本内容2.2.1.1 标题 2.2.2 图像2.2.3 链接2.2.4 列表2.2.5 表格2.2.6 HTML代码注释 2.3 第一个HTML文件2.3.1 示例2.3.2 补充2.3.2.1 < l…

苹果iPad通过Code APP应用实现SSH连接服务器远程进行开发

文章目录 1. 在iPad下载Code APP2.安装cpolar内网穿透2.1 cpolar 安装2.2 创建TCP隧道 3. iPad远程vscode4. 配置固定TCP端口地址4.1 保留固定TCP地址4.2 配置固定的TCP端口地址4.3 使用固定TCP地址远程vscode 本文主要介绍开源iPad应用IDE Code App 如何下载安装&#xff0c;并…

基于springboot + vue实现的前后端分离-酒店管理系统

项目介绍 基于springboot vue实现的酒店管理系统一共有酒店管理员和用户这两种角色。 管理员功能 登录&#xff1a;管理员可以通过登录功能进入系统&#xff0c;确保只有授权人员可以访问系统。用户管理&#xff1a;管理员可以添加、编辑和删除酒店的用户&#xff0c;包括前…

运维SRE-18 自动化批量管理-ansible4

12.2handles handles触发器(条件)&#xff0c;满足条件后再做什么事情应用场景&#xff1a;想表示&#xff1a;配置文件变化&#xff0c;再重启服务 配置handlers之前&#xff0c;每次运行剧本都会重启nfs&#xff0c;无论配置文件是否变化。 [rootm01 /server/ans/playbook]…