


字符串变量



| 维度 | 字符串常量 | 字符串变量(字符数组) |
|---|---|---|
| 内存区域 | 只读数据区(.rodata) | 栈 / 堆(可读写区) |
| 能否修改内容 | ❌ | ✅ 可以(如arr[0] = 'x') |
| 内存复用 | 相同常量只存一份 | 每个变量独立占内存 |
| 常见写法 | char *p = "abc"; |
char arr[] = "abc"; / char arr[10] = "abc"; |
| 生命周期 | 程序全程存在(直到退出) | 局部变量:出作用域销毁;全局变量:程序全程存在 |
p本身是指针变量 (存地址的变量):它的本质是 "一个能装内存地址的容器",和int a = 10;里的a一样,是可以修改的(比如p = "def";完全合法);- 真正 "常量" 的是
"abc":这个字符串存放在只读内存区,内容不可改,但它和p是两个完全独立的东西。
初学者容易犯的错:看到 "p[0] = 'A'会崩溃",就误以为 "p是常量不能改",但崩溃的原因是修改了只读的字符串内容 ,不是p本身不能改。
| 错误认知 | 实际真相 |
|---|---|
char *p = "abc" → p是常量 |
p是普通指针变量,可改地址;只是指向的内容不可改 |
const char *p → p是常量 |
const只约束 "通过p改内容",p本身仍可改地址 |
char *const p → p指向的内容是常量 |
反过来了!const靠近p,约束p不能改地址,但指向的内容(若不是常量)可改 |
| 场景 | p指向的内容 |
能否通过p修改? |
本质原因 |
|---|---|---|---|
char *p = "abc" |
字符串常量(只读区) | 不能 | 内容存在只读区,修改会崩溃 |
char *p = a;(a 是数组) |
字符串变量(栈区) | 能 | 内容存在可读写区,数组本身可改 |


可见i和s3都在本地变量,s和s2指向了很远的地方(只读区)


-
"这个字符串在这里" 比如
char word[] = "Hello";,数组word是在当前代码的 ** 栈内存(本地变量区)** 里,直接存了H、e、l、l、o、\0这 6 个字符 ------ 相当于 "字符串的内容就放在数组这个'容器'里,位置明确"。 -
"作为本地变量空间自动被回收" 如果这段代码写在函数里(比如一个函数
void test() { char word[] = "Hello"; }),当函数执行完,word所在的栈内存会被系统自动清空 ------ 相当于 "函数结束后,数组占的空间就没了"。 -
"这个字符串不知道在哪里"
char *str = "Hello";里的"Hello"是字符串常量 ,它被存在程序的 "只读数据段"(内存的一个专门区域),但程序员不用关心它具体存在哪个地址 ------ 只知道str指针存了这个地址,所以说 "不知道在哪里"。 -
"处理参数" 比如写一个函数
void print(char *s) { puts(s); },调用时传print(str)------ 指针可以把字符串的地址传给函数,方便函数操作字符串,这就是 "处理参数" 的场景。 -
"动态分配空间" 比如用
str = (char *)malloc(10);(手动申请 10 字节内存),之后可以往这个内存里存内容 ------ 指针能配合malloc/free自己管理内存,这就是 "动态分配空间"。



s = t只是 "复制了指针的地址",并没有复制字符串的内容
如何复制原字符串的内容到新的内存空间?。。。

比如此时输入hello world,只输出hello

所以输入长度超了7怎么办(结尾0占了第8位)?
越界

解决:代表超7个就不要了
输入123 12345678回车 输出123##1234567##
输入12345678回车 输出1234567##8##


没有让指针指向有效地址

想写一数组表达很多字符串?---用char*a


#include <stdio.h>
int main() {
// 定义指针数组,每个元素指向一个字符串常量
char *str_array[] = {
"苹果", // a[0] 指向这个字符串
"香蕉", // a[1] 指向这个字符串
"橙子", // a[2] 指向这个字符串
"葡萄" // a[3] 指向这个字符串
};
// 数组长度 = 总字节数 / 单个元素字节数(指针占8字节,32位系统是4)
int len = sizeof(str_array) / sizeof(str_array[0]);
// 遍历打印所有字符串
printf("场景1:常量字符串数组\n");
for (int i = 0; i < len; i++) {
printf("第%d个字符串:%s\n", i+1, str_array[i]);
}
return 0;
}


int main(int argc, char const *argv[])
-
这是程序的入口函数
main,它能接收 "用户在命令行里输入的参数":-
argc:表示 "参数的总个数"(是argument count的缩写); -
char const *argv[]:是一个指针数组 (和之前讲的存多个字符串的数组一样),每个元素是一个字符串,对应命令行里的一个参数(argv是argument vector的缩写); -
const表示这些参数是 "只读的",不能修改。
-
-
假设写了一个程序叫
test,编译后在终端里输入:./test 苹果 香蕉 橙子
-
argc= 4(因为参数有 4 个:./test、苹果、香蕉、橙子); -
argv[0] = "./test"(对应 "命令本身",也就是你运行的程序名); -
argv[1] = "苹果"; -
argv[2] = "香蕉"; -
argv[3] = "橙子"。
argv 是存命令行参数的字符串数组,但 C 语言本身 "不知道数组有多长",argc 就是专门告诉程序「这个数组里到底有多少个有效参数」,没有 argc ?
// 错误示范:没有argc,不知道循环到哪停
int main(char *argv[]) {
int i = 0;
while (/* 不知道该写什么条件 */) {
printf("%s\n", argv[i]);
i++;
}
return 0;
}
单字符输入输出

1. 函数定义
int putchar(int c);
- 入参
c:需输出的字符(虽然类型是int,实际传递的是字符对应的 ASCII 码值)。 - 返回值:
- 成功:返回输出的字符(以
int形式); - 失败:返回
EOF(即-1,表示写操作失败)。
- 成功:返回输出的字符(以
2. 功能与用途
-
作用:向 ** 标准输出(通常是终端 / 控制台)** 输出一个字符。
-
典型场景:快速打印单个字符(比
printf更简洁高效),例如:// 输出字符'A'
putchar('A');
// 输出换行符
putchar('\n');
putchar 只输出1 个字符 ,所以它的返回值不是 "写了几个字符" ------ 而是 "确认自己有没有成功写出这 1 个字符"

1. 函数定义与功能
- 定义:
int getchar(void);- 入参:
void表示无需传入参数; - 功能:从 标准输入(通常是键盘)读取一个字符。
- 入参:
2. 返回值设计的核心逻辑
- 返回类型为
int的原因:字符本身只需char类型(1 字节),但getchar需要返回 ** 特殊标记EOF(值为 - 1)** 表示 "输入结束 / 读取失败"。而char无法表示 - 1(默认char是无符号时范围为 0~255),因此用int类型兼容 "字符的 ASCII 码" 和 "EOF(-1)"。
3. 输入结束的操作方式
EOF需通过特定快捷键触发,不同系统对应操作不同:
-
Windows 系统:按下
Ctrl+Z(需单独按,或在输入后按); -
Unix 类系统(Linux、macOS):按下
Ctrl+D。#include <stdio.h>
int main() {
int ch; // 用int存储返回值,兼容字符和EOF
printf("请输入字符(按对应快捷键结束):\n");// 循环读取字符,直到遇到EOF while ((ch = getchar()) != EOF) { // 输出读取到的字符 putchar(ch); } return 0;}
注:键盘输入会先存在 "输入缓冲区" 里,回车是 "提交缓冲区" 的信号,getchar要等缓冲区提交后才会读取内容 ,不是getchar要等回车,是系统先攒着输入,回车才让这些内容给到程序
注:
运行getchar循环读取的程序,敲了几个字符后按Ctrl+Z,再按回车:
-
系统会把
Ctrl+Z识别为EOF,getchar读取到EOF后,循环条件ch != EOF不成立,程序正常退出; -
这是
Ctrl+Z的「设计用途」:告诉程序 "输入结束了"#include <stdio.h>
int main() {
int ch;
printf("输入字符(Ctrl+Z+回车结束,Ctrl+C强制退出):\n");
while ((ch = getchar()) != EOF) {
printf("读取到:%c\n", ch);
}
printf("程序正常退出\n"); // Ctrl+Z会走到这里,Ctrl+C不会
return 0;
}
字符串函数

一:字符串长度


函数内部参考:

二:比较字符串

返回值的含义由 "str1 - str2的首个不同字符的 ASCII 码差值" 决定:
- 返回 0 :
str1和str2的内容完全相同; - 返回正数 :
str1的首个不同字符的 ASCII 码 >str2的对应字符(比如"apple"vs"app",str1多了'l',返回正数); - 返回负数 :
str1的首个不同字符的 ASCII 码 <str2的对应字符(比如"app"vs"apple",返回负数) - 比如
"abcde"和"abcf",只比较到第 4 个字符('d'vs'f'),后面的字符不再比较
函数内部参考:

s1[idx]:等价于*(s1 + idx)------ 取字符串s1第idx个位置的字符
*s1 == *s2 && *s1!= '\0':当前s1和s2指向的字符相等,并且这个字符不是字符串的结束符'\0
s1++; s2++:指针指向往后一位
如果两个字符串长度不同,短的那个会先遇到结尾符'\0',循环就会停止,然后比较 "短字符串的'\0'" 和 "长字符串对应位置的字符"
三:复制字符串

src和dst是两个指向字符串的指针,它们对应的内存区域不能有 重叠的部分

应用:复制字符串

strlen(src):计算原字符串src的有效字符长度 (比如src="hello",strlen(src)是 5,只算h/e/l/l/o,不算结尾的'\0');+1:必须多分配 1 个字节 ------ 因为 C 语言的字符串要以'\0'(结束符)结尾,所以要给'\0'留位置(比如src="hello",需要分配5+1=6字节,存h/e/l/l/o/\0);(char*):malloc返回的是通用指针void*,这里强制转成char*(字符指针),和dst的类型匹配- 解决了 "直接用指针指向原字符串
函数内部参考;
注意最后要给dst一个0

(1)char* ret = dst;保存dst的初始地址 。后续dst会通过dst++向后移动,最终需要返回 "目标字符串的起始位置",因此先将dst最初的地址存在ret中。
(2)while ( *dst++ = *src++ ) ;
- 拷贝字符 :
*dst = *src------ 将src当前指向的字符,赋值到dst当前指向的内存位置; - 指针后移 :
dst++/src++------ 拷贝完一个字符后,dst和src同时向后移动 1 位,准备拷贝下一个字符; - 循环终止条件 :C 语言中 "赋值表达式的结果是被赋的值"。当拷贝到
src的结束符'\0'时,*src = '\0',赋值后*dst也为'\0',此时 "0" 会被判定为 "假",循环自动结束。
- 效果:既拷贝了
src的所有有效字符,也将src的结束符'\0'一并拷贝到dst中。
(3)冗余代码:*dst = '\0';
(4)return ret;返回最初保存的dst起始地址(符合strcpy的设计:返回目标字符串地址,支持链式调用,例如printf("%s", strcpy(dst, src)))
注;
| 函数写法 | 实际返回类型 | 含义(加工厂产出) | 例子场景 |
|---|---|---|---|
char* my_strcpy(...) |
char* | 产出 "字符指针"(地址) | 返回目标字符串起始地址 |
int mycmp(...) |
int | 产出 "整数" | 返回字符串比较的差值 |
void print_str(...) |
void | 不产出任何东西 | 只打印字符串,无返回值 |
四:字符串中找字符

1. strchr(const char *s, int c)
- 功能 :在字符串
s中,从开头向末尾 查找字符c(int c实际传入的是字符的 ASCII 码)。 - 返回值 :找到则返回该字符在
s中第一次出现位置 的指针;未找到则返回NULL。 - 示例 :
strchr("hello", 'l')会返回指向第一个'l'(s[2])的指针。
2. strrchr(const char *s, int c)
- 功能 :在字符串
s中,从末尾向开头 查找字符c。 - 返回值 :找到则返回该字符在
s中最后一次出现位置 的指针;未找到则返回NULL。 - 示例 :
strrchr("hello", 'l')会返回指向第二个'l'(s[3])的指针。 - 两者均区分大小写
- 入参
int c的设计:兼容字符的 ASCII 码和结束符'\0'(若c='\0',strchr/strrchr会返回字符串末尾'\0'的地址)
返回了指向第一个l的指针

想把l后面的复制到另一字符串

想输出he

p指向s[2]---s[2]的字符值给c暂存---p指向的位置(s[2])的内容改为字符串结束符'\0'---此时s就变为he了---后续建议*p=c,恢复s
五:字符串中找字符串

1. strstr(const char *s1, const char *s2)
- 功能:在字符串
s1中,查找子串s2第一次出现的位置。 - 特点:区分大小写 (比如
s1="Hello"、s2="ell",会找不到,因为s1里的是'E'大写)。 - 返回值:找到则返回子串在
s1中起始位置的指针;没找到则返回NULL。
2. strcasestr(const char *s1, const char *s2)
- 功能:和
strstr一样,但忽略大小写 查找子串(比如s1="Hello"、s2="ell",能找到,因为不区分大小写)。 - 特点:仅在 Unix 类系统(Linux、macOS)支持,Windows 系统无此函数。
- 返回值:同
strstr(找到返回指针,没找到返回NULL)
枚举
1.常量符号化:"用符号而非具体数字表达实际数字":给数字起一个 "符号名字"(也叫 "符号常量 / 宏常量"),让代码更易读、易修改,不用到处写硬编码的数字

枚举代替const int


void f(enum color c);声明,参数是enum color类型的变量c
enum color t = red;
- 定义一个
enum color类型的变量t,并初始化为枚举成员red(即t的初始值是0)。
scanf("%d", &t);
-
从控制台读取一个整数,赋值给变量
t; -
注意:
enum类型本质是整数,所以可以用%d格式符输入(但输入值应对应枚举成员的整数,比如输入1对应yellow)。
f(t);
- 调用之前声明的函数
f,并将变量t作为参数传递给f的形参c。
传入red则输出0,传入yellow则输出1





