一、引言:字符串在C语言中的独特地位
在C语言中,字符串有着特殊的地位。它不像其他语言那样有专门的字符串类型,而是以字符数组的形式存在,并且以空字符’\0’作为结束标志。这种设计使得字符串既简单又灵活,但也带来了一些复杂性。
想象一下,字符串就像一列火车:
· 每个字符是车厢
· 字符串结束符’\0’是车尾标志
· 指针就是列车时刻表,告诉我们火车在哪里
charstr[]="Hello";// 这列火车有5节载客车厢和1节车尾标志车厢// 内存布局: 'H' 'e' 'l' 'l' 'o' '\0'二、C语言字符串的本质
2.1 字符串的三种表示方法
C语言中表示字符串有三种主要方式:
#include<stdio.h>voidstring_representations(){printf("=== 字符串的三种表示方法 ===\n\n");// 方法1:字符数组(栈上分配)charstr1[]="Hello World";// 自动添加'\0'printf("方法1 - 字符数组:\n");printf(" 声明: char str1[] = \"Hello World\";\n");printf(" 内容: %s\n",str1);printf(" 大小: sizeof(str1) = %zu 字节\n",sizeof(str1));printf(" 地址: %p\n",(void*)str1);// 方法2:字符指针(指向字符串字面量)char*str2="Hello World";// 指向常量区printf("\n方法2 - 字符指针:\n");printf(" 声明: char *str2 = \"Hello World\";\n");printf(" 内容: %s\n",str2);printf(" 大小: sizeof(str2) = %zu 字节(指针大小)\n",sizeof(str2));printf(" 地址: %p\n",(void*)str2);// 方法3:动态分配的字符串(堆上分配)char*str3=(char*)malloc(12*sizeof(char));// 为"Hello World"分配空间if(str3!=NULL){strcpy(str3,"Hello World");printf("\n方法3 - 动态分配:\n");printf(" 声明: char *str3 = malloc(12); strcpy(str3, \"Hello World\");\n");printf(" 内容: %s\n",str3);printf(" 大小: 动态分配12字节\n");printf(" 地址: %p\n",(void*)str3);free(str3);}printf("\n内存区域对比:\n");printf(" str1: 栈内存(可修改)\n");printf(" str2: 常量区(通常不可修改)\n");printf(" str3: 堆内存(可修改,需手动释放)\n");}2.2 字符串结束符’\0’的重要性
‘\0’(ASCII值为0)是C语言字符串的"终结者",没有它,字符串操作函数就不知道在哪里停止:
#include<stdio.h>voidnull_terminator_demo(){printf("=== 字符串结束符的重要性 ===\n\n");// 正确的字符串(自动包含'\0')charcorrect[]="Hello";printf("正确字符串: \"%s\"\n",correct);printf("长度: %zu (使用strlen)\n",strlen(correct));printf("内存内容: ");for(inti=0;i<=strlen(correct);i++){// 注意:包含'\0'if(correct[i]=='\0'){printf("'\\0' ");}else{printf("'%c' ",correct[i]);}}printf("\n\n");// 错误的字符串(忘记'\0')charwrong[5]={'H','e','l','l','o'};// 没有'\0'!printf("错误字符串: 没有'\\0'的字符数组\n");printf("打印结果: %s (可能包含垃圾数据)\n",wrong);// 演示strlen在没有'\0'时的行为printf("\n演示字符串函数的行为:\n");chartest1[6]={'H','e','l','l','o','\0'};chartest2[5]={'H','e','l','l','o'};// 没有'\0'printf("test1 (有'\\0'): \"%s\", strlen = %zu\n",test1,strlen(test1));printf("test2 (无'\\0'): ");// 危险!strlen会一直读取直到遇到'\0'size_tlen=0;while(test2[len]!='\0'&&len<100){// 防止无限循环len++;}printf("strlen可能返回 %zu 或导致问题\n",len);printf("\n经验法则: 总是确保字符串以'\\0'结束!\n");}三、字符指针与字符串操作
3.1 声明和初始化字符串指针
字符串指针的声明和初始化有多种方式,各有特点:
#include<stdio.h>#include<stdlib.h>#include<string.h>voidstring_pointer_declaration(){printf("=== 字符串指针的声明与初始化 ===\n\n");// 1. 指向字符串字面量(常量区)char*ptr1="Immutable String";printf("1. 指向字面量:\n");printf(" ptr1 = \"%s\"\n",ptr1);printf(" 注意: 不能修改内容!\n");// ptr1[0] = 'X'; // 错误:运行时错误(试图修改常量区)// 2. 指向字符数组(栈内存)chararr[]="Mutable String";char*ptr2=arr;// 指向栈上的数组printf("\n2. 指向字符数组:\n");printf(" 原始: %s\n",ptr2);ptr2[0]='X';// 可以修改printf(" 修改后: %s\n",ptr2);// 3. 动态分配(堆内存)char*ptr3=(char*)malloc(20*sizeof(char));if(ptr3){strcpy(ptr3,"Dynamic String");printf("\n3. 动态分配:\n");printf(" 初始: %s\n",ptr3);ptr3[0]='D';// 可以修改printf(" 修改后: %s\n",ptr3);free(ptr3);}// 4. 指向字符串常量数组constchar*ptr4="This is constant";printf("\n4. 使用const保护:\n");printf(" ptr4 = \"%s\"\n",ptr4);printf(" 明确表示不可修改,更安全\n");// ptr4[0] = 'X'; // 编译时错误,而不是运行时错误printf("\n总结:\n");printf(" - 字面量: 常量区,不可修改\n");printf(" - 字符数组: 栈内存,可修改\n");printf(" - 动态分配: 堆内存,可修改,需释放\n");printf(" - const修饰: 明确不可修改,更安全\n");}3.2 字符串指针的运算
字符串指针支持指针运算,这使得遍历字符串变得非常方便:
#include<stdio.h>voidstring_pointer_arithmetic(){printf("=== 字符串指针运算 ===\n\n");charstr[]="C Programming";char*ptr=str;// 指向字符串开头printf("原始字符串: \"%s\"\n",str);printf("指针初始位置: ptr指向 '%c'\n",*ptr);printf("\n1. 指针自增遍历字符串:\n");printf(" 遍历结果: ");while(*ptr!='\0'){printf("%c",*ptr);ptr++;// 移动到下一个字符}printf("\n 现在ptr指向: '\\0'\n");// 重新指向开头ptr=str;printf("\n2. 指针运算访问特定字符:\n");printf(" ptr + 0 = '%c' (str[0])\n",*(ptr+0));printf(" ptr + 2 = '%c' (str[2])\n",*(ptr+2));printf(" ptr + 5 = '%c' (str[5])\n",*(ptr+5));printf("\n3. 计算字符串长度(手动实现):\n");char*start=str;char*end=str;// 找到字符串结尾while(*end!='\0'){end++;}printf(" 字符串长度: %ld (指针差: end - start)\n",end-start);printf("\n4. 反向遍历字符串:\n");printf(" 反向输出: ");char*reverse_ptr=end-1;// 指向最后一个字符(不是'\0')while(reverse_ptr>=start){printf("%c",*reverse_ptr);reverse_ptr--;}printf("\n");printf("\n5. 指针比较:\n");char*p1=str;char*p2=str+5;printf(" p1 = %p, 指向 '%c'\n",(void*)p1,*p1);printf(" p2 = %p, 指向 '%c'\n",(void*)p2,*p2);if(p1<p2){printf(" p1 在 p2 前面\n");}if(p2>p1){printf(" p2 在 p1 后面\n");}}四、字符串处理函数详解
4.1 标准库字符串函数
C标准库提供了一系列字符串处理函数,它们都定义在<string.h>中:
#include<stdio.h>#include<string.h>#include<stdlib.h>voidstandard_string_functions(){printf("=== 标准库字符串函数 ===\n\n");// 1. strlen - 字符串长度(不包括'\0')char*str1="Hello World";printf("1. strlen - 字符串长度:\n");printf(" \"%s\" 的长度是 %zu\n",str1,strlen(str1));// 2. strcpy - 字符串复制chardest1[20];strcpy(dest1,"Copy Me");printf("\n2. strcpy - 字符串复制:\n");printf(" 复制后: %s\n",dest1);// 3. strncpy - 安全字符串复制(指定最大长度)chardest2[10];strncpy(dest2,"This is too long",sizeof(dest2)-1);dest2[sizeof(dest2)-1]='\0';// 确保以'\0'结束printf("\n3. strncpy - 安全复制:\n");printf(" 安全复制: %s\n",dest2);// 4. strcat - 字符串连接chardest3[30]="Hello";strcat(dest3," World");printf("\n4. strcat - 字符串连接:\n");printf(" 连接后: %s\n",dest3);// 5. strcmp - 字符串比较char*str2="apple";char*str3="banana";intresult=strcmp(str2,str3);printf("\n5. strcmp - 字符串比较:\n");printf(" \"%s\" 与 \"%s\" 比较: ",str2,str3);if(result<0){printf("小于\n");}elseif(result>0){printf("大于\n");}else{printf("等于\n");}// 6. strchr - 查找字符char*str4="Find the letter e";char*found=strchr(str4,'e');printf("\n6. strchr - 查找字符:\n");if(found){printf(" 在 \"%s\" 中找到 'e',位置: %s\n",str4,found);}else{printf(" 未找到\n");}// 7. strstr - 查找子串char*str5="C Programming Language";char*substr=strstr(str5,"Program");printf("\n7. strstr - 查找子串:\n");if(substr){printf(" 在 \"%s\" 中找到 \"Program\",位置: %s\n",str5,substr);}else{printf(" 未找到\n");}// 8. strtok - 字符串分割charstr6[]="apple,banana,cherry,date";printf("\n8. strtok - 字符串分割:\n");printf(" 分割 \"%s\":\n",str6);char*token=strtok(str6,",");while(token!=NULL){printf(" - %s\n",token);token=strtok(NULL,",");}}4.2 手动实现字符串函数
理解标准库函数的最好方法是手动实现它们:
#include<stdio.h>#include<assert.h>// 1. 手动实现strlensize_tmy_strlen(constchar*str){constchar*p=str;while(*p!='\0'){p++;}returnp-str;// 指针相减得到字符数}// 2. 手动实现strcpychar*my_strcpy(char*dest,constchar*src){char*d=dest;constchar*s=src;while((*d++=*s++)!='\0'){// 空循环,复制在条件中进行}returndest;}// 3. 手动实现strcatchar*my_strcat(char*dest,constchar*src){char*d=dest;// 找到dest的结尾while(*d!='\0'){d++;}// 追加srcmy_strcpy(d,src);returndest;}// 4. 手动实现strcmpintmy_strcmp(constchar*str1,constchar*str2){while(*str1&&(*str1==*str2)){str1++;str2++;}return*(unsignedchar*)str1-*(unsignedchar*)str2;}// 5. 手动实现strchrchar*my_strchr(constchar*str,intch){while(*str!='\0'){if(*str==ch){return(char*)str;// 找到,返回指针}str++;}if(ch=='\0'){// 特殊处理查找'\0'return(char*)str;}returnNULL;// 未找到}voidimplement_string_functions(){printf("=== 手动实现字符串函数 ===\n\n");// 测试my_strlenprintf("1. my_strlen 测试:\n");char*test_str="Hello";printf(" \"%s\" 长度: %zu\n",test_str,my_strlen(test_str));// 测试my_strcpyprintf("\n2. my_strcpy 测试:\n");chardest1[20];my_strcpy(dest1,"Copied String");printf(" 复制后: %s\n",dest1);// 测试my_strcatprintf("\n3. my_strcat 测试:\n");chardest2[30]="Hello";my_strcat(dest2," World");printf(" 连接后: %s\n",dest2);// 测试my_strcmpprintf("\n4. my_strcmp 测试:\n");printf(" \"apple\" vs \"banana\": %d\n",my_strcmp("apple","banana"));printf(" \"apple\" vs \"apple\": %d\n",my_strcmp("apple","apple"));printf(" \"banana\" vs \"apple\": %d\n",my_strcmp("banana","apple"));// 测试my_strchrprintf("\n5. my_strchr 测试:\n");char*str="Find the letter e";char*found=my_strchr(str,'e');if(found){printf(" 在 \"%s\" 中找到 'e',从位置: %s\n",str,found);}// 验证与标准库的一致性printf("\n6. 验证与标准库的一致性:\n");chartest1[]="Test String";chartest2[]="Another String";assert(my_strlen(test1)==strlen(test1));charcopy1[20],copy2[20];my_strcpy(copy1,test1);strcpy(copy2,test1);assert(strcmp(copy1,copy2)==0);printf(" 所有测试通过!\n");}五、字符串数组与指针数组
5.1 字符串数组的两种实现方式
处理多个字符串时,有两种主要方式:
#include<stdio.h>#include<string.h>voidstring_arrays(){printf("=== 字符串数组的两种方式 ===\n\n");// 方式1:二维字符数组(矩形数组)printf("方式1: 二维字符数组\n");charnames1[4][20]={// 4个字符串,每个最多19字符+'\0'"Alice","Bob","Charlie","David"};printf(" 内存布局: 连续存储,每行固定20字节\n");for(inti=0;i<4;i++){printf(" names1[%d] = \"%s\" (地址: %p)\n",i,names1[i],(void*)names1[i]);}printf(" 总内存: %zu 字节\n",sizeof(names1));// 可以修改内容names1[0][0]='A';// 正确printf(" 修改后: %s\n\n",names1[0]);// 方式2:字符指针数组printf("方式2: 字符指针数组\n");char*names2[]={// 4个指针,指向字符串字面量"Alice","Bob","Charlie","David"};printf(" 内存布局: 指针数组+字符串字面量\n");for(inti=0;i<4;i++){printf(" names2[%d] = \"%s\" (指针地址: %p, 字符串地址: %p)\n",i,names2[i],(void*)&names2[i],(void*)names2[i]);}printf(" 指针数组大小: %zu 字节\n",sizeof(names2));// 注意:不能修改字符串字面量的内容// names2[0][0] = 'A'; // 错误:运行时错误printf(" 注意: 字符串字面量通常不可修改\n\n");// 方式3:动态分配的指针数组printf("方式3: 动态分配的字符串数组\n");char**names3=malloc(4*sizeof(char*));// 分配指针数组if(names3){for(inti=0;i<4;i++){names3[i]=malloc(20*sizeof(char));// 为每个字符串分配空间sprintf(names3[i],"Dynamic %d",i+1);// 创建字符串}for(inti=0;i<4;i++){printf(" names3[%d] = \"%s\"\n",i,names3[i]);}// 可以修改names3[0][0]='X';printf(" 修改后: %s\n",names3[0]);// 释放内存for(inti=0;i<4;i++){free(names3[i]);}free(names3);}printf("\n对比总结:\n");printf(" 二维数组: 内存连续,固定大小,可修改\n");printf(" 指针数组: 内存不连续,字符串长度可变,字面量不可修改\n");printf(" 动态分配: 最灵活,但需要管理内存\n");}5.2 命令行参数处理
main函数的参数就是字符串指针数组的典型应用:
#include<stdio.h>// argc: 参数个数// argv: 参数向量(字符串指针数组)intmain(intargc,char*argv[]){printf("=== 命令行参数处理 ===\n\n");printf("程序名: %s\n",argv[0]);printf("参数个数: %d\n",argc-1);if(argc>1){printf("参数列表:\n");for(inti=1;i<argc;i++){printf(" argv[%d] = \"%s\"\n",i,argv[i]);}// 解析参数printf("\n参数解析示例:\n");for(inti=1;i<argc;i++){if(argv[i][0]=='-'){// 选项printf(" 选项: %s\n",argv[i]);// 进一步解析选项for(intj=1;argv[i][j]!='\0';j++){printf(" 子选项: -%c\n",argv[i][j]);}}else{// 参数printf(" 参数: %s\n",argv[i]);}}}else{printf("没有额外参数\n");printf("用法: %s [选项] [参数]\n",argv[0]);}return0;}/* 编译后运行示例: $ ./program -abc input.txt output.txt 输出: === 命令行参数处理 === 程序名: ./program 参数个数: 3 参数列表: argv[1] = "-abc" argv[2] = "input.txt" argv[3] = "output.txt" 参数解析示例: 选项: -abc 子选项: -a 子选项: -b 子选项: -c 参数: input.txt 参数: output.txt */六、字符串与内存管理
6.1 常见的内存错误
字符串操作是内存错误的常见来源:
#include<stdio.h>#include<stdlib.h>#include<string.h>voidcommon_string_errors(){printf("=== 字符串常见内存错误 ===\n\n");printf("错误1: 缓冲区溢出\n");charbuffer[10];// strcpy(buffer, "This string is too long"); // 溢出!// 正确做法:strncpy(buffer,"This string is too long",sizeof(buffer)-1);buffer[sizeof(buffer)-1]='\0';printf(" 安全复制: %s\n",buffer);printf("\n错误2: 忘记分配内存\n");char*ptr;// strcpy(ptr, "Hello"); // 错误:ptr未初始化// 正确做法:ptr=malloc(10*sizeof(char));if(ptr){strcpy(ptr,"Hello");printf(" 正确分配: %s\n",ptr);free(ptr);}printf("\n错误3: 修改字符串字面量\n");char*literal="Immutable";// literal[0] = 'X'; // 运行时错误// 正确做法: 使用字符数组或constcharmutable[]="Mutable";mutable[0]='X';// 正确printf(" 可修改字符串: %s\n",mutable);printf("\n错误4: 内存泄漏\n");char*leaky=malloc(100*sizeof(char));strcpy(leaky,"This will leak");// 忘记 free(leaky); // 内存泄漏!// 正确做法:free(leaky);leaky=NULL;// 避免悬空指针printf(" 已正确释放内存\n");printf("\n错误5: 使用已释放的内存\n");char*freed=malloc(20*sizeof(char));strcpy(freed,"Some text");free(freed);// printf("%s\n", freed); // 错误:使用已释放的内存freed=NULL;// 安全做法printf(" 已设置指针为NULL\n");printf("\n安全编程建议:\n");printf(" 1. 始终检查边界\n");printf(" 2. 使用strncpy而不是strcpy\n");printf(" 3. 记得释放动态内存\n");printf(" 4. 使用const保护不可修改的字符串\n");printf(" 5. 释放后立即设置指针为NULL\n");}6.2 安全的字符串处理函数
C11标准引入了安全版本的字符串函数:
#define__STDC_WANT_LIB_EXT1__1// 启用安全函数#include<stdio.h>#include<string.h>voidsafe_string_functions(){printf("=== 安全的字符串函数(C11) ===\n\n");chardest[10];charsrc[]="This is a very long string that might overflow";printf("传统方法(危险):\n");// strcpy(dest, src); // 缓冲区溢出!printf("安全方法1: strncpy\n");strncpy(dest,src,sizeof(dest)-1);dest[sizeof(dest)-1]='\0';printf(" 结果: %s\n",dest);#ifdef__STDC_LIB_EXT1__printf("\n安全方法2: strcpy_s(C11安全函数)\n");// strcpy_s(dest, sizeof(dest), src); // 运行时检查// printf(" 如果溢出会返回错误\n");#elseprintf("\n注意: 此编译器不支持C11安全函数\n");#endifprintf("\n自己实现的安全函数:\n");// 安全的字符串复制char*safe_strcpy(char*dest,size_tdest_size,constchar*src){if(dest==NULL||src==NULL||dest_size==0){returnNULL;}size_ti;for(i=0;i<dest_size-1&&src[i]!='\0';i++){dest[i]=src[i];}dest[i]='\0';returndest;}charbuffer[10];safe_strcpy(buffer,sizeof(buffer),"Hello World");printf(" 安全复制到大小为10的缓冲区: %s\n",buffer);// 安全的字符串连接char*safe_strcat(char*dest,size_tdest_size,constchar*src){if(dest==NULL||src==NULL||dest_size==0){returnNULL;}// 找到dest的结尾size_tdest_len=0;while(dest_len<dest_size&&dest[dest_len]!='\0'){dest_len++;}// 计算剩余空间size_tremaining=dest_size-dest_len;// 追加src(不超过剩余空间-1留给'\0')size_ti;for(i=0;i<remaining-1&&src[i]!='\0';i++){dest[dest_len+i]=src[i];}dest[dest_len+i]='\0';returndest;}charbuf[15]="Hello";safe_strcat(buf,sizeof(buf)," World!");printf(" 安全连接: %s\n",buf);}七、字符串与文件操作
7.1 文件中的字符串处理
#include<stdio.h>#include<stdlib.h>#include<string.h>voidstring_file_operations(){printf("=== 字符串与文件操作 ===\n\n");// 1. 写入字符串到文件printf("1. 写入字符串到文件:\n");FILE*file=fopen("test.txt","w");if(file){char*lines[]={"Hello World\n","This is a test\n","String file operations\n"};for(inti=0;i<3;i++){fputs(lines[i],file);// 写入字符串}fclose(file);printf(" 已写入3行到test.txt\n");}// 2. 从文件读取字符串printf("\n2. 从文件读取字符串:\n");file=fopen("test.txt","r");if(file){charbuffer[100];intline_num=1;printf(" 文件内容:\n");while(fgets(buffer,sizeof(buffer),file)!=NULL){printf(" 行 %d: %s",line_num++,buffer);// 处理每行字符串// 去除换行符size_tlen=strlen(buffer);if(len>0&&buffer[len-1]=='\n'){buffer[len-1]='\0';}printf(" 处理后的长度: %zu\n",strlen(buffer));}fclose(file);}// 3. 格式化字符串写入printf("\n3. 格式化字符串写入:\n");file=fopen("data.txt","w");if(file){charname[]="Alice";intage=25;floatscore=95.5;fprintf(file,"Name: %s\n",name);fprintf(file,"Age: %d\n",age);fprintf(file,"Score: %.2f\n",score);fclose(file);printf(" 已格式化写入到data.txt\n");}// 4. 读取CSV文件(逗号分隔值)printf("\n4. 读取CSV文件示例:\n");file=fopen("data.csv","w+");if(file){// 写入CSV数据fprintf(file,"Name,Age,Score\n");fprintf(file,"Alice,25,95.5\n");fprintf(file,"Bob,30,88.0\n");fprintf(file,"Charlie,22,92.5\n");// 回到文件开头rewind(file);// 读取并解析CSVprintf(" 解析CSV数据:\n");charline[100];fgets(line,sizeof(line),file);// 跳过标题行while(fgets(line,sizeof(line),file)!=NULL){// 去除换行符line[strcspn(line,"\n")]='\0';// 使用strtok分割char*name=strtok(line,",");char*age_str=strtok(NULL,",");char*score_str=strtok(NULL,",");if(name&&age_str&&score_str){intage=atoi(age_str);floatscore=atof(score_str);printf(" 姓名: %-10s 年龄: %3d 分数: %5.1f\n",name,age,score);}}fclose(file);}// 清理remove("test.txt");remove("data.txt");remove("data.csv");}7.2 配置文件解析
#include<stdio.h>#include<stdlib.h>#include<string.h>#include<ctype.h>#defineMAX_LINE_LENGTH256#defineMAX_KEY_LENGTH50#defineMAX_VALUE_LENGTH100// 配置文件条目结构typedefstruct{charkey[MAX_KEY_LENGTH];charvalue[MAX_VALUE_LENGTH];}ConfigEntry;// 去除字符串两端的空白字符voidtrim_string(char*str){char*end;// 去除前导空白while(isspace((unsignedchar)*str)){str++;}// 如果是空字符串if(*str=='\0'){return;}// 去除尾部空白end=str+strlen(str)-1;while(end>str&&isspace((unsignedchar)*end)){end--;}// 写入新的结束符*(end+1)='\0';}// 解析配置文件intparse_config_file(constchar*filename,ConfigEntry*configs,intmax_configs){FILE*file=fopen(filename,"r");if(!file){printf("无法打开文件: %s\n",filename);return0;}charline[MAX_LINE_LENGTH];intcount=0;while(fgets(line,sizeof(line),file)!=NULL&&count<max_configs){// 去除换行符line[strcspn(line,"\n")]='\0';// 跳过空行和注释trim_string(line);if(line[0]=='\0'||line[0]=='#'){continue;}// 查找等号char*equal_sign=strchr(line,'=');if(!equal_sign){continue;// 无效行}// 分割键和值*equal_sign='\0';char*key=line;char*value=equal_sign+1;// 去除空白trim_string(key);trim_string(value);if(key[0]!='\0'){// 存储到配置数组strncpy(configs[count].key,key,MAX_KEY_LENGTH-1);configs[count].key[MAX_KEY_LENGTH-1]='\0';strncpy(configs[count].value,value,MAX_VALUE_LENGTH-1);configs[count].value[MAX_VALUE_LENGTH-1]='\0';count++;}}fclose(file);returncount;}// 查找配置值constchar*get_config_value(constchar*key,ConfigEntry*configs,intcount){for(inti=0;i<count;i++){if(strcmp(configs[i].key,key)==0){returnconfigs[i].value;}}returnNULL;}voidconfig_file_demo(){printf("=== 配置文件解析示例 ===\n\n");// 创建示例配置文件FILE*config_file=fopen("app.conf","w");if(config_file){fprintf(config_file,"# 应用程序配置\n\n");fprintf(config_file,"app_name = My Application\n");fprintf(config_file,"version = 1.0.0\n");fprintf(config_file,"max_users = 100\n");fprintf(config_file,"debug_mode = true\n");fprintf(config_file,"log_level = info\n");fclose(config_file);}// 解析配置文件ConfigEntry configs[10];intcount=parse_config_file("app.conf",configs,10);if(count>0){printf("成功解析 %d 个配置项:\n\n",count);for(inti=0;i<count;i++){printf(" %-15s = %s\n",configs[i].key,configs[i].value);}// 查找特定配置printf("\n查找配置项:\n");constchar*keys[]={"app_name","max_users","not_found"};for(inti=0;i<3;i++){constchar*value=get_config_value(keys[i],configs,count);if(value){printf(" %s: %s\n",keys[i],value);}else{printf(" %s: (未找到)\n",keys[i]);}}// 类型转换示例printf("\n类型转换:\n");constchar*max_users_str=get_config_value("max_users",configs,count);if(max_users_str){intmax_users=atoi(max_users_str);printf(" max_users (整数): %d\n",max_users);}constchar*debug_mode_str=get_config_value("debug_mode",configs,count);if(debug_mode_str){// 简单布尔值解析intdebug_mode=0;if(strcmp(debug_mode_str,"true")==0||strcmp(debug_mode_str,"1")==0||strcmp(debug_mode_str,"yes")==0){debug_mode=1;}printf(" debug_mode (布尔): %s\n",debug_mode?"是":"否");}}// 清理remove("app.conf");}八、字符串加密与编码
8.1 简单的字符串加密算法
#include<stdio.h>#include<string.h>#include<ctype.h>// 凯撒密码:将字符串中的每个字母移动n位voidcaesar_cipher(char*str,intshift){while(*str){if(isalpha(*str)){charbase=islower(*str)?'a':'A';*str=((*str-base+shift)%26)+base;}str++;}}// XOR加密:使用密钥对字符串进行XOR运算voidxor_cipher(char*str,charkey){while(*str){*str=*str^key;// XOR运算str++;}}// 简单的Base64编码(简化版)voidsimple_base64_encode(constchar*input,char*output){constcharbase64_table[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";inti=0,j=0;unsignedcharchar_array_3[3];unsignedcharchar_array_4[4];while(*input){char_array_3[i++]=*(input++);if(i==3){char_array_4[0]=(char_array_3[0]&0xfc)>>2;char_array_4[1]=((char_array_3[0]&0x03)<<4)+((char_array_3[1]&0xf0)>>4);char_array_4[2]=((char_array_3[1]&0x0f)<<2)+((char_array_3[2]&0xc0)>>6);char_array_4[3]=char_array_3[2]&0x3f;for(i=0;i<4;i++){output[j++]=base64_table[char_array_4[i]];}i=0;}}if(i){for(intk=i;k<3;k++){char_array_3[k]='\0';}char_array_4[0]=(char_array_3[0]&0xfc)>>2;char_array_4[1]=((char_array_3[0]&0x03)<<4)+((char_array_3[1]&0xf0)>>4);char_array_4[2]=((char_array_3[1]&0x0f)<<2)+((char_array_3[2]&0xc0)>>6);char_array_4[3]=char_array_3[2]&0x3f;for(intk=0;k<i+1;k++){output[j++]=base64_table[char_array_4[k]];}while(i++<3){output[j++]='=';}}output[j]='\0';}voidstring_encryption_demo(){printf("=== 字符串加密与编码 ===\n\n");// 凯撒密码示例printf("1. 凯撒密码:\n");chartext1[]="Hello World";printf(" 原文: %s\n",text1);caesar_cipher(text1,3);// 移3位printf(" 加密: %s\n",text1);caesar_cipher(text1,-3);// 移回3位printf(" 解密: %s\n\n",text1);// XOR加密示例printf("2. XOR加密:\n");chartext2[]="Secret Message";charkey=0x55;// 加密密钥printf(" 原文: %s\n",text2);xor_cipher(text2,key);printf(" 加密: ");for(inti=0;text2[i]!='\0';i++){printf("%02X ",(unsignedchar)text2[i]);}printf("\n");xor_cipher(text2,key);// 再次XOR恢复原文本printf(" 解密: %s\n\n",text2);// Base64编码示例printf("3. Base64编码:\n");chartext3[]="Hello Base64";charencoded[100];printf(" 原文: %s\n",text3);simple_base64_encode(text3,encoded);printf(" 编码: %s\n\n",encoded);// 实用示例:密码隐藏printf("4. 密码输入隐藏:\n");charpassword[50];inti=0;charch;printf(" 请输入密码: ");// 简单的密码隐藏(不回显)while(1){ch=getchar();if(ch=='\n'||ch=='\r'){break;}elseif(ch==127||ch==8){// 退格键if(i>0){i--;printf("\b \b");// 删除一个星号}}else{if(i<sizeof(password)-1){password[i++]=ch;printf("*");}}}password[i]='\0';printf("\n 您输入的密码长度: %zu\n",strlen(password));// 简单加密存储for(intj=0;j<i;j++){password[j]=password[j]^0xAA;// 简单加密}printf(" 加密后的密码: ");for(intj=0;j<i;j++){printf("%02X ",(unsignedchar)password[j]);}printf("\n");}九、字符串搜索与替换
9.1 实现文本搜索功能
#include<stdio.h>#include<string.h>#include<stdlib.h>// 查找字符串中所有出现的位置voidfind_all_occurrences(constchar*str,constchar*substr){printf("在 \"%s\" 中查找 \"%s\":\n",str,substr);constchar*pos=str;intcount=0;while((pos=strstr(pos,substr))!=NULL){printf(" 位置 %d: 偏移量 %ld\n",++count,pos-str);pos+=strlen(substr);// 移动到下一个可能的位置}if(count==0){printf(" 未找到\n");}else{printf(" 总共找到 %d 次\n",count);}}// 不区分大小写的字符串比较intcase_insensitive_compare(constchar*str1,constchar*str2){while(*str1&&*str2){if(tolower(*str1)!=tolower(*str2)){returntolower(*str1)-tolower(*str2);}str1++;str2++;}returntolower(*str1)-tolower(*str2);}// 不区分大小写的查找voidfind_case_insensitive(constchar*str,constchar*substr){printf("\n不区分大小写查找 \"%s\":\n",substr);intstr_len=strlen(str);intsub_len=strlen(substr);intcount=0;for(inti=0;i<=str_len-sub_len;i++){// 创建临时子字符串进行比较chartemp[sub_len+1];strncpy(temp,str+i,sub_len);temp[sub_len]='\0';if(case_insensitive_compare(temp,substr)==0){printf(" 位置 %d: 偏移量 %d\n",++count,i);}}if(count==0){printf(" 未找到\n");}}// 字符串替换函数char*replace_string(constchar*str,constchar*old,constchar*new){// 计算新字符串所需的空间intcount=0;constchar*tmp=str;while((tmp=strstr(tmp,old))!=NULL){count++;tmp+=strlen(old);}// 分配内存size_tnew_len=strlen(str)+count*(strlen(new)-strlen(old))+1;char*result=malloc(new_len);if(!result){returnNULL;}char*current=result;constchar*start=str;constchar*found;while((found=strstr(start,old))!=NULL){// 复制旧字符串之前的部分size_tlen=found-start;strncpy(current,start,len);current+=len;// 复制新字符串strcpy(current,new);current+=strlen(new);// 移动到旧字符串之后start=found+strlen(old);}// 复制剩余部分strcpy(current,start);returnresult;}voidsearch_and_replace_demo(){printf("=== 字符串搜索与替换 ===\n\n");chartext[]="The quick brown fox jumps over the lazy dog. The fox is quick.";// 查找所有出现find_all_occurrences(text,"fox");find_all_occurrences(text,"the");// 不区分大小写查找find_case_insensitive(text,"the");// 字符串替换printf("\n字符串替换:\n");printf("原文: %s\n",text);char*replaced=replace_string(text,"fox","cat");if(replaced){printf("替换后: %s\n",replaced);free(replaced);}// 更复杂的替换replaced=replace_string(text,"quick","fast");if(replaced){printf("再次替换: %s\n",replaced);free(replaced);}// 单词统计printf("\n单词统计:\n");char*copy=strdup(text);// 复制字符串char*word;intword_count=0;word=strtok(copy," .,!?");// 按空格和标点分割while(word!=NULL){word_count++;printf(" 单词 %d: %s\n",word_count,word);word=strtok(NULL," .,!?");}printf("总单词数: %d\n",word_count);free(copy);}十、实战项目:简易文本编辑器
#include<stdio.h>#include<stdlib.h>#include<string.h>#include<ctype.h>#defineMAX_LINES1000#defineMAX_LINE_LENGTH256// 文本缓冲区结构typedefstruct{char**lines;// 字符串指针数组intline_count;// 总行数intcapacity;// 容量}TextBuffer;// 初始化文本缓冲区TextBuffer*create_text_buffer(){TextBuffer*buffer=malloc(sizeof(TextBuffer));if(!buffer)returnNULL;buffer->capacity=100;buffer->line_count=0;buffer->lines=malloc(buffer->capacity*sizeof(char*));if(!buffer->lines){free(buffer);returnNULL;}returnbuffer;}// 添加一行文本intadd_line(TextBuffer*buffer,constchar*text){if(buffer->line_count>=buffer->capacity){// 扩容buffer->capacity*=2;buffer->lines=realloc(buffer->lines,buffer->capacity*sizeof(char*));if(!buffer->lines)return0;}buffer->lines[buffer->line_count]=strdup(text);if(!buffer->lines[buffer->line_count])return0;buffer->line_count++;return1;}// 插入一行文本intinsert_line(TextBuffer*buffer,intposition,constchar*text){if(position<0||position>buffer->line_count){return0;// 位置无效}if(buffer->line_count>=buffer->capacity){buffer->capacity*=2;buffer->lines=realloc(buffer->lines,buffer->capacity*sizeof(char*));if(!buffer->lines)return0;}// 移动后续行for(inti=buffer->line_count;i>position;i--){buffer->lines[i]=buffer->lines[i-1];}buffer->lines[position]=strdup(text);if(!buffer->lines[position])return0;buffer->line_count++;return1;}// 删除一行intdelete_line(TextBuffer*buffer,intposition){if(position<0||position>=buffer->line_count){return0;// 位置无效}free(buffer->lines[position]);// 移动后续行for(inti=position;i<buffer->line_count-1;i++){buffer->lines[i]=buffer->lines[i+1];}buffer->line_count--;return1;}// 保存到文件intsave_to_file(TextBuffer*buffer,constchar*filename){FILE*file=fopen(filename,"w");if(!file)return0;for(inti=0;i<buffer->line_count;i++){fprintf(file,"%s\n",buffer->lines[i]);}fclose(file);return1;}// 从文件加载intload_from_file(TextBuffer*buffer,constchar*filename){FILE*file=fopen(filename,"r");if(!file)return0;charline[MAX_LINE_LENGTH];// 清空当前缓冲区for(inti=0;i<buffer->line_count;i++){free(buffer->lines[i]);}buffer->line_count=0;// 读取文件while(fgets(line,sizeof(line),file)!=NULL){// 去除换行符line[strcspn(line,"\n")]='\0';if(!add_line(buffer,line)){fclose(file);return0;}}fclose(file);return1;}// 查找文本voidsearch_text(TextBuffer*buffer,constchar*pattern){printf("\n查找 \"%s\":\n",pattern);intfound=0;for(inti=0;i<buffer->line_count;i++){char*pos=strstr(buffer->lines[i],pattern);if(pos){printf(" 行 %d: %s\n",i+1,buffer->lines[i]);// 高亮显示匹配部分printf(" ");for(intj=0;j<pos-buffer->lines[i];j++){printf(" ");}for(intj=0;j<strlen(pattern);j++){printf("^");}printf("\n");found=1;}}if(!found){printf(" 未找到\n");}}// 替换文本intreplace_text(TextBuffer*buffer,constchar*old,constchar*new){intreplaced=0;for(inti=0;i<buffer->line_count;i++){char*replaced_line=replace_string(buffer->lines[i],old,new);if(replaced_line&&strcmp(replaced_line,buffer->lines[i])!=0){free(buffer->lines[i]);buffer->lines[i]=replaced_line;replaced=1;}elseif(replaced_line){free(replaced_line);}}returnreplaced;}// 显示文本voiddisplay_text(TextBuffer*buffer){printf("\n当前文本内容:\n");printf("========================================\n");for(inti=0;i<buffer->line_count;i++){printf("%3d: %s\n",i+1,buffer->lines[i]);}printf("========================================\n");}// 释放缓冲区voidfree_text_buffer(TextBuffer*buffer){for(inti=0;i<buffer->line_count;i++){free(buffer->lines[i]);}free(buffer->lines);free(buffer);}voidtext_editor_demo(){printf("=== 简易文本编辑器 ===\n\n");TextBuffer*buffer=create_text_buffer();if(!buffer){printf("创建文本缓冲区失败!\n");return;}// 添加一些示例文本add_line(buffer,"这是第一行文本");add_line(buffer,"这是第二行,包含一些内容");add_line(buffer,"第三行用于演示文本编辑器");add_line(buffer,"第四行包含关键词:文本");intchoice;charinput[MAX_LINE_LENGTH];charfilename[100];intline_num;do{printf("\n菜单:\n");printf("1. 显示文本\n");printf("2. 添加行\n");printf("3. 插入行\n");printf("4. 删除行\n");printf("5. 保存到文件\n");printf("6. 从文件加载\n");printf("7. 查找文本\n");printf("8. 替换文本\n");printf("0. 退出\n");printf("请选择: ");scanf("%d",&choice);getchar();// 消耗换行符switch(choice){case1:display_text(buffer);break;case2:printf("请输入要添加的文本: ");fgets(input,sizeof(input),stdin);input[strcspn(input,"\n")]='\0';// 去除换行符if(add_line(buffer,input)){printf("添加成功!\n");}else{printf("添加失败!\n");}break;case3:printf("请输入行号: ");scanf("%d",&line_num);getchar();// 消耗换行符printf("请输入要插入的文本: ");fgets(input,sizeof(input),stdin);input[strcspn(input,"\n")]='\0';if(insert_line(buffer,line_num-1,input)){printf("插入成功!\n");}else{printf("插入失败!\n");}break;case4:printf("请输入要删除的行号: ");scanf("%d",&line_num);getchar();// 消耗换行符if(delete_line(buffer,line_num-1)){printf("删除成功!\n");}else{printf("删除失败!\n");}break;case5:printf("请输入文件名: ");fgets(filename,sizeof(filename),stdin);filename[strcspn(filename,"\n")]='\0';if(save_to_file(buffer,filename)){printf("保存成功!\n");}else{printf("保存失败!\n");}break;case6:printf("请输入文件名: ");fgets(filename,sizeof(filename),stdin);filename[strcspn(filename,"\n")]='\0';if(load_from_file(buffer,filename)){printf("加载成功!\n");}else{printf("加载失败!\n");}break;case7:printf("请输入要查找的文本: ");fgets(input,sizeof(input),stdin);input[strcspn(input,"\n")]='\0';search_text(buffer,input);break;case8:printf("请输入要替换的文本: ");fgets(input,sizeof(input),stdin);input[strcspn(input,"\n")]='\0';charnew_text[MAX_LINE_LENGTH];printf("请输入替换后的文本: ");fgets(new_text,sizeof(new_text),stdin);new_text[strcspn(new_text,"\n")]='\0';if(replace_text(buffer,input,new_text)){printf("替换成功!\n");}else{printf("未找到匹配文本!\n");}break;case0:printf("退出文本编辑器\n");break;default:printf("无效选择!\n");}}while(choice!=0);free_text_buffer(buffer);}十一、总结:字符串指针的核心要点
11.1 关键概念回顾
- 字符串的本质:以’\0’结尾的字符数组
- 字符串指针:指向字符串第一个字符的指针
- 内存区域:字符串可以存储在栈、堆或常量区
- 安全性:注意缓冲区溢出和内存管理
11.2 常见字符串操作模式
操作 安全做法 危险做法
复制 strncpy, 检查边界 strcpy, 不检查边界
连接 strncat, 计算剩余空间 strcat, 可能溢出
比较 strncmp, 指定长度 strcmp, 可能越界
内存 动态分配,及时释放 静态分配,可能不够
11.3 最佳实践
- 始终检查边界:确保目标缓冲区足够大
- 使用const修饰不可修改的字符串:提高代码安全性
- 记得释放动态内存:防止内存泄漏
- 处理字符串结束符:确保字符串正确终止
- 考虑本地化:对于多语言支持,注意字符编码