C++基础(十八):继承(重点)

        各位看官,大家好!今天我们将探讨C++中的三大特性之一:继承。继承是一种面向对象编程的重要概念,它允许我们通过创建新的类,从而复用和扩展现有类的功能。通过继承,我们不仅能够提高代码的可重用性和可维护性,还能更好地体现现实世界中事物的层次结构。希望大家通过今天的学习,能够深入理解继承的核心原理,并能在实际编程中灵活应用这一强大的工具。

目录

一、继承的概念及定义

1.1继承的概念

1.2 继承定义

1.2.1定义格式

1.2.2继承关系和访问限定符

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

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

2.1  子类可以赋值给父类

2.2 父类不可以赋值给子类 

2.3  父类赋值给子类的特殊情况

三、继承中的作用域

3.1 隐藏的概念

3.2 如何解决呢?

3.3  注意事项

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

4.1 构造函数

 4.2 拷贝构造函数

4.3 赋值重载 

4.4 析构函数

4.5 总结

4.6 练习

五、继承与友元

六、继承与静态成员

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

7.1 继承的分类及概念

7.2 菱形继承存在的问题

7.3 虚拟继承

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

八、继承的总结和反思

8.1理解

8.2. 继承和组合

九、笔试面试题

9.1 C++的缺陷是什么

9.2  什么是菱形继承?菱形继承的问题是什么?


一、继承的概念及定义

1.1继承的概念

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

class Person
{public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}protected:string _name = "peter";   // 姓名int _age = 18;           // 年龄
};class Student : public Person    //学生类继承自Person类
{protected:int _stuid; // 学号  (新增的属于自己类成员变量)
};class Teacher : public Person   //老师类继承自Person类
{protected:int _jobid; // 工号 (新增的属于自己类成员变量)
};int main()
{Student s;Teacher t;s.Print();t.Print();return 0;
}

继承带来的作用:

       子类/派生类会具有父类/基类的成员变量和成员函数,当然也可以有属于自己类的成员变量和函数。

1.2 继承定义

1.2.1定义格式

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

1.2.2继承关系和访问限定符

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

总结:

  1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私 有成员还是被继承到了派生类对象中(内存空间会有这个成员变量),但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
  2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在 派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。protected访问限定符和private访问限定符在当前类中没有区别,他们是一样的,类外都不能访问,区别在于继承的派生类,private成员无论什么继承方式,在派生类中都不能访问,但是protected就不一样了!
  3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。
  4. 使用关键字class声明的类(指的是派生类)时默认的继承方式(派生类不写继承方式)是private,使用struct声明的类(指的是派生类)默认的继承方式(派生类不写继承方式)是public,不过最好显示的写出继承方式。
  5. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡 使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。
// 实例演示三种继承关系下基类成员的各类型成员访问关系的变化  
class Person
{public :void Print (){cout<<_name <<endl;}protected :string _name ; // 姓名private :int _age ; // 年龄
};//class Student : protected Person
//class Student : private Person
class Student : public Person
{protected :int _stunum ; // 学号
};

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

2.1  子类可以赋值给父类

 1、派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片 或者切割。寓意把派生类中父类那部分切来赋值过去。

2.2 父类不可以赋值给子类 

2、基类对象不能赋值给派生类对象。

2.3  父类赋值给子类的特殊情况

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

class Person
{protected :string _name; // 姓名string _sex;  // 性别int    _age; // 年龄
};class Student : public Person
{public :int _No ; // 学号
};void Test ()
{Person  p;Student s ;子类和父类之间的赋值兼容规则:// 1.子类对象可以赋值给父类的对象/的指针/的引用,叫做切片Person p = s ;Person* ptr = &s;Person& rp = s;//2.父类对象不能赋值给子类对象(父给子是不可以的!反过来是不可以的!)s = p;       坚决不可以!// 3.基类的指针可以通过强制类型转换赋值给派生类的指针ptr = &sStudent* ps1 = (Student*)ptr;//这种情况转换时可以的,因为这个父类的指针有时是指向子类对象的ps1->_No = 10;ptr = &p;Student* ps2 = (Student*)ptr; //这种情况转换时虽然可以,但是会存在越界访问的问
题ps2->_No = 10;
}

三、继承中的作用域

3.1 隐藏的概念

       在继承体系中基类和派生类都有独立的作用域。当父类和子类同时有同名成员变量或者成员函数时,子类就会隐藏父类的同名成员变量或者成员函数。子类和父类中有同名成员,子类成员将屏蔽对父类同名成员(成员变量或者成员函数)的直接访问,这种情况叫隐藏, 也叫重定义

3.2 如何解决呢?

    (如何解决呢?在子类成员函数中,可以使用 基类::基类成员 显示访问)

3.3  注意事项

注意在实际中在继承体系里面最好不要定义同名的成员变量或者同名的成员函数。

// Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆
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; // 学号
};void Test()
{Student s1;s1.Print();
};
B中的fun和A中的fun不是构成重载,因为不是在同一作用域!!!
B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。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.A::fun();   //必须要显示的指定基类,才可以调用父类的这个同名的隐藏函数
};

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

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

4.1 构造函数

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 ; //学号
};int main()
{Student s;return 0;
}

基本原则:

        派生类的初始化和析构会分别自动调用基类的构造函数初始化基类的那一部分成员和自动调用析构函数,然后还会调用自己的构造函数和析构函数。也就是说他把父类和基类分的很清楚!

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): _name(name)   这里编译器不允许这样初始化!不写这个,他会编译通过,因为他会自动调用父类的构造函数初始化, _num(num){cout<<"Student()" <<endl;}protected :int _num ; //学号
};int main()
{Student s("peter",1);return 0;
}

       派生类继承父类,对于父类那一部分,调用父类的构造函数进行初始化! 不可以在初始化列表中以初始化自己的成员变量的方式进行初始化!如果我们不对父类的成员变量进行初始化,他会自动的调用父类的构造函数进行初始化,如果想要在派生类中显示的初始化这个父类的成员变量,就必须以父类的构造函数的方式显示初始化,如下所示:

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;}protected :int _num ; //学号
};int main()
{Student s("peter",1);return 0;
}

  总结1: 

        派生类的构造函数包含两个部分:第一部分是父类继承的,不能自己去初始化父类继承的那一部分,必须要调用父类的构造函数进行初始化(或者你不调,他会去调用父类默认的那个构造函数:编译器默认生成的构造函数、全缺省的构造函数、无参的构造函数,进行初始化)。第二部分是派生类自己的成员变量和之前普通的类没有什么区别。

 4.2 拷贝构造函数

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;}protected :int _num ; //学号
};int main()
{Student s1("jack", 18);Student s2(s1);  //拷贝构造}

      派生类不实现自己的拷贝构造函数,编译器会自动生成一个拷贝构造函数,进行拷贝,对于父类的那一部分,他会自动的调用父类的拷贝构造函数。

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)//: _name(s._name)   这里是不可以的!: Person(s)      这样做!把子类对象给父类的引用,切片!!!, _num(s ._num){cout<<"Student(const Student& s)" <<endl ;}protected :int _num ; //学号
};int main()
{Student s1("jack", 18);Student s2(s1);  //拷贝构造}

  总结2: 

        派生类的拷贝构造函数也同样包含两个部分:第一部分是父类继承的,不能自己去拷贝父类继承的那一部分,必须要调用父类的拷贝构造函数进行拷贝,第二部分是派生类自己的成员变量和之前普通的类没有什么区别,这一部分也会调用自己的拷贝构造函数进行拷贝。

4.3 赋值重载 

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 ){cout<<"Student& operator= (const Student& s)"<< endl;if (this != &s){operator =(s);  这样显示调用赋值重载(this->operator=(s);),这里也有切片_num = s ._num;cout<<"Student& operator= (const Student& s)"<< endl;}return *this ;} protected :int _num ; //学号
};int main()
{Student s1("jack", 18);Student s2(s1);  //拷贝构造Student s3("rose", 20);s1 = s3;}

为什么会发生栈溢出??

        因为派生类调用operator=与基类的operator=构成隐藏了(同名函数),子类和父类中有同名成员函数,那么子类成员函数将屏蔽对父类同名成员函数的直接访问!!!也就是说,没办法调用基类的operator=。解决办法:指定基类!

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 ;} protected :int _num ; //学号
};int main()
{Student s1("jack", 18);Student s2(s1);  //拷贝构造Student s3("rose", 20);s1 = s3;}

4.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: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;}~Student()       //子类的析构函数和父类的析构函数构成隐藏!!!因为他们的名字会被编译器统一处理成: destructor(跟多态相关){//~Person();      //不能这样直接调用基类的析构函数,因为它们构成隐藏,基类无法访问父类的析构函数,解决办法:指定基类Person::~Person();cout << "~Student()" << endl;}protected:int _num; //学号
};int main()
{Student s1("jack", 18);return 0;}

第一个Person析构应该去掉!基类的析构函数不需要我们显示的去调用,他会在派生类析构函数调用后,自动的去调用基类的析构函数!!修改如下:

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;}~Student()       //子类的析构函数和父类的析构函数构成隐藏!!!因为他们的名字会被编译器统一处理成: destructor(跟多态相关){//Person::~Person();cout << "~Student()" << endl;}protected:int _num; //学号
};int main()
{Student s1("jack", 18);return 0;}

4.5 总结

4.6 练习

 请设计一个类,不能被继承

        只需要将父类的构造函数的访问限定符设置成私有的,这样子类无论以什么方式继承,父类的构造函数在子类中都不可见,那么我们在创建子类对象时,它必须首先去调用父类的构造函数进行初始化,但是,发现父类的构造函数此时不可见,那他就不能调用了,那么子类对象就创建失败了!也就是说,这个父类不能被继承。

class A
{
private:A(){}
};class B:public A
{B(){}
};

五、继承与友元

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


class Person
{public:friend void Display(const Person& p, const Student& s);protected:string _name; // 姓名
};void Display(const Person& p, const Student& s)
{cout << p._name << endl;cout << s._stuNum << endl;
}class Student : public Person
{protected:int _stuNum; // 学号
};int main()
{Person p;Student s;Display(p, s);return 0;
}

六、继承与静态成员

      基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子 类,都只有一个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 ; // 研究科目
};void main()
{Student s1 ;Student s2 ;Student s3 ;Graduate s4 ;cout <<" 人数 :"<< Person ::_count << endl;Student ::_count = 0;cout <<" 人数 :"<< Person ::_count << endl;
}

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

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

7.3 虚拟继承

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

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

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

正常的菱形继承:

class A
{public:int _a;
};class B : public A
{public:int _b;
};class C : public A
{public:int _c;
};class D : public B, public C
{public:int _d;
};int main()
{D d;cout<<sizeof(d)<<endl;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;return 0;
}

 

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

虚拟菱形继承:

class A
{public:int _a;
};class B :  virtual public A
{public:int _b;
};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。

有童鞋会有疑问为什么D中B和C部分要去找属于自己的A?那么大家看看当下面的赋值发生时,d是
不是要去找出B/C成员中的A才能赋值过去?D d;
B b = d;
C c = d;

下面是上面的Person关系菱形虚拟继承的原理解释:

八、继承的总结和反思

8.1理解

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

8.2. 继承和组合

九、笔试面试题

9.1 C++的缺陷是什么

       多继承就是C++的一个问题,多继承中的菱形继承存在数据冗余和二义性的问题,解决它的方法是虚拟继承,它的底层结构的对象模型非常复杂,且有一定的效率损失。

9.2  什么是菱形继承?菱形继承的问题是什么?

         菱形继承(diamond inheritance)是C++中多重继承的一种特殊情况,其继承结构形成一个菱形,因此得名。这种继承方式通常涉及一个基类、两个从这个基类继承的中间类以及一个从这两个中间类继承的派生类。

菱形继承的问题

  1. 重复继承(重复基类): 当D继承自BC时,由于BC都继承自A,导致D将包含两份A的成员,这会造成数据冗余和不一致性问题。

  2. 二义性(Ambiguity): 如果在类D中调用基类A的成员,例如函数或变量,由于D包含两份A的成员,编译器无法确定应该调用哪一份,会导致二义性错误。

至此,这一讲内容介绍完毕,内容简单,星光不问赶路人,加油吧,感谢阅读,如果对此专栏感兴趣,点赞加关注!

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

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

相关文章

9.5 栅格图层符号化多波段彩色渲染

文章目录 前言多波段彩色渲染QGis设置为多波段彩色二次开发代码实现多波段彩色 总结 前言 介绍栅格图层数据渲染之多波段彩色渲染说明&#xff1a;文章中的示例代码均来自开源项目qgis_cpp_api_apps 多波段彩色渲染 以“3420C_2010_327_RGB_LATLNG.tif”数据为例&#xff0c…

常见条件控制算法流程图

内容讲解&#xff1a;流程控制[if…else…(if…elif…else…),while,for] 常见条件控制算法流程图高清图

新手教学系列——高效管理MongoDB数据:批量插入与更新的实战技巧

前言 在日常开发中,MongoDB作为一种灵活高效的NoSQL数据库,深受开发者喜爱。然而,如何高效地进行数据的批量插入和更新,却常常让人头疼。今天,我们将一起探讨如何使用MongoDB的bulk_write方法,简化我们的数据管理流程,让代码更加简洁高效。 常规做法:find、insertone…

【Linux】常见指令收官权限理解

tar指令 上一篇博客已经介绍了zip/unzip指令&#xff0c;接下来我们来看一下另一个关于压缩和解压的指令&#xff1a;tar指令tar指令&#xff1a;打包/解包&#xff0c;不打开它&#xff0c;直接看内容 关于tar的指令有太多了&#xff1a; tar [-cxtzjvf] 文件与目录 ...…

C++运行时类型识别

目录 C运行时类型识别A.What&#xff08;什么是运行时类型识别RTTI&#xff09;B.Why&#xff08;为什么需要RTTI&#xff09;C.dynamic_cast运算符Why&#xff08;dynamic_cast运算符的作用&#xff09;How&#xff08;如何使用dynamic_cast运算符&#xff09; D.typeid运算符…

各地户外分散视频监控点位,如何实现远程集中实时监看?

公司业务涉及视频监控项目承包搭建&#xff0c;此前某个项目需求是为某林业公司提供视频监控解决方案&#xff0c;需要实现各地视频摄像头的集中实时监看&#xff0c;以防止国家储备林的盗砍、盗伐行为。 公司原计划采用运营商专线连接各个视频监控点位&#xff0c;实现远程视…

跟着李沐学AI:线性回归

引入 买房出价需要对房价进行预测。 假设1&#xff1a;影响房价的关键因素是卧室个数、卫生间个数和居住面积&#xff0c;记为x1、x2、x3。 假设2&#xff1a;成交价是关键因素的加权和 。权重和偏差的实际值在后面决定。 拓展至一般线性模型&#xff1a; 给定n维输入&…

MySQL 9.0 正式发行Innovation创新版已支持向量

从 MySQL 8.1 开始&#xff0c;官方启用了新的版本模型&#xff1a;MySQL 创新版 (Innovation) 和长期支持版 (LTS)。 根据介绍&#xff0c;两者的质量都已达到可用于生产环境级别。区别在于&#xff1a; 如果希望尝试最新的功能和改进&#xff0c;并喜欢与最新技术保持同步&am…

怎样在 C 语言中实现栈?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01; &#x1f4d9;C 语言百万年薪修炼课程 通俗易懂&#xff0c;深入浅出&#xff0c;匠心打磨&#xff0c;死磕细节&#xff0c;6年迭代&#xff0c;看过的人都说好。 文章目…

动手学深度学习(Pytorch版)代码实践 -循环神经网络-55循环神经网络的从零开始实现和简洁实现

55循环神经网络的实现 1.从零开始实现 import math import torch from torch import nn from torch.nn import functional as F from d2l import torch as d2l import matplotlib.pyplot as plt import liliPytorch as lp# 读取H.G.Wells的时光机器数据集 batch_size, num_ste…

ElasticSearch第一天

学习目标&#xff1a; 能够理解ElasticSearch的作用能够安装ElasticSearch服务能够理解ElasticSearch的相关概念能够使用Postman发送Restful请求操作ElasticSearch能够理解分词器的作用能够使用ElasticSearch集成IK分词器能够完成es集群搭建 第一章 ElasticSearch简介 1.1 什么…

只会vue的前端开发工程师是不是不能活了?最近被一个flutter叼了

**Vue与Flutter&#xff1a;前端开发的新篇章** 在前端开发的世界里&#xff0c;Vue.js和Flutter无疑是两颗璀璨的明星。Vue以其轻量级、易上手的特点吸引了大量前端开发者的青睐&#xff0c;而Flutter则以其跨平台、高性能的优势迅速崛起。那么&#xff0c;对于只会Vue的前端…

【深度学习基础】环境搭建 linux系统下安装pytorch

目录 一、anaconda 安装二、创建pytorch1. 创建pytorch环境&#xff1a;2. 激活环境3. 下载安装pytorch包4. 检查是否安装成功 一、anaconda 安装 具体的安装说明可以参考我的另外一篇文章【环境搭建】Linux报错bash: conda: command not found… 二、创建pytorch 1. 创建py…

OceanBase:引领下一代分布式数据库技术的前沿

OceanBase的基本概念 定义和特点 OceanBase是一款由蚂蚁金服开发的分布式关系数据库系统&#xff0c;旨在提供高性能、高可用性和强一致性的数据库服务。它结合了关系数据库和分布式系统的优势&#xff0c;适用于大规模数据处理和高并发业务场景。其核心特点包括&#xff1a; …

python调用阿里云汇率接口

整体请求流程 介绍&#xff1a; 本次解析通过阿里云云市场的云服务来实现程序中对货币汇率实时监控&#xff0c;首先需要准备选择一家可以提供汇率查询的商品。 https://market.aliyun.com/apimarket/detail/cmapi00065831#skuyuncode5983100001 步骤1: 选择商品 如图点击…

debian 12 Install

debian 前言 Debian是一个基于Linux内核的自由和开放源代码操作系统&#xff0c;由全球志愿者组成的Debian项目维护和开发。该项目始于1993年&#xff0c;由Ian Murdock发起&#xff0c;旨在创建一个完整的、基于Linux的自由软件操作系统。 debian download debian 百度网盘…

分布式应用系统设计:即时消息系统

即时消息(IM)系统&#xff0c;涉及&#xff1a;站内消息系统 组件如下&#xff1b; 客户端&#xff1a; WEB页面&#xff0c;IM桌面客户端。通过WebSocket 跟ChatService后端服务连接 Chat Service&#xff1a; 提供WebSocket接口&#xff0c;并保持跟“客户端”状态的维护。…

会声会影分割音频怎么不能用 会声会影分割音频方法 会声会影视频制作教程 会声会影下载免费中文版2023

将素材中的音频分割出来&#xff0c;对声音部分进行单独编辑&#xff0c;是剪辑过程中的常用操作。会声会影视频剪辑软件在分割音频后&#xff0c;还可以对声音素材进行混音编辑、音频调节、添加音频滤镜等操作。有关会声会影分割音频怎么不能用&#xff0c;会声会影分割音频方…

如何快速制作您的数据可视化大屏?

数据大屏可视化主要就是借助图形&#xff0c;利用生动、直观的形式展示出数据信息的具体数值&#xff0c;使得使用者短时间内更加直观的接受到大量信息。数据大屏以直观、高度视觉冲击力的方式向受众揭示数据背后隐藏的规律&#xff0c;传达数据价值。其以图形化的形式呈现数据…

全国产T3+FPGA的SPI与I2C通信方案分享

近年来&#xff0c;随着中国新基建、中国制造2025规划的持续推进&#xff0c;单ARM处理器越来越难胜任工业现场的功能要求&#xff0c;特别是如今能源电力、工业控制、智慧医疗等行业&#xff0c;往往更需要ARM FPGA架构的处理器平台来实现例如多路/高速AD采集、多路网口、多路…