字符串
字符串常量的例子非常常见。字符串常量是放在一对双引号中的一串字符和符号。一对双引号之间的任何内容都会被编译器视为字符串,包括特殊字符和嵌入的空格。每次使用 printf 显示信息时,就将该信息定义成字符串常量了。以下是字符串的例子:
第二个字符串中带有换行符。第三个字符串输出双引号和斜线。
字符串的串就是一连串,所以字符串在内存中的存储是这样的:
上图中每个字符串的末尾都添加了代码值 0 的特殊字符,这个字符称为空字符,写为 \0。C 中的字符串总是由 \0 字符结束。所以字符串的长度永远都比字符串中的字符数多 1。
可以自己将 \0 字符添加到字符串的结尾,但是这样会使得字符串的末尾有两个 \0 字符。下面说明空字符 \0 是如何运作:
#include <stdio.h>
int main(void)
{
printf("The character \0 is used to terminate a string.");
return 0;
}Code language: PHP (php)
以上程序输出:
这不是期望的结果,因为它仅仅显示了字符串的第一部分。这个程序显示了前两个字符串就结束输出。因为在字符串中有 \0,printf 函数遇到第一个空字符 \0 就会停止输出。即使末尾有另外一个 \0 也永远不会执行它。
#include <stdio.h>
int main(void)
{
printf("\\\\www.foxdevelop.com \\0\n\"Foxdevelop\"");
return 0;
}Code language: PHP (php)
C 语言对变量存储字符串的语法没有特殊规定,而且 C 根本就没有字符串变量,也没有处理字符串的特殊运算符。但这不成问题,因为标准库提供了许多函数来处理字符串。整数 int 这些类型 C 语言提供 + - * 等运算符,但是字符串 C 语言没有提供类型也没有符号运算,不过标准库里面有人使用 C 语言写好了代码。比如假如希望 C 语言使用 + -,其实可以使用其他方式去实现,不需要写到这门语言里面。
之前说的 char 类型是用来存储字符的,字符串就是多个字符连起来,数组可以存储多个相同类型的数据。换个角度思考,就可以使用 char 类型的数组来保存字符串。这是字符串变量的最简单的方式:
char saying[20];Code language: CSS (css)
这个变量可以存储一个至多包含 19 个字符的字符串,因为必须给终止字符提供一个数组元素,也就是需要一个 \0。当然也可以使用该数组存储 20 个字符,但这样就不是一个字符串了。
也可以用以下的声明初始化前面的字符串变量:
char saying[] = "this is a string.";Code language: JavaScript (javascript)
这里没有明确定义数组的大小,编译器指定一个足以容纳这个字符串常量的数值。在这个例子中它是 18,其中 17 个元素用于存储字符串中的字符,再加上一个额外的终止字符 \0。当然可以指定数值,但要确保它一定正确。
也可以用一个字符串初始化 char 类型数组的部分元素:
char str[40] = "To be";Code language: JavaScript (javascript)
这里编译器会使用指定字符串的字符初始化 str[0] 到 str[4] 的前五个元素,而 str[5] 则是给 \0。数组的所有 40 个元素都会被分配空间,可以以任意方式使用。
初始化一个 char 数组,将它声明为常量,是处理标准信息的好方法:
const char message[] = "The end of the world is nigh.";Code language: JavaScript (javascript)
将 message 声明为常量,它就不会在程序中被更改。只要试图更改它,编译器都会产生错误信息。当标准信息在程序中的许多地方使用时,这种定义方法特别有用,可以防止在程序的其他部分意外地修改这种常量。当然,如果必须改变这条信息,就不应将它指定为 const。
要引用存储在数组中的字符串时,只需要使用数组名即可。例子:
printf("\n The message is:%s", message);Code language: JavaScript (javascript)
printf 会将第一个参数的 %s 的位置输出 message 数组中的连续的字符,直到遇到 \0 为止。这个例子将初始化两个字符串,然后确定每个字符串有多少个字符,不包含空字符:
#include <stdio.h>
int main(void)
{
char str1[] = "To be or not to be";
char str2[] = ",that is the question";
int count = 0;
while (str1[count] != '\0')
count++;
printf("\n字符串 \"%s\" 的长度是 %d 个字符.", str1, count);
count = 0;
while (str2[count] != '\0')
count++;
printf("\n字符串 \"%s\" 的长度是 %d 个字符.\n", str2, count);
return 0;
}Code language: PHP (php)
\0 字符的 ASCII 码是 0,对应于布尔值的 false,其他 ASCII 码都不是 0。
可以使用 char 类型的二维数组存储字符串,数组的每一行都用来存储一个字符串,就可以存储一整串字符串,通过一个变量名引用它们,例如:
这个包含 3 行,每行 32 个字符。这样就相当于有三个字符串了。比如 sayings[0] 就是 “Manners maketh man.”,32 刚好是最长的字符串的长度包含 \0。
#include <stdio.h>
int main(void)
{
char str1[3][32] = {"To be or not to be",
"www.foxdevelop.com",
"www.baidu.com"};
printf("%c\n", str1[1][3]);
printf("%s\n", str1[1]);
return 0;
}Code language: PHP (php)
在字符串数组中,必须制定第二维的大小,也可以让编译器计算数组有多少个字符串。上面可以写成:
可以用下面代码输出以上三个字符串:
上面代码 sizeof(str1) 是整个二维数组的字节数,sizeof(str1[0]) 则是表示里面的一个字符串的长度,因为这三个字符串都是固定长度 32,所以只需要用第一个字符串就可以:
#include <stdio.h>
int main(void)
{
char str1[][32] = {"To be or not to be",
"www.foxdevelop.com",
"www.baidu.com",
"www.taobao.com"};
for(int i = 0; i < sizeof(str1) / sizeof(str1[0]); i++)
{
printf("%s\n", str1[i]);
}
return 0;
}Code language: PHP (php)
更改前面的例子,确定二维数组中字符串个数和每个字符串的长度:
#include <stdio.h>
int main(void)
{
char str[][70] = {
"Computers do what you tell them to do,not what you want them to do.",
"When you put somthing in memory,remember where you put it.",
"Never test for a condition you don't know what to do with."
};
unsigned int count = 0;
unsigned int strCount = sizeof(str) / sizeof(str[0]);
printf("一共有%u个字符串。\n", strCount);
for(unsigned int i = 0; i < strCount; i++)
{
count = 0;
while(str[i][count])
++count;
printf("字符串:\"%s\"占%u个字符\n", str[i], count);
}
return 0;
}Code language: PHP (php)
前面说明了确定字符串长度的代码,但其实并不需要编写这样的代码。标准库提供了一个执行该操作的函,和许多处理字符串的其他函数。要使用他们,必须把 string.h 头文件包含在源文件中。
标准库提供的字符串处理函数集合并不安全,他们使代码包含错误的可能性很大,有时这些错误很难查找。标准库提供了处理字符串函数的可选版本,他们更安全更不容易出错,因为会检查数组的维度,确保空间足够。这些可选函数是以 _s 结尾。
很容易确定 C 编译器附带的标准库是否支持这些可选函数:
#include <stdio.h>
#define __STDC_WANT_LIB_EXT1__ 1
#include <string.h>
int main(void)
{
#if defined __STDC_LIB_EXT1__
printf("可选函数有定义\n");
#else
printf("可选函数没有有定义\n");
#endif
return 0;
}Code language: PHP (php)
但是发现编译器不支持 C11。点击 Settings -> Compiler,打开 Global compiler settings 界面,将 “Have g++ follow the C++11 ISO C++ language” 选项打上勾,最后点击 OK,可以设置编译器让它支持 C11。
strnlen 或者 strnlen_s 函数返回字符串的长度,它需要两个参数,一个是字符串的地址另外一个是数组的大小。该函数把字符串的长度返回为一个 size_t 类型的整数值。如果第一个参数是 NULL,就返回 0。如果第二个参数值的元素个数中,第一个参数指定的数组不包括 \0,就返回第二个参数值作为字符串的长度。例如:
以上是 strnlen_s,这是扩展方法但有些编译器不支持。这时候可以使用:
#include <string.h>
#include <stdio.h>
int main(void)
{
char str[] = "foxdevelop.com";
printf("%zd", strlen(&str));
}Code language: PHP (php)
以上没有使用扩展库,最后返回 14。
扩展库中的 strcpy_s 函数用法如下,它把一个字符串变量的内容赋予另外一个字符串。strcpy_s 三个参数:第一个是复制的目标,第二个是一个整数指定第一个参数的大小,第三个是源字符串。指定目标字符串的长度可以使函数避免覆盖目标字符串最后一个字符后面的内存。如果源字符串比目标字符串长就会发生这种情形。如果一切正常该函数就返回 0 否则返回非 0。
再看标准的复制函数 strcpy(),它会把第二个参数指定的字符串复制到第一个参数指定的位置上,不检查目标字符串的容量:
#include <stdio.h>
int main(void)
{
char str[] = "foxdevelop.com";
char str2[8];
strcpy(str2, str);
printf("%s", str2);
return 0;
}Code language: PHP (php)
连接是把一个字符串连接到另一个字符串的尾部,这是很常见的需求。例如把两个或多个字符串合成一条信息。strcat_s() 函数需要三个参数,字符串 str1 和 str2 连接在一起,str2 追加到 str1 上。str1 后面的 \0 会被覆盖,然后再在后面添加一个 \0。如果一切正常就返回 0,如果 str1 不够大不能追加 str2 则返回非 0。
由于编译器不支持扩展库,所以只能使用 strcat:
#include <stdio.h>
#include <string.h>
int main(void)
{
char name[] = "Foxdevelop";
char str[20] = "foxdevelop.com";
strcat(&str, &name);
printf("%s", str);
return 0;
}Code language: PHP (php)
如果是数值,比较两个数很好理解,比如 11 > 3。字符串库提供的函数还可以比较字符串,确定一个字符串是大于还是小于另外一个字符串。两个字符串的比较是基于它们的字符码。如果两个字符串相同则它们相等。要确定第一个字符串是小于还是大于第二个字符串,比较两个字符串中第一对不同的字符。例如如果第一个字符串中某字符的字符码小于第二个字符串中对应的字符,则第一个字符串就小于第二个字符串。
比较字符串没有可选函数,使用的标准函数是 strcmp(str1, str2),比较两个字符串,返回一个小于、等于或大于 0 的 int 值,分别对应 str1 小于、等于和大于 str2:
#include <stdio.h>
int main(void)
{
char str1[] = "foxdevelop.cam";
char str2[] = "foxdevelop.cn";
int res = strcmp(str1, str2);
printf("%d", res);
return 0;
}Code language: PHP (php)
strncmp 函数用来比较两个字符串的前 n 个字符,它比 strcmp 多了第三个参数,第三个参数类型是 size_t 用来指定比较的字符数。如果要处理的字符串中表示零件号或序列号的前十个字符,就可以使用这个函数:
#include <stdio.h>
int main(void)
{
char str1[] = "foxdevelop.cam";
char str2[] = "foxdevelop.cn";
int res = strncmp(str1, str2, 6);
printf("%d", res);
return 0;
}Code language: PHP (php)
头文件 string.h 声明了几个字符串搜索函数,但是在探讨它们之前,先了解一下下一章的主题——指针,这里需要这些基础知识以理解如何使用字符串搜索函数。
指针的概念
C 提供一个非常有用的变量类型叫做指针。指针是含有地址的变量,它含有内存中另一个包含数值的位置的引用。内存有一个编号,每个字节有个编号从 0 开始。函数 scanf 就是使用了地址。下面的第二条语句就定义了指针 pNumber:
int Number = 25;
int *pNumber = &Number;Code language: JavaScript (javascript)
然后就可以使用 *pNumber 来得到 Number 里面的值。* 是取消引用运算符,其作用是访问指针指定的地址中存储的数据:
#include <stdio.h>
int main(void)
{
int Number = 25;
int *pNumber = &Number;
printf("%d\n", Number);
printf("%d\n", *pNumber);
printf("%p\n", pNumber);
return 0;
}Code language: PHP (php)
函数 strchr() 在字符串中搜索给定的字符。它的第一个参数是要搜索的字符串,是 char 数组的地址。第二个参数是要查找的字符。这个函数会从字符串的开头开始搜索,返回在字符串中找到的第一个给定字符的地址。这是一个在内存中的地址,其类型是 char *。所以要存储这个返回值,必须创建一个能存储字符串地址的变量。如果没有找到给定的字符,函数就会返回 NULL。
#include <stdio.h>
int main(void)
{
char str[] = "foxdevelop.com";
char ch = 'm';
char *pch = strchr(str, ch);
if(pch)
{
printf("%p\n", pch);
printf("%c\n", *pch);
}
else
{
printf("no found");
}
return 0;
}Code language: PHP (php)
也可以写成:
#include <stdio.h>
int main(void)
{
char str[] = "foxdevelop.com";
char ch = 'm';
char *pch = strchr(str, ch);
if(pch)
{
printf("%s\n", pch);
}
else
{
printf("no found");
}
return 0;
}Code language: PHP (php)
NULL 指针值转换为 bool 值是 false,非 NULL 是 true。所以可以使用 if 来判断是否找到字符。
strstr 函数是所有搜索函数中最有用的函数,它查找一个字符串中的子字符串,返回找到的第一个子字符串的位置指针。同样如果找不到匹配的子字符串就返回 NULL:
#include <stdio.h>
int main(void)
{
char text[] = "foxdevelop.com";
char word[] = "mn";
char *pFound = NULL;
pFound = strstr(text, word);
printf("%s", pFound);
return 0;
}Code language: PHP (php)
#include <stdio.h>
#include <string.h>
int main(void)
{
char str1[] = "This string contains the holy grail.";
char str2[] = "the holy grail";
char str3[] = "the holy grill";
if(strstr(str1, str2) == NULL)
printf("\n\"%s\" was not found.", str2);
else
printf("\n\"%s\" was found in \"%s\"", str2, str1);
if(strstr(str1, str3) == NULL)
printf("\n\"%s\" was not found.", str3);
else
printf("\nWe shouldn't get to here!");
return 0;
}Code language: PHP (php)
C 库函数 char *strtok(char *str, const char *delim) 分解字符串 str 为一组字符串,delim 为分隔符。该函数返回被分解的第一个子字符串,如果没有可检索的字符串则返回一个空指针:
#include <string.h>
#include <stdio.h>
int main(void)
{
char str[80] = "This is - www.baidu.com - website";
const char s[2] = "-";
char *token;
token = strtok(str, s);
while(token != NULL)
{
printf("%s\n", token);
token = strtok(NULL, s);
}
return 0;
}Code language: PHP (php)
当 strtok() 在参数 s 的字符串中发现参数 delim 中包含的分割字符时,则会将该字符改为 \0 字符。在第一次调用时 strtok() 必需给予参数 s 字符串,往后的调用则将参数 s 设置成 NULL。每次调用成功则返回指向被分割出片段的指针。
下面例子判断从键盘输入的一个字符串中有多少个数字、字母和标点符号:
#include <stdio.h>
#include <ctype.h>
int main(void)
{
char buffer[80];
int i = 0;
int num_letters = 0;
int num_digits = 0;
printf("\nEnter an interesting string of less than 80 characters:\n");
gets(buffer);
while(buffer[i] != '\0')
{
if(isalpha(buffer[i]))
num_letters++;
if(isdigit(buffer[i++]))
num_digits++;
}
printf("\nYour string contained %d letters and %d digits.\n",
num_letters, num_digits);
return 0;
}Code language: PHP (php)
头文件 stdlib.h 声明了一些将字符串转换成数值的函数。这些函数会忽略前导空白:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char str[] = "10";
int num = atoi(str);
printf("%d", num * 10);
return 0;
}Code language: PHP (php)