重新认识C语言 —— 字符串
常用的定义字符串的方法是:字符串常量、char数组、char指针和字符串数组。
- 重新认识C语言 —— 字符串
- 字符串的输入
- 字符串输出
- 字符串函数
- size_t strlen(const char *s);
- char *strcat(char *dest, const char *src);
- char *strncat(char *dest, const char *src, size_t n);
- int strcmp(const char *s1, const char *s2);
- int strncmp(const char *s1, const char *s2, size_t n);
- char *strcpy(char *dest, const char *src);
- char *strncpy(char *dest, const char *src, size_t n);
说明
本文实际上是《C Primer Plus》第5版的读书笔记。笔者已经使用C语言有一段时间,笔者对于C语言似乎已经足够熟悉,然而前段时间面试新的工作岗位,笔者居然不能够写出 strcpy() 的实现。于是决定重新阅读《C Primer Plus》,留意以前没有注意到的细节,重新认识C语言,并且将笔记贴出来。
字符串常量
字符串属于静态存储类。 如果在一个函数中使用字符串常量, 即使是多次调用了这个函数, 该字符串在程序整个运行过程中只存储一份。所以, char *str = “hello world!”, 实际上是将”hello world“的首地址赋值给str。
在Linux中,当程序运行时,字符串常量的地址会分配到代码段之后的数据段。
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("%s %p %c\n","hello world", &"hello world", *"hello world");
printf("%s %p\n","hello world", &"hello world");
return 0;
}
以上代码输出:
hello world 00405064
hello world 00405064
char数组
char数组的初始化:
char str[100] = "hello world";// 编译器无法预知需要多大的空间,所以必须指定数组的长度。
char str[] = "hello world";// 让编译器决定数组的长度
通常,被引用的字符串存储在可执行文件的数据段部分;当程序被加载到内存中时,字符串也被加载到内存中。被引用的字符串存储在静态存储区。程序开始运行后,为数组分配存储空间,这时候把被引用的字符串复制到数组中。
#include <stdio.h>
#include <stdlib.h>
#define STR "hello world"
int main()
{
char str[] = STR;
printf("%s %p, %s %p\n", str, str, STR, STR);
return 0;
}
输出结果如下:
hello world 0028FF34, hello world 00405064
可以看到, 数组str 与字符串常量STR 的地址是不一样的。char数组的初始化就是,在程序开始运行后,将字符串常量STR的内容复制到数组str中。
char指针
使用char指针初始化,其实是将字符串常量的首地址赋值给char指针。字符串常量引号中的内容指向静态存储区保存字符串的首地址。
char指针初始化之后,如果试图通过指针去修改字符串常量的内容,编译的时候不会报错,但是在程序运行时会导致内存访问错误。
例如,执行以下代码,在Linux下运行报“Core Dump” 的错误。
原因在于,编译器使用相同的地址来代替每一个“bee”实例。假如程序运行成功的话,那么会导致第8行和第9行的代码输出错误,输出的结果是“tee”而不是”bee“
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char *str = "bee";
str[0] = 't';
printf("bee");
printf("%s\n","bee");
return 0;
}
字符串的输入
如果想把一个字符串读到程序中,必须首先预留存储字符串的空间,然后使用输入函数来获取字符串。
预留存储空间的方法有2种,一种是声明一个char数组,另外一种是动态申请一片内存空间。
获取字符串的输入函数有:gets(), fgets(), scanf();
char * gets(char *s);
gets 从stdin中读取一行字符串到s指向的内存中,直到遇到换行符‘\n’, 然后在’\n’字符前面读取到的字符最后添加‘\0’, 并把’\n’丢弃。下一次读取从新的一行开始。
gets 成功返回 char * s,失败或者没有字符输入返回NULL。因此可以使用 NULL != gets(s) 来判断有字符读入.
gets 的问题在于,不检查预留的存储空间是否能够容纳实际输入的数据。
char *fgets(char *s, int size, FILE *stream);
fgets 从 stream 最多读入size -1 个字符到 s 指向的存储空间中, 读取到的字符最后添加’\0’。fgets读取到’\n’会保存到字符串里。需要给fgets指定文件流,从键盘输入的话,指定为标准输入流stdin
int scanf(const char *format, …);
如果使用%s格式,字符串读到下一个空白字符(空格、制表符、换行符);如果指定了字段宽度,例如%10s, scanf就会读入10个字符或直到遇到第一个空白字符。
scanf 返回整数值,成功读取的项目数,或者遇到文件结束时,返回EOF。
字符串输出
int puts(const char *s);
puts() 显示字符串时,自动在其后添加一个换行符。
puts() 遇到空字符时才会停下来,所以应该要确保有空字符的存在。
int fputs(const char *s, FILE *stream);
第二个参数说明要写的文件。stdout在stdio.h中定义,可以使用stdout输出到终端。
与puts()不同,fputs()不会为输出自动添加换行符。
gets() 丢掉输入中的换行符, puts()为输出添加换行符;
fgets() 存储输入中的换行符,fputs()不为输出添加换行符;
假定写一个循环,读取一行并把它回显一行, 可以这样写:
char line[81];
while (gets(line))
puts(line);
也可以这样写:
char line[81];
while (fgets(line, 81, stdin))
fputs(line,stdout);
printf()
printf("%s\n", string);
相当于
puts(string);
字符串函数
C库提供了许多字符串处理函数;ANSI C 用头文件 string.h 给出了这些函数的原型。常用的字符串函数有:strlen(), strcat(), strncat(), strcmp(), strncmp(), strcpy() 和 strncpy().
size_t strlen(const char *s);
返回字符串的长度
char *strcat(char *dest, const char *src);
将第二个字符串的一份拷贝添加到第一个字符串的结尾, 从而使第一个字符串成为一个新的组合字符串,第二个字符串并没有改变。返回值就是第一个字符串的地址。
char *strncat(char *dest, const char *src, size_t n);
strcat() 函数并不检查第一个数组是否能够容纳第二个字符串。如果没有为第一个数组分配足够大的空间,多出来的字符溢出到相邻的存储单元时就会有问题。strncat() 的第3个参数指明最多允许添加的字符的数目。
int strcmp(const char *s1, const char *s2);
如果两个字符串相同,就返回0。
ANSI标准规定,如果第一个字符串在字母表中的顺序先于第二个字符串,strcmp()返回一个负数;
如果两个字符串相同,它返回0;如果第一个字符串在字母表中的顺序落后于第二个字符串,它返回一个正数。 确切的数值依赖于不同的C实现(可以是二者ASCII编码值之差, 也可以是+1/-1)。
编译器是:
gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9)
#include <stdio.h>
#include <string.h>
int main(void)
{
printf("A cmp A : %d\n", strcmp("A","A"));
printf("A cmp B : %d\n", strcmp("A","B"));
printf("A cmp C : %d\n", strcmp("A","C"));
return 0;
}
输出结果是:
A cmp A : 0
A cmp B : -1
A cmp C : -1
说明在Ubuntu中,使用gcc编译器,strcmp(),在两个字符串不相同时,返回的是1/-1。
一般来说, strcmp() 函数会一直往后查找,直到找到第一对不一致的字符。然后它就返回相应的值。每个字符串最后一个字符是’\0’。例如 “hello” 和 “helloworld” 比较,“hello”的’\0’字符会和”helloworld”的’w’字符比较,然后返回-1;
说明在Ubuntu中,使用gcc编译器,strcmp(),在两个字符串不相同时,返回的是1/-1。
一般来说, strcmp() 函数会一直往后查找,直到找到第一对不一致的字符。然后它就返回相应的值。每个字符串最后一个字符是’\0’。例如 “hello” 和 “helloworld” 比较,“hello”的’\0’字符会和”helloworld”的’w’字符比较,然后返回-1;
int strncmp(const char *s1, const char *s2, size_t n);
比较两个字符串的前n个字符,可以在前n个字符中,比较到不同的字符就返回或者比较完前n个字符再返回。
char *strcpy(char *dest, const char *src);
char *strncpy(char *dest, const char *src, size_t n);
strcpy() 接收2个字符串指针,将第二个字符串复制到第一个字符串。strcpy() 的第一个参数不一定非得是字符串的首地址,也可以是字符串中的其他地址,前提是已经给将要复制到的地方分配了足够的内存。
strncpy() 使用第三个参数指定,最多可以复制的字符串的数量。
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
char a[] = "Hello world";
const char *b = "wood";
puts(a);
strcpy(a+6, b);
puts(a);
return 0;
}
输出结果为:
Hello world
Hello wood