C++ 的 pair 和 tuple

news2025/1/11 5:59:55

1 std::pair

1.1 C++ 98 的 std::pair

1.1.1 std::pair 的构造

​ C++ 的二元组 std::pair<> 在 C++ 98 标准中就存在了,其定义如下:

template<class T1, class T2> struct pair;

std::pair<> 是个类模板,它有两个成员:first 和 second,类型分别是模板参数指定的 T1 和 T2。可以用以下几种方法构造 std::pair<> 类型的变量(对象实例):

std::pair<int, double> ap1;  //默认的构造函数
std::pair<int, double> ap2(5,2.8); 
std::pair<int, double> ap3(ap2); //拷贝构造函数
ap1 = std::make_pair(6, 7.2);  //使用 make_pair函数
std::pair<int, std::string> p4{5, "ak47"};  //C++ 11 初始化列表
std::pair ap5(5, "ak47");  //C++ 17 推断指示语法,将在 1.3 节介绍

1.1.2 赋值与转换

对 std::pair<> 的访问也很简单,直接操作它的两个成员:

std::pair<int, double> ap(5,2.8); 
std::cout << ap.first << ", " << ap.second;
ap.first = 42;

如果你的数据中有两个数据耦合比较紧密,经常需要在一起成对出现,而你又不想额外定义一个 struct 的时候,可以考虑使用 std::pair<>。另外,std::pair<> 也可用于函数返回值的类型,这样就可以用一个 return 语句返回两个值。

​ 对于 C++ 来说,std::pair<char, int> 与 std::pair<int, char> 是两个完全不同的类型,它们之间的差别就像 std::string 和 std::vector 的差别一样大。一般来说,两个不同类型的 std::pair<> 变量是不能互相赋值的,但是如果两个 std::pair<> 变量对应的 first 和 second 属性能够对应进行隐式类型转换,则这样的赋值是允许的,比如:

std::pair<char, int> p1('A', 4);
std::pair<int, double> p2 = p1; //OK,隐式转换char -> int, int -> double

除了 C++ 内建的隐式转换,通过自定义构造函数进行的隐式转换也是可以的,比如:

struct FooTest {
    FooTest(int a)
    { value = std::format("{}", a);  }

    std::string value;
};

std::pair<char, int> p1('A', 4);
std::pair<int, FooTest> p2 = p1; //OK, FooTest(int a) 构造函数完成隐式转换

1.1.3 比较

​ 两个 std::pair<> 变量可以互相比较大小,比较的原则就是先比较 first 属性,如果 first 属性的值相等(按照严格弱序比较)则继续比较 second 属性的值,来看个比较的例子:

std::pair<int, std::string> p1(5, "ak47");
std::pair<int, std::string> p2(5, "ak57");

assert(p1 < p2); //5==5,但是 "ak47" < "ak57"

1.2 C++ 11 和 C++ 14 的改进

​ C++ 11 对 std::pair<> 进行了一些扩展,增加了一个成员函数 swap(),用于和另一个同类型的 std::pair<> 变量交换内容,比如:

std::pair<int, std::string> p1(42, "Hello");
std::pair<int, std::string> p2;
p2.swap(p1);
assert(p2.first == 42);
assert(p1.first == 0);

和其他类型一样,C++ 11 全局的 std::swap() 函数也支持 std::pair<> ,上面的交换代码也可以这样写:

std::swap(p1, p2);

​ C++ 11 提供的 std::get<> 函数也支持 std::pair<>,可以通过索引(0 或 1)获取一个 std::pair<> 变量的内容,C++ 14 又进行了补充,即可以根据类型匹配获取一个 std::pair<> 变量的内容,比如:

std::pair<int, std::string> p1(42, "Hello");

assert(std::get<0>(p1) == std::get<int>(p1));
assert(std::get<1>(p1) == std::get<std::string>(p1));

需要注意,类型匹配的方式只适用于两个不同类型的数据组成的 pair。

​ 此外,一些用于 tuple 类型的操作也可以用于 std::pair<>,比如在编译期获取 std::tuple<> 类型中元素个数的 std::tuple_size,还有在编译期获取 std::tuple<> 类型中每个位置的元素类型的 std::tuple_element<N,T> 等等。对于 std::pair<> 来说,std::tuple_size 得到的值固定是 2,来看个例子:

std::cout << std::tuple_size<std::pair<int, std::string>>::value;  //输出 2

std::tuple_element<0, std::pair<int, double>>::type a; //变量 a 的类型是 int

这两个方法配合,可以在编译期决断一些事情,比如这个例子:

template<class T>
void Test(const T& t) {
    int a[std::tuple_size<T>::value] = { 0 };  //定义数组
    typename std::tuple_element<0, T>::type myValue;
    myValue = t.first;
}

std::pair<int, std::string> p1(5, "ak47");
Test(p1);  //此时 myValue 是 int 类型
Test(std::make_pair('A', 4));   //此时 myValue 是 char 类型

1.3 C++ 17 的推断指引

​ C++ 17 引入了推断指引(Deduction Guides)语法,当然,std::pair<> 也支持推断指引。没有推断指引的时候,构造一个 std::pair <>的对象实例需要指定具体的类型,也就是 std::pair<> 的两个模板参数,就是这样:

std::pair<int, std::string> p1(5, "ak47");

有了推断指引语法之后,代码就可以简化成这个样子:

std::pair p1(5, "ak47");

因为编译器能够从构造 p1 的两个参数中推断出它们的类型,所以就不需要显示指定具体的类型了。推断指引是个好东西,能少敲几次键盘,节省体力。

2 std::tuple

​ std::tuple 元组是 C++ 11 提供的标准库扩展,利用扩展的参数包语法,std::tuple 实现了对任意个数的非同质元素的聚合。元组是个好东西,有了它可以代替很多琐碎的、毫无价值的传统数据结构(struct)定义。同时,它还支持右值和移动语义,作为参数或返回值传递的时候,比某些构造不良的 struct 具有更好的效率。

2.1 std::tuple 的语法

2.1.1 std::tuple 的构造

​ std::tuple<> 是个模板类型,其定义如下:

template< class... Types >
class tuple;

class… Types 是参数包语法,Types 就是具体的类型列表。构造 std::tuple<> 对象实例可以借助于构造函数,也可以使用 std::make_tuple() 方法:

std::tuple<int, std::string, double> t1; //默认构造函数
std::tuple<int, std::string, double> t2 = {42, "hello", 2.7};
std::tuple<int, std::string, double> t3{ 42, "hello", 2.7 }; // C++ 11 初始化列表
std::tuple<int, std::string, double> t4(42, "hello", 2.7); //拷贝构造
t1 = t4; 
std::tuple<int, std::string, double> t5 = std::make_tuple(42, "hello", 2.7); //右值拷贝构造(move)
auto t5 = std::make_tuple(42, "hello", 2.7);  //等价于上一行
std::tuple t6(42, "hello", 2.7);  //C++ 17 的推断指示语法,将在 2.2 节介绍

​ 元组中可以使用引用类型,在构造元组的时候指定引用绑定的对象即可,绑定引用对象时可以使用 std::ref,也可以不使用:

int value = 3;
std::tuple<int&, std::string, double> t9(std::ref(value), "hello", 2.7);
//std::tuple<int&, std::string, double> t9(value, "hello", 2.7); 效果一样
std::get<0>(t9) = 4;
std::cout << "value=" << value << ", t9[0]=" << std::get<0>(t9) << std::endl;  //4,4

​ 需要注意的是,尽管一些过时的资料中提到 std::tuple<> 采用链式结构存放每个元素的值,但是实际情况并不是这样的。无论 GCC 还是 Visual C++,对元组的存储都是在内存中连续存放的,并且每个同类型的元组使用的内存大小是一样的。以 Visual C++ 为例,元组变量在内存中按照类型列表的倒序方式连续存储在一个内存块中,当然,如果一个对象中使用了指针属性,元组只存储这个对象的内容(包含指针),对象指针属性指向的内容则由对象自己负责存储和释放。

2.1.2 赋值和转换

​ std::tuple<> 的内部实现是借助于模板的递归推导机制做的,所以无法像 std::pair<> 那样提供成员属性用于访问元组内的各个元素,但是可以借助于同样模板化的 std::get() 方法访问和修改各个元素的值。来看下面的代码:

std::tuple<int, std::string, double> t1(42, "hello", 2.7); 
std::cout << std::get<0>(t1);  //输出 42
std::get<1>(t1) = "NiHao";
std::cout << std::get<1>(t1);  //输出 NiHao

注意,std::get() 中的模板参数 N 不支持动态绑定,即这样写代码是无法编译的:

std::tuple<int, std::string, double> t1(42, "hello", 2.7); 

for (int i = 0; i < 3; i++)
    std::cout << i + 1 << ": " << std::get<i>(t1) << std::endl;  // 编译错误

当然,可以使用 std::tuple_size 和 std::tuple_element<N,T> 在编译期获得元素的个数和元组各个元素的类型:

// 以下两行代码等价,都输出 3
std::cout << std::tuple_size<std::tuple<int, std::string, double>>::value << std::endl;
std::cout << std::tuple_size<std::tuple<int, std::string, double>>() << std::endl;

std::tuple<int, std::string, double> t1(42, "hello", 2.7);
std::cout << std::tuple_size<decltype(t1)>::value << std::endl; //使用 decltype

std::tuple_element<2, std::tuple_size<std::tuple<int, std::string, double>>::type a; //double 类型
std::tuple_element<2, std::tuple_size<decltype(t1)>::type b; //double 类型

std::tuple<> 同样提供了 swap() 方法用于和另一个同类型(或可隐式转换)的 std::tuple<> 对象实例交换内容,当然全局的 std::swap() 方法也支持 std::tuple<>:

std::tuple<int, std::string, double> t1;
std::tuple<int, std::string, double> t2 = {42, "hello", 2.7};

t1.swap(t2);  //效果与 std::swap(t1, t2); 一样

​ 一般来说,两个不同类型的元组变量是不可以赋值的,但是如果对应位置的元素类型可以隐式转换,那么赋值是可以接受的,比如:

std::tuple<char, double> t16('A', 2.7);
std::tuple<double, std::string> t17 = t16;  //错误,无法赋值
std::tuple<int, double> t17 = t16; //OK, char 可以隐式转换成 int

如果元组中的元素类型支持通过构造函数隐式转换,赋值也是可以的,请参考 1.1.2 节 FooTest 的例子,这里不再赘述。

2.1.3 tie 和 ignore

​ 除了使用 std::get() 访问元素的元素,还可以使用 std::tie() 方法将元组内的元素与某个具名的变量关联,将元组的内容传递给具名变量。来看个例子:

std::tuple<int, std::string, double> t1(3, "Kitty", 2.7); 

int age;
std::string name;
double weight;
std::tie(age, name, weight) = t1;

std::cout << "Name: " << name << ", Age: " << age << ", Weight: " << weight << " Kg(s)" << std::endl;

age = 10;  //修改 age 的值不影响 t1

显然,使用具名变量可以提高代码的可读性,毕竟,一个有具体名字的变量比生冷的 std::get<0> 要强多了。但是需要注意,std::tie() 的捆绑效果是单向的,并且是一次性的,std::tie() 之后再修改具名变量的值不会影响关联的元组的值。

​ 如果关联到的时候对某个元素不感兴趣,可以使用 std::ignore 占位符,比如:

std::tuple<int, std::string, double> t1(3, "Kitty", 2.7); 

int age;
double weight;
std::tie(age, std::ignore, weight) = t1; //只关心年龄和体重,不关心名字

2.1.4 拼接元组

​ 可以使用 std::tuple_cat() 拼接两个元组变量,得到一个更大的元组,看看这个例子:

std::tuple<int, std::string, double> t1(3, "Kitty", 2.7); 

auto t2 = std::tuple_cat(t1, std::make_tuple("Garfield", "United Kingdom"));

assert(std::tuple_size<decltype(t2)>::value == 5);

拼接后 t2 有五个元素,分别是 (3, “Kitty”, 2.7, “Garfield”, “United Kingdom”)。

2.1.5 std::forward_as_tuple()

​ 2.1.1 节提到了在定义 std::tuple<> 的时候可以使用左值引用类型的元组元素,既然能使用左值引用,当然也可以使用右值引用类型。std::forward_as_tuple() 的作用是返回一个 std::tuple<> 对象,其元素类型是给定的函数参数类型对应的右值引用类型。这句话有点难以理解,用这行代码做例子来理解这个函数:

std::tuple<int&&, FooTest&&> k = std::forward_as_tuple(42, FooTest(5));

当我们传递两个值给 std::forward_as_tuple() 方法时,它的返回值类型是对应的 std::tuple<int&&, FooTest&&>。这个方法存在意义是什么呢?当然是为了参数传递的效率。我们用 std::make_tuple() 跟他做个对比,在对比之前,先看看 FooTest 的实现,我们增加了很多打印信息跟踪这个对象实例的构造和销毁:

struct FooTest {
    FooTest(const FooTest& f)
    {   std::cout << "FooTest(const FooTest&)" << std::endl; }
    FooTest(FooTest&& f)
    {   std::cout << "FooTest(FooTest&&)" << std::endl; }
    FooTest()
    {   std::cout << "FooTest()" << std::endl; }
    FooTest(int a)
    {   std::cout << "FooTest(int)" << std::endl; }
    ~FooTest()
    {   std::cout << "~FooTest()" << std::endl; }
};

先来看看 std::make_tuple() 的执行情况,对于这行代码:

auto kk = std::make_tuple(42, FooTest(5));

打印输出结果如下,执行了两次对象的构造和销毁,其中一次右值构造是因为构造函数返回时产生了一个将亡值临时对象:

FooTest(int)
FooTest(FooTest&&)
~FooTest()
~FooTest()

好了,现在看看 std::forward_as_tuple() 是什么情况,同样的代码:

auto kk = std::forward_as_tuple(42, FooTest(5));

对应的打印结果是:

FooTest(int)
~FooTest()

看到了吗?只在 FooTest(5) 调用时产生了一次 FooTest 对象实例的构造,随后这个对象实例被转发出来,最后随着 kk 销毁的时候一起销毁。现在明白这个方法为什么叫 forward_as_tuple() 了吧?因为它的作用和 std::forward() 类似,具有相同的语意。

​ 由此可见,C++ 对效率的追求到了近乎偏执的地步。类似的右值转发对效率提升是非常显著的,如果有恰当设计的函数配合,右值对象可以“一镜到底”:

void print_Tuple(std::tuple<int&&, FooTest&&> pack)
{ std::cout << std::get<0>(pack) << std::endl; }

print_Tuple(std::forward_as_tuple(42, FooTest(5)));

输出结果是:

FooTest(int)
42
~FooTest()

你想到了吗?

2.2 C++ 17 的改进

2.2.1 推断指引

​ std::tuple<> 也支持推断指引,像这样繁琐的代码:

std::tuple<int, std::string, double, std::string, std::string> t10(3, "Kitty", 2.7, "Garfield", "United Kingdom"); 

可以简化为:

std::tuple t1(3, "Kitty", 2.7, "Garfield", "United Kingdom");

你只负责想象,剩下的交给编译器。

2.2.2 结构化绑定

​ 使用 std::tie() 可以将元组内的元素关联到一些具名变量上,提高代码的可读性,但是 std::tie() 的使用并不友好,变量需要提前定义好,写代码很繁琐。C++ 17 引入的结构化绑定语法也适用于 std::tuple<>,使用结构化绑定可以简化代码的实现,2.1.3 节的例子可以这样简单地实现:

std::tuple<int, std::string, double> t1(3, "Kitty", 2.7); 

auto [age, name, weight] = t1;  //无需事先声明 age, name 和 weight

前面提到过,std::tie() 关联具名变量是单向的一次性动作,结构化绑定虽然也是一次性动作,但是可以通过引用绑定方式修改被关联对象实例的值,比如:

std::tuple<int, std::string, double> t1(3, "Kitty", 2.7); 

auto& [age, name, weight] = t1; //引用绑定
age = 4;  //同时修改了 t1 的值
assert(std::get<0>(t1) == 4);

除此之外,std::tie() 还有一个局限,那就是它只能用于关联到一个左值类型对象实例,不能用于右值,但是结构化绑定可以,来看一个函数返回值的例子:

std::tuple<int, std::string, double> GetInfo(const std::string& name) {
    return std::make_tuple(42, "Simon", 108.2);
}

int age;
std::string name;
double weight;
std::tie<age, name, weight> = GetInfo("Kitty");  //错误,函数返回值不是左值

auto [aa, nn, ww] = GetInfo("Kitty");  //OK,结构化绑定可以

使用结构化绑定的结果就是 aa、nn 和 ww 分别是对应类型的右值引用类型,没有任何临时对象拷贝的开销,非常 nice。

关注作者的算法专栏
https://blog.csdn.net/orbit/category_10400723.html

关注作者的出版物《算法的乐趣(第二版)》
https://www.ituring.com.cn/book/3180

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

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

相关文章

docker(目录挂载、卷映射)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、目录挂载1.命令2.案例3.补充 二、卷映射1.命令2.案例 总结 前言 在使用docker部署时&#xff0c;我们如果要改变一些配置项目&#xff0c;不可能每次都进入…

opencv warpAffine仿射变换C++源码分析

基于opencv 3.1.0源代码 sources\modules\imgproc\src\imgwarp.cpp void cv::warpAffine( InputArray _src, OutputArray _dst,InputArray _M0, Size dsize,int flags, int borderType, const Scalar& borderValue ) {...if( !(flags & WARP_INVERSE_MAP) ){//变换矩阵…

使用 IntelliJ IDEA 创建简单的 Java Web 项目

以下是使用 IntelliJ IDEA 创建几个简单的 Java Web 项目的步骤&#xff0c;每个项目实现基本的登录、注册和查看列表功能&#xff0c;依赖 Servlet/JSP 和基本的 Java Web 开发。 前置准备 确保安装了 IntelliJ IDEA Ultimate&#xff08;社区版不支持 Web 应用&#xff09;。…

R语言在森林生态研究中的魔法:结构、功能与稳定性分析——发现数据背后的生态故事!

森林生态系统结构、功能与稳定性分析与可视化研究具有多方面的重要意义&#xff0c;具体如下&#xff1a; 一、理论意义 ●深化生态学理论 通过研究森林生态系统的结构、功能与稳定性&#xff0c;可以深化对生态系统基本理论的理解。例如&#xff0c;生物多样性与生态系统稳定性…

QML states和transitions的使用

一、介绍 1、states Qml states是指在Qml中定义的一组状态&#xff08;States&#xff09;&#xff0c;用于管理UI元素的状态转换和属性变化。每个状态都包含一组属性值的集合&#xff0c;并且可以在不同的状态间进行切换。 通过定义不同的状态&#xff0c;可以在不同的应用场…

Git:Cherry-Pick 的使用场景及使用流程

前面我们说了 Git合并、解决冲突、强行回退等解决方案 >> 点击查看 这里再说一下 Cherry-Pick功能&#xff0c;Cherry-Pick不是merge&#xff0c;只是把部分功能代码Cherry-Pick到远程的目标分支 git cherry-pick功能简介&#xff1a; git cherry-pick 是用来从一个分…

【SpringAOP】Spring AOP 底层逻辑:切点表达式与原理简明阐述

前言 &#x1f31f;&#x1f31f;本期讲解关于spring aop的切面表达式和自身实现原理介绍~~~ &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 &#x1f525; 你的点赞就是小编不断更新的最大动力 &am…

python基础和redis

1. Map函数 2. filter函数 numbers generate_numbers() filtered_numbers filter(lambda x: x % 2 0, numbers) for _ in range(5):print(next(filtered_numbers)) # 输出: 0 2 4 6 83. filter map 和 reduce 4. picking and unpicking 5. python 没有函数的重载&#xff0…

python-42-使用selenium-wire爬取微信公众号下的所有文章列表

文章目录 1 seleniumwire1.1 selenium-wire简介1.2 获取请求和响应信息2 操作2.1 自动获取token和cookie和agent2.3 获取所有清单3 异常解决3.1 请求url失败的问题3.2 访问链接不安全的问题4 参考附录1 seleniumwire Selenium WebDriver本身并不直接提供获取HTTP请求头(header…

Windows安装ES单机版设置密码

下载ES ES下载链接 我用的是7.17.26 启动前配置 解压之后打开D:\software\elasticsearch-7.17.26\bin\elasticsearch-env.bat 在elasticsearch-env.bat文件中修改jdk的路径 修改前 修改内容 if defined ES_JAVA_HOME (set JAVA"D:\software\elasticsearch-7.17.26\…

Java并发编程面试题:内存模型(6题)

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

标准应用 | 2025年网络安全服务成本度量实施参考

01 网络安全服务成本度量依据相关新变化 为了解决我国网络安全服务产业发展中面临的服务供需两方对于服务成本组成认知偏差较大、网络安全服务成本度量缺乏依据的问题&#xff0c;中国网络安全产业联盟&#xff08;CCIA&#xff09;组织北京赛西科技发展有限责任公司、北京安…

太速科技-FMC141-四路 250Msps 16bits AD FMC子卡

FMC141-四路 250Msps 16bits AD FMC子卡 一、产品概述&#xff1a; 本板卡基于 FMC 标准板卡&#xff0c;实现 4 路 16-bit/250Msps ADC 功能。遵循 VITA 57 标准&#xff0c;板卡可以直接与xilinx公司或者本公司 FPGA 载板连接使用。板卡 ADC 器件采用 ADI 公司 AD9467 芯…

通义灵码在跨领域应用拓展之物联网篇

目录 一.引言 二.通义灵码简介 三.通义灵码在物联网领域的设备端应用 1.传感器数据采集 (1).不同类型传感器的数据读取 (2).数据转换与预处理 2.设备控制指令接收和执行 (1).指令解析与处理 (2).设备动作执行 四.通义灵码在物联网领域的云端平台应用 1.数据存储和管…

使用Kubernetes部署Spring Boot项目

目录 前提条件 新建Spring Boot项目并编写一个接口 新建Maven工程 导入 Spring Boot 相关的依赖 启动项目 编写Controller 测试接口 构建镜像 打jar包 新建Dockerfile文件 Linux目录准备 上传Dockerfile和target目录到Linux 制作镜像 查看镜像 测试镜像 上传镜…

C#基础之 继承类相关构造函数使用

类构造函数 作用是为 类中成员变量进行赋值操作 单个类的时候 一般不会有什么思路问题&#xff0c;主要说明一下 有继承关系类的时候 当存在继承关系的类 如 A&#xff1a;B 首先要注意第一点&#xff1a;顺序 那么在构造函数时 顺序是由 B先构造 然后 A在构造 注意第二点方法…

【leetcode刷题】:双指针篇(有效三角形的个数、和为s的两个数)

文章目录 一、有效三角形的个数题目解析算法原理代码编写 二、和为s的两个数题目解析算法原理代码编写 一、有效三角形的个数 题目解析 有效三角形的个数【点击跳转】 题目意思很好理解&#xff1a;就是在一堆非负整数的数组里&#xff0c;随机选三个数进行搭配&#xff0c;…

【Unity3D】apk加密(global-metadata.dat加密)

涉及&#xff1a;apk、aab、global-metadata.dat、jks密钥文件、APKTool、zipalign 使用7z打开apk文件观察发现有如下3个针对加密的文件。 xxx.apk\assets\bin\Data\Managed\Metadata\global-metadata.dat xxx.apk\lib\armeabi-v7a\libil2cpp.so xxx.apk\lib\arm64-v8a\libil…

机器学习之贝叶斯分类器和混淆矩阵可视化

贝叶斯分类器 目录 贝叶斯分类器1 贝叶斯分类器1.1 概念1.2算法理解1.3 算法导入1.4 函数 2 混淆矩阵可视化2.1 概念2.2 理解2.3 函数导入2.4 函数及参数2.5 绘制函数 3 实际预测3.1 数据及理解3.2 代码测试 1 贝叶斯分类器 1.1 概念 贝叶斯分类器是基于贝叶斯定理构建的分类…

前端报告 2024:全新数据,深度解析未来趋势

温馨提示: 此报告为国际版全球报告,其中所涉及的技术应用、工具偏好、开发者习惯等情况反映的是全球前端开发领域的综合态势。由于国内外技术发展环境、行业生态以及企业需求等存在差异,可能有些内容并不完全契合国内的实际情况,请大家理性阅读,批判性地吸收其中的观点与信…