翁恺 11字符串

字符串变量

维度 字符串常量 字符串变量(字符数组)
内存区域 只读数据区(.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 *pp是常量 const只约束 "通过p改内容",p本身仍可改地址
char *const pp指向的内容是常量 反过来了!const靠近p,约束p不能改地址,但指向的内容(若不是常量)可改
场景 p指向的内容 能否通过p修改? 本质原因
char *p = "abc" 字符串常量(只读区) 不能 内容存在只读区,修改会崩溃
char *p = a;(a 是数组) 字符串变量(栈区) 内容存在可读写区,数组本身可改

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

  1. "这个字符串在这里" 比如char word[] = "Hello";,数组word是在当前代码的 ** 栈内存(本地变量区)** 里,直接存了H、e、l、l、o、\0这 6 个字符 ------ 相当于 "字符串的内容就放在数组这个'容器'里,位置明确"。

  2. "作为本地变量空间自动被回收" 如果这段代码写在函数里(比如一个函数void test() { char word[] = "Hello"; }),当函数执行完,word所在的栈内存会被系统自动清空 ------ 相当于 "函数结束后,数组占的空间就没了"。

  3. "这个字符串不知道在哪里" char *str = "Hello";里的"Hello"字符串常量 ,它被存在程序的 "只读数据段"(内存的一个专门区域),但程序员不用关心它具体存在哪个地址 ------ 只知道str指针存了这个地址,所以说 "不知道在哪里"。

  4. "处理参数" 比如写一个函数void print(char *s) { puts(s); },调用时传print(str)------ 指针可以把字符串的地址传给函数,方便函数操作字符串,这就是 "处理参数" 的场景。

  5. "动态分配空间" 比如用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;
}
  1. int main(int argc, char const *argv[])
  • 这是程序的入口函数main,它能接收 "用户在命令行里输入的参数":

    • argc:表示 "参数的总个数"(是argument count的缩写);

    • char const *argv[]:是一个指针数组 (和之前讲的存多个字符串的数组一样),每个元素是一个字符串,对应命令行里的一个参数(argvargument vector的缩写);

    • const表示这些参数是 "只读的",不能修改。

  1. 假设写了一个程序叫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识别为EOFgetchar读取到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 码差值" 决定:

  1. 返回 0str1str2的内容完全相同;
  2. 返回正数str1的首个不同字符的 ASCII 码 > str2的对应字符(比如"apple" vs "app"str1多了'l',返回正数);
  3. 返回负数str1的首个不同字符的 ASCII 码 < str2的对应字符(比如"app" vs "apple",返回负数)
  4. 比如"abcde""abcf",只比较到第 4 个字符('d' vs 'f'),后面的字符不再比较

函数内部参考:

s1[idx]:等价于*(s1 + idx)------ 取字符串s1idx个位置的字符

*s1 == *s2 && *s1!= '\0':当前s1s2指向的字符相等,并且这个字符不是字符串的结束符'\0

s1++; s2++:指针指向往后一位

如果两个字符串长度不同,短的那个会先遇到结尾符'\0',循环就会停止,然后比较 "短字符串的'\0'" 和 "长字符串对应位置的字符"

三:复制字符串

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

应用:复制字符串

  • 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++ ) ;
  1. 拷贝字符*dst = *src------ 将src当前指向的字符,赋值到dst当前指向的内存位置;
  2. 指针后移dst++/src++------ 拷贝完一个字符后,dstsrc同时向后移动 1 位,准备拷贝下一个字符;
  3. 循环终止条件 :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中,从开头向末尾 查找字符cint 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

相关推荐
渡我白衣2 小时前
计算机组成原理(4):计算机的层次结构与工作原理
运维·c语言·网络·c++·人工智能·笔记·硬件架构
C语言小火车3 小时前
红黑树(C/C++ 实现版)—— 用 “带配重的书架” 讲透本质
c语言·开发语言·c++·红黑树
猫猫的小茶馆3 小时前
【ARM】VSCode和IAR工程创建
c语言·开发语言·arm开发·ide·vscode·stm32·嵌入式硬件
zephyr_zeng3 小时前
CubeMX项目轻松导入Vscode+EIDE编译
c语言·ide·vscode·stm32·mcu·物联网·编辑器
代码游侠4 小时前
应用——文件I/O操作代码
linux·运维·c语言·笔记·学习·算法
亭上秋和景清4 小时前
计算器回调函数
c语言·数据结构·算法
枫叶丹44 小时前
【Qt开发】Qt窗口(八) -> QFileDialog 文件对话框
c语言·开发语言·数据库·c++·qt
秦苒&6 小时前
【C语言指针二】从入门到通透:核心知识点全梳理(野指针,assert断言,指针的使用和传址调用,数组名的理解和使用指针反访问数组)
c语言·开发语言
Tandy12356_7 小时前
手写TCP/IP协议——IP层输出处理
c语言·网络·c++·tcp/ip·计算机网络