10.C语言字符串操作详解:声明、复制、比较与函数使用

1.前言

本篇原文为:C语言字符串操作详解:声明、复制、比较与函数使用

更多C++进阶、rust、python、逆向等等教程,可点击此链接查看:酷程网

C 语言没有单独的字符串类型,字符串被当作字符数组,即char类型的数组。比如,字符串“Hello”是当作数组{'H', 'e', 'l', 'l', 'o'}处理的。

编译器会给数组分配一段连续内存,所有字符储存在相邻的内存单元之中。

在字符串结尾,C 语言会自动添加一个全是二进制0的字节,写作\0字符,表示字符串结束。

字符\0不同于字符0,前者的 ASCII 码是0(二进制形式00000000),后者的 ASCII 码是48(二进制形式00110000)。

所以,字符串“Hello”实际储存的数组是{'H', 'e', 'l', 'l', 'o', '\0'}

所有字符串的最后一个字符,都是\0,这样做的好处是,C 语言不需要知道字符串的长度,就可以读取内存里面的字符串,只要发现有一个字符是\0,那么就知道字符串结束了。

char localString[10];

上面示例声明了一个10个成员的字符数组,可以当作字符串。由于必须留一个位置给\0,所以最多只能容纳9个字符的字符串。

字符串写成数组的形式,是非常麻烦的,C 语言提供了一种简写法,双引号之中的字符,会被自动视为字符数组。

{
   
   'H', 'e', 'l', 'l', 'o', '\0'}

// 等价于
"Hello"

上面两种字符串的写法是等价的,内部存储方式都是一样的,双引号里面的字符串,不用自己添加结尾字符\0,C 语言会自动添加。

注意,双引号里面是字符串,单引号里面是字符,两者不能互换。如果把Hello放在单引号里面,编译器会报错:

// 报错
'Hello'

另一方面,即使双引号里面只有一个字符(比如"a"),也依然被处理成字符串(存储为2个字节),而不是字符'a'(存储为1个字节)。

如果字符串内部包含双引号,则该双引号需要使用反斜杠转义:

"She replied, \"It does.\""

反斜杠还可以表示其他特殊字符,比如换行符(\n)、制表符(\t)等:

"Hello, world!\n"

如果字符串过长,可以在需要折行的地方,使用反斜杠(\)结尾,将一行拆成多行:

"hello \
world"

上面示例中,第一行尾部的反斜杠,将字符串拆成两行。

上面这种写法有一个缺点,就是第二行必须顶格书写,如果想包含缩进,那么缩进也会被计入字符串。

为了解决这个问题,C 语言允许合并多个字符串字面量,只要这些字符串之间没有间隔,或者只有空格,C 语言会将它们自动合并:

char greeting[50] = "Hello, ""how are you ""today!";
// 等同于
char greeting[50] = "Hello, how are you today!";

这种新写法支持多行字符串的合并:

char greeting[50] = "Hello, "
  "how are you "
  "today!";

printf()使用占位符%s输出字符串:

printf("%s\n", "hello world")

2.字符串声明

字符串变量可以声明成一个字符数组,也可以声明成一个指针,指向字符数组:

// 写法一
char s[14] = "Hello, world!";

// 写法二
char* s = "Hello, world!";

上面两种写法都声明了一个字符串变量s。如果采用第一种写法,由于字符数组的长度可以让编译器自动计算,所以声明时可以省略字符数组的长度:

char s[] = "Hello, world!";

上面示例中,编译器会将数组s的长度指定为14,正好容纳后面的字符串。

字符数组的长度,可以大于字符串的实际长度:

char s[50] = "hello";

上面示例中,字符数组s的长度是50,但是字符串“hello”的实际长度只有6(包含结尾符号\0),所以后面空出来的44个位置,都会被初始化为\0

字符数组的长度,不能小于字符串的实际长度:

char s[5] = "hello";

上面示例中,字符串数组s的长度是5,小于字符串“hello”的实际长度6,这时编译器会报错。

因为如果只将前5个字符写入,而省略最后的结尾符号\0,这很可能导致后面的字符串相关代码出错。

字符指针和字符数组,这两种声明字符串变量的写法基本是等价的,但是有两个差异。

第一个差异是,指针指向的字符串,在 C 语言内部被当作常量,不能修改字符串本身:

char* s = "Hello, world!";
s[0] = 'z'; // 错误

上面代码使用指针,声明了一个字符串变量,然后修改了字符串的第一个字符。这种写法是错的,会导致难以预测的后果,执行时很可能会报错。

如果使用数组声明字符串变量,就没有这个问题,可以修改数组的任意成员:

char s[] = "Hello, world!";
s[0] = 'z';

为什么字符串声明为指针时不能修改,声明为数组时就可以修改?

原因是系统会将字符串的字面量保存在内存的常量区,这个区是不允许用户修改的。

声明为指针时,指针变量存储的值是一个指向常量区的内存地址,因此用户不能通过这个地址去修改常量区。

但是,声明为数组时,编译器会给数组单独分配一段内存,字符串字面量会被编译器解释成字符数组,逐个字符写入这段新分配的内存之中,而这段新内存是允许修改的。

为了提醒用户,字符串声明为指针后不得修改,可以在声明时使用const说明符,保证该字符串是只读的:

const char* s = "Hello, world!";

上面字符串声明为指针时,使用了const说明符,就保证了该字符串无法修改。一旦修改,编译器肯定会报错。

第二个差异是,指针变量可以指向其它字符串:

char* s = "hello";
s = "world";

上面示例中,字符指针可以指向另一个字符串。

但是,字符数组变量不能指向另一个字符串:

char s[] = "hello";
s = "world"; // 报错

上面示例中,字符数组的数组名,总是指向初始化时的字符串地址,不能修改。

同样的原因,声明字符数组后,不能直接用字符串赋值:

char s[10];
s = "abc"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

余识-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值