重新认识C语言 —— 字符串

重新认识C语言 —— 字符串

常用的定义字符串的方法是:字符串常量、char数组、char指针和字符串数组。

说明

本文实际上是《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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值