C字符串和字符串函数(一)
C字符串和字符串函数(一)
- 字符串函数:读写字符串、拷贝字符串、比较字符串、合并字符串、查找字符串
- 创建、使用字符串
- 使用
C
库中的字符和字符串函数,创建自定义字符串函数- 使用命令行参数
字符串
字符串的本质:
以空字符(\0
)结尾的char
类型数组
由于是数组,所以上一章的指针和数组的操作可以应用其中
示例代码:
/**
* @Author: Lucifer
* @Date: 5/15/2023, 9:27:24 PM
* @LastEditors: Lucifer
* @LastEditTime: 5/15/2023, 9:27:24 PM
* Description: 字符串的使用,字符串本质的运用
* Copyright: Copyright (©)}) 2023 Your Name. All rights reserved.
*/
# include<stdio.h>
# define MSG "I am a symbolic string constant."
# define MAXLENGTH 81
int main(void)
{
char words[MAXLENGTH] = "I am a string in an array.";
const char * p1 = "Something is pointing at me.";
puts("Here are some strings:");
puts(MSG);
puts(words);
puts(p1);
words[8] = 'p';
puts(words);
getchar();
return 0;
}
注意:
puts()
函数,属于stdio.h
系列输入输出函数,会自动在显示的字符串末尾加上换行符
字符串常量
特点:
- 双引号当中的字符和编译器当中的
\0
字符都被作为字符串存储在内存中 ANSI C
标准,如果字符串之间没有间隔,或者用空白字符分隔,将作为串联起来的字符串常量- 字符串内部要使用双引号,则要在双引号前加反斜杠
\
-> 示例代码printf("\"Run\".n");
字符串常量存储特点:
- 静态存储类型
- 函数中使用字符串常量,该字符串只会被存储一次,用双引号括起来的内容会被视为指向该字符串存储位置的指针
示例代码:
/**
* @Author: Lucifer
* @Date: 5/15/2023, 10:07:13 PM
* @LastEditors: Lucifer
* @LastEditTime: 5/15/2023, 10:07:13 PM
* Description: 将字符串看作指针
* Copyright: Copyright (©)}) 2023 Your Name. All rights reserved.
*/
# include<stdio.h>
int main(void)
{
printf("%s, %p, %c.\n", "We", "are", *"space farers");
/**
* %p打印的是are字符串首字符的地址
* %c打印的是字符串"space farers"的首字符 -> 解引用,说明该字符串是地址,一个指向字符串的首字符的指针
*/
return 0;
}
字符串数组和初始化
关键点:
- 定义字符串数组需要让编译器知道需要多少空间
示例代码:
const char m1[10] = "Hello";
const char m2[10] = {'H', 'e', 'l', 'l', 'o', '\0'};
/**
* 第一种声明方式比第二种简洁,但是两种声明方式都已经在编译时声明的数组的大小
* 第二种声明方式当中如果没有最后一位'\0'将是字符数组,不是字符串
*/
基于上诉的情况,下面的表达式均为真:
m1 == &m1[0]
*m1 == 'H'
*(m1 + 1) == m1[1] == 'H'
几乎相同的声明方式:
const char * p = "Hello";
const char ar[] = "Hello";
数组声明和指针声明的区别
数组声明:
-
字符串作为可执行文件的一部分存储在数据段中
-
程序载入内存时,也载入程序中的字符串 -> 字符串被存储在静态存储区
-
程序开始时才为数组分配内存 -> 此时将字符串拷贝到数组中
-
此时字符串有两个副本,一个在静态内存当中,一个在数组当中
由上诉可知:
- 编译器把数组名
ar
作为了数组首元素地址的别名 ar
是地址常量,不能更改 -> 如果更改了意味着改变了数组的存储位置 -> 不能用++ar
指针声明:
- 程序开始时为指针留出一个存储位置
- 把字符串地址存储在指针变量当中 -> 最初指向字符串首字符
- 值可以改变,可以用递增运算符 -> 可以用
++p
示例代码:
/**
* @Author: Lucifer
* @Date: 5/15/2023, 10:57:25 PM
* @LastEditors: Lucifer
* @LastEditTime: 5/15/2023, 10:57:25 PM
* Description: 通过指针和字符的位置观察指针在字符中的实际情况,观察数组声明和指针声明的不同
* Copyright: Copyright (©)}) 2023 Your Name. All rights reserved.
*/
# include<stdio.h>
# define MSG "Hello"
int main(void)
{
char ar[] = MSG;
const char * p = MSG;
printf("\"Hello\" 的地址是: %p.\n", "Hello");
printf("ar的地址是: %p.\n", ar);
printf("p的地址是: %p.\n", p);
printf("MSG的地址是: %p.\n", p);
printf("\"Hello\"的地址是: %p.\n", "Hello");
/**
* 可以看到输出1、3、4、5的地址都是相同的,说明这些值的指针一致
* 输出2地址不相同,说明经过了拷贝的过程,和上面的描述一致
*/
getchar();
return 0;
}
可以简单理解为:
- 值传递 -> 数组声明法
- 指针传递 -> 指针声明法
具体使用声明声明方式看该字符串主要做什么事情
数组和指针的区别
导致这种区别的核心原因:
编译器在处理指针的时候不一致
查看示例:
char heart[] = "I am man"; // heart被声明为头元素指针的别名
const char * head = "I am man";
二者区别:
- 都可以进行指针加法操作 ->
*(heart + 1)
和*(head + 1)
都被允许 - 只有指针表示法允许进行递增操作 ->
*(head++)
被允许,*(heart++)
不被允许 -> 原因:数组的元素是变量(指针指向数组元素),数组名不是变量(heart被声明为头元素指针别名在数组当中)
指针的好处也是指针的危害:
char * word = "frame";
word[1] = 'l';
-> 编译器允许这个行为,但是在c
标准当中这个行为未被定义,该语句可能导致内存访问错误. -> 编译器可以使用内存中的一个副本来表示所有完全相同的字符串字面量
示例代码:
void dangerous(void)
{
char * p = "Klingon";
p[0] = 'F';
printf("Klingon");
printf(":结果 %s!.\n", "Klingon");
}
这段代码当中把指针表示和数组表示混合起来使用,导致的结果是:
- 编译器可以使用相同的地址替换每个
Klingon
实例 - 编译器允许
p[0] = 'F'
这种修改 - 影响所有的该字符串代码 -> 两个
print()
当中都会变成Flingon
解决办法:
- 把指针初始化为字符串字面量时使用
const
限定符 - 打算修改字符串,不要用指针指向字符串字面量
字符串数组
示例代码:
/**
* @Author: Lucifer
* @Date: 5/16/2023, 9:08:53 PM
* @LastEditors: Lucifer
* @LastEditTime: 5/16/2023, 9:08:53 PM
* Description: 数组和指针在字符串数组当中的声明和使用,注意区别
* Copyright: Copyright (©)}) 2023 Your Name. All rights reserved.
*/
# include<stdio.h>
# define SLEN 40
# define LIM 5
int main(void)
{
/** 指针声明 */
const char * mytalents[LIM] =
{
"Adding numbers swiftly",
"Multiplying accurately",
"Stashing data",
"Following instructions to the letter",
"Understanding the C language"
};
/** 数组声明 */
char yourtalents[LIM][SLEN] =
{
"Walking in a straight line",
"Sleeping",
"Watching television",
"Mailing letters",
"Reading email"
};
int i;
puts("Let's compare talents.");
printf("%-36s %-25s.\n", "My Talents", "Your Talents");
for ( i = 0; i < LIM; i++)
printf("for my: %-36s for your: %-25s.\n", mytalents[i], yourtalents[i]);
printf("\nsizeof mytalents: %zd, sizeof yourtalents: %zd.\n", sizeof(mytalents), sizeof(yourtalents));
getchar();
return 0;
}
关键区别点:
mytalents
中的指针指向初始化所用的字符串字面量的位置,这些字面量存储在静态内存当中,yourtalents
中数组存储着每个字符串字面量的副本,为字符串数组分配内存的使用率较低mytalents
中的每个字符串的存储空间不是定长的,yourtelents
当中的字符串字面量副本都是存储在定长的数组当中,长度不足用\0
补足mytalents
中每个字符串不必存储在连续的内存空间,yourtalents
当中的字符串字面量副本必须存储在连续的内存空间当中mytalents
当中指针指向的字符串字面量不能够修改,而yourtalents
当中的可以
指针和字符串
特点:
- 两个指针,一个指向字符串,一个未初始化.如若将指针
A
赋值给指针B
(指针A
指向字符串),那么则是地址的赋值,两个指针将指向同一个地址
示例代码:
/**
* @Author: Lucifer
* @Date: 5/16/2023, 9:40:55 PM
* @LastEditors: Lucifer
* @LastEditTime: 5/16/2023, 9:40:55 PM
* Description: 指针赋值是地址的拷贝,不会重新声明一个内存空间重新存储字符串
* Copyright: Copyright (©)}) 2023 Your Name. All rights reserved.
*/
# include<stdio.h>
int main(void)
{
const char * mesg = "Don't be a fool";
const char * copy;
copy = mesg;
printf("mesg = %s, &mesg = %p, value = %p.\n", mesg, &mesg, mesg);
printf("copy = %s, © = %p, value = %p.\n", copy, ©, copy);
/**
* 指针copy和指针mesg是两个内存地址
* 但是copy和mesg指向同一个内存地址,值相同
*/
getchar();
return 0;
}
It's a lonely road!!!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!