news 2026/10/26 14:24:09

C++继承名称遮掩:名字查找、重载决议与using修复

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++继承名称遮掩:名字查找、重载决议与using修复

《Effective C++》里讲继承的那几条,我反复翻得最多的不是虚析构,也不是 slicing,而是第 33 条“避免遮掩继承而来的名称”。原因很粗暴:它制造的报错信息最具有欺骗性。编译器的提示往往是no matching function for call to 'func(int)',行号指向一行看起来毫无问题的代码,而基类里明明躺着三个同名重载。这时候新手会怀疑参数类型、怀疑 include、怀疑编译器,老手一般先去看派生类里是不是又写了一个同名函数——名字查找(name lookup)在派生类作用域就停了,基类的那些重载压根没进过候选集。

这篇东西想把这件“看着简单、坑很深”的事拆开讲清楚。内容包括 C++ 名字查找的真实顺序、遮掩发生的完整机制、using声明和转交函数两种修法的适用边界、私有继承下的访问副作用,以及模板基类、默认参数这些交叉雷区。适合已经能写继承、但被“明明有却调不到”坑过的读者,也适合带人做代码审查的同学拿去当检查清单。全程用可编译的代码说话,尽量不空谈。

1. 先搞清楚这条条款到底在讲什么

1.1 一个“明明有却调不到”的最小复现

把问题剥到最干净,只需要两个类和三个重载:

class Base { public: virtual void func() { /* ... */ } virtual void func(int) { /* ... */ } virtual void func(double) { /* ... */ } }; class Derived : public Base { public: void func() { /* 只写了无参版本 */ } }; int main() { Derived d; d.func(); // 正常 d.func(3); // 编译失败:no matching function }

这不是重载决议失败,是名字查找阶段就已经失败。编译器在Derived的作用域里搜到名为func的声明,查找立刻停止,Base::func(int)和Base::func(double)从来没有进入候选集,所以连“参数不匹配”这种更友好的诊断都给不出来。

新手最容易在这里产生一个错误直觉:以为“继承”意味着“基类成员像被复制粘贴进派生类”。C++ 不是这么做的。继承建立的是类型关系和作用域嵌套关系,而不是文本合并。派生类作用域是内层,基类作用域是外层,和函数里套一个代码块、代码块里套另一个代码块,本质上是同一种结构。

1.2 由内向外、逐层停止:C++ 名字查找的基本模型

在一段成员函数体里,编译器找某个名字的顺序大致是这样的:

顺序查找范围典型例子
1函数体局部作用域形参、局部变量
2所在类的作用域(含其基类)成员函数、成员变量、嵌套类型
3外围命名空间namespace xx { ... }
4全局作用域全局函数、全局变量

关键在于第 2 步的内部实现:编译器先扫派生类自身声明的名字,一旦命中就直接返回这个命中结果,只有完全没找到,才继续去基类作用域里找。这个“命中即停止”是整条条款的根。

用生活里的例子类比会更直观。你去问一个部门的助理要某份资料,助理桌上有这份资料的封皮(同名函数),她就把封皮递给你,而不会跑去楼上档案室拿里面的文件。你得到的是一份“空的封皮”,报错时就表现为“这东西存在,但是用不了”。

重载决议发生在名字查找之后。名字查找交出候选集,重载决议在这个集合里挑最匹配的。候选集是空的或者只有不匹配的那一个,后面再怎么算都是白算。所以理解顺序很重要:不是重载失败了,是重载根本没开始。

1.3 语言为什么这样设计

有人会问,Java 和 C# 里派生类写一个同名方法,基类的其他重载不是照样能调吗?为什么 C++ 要反着来?

第一个理由是可预测性。如果派生类新增一个重载,基类的所有重载就自动并进来,那基类作者后来加一个签名,派生类的重载决议结果可能悄悄变了——参数类型刚好能隐式转换的话,某个原本调用派生类版本的语句可能改调基类版本。这种“远距离的意外行为”在大型代码库里极难排查。C++ 的选择是:派生类一旦声明了同名名字,就把外层那一层整个挡住,行为完全由本层决定,改动基类不会暗中影响派生类的决议。

第二个理由是实现成本。名字查找是编译期最高频的操作之一,如果每次都要沿着继承链把每一层的同名重载全部收集起来再统一做重载决议,模板和深层继承体系下的编译时间会明显上涨。分层停止的模型实现简单、边界清晰。

所以遮掩不是 bug,是刻意的语义。条款的措辞是“避免遮掩”,而不是“消除遮掩”——它要求你把遮掩控制在显式、可见、有意的范围内,而不是让它在不知不觉中发生。

2. 遮掩发生的完整机制拆解

2.1 名字查找先于重载决议,这是两件事

很多资料把这两个阶段混着讲,导致读者以为“编译器看到d.func(3)就去全继承链找func(int)”。真实流程分两段:

// 阶段一:名字查找,只在 Derived 作用域内找 "func" // 命中 Derived::func(),停止,候选集 = { Derived::func() } // 阶段二:重载决议,在候选集 { Derived::func() } 里找能接受 int 的 // 没有,报错

把派生类里的func()注释掉再编译,候选集会变成Base里的三个重载,d.func(3)立刻恢复正常。这个实验我建议每个刚接触这条的人都做一遍,五分钟就能建立正确直觉。

还有一个更容易被忽略的现象:在派生类自己的成员函数里调用同名基类函数,同样会被挡住。

class Derived : public Base { public: void func() { func(42); // 错误:这里也找不到 Base::func(int) } };

原因一样——成员函数体内的名字查找,先看局部,再看类作用域。类作用域里已经有Derived::func了,查找停止。要绕开,得写Base::func(42)显式限定,或者用后面的using声明。

2.2 静态类型决定在哪一层找,动态类型决定调哪个实现

这一点是虚函数和遮掩交叉时最容易懵的地方。看下面这段:

Derived d; Base* p = &d; p->func(3); // 可以,静态类型 Base,在 Base 作用域里找,找到 func(int) d.func(3); // 不行,静态类型 Derived,在 Derived 作用域里找,只找到 func()

同一个对象,通过不同静态类型的指针或引用访问,可见的接口集合可以完全不同。名字查找只依赖静态类型,虚函数机制才关心动态类型,两者发生在不同阶段,维度正交。

由此可以推出一个反直觉但正确的结论:遮掩完全不影响虚函数的动态绑定。假如Derived写了void func()覆盖基类的虚函数,只要你拿到Base*或Base&,调用照样走到派生类实现。但只要你手里是Derived对象本身或Derived*,基类的另外两个重载就是看不见。

实际工程里这会变成一种很隐蔽的不一致:某个模块用基类指针调,一切正常;另一个模块用派生类对象直接调,编译就过不去。排查的人如果不清楚这条规则,很容易怀疑到“是不是头文件版本不一致”上,白白浪费时间。

提示:遇到“同一个函数有的地方能调有的地方不能调”,第一反应应该是检查调用点的静态类型,而不是检查链接或头文件。

2.3 被遮掩的不只是函数,变量、类型、枚举一视同仁

大部分人只把遮掩和函数重载联系起来,实际上规则对所有名字生效:

class Base { public: enum Color { Red, Green, Blue }; int size; using SizeType = std::size_t; }; class Derived : public Base { public: enum Color { Red, Green }; // 名字 Color 被遮掩 int size; // Base::size 被遮掩 // SizeType 没有在本层声明,仍然可见 };

Derived里访问size拿到的是派生类自己的,Base::size需要显式限定。Color::Red这种写法也会受影响——Derived::Red和Base::Red是两个不同的枚举值,混用会造成非常隐蔽的逻辑错误,编译器却不报错,因为它们都是int可转换的枚举。

成员函数指针、静态成员、嵌套类,都在同一套规则下。写代码审查清单时,我会把“派生类是否重复声明了基类已有的名字”单独列一条,就是为了覆盖这些非函数场景。

同名的成员变量还有个更疼的变体:派生类不小心和基类起了同一个名字,本来想用的是基类那个,结果一直是派生类里的默认值。这种 bug 不会报错,只会让某个配置项永远是 0,排查起来全凭运气。

3. 三种主流修法的取舍与边界

3.1 using 声明:一次性把整组重载拉回视野

最标准的解法是在派生类的 public 区写一行using:

class Derived : public Base { public: using Base::func; // 把 Base 中所有名为 func 的成员引入本作用域 void func() override { /* ... */ } }; Derived d; d.func(); // 调用 Derived 版本 d.func(3); // 调用 Base::func(int) d.func(2.5); // 调用 Base::func(double)

using Base::func;引入的是全部同名重载,不是某一个签名。这是它最大的优点,也是它最需要注意的地方。

优点在于“向前兼容”:基类以后新增func(long)之类的重载,派生类自动获得,不用改代码。缺点同样在这里——基类的重载集合变了,派生类的公开接口就跟着变,如果派生类是对外 API 的一部分,这会是一次隐式的接口扩张。所以我的习惯是:内部实现类用using图省事,对外发布的接口类要么用转交函数精确控制,要么在文档里明确说明“本类暴露基类全部 func 重载”。

还有一条 C++ 标准层面的约束经常被忽略:using声明不能引入基类的 private 成员,尝试这么做会直接编译失败。如果基类成员是 protected,using写在派生类 public 区,会把它的可访问性提升到 public——这一点在下一节展开。

3.2 转交函数:只放行你想要的那个签名

当你只想让某个特定重载可见,或者想在新版本里加日志、加断言、加参数校验时,转交函数更合适:

class Derived : public Base { public: void func() override { /* 新实现 */ } void func(int x) { // 只放行 int 版本 // 可以在这里加日志、断言、参数检查 Base::func(x); } };

这时候d.func(2.5)依然是编译错误,因为double版本没有被放行。这是有意的:接口面越窄,将来改动的自由度越大。

代价是维护成本。基类每加一个重载,你得决定放不放行,不放行就要在文档里写清理由,否则下一个接手的人会以为这是遗漏。而且转交函数是普通函数,不是虚函数(除非你显式声明),通过Base&调用时不会走到这里——它只是解决了“可见性”,没有改变多态行为。

有个折中写法我比较常用:在派生类里同时保留using和覆盖版本,用注释标明“这里的 using 是有意的,用于保留基类重载”。遮掩是显式的,以后维护的人一眼能看懂意图,不会当成顺手删掉的多余代码。

3.3 私有继承下的访问恢复与 using 的位置陷阱

私有继承时,基类的 public 成员在派生类里全变成 private。想选择性放开,using声明是最直接的工具:

class Derived : private Base { public: using Base::func; // 把 Base 的 func 重载恢复为 public };

using写在 public 区,被引入的名字就是 public;写在 private 区,就是 private。它的位置直接决定访问级别,这一点比很多人想的要灵活,也更容易出事。我见过一段代码,把using Base::func;顺手写在 private 区,结果派生类对外只剩一个func(),所有原本依赖基类重载的调用点集体编译失败,报错信息又全是“no matching function”,排查了半天。

注意:using声明的位置就是访问控制的位置。它不是“声明一下存在性”,而是“以当前访问级别重新声明一遍”。

顺便说一句,私有继承这种场景下,组合(把基类对象作为成员)通常比私有继承更清晰,除非你需要访问 protected 成员或者需要覆盖虚函数。选型时先问自己“我是要复用实现,还是要被当成基类使用”,答案决定了继承还是组合。这条和《Effective C++》第 38、39 条是一套的,这里不展开。

4. 完整实操演练:从复现到修复

4.1 构造一个贴近真实场景的例子

真实项目里的遮掩很少像第一节那么干净,通常是几个因素叠在一起。下面这个例子来自一个渲染模块的简化版本,包含虚函数覆盖、重载、默认参数和模板基类四种情况:

struct Shape { virtual ~Shape() = default; virtual void draw() { std::cout << "shape\n"; } virtual void draw(int scale) { std::cout << "shape x" << scale << "\n"; } virtual void draw(double scale) { std::cout << "shape x" << scale << "\n"; } }; struct Circle : Shape { void draw() override { std::cout << "circle\n"; } }; void render(Shape& s) { s.draw(); s.draw(2); // 正常:静态类型是 Shape } int main() { Circle c; render(c); // 全部正常 c.draw(2); // 编译失败 }

render里一切正常,main里直接调用就炸。这种“换个调用点就不行”的现象,就是第 2.2 节说的静态类型差异的现场版。

4.2 逐行追踪编译器做了什么

对c.draw(2)这一行,编译器内部的动作可以拆成四步:

  1. 确定c的静态类型是Circle。
  2. 在Circle的作用域里查找名字draw,命中Circle::draw()。
  3. 名字查找停止,候选集 ={ Circle::draw() }。这一步就已经决定了成败。
  4. 重载决议在候选集里找匹配(int)的,没有,报错。

对s.draw(2)这一行:

  1. 静态类型是Shape&。
  2. 在Shape作用域查找draw,命中三个重载。
  3. 名字查找停止,候选集 = 三个重载。
  4. 重载决议选出draw(int)。
  5. 因为它是虚函数,运行时通过虚表找到Circle的实现——但Circle只覆盖了无参版本,所以这里调用的是Shape::draw(int)。

第 5 步值得单独说:Circle只覆盖了draw(),draw(int)没有被覆盖,动态调用会回落到基类实现。这也是“覆盖”和“遮掩”容易混淆的地方——覆盖改变的是运行时行为,遮掩改变的是编译期可见性,两件事完全独立。

4.3 修复并验证每一种调用路径

用using修复之后:

struct Circle : Shape { using Shape::draw; // 引入全部 draw 重载 void draw() override { std::cout << "circle\n"; } };

验证清单我一般会跑这四条:

调用形式预期结果
c.draw()调用Circle::draw()
c.draw(2)调用Shape::draw(int)
Circle& cr = c; cr.draw(2)同上,编译通过
Shape& sr = c; sr.draw()虚调用,走Circle::draw()

四条都过,说明可见性和多态行为都没问题。少测第 3 条的话,很容易出现“main 里能编,传参给别人又编不过”的反复。

如果改用转交函数,写法是:

struct Circle : Shape { void draw() override { std::cout << "circle\n"; } void draw(int s) { Shape::draw(s); } // 只放行 int };

此时c.draw(2.5)会报错,因为double版本没被放行。这是有意收窄接口的写法,别当成 bug。

4.4 两个隐藏雷区:默认参数与模板基类

默认参数是静态绑定的,这一点和遮掩常一起出现,容易互相掩盖:

struct Base { virtual void run(int speed = 10) { /* ... */ } }; struct Derived : Base { void run(int speed = 20) override { /* ... */ } }; Derived d; Base& b = d; b.run(); // 实际传入 10,不是 20 d.run(); // 传入 20

同一个对象,通过不同静态类型调用,默认参数不一样。这个坑和遮掩无关,但报错现象经常被混为一谈。我的习惯是虚函数一律不写默认参数,需要默认值时用重载或者std::optional,把选择权交给调用方,别交给静态类型。

模板基类的名字查找是另一个高频雷区:

template <typename T> struct Base { void execute() { /* ... */ } }; template <typename T> struct Derived : Base<T> { void run() { execute(); // 编译失败:找不到 execute } };

原因是Base<T>是依赖基类,编译器在解析模板定义时不会去依赖基类里查找非依赖名字——它没法确定Base<T>会不会有特化版本。三种写法可以解决:

// 写法一:this-> this->execute(); // 写法二:using 声明 using Base<T>::execute; execute(); // 写法三:显式限定 Base<T>::execute();

我一般用using,因为它顺带把可见性和访问级别都说清楚了,而且能一次性解决“要不要暴露基类全部重载”的问题。this->写法更短,但只解决当前函数体内的可见性,不改变外部调用者能看见什么。

5. 常见问题速查与排查技巧

5.1 现象与成因对照表

踩得多了,基本能靠现象反推原因。下面这张表是我自己整理的:

现象最可能的原因处理方式
no matching function for call to 'f(int)',基类明显有派生类声明了同名函数,遮掩了基类重载加using Base::f;或转交函数
同一个调用,A 处能编 B 处不能两处静态类型不同(派生类 vs 基类)检查调用点类型,或用基类引用
派生类成员函数里调不到基类同名函数类作用域内查找已命中本层显式Base::f(...)
模板派生类里调基类成员报“找不到”依赖基类的名字查找规则this->、using、显式限定三选一
派生类成员变量值永远是默认值与基类成员同名,误用了本层那个改名,或显式Base::member
私有继承后外部调不到基类函数访问级别随继承方式改变在 public 区加using Base::f;
两个基类有同名函数,调用报二义性多重继承下两个基类作用域都被找到using A::f;消歧义

表格里的最后一条值得多说一句:多重继承时,两个基类都有f,派生类没声明f,名字查找会把两个都收进候选集,重载决议发现两个一样好的候选,报二义性。加一条using A::f;明确表态,问题就消失了。这也是using除了“解遮掩”之外的第二个用途——消歧义。

5.2 我自己的排查顺序

遇到这类报错,我会按这个顺序走,通常两分钟内能定位:

第一步,看报错行的对象的静态类型。是派生类对象还是基类引用?这一步决定了查找范围,也决定了问题是不是遮掩。

第二步,去派生类定义里搜报错提到的函数名。搜到就基本确诊了,因为名字查找命中即停。搜不到再往下走。

第三步,检查是不是模板基类。如果是,看有没有用this->或using,没有的话九成是依赖名字查找的问题。

第四步,检查是不是多重继承,有没有同名成员导致二义性。

第五步,才是看参数类型、隐式转换这些常规方向。我把它放最后,是因为前四步能解释九成的“明明有却找不到”。

提示:不要一上来就去检查链接错误或者头文件重复包含。遮掩是纯编译期问题,链接错误的表现形式完全不同。

5.3 几个不写进教科书的小细节

细节一:using声明放在类的哪个位置,会影响重载决议的顺序吗?不会。using引入的名字和类内声明的名字在重载决议里地位相同,谁声明在前后无所谓。但如果派生类版本的签名和基类某个重载完全一样,派生类的会胜出——这不算遮掩,是正常的重载决议结果,正好重合的那个签名会调用派生类版本。

细节二:override关键字不解决遮掩。它只做一件事——检查这个函数是不是真的覆盖了基类虚函数,签名不对就报错。加了override,draw(int)该被遮掩还是被遮掩,两回事。

细节三:final也不解决遮掩。final禁止进一步覆盖,和名字可见性没有关系。我见过有人以为给类加final能让基类重载重新可见,这是误解。

细节四:遮掩的判定和函数体内容无关。派生类里只写一个声明、函数体是空的,也一样遮。有些同学写了个空实现想“占位”,结果把基类的整组重载都挡住了,这属于典型的搬石头砸脚。

细节五:using引入后再覆盖,两个版本会共存于候选集。如果using Base::func;和void func() override;同时存在,调用func()会走派生类版本,因为签名完全一样的两个候选里派生类的更“近”。这个行为是规范的,可以放心依赖。

还有一个我踩过的坑:在头文件里写using Base::func;之后,如果基类后来把某个重载删掉了,派生类不会立刻报错,而是要等到某个调用点真的用了那个签名才炸。所以做基类接口精简时,一定得把所有派生类的调用点扫一遍,别以为编译过了就没事。

6. 接口设计与代码审查中的落地建议

6.1 设计阶段就把遮掩的可能性掐掉

最好的处理方式是不让它发生。基类如果有一组重载,派生类就尽量不要用同名函数,宁可换个语义更明确的名字。比如基类的draw()/draw(int)是一组,派生类想加的东西叫render()或者drawWithScale(),名字一换,所有问题消失。

这条建议听起来像是回避问题,但实际项目里非常有效。名字本身就该携带语义,“同一个名字的不同重载”意味着这些版本在语义上是可以互换的,如果派生类要加的东西语义已经不同了,那本来就不该同名。

反过来,如果确实需要派生类覆盖基类行为、同时保留其他重载,那就明确写using,并加一行注释说明“此处using是有意为之,用于保留基类重载”。代码里最危险的不是错误,是“看起来像遗漏的正确写法”——下一个人清理代码时顺手删掉,问题在三个月后爆发。

6.2 一份可以照着用的代码审查清单

我把这条条款相关的检查压缩成五条,做 review 时逐条过:

第一,派生类是否声明了与基类同名的成员函数?如果有,是否显式处理了基类重载的可见性(using或转交)?

第二,using Base::xxx;的书写位置是否与预期访问级别一致?有没有不小心写在 private 区导致接口收窄?

第三,模板派生类里调用基类成员,是否使用了this->、using或显式限定?

第四,基类的虚函数是否使用了默认参数?如果用了,派生类覆盖时是否也重复了默认参数(重复了反而更危险,因为值可能不一致)?

第五,是否存在派生类与基类同名的成员变量?如果有,改成不同名字,别依赖作用域区分。

这五条覆盖了我在实际代码里见过的绝大多数相关问题。清单不长,但每一条背后都对应过一次真实的排查。

6.3 与虚函数、多重继承交叉时的注意事项

虚函数场景下有两条要记住。第一,遮掩不影响动态绑定,只要调用点的静态类型在基类那一层,虚调用照样生效。第二,覆盖一个重载不会让其他重载变成虚函数——基类的每个重载各自独立,派生类覆盖哪个,只有哪个的行为改变。

多重继承场景下,除了前面说的二义性,还有菱形继承的问题。虚继承能解决数据重复,但解决不了名字查找的二义性,照样得用using明确指定走哪一路。这种情况下我倾向于在设计上避开——多重继承的接口组合用纯虚基类(接口类)来做,实现复用走组合,能省掉一大半这类问题。

最后提一句和这条条款相邻的内容:第 34 条讲接口继承与实现继承的区别,第 36 条讲非虚函数不要在派生类里重定义,第 37 条讲虚函数的默认参数不要重定义。这四条串起来读,基本能覆盖继承相关的绝大部分编译期陷阱。我自己是每次写继承体系之前翻一遍,比出了问题再查要省时间。

实际用下来,这条条款给我的最大改变不是学会了using的语法,而是养成了一个习惯:写派生类之前,先去看一眼基类有哪些同名成员,先想清楚“我是要覆盖、要遮掩、还是要并存”。想清楚了再动手,比编译报错之后再回头猜,效率高得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/26 14:23:31

贪心算法解LeetCode跳跃游戏问题详解

1. 问题背景与题目解析 今天我们来拆解力扣&#xff08;LeetCode&#xff09;第55题"跳跃游戏"&#xff0c;这是一道经典的贪心算法应用题。题目描述很简单&#xff1a;给定一个非负整数数组nums&#xff0c;你最初位于数组的第一个下标&#xff0c;数组中的每个元素…

作者头像 李华
网站建设 2026/10/26 14:23:29

西部数据硬盘型号命名规范:六位代码拆解实战指南

简介&#xff1a;西部数据硬盘型号命名规范是一份面向渠道商、系统集成商及普通用户的速查文档&#xff0c;旨在让读者快速识别WD型号中隐含的容量、尺寸、转速、缓存及接口类型等核心参数。文件为单个PDF&#xff0c;容量约47KB&#xff0c;内容紧凑、便于离线查阅&#xff0c…

作者头像 李华
网站建设 2026/10/26 14:23:15

数据仓库模型设计与分层架构实战指南

简介&#xff1a;本资源是一份面向数据工程师、数仓开发人员及大数据初学者的系统性数据仓库建模方法论文档&#xff0c;聚焦解决实际项目中常见的模型混乱、分层不清、口径不一等核心痛点。文档完整覆盖数据仓库标准分层&#xff08;ODS→CDM→ADS&#xff09;设计逻辑&#x…

作者头像 李华
网站建设 2026/10/26 14:22:53

Rocky Linux 9.7部署OpenClaw的Node.js实践指南

1. 项目概述 在Rocky Linux 9.7系统上通过npm安装部署OpenClaw是一个典型的Node.js应用部署场景。OpenClaw作为一款新兴的开源工具&#xff08;具体用途根据社区资料显示可能与数据分析/自动化流程相关&#xff09;&#xff0c;其部署过程涉及Linux系统配置、Node.js环境搭建、…

作者头像 李华
网站建设 2026/10/26 14:22:28

CANoe+VH6501采样点测试:硬件级bit扰动定位原理与CAPL实现

简介&#xff1a;本资源是一份面向汽车电子测试工程师与CAN总线开发人员的专业级采样点鲁棒性验证方案&#xff0c;聚焦于利用CANoe软件与VH6501硬件协同开展DUT采样点定位与抗干扰能力评估。核心解决CAN节点在噪声环境下因采样时刻偏移导致Form Error误判的关键问题&#xff0…

作者头像 李华
网站建设 2026/10/26 14:22:05

大模型零基础考点精讲:原理、API调用与本地部署全攻略

最近收到好几条类似的私信&#xff1a;大模型到底怎么学&#xff1f;考试重点在哪里&#xff1f;零基础直接裸考行不行&#xff1f;问的人多了&#xff0c;我发现一个共性——大家不是不努力&#xff0c;是不知道哪些该认真背、哪些只要理解就行。市面上讲大模型的内容要么太理…

作者头像 李华