C++ STL 学习之【string】

news2024/11/16 0:42:15

✨个人主页: Yohifo
🎉所属专栏: C++修行之路
🎊每篇一句: 图片来源

  • The key is to keep company only with people who uplift you, whose presence calls forth your best.

    • 关键是只与那些提升你的人在一起,他们的存在唤起了你最好的一面。

    配图


文章目录

  • 📘前言
  • 📘正文
    • 📖basic_string
    • 📖编码理解
    • 📖构造函数相关
      • 🖋️无参(默认)构造函数
      • 🖋️带参构造函数
    • 📖容量操作相关
      • 🖋️获取数据
      • 🖋️扩容空间
      • 🖋️调整长度
    • 📖遍历字符相关
      • 🖋️下标访问
      • 🖋️迭代器
    • 📖字符修改相关
      • 🖋️尾插字符/字符串
      • 🖋️任意位置插入字符/字符串
      • 🖋️删除字符/字符串
      • 🖋️查找字符/字符串位置
      • 🖋️截取字符串
    • 📖非成员函数
      • 🖋️流操作
      • 🖋️获取字符串
      • 🖋️比较函数
    • 📖相关试题
  • 📘总结


📘前言

STLC++ 的重要组成部分,由六大部分构成:伪函数空间配置器算法容器迭代器配接器,其中各种各样的 容器 可以很好的辅助我们写程序,比如今天要介绍的 string,有了它之后,我们对字符串的操作就能变得行云流水

STL

注意: string 诞生于 STL 之前,因此存在部分接口冗余的情况


📘正文

本文介绍的是 string 部分常用接口

📖basic_string

stringbasic_string模板 的一份实例,因为字符串多种多样,所以 string 也有各种各样的版本

  • string 常规字符串类,即每个字符占位 1byte
  • wstring 宽字符串类,用来处理较长字符串,Winows下占位 2byte,而 Linux下占位 4byte
  • u16string 匹配 UTF-16 编码标准,指定字符占位 2byteC++11
  • u32string 匹配 UTF-32 编码标准,规定字符占位 4byteC++11

世界上有各种各样的语言,其字符长度大多不一样,因此需要使用不同的 string 来匹配输出自己国家的字符
basic_string


📖编码理解

我们这里介绍的是 string 类,它匹配 UTF-8 标准,而此标准又兼容了 ASCII 码,因此比较常用

ASCII 是美国信息标准交换代码,仅仅通过 1byte 就能满足其字符需求
ASCII
UTF-8 的特点是能根据不同范围的字符匹配使用不同的标准,因为ASCII 都是 0xxxxxxx 的形式,当识别到其他字符时,会匹配使用对应标准,比如当识别到汉字时,会使用 GBK 编码标准来进行输出(Windows)
GBK
后续随着万国码 Unicode 的诞生,提出了能适用更多语言的编码标准,即 UTF-16UTF-32 ,而 basic_string 中的 u16stringu32string 这两个类就是用来匹配编码标准的

注: 这两个类是在 C++11 标准中制定的

我们的 string 其实就是 basic_string <char> 的别名
string


📖构造函数相关

现在正式进入 string 类的学习,先从默认成员函数—构造函数入手

注意: string 包含于 iostream 头文件中,并且还需要展开 std 命名空间

🖋️无参(默认)构造函数

#include<iostream>
using namespace std;

int main()
{
	string s;	//此时调用的是无参构造函数
	return 0;
}

调用无参构造函数时,默认将对象初始化为空串,即只包含 '\0' 的字符串

无参构造函数

🖋️带参构造函数

我们也可以指定 string 对象中的内容

int main()
{
	string s("Hello String!");	//指定内容
	//string s = "Hello String!";	//下面这种写法也是完全可以的
	return 0;
}

指定初始化

string 也支持将对象构造为 n 个字符 c

int main()
{
	string s(10, 'w');	//构造10个w字符
	return 0;
}

指定字符构造

最后再来看看 string 类的 拷贝构造 函数

int main()
{
	string s1("Hello");
	
	string s2(s1);	//将 s1 的内容构造给 s2
	//string s2 = s1;	//这种写法也是可以的
	
	return 0;
}

拷贝构造


📖容量操作相关

我们可以把 string 类看作一个专门用来处理字符的顺序表,因为它有字符指针容量长度等信息,我们也可以进行手动扩容等操作

🖋️获取数据

获取 string 对象中指向字符串的指针 _str

C++兼容C,在某些场景下需要使用指向字符串的指针,因此 string 类中提供了这个接口

int main()
{
	string s("hello");
	cout << s.c_str() << endl;	//获取对象s中的字符串指针
	return 0;
}

c_str
此时直接打印内容的原因是当指针指向对象为常量字符串时,编译器会直接打印内容
我们可以通过强转来观察函数 c_str()

cout << (void*)s.c_str() << endl;	//此时指针非常量字符指针

地址
通过函数 capapcity()size() 获取当前对象的容量和大小

int main()
{
	string s(200, 'H');	//直接构造200个字符H
	cout << "The string capacity is " << s.capacity() << endl;
	cout << "The string size is " << s.size() << endl;

	//cout << "The string size is " << s.length() << endl;	//这种方式也能获取大小
	return 0;
}

容量
length() 函数能起到和 size() 函数完全一样的效果, 那为什么会有两个函数呢?

  • string 诞生于 STL 之前,当时的设计的获取大小函数为 length()
  • 后来当 string 并入 STL 后,委员会为了统一化,就在 string 类中添加了一个 size() 函数,因为其他容器中获取大小的函数都是 size()
  • 为了确保向前兼容性,不能直接删除 length(),这里推荐使用 size()

🖋️扩容空间

new 出来的空间不支持像 realloc 一样直接扩容,而是需要通过函数扩容

  • realloc 大多数情况下都是异地扩容,即 开辟-拷贝-销毁-更改指向
  • reserve() 函数实现的就是异地扩容
int main()
{
	string s(30, 'H');	//初始化大小为10
	cout << "The default capacity " << s.capacity() << endl;

	s.reserve(300);	//扩容为300
	cout << "The expansion capacity " << s.capacity() << endl;
	return 0;
}

扩容
VS 中的容量都会稍微多一点

假若我们不手动扩容,string 也会像顺序表一样,识别到容量不够时,自动扩容

VS中 string 的扩容策略

  • 默认给一个大小为 15 的数组存储数据,当数组够用时,都是用的数组
  • 当数组容量不够时,改用指针,先 2倍 扩容至 30,后续字符都是存在指针中
  • 之后的扩容操作,都是以 1.5倍 进行扩容
  • 会多开辟一些空间

Linux中 string 的扩容策略

  • 默认大小为 0 的空间
  • 当第一次扩容时,会先扩至 1
  • 扩容时每次都是 2倍 扩容法,比较清晰
  • 不会多开空间
int main() 
{
	string s;
	int capacity = s.capacity();

	cout << "The default capacity " << capacity << endl;
	int n = 0;
	while (n <= 100)
	{
		//尾插字符
		s += 'a';
		if (capacity != s.capacity())
		{
			capacity = s.capacity();
			cout << "The new capacity " << capacity << endl;
		}

		n++;
	}

	return 0;
}

扩容机制
至于 Windows 中为何如此复杂?首先是 STL 版本不同,其次string 在实际使用中,都用不了太大的空间,因此 VS 就直接索性给了一个默认大小为 15 的数组,后续有需要再进行扩容

频繁扩容会导致内存碎片问题,VS在这里的处理方法是比较合理的

小技巧: 在使用 string 时,可以先提前计算好需要的空间,然后通过 reserve 直接提前扩好,避免因自动扩容而导致的内存碎片问题

🖋️调整长度

除了可以扩容外,我们还可以改变 size

int main()
{
	string s(50, 'W');	//当前的 size 为50
	cout << "The default size " << s.size() << endl;
	cout << "The default capacity " << s.capacity() << endl;

	cout << endl;

	s.resize(30);	//改变 size 为30
	//s.resize(100, 'Z');	//还可以这样写,更改后50块空间为 Z
	cout << "The new size " << s.size() << endl;
	cout << "The new capacity " << s.capacity() << endl;

	return 0;
}

size
resize() 有两种情况:

  1. 调整后空间比原空间大,此时相当于扩容 reserve(),不过 resize() 还有一个初始化的功能,即将参数2设为指定字符,如果没有指定就默认为 \0
  2. 调整后空间比原空间小,此时将 _size 调整至目标空间,而 _capapcity 不变,此时我们也无法访问到 _size 之外的数据

resize() 并不会缩容,因为缩容的代价比较大,需要先开辟新空间,然后拷贝,释放原空间,才能完成缩容,因此 resize() 在处理时,若新空间比原空间小,是不会改变 _capaciy


📖遍历字符相关

字符串当然少不了遍历操作,主要有三种遍历方式:下标at()迭代器,因为 下标at() 区别不大,所以可以一起介绍,而 迭代器 是一个很重要的东西,后续容器学习中都会出现它的影子

🖋️下标访问

首先来看看 下标访问,实现原理很简单:运算符重载 operator[]

int main()
{
	string s("chatGPT");
	
	size_t pos = 0;	//下标
	while (pos < s.size())
	{
		//直接像数组一样通过下标访问字符
		cout << "The " << pos + 1 << " char is " << s[pos] << endl;
		pos++;
	}

	return 0;
}

结果
当我们出现越界行为时,下标访问是直接通过 assert 报错的

下面再来看看 at()

cout << "The " << pos + 1 << " char is " << s.at(pos) << endl;

运行结果与 operator[] 一致,其实这两种方法的实现原理都一样,不过处理问题的方法不一样

当出现越界访问,at() 是抛出异常,而非直接断言报错

总的来说,at() 用的比较少,我们一般都是使用 operator[] 来进行下标的随机访问

🖋️迭代器

下面来看看迭代器 iterator 遍历字符串

int main()
{
	string s("chatGPT");

	//创建迭代器
	string::iterator it = s.begin();	//此时的it相当于指向第一个字符的指针
	//auto it = s.begin();	//可以利用 auto 自动识别类型
	while (it != s.end())
	{
		cout << *it;
		it++;
	}

	cout << endl;

	return 0;
}

迭代器遍历
注: begin() 获取第一个字符,end() 获取最后一个字符的下一个字符,即 '\0'

除了可以正向遍历外,我们还可以通过反向迭代器 reverse_iterator 进行反向遍历

int main()
{
	string s("chatGPT");

	//创建反向迭代器
	string::reverse_iterator rit = s.rbegin();	//此时的rit相当于指向最后一个字符的指针
	//auto it = s.begin();
	while (rit != s.rend())
	{
		cout << *rit;
		rit++;
	}

	cout << endl;

	return 0;
}

反向迭代器
注: rbegin() 获取最后一个字符,rend() 获取第一个字符的前一个字符

迭代遍历区间都是左闭右开

除了上面两种普通迭代器外,还有两个 const 修饰的迭代器,用来遍历常量字符串

  • const_iterator 正向遍历常量字符串
  • const_reverse_iterator 反向遍历常量字符串

注意:

  • 迭代器名 const_iterator 中的 const 并非是 const 操作符,而是与普通迭代器构成重载
  • 迭代器不太适合遍历顺序表,适合用来遍历链表
  • 所谓的 范围for 其实就是在调用迭代器进行遍历

📖字符修改相关

现在来谈谈字符修改相关接口

🖋️尾插字符/字符串

尾插字符/字符串有三种方式:

  • push_back() 尾插字符
  • append() 尾插字符/字符串
  • operator+= 尾插字符/字符串

先来看看 push_back()

int main()
{
	string s = "Hello ";

	//尾插字符
	s.push_back('X');

	cout << s << endl;
	return 0;
}

尾插字符
push_back() 就像是顺序表的尾插,一次只能插入一个字符

再来看看 append()

int main()
{
	string s = "Hello ";

	//尾插字符
	s.append(3, 'X');	//需要指定待插入的字符数
	s.append(" YYY");	//或者直接插入字符,都是可以的

	cout << s << endl;
	return 0;
}

append
append() 还有很多其他用法,感兴趣的可以去查看官方文档

最后再来看看 operator+= ,这个是使用频率最高的,因为比较方便

int main()
{
	string s = "Hello ";

	//尾插字符
	s += 'C';	//直接和字符拼接
	s += "SDN";	//和字符串拼接也是可以的

	cout << s << endl;
	return 0;
}

运算符重载
在日常使用中,对于字符串尾插这件事,我们通常都是使用 operator+=

🖋️任意位置插入字符/字符串

string 支持在任意位置插入字符/字符串

int main()
{
	string s("cccccc");
	cout << "Begin insert:" << s << endl;

	s.insert(2, 1, 'A');	//在第 n 个位置插入 m 个字符 c
	s.insert(4, "BBB");	//在第 n 个位置插入字符串

	cout << "After insert:" << s << endl;

	return 0;
}

insert
insert() 的用法同样还有很多,可以自行查看官方文档

🖋️删除字符/字符串

有任意位置插入,当然就有任意位置删除 erase()

int main()
{
	string s("ABCDEFG");
	cout << "Begin erase:" << s << endl;

	//任意位置删除
	s.erase(3, 1);	//从pos3开始,删除第1个字符
	cout << "After erase 1 char:" << s << endl;

	s.erase(2, 4);	//从pos2开始,删除4个字符
	cout << "After erase 4 char:" << s << endl;
	
	s.erase();	//默认全删
	cout << "After erase all:" << s << endl;

	return 0;
}

erase
注意: erase() 是一个全缺省参数,参数1为 0 ,表示默认从 pos0 开始,参数2为 npos,这是无符号整型中的 -1 ,为无符号整型最大值,意思就是如果不写参数2,默认就全删完了

rease
来看看 npos
npos
它的值是 4294967295,没有字符串长达 42亿 多,因此可以用来当作默认长度值

🖋️查找字符/字符串位置

string 类中提供了查找字符/字符串的函数 find()

int main()
{
	string s("My name is KiKi");

	//查找,返回的是目标字符/字符串第一次出现的下标
	cout << "Find 1 char pos: " << s.find('n') << endl;	//找字符,默认从pos0开始
	cout << "Find str pos: " << s.find("KiKi", 5) << endl;	//找字符串,从pos5开始
	cout << "Find not exist str pos: " << s.find("KaKa", 10) << endl;	//假设没找到
	return 0;
}

find
可以看到,当目标不存在时,返回的就是 npos

find() 还有几种形式:

  • rfind() 从后往前找
  • find_first_of(str, pos = 0)pos位置往后,找 str 中出现的任意字符
  • find_last_of(str, pos = npos)npos 位置往前,找 str 中出现的任意字符
  • find_first_not_of() 反向查找
  • find_last_not_of() 反向查找

string 类的接口雀氏很多

🖋️截取字符串

我们可以截取字符串中的目标字符串 substr()

int main()
{
	string s("I am an iKun, love sing、jump、rap and basketball");

	//利用 find 和 substr 切割出 iKun
	cout << "The target is " << s.substr(s.find('i'), 4) << endl;
	return 0;
}

iKun
食不食油饼~
其实 substr() 通常用来截取网址中的域名


📖非成员函数

string类中还有很多定义在类外的非成员函数

🖋️流操作

我们可以直接对 string 对象使用流插入 operator<< 和流提取 operator>>

int main()
{
	string s;
	cin >> s;
	cout << s;
	return 0;
}

流插入和流提取

🖋️获取字符串

单纯的流插入是无法满足字符串插入需要的,因为字符串中往往都会包含 ' ',而 cin 会认为这是结束标志,进而不再读取字符,因此有专门的函数获取字符串 getline()

#include<string>

int main()
{
	string s;
	getline(cin, s);	//需要包含头文件 string
	cout << s;
	return 0;
}

getline
注意: 需要包含头文件 string

🖋️比较函数

string 类中存在一系列的大小比较函数(18个),光是判断相等就有3个,其实没必要设计这么多函数,这可能也是 string 饱受别人吐槽的原因之一,大佬陈浩也写过相关文章吐槽
比较函数
大佬的吐槽
原文出处:《STL中的string类怎么啦?》


📖相关试题

简单学完 string 类后,还是有很多试题值得我们去练习的,感兴趣的同学可以点击下面的链接直达题目仓库

string值得练习的题目


📘总结

以上就是本次关于 STLstring 的全部讲解了,string 类接口众多,但常用的也就那二三十个,其中大多数函数都有多个版本,如果还想了解更多关于 string 类细节的,可以阅读官方文档

如果你觉得本文写的还不错的话,可以留下一个小小的赞👍,你的支持是我分享的最大动力!

如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正


星辰大海

相关文章推荐

C/C++【内存管理】

===============

类和对象实操

类和对象实操之【日期类】

===============

类和对象系列

类和对象(下)

类和对象(中)

类和对象(上)

感谢支持

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

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

相关文章

前端开发常用案例(二)

这里写目录标题1.loding加载动画2.全屏加载动画效果3.吃豆豆4.鼠标悬停3D翻转效果5.3D旋转木马效果6.flex弹性布局-酷狗音乐播放列表flex弹性布局-今日头条首页热门视频栏grid网格布局-360图片展示小米商城左侧二级菜单1.loding加载动画 代码如下&#xff1a; <!DOCTYPE h…

干货 | PCB电路板短路了!试试这六种检查方法

首先&#xff0c;了解一下常见的电路板短路的种类&#xff1a;短路按照功能性可分为&#xff1a;焊接短路&#xff08;如&#xff1a;连锡&#xff09;、PCB短路&#xff08;如&#xff1a;残铜、孔偏等&#xff09;、器件短路、组装短路、ESD/EOS击穿、电路板内层微短路、电化…

九龙证券|房企纷纷驶入代建赛道 抢占千亿新蓝海

跟着房地产职业进入深度调整期&#xff0c;代建形式日益受到房企青睐&#xff0c;不少房企纷繁入局或加快布局&#xff0c;成为了近期商场关注的焦点。 2月11日&#xff0c;上坤集团宣告将布局共建办理事务&#xff0c;这也意味着其正式入局代建商场。实际上&#xff0c;上一年…

谷歌seo快排技术怎么做?Google排名霸屏推广原理

本文主要分享关于谷歌快速排名的方法和所需要的条件。 本文由光算创作&#xff0c;有可能会被剽窃和修改&#xff0c;我们佛系对待这种行为吧。 首先提出一个问题&#xff1a;谷歌seo快排技术怎么做&#xff1f;如何达到谷歌霸屏的效果&#xff1f; 答案是&#xff1a;利用谷…

Java 基础面试题——常见类

目录1.String 为什么是不可变的&#xff1f;2.字符串拼接用“” 和 StringBuilder 有什么区别?3.String、StringBuffer 和 StringBuilder 的区别是什么?4.String 中的 equals() 和 Object 中的 equals() 有何区别&#xff1f;5.Object 类有哪些常用的方法&#xff1f;6.如何获…

【C语言进阶】你听说过柔性数组吗?

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前是C语言学习者 ✈️专栏&#xff1a;C语言航路 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&a…

ChatGPT入门案例|商务智能对话客服(三)

本篇介绍智能客服的基本功能架构和基本概念&#xff0c;并利用对话流技术构建商务智能应用。 01、商务智能客服功能结构 互联网的发展已经深入到社会的各个方面&#xff0c;智能化发展已经成为社会发展的大趋势。在大数据和互联网时代&#xff0c;企业和组织愈加重视客户沟通…

波奇学数据结构:时间复杂度和空间复杂度

数据结构&#xff1a;计算机存储&#xff0c;组织数据方式。数据之间存在多种特定关系。时间复杂度&#xff1a;程序基本操作&#xff08;循环等&#xff09;执行的次数大O渐进法表示法用最高阶的项来表示&#xff0c;且常数变为1。F&#xff08;n&#xff09;3*n^22n1//F(n)为…

git基础使用

Git安装 去安装>> 正式开始 进入要管理的目录&#xff0c;执行命令 git init 查看管理目录下的状态 git status 注&#xff1a;新增文件和修改过后的文件都是红色 管理指定文件&#xff08;红变绿&#xff09; 指定文件&#xff1a;git add 文件名 当前目录下所有&…

【Python入门第十二天】Python 列表

Python 集合&#xff08;数组&#xff09; Python 编程语言中有四种集合数据类型&#xff1a; 列表&#xff08;List&#xff09;是一种有序和可更改的集合。允许重复的成员。元组&#xff08;Tuple&#xff09;是一种有序且不可更改的集合。允许重复的成员。集合&#xff08…

深度学习常用的python函数(一)

由于我只简单的学过python和pytorch&#xff0c;其中有很多函数的操作都还是一知半解的&#xff0c;其中有些函数经常见到&#xff0c;所以就打算记录下来。 1.zip zip(*a):针对单个可迭代对象压缩成n个元组&#xff0c;元组数量n等于min(a中元素的最小长度) a [(1, 2), (3…

springmvc网上商城购物每日推荐购买系统 java ssm

为了解决用户便捷地在网上购物&#xff0c;本文设计和开发了一个熙迪网上购买系统。本系统是基于web架构设计&#xff0c;SSM框架 &#xff0c;jsp技术的前台页面设计与实现&#xff0c;使用Mysql数据库管理&#xff0c;综合采用jsp模式来完成系统的相关功能。主要实现了管理员…

Linux中最基本常见命令总结

❤❤&#x1f49b;&#x1f49b;&#x1f49a;&#x1f49a;&#x1f499;&#x1f499;&#x1f49c;&#x1f49c;您的认可是对我最大的帮助&#x1f49c;&#x1f49c;&#x1f499;&#x1f499;&#x1f49a;&#x1f49a;&#x1f49b;&#x1f49b;❤❤ &#x1f90e;&…

【算法基础】堆⭐⭐⭐

一、堆 1. 堆的概念 堆(heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质: (1)堆中某个结点的值总是不大于或不小于其父结点的值; (2)堆总是一棵完全二叉树。 将根结点最大的堆叫做最大堆或大根堆,根结点…

以太网协议、arp协议、NAT协议、DNS协议

目录 数据链路层&#xff1a; 以太网协议&#xff1a; arp协议 1、arp协议格式 2、arp协议内容解释&#xff1a; arp缓存表 NAT协议&#xff1a;地址转换协议 1、作用&#xff1a;将网络数据当中的私网IP替换成为公网IP&#xff0c;或者将网络数据当中的公网IP替换为私网I…

大数据框架之Hadoop:MapReduce(二)Hadoop序列化

2.1序列化概述 1、什么是序列化 序列化就是把内存中的对象&#xff0c;转换成字节序列&#xff08;或其他数据传输协议&#xff09;以便于存储到磁盘&#xff08;持久化&#xff09;和网络传输。 反序列化就是将收到字节序列&#xff08;或其他数据传输协议&#xff09;或者…

TCP 的演化史-fast retransmit/recovery

工作原因要对一个 newreno 实现增加 sack 支持。尝试写了 3 天 C&#xff0c;同时一遍又一遍梳理 sack 标准演进。这些东西我早就了解&#xff0c;但涉及落地写实现&#xff0c;就得不断抠细节&#xff0c;试图写一个完备的实现。 这事有更简单的方法。根本没必要完全实现 RFC…

大型信息系统

一、大型信息系统二、信息系统的规划方法三、信息系统的规划工具 一、大型信息系统 信息系统规划&#xff08;也称为信息系统战略规划&#xff09;是一个组织有关信息系统建设与应用的全局性谋划&#xff0c;主要包括战略目标、策略和部署能内容。 信息化规划是企业信息化建设…

安全—08day

ApabilitiesapabilitiesLinux Capabilities线程的 capabilitiesPermitted 允许Effective 有效InheritableBoundingAmbient文件的 capabilitiesPermittedInheritableEffective运行 execve() 后 capabilities 的变化案例分析方法一、依次执行如下命令方法二、iptables端口转发方案…

SAP ABAP GUI_DOWNLOAD中下载乱码的问题

1 GUI_DOWNLOAD 1.1 问题表现 GUI_DOWNLOAD在应用当中有时会导致输出的文件在某些电脑正常显示&#xff0c;在某些电脑乱码显示。这个固然是由于各个电脑系统配置有差异&#xff0c;但是我们可以在应用该函数时就排除该差异来保证任意台电脑正常显示输出的文件。 如下…