C语言学习(十二)C语言中的字符(宽字符与窄字符)、从字符谈谈C语言的编码、转义字符

目录

  • C语言学习(十二)C语言中的字符(宽字符与窄字符)、从字符谈谈C语言的编码、转义字符
    • 英文字符
      • 字符的表示
      • 字符的输出
      • 字符与整数
      • 初识字符串
    • 中文字符
      • 中文字符的存储
      • 宽字符的输出
      • 宽字符串
    • C语言到底使用什么编码?char类型的字符串使用什么编码?
      • 源文件使用什么编码
      • char类型窄字符串使用什么编码
      • 总结
      • 关于编码字符集和运行字符集
    • 转义字符

字符串是多个字符的集合,他们由 " "包围,如 "http://www.baidu.com"。字符串中的字符在内存中按照次序、紧挨着排列,整个字符串占用一块连续的内存。

当然,字符串也可以只包含一个字符,例如"A",不过一般我们使用专门的字符类型来处理这种只包含一个字符的情况。

常用到的字符类型是char,他的长度为1,只能容纳ASCII码表中的字符,也就是英文字符。

如果想处理汉语、日语、汉语等,就需要使用其他的字符类型,char是做不到的,我们一般使用wchar_t。下面我们详细说说。

英文字符

字符的表示

char字符类型由单引号' '包围,而字符串才用双引号" "包围。

例:

//正确的写法
char a = '1';
char b = '$';
char c = 'X';
char d = ' ';  // 空格也是一个字符//错误的写法
char x = '中';  //char 类型不能包含 ASCII 编码之外的字符
char y = 'A';  //A 是一个全角字符
char z = "t";  //字符类型应该由单引号包围

说明:在字符集中,全角和半角字符对应的编号(或者说编码值)不同,是两个完全不一样的字符。ASCII码只定义了半角字符,没有定义全角字符。

字符的输出

输出char类型的字符有两种办法,分别是:

  • 使用专门的字符输出函数putchar
  • 使用格式化输出函数printf,char对应的格式控制符是%c。

例:

#include <stdio.h>
int main() {char a = '1';char b = '$';char c = 'X';char d = ' ';//使用 putchar 输出putchar(a); putchar(d);putchar(b); putchar(d);putchar(c); putchar('\n');//使用 printf 输出printf("%c %c %c\n", a, b, c);return 0;
}

运行结果:
1 $ X
1 $ X

putchar函数每次只能输出一个字符,输出多个字符需要调用多次。

字符与整数

我们知道,在计算机中存储字符时并不是真的要存储字符实体,而是存储字符在字符集中的编号(编码值)。对于char类型,他实际上存储的就是字符的ASCII码。

无论在哪个字符集中,字符编号都是一个整数。从这个角度看,字符类型和整数类型本质上没什么区别。

我们也可以给字符类型赋值一个整数,或者以整数形式输出字符类型。反过来也可以给整数类型赋值一个字符,或者以字符形式输出整数。

例:

#include <stdio.h>
int main()
{char a = 'E';char b = 70;int c = 71;int d = 'H';printf("a: %c, %d\n", a, a);printf("b: %c, %d\n", b, b);printf("c: %c, %d\n", c, c);printf("d: %c, %d\n", d, d);return 0;
}

输出结果:
a: E, 69
b: F, 70
c: G, 71
d: H, 72

在 ASCII 码表中,字符 ‘E’、‘F’、‘G’、‘H’ 对应的编号分别是 69、70、71、72。

a、b、c、d实际上存储的都是整数:

  • 当给a、d赋值一个字符时,字符会先转换成ASCII码再存储
  • 当给b、c赋值一个整数时,不需要任何转换,直接存储就可以
  • 当以%c输出a、b、c、d时,会根据ASCII码表将整数转换成对应的内存
  • 当以%d输出a、b、c、d时,不需要任何转换,直接输出就可以

可以说,ASCII码表将英文字符和整数关联了起来。

初识字符串

之前我们提到了字符串的概念,也了解过如何输出字符串,但是没有说如何用变量存储一个字符串。其实在c语言中,没有专门的字符串类型,只能使用数组或者指针来间接的存储字符串。

数组和指针的概念,后面我们会具体说,这里先记下这两种字符串的表示方式:

char str1[] = "http://www.baidu.com";
char *str2 = "百度一下,你就知道";

[]和*是固定写法,我们这里先记住。他们可以通过专用的puts函数和通用的printf函数输出。

演示:

#include <stdio.h>
int main()
{char web_url[] = "http://www.baidu.com";char *web_name = "百度一下,你就知道";puts(web_url);puts(web_name);printf("%s\n%s\n", web_url, web_name);return 0;
}

中文字符

c语言不仅能处理英文,他是一门全球化的编程语言,他支持世界上任何一个国家的语言文化,包括中文、日语、韩语等。

中文字符的存储

正确存储中文需要解决两个问题。

1)足够长的数据类型

char只能处理ASCII编码中的英文字符,是因为char字符太短,只有一个字节,容纳不了中文几万个汉字,要想处理中文字符,必须得使用更长的数据类型。

一个字符在存储前会转换成他在字符集中的编号,这样的编号是一个整数,所以我们可以用整型来存储一个字符,比如unsigned short、unsigned int、unsigned long等。

2)选择包含中文的字符集

c语言规定,对于使用ASCII编码外的单个字符,如汉语、日语、韩语等,也就是专门的字符类型,需要使用宽字符的编码方式。常见的宽字符编码有UTF-16和UTF-32,他们都是基于Unicode字符集的,能支持全球的语言文化。

在真正实现时,微软编译器(内嵌于Visual Studio或Visual C++中)采用UTF-16编码,使用2个字节存储一个字符,用unsigned short类型就可以容纳。GCC、LLVM/Clang(内嵌于Xcode)采用UTF-32编码,使用4个字节存储,用unsigned int就可以容纳。

对于编号较小的字符,UTF-16采用2个字节存储;对于编号较大的字符,UTF-16使用4个字节存储。但是,全球常见的字符也就几万个,使用两个字节足够,只有极其罕见,或者很古老的字符才会用到4个字节。
微软编译器使用两个字节来存储UTF-16编码的字符,虽然不能包含所有的Unicode字符,但也足够容纳全球常见的字符了。基本满足了软件开发需求。使用2个字节存储的另一个好处是可以节省内存,而使用4个字节会浪费50%以上的内存。

不同编译器可以使用不同的整数类型。如果我们的代码使用unsigned int来存储宽字符,那么在微软编译器下就是一种浪费;如果我们的代码用unisigned short来存储宽字符,那么在GCC、LLVM/Clang下就不够。

为了解决这个问题,C语言推出了一种新的类型,叫做wchar_t。w是wide的首字母,t是type的首字符,wchar_t的意思就是宽字符类型。wchar_t的长度由编译器决定:

  • 在微软编译器下,他的长度是2,等价于unsigned short
  • 在GCC、LLVM/CLang下,他的长度是4,等价于unsigned int

wchar_t 其实是用typedef关键字定义的一个别名,typedef的用法我们以后再说。现在我们只需要知道 wchar_t 在不同编译器下长度不一样。

前文我们说到,单独的字符由单引号' '包围,如'A''B'等。但是这样的字符只能使用ASCII编码,要想使用宽字符编码的方式,就要加上L前缀,如L'A'L'B'L'中'等。

注意,加上 L 前缀后,所有字符都将成为宽字符,占用2个字节或4个字节的内存,包括ASCII中的英文字符。

例:

wchar_t a = L'A';  //英文字符(基本拉丁字符)
wchar_t b = L'9';  //英文数字(阿拉伯数字)
wchar_t c = L'中';  //中文汉字
wchar_t d = L'国';  //中文汉字
wchar_t e = L'。';  //中文标点
wchar_t f = L'ヅ';  //日文片假名
wchar_t g = L'♥';  //特殊符号
wchar_t h = L'༄';  //藏文

之后,我们将不加 L 前缀的字符成为窄字符,将加上 L 前缀的字符成为宽字符。窄字符使用ASCII编码,宽字符使用UTF-16或UTF-32编码。

宽字符的输出

putchar、printf智能输出不加 L 前缀的窄字符,要想输出宽字符,我们必须使用 <wchar.h>头文件中的宽字符输出函数,他们分别是 putwchar 和 wprintf :

  • putwchar 函数专门用来输出一个宽字符,他和putchar的用法类似
  • wprintf是通用的、格式化的宽字符输出函数,他出了可以输出单个字符,还可以输出字符串。宽字符对应的格式控制符为%lc

另外,在输出宽字符之前还要使用 setlocale 函数进行本地化设置,告诉程序如何才能正确的处理各个国家文化。

如果希望设置为中文简体环境,在Windows下写作:

setlocale(LC_ALL, “zh-CN”);

在Linux和Mac OS下写作:

setlocale(LC_ALL, “zh_CN”);

setlocale 函数位于 <locale.h>头文件中,使用前我们必须引入他。

完整演示宽字符的输出:(Linux、Mac OS)

#include <wchar.h>
#include <locale.h>int main(){wchar_t a = L'A';  //英文字符(基本拉丁字符)wchar_t b = L'9';  //英文数字(阿拉伯数字)wchar_t c = L'中';  //中文汉字wchar_t d = L'国';  //中文汉字wchar_t e = L'。';  //中文标点wchar_t f = L'ヅ';  //日文片假名wchar_t g = L'♥';  //特殊符号wchar_t h = L'༄';  //藏文//将本地环境设置为简体中文setlocale(LC_ALL, "zh_CN");//使用专门的 putwchar 输出宽字符putwchar(a);  putwchar(b);  putwchar(c);  putwchar(d);putwchar(e);  putwchar(f);  putwchar(g);  putwchar(h);putwchar(L'\n');  //只能使用宽字符//使用通用的 wprintf 输出宽字符wprintf(L"Wide chars: %lc %lc %lc %lc %lc %lc %lc %lc\n",  //必须使用宽字符串a, b, c, d, e, f, g, h);return 0;
}

运行结果:
A9中国。ヅ♥༄

Wide chars: A 9 中 国 。
ヅ ♥ ༄

宽字符串

给字符串加上 L 前缀就变成了宽字符串,他包含的每个字符都是宽字符,一律采用UTF-16或UTF-32编码。输出宽字符串可以使用 <wchar.h>头文件中的 wprintf函数,对应的格式控制符是%ls

如何使用宽字符串演示:

#include <wchar.h>
#include <locale.h>int main(){wchar_t web_url = L"http://www.baidu.com"wchar_t *web_name = L"百度一下,你就知道";//将本地环境设置为简体中文setlocale(LC_ALL, "zh_CN");//使用通用的 wprintf 输出宽字符wprintf(L"web_url: %ls \nweb_name: %ls\n", web_url, web_name);return 0;
}

运行结果:
web_url: http://www.baidu.com
web_name: 百度一下,你就知道

实际上,不加 L 前缀的窄字符也可以处理中文,那么他和加了 L 前缀的宽字符串又有什么区别呢?我们接着往后看。

C语言到底使用什么编码?char类型的字符串使用什么编码?

c语言是70年代的产物,那个时候只有ASCII,各个国家的字符编码都还未走向成熟,所以c语言不可能从底层支持GB2312、GBK、Big5等国家编码,也不可能支持Unicode字符集。

有点c语言编程基础的可能认为c语言使用ASCII编码,字符在存储时会被转成ASCII码值,这是不对的。在c语言中,只有char类型的窄字符才使用ASCII编码,char类型的窄字符串、wchar_t 类型的宽字符和宽字符串都不能使用ASCII编码!

wchar_t类型的宽字符和宽字符串使用UTF-16和UTF-32编码。现在还剩下一个char类型的窄字符串,我们来详细说说。

对于char类型的窄字符串,c语言并没有规定使用哪种特定的编码,只要选用的编码能够适应当前的环境就可以。所以,char类型窄字符串的编码与操作系统和编译器有关。

但是,可以肯定的说,现在计算机中,char类型窄字符串已经不再使用ASCII编码了,因为ASCII编码只能显示字母、数字等英文字符,对汉语、日语、韩语等其他地区的字符无能为力。

我们要从两个方面来聊聊char类型窄字符串的编码。

源文件使用什么编码

源文件用来保存我们编写的代码,他最终会被存储到本地硬盘,或者远程服务器,这个时候就要尽量压缩文件体积,以节省硬盘空间或者网络流量,而代码中大部分字符都是ASCII编码中的字符,用一个字节足以容纳,所以 UTF-8 编码是一个不错的选择。

UTF-8兼容ASCII,代码中大部分字符串可以用一个字节保存;另外UTF-8基于Unicode,能支持全世界的字符。

常见的IDE或者编辑器,如Xcode、Sublime Text、Vim等,在创建源文件时一般默认使用UTF-8编码。不过 Visual Studio默认使用本地编码来创建源文件。

所谓本地编码,就是像GBK、Big5、Shift-JIS等这样的国家编码(地区编码);针对不同国家发行的操作系统,默认的本地编码一般不同。简体中文的Windows默认的本地编码是GBK。

对于编译器来说,他往往支持多种编码格式的源文件。微软编译器、GCC、LLVM/Clang(内嵌于Xcode)都支持UTF-8和本地编码文件,不过微软编译器还支持UTF-16编码的源文件。如果考虑到通用性,就只能使用UTF-8和本地编码了。

char类型窄字符串使用什么编码

前面我们说到,用puts或者printf可以输出字符串,代码如下:

#include <stdio.h>
int main()
{puts("百度一下");printf("http://www.baidu.com");return 0;
}

"百度一下""http://www.baidu.com"就是需要被处理的窄字符串,程序运行过后,他们会被载入到内存中。这里还用到了中文,肯定不能使用ASCII编码。
1)微软编译器使用本地编码来保存这些字符。不同地区的Windows版本默认的本地编码不一样,所以,同样的窄字符串在不同Windows版本下使用不一样。对于简体中文版的Windows,使用的是GBK编码。

2)GCC、LLVM/Clang编译器使用和源文件相同的编码来保存这些字符:如果源文件使用的是UTF-8编码,那么这些字符也使用UTF-8编码;如果源文件使用GBK编码,那么这些字符也使用GBK编码。

对于代码中需要被处理的窄字符串,不同编译器差别还是很大的。不过可以肯定的是,这些字符始终都使用窄字符(多字节字符)编码。

正是由于这些字符使用UTF-8、GBK等编码,而不是使用ASCII编码,所以他们才能包含中文。

总结

对于char类型的窄字符,始终使用ASCII编码。

对于wchar_t类型的宽字符和宽字符串,使用UTF-16或者UTF-32编码,他们都是基于Unicode字符集的。

对于char类型的窄字符串,微软编译器使用本地编码,GCC、LLVM/Clang使用和源文件编码相同的编码。

另外,处理窄字符和处理宽字符用的函数也不一样:

  • stdio.h 头文件中的putchar、puts、printf函数只能用来处理窄字符
  • wchar.h 头文件中的putwchar、wprintf函数只能用来处理宽字符

关于编码字符集和运行字符集

站在专业的角度来说,源文件使用的字符集被称为编码字符集,也就是写代码的时候使用的字符集;程序中的字符或者字符串使用的字符集被称为运行字符集,也就是程序运行后使用的字符集。

源文件需要保存到硬盘,或者在网络上传输,使用的编码要尽量节省存储空间,同时要方便跨国交流,所以一般使用UTF-8,这就是选择编码字符集的标准。

程序中的字符或者字符串,在程序运行后必须被载入到内存,才能进行后续的处理,对于这些字符来说,要尽量选用能够提高处理速度的编码,如UTF-16和UTF-32编码就能够快速定位(查找)字符。

编码字符集是站在存储和传输的角度,运行字符集是站在处理或者操作的角度,所以他们并不一定相同。

转义字符

字符集(Character Set)为每个字符分配了唯一的编号,我们不妨将它称为编码值。在c语言中,一个字符出了可以用他的实体(也就是真正字符)表示,还可以用编码值表示。这种用编码值来间接地表示字符的方式称为转义字符(Escape Character)

转义字符以\或者\x开头,以\开头表示后跟八进制形式的编码值,以\x开头表示后跟十六进制形式的编码值。对于转义字符来说,只能使用八进制或者十六进制。

字符 1、2、3、a、b、c 对应的 ASCII 码的八进制形式分别是 61、62、63、141、142、143,十六进制形式分别是 31、32、33、61、62、63。下面的例子演示了转义字符的用法:

char a = '\61';  //字符1
char b = '\141';  //字符a
char c = '\x31';  //字符1
char d = '\x61';  //字符a
char *str1 = "\x31\x32\x33\x61\x62\x63";  //字符串"123abc"
char *str2 = "\61\62\63\141\142\143";  //字符串"123abc"
char *str3 = "The string is: \61\62\63\x61\x62\x63"  //混用八进制和十六进制形式

转义字符既可以用于单个字符,也可以用于字符串,并且一个字符串中可以同时使用八进制形式和十六进制形式。

转义字符的初衷是用于ASCII编码,所以他的取值范围有限:

  • 八进制形式的转义字符最后后跟三个数字,也即\ddd,最大取值是\177
  • 十六进制形式的转义字符最多后跟两个数字,即\xdd,最大取值是\x7f

超出范围的转义字符行为是未定义的,有的编译器会将编码值直接输出,有的会报错。

对于 ASCII 编码,0~31(十进制)范围内的字符为控制字符,它们都是看不见的,不能在显示器上显示,甚至无法从键盘输入,只能用转义字符的形式来表示。不过,直接使用 ASCII 码记忆不方便,也不容易理解,所以,针对常用的控制字符,C语言又定义了简写方式,完整的列表如下:

\n\t是最常用的两个转义字符:

  • \n用来换行,让文本从下一行的开头输出,前面我们已经多次使用
  • \t用来占位,一般相当于四个空格,或者tab键的功能

单引号、双引号、反斜杠是特殊字符,不能直接表示:

  • 单引号是字符类型的开头和结尾,要使用\'表示,也即'\''
  • 双引号是字符串的开头和结尾,要使用\"表示,也即"abc\"123"
  • 反斜杠是转义字符的开头,要使用\\表示,也即'\\',或者"abc\\123"

转义字符例子:

#include <stdio.h>
int main(){puts("C\tC++\tJava\n\"C\" first appeared!");return 0;
}

运行结果:
C C++ Java
“C” first appeared!

C语言学习(十二)C语言中的字符(宽字符与窄字符)、从字符谈谈C语言的编码、转义字符相关推荐

  1. Go语言学习十二 变量和常量

    本文最初发表在我的个人博客,查看原文,获得更好的阅读体验 Go 使用var关键字声明变量:使用关键字const声明常量.变量可以像常量一样初始化. 一 变量 1.1 变量声明 语法: var 变量名 ...

  2. C语言学习笔记Day3——持续更新中... ...

    上一篇文章C语言学习笔记Day2--持续更新中- - 八. 容器 1. 一维数组 1.1 什么是一维数组 当数组中每个元素都只带有一个下标(第一个元素的下标为0, 第二个元素的下标为1, 以此类推)时 ...

  3. (转)SpringMVC学习(十二)——SpringMVC中的拦截器

    http://blog.csdn.net/yerenyuan_pku/article/details/72567761 SpringMVC的处理器拦截器类似于Servlet开发中的过滤器Filter, ...

  4. (c语言)编程输出二维数组中元素的最大值,要求用指针实现。

    (c语言)编程输出二维数组中元素的最大值,要求用指针实现. #include<stdio.h> #include<stdlib.h> #define N 6 //行数 #def ...

  5. jQuery学习(十二)—jQuery中对象的查找方法总结

    jQuery学习(十二)-jQuery中对象的查找方法总结 一.find方法 作用:在元素1中查找元素2,类似于选择器中的后代选择器 格式:元素1.find(元素2),元素2为CSS选择器或者jQue ...

  6. OpenCV与图像处理学习十二——图像形状特征之HOG特征

    OpenCV与图像处理学习十二--图像形状特征之HOG特征 一.图像特征理解 1.1 颜色特征 1.2 纹理特征 1.3 形状特征 1.4 空间关系特征 二.形状特征描述 2.1 HOG特征 2.1. ...

  7. PyTorch框架学习十二——损失函数

    PyTorch框架学习十二--损失函数 一.损失函数的作用 二.18种常见损失函数简述 1.L1Loss(MAE) 2.MSELoss 3.SmoothL1Loss 4.交叉熵CrossEntropy ...

  8. C1认证学习十二(网络拓扑)

    C1认证学习十二(网络拓扑) 任务背景 互联网是一个广义的概念,它泛指是一切通过网路连接在一起的计算机的集合,所以,若果只是局部观察,那就不能再说互联网是一个互联的了,那么,如果说对于一个公司来说,具 ...

  9. Js高级程序设计第三版学习(十二章)

                                  Js高级程序设计第三版学习(十二章) 第十二章 DOM2和DOM3   1.样式: 访问样式属性 任何支持style特性的HTML元素都有一 ...

  10. 唐 库利超级计算机,第七卷 乖离性 百万亚瑟王_第二百五十二章 绝望中的希望...

    第七卷 乖离性 百万亚瑟王_第二百五十二章 绝望中的希望 赫布里底训练大厅. "诸位,结果已经分析出来了,丘库林,也就是被你们捉到的闯入者,他身上的神装的确是断绝时代的遗物不错,而且,这件神 ...

最新文章

  1. 日常必备的16条Linux命令
  2. FFT算法的完整DSP实现(转)
  3. WEB API系列(一):WEB API的适用场景、第一个实例
  4. ESP32 + ESP-IDF |GPIO 01 - 驱动外部两个LED灯,以每300ms的时间间隔闪烁
  5. C#委托和事件实现观察者模式
  6. SQL Server 2008 对 T-SQL 语言的增强(转载)
  7. static、const、static const、const static成员的初始化问题
  8. 电容的耐压值选择---陶瓷电容、钽电容、电解电容
  9. python 函数调用自身_Python-函数的递归调用
  10. Android Fingerprint完全解析(三) :Fingerprint Hal层分析
  11. tp无线路由器设置打印服务器,打印服务器复位大全tplink路由器设置
  12. 使用ORC识别图片的文字
  13. 学习突围3 - 关于精力
  14. RNA 10. SCI 文章中基因表达富集之 KEGG 注释
  15. RAID磁盘阵列是什么
  16. 计算机财务管理知识点,财务管理知识点梳理(财务基础必背知识点整理篇)
  17. 计算机网络 自顶向下(5)链路层——学习笔记
  18. 程序设计思维与实践 Week9 作业 A 咕咕东的目录管理器
  19. 微软计划为 Chrome 添加光标浏览模式
  20. 利用JAVA多线程模拟售票系统,对统一资源进行处理

热门文章

  1. html scr 拼接,css3scr.html
  2. 【转载】深入浅出的讲解傅里叶变换
  3. windows上搭建IOS开发环境
  4. python6翻了_Python-6.BeautifulSoup网络爬虫
  5. 嵌入式中SIM卡接口电路设计
  6. 1367 二叉树中的列表
  7. Sklearn之KMeans算法
  8. 华为C8815 默认logcat信息
  9. win7系统笔记本做无线路由器
  10. tf.cast()函数介绍和示例