指针和字符串
![](/images/pointer-string.png)
指针和字符串
苏丙榅1. 字符指针
1.1 处理字符串
字符指针是指向字符(char)数据类型的指针变量。它们可以用于处理字符串(以 NULL结尾的字符数组)和字符数组,并对其中的字符进行读取、修改和操作。以下是一个示例,展示了字符指针的使用:
1 |
|
在上述示例中,我们定义了一个字符数组 str
,其中存储了字符串 "Hello, World!"
。然后,我们定义了字符指针 p
和ptr
,将其指向字符数组的首地址。
通过字符指针,我们可以访问字符数组中的每个字符,并通过解引用操作符 *
访问当前指针位置上的字符。在循环之前,我们通过字符指针p
来修改字符串中的某个字符值;在循环中,我们通过ptr
输出了字符数组中的每个字符,直到遇到 NULL 终止符 \0
。
需要注意的是,字符指针没有固定长度,它可以指向任意长度的字符数组。在处理字符串时,字符指针的常见用途包括字符串的遍历、拷贝、比较和连接等操作。
1.2 做函数参数
字符指针做函数参数和整形指针做函数参数的使用方法是相同的,不同的就是指针指向的内存地址类型不同,内存大小也不同。
需求:编写一个函数,合并两个字符数组。
1 |
|
在前面给大家特意强调过不通过通过运算符sizeof 计算指针指向的内存大小,所以需要在mystrcat
函数内部先计算两个形参指向的实参数组中的元素个数,然后再通过循环进行两个数组的拼接。
根据参数的功能,可以将其分为三大类:
- 传入参数:将数据传入到函数体内部,进行使用
- 传出参数:将函数体内部数据通过参数传到函数体外部,相当于返回值
- 传入传出参数:先将外部数据出入到函数体内部进行处理,然后将新数据重新写入到该参数中,将数据从函数体传出,相当于返回值
通过上面的描述可以得知:
mystrcat
函数的第一个参数dest
是传入传出参数- 携带有用的数据到函数体内部,再将更新后的数据传到函数体外
mystrcat
函数的第二个参数src
是传入参数- 将外部数据传递到函数体内部
- 为了保证外部数据不被修改,所以给参数添加了
const
限定
2. 带参数的main函数
C语言的入口函数main
有两种形式,一种是不带参数的,一种是带参数是,参数的数量和类型都是固定的,其函数原型如下:
1 | int main(int argc, char *argv[]); |
argc
代表命令行参数的数量,程序名字本身算一个参数argv
是命令行参数的字符串数组argc
也可以用来描述第二个参数argv
数组的成员数量,argv
数组的每个成员都是char *
类型
1 | // a.c |
在终端中执行上面的代码,就可以在命令行中指定命令行参数了:
1 | dabing@Linux:~/code$ gcc a.c -o app |
上面在命令行中执行程序的时候,一共指定了5个参数(aaa、bbb、ccc、1213、456
),分别被存储到main
函数的参数argv
数组中(argv[1] ~ argv[5]
),argv[0]
中存储的是可执行程序的名字。
3. 字符串操作函数
3.1 strlen()
strlen
是一个 C 语言标准库函数,用于计算字符串的长度,即字符串中字符的个数(不包括结尾的空字符 \0
)。它的原型如下:
1 |
|
strlen
函数接受一个字符指针 str
,指向一个以 \0
结尾的字符数组(即字符串)。函数将返回一个 size_t
类型的无符号整数,表示字符串的长度。
以下是一个示例,展示了如何使用 strlen
函数:
1 |
|
在上述示例中,我们定义了一个字符数组 str
,它存储了字符串 “Hello, World!”。然后,我们使用 strlen
函数计算字符串的长度,并将结果存储在 length
变量中。
最后,我们使用 %zd
标志符打印出字符串的长度。%zd
是用于无符号整数的标志符,对应 size_t
类型。
需要注意的是,strlen
函数只会计算字符的个数,不会包括结尾的空字符 \0
。因此,字符串的长度将不包括空字符。
strlen
函数在处理字符串时非常常用,可以用于判断字符串是否为空、计算字符串的长度、拷贝字符串等操作。
3.2 strcpy()
strcpy
是一个 C 语言标准库函数,用于将一个字符串拷贝到另一个字符串中。它的原型如下:
1 |
|
strcpy
函数接受两个参数:destination
和 source
。
destination
是目标字符串的指针,表示将要拷贝到的位置source
是源字符串的指针,表示要拷贝的字符串。
函数将会从 source
拷贝字符直到遇到结尾的空字符 \0
,然后将这些字符以及结尾的空字符拷贝到 destination
所指向的位置。最终,函数执行成功返回 destination
字符串首地址,失败返回 NULL。
以下是一个示例,展示了如何使用 strcpy
函数:
1 |
|
在上述示例中,我们定义了两个字符数组:source
和 destination
。source
存储了源字符串,而 destination
是目标字符串的缓冲区。
然后,我们使用 strcpy
函数将源字符串拷贝到目标字符串中。注意,目标字符串的大小必须足够大以容纳源字符串及其结尾的空字符。
最后,我们打印出拷贝后的目标字符串。
需要注意的是,strcpy
函数没有提供缓冲区溢出的保护机制。因此,在使用 strcpy
函数时,需要确保目标字符串的缓冲区足够大,以避免溢出风险。为了避免这个问题,可以考虑使用更安全的函数,如 strncpy
,它允许指定最大拷贝的字符数。
3.3 strncpy()
strncpy
是一个 C 语言标准库函数,用于将一个字符串的一部分拷贝到另一个字符串中。它的原型如下:
1 |
|
strncpy
函数接受三个参数:destination
、source
和 num
。
destination
是目标字符串的指针,表示将要拷贝到的位置source
是源字符串的指针,表示要拷贝的字符串num
是要拷贝的最大字符数。
函数将会从 source
拷贝字符直到遇到结尾的空字符 \0
或拷贝了 num
个字符为止,然后将这些字符以及结尾的空字符拷贝到 destination
所指向的位置。如果源字符串的长度小于 num
,则目标字符串的剩余部分将用空字符填充。
最终,函数执行成功将返回 destination
字符串首地址,失败返回 NULL。以下是一个示例,展示了如何使用 strncpy
函数:
1 |
|
在上述示例中,我们定义了两个字符数组:source
和 destination
。source
存储了源字符串,而 destination
是目标字符串的缓冲区。
然后,我们使用 strncpy
函数将源字符串的一部分拷贝到目标字符串中。sizeof(destination)
指定了最大拷贝的字符数,以确保目标字符串不会溢出。
最后,我们打印出拷贝后的目标字符串。
需要注意的是,strncpy
函数会确保目标字符串的剩余部分被填充上空字符,而不会保留源字符串后面的字符。因此,需要谨慎处理目标字符串的结尾。如果需要在目标字符串截断之后确保结尾有空字符,可以手动将目标字符串的最后一个字符设置为 \0
。
3.4 strcat()
strcat
是一个 C 语言标准库函数,用于将一个字符串追加到另一个字符串的末尾。它的原型如下:
1 |
|
strcat
函数接受两个参数:destination
和 source
。
destination
是目标字符串source
是要追加的源字符串。
该函数将源字符串 source
的内容追加到目标字符串 destination
的末尾,并返回指向拼接后的目标字符串的指针。
以下是一个示例,展示了如何使用 strcat
函数:
1 |
|
在上述示例中,我们定义了一个字符数组 destination
,并初始化为 “Hello”,以及一个源字符串 source
,其中为 “, World!”。
使用 strcat
函数,我们将源字符串 source
的内容追加到目标字符串 destination
的末尾,形成 “Hello, World!”。最后,我们打印出拼接后的目标字符串。
需要注意的是,为了保证目标字符串有足够的容量来容纳两个字符串的拼接结果,destination
字符数组的大小必须足够大。如果 destination
的大小不够,会导致溢出的问题,可能破坏其他内存区域。
另外,目标字符串 destination
必须以 NULL 字符('\0'
)结尾,以便正确识别字符串的结尾。
3.5 strncat()
strncat
是一个 C 语言标准库函数,用于将一个字符串的指定长度追加到另一个字符串的末尾。它的原型如下:
1 |
|
strncat
函数接受三个参数:destination
、source
和 num
。
destination
是目标字符串source
是要追加的源字符串num
是要追加的字符个数。
该函数将源字符串 source
的前 num
个字符追加到目标字符串 destination
的末尾,并返回指向拼接后的目标字符串的指针。
以下是一个示例,展示了如何使用 strncat
函数:
1 |
|
在上述示例中,我们定义了一个字符数组 destination
,并初始化为 “Hello”,以及一个源字符串 source
,其中为 “, World!”,以及一个追加字符个数 num
。
使用 strncat
函数,我们将源字符串 source
的所有字符追加到目标字符串 destination
的末尾,形成 “Hello, World!”。最后,打印出拼接后的目标字符串。
需要注意的是,为了保证目标字符串有足够的容量来容纳两个字符串的拼接结果,destination
字符数组的大小必须足够大。如果 destination
的大小不够,会导致溢出的问题,可能破坏其他内存区域。此外,拼接的字符个数 num
应小于等于源字符串 source
的长度。
另外,目标字符串 destination
必须以 NULL 字符(’\0’)结尾,以便正确识别字符串的结尾。
3.6 strcmp()
strcmp
是一个 C 语言标准库函数,用于比较两个字符串的大小。它的原型如下:
1 |
|
strcmp
函数接受两个参数:str1
和 str2
,它们分别是要进行比较的两个字符串。
该函数会按照字符的 ASCII 值进行比较,从两个字符串的第一个字符开始逐个比较,直到遇到不相等的字符或者其中一个字符串的结束符(NULL 字符\0
)。函数返回一个整数值:
- 如果
str1
小于str2
,则返回一个负数(通常为 -1)。 - 如果
str1
等于str2
,则返回 0。 - 如果
str1
大于str2
,则返回一个正数(通常为 1)。
以下是一个示例,展示了如何使用 strcmp
函数:
1 |
|
在上述示例中,我们定义了两个字符串 str1
和 str2
,分别为 “Hello” 和 “World”。使用 strcmp
函数,对这两个字符串进行了比较。
需要注意的是,strcmp
函数是区分大小写的。如果希望进行大小写不敏感的比较,可以使用 strcasecmp
函数(Linux 平台上的一个扩展函数),或者先将字符串转换为统一的大小写再进行比较。
此外,为了避免可能的缓冲区溢出问题,需要确保被比较的字符串以 NULL字符(’\0’)结尾,以便函数正确识别字符串的结尾。
3.7 strncmp()
strncmp
是一个 C 语言标准库函数,用于比较两个字符串的指定长度。它的原型如下:
1 |
|
strncmp
函数接受三个参数:str1
、str2
和 num
。
str1
和str2
分别是要进行比较的两个字符串num
是要比较的字符的最大个数。
该函数会按照字符的 ASCII 值进行比较,从两个字符串的第一个字符开始逐个比较,直到遇到不相等的字符、其中一个字符串的结束符(NULL 字符\0
)或者达到指定的字符个数。函数返回一个整数值:
- 如果
str1
小于str2
,则返回一个负数(通常为负的str1
和str2
第一个不相等字符的 ASCII 差值)。 - 如果
str1
等于str2
,则返回 0。 - 如果
str1
大于str2
,则返回一个正数(通常为正的str1
和str2
第一个不相等字符的 ASCII 差值)。
以下是一个示例,展示了如何使用 strncmp
函数:
1 |
|
在上述示例中,我们定义了两个字符串 str1
和 str2
,分别为 “Hello” 和 “Hi”,以及一个指定的字符个数 num
,在这里我们设置为 3。
使用 strncmp
函数,我们比较了这两个字符串的前 3 个字符。根据比较结果打印出相应的消息,指示字符串的大小关系。
需要注意的是,在比较的字符个数 num
处达到指定长度后,即使字符串中还有其他字符,也不会再继续比较了。
和 strcmp
函数一样,要确保被比较的字符串以 NULL 字符(’\0’)结尾,以便函数正确识别字符串的结尾。
strncmp
函数适用于需要比较指定长度字符串的情况,特别是在处理固定长度字符串或使用字符数组时。可以根据需求调整要比较的字符个数 num
。
在通过printf
打印字符串的时候用到了 %.*s
,它是 C 语言中格式化输出的一种格式控制符。使用 %.*s
可以打印指定长度的字符串。它的语法是:
1 | printf("%.*s", length, str); |
length
是一个整数,表示要打印的字符串的最大长度str
是要打印的字符串
这种格式控制符允许我们在打印字符串时指定其长度,而不是将整个字符串打印出来。这对于处理固定长度的字符串或需要截断显示的字符串很有用。
3.8 strchr()
strchr
是一个 C 语言标准库函数,用于在一个字符串中查找指定字符
的第一次出现。它的原型如下:
1 |
|
strchr
函数接受两个参数:str
和 c
。
str
是要在其中进行搜索的字符串c
是要查找的字符的整数值。
该函数会在 str
字符串中搜索第一次出现字符 c
的位置,并返回一个指向该位置的指针。如果没有找到字符 c
,函数将返回 NULL
。
以下是一个示例,展示了如何使用 strchr
函数:
1 |
|
在上述示例中,我们定义了一个字符数组 str
,其中存储了待搜索的字符串 “Hello, World!”,以及一个目标字符 c
,其中存储了要查找的字符 ‘W’。
使用 strchr
函数,我们在 str
字符串中搜索第一次出现字符 c
的位置。如果成功找到字符 c
,函数将返回一个指向 str
字符串中该字符的指针。我们可以通过计算指针之间的偏移量来获取字符 c
在原字符串中的位置。
在上述示例中,我们将找到的字符的位置打印出来。如果没有找到字符 c
,函数将返回 NULL
,我们可以相应地进行错误处理。
需要注意的是,strchr
函数参数 c
是一个整数类型,因为 C 语言使用整数表示字符,而字符常量可以直接用单引号括起来表示。在比较字符时,应使用整数值而不是字符常量。
此外,strchr
函数只能用于查找单个字符,而不能用于查找字符串。如果需要在字符串中查找子字符串,则可以使用 strstr
函数。
3.9 strstr()
strstr
是一个 C 语言标准库函数,用于在一个字符串中查找另一个字符串
的第一次出现。它的原型如下:
1 | char* strstr(const char* haystack, const char* needle); |
strstr
函数接受两个参数:haystack
和 needle
。
haystack
是要在其中进行搜索的字符串needle
是要查找的目标字符串。
该函数会在 haystack
字符串中搜索第一次出现 needle
字符串的位置,并返回一个指向该位置的指针。如果没有找到目标字符串,函数将返回 NULL
。
以下是一个示例,展示了如何使用 strstr
函数:
1 |
|
在上述示例中,我们定义了一个字符数组 haystack
,其中存储了待搜索的字符串 “Hello, World!”,以及一个目标字符串 needle
,其中存储了要查找的字符串 “World”。
使用 strstr
函数,我们在 haystack
字符串中搜索第一次出现 needle
字符串的位置。如果成功找到目标字符串,函数将返回一个指向 haystack
字符串中目标字符串的指针。我们可以通过计算指针之间的偏移量来获取目标字符串在原字符串中的位置。
在上述示例中,我们将找到的目标字符串的位置打印出来。如果没有找到目标字符串,函数将返回 NULL
,我们可以相应地进行错误处理。
需要注意的是,strstr
函数是区分大小写的。如果需要进行大小写不敏感的搜索,可以考虑使用其他函数,比如 strcasestr
(Linux 平台上的一个扩展函数)或者自行实现相应的逻辑。
另外,当 needle
为空字符串时,strstr
函数将直接返回 haystack
,因为空字符串可以被认为是任何字符串的子字符串。
3.10 strtok()
strtok
是一个 C 语言标准库函数,用于将字符串分割为多个子字符串。它的原型如下:
1 |
|
strtok
函数接受两个参数:str
和 delimiters
。
str
是一个指向待分割的字符串的指针delimiters
是一个指向包含分割符的字符串的指针。
函数将会按照 delimiters
中指定的分隔符将 str
字符串分割为多个子字符串。每次调用 strtok
函数,它将返回下一个分割的子字符串,并将 str
指针指向下一个未分割的位置。当没有更多的子字符串时,函数会返回空指针。
以下是一个示例,展示了如何使用 strtok
函数:
1 |
|
在上述示例中,我们定义了一个字符数组 str
,其中包含待分割的字符串 ,以及一个包含分隔符的字符串 delimiters
,其中包含了空格、逗号和感叹号。
然后,我们使用 strtok
函数将 str
字符串按照 delimiters
中指定的分隔符进行分割。在第一次调用 strtok
时,传入 str
和 delimiters
,它将返回第一个分割的子字符串 “Hello”,并将 str
指针指向下一个未分割的位置。接下来,在循环中继续调用 strtok
,并传入 NULL
作为第一个参数,以便继续分割剩余的子字符串。
最后,我们打印出每个分割的子字符串。
需要注意的是,strtok
函数会修改传入的 str
字符串,在分割过程中会将分隔符替换为 \0
字符来标记子字符串的结尾。因此,在使用 strtok
之后,原始字符串可能已被修改。
此外,如果需要保留原始字符串不被修改,可以先创建副本,然后对副本进行分割操作。
3.11 sprintf
sprintf
是一个 C 语言标准库函数,用于将格式化的数据写入字符串中。它的原型如下:
1 | int sprintf(char* str, const char* format, ...); |
str
表示要将格式化的数据写入到该数组中format
是一个格式化字符串,用于指定输出的格式和要插入的数据,后面可以跟上要插入的数据,根据格式字符串的要求。
以下是一个示例,展示了如何使用 sprintf
函数:
1 |
|
在上述示例中,定义了一个字符数组 str
,大小为 100,用于存储格式化后的字符串。同时还定义了一个整数 num
,值为 123,以及一个浮点数 f
,值为 3.14,还有一个指针p
指向一个常量字符串。
使用 sprintf
函数,我们将格式化后的内容写入到字符数组 str
中。在格式化字符串中 %d
表示整数的占位符,%f
表示浮点数的占位符,%s
表示字符串的占位符。
需要注意的是,为了保证字符数组 str
有足够的容量来存储格式化后的字符串,应该根据实际需要选择合适的大小。避免发生缓冲区溢出的问题。
此外,除了将格式化数据写入字符串数组中,还有类似的函数可以将格式化数据写入文件中(fprintf
)或标准输出流中(printf
)。这些函数在使用方式和格式化字符串的语法上类似。
4. 常见字符串操作
4.1 练习1
利用
strstr
标准库函数找出一个字符串中substr
出现的次数。
4.1.1 使用 while
示例代码如下:
1 |
|
4.1.2 使用 do…while
示例代码如下:
1 |
|
4.2 练习2
计算给定的字符串中剔除了两端空白之后剩余的子字符串的长度。
示例代码如下:
1 |
|
4.3 练习3
字符串逆置(翻转字符串)。
示例代码如下:
1 |
|