欢迎光临散文网 会员登陆 & 注册

第 17 讲:指针和字符

2021-09-19 21:35 作者:SunnieShine  | 我要投稿

前文我们提到了近乎所有的指针的用法,不过指针和数组用起来确实挺难的,今天要说到的知识点是字符串、字符数组和指针三者的关系。


什么是字符?

在之前,我们对字符提到的内容很少,所以我们这里统一为大家介绍它们。

字符是以一个单引号包含起来,以区分数值字面量的存在。比如下面的这个写法:

这样就表示 c 变量是一个字符类型的变量,存储的元素是一个字符 1。这个字符 1 和普通的 1 不同,字符 1 一般用于输出才用到,而数字 1 则可以操作和计算。

在 C 语言里,用单引号把这些符号引起来可以和数字作出区分。但为了灵活使用,字符有时候也可以和数字进行转换,转换关系是通过一个叫做 ASCII 码表来搞定的。比如,在这个表里规定字符 A 的对应整数是 65,a 是 97,而字符 1 对应数值则是 49。

这样定义,来区分字符是有意义的。在计算机处理这些字符的时候,为了可以明确表达数字的行为是计算,而字符的行为是输出,所以 C 语言发明了类型这个体系,来区分字符和整数,所以字符 1 和数字 1 具有截然不同的用法,所以写法和声明(定义)语句上的赋值也是有不同的地方的。

如果你书写的方式是这样:

这样只能表达 i 变量的数值是 49,反之,如果你书写成

这样只能表达 c 变量表示的字符是 ASCII 码表里编号为 1 的那个字符。


使用字符和数字进行输出

在前文里,我们提到过数字的输出模式。如果是整数,输出则使用 %d 的格式化字符串,它会自动帮我们改为字符串的形式书写出来:

比如上面的这个写法,显然第一个 0 是没有意义的,但用整数输出的时候,你也就看不到这个 0 了,而只有 12 这个字符串。这就是数字的输出模式。

字符的输出使用的是 %c 这个格式化字符串。

这样你就可以在输出里得到一个 1。不过,如果你把这个单引号去掉了:

它就好比是

所以,输出的内容其实是 ASCII 码表里编号为 1 的那个字符,而并不是 1,因为 1 在编号第 49 号的地方,所以如果想输出 49,你还必须得写成

这样才可以得到一个字符 1


字符串

字符串(也经常被简称为,string),是字符的一组序列,即由多个字符构成的这个序列,称为字符串。它的表现形式有两种,一种是字面量字符串,一种是字符数组。我们都来说一下,以及它们的使用。


字面量字符串

字符可以用字面量的形式呈现,也可以用数字呈现,这表现出字符的两种形态。字符串也是这样。第一种呈现形式是通过双引号的形式出现的。比如 "Hello, world!\n" 就是一个合格的字面量字符串写法,用双引号把所有原封不动的字符序列包括起来。

不过,字面量字符串如何赋值和输出呢?赋值的话,它的类型应该是什么呢?

之前说过,字符串是一系列字符,所以它肯定不是单纯的 char 类型了。实际上,它可以使用字符指针来表示,也可以用字符数组表示:

C 语言允许第一种写法,让 char 类型的指针(干脆就叫 char * 类型)来“接收”这个字符串,这样的话,这个指针将会指向这个字符串的首地址(和数组一致,指向数组的第 0 号元素,即 charPointer 本身就等价于 &charPointer[0])。这种表示方式的优势是,不用去数和关心字符串到底有多长。

第二种写法也是允许的,C 语言会自动把这个字符串拆解为一个字符数组,然后挨个存放进去,最后得到这个数组的首地址。所以第二种写法下,charArray 变量名依旧等价于数组的首地址。

不过,需要你额外注意一点。它和普通元素类型的数组不同,字符串会被处理成字符数组的形式存储,然后返回的首地址可以用字符指针变量名或字符数组变量名接收。但是,字符串会自动为末尾添加一个结束标记字符 \0。这个字符是计算机内表达这个字符的写法,输出并非是一个反斜杠和一个数字 0,而是啥都没有。

\0 这个字符仅用于标记字符串的结尾用,没有其它的功能。而如果你非要把它当整数形式表示的话,它在 ASCII 码表里对应的整数也就是 0。所以 0 对应 \0 这个字符。

所以,这个知识点告诉我们,Hello, world! 这个字符串整体长度是 14,除了字面量给定的 10 个英文字母、2 个符号、一个空格以外,还默认在末尾带有一个字符 \0 标记,所以这个字符串长度是 14,并不是 13;而在第二种赋值格式里,这个中括号里就应该是 14,而不是 13。

输出字面量字符串的方式可以用 %s 的方式输出:

这样就可以完整输出 s 这个字符串的所有内容。另外,这样书写每次都必须写上 %s,所以你可以改写为这样:

即直接使用 puts 函数来输出一个字符串。


字符数组

显然,字面量字符串会被处理为字符数组,所以其实还可以书写为字符数组。

但请注意,如果写成字符数组,系统就不会自己给你补充 \0 字符了。所以这种写法必须自己补充一个 \0 符号在字符数组的末尾。

当然,你把 \0 写在字符数组的中间某处也可以,不过这个字符串就会从这个地方截断:

这样的话,这个字符数组长度就是 7 而不是原本的 13 或者 14 了。也就是说,这个 s 接收到的完整的字符数组序列到第一个 \0 就结束了,后面的内容都不在 s 里。不过在内存里,这些字符确实是挨着存储的,不过你必须通过语法的 bug 越界访问数组,才看得到它们了。

同样地,字符数组也具有退化赋值的特性,所以我们依旧可以使用这一点,把字符数组赋值给一个指针。

字符串的使用

讲完了两种字符串的书写格式后,我们来说明一下字符串的基本使用。

考虑如下实例。假设我们在写一个代码,输出一个数字是否是质数,代码大部分已经写好:

这就是前面的例子,只是我为了逻辑清晰,把 isPrimeint 改为了 bool 类型(只是这么做就只能让代码运行在允许 C99 标准的地方了)。

现在,考虑输出语句。可以从输出语句里看到,它们大部分输出信息都是相同的,只是多了一个 not。现在我们可以考虑把代码写成一个三目运算符的方式:

 printf("%d is %sa prime.\n", digit, isPrime ? "" : "not ");

注意输出语句里有一个 %sa,其实它是 %s 和一个字符 a 而已。它想要表示的是,某某数字,是(或不是)一个质数,这样的输出格式。当 isPrimetrue 的时候,我们就不需要 not 这个单词的修饰,所以这个输出一个空的字符串(只有一个 \0,反正里面其它东西都没有,到时候计算机会帮我们作出处理);否则加一个 not 修饰符来表示它”不是“质数。


常见字符串函数

我们经常要使用到一些字符串的处理模式,比如提取字符串里其中一部分字符、求长度等等。下面就来看看它们。


求字符串长度

我们需要实现一个函数 strlen,获取一个字符串的长度。这个算法比较容易实现:

可以从代码里看到,这就是一个基本的计算方式。首先我们定义一个结果 count 变量来表示一共多少字符,然后一个指针变量指向它的第一个元素,这样一会儿我们就可以使用 ++ 将变量移动指向,让其指向下一个字符信息。然后最终当 cur 指向 \0 的时候,表示字符统计完毕,跳出 while 循环,返回结果 count 即可。

另外,参数 str 在过程里都不用修改变量的本身指向和指向的内容,所以我们可以对 str 使用 const 修饰。

这个算法可以改写为这样:

使用 for 循环就会简单不少。另外,cur 指针如果不是指向 \0 字符就一直遍历,这个条件是 *cur != '\0',而前文说过 \0 的 ASCII 码表编码数值是 0,所以就相当于 *cur != 0,而这种写法在条件判断一节的时候说过,它等价于不写 != 0 部分,所以最终可以直接写成 *cur 来表示 *cur != '\0'。另外,str 就等价于 &str[0],所以初始赋值也可以不要地址符号和索引器部分 [0]


取出字符串的一部分字符

尝试写出一个 strstr 函数,来获取某个字符串里指定起始点和长度的其中一部分字符。

首先判断输入的所有参数是否都符合条件。如果取出的长度 length 比 0 还小,或者比字符串长度还大,是不满足要求的;同时,如果获取元素的起始索引 startIndex 比 0 小,或超过字符串长度,也依然是无效的。所以这个时候我们给用户输出一个空字符串,来表示错误(当然你也可以给它另外的东西,或者执行指定的某个行为来表示输入信息错误)。

否则,我们尝试让 ptr 指向 str[startIndex] 处的元素,然后不断更新 lastptr 的数值。当 last 减到 0 的时候就是获取结束的时候。

最后得到结果后,我们把 ptr 指向的字符改写为 \0 表示从这里截断,表示这里是字符串的结束。最后返回 str[startIndex] 的地址就可以了。

当然你也可以改写一下:

注意,返回值也可以使用 const 修饰符来表示不可以修改。

第 17 讲:指针和字符的评论 (共 条)

分享到微博请遵守国家法律