深入浅出字符编码

news2025/1/15 20:54:51

本文对应视频链接: 深入浅出字符编码
在这里插入图片描述

什么是编码格式

从一个小问题引入

我们在学习C语言的时候,有一道必做的题目是将大写字母转换成小写,相信有点基础的同学都能不加思索的写出下面的代码:

char toLower(char upper){
    if (upper >= 'A' && upper <= 'Z'){
        return upper + 32;
    }else{
        return upper;
    }
} 

要问为什么是这段代码?我们往往也能说得出:因为大小写字母在ASCII码上正好相差32(字符'a'为97, 字符'A'为65)。
我们在进行字符初始化的时候,往往会将字符初始化为'\0'。因为'\0'在ASCII码中对应的数值是0。
我们理所应当地知道char型字符对应的范围是0~127,因为ASCII码的范围就是0~127
但是有没有想过,为什么是ASCII码?
所谓的ASCII码,又到底是什么?

编码格式介绍

要说起ASCII码,不得不说起编码格式。
我们知道,对于计算机来说,我们在屏幕上看到的千姿百态的文字、图片、甚至视频是不能直接识别的,而是要通过某种方式转换为0和1组成的二进制的机器码,最终被计算机识别(0为低电平,1为高电平)。
对于数字来说,有一套非常成熟的转换方案,就是将十进制的数字转换为二进制,就能直接被计算机识别(如5转换为二进制是 0000 0101)。但是对于像ABCD这样的英文字母,还有!@#$这样的特殊符号,计算机是不能直接识别的,所以就需要有一套通用的标准来进行规范。
这套规范就是ASCII码。
ASCII码使用127个字符,表示AZ等26个大小写字母,包含数字09,所有标点符号以及特殊字符,甚至还有不能在屏幕上直接看到的比如回车、换行、ESC等。
在这里插入图片描述

按照这套SACII的编码标准,就很容易的知道,'\0'代表的是0, 'A'代表的是65,而'a'代表的是97,'A''a'之间正好相差了32。
ASCII码虽然只有127位,但基本实现了对所有英文的支持。所以为什么说char类型只占1个字节?因为char型最大的数字是127,转成二进制也不过是0111 1111,只需要1个字节就能表示所有的char型字符,因此char只占1个字节。
但是随着计算机的普及,计算机不但要处理英文,还有汉字、甚至希腊文字、韩文、日文等诸多文字,这时,127个字符肯定不够了,这时就引入了Unicode的概念。
Unicode是一个编码字符集,它基本涵盖了世界上绝大多数的文字(只有极少数没有包含),在Unicode中文对照表中可以查看一些汉字的Unicode字符集。
比如,汉字”七“在Unicode表示为十六进制0x4e03,表示成二进制位0100 1110 0000 0011,占了15位,至少需要两个字节才能放得下,有些更复杂的生僻字,可能占用的字节数甚至不止两位。
这就面临着一个问题,当一个中英文夹杂的字符串输入到电脑的时候,计算机是如何知道它到底是什么的?
就像上面的0100 1110 0000 0011,它到底是表示的是0100 11100000 0011两个ASCII字符,还是汉字”七“?计算机并不知道。所以就需要一套规则来告诉计算机,到底该按照什么来解析。这些规则,就是字符编码格式。
其中就包括以下几种。

  • ASCII
  • UTF-8
  • GBK
  • GB2312
  • GB18030
  • BIG5
  • ISO8859

编码格式分类

ASCII

ASCII 编码前面已经介绍过,此处就不再多说了。它使用0~127这128位数字代表了所有的英文字母以及数字、标点、特殊符号和键盘上有但屏幕上看不见的特殊按键。
它的优点是仅用128个数字就实现了对英文的完美支持,但是缺点也同样明显,不支持中文等除英文以外的其他语言文字。
因此,ASCII码基本可以看做是其他字符编码格式的一个子集,其他字符编码都是在ASCII码的基础上实现了一定的扩展,但毫无意外地,都实现了对ASCII码的兼容。

UTF-8

在汉字环境下,UTF-8可以说是最常见的编码。它是Windows系统默认的文本编码格式。
UTF-8是一种变长的编码方式,最大可以支持到6位。这就意味着他可以有效地节省空间(在后面介绍GBK的时候,会讲GBK是固定长度的编码方式)。
那么,UTF8是如何知道当前所要表达的字符是几个字节呢?
UTF8中,它以首字节的高位作为标识,用来区别当前字节的长度。其规则大致如下:

1字节 0xxxxxxx (范围:0x00-0x7F)
2字节 110xxxxx 10xxxxxx (范围:0x80-0x7ff)
3字节 1110xxxx 10xxxxxx 10xxxxxx (范围:0x800-0xffff)
4字节 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (范围:0x10000-0x10ffff)
5字节 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
6字节 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

如上面的汉字”七“的unicode码是0x4e03,在0x800-0xffff区间,所以是3字节,用UTF-8表示就是11100100 10111000 10000011(十六进制表示为0xe4b883)。
"七"的Unicode码是0100 1110 0000 0011,可为什么是这个数呢?
根据3字节的填充规则,从右往左,依次填充x的位置:

       0100     111000     000011
+
1110xxxx 10xxxxxx 10xxxxxx

11100100 10111000 10000011

事实上,utf-8编码下,汉字都为3字节。
实际上UTF家族除了UTF-8外,还有UTF-16UTF-32等,由于不太常用,此处也就不展开讨论了。

GBK/GB2312/GB18030

GB就是”国标“的拼音开头,顾名思义,以GB开头的编码都是中国人专门为支持汉语而设计的编码格式。但这三者又有区别,最早出现的是GB2312,它收录了6763个汉字,基本满足了计算机对汉字的处理需要。
GB2312使用双字节表示一个汉字。对汉字进行分区处理。每个区含有94个汉字(或符号),这种表示方式称之为区位码。

  • 01-09 区为特殊符号。
  • 16-55 区为一级汉字,按拼音排序。
  • 56-87 区为二级汉字,按部首/笔画排序。
  • 10-15 区及 88-94 区则未有编码。
    GB2312编码范围:A1A1-FEFE,其中汉字编码范围:B0A1-F7FE。表示汉字时,第一字节0xB0-0xF7(对应区号:16-87),第二个字节0xA1-0xFE(对应位号:01-94)。
    GBK是在GB2312基础上的扩展。GBKK就是扩展的”扩“的拼音首字母。因此,GBK向下兼容GB2312
    GBK也使用双字节表示汉字,其中首字节范围0x81-0xfe,第二个字节范围0x40-0xfe,剔除0x7F一条线。因此,GBK所能表示的汉字比GB2312要多得多(能表示21886个汉字)。
    GB18030是最新的内码字集,可以表示70244个汉字。它与UTF-8类似,采用多字节编码,每个汉字由1、2、4个字节组成。
  • 单字节,其值从 0 到 0x7F,与 ASCII 编码兼容。
  • 双字节,第一个字节的值从 0x81 到 0xFE,第二个字节的值从 0x40 到 0xFE(不包括0x7F),与 GBK 标准兼容。
  • 四字节,第一个字节的值从 0x81 到 0xFE,第二个字节的值从 0x30 到 0x39,第三个字节从0x81 到 0xFE,第四个字节从 0x30 到 0x39。
    如果你看到这个地方已经觉得很乱了,不要紧。我们只需要知道,在GB打头的编码格式下,我们能够用键盘敲出来的,你在电脑上所看见的所有汉字,都是双字节的(四字节的汉字极少,只有一些极少数不常用的生僻字用到)。

BIG5

BIG5,从字面翻译来看,叫做”大五码“,它主要用来表示中文繁体字。
它也是用双字节表示一个汉字,其中高位字节使用了0x81-0xFE,低位字节使用了0x40-0x7E,及0xA1-0xFE。。
这种编码格式用的比较少,此处就不展开说了。

汉字编码

上面介绍的几种编码格式,UTF-8GBK等都支持汉字,但是标准不同,因此,在实际进行开发的过程中,对汉字的处理也不尽相同。

如何判断汉字编码

无论是UTF-8GBK,还是GB18030,或者BIG5,它都是向下兼容ASCII的,为了区分ASCII码和汉字,在汉字的高位补1。
这也就是说,如果我们以int的形式取出单个字符的值,汉字都是小于0的。
因此,判断是否是汉字也就变得简单了:

enum boolean{true, false};
typedef int boolean;

boolean isChinese(char ch){
    return (ch < 0) ? true : false;
}

写一段代码验证一下:

void test01(){
    char str[20];
    memset(str, 0, sizeof(str));
    strcpy(str, "hello汉字");
    for (int i = 0; i < strlen(str); i++){
        if (isChinese(str[i]) == true){
            printf("str[%d]: Chinese\n", i);
        }else{
            printf("str[%d]: English\n", i);
        }
    }
}

我们在main函数里调用test01函数,得到如下结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F3agEKhs-1683503222799)(/img/bVbL2Tz)]

因为在utf-8下,一个汉字占3字节,所以后面从5~10这6个字节正好代表着2个汉字。
如果我们把编码改成GB2312,运行可以得到如下结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2uHepWzK-1683503222800)(/img/bVbL2TR)]
可以看到,只有最后4个字节是汉字,充分说明了GB2312编码格式下,一个汉字占2个字节。

如何处理汉字截断问题

如果我们把上面的字符串按字符打印出来,得到下面的结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aBwxtXQi-1683503222801)(/img/bVbL2VW)]
可以看到,所有的汉字都乱码了,原因就在于,UTF-8编码下,每个汉字占3个字节,一个字节不足以表示完整的汉字,所以打印出来都是乱码的。
在实际开发中,比较常见的需要处理的问题是,截取一定长度的字符串,但是如果截取的位置正好是个汉字,难免会遇到汉字被截断的问题。
那么,这类问题如何处理呢?
根据汉字的编码规则,我们知道,UTF-8GBK对汉字的处理是不一样的。
UFT-8一个汉字是3字节,且规则如下:

1110xxxx 10xxxxxx 10xxxxxx

所以,我们很容易知道,汉字的首字节范围为11100000~11101111,转成十六进制为0xe0~0xef,第二、三字节的范围为10000000~10111111,转成十六进制范围为0x80~0xbf
所以UTF-8的汉字截断问题处理可以如下:

void HalfChinese_UTF8(const char *input, size_t input_len, char *output, size_t *output_len)
{
    char current = *(input + input_len);
    if (isChinese(current) == false)
    {
        *output_len = input_len;
        strncpy(output, input, *output_len);
        return;
    }
    //汉字
    *output_len = input_len;
    //1110xxxx 10xxxxxx 10xxxxxx
    //第二位和第三位的范围是10000000~10ffffff,转成十六进制是0x80~0xbf,在这个范围内都说明是汉字被截断
    while ((current&0xff) < 0xc0 && (current&0xff) >= 0x80)
    {
        (*output_len)++;
        current = *(input + *output_len);
    }
    strncpy(output, input, *output_len);
}

该函数有四个参数,其中inputinput_len作为原始输入,input_len代表需要截取的位置,outputoutput_len作为输出,output为截断处理后的字符串,output_len为截断处理后的长度。
我们使用下面的代码进行测试:

void test02()
{
    char in[20], out[20];
    memset(in, 0, sizeof(in));
    memset(out, 0, sizeof(out));
    strcpy(in, "hello汉字");
    size_t out_len = 0;
    for (int i = 1; i <= strlen(in); i++)
    {
        HalfChinese_UTF8(in, i, out, &out_len);
        printf("out: %s\n", out);
    }
}

运行后结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fMLaQbSs-1683503222803)(/img/bVbL3r4)]

如果是GBK编码,要稍微麻烦一点。因为我们知道,GBK是双字节表示汉字,且第一个字节的值从 0x810xFE,第二个字节的值从 0x400xFE(不包括0x7F),单从字符的值无法判断到底是汉字的首字节还是后一个字节(因为二者的值有重复部分)。
如果字符串纯为汉字倒还好办,我们已经知道汉字占2个字节,直接根据长度的奇偶来判断就可以,但如果是中英文夹杂就不能采用这种方式了。
在这里,我使用的是先对字符串进行一道过滤处理,判断字符串中除掉英文字符后纯汉字的长度,如果为奇数,代表汉字被截断,加1就能取其完整的汉字,如果是偶数,说明正好是一个完整的汉字,无需处理,直接返回即可。
代码实现如下:

void HalfChinese_GBK(const char *input, size_t input_len, char *output, size_t *output_len){
    char current = *(input + input_len);
    if (isChinese(current) == false)
    {
        *output_len = input_len;
        strncpy(output, input, *output_len);
        return;
    }
    *output_len = input_len;
    if (MoveEnglish(input, input_len) %2 != 0){
        (*output_len)++;
    }
    strncpy(output, input, *output_len);
}

int MoveEnglish(const char *input, size_t input_len){
    int out_len = input_len;
    for (int i = 0; i < input_len; i++)
    {
        if (isChinese(input[i]) == false){
            out_len++;
        }
    }
    return (out_len > 0) ? out_len : 0;
}

同样使用上面的测试代码进行测试,得到如下结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3mO5W6ia-1683503222804)(/img/bVbL3sd)]

如何实现编码之间互相转换

既然编码格式这么多,那么怎么进行编码之间的转换呢?
在C语言下,主要是利用系统的iconv函数完成。
iconv函数包含在头文件iconv.h中,其函数原型如下所示:

size_t iconv (iconv_t __cd, char **__restrict __inbuf,
                size_t *__restrict __inbytesleft,
                char **__restrict __outbuf,
                size_t *__restrict __outbytesleft);

第一个参数是转换的一个句柄,由iconv_open函数创建,第二个参数是输入的字符串,第三个参数是输入字符串的长度,第四个参数是转换后的输出字符串,第五个参数是输出字符串的长度。在编码转换完成之后,需要调用iconv_close函数关闭句柄。所以完整的调用顺序为:

  • iconv_open打开iconv句柄
  • 调用iconv进行编码转换
  • iconv_close关闭句柄

还有一点需要注意的是,__inbytesleft__outbytesleft的长度,因为不同编码对于汉字的处理字节数不同,比如从UTF-8转换为GBK,同样都是两个汉字,转换前长度为6,转换后长度为4。也就是说,在编码转换过程中,字符串可能会变长或缩短,如果长度不正确,很容易造成越界,从而导致错误。
完整的编码转换功能封装如下:

boolean convert_encoding(char *in, size_t in_len, char *out, size_t out_len, const char *from, const char *to)
{
    if (strcasecmp(from, to) == 0){
        size_t len = (in_len < out_len) ? in_len : out_len;
        memcpy(out, in, len);
        return true;
    }

    iconv_t cd = iconv_open(from, to);
    if (cd == (iconv_t)-1){
        printf("iconvopen err\n");
        return false;
    }
    size_t inbytesleft = in_len;
    size_t outbytesleft = out_len;

    char *src = in;
    char *dst = out;

    size_t nconv;
    nconv = iconv(cd, &src, &inbytesleft, &dst, &outbytesleft);
    if (nconv == (size_t)-1){
        if (errno == EINVAL){
            printf("EINVAL\n");
        } else {
            printf("error:%d\n", errno);
        }
    }
    iconv_close(cd);
    return true;
}

注意,由于使用到了libiconv,编译时需要加-liconv进行链接。
测试代码如下:

void test04()
{
    char in[20], out[20];
    memset(in, 0, sizeof(in));
    memset(out, 0, sizeof(out));
    strcpy(in, "hello汉字world");
    if (false == convert_encoding(in, strlen(in), out, 20, "utf-8", "gbk")){
        printf("failed\n");
        return;
    }
    printf("in: %s\nout:%s\n", in, out);
}

以上代码运行结果如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ccKXwgxb-1683503222805)(/img/bVbL3Yp)]

GBK转换为UTF-8也是同样的操作,此处就不做演示了。


推荐一个零声学院免费教程,个人觉得老师讲得不错,分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,
fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,
TCP/IP,协程,DPDK等技术内容,点击立即学习: C/C++Linux服务器开发/高级架构师

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

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

相关文章

大数据Doris(十四):数据模型的选择建议和列定义建议

文章目录 数据模型的选择建议和列定义建议 一、数据模型的选择建议 1、Aggregate数据模型选择<

如何快速给出解释——正交矩阵子矩阵的特征值的模必然不大于1

Memory 首先快速回忆一下正交矩阵的定义&#xff1a; A为n阶实矩阵&#xff0c;且满足A‘AE或是说AA’E&#xff0c;那么A为正交矩阵。 &#xff08;啊&#xff0c;多么简洁的定义&#xff09; 其次快速想到它的性质&#xff1a; ① 实特征值必然 或 其他复数…

qt几种常见安装包制作工具-Qt Installer Framework

Qt Installer Framework&#xff08;简称&#xff1a;QtIFW&#xff09;概述 QtIFW提供了一组工具和实用程序&#xff0c;可以一次性创建安装程序&#xff0c;并在所有支持的桌面Qt平台上部署它们&#xff0c;而无需重写源代码。安装程序将在运行它们的平台上具有本机外观和感…

第四节 特殊权限SUID、SGID、SBIT

1.Set UID 简称 SUID 简称 SUID 限制与功能&#xff1a; SUID权限仅对二进制程序有效&#xff1b; 执行者对于该程序需要具有x的执行权限&#xff1b; 本权限仅在执行该程序的过程中有效&#xff1b;  执行者将具有该程序拥有者的权限 特殊权限SUID、SGID、SBIT 例&am…

【分布族谱】Zipf分布及其Python可视化

文章目录 zipf分布简介zipfian和zipf对象zipf分布到zeta分布的变化情况分布族谱图 zipf分布简介 #mermaid-svg-mG901pJXpTYFT7Bk {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-mG901pJXpTYFT7Bk .error-icon{fill:…

Spring Boot 如果防护 XSS + SQL 注入攻击

Spring Boot 如果防护 XSS SQL 注入攻击 XSS跨站脚本攻击XSS漏洞介绍XSS漏洞分类防护建议 SQL注入攻击SQL注入漏洞介绍防护建议mybatis是如何做到sql预编译的呢 SpringBoot中如何防止XSS攻击和sql注入创建Xss请求过滤类XssHttpServletRequestWraper把请求过滤类XssHttpServlet…

使用fork函数创建一个进程

pid_t fork(void) fork函数调用成功&#xff0c;返回两次 &#xff08;1&#xff09;返回值为0&#xff0c;代表当前进程是子进程 &#xff08;2&#xff09;返回值为非负数&#xff0c;代表当前进程是父进程 &#xff08;3&#xff09;调用失败&#xff0c;则返回-1 代码如…

【人工智能概论】 K折交叉验证

【人工智能概论】 K折交叉验证 文章目录 【人工智能概论】 K折交叉验证一. 简单验证及其缺点1.1 简单验证简介1.2 简单验证的缺点 二. K折交叉验证2.1 K折交叉验证的思路2.2 小细节2.3 K折交叉验证的缺点2.4 K折交叉验证的代码 一. 简单验证及其缺点 1.1 简单验证简介 简单验…

join 语句使用

目录 前言 创建数据 知识点补充 Join算法Index Nested-Loop 小结&#xff1a; Join算法Block Nested-Loop join_buffer放不下驱动表情况 小结&#xff1a; 小表是什么&#xff1f; 总结&#xff1a; 参考内容 前言 在实际开发中&#xff0c;我们一般会有两类问题&a…

腾讯云2核4G服务器5M带宽轻量CPU性能、流量和系统盘测试

腾讯云轻量应用服务器2核4G5M配置&#xff0c;自带5M公网带宽&#xff0c;5M带宽下载速度峰值可达640KB/秒&#xff0c;系统盘为60GB SSD盘&#xff0c;每月500GB流量包&#xff0c;折合每天16GB流量。腾讯云百科来详细说下腾讯云轻量应用服务器2核4G5M配置、CPU型号处理器主频…

威联通nas服务器中勒索病毒被encrypted勒索病毒攻击怎么办有哪些预防措施

威联通是一家专业提供网络存储设备和应用方案的公司&#xff0c;旗下NAS服务器因为实用、多功能而深受用户喜欢&#xff0c;但是NAS服务器在使用过程中也面临许多安全问题&#xff0c;例如被encrypted勒索病毒攻击。下面将为大家介绍encrypted勒索病毒在威联通NAS服务器上的危害…

黑马---Redis入门到实战【实战篇】

一、短信登录 基于session实现短信登录的流程 实现发送短信验证码功能 发送验证码功能&#xff1a; Overridepublic Result sendCode(String phone, HttpSession session) {//1.校验手机号if(RegexUtils.isPhoneInvalid(phone)){//2.如果不符合&#xff0c;返回错误信息return…

Java 基础进阶篇(十三)—— 异常处理机制

文章目录 一、异常概述、体系二、异常的分类三、异常的默认处理流程四、异常的处理机制4.1 编译时异常的处理机制4.1.1 方式一&#xff1a;抛出异常4.1.2 方式二&#xff1a;捕获异常4.1.3 方式三&#xff1a;前两者结合 4.2 运行时异常的处理机制 五、自定义异常5.1 自定义编译…

程序员面试金典10.*

文章目录 10.1合并排序的数组10.02变位词组10.03搜索旋转数组10.05稀疏数组搜索10.09排序矩阵查找10.10 数字流的秩10.11 峰与谷 10.1合并排序的数组 这个就从后往前加入到新数组里就行。如果B的下标是-1则结束&#xff0c;A的下标是-1则一直加B的元素。 class Solution { pub…

挑战14天学完Python---初识python基本图形绘制

往期文章 目录 往期文章前言1."Python蟒蛇绘制"实例2.Python标准库 之turtle库3. 面向对象编程风格3.1 import更多玩法3.1.1使用from和import保留字共同完成3.1.2 使用import和as保留字共同完成 4.turtle的原(wan)理 (fa)4.1 turtle绘图窗体布局---turtul.setup()4.2…

京东小程序折叠屏适配探索 | 京东云技术团队

前言 随着近年来手机行业的飞速发展&#xff0c;手机从功能机进入到智能机&#xff0c;手机屏幕占比也随着技术和系统的进步越来越大&#xff0c;特别是Android 10推出以后&#xff0c;折叠屏逐渐成为Android手机发展的趋势。 图 1 Android手机屏幕发展趋势 京东小程序近年来…

Python程序员辞职后,如何踏出自由职业的第一步,聊聊我自己的看法

大家好&#xff0c;我是兴哥。有个广州的朋友说他辞职了&#xff0c;想要自由职业该怎么开始第一步呢&#xff1f;我问他你之前的收入月薪是多少&#xff0c;他说2万出头。我不得不说&#xff0c;对于写项目的自由职业程序员&#xff0c;2万是一个极高的门槛。但既然他已经辞职…

第三十章 React的路由基本使用

关于React路由&#xff0c;我们在学习之前先了解一下其他知识点&#xff1a;SPA应用、路由的理解、react中如何使用路由。 SPA应用的理解 我们知道React脚手架给我们构建的是一个单页应用程序&#xff08;SPA&#xff09;&#xff0c;在页面加载时&#xff0c;只会加载一个HT…

2.Redis入门概述

1.Redis是什么 Remote Dictionary Server(远程字典服务)是完全开源的&#xff0c;使用ANSIC语言编写遵守BSD协议&#xff0c; 是一个高性能的Key-Value数据库&#xff0c; 提供了丰富的数据结构&#xff0c;例如String、Hash、List、Set、SortedSet等等。 数据是存在内存中的&a…

学会这几个Word技巧,让你办公省时又省力(二)

Word是我们经常用到的办公软件&#xff0c;下面分享的几个小技巧&#xff0c;可以提高你的办公效率&#xff0c;一起看看吧。 1. 改变Word文档的背景颜色 有时候我们打开的Word文档是有颜色的&#xff0c;如果你想恢复白色背景&#xff0c;或者改成其他颜色&#xff0c;只…