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.pswp.cn/web/44340.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/44340.shtml
英文地址,請注明出處:http://en.pswp.cn/web/44340.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

算法刷題筆記 KMP字符串(C++實現,并給出了求next數組的獨家簡單理解方式)

文章目錄 題目描述基本思路實現代碼 題目描述 給定一個字符串S&#xff0c;以及一個模式串P&#xff0c;所有字符串中只包含大小寫英文字母以及阿拉伯數字。模式串P在字符串S中多次作為子串出現。求出模式串P在字符串S中所有出現的位置的起始下標。 輸入格式 第一行輸入整數…

docker拉取鏡像,報錯error pulling image configuration: download failed after attempts=6: dial tcp 157.240.1

error pulling image configuration: download failed after attempts6: dial tcp 157.240.10.32:443: i/o timeout docker compose pull docker pull langgenius/dify-web:0.6.13 重啟docker sudo systemctl restart dockerhttps://stackoverflow.com/questions/72353203/do…

9.5 柵格圖層符號化多波段彩色渲染

文章目錄 前言多波段彩色渲染QGis設置為多波段彩色二次開發代碼實現多波段彩色 總結 前言 介紹柵格圖層數據渲染之多波段彩色渲染說明&#xff1a;文章中的示例代碼均來自開源項目qgis_cpp_api_apps 多波段彩色渲染 以“3420C_2010_327_RGB_LATLNG.tif”數據為例&#xff0c…

代碼隨想錄打卡第二十一天

代碼隨想錄–二叉樹部分 day 21 二叉樹第八天 文章目錄 代碼隨想錄--二叉樹部分一、力扣669--修建二叉搜索樹二、力扣108--將有序數組轉換為二叉搜索樹三、力扣538--把二叉搜索樹轉換為累加樹 一、力扣669–修建二叉搜索樹 代碼隨想錄題目鏈接&#xff1a;代碼隨想錄 給你二叉…

常見條件控制算法流程圖

內容講解&#xff1a;流程控制[if…else…(if…elif…else…),while,for] 常見條件控制算法流程圖高清圖

新手教學系列——高效管理MongoDB數據:批量插入與更新的實戰技巧

前言 在日常開發中,MongoDB作為一種靈活高效的NoSQL數據庫,深受開發者喜愛。然而,如何高效地進行數據的批量插入和更新,卻常常讓人頭疼。今天,我們將一起探討如何使用MongoDB的bulk_write方法,簡化我們的數據管理流程,讓代碼更加簡潔高效。 常規做法:find、insertone…

Unity 之 抖音小游戲集成排行榜功能詳解

Unity 之 抖音小游戲集成排行榜功能詳解 一,前言1.1 為游戲設計利于傳播的元素?2.2 多人競技、社交傳播?二,集成說明2.1 功能介紹2.2 完整代碼2.3 效果展示三,發現的問題和迭代計劃一,前言 對于 Unity 開發者而言,在開發抖音小游戲時集成排行榜功能是提升游戲社交性和玩…

Java實戰中處理高并發的策略

引言 隨著互聯網的快速發展&#xff0c;高并發成為了許多應用必須面對的挑戰。Java作為一門廣泛應用于企業級開發的語言&#xff0c;提供了豐富的工具和技術來應對高并發問題。本文將詳細探討Java中處理高并發的幾種常見策略和技術。 1. 并發編程基礎 1.1 線程與線程池 Jav…

【TVM 教程】使用 TVM 部署框架預量化模型

本文介紹如何將深度學習框架量化的模型加載到 TVM。預量化模型的導入是 TVM 中支持的量化之一。有關 TVM 中量化的更多信息&#xff0c;參閱 此處。 這里演示了如何加載和運行由 PyTorch、MXNet 和 TFLite 量化的模型。加載后&#xff0c;可以在任何 TVM 支持的硬件上運行編譯…

【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運算符…

【Scrapy】 Scrapy 爬蟲框架

準我快樂地重飾演某段美麗故事主人 飾演你舊年共尋夢的戀人 再去做沒流著情淚的伊人 假裝再有從前演過的戲份 重飾演某段美麗故事主人 飾演你舊年共尋夢的戀人 你縱是未明白仍夜深一人 穿起你那無言毛衣當跟你接近 &#x1f3b5; 陳慧嫻《傻女》 Scrapy 是…

各地戶外分散視頻監控點位,如何實現遠程集中實時監看?

公司業務涉及視頻監控項目承包搭建&#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…

開發個人Ollama-Chat--7 服務部署

開發個人Ollama-Chat–7 服務部署 服務部署 go-ChatGPT項目涉及的中間件服務較多&#xff0c;以下部署文件目錄&#xff1a; |-- chat-api | |-- etc | | -- config.yaml | -- logs |-- chat-rpc | |-- etc | | -- config.yaml | -- logs |-- docker-compos…

ElasticSearch第一天

學習目標&#xff1a; 能夠理解ElasticSearch的作用能夠安裝ElasticSearch服務能夠理解ElasticSearch的相關概念能夠使用Postman發送Restful請求操作ElasticSearch能夠理解分詞器的作用能夠使用ElasticSearch集成IK分詞器能夠完成es集群搭建 第一章 ElasticSearch簡介 1.1 什么…

windows 中的 Nsight Systems 通過ssh 鏈接分析 Linux 中的cuda程序性能

1&#xff0c;Linux 環境 安裝 ssh-server $ sudo apt install openssh-server 安裝較新版本的 cuda sdk 下載cuda-samples github repo 編輯修改 ssh 配置&#xff1a; $ sudo vim /etc/ssh/sshd_config 刪除相關注釋&#xff0c;修改后如下&#xff1a; Port 22 Addres…