C++封裝、繼承、多態(虛函數)

目錄

1、封裝

2、繼承

繼承方式:

(1)公有繼承;public

(2)保護繼承;protected

(3)私有繼承;private

菱形繼承:?

同名隱藏?

含義:

產生原因:

?同名覆蓋?(函數重寫)

定義

作用

?3、多態

(1)多態的分類

(2)虛表:

(3)代碼示例?

指針:?

引用實現:

(1)派生類對象可以給基類,但基類不能給派生類。?

(2)強制類型轉換后,查的仍然是Base的虛表:

?(3)定義obj類型的對象,訪問的仍是Obj的虛表,

?(4)繼承關系中,動態創建派生類對象,但是是拿基類對象指向的,Object * op = new Base();在delete *op時,調用派生類的析構函數,解決辦法是將基類的析構函數設為虛函數,之后,就可以先調用~Base();再調基類的析構是為什么?

原理分析


1、封裝

封裝是面向對象編程(OOP)的四大基本特性之一(另外三個是繼承、多態和抽象),它是一種將數據(屬性)和操作這些數據的方法(行為)捆綁在一起,并對外部隱藏對象的內部實現細節的機制。

class?類名:繼承方式 基類名1,繼承方式 基類名2,。。。繼承方式 基類名n

{

???派生類成員定義

};

2、繼承

單繼承:一個類只有一個直接基類。

多繼承:一個類擁有多個直接基類。

繼承方式:

(1)公有繼承;public

  • 基類的私有成員在派生類中不能直接訪問。
  • 基類的保護成員只能在派生類內部訪問,不能在派生類外部訪問, 在派生類中,繼承而來的基類保護成員依然是protected。
  • 基類的公有成員在派生類內部和外部都可以被訪問得到,在派生類中,繼承而來的基類的公有成員依然是public。

(2)保護繼承;protected

  • 基類的私有成員在派生類中不能直接訪問。
  • 基類的保護成員只能在派生類內部訪問,不能在派生類外部訪問, 在派生類中,繼承而來的基類保護成員依然是protected。
  • 基類的公有成員在派生類內部可以被訪問得到,在派生類中,繼承而來的基類的公有成員變成了是protected。

(3)私有繼承;private

  • 基類的私有成員在派生類中不能直接訪問。
  • 基類的保護成員只能在派生類內部訪問,不能在派生類外部訪問, 在派生類中,繼承而來的基類保護成員是private。
  • 基類的公有成員在派生類內部可以被訪問得到,在派生類中,繼承而來的基類的公有成員變成了private。

注:基類的私有成員在派生類中時存在的,但是不能在派生類中直接訪問,即無論通過何種方式繼承,都無法在派生類內部直接訪問繼承自基類的私有成員。只能通過基類中的公共函數,來訪問基類的私有成員。絕大多數情況下的繼承是公有繼承。

菱形繼承:?

C在繼承了B1類和B2類之后對于B1和B2中同樣來自于A類的數據就會造成訪問二義性問題。?會造成數據冗余。來自A的數據有兩份。

解決辦法:使用虛繼承

派生類訪問間接基類的數據時,實際上訪問的是該類對象中的虛基表指針,通過虛基表指針訪問到了虛基表,而虛基表中存儲的內容是當前虛基表指針位置到間接基類成員的地址偏移量。那么這樣子就能夠在使用派生類訪問間接基類成員時,通過偏移量直接找到繼承而來的間接基類的成員。所以在內存中只用保留一份間接基類的成員就行 。

同名隱藏?

含義:

同名隱藏指在繼承關系里,當派生類定義了和基類中同名的成員(包含成員變量和成員函數)時,基類的同名成員會被派生類的成員隱藏。這意味著在派生類的作用域內,若直接使用該成員名,默認訪問的是派生類的成員,基類的同名成員就好像 “被隱藏” 了,若要訪問基類的同名成員,需要使用作用域解析運算符(::

產生原因:

這種機制源于 C++ 等語言在處理繼承時的名稱查找規則。當在派生類中使用一個名稱時,編譯器會先在派生類的作用域內查找該名稱,若找到就使用該名稱對應的成員,不再去基類的作用域中查找;若在派生類的作用域內沒找到,才會去基類的作用域中查找。

?同名覆蓋?(函數重寫)

在面向對象編程中,同名覆蓋(也常被稱為函數重寫,Override)是一種重要的多態性機制,主要發生在具有繼承關系的類之間。以下是關于它的詳細介紹:

定義

當派生類中定義了一個與基類中虛函數具有相同簽名(函數名、參數列表、返回值類型)的函數時,就發生了同名覆蓋。此時,派生類的對象在調用該函數時,會執行派生類中重寫的版本,而不是基類中的版本。

作用

同名覆蓋是實現多態性的關鍵手段之一。通過它,我們可以在不修改基類代碼的情況下,在派生類中根據具體需求對基類的虛函數進行重新定義,從而實現不同的行為。這樣,當使用基類指針或引用指向不同的派生類對象時,調用相同的函數名可以產生不同的效果,提高了代碼的可擴展性和可維護性。

?3、多態

(1)多態的分類

?????編譯時多態,在程序編譯時確定同名操作和具體的操作對象。(早期綁定)

? ? ? ? ? ? ? ?強制多態—強制類型轉換

? ? ? ? ? ? ? ?重載多態—函數重載和運算符重載

? ? ? ? ? ? ? ?參數化多態—類模板及函數模板

?????運行時多態,在程序運行時才會確定同名操作和具體的操作對象。通過類繼承關系和虛函數來實現。

? ? ? ? ? ? ? ? ? 包含多態—虛函數重寫

虛函數的重寫:三同:函數名、返回類型、參數列表

(2)虛表:

在 C++ 里,只要類包含虛函數,編譯器就會為該類創建一個虛表(Virtual Table,簡稱 VTable)。虛表本質上是一個存儲類的虛函數地址的指針數組,這個數組的首元素上存儲RTTI(運行時類型識別信息的指針),從數組下標0開始依次存儲虛函數地址。最后面放了一個nullptr。類的每個對象中都有一個指向該類虛表的指針(虛表指針,vptr)。

指針數組是指一個數組,其元素的類型為指針。也就是說,指針數組中的每個元素都存儲著一個內存地址

虛函數地址表在 .data 區。

運行時多態:必須用指針或引用調用虛函數,對象.虛函數,這是編譯時,不是運行時多態。?

(3)代碼示例?

#include<stdio.h>
#include<iostream>
#include <cassert>
using namespace std;class Object
{
private:int value;
public:Object(int x = 0) :value(x){}~Object(){}virtual void add() { cout << "Object::add" << endl; }virtual void func() { cout << "Object::func" << endl; }virtual void print()const { cout << "Object::printf" << endl; }
};class Base :public Object
{
private:int num;
public:Base(int x=0):Object(x),num(x+10){}//重寫虛函數virtual void add() { cout << "Base::add" << endl; }virtual void func() { cout << "Base::func" << endl; }virtual void show() { cout << "Base::show" << endl; }};class Test :public Base
{
private:int count;
public:Test(int x=0):Base(x),count(x+10){}virtual void add() { cout << "Test::add" << endl; }virtual void show() { cout << "Test::show" << endl; }virtual void print()const { cout << "Test::printf" << endl; }};void funcPobj(Object* pobj)
{assert(pobj != nullptr);pobj->add();pobj->func();pobj->print();
}
int main()
{Test test(10);funcPobj(&test);return 0;
}

以上代碼,在內存中的虛表大致如下:

sizeof(Object):8;int+一個指向虛表的指針(32位操作系統)?

指針:?

通過虛表指針,訪問Test類的虛表

引用實現:

(1)派生類對象可以給基類,但基類不能給派生類。?

(2)強制類型轉換后,查的仍然是Base的虛表:

?(3)定義obj類型的對象,訪問的仍是Obj的虛表,

訪問obj的虛表,obj中沒有派生類的show方法,執行到“000000”報錯。這種強轉可以理解為:無效的。

(Base*) & obj?和?(Test*) & obj)只是簡單地改變了指針的類型,而不會改變對象本身的實際類型。obj?實際上是?Object?類型的對象,盡管你把它的指針強制轉換為?Base*?或?Test*?類型,但對象的內存布局和實際類型依舊是?Object

  • 虛函數調用Base?和?Test?類繼承自?Object?類,并且有各自的虛表。當你把?Object?類型的指針強制轉換為?Base*?或?Test*?類型并調用虛函數時,程序會依據轉換后的指針類型去訪問相應的虛表。然而,obj?實際上是?Object?類型的對象,它只有?Object?類的虛表,這就會導致程序訪問錯誤的虛表,從而引發未定義行為。
  • 成員訪問Base?和?Test?類可能包含?Object?類沒有的成員變量和成員函數。當你通過強制轉換后的指針訪問這些額外的成員時,程序會嘗試訪問不存在的內存位置,這也會導致未定義行為。
?(4)繼承關系中,動態創建派生類對象,但是是拿基類對象指向的,Object * op = new Base();在delete *op時,調用派生類的析構函數,解決辦法是將基類的析構函數設為虛函數,之后,就可以先調用~Base();再調基類的析構。

在繼承關系里,當使用基類指針指向動態創建的派生類對象,并且基類的析構函數不是虛函數時,在執行?delete?操作時只會調用基類的析構函數,這可能會造成派生類對象的部分資源無法正確釋放,進而引發內存泄漏等問題。

當基類的析構函數不是虛函數時delete?操作依據指針的靜態類型來決定調用哪個析構函數。由于指針類型是基類指針,所以只會調用基類的析構函數,派生類的析構函數不會被調用。?

?

基類析構不是虛函數示例代碼如下:?

#include <iostream>class Object {
public:~Object() {std::cout << "Object::~Object()" << std::endl;}
};class Base : public Object {
public:~Base() {std::cout << "Base::~Base()" << std::endl;}
};int main() {Object* op = new Base();delete op; return 0;
}

當把基類的析構函數設為虛函數后,delete?操作會依據對象的實際類型來決定調用哪個析構函數。因為對象的實際類型是派生類,所以會先調用派生類的析構函數,然后再調用基類的析構函數。

#include <iostream>class Object {
public:virtual ~Object() {std::cout << "Object::~Object()" << std::endl;}
};class Base : public Object {
public:~Base() {std::cout << "Base::~Base()" << std::endl;}
};int main() {Object* op = new Base();delete op; return 0;
}

?Base::~Base()

Object::~Object()

原理分析
  • 虛表機制:當基類的析構函數被聲明為虛函數時,編譯器會為基類和派生類分別創建虛表。在對象的內存布局中,會有一個虛表指針指向對應的虛表。當執行?delete?操作時,程序會通過對象的虛表指針找到對應的虛表,然后從虛表中獲取析構函數的地址并調用。由于對象的實際類型是派生類,所以會先調用派生類的析構函數。
  • 析構順序:在 C++ 里,析構函數的調用順序與構造函數的調用順序相反。當創建派生類對象時,會先調用基類的構造函數,再調用派生類的構造函數;而在銷毀對象時,會先調用派生類的析構函數,再調用基類的析構函數,以此確保對象的資源能夠被正確釋放。
(5)運行時多態是怎么實現的??

運行時多態主要基于繼承和虛函數實現。當基類指針或引用指向派生類對象時,通過該指針或引用調用虛函數,程序會在運行時根據對象的實際類型來決定調用哪個類的虛函數,從而實現不同的行為。

用一個指針指向一個對象,調用函數的時候,指向對象虛表的地址給edx,調用第幾個函數就(edx+偏移量 4n)?

運行時多態怎么實現的(匯編)?例:
#include <iostream>class Base {
public:virtual void func1() {std::cout << "Base::func1()" << std::endl;}virtual void func2() {std::cout << "Base::func2()" << std::endl;}
};class Derived : public Base {
public:void func1() override {std::cout << "Derived::func1()" << std::endl;}void func2() override {std::cout << "Derived::func2()" << std::endl;}
};int main() {Base* ptr = new Derived();ptr->func1();ptr->func2();delete ptr;return 0;
}

當創建?Derived?類的對象并讓?Base?類型的指針?ptr?指向它時,Derived?對象的內存布局起始位置會有一個虛表指針,該指針指向?Derived?類的虛表。

函數調用過程
  • 獲取虛表指針:當執行?ptr->func1()?時,程序首先通過?ptr?指針找到對象的內存地址,進而獲取對象的虛表指針,通常會把這個虛表指針的值存到某個寄存器(如你所說的?edx)中。
  • 計算函數地址:虛表本質是一個存儲函數指針的數組,每個函數指針在虛表中按聲明順序排列,且每個指針占一定字節數(在 32 位系統中一般是 4 字節,64 位系統中是 8 字節)。要調用第?n?個虛函數,就需要在虛表指針的基礎上加上偏移量?4n(32 位系統)或?8n(64 位系統)來獲取該函數的地址。例如,調用?func1()?時,偏移量為 0;調用?func2()?時,偏移量為 4(32 位)或 8(64 位)。
  • 調用函數:獲取到函數地址后,程序就會跳轉到該地址處執行相應的函數代碼。

4、靜態聯編和動態聯編

靜態聯編:在編譯和鏈接階段,就將函數實現和函數調用關聯起來。

C語言中,所有的聯編都是靜態聯編。

C++語言中,函數重載和函數模版也是靜態聯編。

C++中,對象.成員運算符,去調用對象虛函數,也是靜態聯編。

動態聯編:程序執行的時候才將函數實現和函數調用關聯起來。

C++中,使用引用、指針->,則程序在運行時選擇虛函數的過程稱為動態聯編。

5、例題:memset對vptr的影響:

class Object
{
private:int value;
public:Object(int x = 0) :value(x){memset(this, 0, sizeof(Object));}void func(){	cout << "func" << endl;			}virtual void add(int x) {	cout << "obj add" << endl;}
};
int main()
{Object obj;Object* op = &obj;obj.add(1); //靜態聯編op->add(2); //報錯
}

??? ?op->add(2);編譯會報錯

原因:

1.?memset?對虛表指針的影響

在 C++ 里,要是一個類包含虛函數,編譯器會為這個類創建虛表,并且在類的每個對象里插入一個虛表指針(vptr),此指針一般處于對象內存布局的起始位置。memset(this, 0, sizeof(Object));?這個操作會把對象的整個內存區域都置為 0,這就包含了虛表指針。一旦虛表指針被置為 0,就無法正確指向對應的虛表。

2. 虛函數調用機制

當借助基類指針(這里是?op)調用虛函數(像?op->add(2);)時,程序會通過對象的虛表指針找到對應的虛表,再從虛表中獲取該虛函數的地址,最后調用這個函數。但由于虛表指針被?memset?置為 0 了,程序就無法找到正確的虛表,從而引發運行時錯誤。

3. 直接對象調用和指針調用的區別
  • obj.add(1);這是直接通過對象調用虛函數。在這種情形下,編譯器能夠在編譯時就確定要調用的函數,所以不會借助虛表指針,也就不會受到?memset?操作的影響。
  • op->add(2);:這是通過指針調用虛函數,需要在運行時依靠虛表指針來確定要調用的函數。由于虛表指針被置為 0,程序就無法找到正確的虛表,進而導致運行時錯誤。

6、例題:

class Object
{
private:int value;
public:Object(int x = 0) :value(x){}void print() {cout << "obj::print" << endl;add(1);}virtual void add(int x) {	cout << "obj::add"<<x << endl;}
};
class Base :public Object {
private:int num;
public :Base(int x = 0) :Object(x + 10), num(x){}void show() { cout << "Base::show" << endl; print(); //this->print();}virtual void add(int x) { cout << "base::add" << x << endl; }
};
int main()
{Base base;base.show();return 0;
}

?

類的成員函數在調用數據時有this,?

調用過程分析
  1. main?函數中調用?base.show():創建了一個?Base?類的對象?base,然后調用其?show?方法。
  2. Base::show?方法中調用?print?方法:在?Base::show?方法里調用了?print?方法,由于?Base?類沒有重寫?print?方法,所以實際上調用的是基類?Object?的?print?方法。這里的?this?指針指向的是?Base?類的對象?base
  3. Object::print?方法中調用?add?方法:在?Object::print?方法中調用了?add(1)。因為?add?方法在?Object?類中被聲明為虛函數(virtual void add(int x)),并且?Base?類重寫了該虛函數,所以在運行時會根據?this?指針所指向對象的實際類型來決定調用哪個?add?方法。由于?this?指針指向的是?Base?類的對象?base,所以會調用?Base?類中重寫的?add?方法。

?如果在構造、析構函數里調用虛函數,調用誰的?答:調用自身類型的。不會查虛表。

7、動態+靜態聯編例題:

class Object
{
private:int value;
public:virtual void func(int a=10) { cout << "obj::func: a"<<a <<   endl; }
};
class Base :public Object {private:virtual void func(int b = 20) { cout << "Base::func: b"<<b << endl; }
};
int main()
{Base base;Object* op = &base;op->func();return 0;
}

1. 虛函數調用機制

在 C++ 里,當使用基類指針(如?Object* op)指向派生類對象(如?Base base),并且通過該指針調用虛函數(如?op->func())時,會在運行時依據對象的實際類型來決定調用哪個類的函數版本。由于?op?指向的是?Base?類的對象?base所以會調用?Base?類中重寫的?func?函數。

2. 默認參數的綁定規則

默認參數是在編譯時確定的,而不是運行時。當調用?op->func()?時,編譯器會查看指針的靜態類型(也就是?Object*)來確定默認參數的值。在?Object?類中,func?函數的默認數?a?被設定為 10,所以在調用?func?函數時,默認參數的值會使用?Object?類中定義的 10,而非?Base?類中定義的 20。

3. 總結

結合虛函數調用機制和默認參數的綁定規則,op->func()?會調用?Base?類的?func?函數,不過默認參數會使用?Object?類中定義的 10,因此輸出結果為?Base::func: b 10

C++中,構造函數不能為虛。

構造函數的任務:設置虛表指針。?

構造函數的主要作用是初始化對象的成員變量,為對象分配內存并設置初始狀態。在創建對象時,編譯器已經明確知道要創建的對象類型,因此可以直接調用相應的構造函數,不需要通過虛函數機制在運行時動態確定。

構造函數執行時,對象還未完全創建好,虛表指針可能還未被正確初始化。如果構造函數是虛函數,就需要通過虛表指針來調用它,但此時虛表指針可能還沒指向正確的虛表,這會導致無法正確調用構造函數。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/75273.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/75273.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/75273.shtml

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

相關文章

藍橋杯沖刺:一維前綴和

系列文章目錄 藍橋杯系列&#xff1a;一維前綴和 文章目錄 系列文章目錄前言一、暴力的寫法&#xff1a;二、一維前綴和的模板&#xff1a; 具體實現&#xff1a; 三、具體例題&#xff1a;求和 1.題目參考&#xff1a;2.以下是具體代碼實現&#xff1a; 總結 前言 上次我介紹…

使用UDP建立連接,會存在什么問題?

使用UDP建立連接&#xff0c;會存在可靠性、有序性、連接狀態管理等方面的問題&#xff1a; 1、數據傳輸不可靠&#xff1a; UDP沒有確認和重傳機制&#xff0c;發送方發送數據后&#xff0c;不會等待接收方的確認消息。這意味著如果數據在傳輸過程中丟失&#xff0c;發送方不…

YOLOv5配置訓練以及華為昇騰910B推理

參考文章&#xff1a; 保姆式yolov5教程&#xff0c;訓練你自己的數據集 - 知乎 Windows 10|11下安裝mmyolo-0.5.0版本 - 知乎 Ubuntu22.04安裝教程&基于華為Ascend AI處理器的om模型atc轉換環境安裝_ubuntu安裝atc工具-CSDN博客嵌入式AI---在華為昇騰推理自己的yolov5目標…

基于yolov11的汽車損傷檢測系統python源碼+onnx模型+評估指標曲線+精美GUI界面

【算法介紹】 基于YOLOv11的汽車損傷檢測系統是一種先進的計算機視覺技術&#xff0c;旨在快速準確地識別汽車的各種損傷類型。該系統利用YOLOv11模型的強大性能&#xff0c;實現了對車輛損傷的精確檢測與分類。 該系統能夠識別的損傷類型包括裂紋&#xff08;crack&#xff…

[ 3分鐘算法 ] | 遞歸搜索題目 : 合并兩個有序鏈表(遞歸版)

目錄 1. 題目鏈接&#xff1a; 2. 思路分析&#xff1a; 1. 重復子問題&#xff1f; 2. 具體子問題&#xff1f; 3. 遞歸出口&#xff1f; 3. 代碼實現&#xff1a; 4. 小結&#xff1a; 1. 循環(迭代) vs 遞歸 2. 遞歸 vs 深搜 1. 題目鏈接&#xff1a; 21. 合并…

單元測試原則之——不要模擬值對象 (1)

1. 什么是值對象(Value Objects)? 值對象是指那些不可變且僅通過其屬性(數據)來定義的對象。它們通常沒有復雜的邏輯或行為,主要用于存儲和傳遞數據。例如: ● 字符串(String) ● 數字(Integer, Double) ● 日期(LocalDate, Instant) ● 自定義的簡單數據類(如…

【軟件】在Windows和Ubuntu上使用TFTP和NFS

在Windows和Ubuntu上使用TFTP和NFS 零、介紹 最近在玩Linux開發板&#xff0c;在開發的過程中發現需要用到tftp和nfs來幫助傳輸文件&#xff0c;故此記錄如何使用這兩種軟件。 TFTP&#xff08;Trivial File Transfer Protocol&#xff09; &#xff1a;是一種簡化的文件傳輸…

JS判斷變量是否為空的方法

在 JavaScript 中&#xff0c;判斷變量是否為空需要根據不同的數據類型和具體需求來處理。以下是常見場景的解決方案&#xff1a; 1. 基礎判斷&#xff1a;null 或 undefined javascript if (value null || value undefined) {// 變量為空 } 或簡寫為&#xff1a; javasc…

Linux更換掛載nfs遷移數據流程

當前&#xff1a;原nfs&#xff08;10.16.2.1:/myData&#xff09;掛載在/myData&#xff0c;新的nfs&#xff08;10.16.2.2:/myData&#xff09;未掛載 目標&#xff1a;把舊nfs的數據遷移到新的nfs上&#xff0c;并把新nfs掛載到/myData 步驟&#xff1a; 1、新nfs掛載到一…

深入解析音頻:格式、同步及封裝容器

物理音頻和數字音頻 物理音頻 定義&#xff1a;物理音頻就是聲音在自然界中的物理表現形式&#xff0c;本質上是一種機械波&#xff0c;通過空氣或其他介質傳播。例如&#xff0c;當我們說話、樂器演奏或物體碰撞時&#xff0c;都會產生振動&#xff0c;這些振動會引起周圍介…

AI與.NET技術實操系列(四):使用 Semantic Kernel 和 DeepSeek 構建AI應用

1. 引言 在人工智能技術飛速發展的今天&#xff0c;大型語言模型&#xff08;Large Language Models, LLMs&#xff09;已成為智能應用開發的核心驅動力。從智能客服到自動化內容生成&#xff0c;LLMs的應用正在深刻改變我們的工作和生活方式。 對于.NET開發者而言&#xff0c;…

導出cad實體所有信息到txt并打開(生成唯一文件名) ——c#cad二次開發

效果如下: 建議在保存時指定編碼為UTF-8&#xff1a; using (StreamWriter sw new StreamWriter(filePath, false, Encoding.UTF8)) { // 寫入內容 } 最終 using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD…

Redis 源碼硬核解析系列專題 - 第一篇:Redis源碼入門與整體架構

1. 引言 Redis作為一個高性能的內存鍵值數據庫,其源碼以簡潔高效著稱。通過解析Redis源碼,我們可以深入理解其單線程模型、事件驅動機制以及模塊化設計的精髓。本篇將從Redis的源碼目錄結構入手,剖析其整體架構,并聚焦啟動流程和事件循環的核心實現。 2. Redis源碼目錄結構…

異步加載+內存分析

異步加載 Resources和AB包的同步加載與異步加載對比代碼&#xff1a; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI;public class AsyncLoad : MonoBehaviour {// Start is called before the first frame updatev…

將視頻m4s文件轉換為mp4格式

將視頻m4s文件轉換為mp4格式 一般情況&#xff1a;偏大的文件為視頻&#xff0c;偏小的文件為音頻。 環境要求&#xff1a;下載并安裝ffmpeg&#xff0c;并配置好環境變量&#xff0c;如下圖&#xff1a; 轉換代碼&#xff1a; import subprocessdef merge_m4s_to_mp4(vide…

EXCEL報錯:無法共享此工作薄,因表包含excel表或xml映射的解決方法

在分享工作薄是&#xff0c;如果出現了“無法共享此工作薄&#xff0c;因表包含excel表或xml映射”的報錯&#xff0c;那么有兩個原因&#xff1a; 1.包含Excel表格&#xff0c;這個也是相對比較常見的原因。 首先選中表格。如果你不知道表的位置在哪&#xff0c;那么在Excel左…

w2ui 水平滾動移動 虛擬列 數據丟失

https://w2ui.com/web/docs/1.5/w2grid.disableCVS https://github.com/vitmalina/w2ui/issues/1398 解決方案來源 問題現象: 窗口縮小 導致多列 出現水平滾動,滾動時觸發本地樣式重繪,導致record undefined,從而引發多列報錯 解決方案: 使用 disableCVS : true 一次加載到d…

在ensp進行OSPF+RIP+靜態網絡架構配置

一、實驗目的 1.Ospf與RIP的雙向引入路由消息 2.Ospf引入靜態路由信息 二、實驗要求 需求&#xff1a; 路由器可以互相ping通 實驗設備&#xff1a; 路由器router7臺 使用ensp搭建實驗壞境&#xff0c;結構如圖所示 三、實驗內容 1.配置R1、R2、R3路由器使用Ospf動態路由…

基于mediapipe深度學習和限定半徑最近鄰分類樹算法的人體摔倒檢測系統python源碼

目錄 1.算法運行效果圖預覽 2.算法運行軟件版本 3.部分核心程序 4.算法理論概述 4.1 Mediapipe人體姿態檢測原理 4.2 限定半徑最近鄰分類樹算法原理 5.算法完整程序工程 1.算法運行效果圖預覽 (完整程序運行后無水印) 2.算法運行軟件版本 人工智能算法python程序運行環…

deep-sync開源程序插件導出您的 DeepSeek 與 public 聊天

一、軟件介紹 文末提供下載 deep-sync開源程序插件導出您的 DeepSeek 與 public 聊天&#xff0c;這是一個瀏覽器擴展&#xff0c;它允許用戶公開、私下分享他們的聊天對話&#xff0c;并使用密碼或過期鏈接來增強 Deepseek Web UI。該擴展程序在 Deepseek 界面中添加了一個 “…