目录
前言
一、字符串
二、字符串的输入
1、gets():
2、fgets():gets()的替代品
3、scanf():
三、字符串的输出
1、puts():
2、fputs():
3、printf():
4、自定义的输出函数
四、字符串函数
1、字符串长度函数strlen():
2、字符串的复制函数strcpy():
3、字符串的连接函数strcat():
4、字符串的比较函数strcmp():
5、字符串的复制函数strncpy():
6、字符串的连接函数strncat():
7、字符串的比较函数strncmp()
8、字符串查找函数strstr():
9、切分字符串函数strtok():
10、strerror()
11、perror():
五、字符函数
前言
字符串是C语言中最有用、最重要的数据类型之一本文是对字符串、字符串函数和字符函数笔记的总结。
一、字符串
1.字符串常量字符串字面量用双引号括起来的内容。字符串的结束标志是一个 \0 的转义字符。双引号中的字符和编译器自动加入末尾的\0字符都作为字符串存储在内存中。
如果字符串常量之间没有间隔或者用空白字符分隔C将其视为串联起来的字符串常量。如char greeting[50] "Hello,and""how are""you" " today!";
等价于char greeting[50] "Hello,and how are you today!";
字符串常量属于静态存储类别这说明如果在函数中使用字符串常量该字符串只会被存储一次在整个程序的生命周期内存在即使函数被调用多次。
2.字符串数组的初始化
定义字符串数组时必须要让编译器知道需要多少空间我们可以采取两种方法
1用足够的空间的数组存储字符串必须确保数组元素的个数至少比字符串长度多1用于容纳\0其余所有未被使用的元素都被自动初始化为0这里的0不是指数字字符0而是char形式的空字符,如
2省略数组初始化声明中的大小编译器会自动计算数组的大小如
char arr[] "ant and";
3.字符串数组和字符指针
char sa[] "array"; VS char* sp "point";
1数组形式
在计算机的内存中为数组sa分配一块连续的空间。通常字符串都作为可执行文件的一部分存储在数据段中。当把程序载入内存时也载入了程序的字符串。字符串存储在静态存储区中但是程序在开始运行时才会为该数组分配内存此时才将字符串拷贝到数组中。注意此时字符串有两个副本一个是在静态内存中的字符串常量另一个是存储在sa数组中的字符串。此后编译器便把数组名sa识别为该数组首元素的地址。注意sa为地址常量必能更改如果更改这就意味着改变了数组的存储位置。可以有sa1这样的操作标识数组的下一个元素但是不允许sa等操作递增递减运算符只能用于变量名前不能用于常量。
2指针形式
编译器也会为字符串在静态存储区预留一块连续的空间另外一旦开始执行程序它会为指针变量sp也留出一块存储位置并把字符串的地址存储在指针变量中该变量最终指向字符串的首字符。sp为指针变量它的值可以改变因此可以使用递增递减运算符。
我们来看一段代码
#include #define MSG "I am special"int main(){char sa[] "I am special";const char* sp "I am special";printf("%p\n", "I am special");printf("%p\n", sa);printf("%p\n", sp);printf("%p\n", MSG);printf("%p\n", "I am special");return 0;}
编译并运行该代码输出如下
该程序的输出说明了
①初始化数组把静态存储区的字符串拷贝到数组中而初始化指针只把字符串的地址拷贝给指针。sp和MSG的地址相同而sa的地址不同
②虽然字符串常量“I am special”在程序中的两个printf()函数中出现了两次但是编译器只使用了一个存储位置。而且与MSG的位置相同
③静态数据使用的内存与sa使用的动态内存不同。不仅值不同特定的编译器甚至使用不同的位数表示两种内存
3如果要改变字符串就不要用指针指向字符串常量可以把非const数组初始化为字符串常量因为数组获得的是原始字符串的副本。我们来看一段代码
#include int main(){char* p "Klingon";p[0] F;printf("Klingon\n");printf(":Beware the %s!\n", "Klingon");return 0;}
p[0] F;这样的行为是未被定义的这样的语句有可能导致内存访问出错也有可能会输出Flingon:Beware the Flingons在内存中的字符串常量只有一份修改这一份会影响多出使用的地方编译器在这方面难以捉摸所以我们建议在把指针初始化为字符串常量时使用const限定符。即const char* p1 "Klingon";
4.指针和字符串
#include int main(){const char* mesg "apple";const char* copy;copy mesg;printf("mesg %s; %p; value %p\n", mesg, printf("copy %s; %p; value %p\n", copy, return 0;}
编译并运行该代码输出如下
从输出结果我们可以知道mesg和copy的地址分别是00CFFC50和00CFFC44mesg和copy都指向同一位置程序并没有拷贝字符串两个value值相同。
5.字符串数组和指针数组
char ccolor[][6] {"red","blue","green"};char* pcolor [] {"red","blue","green"};
区别如下
1ccolor是一个内含3个数组的数组每个数组内含6个char类型的值共占用18个字节而pcolor是一个内含3个指针的数组共占用12个字节。
2pcolor中的指针指向初始化时所用的字符串常量的位置这些字符串常量存储在静态内存中它所指向的字符串常量不能更改ccolor中的数组则存储这字符串常量的副本所以每个字符串都被存储了两次此外为字符串数组分配内存的使用率较低ccolor中的每个元素必须相同而且必须是能存储最长字符串的大小
6.我们来做一道有关字符串数组的题目
#include int main(){char str1[] "hello world.";char str2[] "hello world.";char *str3 "hello world.";char *str4 "hello world.";if(str1 str2)printf("str1 and str2 are same\n");elseprintf("str1 and str2 are not same\n");if(str3 str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");return 0; }
编译并运行该程序输出如下
str1 and str2 are not same
str3 and str4 are same
//根据前面的总结我们可以轻松得到这条题的答案
二、字符串的输入 1、gets():
它读取整行输入直至遇到换行符然后丢弃换行符存储其他字符并在这些字符的末尾添加一个空字符使其成为一个c字符串。
注意gets()存在安全隐患gets()无法检查字符串是否装得下输入行如果输入的字符串过长会导致缓冲区溢出如果这些多余的字符只是占用了尚未使用的内存就不会立即出现问题如果它们擦除程序中的其他数据会导致程序异常终止或者还有其他情况。所以不久C11便摒弃了gets()然而在实际应用中编译器为了可以兼容以前的代码大部分都支持该函数。
2、fgets():gets()的替代品
1char *fgets( char *string, int n, FILE *stream );
string数据存储的位置
n读入字符的最大数量
stream要读入的文件如果从键盘输入数据则以stdin标准输入作为参数
2如果一切进行顺利该函数返回的地址和传入的第一个参数相同如果遇到文件末尾或发生错误该函数会返回NULL。可以使用feof或ferror确定是否发生错误。
3与gets()还有一点不同的是如果fgets()读到换行符会把它存储在字符串中。但是有时候我们并不想把\n存储在字符串中这样的换行符会带来一些麻烦我们可以自定义一个函数读取整行输入并用空字符代替换行符或者读取一部分输入并丢弃其余部分看如下代码段
char* mygets (char* st, int n){char* ret;int i 0;ret fgets(st, n, stdin);if(ret){while(st[i] !\n \0){i;}if(st[i] \n){st[i] \0;}else{while(getchar() ! \n){continue;}}}}
我们的这个函数的缺点在于在遇到不合适的输入时毫无反应。
3、scanf():
使用格式控制说明%s输入参数必须是字符型数组名。该函数以下一个空白字符空行、空格、制表符或换行符作为字符串的结束。如果指定了字段宽度如%10s那么scanf()将读取10个字符或读到第一个空白字符停止。如图表
输入语句原输入序列name中内容剩余输入序列scanf("%s",name);Fleebert[]HupFleebert[]Hupscanf("%5s",name);Fleebert[]HupFleebert[]Hupscanf("%5s",name);Ann[]UlarAnn[]Ular注[]代表一个空格
scanf()返回一个整数值该值等于scanf()成功读取的项数或EOF读到文件结尾时返回EOF
其实scanf()和gets()一样也存在一些潜在的缺点。如果输入行的内容过长scanf()也会导致数据溢出不过%s转换说明中使用字段宽度可防止溢出。
三、字符串的输出 1、puts():
puts()的使用很简单只需要把字符串的地址作为参数传递给它即可。它在显示字符串时会自动在其末尾添加一个换行符。该函数在遇到空字符时就停止输出所以必须确保有空字符
2、fputs():
1int fputs( const char *string, FILE *stream );
string:要输出的字符串
stream要写入数据的文件。如果要打印在显示器上可以用定义在stdio.h中的stdout标准输出作为该参数
2与puts()不同的是fputs()不会在输出的末尾添加换行符
3、printf():
1用%s输出字符串输出参数可以是字符数组名或字符串常量输出遇\0结束。
2与puts()不同的是printf()不会在输出的末尾添加换行符
4、自定义的输出函数
不一定非要使用C库中的标准函数我们自己也可以设计一个函数。如该函数可以打印一个字符串并且统计打印的字符数
int myputs(const char* string){int count 0;while(*string)//*string ! \0{putchar(*string);count;}putchar(\n);return 0;}
四、字符串函数 1、字符串长度函数strlen():
size_t strlen( const char *string );
strlen(s);参数s可以是字符数组名或字符串常量
1引用头文件
2字符串以 \0 作为结束标志strlen函数返回的是在字符串中 \0 前面出现的字符个数不包含 \0 )。该函数返回的是无符号整型#include #include int main(){const char*str1 "abcdef";const char*str2 "bbb";if(strlen(str2) - strlen(str1) > 0){printf("str2>str1\n");} else{printf("srt1>str2\n");}return 0; }
编译并运行该代码输出如下
str2>str1
//该函数返回的是的无符号整型无符号数-无符号数无符号数没有符号位的概念3-6-3这将被解读成一个很大的正数
3参数指向的字符串必须要以 \0 结束char arr {"a","b","c"};strlen(arr);//随机值因为你不知道\0在哪里
4自定义的strlen():
#include #include //法一利用计数器int mystrlen(const char* str){int count 0;assert(str ! NULL);while(*str ! \0){count;str;}return count;}//法二函数递归 int mystrlen(const char* str){assert(str ! NULL);if(*str ! \0){return 1 mystrlen(str 1);}else{return 0;}}//法三指针-指针int my_strlen(const char *str) {char *p s;while(*p ! \0 )p;return p-s; }int main(){char* arr "abc";printf("%d\n",mystrlen(arr));return 0;}
2、字符串的复制函数strcpy():
char *strcpy( char *strDestination, const char *strSource );
strcpy(s1,s2);参数s1必须是字符型基地址参数s2可以是字符数组名或字符串常量
1引用头文件
2源字符串必须以 \0 结束。
3会将源字符串中的 \0 拷贝到目标空间。
4目标空间必须足够大以确保能存放源字符串。
5目标空间必须可变。
#include int main(){char arr1[] "xxxxxxxxxx";//char arr1[] { a,b,c };//error 源字符串没有以\0结尾//char arr1[] "xxx";//error 目标空间不够大//const arr1[]"xxxxxxxxxx";//error 目标空间不可变char arr2[] "hello";strcpy(arr1, arr2);}
6该函数返回的是目标字符串strDestination的起始地址
7自定义的strcpy():
#include #include char* mystrcpy(char* dest, const char* src){assert(src char* ret dest;while (*dest *src){;}return ret;}int main(){char arr1[] "abcdef";char arr2[10] { 0 };printf("%s\n", MyStrcpy(arr2, arr1));return 0;}
3、字符串的连接函数strcat():
char *strcat( char *strDestination, const char *strSource );
strcat(s1,s2);参数s1必须是字符型基地址参数s2可以是字符数组名或字符串常量
1引用头文件
2源字符串必须以 \0 结束如果没有\0你不知道在哪里开始连接
3目标空间必须有足够的大能容纳下源字符串的内容
4目标空间必须可修改
5该函数返回的是目标字符串strDestination的起始地址
6自定义的strcat():
#include #include char* mystrcat(char* dest, const char* src){assert(dest, src);char* ret dest;//1.找到目标字符串的末尾while (*dest ! \0){dest;}//2.追加源字符串直到\0while (*dest *src){;}return ret;}int main(){char arr1[20] "abc";char arr2[] { d,e,f,\0};printf("%s\n", mystrcat(arr1, arr2));return 0;}
4、字符串的比较函数strcmp():
int strcmp( const char *string1, const char *string2 );
strcmp(s1,s2);参数s1,s2,可以字符数组名或字符串常量
1引用头文件
2该函数比较的是字符串的内容不是长度。该函数的返回值表示string1和string2的关系
该函数的比较规则是:从两个字符串的首字符串开始依次比较相对应的字符比较字符的ASCII码值直到出现不同的字符或遇到\0为止。如果所有的字符都相同则返回0否则以第一个不相同字符的比较结果为准返回两个字符的差即第一个字符-第二个字符的差。
3自定义的strcmp():
#include #include int mystrcmp(const char* s1, const char* s2){assert(s1 while(*s1 *s2){if(*s1 \0)return 0;else{s1;s2;}}return *s1 - *s2;} int main(){char arr1[] "abcd";char arr2[] "abc";int ret;ret mystrcmp(arr1,arr2);if(ret 0){printf("arr1 arr2");}else if(ret > 0){printf("arr1 > arr2");}else{printf("arr1 }
其实strcmp,strcpy,strcat:长度不受限制的字符串函数(不够安全 但是strncpy,strncat,strncmp:长度受限制的字符串函数(相对安全)
5、字符串的复制函数strncpy():
char *strncpy( char *strDest, const char *strSource, size_t count );
1引用头文件
2strcpy()的问题在于它不能检查目标空间能否容纳源字符串的副本strncpy()的第三个参数指明可以拷贝的最大字符数。如果源字符串的长度小于num则拷贝完源字符串之后在目标的后边追加0直到num个。该函数返回目标字符串strDest的起始地址。
3自定义的strncpy():
#include #include #include char* mystrncpy(char* dest, const char* src, int n){assert(dest char* ret dest;int offset 0;if (strlen(src)
6、字符串的连接函数strncat():
char *strncat( char *strDest, const char *strSource, size_t count );
1引用头文件
2strcat()无法检查参数1能否容得下参数2如果分配给第一个数组的空间不够大多出来的字符溢出到相邻存储单元就会出现问题。strncat()的第三个参数指定了最大添加字符数。该函数返回目标字符串strDest的起始地址。
3自定义的strncat():
#include #include #include char* myStrncat(char* dest, const char* src, int n){char* ret dest;assert(dest while (*dest ! \0){dest;}if (strlen(src) int strncmp( const char *string1, const char *string2, size_t count ); 1引用头文件 2与strcmp()不同的是该函数比较到出现另个字符不一样或者一个字符串结束或者count个字符全部比较完 char *strstr( const char *string, const char *strCharSet ); 1引用头文件。 2在string中搜索strCharSet。该函数返回一个指针指向字符串中第一个出现的strCharSet如果strCharSet未出现在字符串中则返回NULL。如果strCharSet指向长度为零的字符串则函数返回字符串。 3自定义的strstr(): #include #include char* mystrstr(const char* str1, const char* str2)//只是查找不会修改内容{assert(str1 char* s1;char* s2;char* cp str1;if (*str2 \0){return str1;}while (*cp){s1 cp;s2 str2;while (*s1 *s2 \0 \0){s1;s2;}if (*s2 \0){return cp;}cp;}return NULL;}int main(){char arr1[] "i am a good student,a smart student";char arr2[] "student";char* ret mystrstr(arr1, arr2);//查找arr1中arr2第一次出现的位置if (ret NULL){printf("找不到\n");}else{printf("%s\n", ret);}return 0;} char *strtok( char *str, const char *sep ); 1引用头文件 2sep参数是个字符串定义了用作分隔符的字符集合 #include #include int main(){char arr1[] "zpwbitedu.tech";char arr2[100] { 0 };//临时数据char sep[] ".";//char* pch;char* ret NULL;strcpy(arr2, arr1);//将数据拷贝一份处理arr数组的内容//pch strtok(arr2, sep);//while(pch!NULL)//{// printf("%s\n",pch);// strtok(NULL, sep);//}//其实我们第一次传的是非空指针后面传的全部是空指针利用这个规律我们可以巧妙的利用for循环for (ret strtok(arr2, sep); ret ! NULL;ret strtok(NULL,sep)){printf("%s\n", ret);}return 0;} char *strerror( int errnum ); 1引用头文件和 2该函数的作用是返回错误码所对应的错误信息C语言库函数调用失败的时候会把错误码存储到errno变量中。 #include #include #include int main(){FILE* pf fopen("test.txt","r");if(pf NULL){printf("%s\n",strerror(errno));}else{printf("打开文件成功\n");}return 0;} 编译并运行该代码输出如下 因为我是以读的形式打开文件的并且我的电脑中没有这个文件所以会打开文件失败。 1引用头文件或 2该函数的功能打印的功能strerror()的功能 #include int main(){FILE* pf fopen("test.txt","r");if(pf NULL){perror("测试");}else{printf("打开文件成功\n");}return 0;} 编译并运行该代码输出如下 该表所列函数在头文件中说明。 十六进制数字包括所有十进制数字小写字母a-f大写字母A-F 8、字符串查找函数strstr():
9、切分字符串函数strtok():
10、strerror()
11、perror():
五、字符函数