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

【C++高级主题】多重继承下的类作用域

目录

一、类作用域与名字查找规则:理解二义性的根源

1.1 类作用域的基本概念

1.2 单继承的名字查找流程

1.3 多重继承的名字查找特殊性

1.4 关键规则:“最近” 作用域优先,但多重继承无 “最近”

二、多重继承二义性的典型类型与代码示例

2.1 成员变量的二义性:同名变量冲突

2.2 成员函数的二义性:同名函数冲突

2.3 虚函数的二义性:同名虚函数未覆盖

2.4 菱形继承的二义性:公共基类的多份拷贝

三、名字查找的底层规则:编译器如何判定二义性

3.1 依赖于 “无歧义的声明” 原则

3.2 示例分析:同名但不同类型的成员

3.3 作用域查找的流程图  

四、避免用户级二义性的四大策略

4.1 显式作用域限定:指定基类作用域

4.2 派生类重写成员:覆盖基类同名成员

4.3 虚继承:解决菱形继承的公共基类二义性

4.4 使用 using 声明引入基类成员到派生类作用域

五、多重继承派生类的赋值控制:避免作用域引发的赋值错误

5.1 赋值运算符的隐式生成规则

5.2 二义性对赋值的影响

5.3 显式重载赋值运算符

六、最佳实践:避免多重继承的作用域陷阱

6.1 优先使用组合而非多重继承

6.2 限制多重继承的使用场景

6.3 显式覆盖所有可能冲突的成员

6.4 使用虚继承解决菱形问题

七、结论


在 C++ 中,多重继承(Multiple Inheritance)允许一个派生类同时继承多个基类的特性,这在设计复杂系统(如 “可序列化”+“可绘制” 的图形组件)时提供了强大的灵活性。但随之而来的挑战是:多个基类的作用域重叠可能导致名字冲突(二义性,Ambiguity),例如两个基类拥有同名的成员变量或函数。

一、类作用域与名字查找规则:理解二义性的根源

1.1 类作用域的基本概念

在 C++ 中,每个类(包括基类和派生类)都有独立的作用域(Scope),类的成员(变量、函数、类型别名等)被封装在该作用域内。当通过类对象或指针访问成员时,编译器需要确定成员所在的作用域,这一过程称为名字查找(Name Lookup)

1.2 单继承的名字查找流程

在单继承中,名字查找遵循 “从派生类到基类” 的递归规则:

  1. 首先在派生类的作用域中查找目标名字(如成员函数名、变量名)。
  2. 若未找到,递归到直接基类的作用域查找。
  3. 继续递归到基类的基类,直到找到目标名字或遍历完所有基类。

1.3 多重继承的名字查找特殊性

在多重继承中,派生类有多个直接基类(如BaseABaseB),名字查找会同时遍历所有直接基类的作用域。若多个基类的作用域中存在同名的成员,且这些成员在派生类中未被覆盖,则编译器无法确定应选择哪个基类的成员,导致二义性错误(编译失败)。

1.4 关键规则:“最近” 作用域优先,但多重继承无 “最近”

单继承中,基类的作用域是 “线性” 的,派生类到基类的路径唯一,因此名字查找不会歧义。但多重继承中,多个基类的作用域是 “并行” 的,若多个基类包含同名成员,且派生类未覆盖该成员,则编译器无法判断应选择哪个基类的成员(因为多个基类的作用域是 “同等距离” 的)。

二、多重继承二义性的典型类型与代码示例

2.1 成员变量的二义性:同名变量冲突

当多个基类定义了同名的成员变量时,派生类对象访问该变量会引发二义性。

代码示例:成员变量的二义性

#include <iostream>// 基类A:包含成员变量x
class BaseA {
public:int x = 10;
};// 基类B:包含同名成员变量x
class BaseB {
public:int x = 20;
};// 派生类D,同时继承BaseA和BaseB
class Derived : public BaseA, public BaseB {};int main() {Derived d;// std::cout << d.x << std::endl;  // 编译错误:'x' is ambiguousreturn 0;
}

错误信息 

2.2 成员函数的二义性:同名函数冲突

多个基类包含同名的成员函数(非虚函数或未被覆盖的虚函数)时,派生类直接调用该函数会引发二义性。

代码示例:成员函数的二义性

#include <iostream>class BaseA {
public:void func() { std::cout << "BaseA::func()" << std::endl; }
};class BaseB {
public:void func() { std::cout << "BaseB::func()" << std::endl; }
};class Derived : public BaseA, public BaseB {};int main() {Derived d;// d.func();  // 编译错误:'func' is ambiguousreturn 0;
}

错误信息  

2.3 虚函数的二义性:同名虚函数未覆盖

若多个基类包含同名虚函数,且派生类未覆盖该虚函数,则通过派生类对象或指针调用该虚函数时会二义性。

代码示例:虚函数的二义性

#include <iostream>class BaseA {
public:virtual void vfunc() { std::cout << "BaseA::vfunc()" << std::endl; }
};class BaseB {
public:virtual void vfunc() { std::cout << "BaseB::vfunc()" << std::endl; }
};class Derived : public BaseA, public BaseB {};  // 未覆盖vfunc()int main() {Derived d;// d.vfunc();  // 编译错误:'vfunc' is ambiguousreturn 0;
}

错误信息   

2.4 菱形继承的二义性:公共基类的多份拷贝

菱形继承(如A→B→DA→C→D)中,顶层基类A在派生类D中存在两份拷贝(B::AC::A),导致访问A的成员时二义性。

代码示例:菱形继承的二义性

#include <iostream>class A {
public:int value = 100;
};class B : public A {};  // B继承A
class C : public A {};  // C继承A
class D : public B, public C {};  // D继承B和Cint main() {D d;// std::cout << d.value << std::endl;  // 编译错误:'value' is ambiguous(d.B::A::value 或 d.C::A::value)return 0;
}

错误信息    

三、名字查找的底层规则:编译器如何判定二义性

3.1 依赖于 “无歧义的声明” 原则

C++ 标准规定:名字查找必须找到唯一的声明。若在多重继承的多个基类作用域中找到同名的声明(无论这些声明是否等价),则视为二义性,编译器拒绝编译。

3.2 示例分析:同名但不同类型的成员

即使多个基类的同名成员类型不同(如一个是int,另一个是void()函数),仍会引发二义性。

代码示例:同名不同类型的成员

class BaseA {
public:int x = 10;  // 成员变量x
};class BaseB {
public:void x() { std::cout << "BaseB::x()" << std::endl; }  // 成员函数x()
};class Derived : public BaseA, public BaseB {};int main() {Derived d;// d.x;  // 编译错误:'x' is ambiguous(变量vs函数)return 0;
}

错误信息     

3.3 作用域查找的流程图  

四、避免用户级二义性的四大策略

4.1 显式作用域限定:指定基类作用域

通过作用域解析符(::)显式指定成员所属的基类,是解决二义性最直接的方法。

代码示例:显式限定作用域

#include <iostream>class BaseA { public: int x = 10; };
class BaseB { public: int x = 20; };
class Derived : public BaseA, public BaseB {};int main() {Derived d;std::cout << "BaseA::x: " << d.BaseA::x << std::endl;  // 输出10std::cout << "BaseB::x: " << d.BaseB::x << std::endl;  // 输出20return 0;
}

运行结果: 

4.2 派生类重写成员:覆盖基类同名成员

在派生类中显式定义与基类同名的成员(变量或函数),覆盖基类的声明。此时,派生类的作用域中存在该成员的唯一声明,名字查找会优先选择派生类的成员。

代码示例:派生类重写成员

#include <iostream>class BaseA { public: void func() { std::cout << "BaseA::func()" << std::endl; } };
class BaseB { public: void func() { std::cout << "BaseB::func()" << std::endl; } };class Derived : public BaseA, public BaseB {
public:void func() { std::cout << "Derived::func()" << std::endl; }  // 重写func()
};int main() {Derived d;d.func();  // 调用Derived::func()(无歧义)d.BaseA::func();  // 显式调用BaseA的func()return 0;
}

运行结果:  

4.3 虚继承:解决菱形继承的公共基类二义性

对于菱形继承问题,使用虚继承(Virtual Inheritance)确保公共基类在派生类中仅存一份实例,避免多份拷贝导致的二义性。

代码示例:虚继承解决菱形二义性

#include <iostream>class A { public: int value = 100; };// B和C虚继承A,确保A在D中仅存一份实例
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};int main() {D d;d.value = 200;  // 无歧义,操作唯一的A实例std::cout << "d.B::A::value: " << d.B::value << std::endl;  // 输出200std::cout << "d.C::A::value: " << d.C::value << std::endl;  // 输出200(与d.B::value共享同一份数据)return 0;
}

运行结果:   

4.4 使用 using 声明引入基类成员到派生类作用域

通过using声明将基类的成员引入派生类的作用域,若多个基类的成员同名,需显式指定其中一个,否则仍会二义性。

代码示例:using 声明的使用 

#include <iostream>class BaseA { public: int x = 10; };
class BaseB { public: int x = 20; };class Derived : public BaseA, public BaseB {
public:using BaseA::x;  // 将BaseA的x引入Derived作用域// using BaseB::x;  // 若同时引入BaseB的x,仍会二义性
};int main() {Derived d;std::cout << "d.x: " << d.x << std::endl;  // 输出10(使用BaseA的x)std::cout << "d.BaseB::x: " << d.BaseB::x << std::endl;  // 仍可显式访问BaseB的xreturn 0;
}

运行结果:    

五、多重继承派生类的赋值控制:避免作用域引发的赋值错误

5.1 赋值运算符的隐式生成规则

C++ 编译器会为类隐式生成赋值运算符(operator=),其行为是逐成员赋值。在多重继承中,派生类的赋值运算符会依次调用各基类的赋值运算符,以及自身成员的赋值运算符。

5.2 二义性对赋值的影响

若多个基类存在同名成员,且未显式覆盖,直接赋值会引发二义性。例如: 

class BaseA { public: int x; };
class BaseB { public: int x; };
class Derived : public BaseA, public BaseB {};int main() {Derived d1, d2;// d1.x = d2.x;  // 编译错误:'x' is ambiguousreturn 0;
}

错误信息    

5.3 显式重载赋值运算符

为避免赋值时的二义性,派生类可显式重载赋值运算符,明确指定基类成员的赋值逻辑。

代码示例:显式重载赋值运算符 

#include <iostream>class BaseA {
public:int x;BaseA& operator=(const BaseA& other) {x = other.x;return *this;}
};class BaseB {
public:int x;BaseB& operator=(const BaseB& other) {x = other.x;return *this;}
};class Derived : public BaseA, public BaseB {
public:Derived& operator=(const Derived& other) {BaseA::operator=(other);  // 显式调用BaseA的赋值运算符BaseB::operator=(other);  // 显式调用BaseB的赋值运算符return *this;}
};int main() {Derived d1, d2;d1.BaseA::x = 10;d1.BaseB::x = 20;d2 = d1;std::cout << "d2.BaseA::x: " << d2.BaseA::x << std::endl;  // 输出10std::cout << "d2.BaseB::x: " << d2.BaseB::x << std::endl;  // 输出20return 0;
}

运行结果:     

六、最佳实践:避免多重继承的作用域陷阱

6.1 优先使用组合而非多重继承

多重继承虽灵活,但容易引入作用域二义性。多数场景下,通过组合(将基类作为派生类的成员变量)可以更简洁地实现功能复用,同时避免作用域冲突。

6.2 限制多重继承的使用场景

仅在以下场景使用多重继承:

  • 实现多个独立的接口(纯虚类),无成员变量冲突。
  • 复用多个不相关的具体实现(如 “日志功能类”+“配置解析类”)。

6.3 显式覆盖所有可能冲突的成员

在派生类中显式覆盖所有基类的同名成员(变量或函数),确保派生类作用域中存在唯一声明,从根本上避免二义性。

6.4 使用虚继承解决菱形问题

若必须使用菱形继承,通过虚继承确保公共基类仅存一份实例,避免多份拷贝导致的二义性和内存浪费。

七、结论

多重继承下的类作用域问题,核心在于名字查找的多路径性基类作用域的并行性。通过本文的学习,得出以下关键结论:

知识点核心规则
名字查找规则多重继承中,编译器同时遍历所有直接基类的作用域,找到唯一声明才合法。
二义性类型成员变量、成员函数、虚函数、菱形继承的公共基类均可能引发二义性。
二义性解决方案显式作用域限定、派生类重写成员、虚继承、using 声明。
赋值控制显式重载赋值运算符,明确调用各基类的赋值逻辑,避免作用域歧义。

掌握这些规则后,可以更安全地使用多重继承,在复杂系统设计中平衡灵活性与代码健壮性。


相关文章:

【C++高级主题】多重继承下的类作用域

目录 一、类作用域与名字查找规则&#xff1a;理解二义性的根源 1.1 类作用域的基本概念 1.2 单继承的名字查找流程 1.3 多重继承的名字查找特殊性 1.4 关键规则&#xff1a;“最近” 作用域优先&#xff0c;但多重继承无 “最近” 二、多重继承二义性的典型类型与代码示…...

基于Android的一周穿搭APP的设计与实现 _springboot+vue

开发语言&#xff1a;Java框架&#xff1a;springboot AndroidJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat12开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;Maven3.6 系统展示 APP登录 A…...

机器学习——使用多个决策树

使用单一决策树的弱点之一是决策树对数据中的微小变化非常敏感&#xff0c;一个使算法不那么敏感或更健壮的解决方案&#xff0c;不是建立一个决策树&#xff0c;而是要建立大量的决策树&#xff0c;我们称之为树合奏。 在这个例子中&#xff0c;我们一直在使用最好的特性来分…...

C# 中的对话框与导航:构建流畅用户交互的完整指南

在现代应用程序开发中&#xff0c;良好的用户交互体验是成功的关键因素之一。作为.NET开发者&#xff0c;熟练掌握C#中的对话框与导航技术&#xff0c;能够显著提升应用程序的易用性和专业性。本文将全面探讨Windows Forms、WPF、ASP.NET Core和MAUI等平台下的对话框与导航实现…...

DeepSeek - 尝试一下GitHub Models中的DeepSeek

1.简单介绍 当前DeepSeek使用的人很多&#xff0c;各大AI平台中也快速引入了DeekSeek&#xff0c;比如Azure AI Foundary(以前名字是Azure AI Studio)中的Model Catalog, HuggingFace, GitHub Models等。同时也出现了一些支持DeepSeek的.NET类库。微软的Semantic Kernel也支持…...

【判断酒酒花数】2022-3-31

缘由对超长正整数的处理&#xff1f; - C语言论坛 - 编程论坛 void 判断酒酒花数(_int64 n) {//缘由https://bbs.bccn.net/thread-508634-1-1.html_int64 t n; int h 0, j 0;//while (j < 3)h t % 10, t / 10, j;//整数的个位十位百位之和是其前缀while (t > 0)h t…...

对称加密-非对称加密

目录 非对称加密算法的优缺点是什么&#xff1f; ​一、非对称加密的核心特点​ ​二、非对称加密的显著优点​ 1. ​解决密钥分发难题​ 2. ​支持数字签名​ 3. ​前向安全性​ 4. ​访问控制灵活性​ ​三、非对称加密的局限性​ 1. ​性能瓶颈​ 2. ​密钥长度要…...

【OCCT+ImGUI系列】011-Poly-Poly_Triangle三角形面片

Poly_Triangle 是什么&#xff1f; Poly_Triangle 是一个非常轻量的类&#xff0c;用于表示一个三角网格中的单个三角形面片。它是构成 Poly_Triangulation&#xff08;三角网格对象&#xff09;的基本单位之一。之后会写关于碰撞检测的相关文章&#xff0c;三角面片是非常重要…...

【机器学习基础】机器学习入门核心算法:Mini-Batch K-Means算法

机器学习入门核心算法&#xff1a;Mini-Batch K-Means算法 一、算法逻辑工作流程与传统K-Means对比 二、算法原理与数学推导1. 目标函数2. Mini-Batch更新规则3. 学习率衰减机制4. 伪代码 三、模型评估1. 内部评估指标2. 收敛性判断3. 超参数调优 四、应用案例1. 图像处理 - 颜…...

机器学习实战36-基于遗传算法的水泵调度优化项目研究与代码实现

大家好,我是微学AI,今天给大家介绍一下机器学习实战36-基于遗传算法的水泵调度优化项目研究与代码实现。 文章目录 一、项目介绍二、项目背景三、数学原理与算法分析动态规划模型遗传算法设计编码方案适应度函数约束处理算法参数能量消耗模型一泵房能耗二泵房能耗效率计算模…...

计算机视觉与深度学习 | 基于Matlab的门禁指纹识别与人脸识别双系统实现

系统架构 #mermaid-svg-d8CEMhB3dNDpJu8M {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-d8CEMhB3dNDpJu8M .error-icon{fill:#552222;}#mermaid-svg-d8CEMhB3dNDpJu8M .error-text{fill:#552222;stroke:#552222;}#…...

TypeScript 定义同步方法

在TypeScript中定义同步方法是一个常见的需求&#xff0c;尤其是在处理不涉及异步操作的情况下。本文将详细介绍如何在TypeScript中定义和使用同步方法&#xff0c;包括代码示例和详细解释。 一、定义同步方法 在TypeScript中&#xff0c;定义同步方法与JavaScript类似&#…...

debian12.9或ubuntu,vagrant离线安装插件vagrant-libvirt,20250601

系统盘: https://mirror.lzu.edu.cn/debian-cd/12.9.0/amd64/iso-dvd/debian-12.9.0-amd64-DVD-1.iso 需要的依赖包,无需安装ruby( sudo apt install -y ruby-full ruby-dev rubygems,后来发现不安装会有编译警告,还是安装吧 ) ,无需安装 zlib1g-dev liblzma-dev libxml2-de…...

【仿muduo库实现并发服务器】使用正则表达式提取HTTP元素

使用正则表达式提取HTTP元素 1.正则表达式2.正则库的使用3.使用正则表达式提取HTTP请求行 1.正则表达式 正则表达式它其实是描述了一种字符串匹配的模式&#xff0c;它可以用来在一个字符串中检测一个特定格式的字串&#xff0c;以及可以将符合特定规则的字串进行替换或者提取…...

核心机制:流量控制

搭配滑动窗口使用的 窗口大小 窗口越大,传输速度就越快,但是也不能无限大,太大了,对于可靠性会有影响 比如发生方以非常快的速度,发送,接收方的处理速度跟不上,也就会导致有效数据被接受方丢弃(又得重传) 流量控制,就是根据接收方的处理能力(如何衡量?),干预到发送方的发送…...

Java中并发修改异常如何处理

在 Java 中&#xff0c;ConcurrentModificationException&#xff08;并发修改异常&#xff09; 是遍历集合时最常见的错误之一。它发生在迭代过程中直接修改集合结构&#xff08;添加/删除元素&#xff09;时&#xff0c;与是否多线程无关。以下是详细的处理方案&#xff1a; …...

极智项目 | 基于PyQT实现的YOLOv12行人目标检测软件设计

基于YOLOv12的专业级行人目标检测软件应用 开发者: 极智视界 软件下载&#xff1a;链接 &#x1f31f; 项目特色 专业检测: 基于最新YOLOv12模型&#xff0c;专门针对行人检测优化现代界面: 采用PyQt5构建的美观、直观的图形用户界面高性能: 支持GPU加速&#xff0c;检测速…...

JavaScript 对象展开语法

文章目录 JavaScript 对象展开语法1、对象展开&#xff08;Spread&#xff09;操作&#xff1a;2、组件注册3、示例应用总结 JavaScript 对象展开语法 示例代码&#xff1a; export default {...student,components: {ConponentA: ConponentA,ConponentB: ConponentB},这段代…...

简单transformer运用

通俗易懂解读&#xff1a;hw04.py 文件内容与 Transformer 的应用 这个文件是一个 Python 脚本&#xff08;hw04.py&#xff09;&#xff0c;用于完成 NTU 2021 Spring 机器学习课程的 HW4 作业任务&#xff1a;扬声器分类&#xff08;Speaker Classification&#xff09;。它…...

vscode不满足先决条件问题的解决——vscode的老版本安装与禁止更新(附安装包)

目录 起因 vscode更新设置的关闭 安装包 结语 起因 由于主包用的系统是centos的&#xff0c;且版本有点老了&#xff0c;再加上vscode现在不支持老版本的&#xff0c;这对主包来说更是雪上加霜啊 但是主包看了网上很多教程&#xff0c;眼花缭乱&#xff0c;好多配置要改&…...

RustDesk 搭建自建服务器并设置服务自启动

目录 0. 介绍 1. 事前准备 1.1 有公网 ip 的云服务器一台 1.2 服务端部署包 1.3 客户端安装包 2. 部署 2.1 服务器环境准备 2.2 上传服务端部署包 2.3 运行 pm2 3. 客户端使用 3.1 安装 3.2 配置 3.2.1 解锁网络设置 3.2.2 ID / 中级服务器 3.3 启动效果 > …...

【数据库】数据库恢复技术

数据库恢复技术 实现恢复的核心是使用冗余&#xff0c;也就是根据冗余数据重建不正确数据。 事务 事务是一个数据库操作序列&#xff0c;是一个不可分割的工作单位&#xff0c;是恢复和并发的基本单位。 在关系数据库中&#xff0c;一个事务是一条或多条SQL语句&#xff0c…...

Qt企业级串口通信实战:高效稳定的工业级应用开发指南

目录 一、前言 二、问题代码剖析 2.1 典型缺陷示例 2.2 企业级应用必备特性对比 三、关键优化策略与代码实现 3.1 增强型串口管理类 问题1&#xff1a;explicit关键字的作用 3.2 智能错误恢复机制 3.3 数据分帧处理算法 四、性能优化实测数据 五、工业级应用场景 六…...

力扣HOT100之动态规划:32. 最长有效括号

这道题放在动态规划里属实是有点难为人了&#xff0c;感觉用动态规划来做反而更难理解了&#xff0c;这道题用索引栈来做相当好理解&#xff0c;这里先讲下索引栈的思路。 索引栈做法 我们定义一个存放整数的栈&#xff0c;定义一个全局变量result来记录最长有效子串的长度&a…...

深入理解前端DOM:现代Web开发的基石

什么是DOM&#xff1f; DOM&#xff08;Document Object Model&#xff0c;文档对象模型&#xff09;是Web开发中最重要的概念之一。它是一个跨平台、语言独立的接口&#xff0c;将HTML或XML文档表示为树形结构&#xff0c;其中每个节点都是文档的一个部分&#xff08;如元素、…...

Springboot中Controller接收参数的方式

在Spring Boot中&#xff0c;Controller或RestController可以通过多种方式接收客户端传递的参数&#xff0c;主要包括以下几种常见方式&#xff1a; 1. 接收路径参数&#xff08;PathVariable&#xff09; 从URL路径中提取参数&#xff0c;适用于RESTful风格的API。 示例 Re…...

从一堆数字里长出一棵树:中序 + 后序构建二叉树的递归密码

从一堆数字里长出一棵树:中序 + 后序构建二叉树的递归密码 一、写在前面:一棵树的“复活计划” 作为一个老程序员,看到「中序 + 后序重建二叉树」这种题,我内心是兴奋的。为啥?它不仅是数据结构基础的“期末大题”,更是递归分解思想的典范——简洁、优雅、极具思维训练价…...

Unity UI 性能优化终极指南 — Image篇

&#x1f3af; Unity UI 性能优化终极指南 — Image篇 &#x1f9e9; Image 是什么&#xff1f; Image 是UGUI中最常用的基本绘制组件支持显示 Sprite&#xff0c;可以用于背景、按钮图标、装饰等是UI性能瓶颈的头号来源之一&#xff0c;直接影响Draw Call和Overdraw &#x1…...

Nginx + Tomcat 负载均衡、动静分离群集

一、 nginx 简介 Nginx 是一款轻量级的高性能 Web 服务器、反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器&#xff0c;在 BSD-like 协议下发行。其特点是占有内存少&#xff0c;并发能力强&#xff0c;在同类型的网页服务器中表现优异&#xff0c;常用…...

【maker-pdf 文档文字识别(包含ocr),安装使用完整教程】

测试效果还比较好&#xff0c;比markitdown要好 安装环境 conda create -n maker-pdf python3.12 conda activate marker-pdf pip install modelscope pip install marker-pdf -U下载模型 建议用modelscope上缓存的模型&#xff0c;不然下载会很慢 from modelscope import s…...