C 语言重拾【二】字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <string.h> // 提供strlen()函数的原型
#define DENSITY 62.4 // 人体密度(单位:磅/立方英尺)
int main () {
float weight, volume;
int size, letters;
char name [40]; // name 是一个可容纳40个宇符的数组

printf("Hi! What's your first name?\n");
scanf("%s", name);
printf("%s, what's your weight in pounds?\n", name);
scanf("%f", &weight);
size = sizeof name;
letters = strlen(name);
volume = weight / DENSITY;
printf("Well, %s, your volume is %2.2f cubic feet. \n", name, volume);
printf("Also, your first name has %d letters, \n", letters);
printf("and we have %d bytes to store it. \n", size);
return 0;
}

运行程序,输入结果如下:

Hi! What’s your first name?
Christine
Christine, what’s your weight in pounds?
154
Well, Christine, your volume is 2.47 cubic feet.
Also, your first name has 9 letters, and we have 40 bytes to store it.

该程序包含以 下新特性。

  • 用数组(array)储存宇符串(character string)。在该程序中,用户输入的名被储存在数组中,该数组占用内存中 40 个连续的字节,每个字节储存一个字符值。
  • 使用名 %s 转换说明来处理字符串的输入和输出。注意,在 scanf() 中,name 没有 & 前缀,而 weight
    有(稍后解释,&weightname 都是地址)。
  • 用 C 预处理器把字符常量 DENSITY 定义为 62.4。
  • 用 C 函数 strlen() 获取字符串的长度。

char 类型数组和 null 字符

C 语言没有专门用于储存字符串的变量类型,字符串都被储存在 char 类型的数组中。数组由连续的存储单元组成,字符串中的宇符被储存在相邻的存储单元中,每个单元储存一个字符,如下图:

1

注意上图中数组末尾位置的字符 \0。这是空字符(null character),C 语言用它标记字符串的结束。

空字符不是数字 0,它是非打印字符,其 ASCII 码值是(或等价于)0。C 中的字符串一定以空字符结束,这意味着数组的容量必须至少比待存储字符串中的字符数多 1。因此,程序清单中有 40 个存储单元的字符串,只能储存 39 个字符,剩下一个字节留给空字符。

那么,什么是数组?可以把数组看作是一行连续的多个存储单元。用更正式的说法是,数组是同类型数据元素的有序序列。程序清单通过以下声明创建了一个包含 40 个存储单元(或元素)的数组,每个单元储存一个 char 类型的值:

1
char name [40];

name 后面的方括号表明这是一个数组,方括号中的 40 表明该数组中的元素数量。char 表明每个元素的类型,见下图。

2

字符串看上去比较复杂!必须先创建一个数组,把字符串中的字符逐个放入数组,还要记得在末尾加上一个 \0。还好,计算机可以自己处理这些细节。

字符串和字符

字符串常量 "x" 和字符常量 'x' 不同。区别之一在于,'x' 是基本类型(char),而 "x" 是派生类型(char数组);区别之二是 "x" 实际上由两个字符组成:'x' 和空字符 \0,见下图:

3

strlen() 和 sizeof

sizeof 运算符报告,name 数组有 40 个存储单元。但是,只有前 11 个单元用来储存 serendipity,所以 strlen() 得出的结果是 11。 name 数组的第 12个单元储存空字符,strlen() 并未将其计入。下图演示了这个概念。

4

对于 PRAISE,

1
#define PRAISE "You are an extraordinary being."

strlen() 得出的也是字符串中的字符数(包括空格和标点符号)。然而,sizeof 运算符给出的数更大,因为它把字符串末尾不可见的空字符也计算在内。该程序并末明确告诉计算机要给字符串预留多少空间,所以它必须计算双引号内的字符数。

另外,还要注意一点:上一章的 sizeof 使用了圆括号,但本例没有。圆括号的使用时机否取决于运算对象是类型还是特定量?运算对象是类型时,圆括号必不可少,但是对于特定量,可有可无。也就是说,对于类型,应写成 sizeof(char)sizeof (float);对于特定量,可写成 sizeof namesizeof 6.28。尽管如此,还是建议所有情况 下都使用圆括号,如 sizeof(6.28)

常量和 C 预处理器

#define

1
#define TAXRATE 0.015

编译程序时,程序中所有的 TAXRATE 都会被替换成 0.015。 这一过程被称为编译时替换(compile-timesubstitution)。在运行程序时,程序中所有的替换均己完成(见下图)。通常,这样定义的常量也称为明示常量(manifest constant)

请注意格式,首先是 #define,接着是符号常量名(TAXRATE),然后是符号常量的值(0.015)(注意,其中并没有 = 符号)。所以,其通用格式如下:

1
#define NANE value

实际应用时,用选定的符号常量名和合适的值来替换 NAMEvalue。注意,末尾不用加分号,因为这是一种由预处理器处理的替换机制。为什么 TAXRATE 要用大写?用大写表示符号常量是 C 语言一贯的传统。这样,在程序中看到全大写的名称就立刻明白这是一个符号常量,而非变量。大写常量只是为了提高程序的可读性,即使全用小写来表示符号常量,程序也能照常运行。尽管如此,初学者还是应该养成大写常量的好习惯。

另外,还有一个不常用的命名约定,即在名称前带 c_k_ 前缀来表示常量(如,c_levelk_line)。

符号常量的命名规则与变量相同。可以使用大小写字母、数字和下划线宇符,首字符不能为数字。

5

const 限定符

C90 标准新增了 const 关键字,用于限定一个变量为只读。其声明如下:

1
const int MONTHS = 12; // MONTHIS 在程序中不可更政,值为 12

这使得 MONTHS 成为一个只读值。也就是说,可以在计算中使用 MONTHS,可以打印 MONTHS,但是不能更改 MONTHS 的值。const 用起来比 #define 更灵活。(详细内容将在接下来的博客中推出。)

参考文献

  • C Primer Plus