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

C++学习之STL学习

        在经过前面的简单的C++入门语法的学习后,我们开始接触C++最重要的组成部分之一:STL

目录

STL的介绍

        什么是STL

        STL的历史

UTF-8编码原理(了解)

UTF-8编码原理

核心编码规则

规则解析

编码步骤示例

1. 确定码点范围

2. 转换为二进制

3. 按格式填充数据位

4. 合并字节序列

UTF-8编码示例表

UTF-8的核心优势

与其他编码的对比

实际应用场景

string类

        为什么要学习string类?

        标准库中的string类

        string类(了解)

        auto和范围for

        auto关键字

范围for 

        string类的常用接口说明

         string类对象的容量操作

string的遍历

一、[]运算符重载介绍

二、迭代器

三、范围for (C++11)

string类对象的修改操作

string类非成员函数

迭代器的介绍

迭代器的概念与分类

string中迭代器的获取方式

迭代器的基本操作

涉及迭代器的string成员函数

与STL算法结合使用

迭代器失效问题

实际应用示例

迭代器的意义:

一些string类的例题

        1.仅仅反转字母

        2.查找字符串中第一个唯一的字符


STL的介绍

        什么是STL

        STL是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个保罗数据结构和算法的软件框架

        STL的历史

C++ STL版本历史发展总览

标准版本发布时间新增组件与特性关键特性与改进影响与意义
C++981998- 容器:vectorlistdequesetmap
- 算法:sortfindcopy
- 迭代器、函数对象、适配器
首次将STL纳入C++标准,确立泛型编程范式。奠定了现代C++标准库的基础,成为工业级开发的标配。
C++032003- 无新增组件技术性修订,修复C++98中的缺陷和模糊定义。提升跨平台一致性,稳定了STL的实现。
C++112011- 新容器:unordered_mapunordered_setarrayforward_list
- 智能指针:shared_ptrunique_ptr
- 移动语义支持
- 引入右值引用和移动语义
- 支持Lambda表达式
- 线程安全组件(如std::thread
现代化STL,显著提升性能和灵活性,推动多核编程。
C++142014- 泛型Lambda
std::exchangestd::quoted
优化C++11特性,增强泛型编程能力。简化代码,提升开发效率。
C++172017std::optionalstd::variantstd::string_view
std::filesystem
- 并行算法
- 文件系统标准化
- 并行执行算法(如std::sort支持多线程)
强化实用性和性能,支持现代硬件并行计算。
C++202020- 范围库(std::ranges
- 概念(Concepts)
std::spanstd::format
- 链式操作简化算法调用(如views::filter
- 约束模板参数,提升编译期类型安全。
革命性改进,代码更简洁、安全,支持函数式编程风格。
C++232023std::flat_mapstd::flat_set
std::ranges::to
- 网络库(提案中)
- 扁平化关联容器优化性能
- 网络编程接口标准化(进行中)
进一步优化数据结构和算法,扩展应用领域(如网络和嵌入式)。

各版本核心改进对比

特性分类C++98C++11C++17C++20
容器基础线性/关联容器哈希容器、固定数组类型安全联合体、文件系统范围视图、span
算法基础泛型算法支持Lambda和移动语义并行算法、随机抽样范围库链式操作
内存管理原始指针智能指针(shared_ptr内存资源管理(PMR)无重大更新
并发支持std::threadstd::mutex无重大更新无重大更新
元编程基础模板类型推导(auto结构化绑定、if constexpr概念(Concepts)
实用性工具std::functionstd::bindstd::optionalstd::anystd::formatstd::chrono

关键版本里程碑

  1. C++98:STL标准化,泛型编程成为主流。

  2. C++11:现代化改造,支持移动语义和多线程。

  3. C++17:实用工具扩展,文件系统和并行计算。

  4. C++20:范围库和概念,代码简洁性与安全性飞跃。

  5. C++23:扁平化容器和网络库,优化高性能场景。

主流STL实现对比

实现名称所属编译器特点
libstdc++GCC兼容性强,支持旧标准,广泛用于Linux系统。
libc++Clang/LLVM轻量高效,积极支持新标准(如C++20特性)。
MSVC STLMSVC深度集成Windows生态,优化调试模式,支持最新C++特性。

UTF-8编码原理(了解)

UTF-8编码原理

UTF-8(Unicode Transformation Format-8)是一种可变长度字符编码,用于将Unicode字符映射为字节序列。它兼容ASCII,支持所有Unicode字符(当前最长达21位),且无字节序(Endianness)问题,广泛应用于互联网和跨平台数据交换。


核心编码规则

UTF-8通过1到4个字节表示一个字符,编码规则如下:

Unicode码点范围(十六进制)UTF-8编码格式(二进制)字节数
U+0000 ~ U+007F0xxxxxxx1字节
U+0080 ~ U+07FF110xxxxx 10xxxxxx2字节
U+0800 ~ U+FFFF1110xxxx 10xxxxxx 10xxxxxx3字节
U+10000 ~ U+10FFFF11110xxx 10xxxxxx 10xxxxxx 10xxxxxx4字节
规则解析
  1. 首字节前缀

    • 1字节:以0开头。

    • 2字节:以110开头。

    • 3字节:以1110开头。

    • 4字节:以11110开头。

  2. 后续字节前缀:所有后续字节均以10开头。

  3. 数据位填充x表示Unicode码点的二进制位,从高位到低位依次填充。


编码步骤示例

以字符 “€”(Unicode码点 U+20AC)为例,演示UTF-8编码过程:

1. 确定码点范围
  • U+20AC 属于 U+0800 ~ U+FFFF,需 3字节 编码。

2. 转换为二进制
  • 十六进制 20AC → 二进制 0010 0000 1010 1100(共16位)。

3. 按格式填充数据位
  • 3字节模板1110xxxx 10xxxxxx 10xxxxxx

  • 将16位码点拆分填充:

    • 首字节:取高4位 0010 → 填充到 1110xxxx → 11100010

    • 第二字节:取中间6位 000010 → 填充到 10xxxxxx → 10000010

    • 第三字节:取低6位 101100 → 填充到 10xxxxxx → 10101100

4. 合并字节序列
  • 最终UTF-8编码(十六进制):E2 82 AC


UTF-8编码示例表

字符Unicode码点UTF-8编码(十六进制)二进制格式
'A'U+00414101000001
'ç'U+00E7C3 A711000011 10100111
'中'U+4E2DE4 B8 AD11100100 10111000 10101101
'😂'U+1F602F0 9F 98 8211110000 10011111 10011000 10000010

UTF-8的核心优势

  1. 兼容ASCII

    • 所有ASCII字符(0x00-0x7F)的UTF-8编码与原ASCII编码一致,旧系统可无缝兼容。

  2. 空间高效

    • 常用字符(如拉丁字母、汉字)仅需2-3字节,比UTF-16/UTF-32更节省空间。

  3. 无字节序问题

    • 字节顺序固定(Big-Endian),无需BOM(Byte Order Mark)。

  4. 容错能力强

    • 通过前缀模式可检测编码错误(如无效的后续字节)。

    • 与其他编码的对比

      编码方式特点适用场景
      UTF-8可变长度(1-4字节),兼容ASCII,无字节序问题网络传输、存储、跨平台
      UTF-16固定2或4字节,存在大端(BE)和小端(LE)问题,需BOM标记Windows系统、部分旧协议
      UTF-32固定4字节,直接映射码点,空间占用大内存处理、内部计算

      实际应用场景

    • Web开发

      • HTML、JSON、XML默认使用UTF-8编码,确保多语言支持。

    • 文件存储

      • 文本文件(如.txt.csv)优先使用UTF-8,避免乱码。

    • 数据库

      • MySQL、PostgreSQL等数据库推荐UTF-8作为字符集。

    • 编程语言

      • Python、Java、JavaScript等均原生支持UTF-8字符串处理。

string类

        为什么要学习string类?

       C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

        标准库中的string类

        string类(了解)

        官方string类的介绍文档:

        <string> - C++ 参考

        string - C++ 参考

        在使用string类的时候,必须要包含头文件<string.h>以及

using namespace std

        auto和范围for

        auto关键字

        早期C/C++中auto的含义是:使用auto修饰的变量是具有自动储存器的局部变量,后来这个不重要了。在C++11 中auto有了全新的含义:auto不再是一个储存类型的指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

        用auto声明指针类型时,用auto和auto*没有任何区别,但是用auto声明引用类型时候必须加&,否则只是拷贝而无法通过它去改变指向的值

	int main(){int x = 10;auto y = x;auto *z= &x;auto& m = x;cout<<typeid(y).name()<<endl;cout<<typeid(z).name()<<endl;cout<<typeid(m).name()<<endl;return 0;}

这么写也可以

auto p1=&x;
auto* p2=&x;

 但是不能这么写

auto *p2=x;

        当同一行声明多个变量的时候,这些变量必须是相同类型的,否则编译器会报错。因为编译器实际上只对第一个类型进行推导,然后用推导出来的类型去定义其他变量

        auto不能作为函数的参数,可以做返回值,但是建议谨慎使用

#include <iostream>
using namespace std;
int func1()
{return 10;
}
//不能做参数
void func2(auto a)
{}
//可以做返回值但是谨慎使用
auto func3()
{return 10;
}
int main()
{int a = 10;auto b = a;auto c = 'a';auto d = func1();//编译错误:“e”包含类型“auto”必须要有初始值auto e;cout<<typeid(b).name()<<endl;cout<<typeid(c).name()<<endl;cout<<typeid(d).name()<<endl;return 0;
}

        auto不能直接声明数组

int main()
{auto aa = 1, bb = 2;//编译错误:声明符列表中,“auto”必须始终推到为同一类型auto cc = 3, dd = 4.0;//编译错误:不能将“auto[]”用作函数参数类型auto arr[] ={4,5,6};return 0;
}

关于    typeid(b).name()    的解析

  • typeid(b)
    使用 typeid 运算符获取变量 b 的类型信息,返回一个 std::type_info 对象的常量引用。

    • 作用:在运行时(RTTI,Run-Time Type Information)或编译时获取类型信息。

    • 头文件:需包含 <typeinfo>

  • .name()
    std::type_info 的成员函数,返回一个表示类型名称的字符串(格式取决于编译器实现)。

    • 输出示例

      • GCC/Clang:i(表示 int

      • MSVC:int

  • cout << ... << endl
    输出 typeid(b).name() 返回的字符串,并换行。

auto真正的应用:

#include <iostream>
#include<string>
#include<map>
using namespace std;
int main()
{std::map<std::string, std::string> dict = { {"apple","苹果"},{"orange","橘子"},{"pear","梨"}};//auto的应用//std::map<std::string, std::string>::iterator it = dict.begin();auto it =dict.begin();while (it != dict.end()){cout<<it->first<<" "<<it->second<<endl;++it;}return 0;
}

范围for 

        对于有范围的集合而言,程序员说明循环的范围是多余的,有时候还会容易犯错误。因此C++11引入了基于范围的for循环。for循环后括号由冒号“:”分为两部分:第一部分是范围用于迭代的变量,第二部分则表示被迭代的范围,自动迭代自动取数据,自动判断结束

        范围for可以作用到数组和容器对象上遍历。

        范围for的底层很简单,容器遍历实际上就是替换为迭代器,从汇编也可以看到

#include<iostream>
#include<string>
#include<map>
using namespace std;
int main()
{int arr[] = {1,2,3,4,5};C++98的遍历//for (int i = 0;i < sizeof(arr) / sizeof(arr[0]);i++)//{//	cout<<arr[i]<<endl;//}//C++11的遍历for (auto& e : arr){e *= 2;}for (auto&e : arr){cout<<e<<" " << endl;}string str("hello world");for (auto ch : str){cout<<ch<<" ";}cout<<endl;return 0;
}

        string类的常用接口说明

        string的构造方式

他们的特点分别为:

(constructor)函数名称功能说明
string(重点)构造空的string类,即空字符串
string(const char*s)(重点)用C-string来构造string类对象
string(size_t n,char c)string类对象包含n个字符c
string(const string &s)(重点)拷贝构造函数

        一个简单的string程序

#include <iostream>
#include<string>
using namespace std;
void test()
{//最常见的两种string构造方式string s1;                      //构造空字符串string s2 = "hello world";      //构造常量字符串string s3(s2);                  //拷贝构造string s4(s2, 1, 5);            //从s2中构造子串string s5(s2, 1, 50);string s6(s2, 1);const char* str = "hello world";//常量字符串string s7(str, 5);                  //从常量字符串构造string s8(100,'#');                    //从常量字符串构造cout << s1 << endl;cout << s2 << endl;cout << s3 << endl;cout << s4 << endl;cout << s5 << endl;cout << s6 << endl;cout << s7 << endl;cout << s8 << endl;
}
int main()
{test();return 0;
}

结果为:

         string类对象的容量操作

        

函数名称功能说明
size(重点)返回字符串有效字符长度
length返回字符串有效字符长度
capacity返回空间总大小
empty(重点)检测字符串释放为空串,是返回true,否返回false
clear(重点)清空有效字符
reserve(重点)给字符串预留空间
resize(重点)讲有效字符的个数改成n个,多出的空间用字符c填充

注意:

1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。

2. clear()只是将string中有效字符清空,不改变底层空间大小。

3. resize(size_t n) 与  resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, charc)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。

4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。

参考文献:string - C++ 参考

以下是一些关键内容的截图:

 

string的遍历

函数名称功能说明
operator[](重点)返回pos的位置,const string类对象调用
begin+endbegin获取一个字符的迭代器+end获取最后一个字符下一个位置的迭代器
rbegin+rendbegin获取一个字符的迭代器+end获取最后一个字符下一个位置的迭代器
范围forC++11支持更简洁的范围for新遍历方式

一、[]运算符重载介绍

我们很明显地发现:s2是不能修改的 

因为它们的调用关系是这样的

void test()
{string s1("hello world");const string s2("hello world");//遍历+修改//下标+[]s1[0]++;/*s2[0]++;*/cout<<s1<<endl;for (size_t i = 0;i < s1.size();i++){s1[i]++;}cout << s1 << endl;
}
int main()
{test();return 0;
}

结果为:

二、迭代器

迭代器是像指针一样的类型对象

void test()
{string s1("hello world");const string s2("hello world");//遍历+修改//下标+[]s1[0]++;/*s2[0]++;*/cout<<s1<<endl;for (size_t i = 0;i < s1.size();i++){s1[i]++;}cout << s1 << endl;//begin() end()返回的是一段迭代器位置的区间,形式是这样的[        )//迭代器//s1--//iterator迭代器//迭代器使用起来像指针string::iterator it = s1.begin(); while (it!= s1.end()){(*it)--;++it;}cout << s1 << endl;}
int main()
{test();return 0;
}

结果为:

迭代器是所有容器的主流迭代方式,迭代器具有迁移性,掌握一个其他的也可以轻松上手

	vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);vector<int>::iterator it2 = v.begin();while (it2 != v.end()){cout<<*it2<<" ";++it2;}

三、范围for (C++11)

        (这里需要结合前面的auto和范围for的拓展知识内容)

#include<iostream>
#include<string>
#include<map>
using namespace std;
int main()
{int arr[] = {1,2,3,4,5};//C++11的遍历for (auto& e : arr)//从:开始自动,特点是://自动取范围中的数据赋值给e,//自动判断结束//自动迭代{e *= 2;}for (auto&e : arr){cout<<e<<" " << endl;}string str("hello world");for (auto ch : str){cout<<ch<<" ";}cout<<endl;return 0;
}

        范围for可以遍历vector,list等其他容器。 

        范围for本质上底层也会替换为新迭代器,即e=*迭代器

string类对象的修改操作

函数名称功能介绍
push  back在字符串中后尾插字符c
append在字符串后追加一个字符串
operator+=(重点)在字符串后追加字符串str
c_str(重点)返回C格式字符串
find+npos(重点)从字符串pos位置开始向后找字符c,返回该字符在字符串中的位置
rfind从字符串pos位置开始往前找字符c,返回该字符在字符串中位置
substr在str中从pos位置开始,截取n个字符然后返回

注意:

1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。

2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。

void test()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);vector<int>::iterator it2 = v.begin();while (it2 != v.end()){cout<<*it2<<" ";++it2;}reverse(v.begin(), v.end());}​

string类非成员函数

        

函数功能说明
operator+尽量少用,因为传值返回,导致深拷贝效率降低
operator>>(重点)输入运算符重载
operator<<(重点)输出运算符重载
getline(重点)获取一行字符串
relation operators(重点)大小比较

         上面的几个接口大家了解一下,下面的OJ题目中会有一些体现他们的使用。string类中还有一些其他的操作,这里不一一列举,大家在需要用到时不明白了查文档即可。

迭代器的介绍

迭代器的概念与分类

迭代器是STL中访问容器元素的通用接口,行为类似指针,但抽象程度更高。

  1. 迭代器类别

    • 正向迭代器(Forward Iterator):单向遍历(如 std::string::iterator)。

    • 双向迭代器(Bidirectional Iterator):支持双向移动(如 std::list::iterator)。

    • 随机访问迭代器(Random Access Iterator):支持跳跃访问(如 std::vector::iterator)。

    • 反向迭代器(Reverse Iterator):逆序遍历容器(如 std::string::reverse_iterator)。

    • const迭代器:禁止修改元素(如 std::string::const_iterator)。

  2. string的迭代器类型

    • std::string::iterator:可修改的随机访问迭代器。

    • std::string::const_iterator:不可修改的随机访问迭代器。

    • std::string::reverse_iterator:可修改的反向迭代器。

    • std::string::const_reverse_iterator:不可修改的反向迭代器。

string中迭代器的获取方式

通过成员函数获取不同类别的迭代器:

正向迭代器

std::string str = "Hello";
auto begin = str.begin();   // 指向第一个字符的迭代器
auto end = str.end();       // 指向末尾(最后一个字符的下一位)

反向迭代器

auto rbegin = str.rbegin(); // 指向最后一个字符的反向迭代器
auto rend = str.rend();     // 指向头部前一位的反向迭代器

const迭代器

auto cbegin = str.cbegin(); // 常量正向迭代器
auto crbegin = str.crbegin(); // 常量反向迭代器

迭代器的基本操作

遍历字符串

for (auto it = str.begin(); it != str.end(); ++it) {std::cout << *it;  // 解引用访问字符
}

反向遍历

for (auto rit = str.rbegin(); rit != str.rend(); ++rit) {std::cout << *rit;  // 输出逆序字符
}

随机访问

auto it = str.begin() + 3;  // 跳转到第4个字符('l')
std::cout << *it;           // 输出 'l'

涉及迭代器的string成员函数

构造与赋值

std::string s4(str.begin(), str.begin() + 3); // 构造子串 "Hel"
str.assign(s4.rbegin(), s4.rend());           // 赋值逆序子串 "leH"

插入与删除

str.insert(str.begin() + 2, 'X');    // 在位置2插入'X' → "HeXllo"
str.erase(str.begin() + 1);          // 删除位置1的字符 → "HXllo"

查找与替换

auto pos = std::find(str.begin(), str.end(), 'X');
if (pos != str.end()) {*pos = 'Y';  // 替换找到的字符
}

与STL算法结合使用

利用标准算法处理字符串:

排序字符串字符

std::sort(str.begin(), str.end()); // 升序排列字符

统计字符出现次数

int count = std::count(str.begin(), str.end(), 'l');

条件查找

auto it = std::find_if(str.begin(), str.end(), [](char c) {return c >= 'A' && c <= 'Z';
});

迭代器失效问题

  1. 导致失效的操作

    • 修改字符串长度(如append()insert()erase())。

    • 重新分配内存(如reserve()不足时扩容)。

  2. 安全实践

    • 在修改操作后,避免使用旧的迭代器。

    • 使用索引或重新获取迭代器。

实际应用示例

逆序输出字符串

for (auto rit = str.rbegin(); rit != str.rend(); ++rit) {std::cout << *rit;
}

删除所有空格

str.erase(std::remove(str.begin(), str.end(), ' '), str.end());

转换为大写

std::transform(str.begin(), str.end(), str.begin(), ::toupper);

迭代器的意义:

1.统一类似的方式遍历修改容器

2.算法脱离了具体的底层结构,与底层结构解耦(降低耦合,降低关联关系)

算法独立模板实现,针对多个容器处理

下图就是相关的代码示例:(使用前要包含头文件<algorithm>)

一些string类的例题

        1.仅仅反转字母

题目:

       这道题本质上是类似于快速排序算法

        这道题不适合用范围for,也不适合迭代器。用下标法求解这道题:

class Solution
{
public:string reverseOnlyLetters(string s) {if(s.empty())return s;size_t begin=0,end=s.size()-1;while(begin<end){while(begin<end&&!isalpha(s[begin])){++begin;}while(begin<end&&!isalpha(s[end])){--end;}swap(s[begin],s[end]);++begin;--end;}return s;}
};

或者

class Solution
{
public:bool isletter(char ch){if(ch>='a'&&ch<='z'){return true;}else if(ch>='A'&&ch<='Z'){return true;}else {return false;}}string reverseOnlyLetters(string s) {if(s.empty())return s;size_t begin=0,end=s.size()-1;while(begin<end){while(begin<end&&!isletter(s[begin])){++begin;}while(begin<end&&!isletter(s[end])){--end;}swap(s[begin],s[end]);++begin;--end;}return s;}
};

        2.查找字符串中第一个唯一的字符

        题目:

         思路:哈希

        哈希就是映射,即建立数字与位置的映射,就像下图这样

class Solution
{
public:int firstUniqChar(string s) {int count[26]={0};//每个字符出现的次数for(auto e:s){count[e-'a']++;}for(size_t i=0;i<s.size();++i){if(count[s[i]-'a']==1){return i;}}return -1;}
};

         本期博客就到这里了,string的内容还没有结束,后续我们会进一步了解string的应用

http://www.xdnf.cn/news/397297.html

相关文章:

  • 联合类型的逻辑或关系与类型保护
  • 关于我在实现用户头像更换时遇到的图片上传和保存的问题
  • Colab使用_文件操作
  • C++.IP协议通信
  • 【C++进阶】第3课—二叉搜索树
  • C++猴子摘桃 2024年信息素养大赛复赛 C++小学/初中组 算法创意实践挑战赛 真题详细解析
  • [超详细,推荐!!!]前端性能优化策略详解
  • VC++ 获取CPU信息的两种方法
  • POSIX信号量
  • 【软件测试】基于项目驱动的功能测试报告(持续更新)
  • k8s中ingress-nginx介绍
  • Spring Boot 中的重试机制
  • 【Python】Python类型标注革命:Annotated类型深度解析与实战
  • 匈牙利算法
  • 信息系统项目管理师-软考高级(软考高项)​​​​​​​​​​​2025最新(十七)
  • java中对象的比较
  • 【文献阅读】地方政府驱动企业参与乡村振兴的机制——乡村振兴注意力视角的分析
  • 【工作记录】crmeb后端项目打开、运行
  • 【Flask开发踩坑实录】pip 安装报错:“No matching distribution found” 的根本原因及解决方案!
  • 1688 开放平台接口对接实战:商品实时数据采集 API 开发全流程
  • cmake:test project
  • OSPF的特殊区域
  • P10225 [COCI 2023/2024 #3] Milano C.le|普及
  • LeetCode 热题 100 543. 二叉树的直径
  • RS485和RS232 通信配置
  • TikTok 运营干货:内容创作与 AI 增效
  • 【高数上册笔记01】:从集合映射到区间函数
  • istio in action之应用弹性与容错机制
  • Babel 插件与预设的区别及使用
  • 每日脚本 5.11 - 进制转换和ascii字符