C++11新标准

重点

  1. auto 类型推导
  2. 范围 for 迭代
  3. 初始化列表
  4. 变参模板

新类型
C++11新增了类型 long long 和 unsigned long long,以支持64位(或更宽)的整型;新增了类型 char16_t和 char32_t,以支持 16位和 32 位的字符表示;还新增了“原始”字符串。

常量

nullptr

nullptr 出现的目的是为了替代 NULL。 C 与 C++ 语言中有空指针常量,它们能被隐式转换成任何指针类型的空指针值,或 C++ 中的任何成员指针类型的空成员指针值。 NULL 由标准库实现提供,并被定义为实现定义的空指针常量。在 C 中,有些标准库会把 NULL 定义为 ((void*)0) 而有些将它定义为 0

C++ 不允许直接将 void * 隐式转换到其他类型,从而 ((void*)0) 不是 NULL 的合法实现。如果标准库尝试把 NULL 定义为 ((void*)0),那么下面这句代码中会出现编译错误:

char *ch = NULL;

没有了 void * 隐式转换的 C++ 只好将 NULL 定义为 0。而这依然会产生新的问题,将 NULL 定义成 0 将导致 C++ 中重载特性发生混乱。考虑下面这两个 foo 函数:

void foo(char*);
void foo(int);

那么 foo(NULL); 这个语句将会去调用 foo(int),从而导致代码违反直觉。

为了解决这个问题,C++11 引入了 nullptr 关键字,专门用来区分空指针、0。而 nullptr 的类型为 nullptr_t,能够隐式的转换为任何指针或成员指针的类型,也能和他们进行相等或者不等的比较。

#include <iostream>
#include <type_traits>void foo(char *);
void foo(int);int main() {if (std::is_same<decltype(NULL), decltype(0)>::value)std::cout << "NULL == 0" << std::endl;if (std::is_same<decltype(NULL), decltype((void*)0)>::value)std::cout << "NULL == (void *)0" << std::endl;if (std::is_same<decltype(NULL), std::nullptr_t>::value)std::cout << "NULL == nullptr" << std::endl;foo(0);          // 调用 foo(int)// foo(NULL);    // 该行不能通过编译foo(nullptr);    // 调用 foo(char*)return 0;
}void foo(char *) {std::cout << "foo(char*) is called" << std::endl;
}
void foo(int i) {std::cout << "foo(int) is called" << std::endl;
}

将输出: 

foo(int) is called
foo(char*) is called

从输出中我们可以看出,NULL 不同于 0nullptr。所以,请养成直接使用 nullptr的习惯。

此外,在上面的代码中,我们使用了 decltypestd::is_same 这两个属于现代 C++ 的语法,简单来说,decltype 用于类型推导,而 std::is_same 用于比较两个类型是否相同,我们会在后面 decltype 一节中详细讨论。

 constexp

C++ 本身已经具备了常量表达式的概念,比如 1+2, 3*4 这种表达式总是会产生相同的结果并且没有任何副作用。如果编译器能够在编译时就把这些表达式直接优化并植入到程序运行时,将能增加程序的性能。一个非常明显的例子就是在数组的定义阶段:

#include <iostream>
#define LEN 10int len_foo() {int i = 2;return i;
}
constexpr int len_foo_constexpr() {return 5;
}constexpr int fibonacci(const int n) {return n == 1 || n == 2 ? 1 : fibonacci(n-1)+fibonacci(n-2);
}int main() {char arr_1[10];                      // 合法char arr_2[LEN];                     // 合法int len = 10;// char arr_3[len];                  // 非法const int len_2 = len + 1;constexpr int len_2_constexpr = 1 + 2 + 3;// char arr_4[len_2];                // 非法char arr_4[len_2_constexpr];         // 合法// char arr_5[len_foo()+5];          // 非法char arr_6[len_foo_constexpr() + 1]; // 合法std::cout << fibonacci(10) << std::endl;// 1, 1, 2, 3, 5, 8, 13, 21, 34, 55std::cout << fibonacci(10) << std::endl;return 0;
}

上面的例子中,char arr_4[len_2] 可能比较令人困惑,因为 len_2 已经被定义为了常量。为什么 char arr_4[len_2] 仍然是非法的呢?这是因为 C++ 标准中数组的长度必须是一个常量表达式,而对于 len_2 而言,这是一个 const 常数,而不是一个常量表达式,因此(即便这种行为在大部分编译器中都支持,但是)它是一个非法的行为,我们需要使用接下来即将介绍的 C++11 引入的 constexpr 特性来解决这个问题;而对于 arr_5 来说,C++98 之前的编译器无法得知 len_foo() 在运行期实际上是返回一个常数,这也就导致了非法的产生。

注意,现在大部分编译器其实都带有自身编译优化,很多非法行为在编译器优化的加持下会变得合法,若需重现编译报错的现象需要使用老版本的编译器。

C++11 提供了 constexpr 让用户显式的声明函数或对象构造函数在编译期会成为常量表达式,这个关键字明确的告诉编译器应该去验证 len_foo 在编译期就应该是一个常量表达式。 

此外,constexpr 修饰的函数可以使用递归:

constexpr int fibonacci(const int n) {return n == 1 || n == 2 ? 1 : fibonacci(n-1)+fibonacci(n-2);
}

 从 C++14 开始,constexpr 函数可以在内部使用局部变量、循环和分支等简单语句,例如下面的代码在 C++11 的标准下是不能够通过编译的:

constexpr int fibonacci(const int n) {if(n == 1) return 1;if(n == 2) return 1;return fibonacci(n-1) + fibonacci(n-2);
}

为此,我们可以写出下面这类简化的版本来使得函数从 C++11 开始即可用:

constexpr int fibonacci(const int n) {return n == 1 || n == 2 ? 1 : fibonacci(n-1) + fibonacci(n-2);
}

变量及其初始化 

if/switch 变量声明强化

在传统 C++ 中,变量的声明虽然能够位于任何位置,甚至于 for 语句内能够声明一个临时变量 int,但始终没有办法在 ifswitch 语句中声明一个临时的变量。例如:

#include <iostream>
#include <vector>
#include <algorithm>int main() {std::vector<int> vec = {1, 2, 3, 4};// 在 c++17 之前const std::vector<int>::iterator itr = std::find(vec.begin(), vec.end(), 2);if (itr != vec.end()) {*itr = 3;}// 需要重新定义一个新的变量const std::vector<int>::iterator itr2 = std::find(vec.begin(), vec.end(), 3);if (itr2 != vec.end()) {*itr2 = 4;}// 将输出 1, 4, 3, 4for (std::vector<int>::iterator element = vec.begin(); element != vec.end(); ++element)std::cout << *element << std::endl;
}

在上面的代码中,我们可以看到 itr 这一变量是定义在整个 main() 的作用域内的,这导致当我们需要再次遍历整个 std::vector 时,需要重新命名另一个变量。C++17 消除了这一限制,使得我们可以在 if(或 switch)中完成这一操作:

// 将临时变量放到 if 语句内
if (const std::vector<int>::iterator itr = std::find(vec.begin(), vec.end(), 3);itr != vec.end()) {*itr = 4;
}

初始化列表

初始化是一个非常重要的语言特性,最常见的就是在对象进行初始化时进行使用。
在传统 C++ 中,不同的对象有着不同的初始化方法,例如普通数组、
POD (Plain Old Data,即没有构造、析构和虚函数的类或结构体)
类型都可以使用 {} 进行初始化,也就是我们所说的初始化列表。
而对于类对象的初始化,要么需要通过拷贝构造、要么就需要使用 () 进行。
这些不同方法都针对各自对象,不能通用。例如:

#include <iostream>
#include <vector>class Foo {
public:int value_a;int value_b;Foo(int a, int b) : value_a(a), value_b(b) {}
};int main() {// before C++11int arr[3] = {1, 2, 3};Foo foo(1, 2);std::vector<int> vec = {1, 2, 3, 4, 5};std::cout << "arr[0]: " << arr[0] << std::endl;std::cout << "foo:" << foo.value_a << ", " << foo.value_b << std::endl;for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {std::cout << *it << std::endl;}return 0;
}

为解决这个问题,C++11 首先把初始化列表的概念绑定到类型上,称其为 std::initializer_list,允许构造函数或其他函数像参数一样使用初始化列表,这就为类对象的初始化与普通数组和 POD 的初始化方法提供了统一的桥梁,例如:

#include <initializer_list>
#include <vector>
#include <iostream>class MagicFoo {
public:std::vector<int> vec;MagicFoo(std::initializer_list<int> list) {for (std::initializer_list<int>::iterator it = list.begin();it != list.end(); ++it)vec.push_back(*it);}
};
int main() {// after C++11MagicFoo magicFoo = {1, 2, 3, 4, 5};std::cout << "magicFoo: ";for (std::vector<int>::iterator it = magicFoo.vec.begin(); it != magicFoo.vec.end(); ++it) std::cout << *it << std::endl;
}

这种构造函数被叫做初始化列表构造函数,具有这种构造函数的类型将在初始化时被特殊关照。

初始化列表除了用在对象构造上,还能将其作为普通函数的形参,例如:

public:void foo(std::initializer_list<int> list) {for (std::initializer_list<int>::iterator it = list.begin();it != list.end(); ++it) vec.push_back(*it);}magicFoo.foo({6,7,8,9});

其次,C++11 还提供了统一的语法来初始化任意的对象,例如:

Foo foo2 {3, 4};

 结构化绑定

结构化绑定提供了类似其他语言中提供的多返回值的功能。在容器一章中,我们会学到 C++11 新增了 std::tuple 容器用于构造一个元组,进而囊括多个返回值。但缺陷是,C++11/14 并没有提供一种简单的方法直接从元组中拿到并定义元组中的元素,尽管我们可以使用 std::tie 对元组进行拆包,但我们依然必须非常清楚这个元组包含多少个对象,各个对象是什么类型,非常麻烦。

C++17 完善了这一设定,给出的结构化绑定可以让我们写出这样的代码:

#include <iostream>
#include <tuple>std::tuple<int, double, std::string> f() {return std::make_tuple(1, 2.3, "456");
}int main() {auto [x, y, z] = f();std::cout << x << ", " << y << ", " << z << std::endl;return 0;
}

类型推导 

在传统 C 和 C++ 中,参数的类型都必须明确定义,这其实对我们快速进行编码没有任何帮助,尤其是当我们面对一大堆复杂的模板类型时,必须明确的指出变量的类型才能进行后续的编码,这不仅拖慢我们的开发效率,也让代码变得又臭又长。

C++11 引入了 autodecltype 这两个关键字实现了类型推导,让编译器来操心变量的类型。这使得 C++ 也具有了和其他现代编程语言一样,某种意义上提供了无需操心变量类型的使用习惯。

auto

auto 在很早以前就已经进入了 C++,但是他始终作为一个存储类型的指示符存在,与 register 并存。在传统 C++ 中,如果一个变量没有声明为 register 变量,将自动被视为一个 auto 变量。而随着 register 被弃用(在 C++17 中作为保留关键字,以后使用,目前不具备实际意义),对 auto 的语义变更也就非常自然了。

使用 auto 进行类型推导的一个最为常见而且显著的例子就是迭代器。你应该在前面的小节里看到了传统 C++ 中冗长的迭代写法:

// 在 C++11 之前
// 由于 cbegin() 将返回 vector<int>::const_iterator
// 所以 it 也应该是 vector<int>::const_iterator 类型
for(vector<int>::const_iterator it = vec.cbegin(); it != vec.cend(); ++it)

而有了 auto 之后可以:

#include <initializer_list>
#include <vector>
#include <iostream>class MagicFoo {
public:std::vector<int> vec;MagicFoo(std::initializer_list<int> list) {// 从 C++11 起, 使用 auto 关键字进行类型推导for (auto it = list.begin(); it != list.end(); ++it) {vec.push_back(*it);}}
};
int main() {MagicFoo magicFoo = {1, 2, 3, 4, 5};std::cout << "magicFoo: ";for (auto it = magicFoo.vec.begin(); it != magicFoo.vec.end(); ++it) {std::cout << *it << ", ";}std::cout << std::endl;return 0;
}

一些其他的常见用法:

auto i = 5;              // i 被推导为 int
auto arr = new auto(10); // arr 被推导为 int *

 从 C++ 14 起,auto 能用于 lambda 表达式中的函数传参,而 C++ 20 起该功能推广到了一般的函数。考虑下面的例子:

auto add14 = [](auto x, auto y) -> int {return x+y;
}int add20(auto x, auto y) {return x+y;
}auto i = 5; // type int
auto j = 6; // type int
std::cout << add14(i, j) << std::endl;
std::cout << add20(i, j) << std::endl;

注意auto 还不能用于推导数组类型:

auto auto_arr2[10] = {arr}; // 错误, 无法推导数组元素类型2.6.auto.cpp:30:19: error: 'auto_arr2' declared as array of 'auto'
auto auto_arr2[10] = {arr};

decltype

decltype 关键字是为了解决 auto 关键字只能对变量进行类型推导的缺陷而出现的。它的用法和 typeof 很相似:

decltype(表达式)

有时候,我们可能需要计算某个表达式的类型,例如:

auto x = 1;
auto y = 2;
decltype(x+y) z;

你已经在前面的例子中看到 decltype 用于推断类型的用法,下面这个例子就是判断上面的变量 x, y, z 是否是同一类型:

if (std::is_same<decltype(x), int>::value)std::cout << "type x == int" << std::endl;
if (std::is_same<decltype(x), float>::value)std::cout << "type x == float" << std::endl;
if (std::is_same<decltype(x), decltype(z)>::value)std::cout << "type z == type x" << std::endl;

其中,std::is_same<T, U> 用于判断 TU 这两个类型是否相等。输出结果为:

type x == int
type z == type x

你可能会思考, auto 能不能用于推导函数的返回类型呢?还是考虑一个加法函数的例子,在传统 C++ 中我们必须这么写:

template<typename R, typename T, typename U>
R add(T x, U y) {return x+y;
}

注意:typename 和 class 在模板参数列表中没有区别,在 typename 这个关键字出现之前,都是使用 class 来定义模板参数的。但在模板中定义有嵌套依赖类型的变量时,需要用 typename 消除歧义

这样的代码其实变得很丑陋,因为程序员在使用这个模板函数的时候,必须明确指出返回类型。但事实上我们并不知道 add() 这个函数会做什么样的操作,以及获得一个什么样的返回类型。

在 C++11 中这个问题得到解决。虽然你可能马上会反应出来使用 decltype 推导 x+y 的类型,写出这样的代码:

decltype(x+y) add(T x, U y)

但事实上这样的写法并不能通过编译。这是因为在编译器读到 decltype(x+y) 时,xy 尚未被定义。为了解决这个问题,C++11 还引入了一个叫做尾返回类型(trailing return type),利用 auto 关键字将返回类型后置:

template<typename T, typename U>
auto add2(T x, U y) -> decltype(x+y){return x + y;
}

令人欣慰的是从 C++14 开始是可以直接让普通函数具备返回值推导,因此下面的写法变得合法:

template<typename T, typename U>
auto add3(T x, U y){return x + y;
}

可以检查一下类型推导是否正确:

// after c++11
auto w = add2<int, double>(1, 2.0);
if (std::is_same<decltype(w), double>::value) {std::cout << "w is double: ";
}
std::cout << w << std::endl;// after c++14
auto q = add3<double, int>(1.0, 2);
std::cout << "q: " << q << std::endl;

decltype(auto)

decltype(auto) 是 C++14 开始提供的一个略微复杂的用法。

要理解它你需要知道 C++ 中参数转发的概念,我们会在语言运行时强化一章中详细介绍,你可以到时再回来看这一小节的内容。

简单来说,decltype(auto) 主要用于对转发函数或封装的返回类型进行推导,它使我们无需显式的指定 decltype 的参数表达式。考虑看下面的例子,当我们需要对下面两个函数进行封装时:

std::string  lookup1();
std::string& lookup2();

在 C++11 中,封装实现是如下形式:

std::string look_up_a_string_1() {return lookup1();
}
std::string& look_up_a_string_2() {return lookup2();
}

 而有了 decltype(auto),我们可以让编译器完成这一件烦人的参数转发:

decltype(auto) look_up_a_string_1() {return lookup1();
}
decltype(auto) look_up_a_string_2() {return lookup2();
}

尾返回类型推导

你可能会思考, auto 能不能用于推导函数的返回类型呢?还是考虑一个加法函数的例子,在传统 C++ 中我们必须这么写:

template<typename R, typename T, typename U>
R add(T x, U y) {return x+y;
}

注意:typename 和 class 在模板参数列表中没有区别,在 typename 这个关键字出现之前,都是使用 class 来定义模板参数的。但在模板中定义有嵌套依赖类型的变量时,需要用 typename 消除歧义

这样的代码其实变得很丑陋,因为程序员在使用这个模板函数的时候,必须明确指出返回类型。但事实上我们并不知道 add() 这个函数会做什么样的操作,以及获得一个什么样的返回类型。

在 C++11 中这个问题得到解决。虽然你可能马上会反应出来使用 decltype 推导 x+y 的类型,写出这样的代码:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/bicheng/84645.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringAI Prompt提示词

基本概念 Prompts提示词 ❝ 提示词的是引导AI模型输出的输入&#xff0c;提示词的正确性直接影响模型输出的。 Message消息 Message 接口封装了 Prompt 文本内容、一组元数据属性以及称为 MessageType 的分类。Spring AI消息API&#xff1a; 其中最重要的就是角色&#xff1a; …

力扣刷题——二分查找

数组是存放在连续内存空间上的相同类型数据的集合数组下标都是从0开始的数组内存空间的地址是连续的正是因为数组在内存空间的地址是连续的&#xff0c;所以我们在删除或者增添元素的时候&#xff0c;就难免要移动其他元素的地址。 使用二分查找法返回的元素下标可能不是唯一的…

黑群晖NAS部署DeepSeek模型与内网穿透实现本地AI服务

文章目录 前言1.安装Container Manager2. 启动ssh功能3. ssh连接黑群晖4. 安装Ollama5. 安装deepseek模型6. 安装open-webui图形界面7. 安装内网穿透7.1 下载cpolar套件7.2 配置群辉虚拟机7.3 配置公网地址小结 7.4 配置固定公网地址 总结 前言 在追求自建网络存储方案的极客群…

Rust 学习笔记:处理任意数量的 future

Rust 学习笔记&#xff1a;处理任意数量的 future Rust 学习笔记&#xff1a;处理任意数量的 future竞争的 future将控制权交给运行时构建我们自己的异步抽象 Rust 学习笔记&#xff1a;处理任意数量的 future 当两个 future 切换到三个 future 时&#xff0c;我们也必须从使用…

2025年TCP洪水攻击防护实战全解析:从协议对抗到AI智能防御

一、2025年TCP洪水攻击的新特征与挑战 AI驱动的自适应攻击 攻击者利用生成式AI动态调整SYN报文特征&#xff08;如载荷内容、发送频率&#xff09;&#xff0c;使攻击流量与正常业务流量的差异率低至0.5%&#xff0c;传统基于规则引擎的防御策略完全失效。 混合协议打击常态化…

二、集成开发环境(IDE)

上节我们在终端演示了python虚拟环境的用法&#xff0c;但终端不方便代码编写和调试&#xff0c;本节介绍两种常用的python集成开发环境&#xff1a;Pycharm和Jupter Notebook。Pycharm是最流行的Python IDE&#xff0c;下载网址&#xff1a;下载 PyCharm&#xff1a;JetBrains…

芯片电感需求趋势及选型关键因素

随着AI产业的快速发展&#xff0c;数据中心、AI芯片、服务器等算力基础设施对于芯片电感等电子元件的要求不断提升。另一方面&#xff0c;电子产品向高功率密度和小型化方向发展&#xff0c;电源模块趋于小型化、低电压、大电流&#xff0c;也对芯片电感提出了小型化、轻量化、…

Vue3+Element Plus表单验证实战:从零实现用户管理

前言 在Vue3项目开发中&#xff0c;表单验证是保证数据完整性和准确性的重要环节。Element Plus作为Vue3的流行UI组件库&#xff0c;提供了强大的表单验证功能。本文将基于一个用户管理模块的实战案例&#xff0c;详细介绍Vue3中如何使用Element Plus实现完整的表单验证流程。…

力扣上C语言编程题:合并区间(涉及数组)

一. 简介 本文记录力扣网上涉及数组方面的编程题&#xff0c;主要以 C语言实现。 二. 力扣上C语言编程题&#xff1a;合并区间&#xff08;涉及数组&#xff09; 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所…

SEO长尾词与关键词优化实战

内容概要 在SEO优化体系中&#xff0c;核心关键词与长尾词的协同作用直接影响流量获取效率与用户触达精度。本文将从基础概念切入&#xff0c;系统梳理核心关键词的筛选标准与竞争强度评估方法&#xff0c;并深入探讨长尾词在细分场景下的价值定位。通过分析用户搜索行为与意图…

博图SCL语言教程:灵活加、减计数制作自己的增减计数器(CTUD)

博图SCL语言教程&#xff1a;使用SCL实现增减计数器(CTUD) 一、什么是增减计数器(CTUD)&#xff1f; 增减计数器&#xff08;Up-Down Counter&#xff09;是PLC编程中的基础功能块&#xff0c;具有以下特性&#xff1a; CU (Count Up)&#xff1a;上升沿触发计数值增加 CD (…

Android 应用被kill问题排查和处理

一、背景 博主有一款应用市场应用,同样的应用,在Android 10上开启三个下载正常,在Android 14上开启下载安装,很频繁被kill。首先想到的是,是不是应用内存太高,导致被kill,通过工具分析内存也不高,后面就想到是不是系统本身分配给应用的内存就不高,后来通过排查,确实是和系统的…

从代码学习深度强化学习 - 多臂老虎机 PyTorch版

文章目录 前言创建多臂老虎机环境多臂老虎机算法基本框架(基类)1. ε-贪心算法 (Epsilon-Greedy)2. 随时间衰减的ε-贪婪算法 (Decaying ε-Greedy)3. 上置信界算法 (Upper Confidence Bound, UCB)4. 汤普森采样算法 (Thompson Sampling)总结前言 欢迎来到“从代码学习深度强化…

Android学习之Window窗口

Android Window机制学习笔记 在使用Window Flag实现界面全屏功能时&#xff0c;发现自身对Android Window机制缺乏系统认知&#xff0c;因此进行了专项学习与整理。 本文主要参考以下优质资料&#xff1a; Android的Window详解Android官方Window文档 Window基本概念 1. Win…

华为云 Flexus+DeepSeek 征文|搭建部署Dify-LLM推理引擎,赋能AI Agent智能体实现动态联网搜索能力

华为云 Flexus 云服务器 X 实例专门为 AI 应用场景设计。它提供了强大的计算能力&#xff0c;能够满足 DeepSeek 模型以及后续搭建 AI Agent 智能体过程中对于数据处理和模型运行的高要求。在网络方面&#xff0c;具备高速稳定的网络带宽&#xff0c;这对于需要频繁联网搜索信息…

Python 100个常用函数全面解析

Python 100个常用函数全面解析 1. 类型转换函数 1.1 int() 将字符串或数字转换为整数。 # 基本用法 int(123) # 123 int(3.14) # 3# 指定进制转换 int(1010, 2) # 10 (二进制转十进制) int(FF, 16) # 255 (十六进制转十进制)# 临界值处理 int() # ValueError: …

分享在日常开发中常用的ES6知识点【面试常考】

前言 在日常的业务开发中&#xff0c;可以熟悉运用掌握的知识点快速解决问题很重要。这篇分享JS相关的知识点&#xff0c;主要就是对数据的处理。 注意&#xff1a;本篇分享的知识点&#xff0c;只是起到一个抛砖引玉的作用&#xff0c;详情的使用和更多的ES6知识点还请参考官…

CHI协议验证中的异常及边界验证

CHI协议验证中的异常及边界验证 针对 CHI 协议的错误注入工具、覆盖率衡量方法及实际项目中的投入平衡 CHI 协议作为多核系统中复杂的缓存一致性协议,验证其行为需要强大的工具和方法来执行错误注入和边界条件测试,并衡量测试覆盖率。以下详细讨论常用工具、覆盖率评估方法及…

技术专栏|LLaMA家族——模型架构

LLaMA的模型架构与GPT相同&#xff0c;采用了Transformer中的因果解码器结构&#xff0c;并在此基础上进行了多项关键改进&#xff0c;以提升训练稳定性和模型性能。LLaMA的核心架构如图 3.14 所示&#xff0c;融合了后续提出的多种优化方法&#xff0c;这些方法也在其他模型&a…

电脑插入多块移动硬盘后经常出现卡顿和蓝屏

当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时&#xff0c;可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案&#xff1a; 1. 检查电源供电问题 问题原因&#xff1a;多块移动硬盘同时运行可能导致USB接口供电不足&#x…