当前位置: 首页 > news >正文

【C++高阶(一)】继承

目录

一、继承的概念

1.继承的基本概念

2.继承的定义和语法

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

​编辑 4.总结

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

三、继承中的作用域

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

1.派生类中的默认构造函数

2.派生类中的拷贝构造函数

3.派生类中的移动构造函数

4.派生类的拷贝赋值运算符

 5.派生类的移动赋值运算符

6.派生类的析构函数

为什么基类析构函数需要virtual关键字修饰?

理由:多态性和正确的析构顺序

问题:非虚析构函数导致的资源泄漏

总结

五、继承和友元

六、继承与静态成员

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

1.单继承

2.多继承

3.菱形继承

 菱形继承的问题

4.菱形虚拟继承

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

虚基表的工作机制


一、继承的概念

在C++中,继承是一种面向对象编程的重要特性,它允许一个类(称为派生类或子类)从另一个类(称为基类或父类)继承属性和行为(成员变量和成员函数)。通过继承,派生类不仅可以拥有基类的所有成员,还可以扩展或修改这些成员以提供更具体或特殊的功能。

1.继承的基本概念

  1. 基类(Base Class):提供基础属性和行为的类。
  2. 派生类(Derived Class):从基类继承并扩展或修改其功能的类。
  3. 访问控制(Access Control)
    • Public 继承:基类的public和protected成员在派生类中保持其访问级别不变,public成员依然是public,protected成员依然是protected。
    • Protected 继承:基类的public和protected成员在派生类中都变为protected成员。
    • Private 继承:基类的public和protected成员在派生类中都变为private成员。
  4. 构造函数和析构函数:派生类的构造函数在执行前会先调用基类的构造函数,析构函数的调用顺序则相反,先调用派生类的析构函数,再调用基类的析构函数。
  5. 多重继承(Multiple Inheritance):C++允许一个派生类从多个基类继承。

2.继承的定义和语法

class Base {
public:int baseValue;void baseFunction() {// 基类成员函数}
};class Derived : public Base {
public:int derivedValue;void derivedFunction() {// 派生类成员函数}
};

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

 4.总结

  1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管是在类内还是在类外都不能去访问它。
  2. 基类private成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是继承才出现的。
  3. 实际上面的表格我们进行一下总结就能发现,基类的私有成员在子类中都是不可见的。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。
  4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
  5. 在实际运用中一般都是使用public继承,几乎很少使用protected/private继承,也不提倡使用protected/private继承,因为 protected/private继承下来的成员都只能在派生类的类里使用,实际中扩展维护性不强。

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

  • 派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。
  • 基类对象不能赋值给派生类对象。
  • 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。

class Base {
public:int baseValue;virtual void display() {std::cout << "Base class" << std::endl;}
};class Derived : public Base {
public:int derivedValue;void display() override {std::cout << "Derived class" << std::endl;}
};Base baseObj;
Derived derivedObj;baseObj = derivedObj;  // 对象切割发生
baseObj.display();     // 输出 "Base class"

在上面的例子中,尽管derivedObj赋值给了baseObj,但baseObj只保留了Base类的部分,派生类的derivedValue被切割掉了,调用display函数时也只会调用基类的版本

此外,C++允许使用基类的指针或引用来指向派生类对象,这可以实现多态性。多态性允许你通过基类接口调用派生类的重载函数。

Base* basePtr = &derivedObj;
basePtr->display();  // 输出 "Derived class"(多态性)Base& baseRef = derivedObj;
baseRef.display();  // 输出 "Derived class"(多态性)

 在上面代码中,basePtr和baseRef都指向Derived对象,并且调用display方法时,会调用派生类Derived中的版本,这是因为display函数被声明为virtual。(virtual关键字我们下面会讲)

另外还有类型转换:static_cast和dynamic_cast,感兴趣的可以去了解下。

三、继承中的作用域

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

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

在之前的学习中, 我们知道类可以自动生成一些默认的成员函数,这些成员函数包括默认构造函数、拷贝构造函数、移动构造函数、拷贝赋值运算符、移动赋值运算符和析构函数。而对于派生类,这些默认成员函数的生成和行为有一些特殊的规则和注意事项,下面我讲详细介绍。

1.派生类中的默认构造函数

默认构造函数在没有用户定义的构造函数时自动生成。对于派生类的默认构造函数,它会调用基类的默认构造函数(如果存在),然后初始化派生类的成员。而如果基类没有默认构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。

class Base {
public:Base() {std::cout << "Base default constructor" << std::endl;}
};class Derived : public Base {
public:Derived() {std::cout << "Derived default constructor" << std::endl;}
};int main() {Derived d;  // 输出:Base default constructor//      Derived default constructorreturn 0;
}

2.派生类中的拷贝构造函数

拷贝构造函数在没有用户定义的情况下自动生成,用于创建类的对象副本。派生类的拷贝构造函数会首先调用基类的拷贝构造函数,然后复制派生类的成员。

class Base {
public:Base(const Base&) {std::cout << "Base copy constructor" << std::endl;}
};class Derived : public Base {
public:Derived(const Derived& other) : Base(other) {std::cout << "Derived copy constructor" << std::endl;}
};int main() {Derived d1;Derived d2 = d1;  // 输出:Base copy constructor//      Derived copy constructorreturn 0;
}

3.派生类中的移动构造函数

移动构造函数在没有用户定义的情况下自动生成,用于移动资源所有权。派生类的移动构造函数会首先调用基类的移动构造函数,然后移动派生类的成员。

class Base {
public:Base(Base&&) noexcept {std::cout << "Base move constructor" << std::endl;}
};class Derived : public Base {
public:Derived(Derived&& other) noexcept : Base(std::move(other)) {std::cout << "Derived move constructor" << std::endl;}
};int main() {Derived d1;Derived d2 = std::move(d1);  // 输出:Base move constructor//      Derived move constructorreturn 0;
}

4.派生类的拷贝赋值运算符

拷贝赋值运算符在没有用户定义的情况下自动生成,用于将一个对象的内容赋值给另一个对象。派生类的拷贝赋值运算符会首先调用基类的拷贝赋值运算符,然后赋值派生类的成员。

class Base {
public:Base& operator=(const Base&) {std::cout << "Base copy assignment operator" << std::endl;return *this;}
};class Derived : public Base {
public:Derived& operator=(const Derived& other) {Base::operator=(other);std::cout << "Derived copy assignment operator" << std::endl;return *this;}
};int main() {Derived d1, d2;d1 = d2;  // 输出:Base copy assignment operator//      Derived copy assignment operatorreturn 0;
}

 5.派生类的移动赋值运算符

移动赋值运算符在没有用户定义的情况下自动生成,用于将一个对象的内容移动到另一个对象。派生类的移动赋值运算符会首先调用基类的移动赋值运算符,然后移动派生类的成员。

class Base {
public:Base& operator=(Base&&) noexcept {std::cout << "Base move assignment operator" << std::endl;return *this;}
};class Derived : public Base {
public:Derived& operator=(Derived&& other) noexcept {Base::operator=(std::move(other));std::cout << "Derived move assignment operator" << std::endl;return *this;}
};int main() {Derived d1, d2;d1 = std::move(d2);  // 输出:Base move assignment operator//      Derived move assignment operatorreturn 0;
}

6.派生类的析构函数

析构函数在没有用户定义的情况下自动生成,用于清理对象派生类的析构函数会首先调用派生类的析构函数,然后调用基类的析构函数。

class Base {
public:virtual ~Base() {std::cout << "Base destructor" << std::endl;}
};class Derived : public Base {
public:~Derived() {std::cout << "Derived destructor" << std::endl;}
};int main() {Base* b = new Derived();delete b;  // 输出:Derived destructor//      Base destructorreturn 0;
}

为什么基类析构函数需要virtual关键字修饰?

基类的析构函数需要加virtual关键字是为了确保在删除派生类对象时能够正确调用析构函数。这是一个非常重要的概念,尤其是在使用多态性和通过基类指针或引用操作派生类对象时。

理由:多态性和正确的析构顺序

class Base {
public:virtual ~Base() {std::cout << "Base destructor" << std::endl;}
};class Derived : public Base {
public:~Derived() {std::cout << "Derived destructor" << std::endl;}
};

当基类的析构函数是虚函数时,通过基类指针删除派生类对象时,C++会首先调用派生类的析构函数,然后再调用基类的析构函数。这确保了派生类中分配的资源可以先被正确释放,再释放基类中分配的资源。

int main() {Base* b = new Derived();delete b;  // 输出顺序:Derived destructor//           Base destructorreturn 0;
}

问题:非虚析构函数导致的资源泄漏

如果基类的析构函数不是虚函数,则通过基类指针删除派生类对象时,只会调用基类的析构函数,而不会调用派生类的析构函数。这会导致派生类中的资源没有被正确释放,造成资源泄漏。

class Base {
public:~Base() {std::cout << "Base destructor" << std::endl;}
};class Derived : public Base {
public:~Derived() {std::cout << "Derived destructor" << std::endl;}
};int main() {Base* b = new Derived();delete b;  // 只输出:Base destructorreturn 0;
}

在上述代码中,由于 Base 类的析构函数不是虚函数,删除 b 时只调用了 Base 的析构函数,Derived 类的析构函数没有被调用,这会导致 Derived 类中的资源没有被正确释放。

总结

为了确保在使用多态性时派生类对象可以被正确地销毁,避免资源泄漏,基类的析构函数应该声明为虚函数。这一做法可以保证删除派生类对象时,派生类和基类的析构函数都能被正确调用。以下是总结的要点:

  1. 多态性支持:使用基类指针或引用操作派生类对象时,确保正确调用派生类的析构函数。
  2. 正确的析构顺序:先调用派生类的析构函数,再调用基类的析构函数,确保资源正确释放。
  3. 避免资源泄漏:防止派生类中的资源没有被释放,导致内存泄漏或其他资源泄漏。

五、继承和友元

友元关系是单向的和局部的,友元关系不能继承!也就是说基类友元不能访问子类私有和保护成员。

#include <iostream>// 基类
class Base {
private:int basePrivateVar;
protected:int baseProtectedVar;
public:int basePublicVar;Base() : basePrivateVar(1), baseProtectedVar(2), basePublicVar(3) {}friend void baseFriendFunction(Base &obj);
};// 基类的友元函数
void baseFriendFunction(Base &obj) {std::cout << "Base Private Var: " << obj.basePrivateVar << std::endl;std::cout << "Base Protected Var: " << obj.baseProtectedVar << std::endl;
}// 派生类
class Derived : public Base {
private:int derivedPrivateVar;
protected:int derivedProtectedVar;
public:int derivedPublicVar;Derived() : derivedPrivateVar(4), derivedProtectedVar(5), derivedPublicVar(6) {}friend void derivedFriendFunction(Derived &obj);
};// 派生类的友元函数
void derivedFriendFunction(Derived &obj) {// 基类的友元不能访问派生类的私有或保护成员// std::cout << "Derived Private Var: " << obj.derivedPrivateVar << std::endl; // 错误// std::cout << "Derived Protected Var: " << obj.derivedProtectedVar << std::endl; // 错误std::cout << "Derived Public Var: " << obj.derivedPublicVar << std::endl;
}int main() {Base baseObj;Derived derivedObj;baseFriendFunction(baseObj); // 可以访问Base类的私有和保护成员derivedFriendFunction(derivedObj); // 可以访问Derived类的公共成员// 基类的友元函数不能访问派生类的私有和保护成员// baseFriendFunction(derivedObj); // 错误return 0;
}

六、继承与静态成员

基类定义了static成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。

静态成员变量需要在类外进行定义和初始化。静态成员函数则不需要在类外定义。

#include <iostream>// 基类
class Base {
public:static int staticVar;  // 声明静态成员变量static void staticFunction() {  // 声明并定义静态成员函数std::cout << "Static Function in Base" << std::endl;}
};// 定义静态成员变量
int Base::staticVar = 10;// 派生类
class Derived : public Base {
public:void display() {std::cout << "Base staticVar: " << staticVar << std::endl;  // 访问基类的静态成员变量staticFunction();  // 调用基类的静态成员函数}
};int main() {Derived obj;obj.display();// 静态成员可以通过类名直接访问Base::staticVar = 20;Derived::staticVar = 30;std::cout << "Base staticVar after modification: " << Base::staticVar << std::endl;std::cout << "Derived staticVar after modification: " << Derived::staticVar << std::endl;return 0;
}

静态成员的特点

  1. 类共享性:所有类的对象共享同一个静态成员变量。
  2. 类作用域:静态成员变量和静态成员函数在类作用域内,但可以通过类名直接访问。
  3. 内存管理:静态成员变量在程序启动时分配内存,程序结束时释放内存。

注意:

  • 静态成员函数:静态成员函数不能访问非静态成员变量和非静态成员函数,因为它们属于类本身,而不是类的某个对象。但是静态成员函数可以访问静态成员变量和其他静态成员函数。

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

1.单继承

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

2.多继承

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

3.菱形继承

菱形继承(也称钻石继承)是指一种特殊的多继承情况,其中一个类从两个基类继承,而这两个基类又继承自同一个祖先类。这种继承关系形成了一个菱形结构。

 菱形继承的问题

 从上面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在Assistant的对象中Person成员会有两份。

#include <iostream>// 祖先类
class A {
public:int value;A() : value(0) {}
};// 两个派生类继承自 A
class B : public A {};
class C : public A {};// 派生类 D 同时继承自 B 和 C
class D : public B, public C {};int main() {D obj;// obj.value; // 错误:二义性问题,不知道是从 B 继承的 A 还是从 C 继承的 A// 解决方法之一是明确指定路径obj.B::value = 1;obj.C::value = 2;std::cout << "obj.B::value: " << obj.B::value << std::endl;std::cout << "obj.C::value: " << obj.C::value << std::endl;return 0;
}

4.菱形虚拟继承

为了解决上面菱形继承所带来的问题,我们可以使用虚拟继承。虚拟继承确保在菱形继承结构中只存在一个基类的实例。

如在上面的代码中,我们可以在B和C继承A的时候使用虚拟继承,即

class B : virtual public A {};
class C : virtual public A {};

需要注意的是,虚拟继承不要在其他地方去使用。

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

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

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

下面是菱形虚拟继承的内存对象成员模型:

 

这里可以分析出D对象中将A放到了D对象组成的最下面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?

这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存储的是偏移量,通过偏移量就能找到A 。

总结:

虚拟继承通过确保每个虚拟基类在派生类中只有一个共享实例,从而避免了重复实例化和二义性问题。为了实现这一点,编译器会使用虚基表来跟踪和管理虚拟基类的实例。

虚基表的工作机制

  1. 虚基表的引入: 每个使用虚拟继承的类会包含一个虚基表指针。这个指针指向一个虚基表,该表包含虚拟基类的指针。

  2. 共享基类实例: 在派生类(如 D)的对象中,虚基表指针确保所有虚拟基类实例都指向同一个实际基类实例。这意味着 D 中只有一个 A 类的实例。

  3. 成员访问的重定向: 在访问基类成员时,编译器使用虚基表来正确地定位基类成员,确保访问的是唯一的基类实例。

所以,当一个类虚拟继承另一个类时,编译器在对象布局中插入一个虚基表指针(vbptr)。这个指针指向一个虚基表(vbtbl),而虚基表中包含指向虚拟基类的偏移量或地址。通过这种方式,每个派生类能够正确地定位并访问唯一的虚拟基类实例。


上面就是我们对C++继承的全部理解了~

相关文章:

【C++高阶(一)】继承

目录 一、继承的概念 1.继承的基本概念 2.继承的定义和语法 3.继承基类成员访问方式的变化 ​编辑 4.总结 二、基类和派生类对象赋值转换 三、继承中的作用域 四、派生类的默认成员函数 1.派生类中的默认构造函数 2.派生类中的拷贝构造函数 3.派生类中的移动构造函数…...

AI原生嵌入式矢量模型数据库ChromaDB-部署与使用指南

在人工智能大模型领域&#xff0c; 离不开NLP技术&#xff0c;在NLP中词向量是一种基本元素&#xff0c;如何存储这些元素呢&#xff1f; 可以使用向量数据库ChromeDB Chroma Chroma 是 AI 原生开源矢量数据库。Chroma 通过为 LLM 提供知识、事实和技能&#xff0c;使构建 L…...

c# 画一个正弦函数

1.概要 c# 画一个正弦函数 2.代码 using System; using System.Drawing; using System.Windows.Forms;public class SineWaveForm : Form {private const int Width 800;private const int Height 600;private const double Amplitude 100.0;private const double Period…...

Docker学习(3):镜像使用

当运行容器时&#xff0c;使用的镜像如果在本地中不存在&#xff0c;docker 就会自动从 docker 镜像仓库中下载&#xff0c;默认是从 Docker Hub 公共镜像源下载。 一、列出镜像列表 可以使用 docker images 来列出本地主机上的镜像。 各个选项说明&#xff1a; REPOSITORY&am…...

【Git】版本控制工具——Git介绍及使用

目录 版本控制版本控制系统的主要目标分类小结 分布式版本控制系统——GitGit特点Git与SVN的区别Git的工作机制 Git安装Git 团队协作机制团队内协作跨团队协作远程仓库远程仓库的作用有以下几个方面远程仓库操作流程/团队协作流程 Git分支什么是分支分支的好处 Git的常用命令Gi…...

面试八股之JVM篇3.6——垃圾回收——强引用、弱引用、虚引用、软引用

&#x1f308;hello&#xff0c;你好鸭&#xff0c;我是Ethan&#xff0c;一名不断学习的码农&#xff0c;很高兴你能来阅读。 ✔️目前博客主要更新Java系列、项目案例、计算机必学四件套等。 &#x1f3c3;人生之义&#xff0c;在于追求&#xff0c;不在成败&#xff0c;勤通…...

博客摘录「 Sql Server 收缩日志文件原理及always on 下的实践」2024年5月22日

四、Always on 环境下实践   先对数据库进行完整备份&#xff1a; EXEC sp_configure show advanced options, 1; RECONFIGURE; EXEC sp_configure xp_cmdshell, 1; RECONFIGURE; DECLARE DbName NVARCHAR(1000); DECLARE myCursor CURSOR LOCAL STATIC FOR S…...

每日一题(5)——StringBuffer操作

编写程序&#xff0c;对StringBuffer对象进行追加&#xff0c;插入和修改缓冲区长度等操作&#xff1b; class StringDemo{public static void main(String[] args){boolean btrue;int i321;long l123456;String s1new String("Hello,world!");StringBuffer s2new S…...

默认路由实现两个网段互通实验

默认路由实现两个网段互通实验 **默认路由&#xff1a;**是一种特殊的静态路由&#xff0c;当路由表中与数据包目的地址没有匹配的表项时&#xff0c;数据包将根据默认路由条目进行转发。默认路由在某些时候是非常有效的&#xff0c;例如在末梢网络中&#xff0c;默认路由可以…...

ComfyUI完全入门:图生图局部重绘

大家好&#xff0c;我是每天分享AI应用的萤火君&#xff01; 这篇文章的主题和美女有关&#xff0c;不过并不是教大家生产美女视频&#xff0c;而是讲解 ComfyUI 的图生图局部重绘&#xff0c;其中将会以美女图片为例&#xff0c;来展示局部重绘的强大威力。 先看看效果&…...

基于UDP的网络多人聊天室

UDP服务器 #include <myheader.h>//宏定义打印错误信息 #define PRINT_ERR(msg) \do \{ \printf("%S,%D,%S\n",__FI…...

美国FDA认证是什么,食品FDA注册申请流程

​美国FDA认证是什么&#xff1f; 美国FDA认证&#xff0c;全称为美国食品药品监督管理局&#xff08;Food and Drug Administration&#xff09;的认证&#xff0c;是美国政府为了确保食品、药品、医疗器械等产品的安全性和有效性所设立的重要制度。FDA认证的种类繁多&#x…...

golang的context和chan 的使用

1. context 作用 context包的context的接口&#xff0c;主要是控制协程执行上下文的时间&#xff0c;以及取消程序的执行&#xff0c;以及上下文中传递数据等作用&#xff0c;golang中耗时或者需要协同的操作都会见到context的身影。 context有几个常用的方法 1.1 context.B…...

洛谷P3574 [POI2014] FAR-FarmCraft(树形dp)

洛谷 P 3574 [ P O I 2014 ] F A R − F a r m C r a f t &#xff08;树形 d p &#xff09; \Huge{洛谷P3574 [POI2014] FAR-FarmCraft&#xff08;树形dp&#xff09;} 洛谷P3574[POI2014]FAR−FarmCraft&#xff08;树形dp&#xff09; 文章目录 题意题目说明 思路标程 题目…...

vue/core源码中ref源码的js化

起源&#xff1a; 当看见reactivity文件中的ref.ts文件长达五百多的ts代码后&#xff0c;突发奇想想看下转化成js有多少行。 进行转化&#xff1a; let shouldTrack true; // Define shouldTrack variable let activeEffect null; // Define activeEffect variable// 定义…...

准备打ccf

准备打ccf...

k8s遇到的错误记录

时隔四年有开始重新鼓捣k8s了&#xff0c;重新安装后遇到的错误记录如下&#xff1a; Error: Package: kubelet-1.14.0-0.x86_64 (kubernetes) Requires: kubernetes-cni 0.7.5 Available: kubernetes-cni-0.3.0.1-0.07a8a2.x86_64 (kubernetes) …...

全局平均池化笔记

全局平均池化&#xff08;Global Average Pooling, GAP&#xff09;是一种用于卷积神经网络&#xff08;CNN&#xff09;中的池化操作&#xff0c;其主要作用和优点包括&#xff1a; 减少参数数量&#xff1a;全局平均池化层将每个特征图通过取其所有元素的平均值&#xff0c;压…...

【数仓系列】maxcompute、postgresql、sparksql等行转列数据处理实战总结(其他类型持续总结更新)

1.熟悉、梳理、总结项目研发实战中的SQL开发日常使用中的问题、经验总结&#xff0c;都是常用的开发技能&#xff0c;可以省去很多时间&#xff0c;时间长就忘记了 2.欢迎点赞、关注、批评、指正&#xff0c;互三走起来&#xff0c;小手动起来&#xff01; 文章目录 1.maxcompu…...

用数据,简单点!奇点云2024 StartDT Day数智科技大会,直播见

在充满挑战的2024&#xff0c;企业如何以最小化的资源投入和试错成本&#xff0c;挖掘新的增长机会&#xff0c;实现确定性发展&#xff1f; “简单点”是当前商业环境的应对策略&#xff0c;也是奇点云2024 StartDT Day的核心理念。 5月28日&#xff0c;由奇点云主办的2024 S…...

Cloneable接口和深拷贝

在java中如何对对象进行拷贝呢&#xff1f;我们可以使用Object类中的clone方法。 一、浅拷贝 在使用clone方法对对象进行拷贝的时候&#xff0c;需要注意&#xff1a; 1.需要重写clone方法&#xff1b; 2.clone方法的返回值是Object类&#xff0c;需要强制类型转化&#xf…...

C++:vector的介绍及使用

✨✨✨学习的道路很枯燥&#xff0c;希望我们能并肩走下来! 文章目录 文章目录 前言 一、vector的介绍 二、vector的使用 2.1.构造和赋值重载&#xff08;Member functions&#xff09; 2.2 vector iterator 的使用 2.3 vector 空间增长问题 2.4 vector 增删查改 三 sort 四 v…...

【机器学习】大模型在机器学习中的应用:从深度学习到生成式人工智能的演进

&#x1f512;文章目录&#xff1a; &#x1f4a5;1.引言 ☔2.大模型概述 &#x1f6b2;3.大模型在深度学习中的应用 &#x1f6f4;4.大模型在生成式人工智能中的应用 &#x1f44a;5.大模型的挑战与未来展望 &#x1f4a5;1.引言 随着数据量的爆炸性增长和计算能力的提…...

营销短信XML接口对接发送示例

在现代社会中&#xff0c;通信技术日新月异&#xff0c;其中&#xff0c;短信作为一种快速、简便的通信方式&#xff0c;仍然在日常生活中占据着重要的地位。为了满足各种应用场景的需求&#xff0c;短信接口应运而生&#xff0c;成为了实现高能有效通信的关键。 短信接口是一种…...

【C语言刷题系列】求一个数组中两个元素a和b的和最接近整数m

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;C语言刷题系列 目录 一、问题描述 二、解题思路 解题思路&#xff1a; 解题步骤: 三、C语言代码实现及测试 一、问题描述 给定一…...

Python pdf2imges -- pdf文件转图片

pdf文件转图片&#xff0c;需要安装PyMuPDF包&#xff0c;具体PyMuPDF包介绍可以参考&#xff1a;Python 处理 PDF 的神器 -- PyMuPDF import fitz # pip install PyMuPDF# PDF转换为IMG统一管理 def pdf_to_images(pdf_path, img_path, filename):"""pdf_p…...

分布式版本控制工具 git

git 是什么 分布式版本控制工具。github 是代码托管平台。 git 有什么用 保存文件的所有修改记录。使用版本号&#xff08;sha1 哈希值&#xff09; 进行区分。随时可浏览历史版本记录。可还原到历史指定版本。对比不同版本的文件差异。 为什么要使用 git 多人协作开发一个大…...

Flutter 中的 ExpansionTile 小部件:全面指南

Flutter 中的 ExpansionTile 小部件&#xff1a;全面指南 在 Flutter 应用中&#xff0c;ExpansionTile 是一个常用的折叠列表项&#xff0c;它允许用户点击标题来展开或折叠更多的内容。这个组件在实现可折叠列表、FAQ 部分或显示详情信息时非常有用。本文将详细介绍 Expansi…...

二进制的协议的测试程序

一、引子 由于要调试二进制私有协议&#xff0c;不想用C重头到尾写&#xff0c;用C写工程量有点大&#xff0c;因此想找一个比较简单的工具&#xff0c;postman无法实现&#xff0c;外界的几乎找不到合适的工具&#xff0c;只能考虑手写一个。 前面写了一个python通过tcp协议发…...

多线程事务

一、业务场景 我们在工作中经常会到往数据库里插入大量数据的工作&#xff0c;但是既需要保证数据的一致性&#xff0c;又要保证程序执行的效率。因此需要在多线程中使用事务&#xff0c;这样既可以保证数据的一致性&#xff0c;又能保证程序的执行效率。但是spring自带的Trans…...