字符串输入输出
char *t = "title";
char *s;
s = t;
上述字符串赋值并没有产生新的字符串,只是让指针s指向了t所指的字符串,即对s的操作等同于对t做。 制造一个新的字符串,让s指向新的字符串,需要用到字符串函数。
对于字符串类型,使用printf和scanf做输入输出时使用%s。
#include <stdio.h>
int main(void)
{
char word[8]; //定义一个8个字符的数组
char word2[8];
scanf("%s",word); //向数组中读入一些内容
scanf("%s",word2);
printf("%s##%s##\n",word,word2); //加入#方便看清输入内容的边界
return 0;
}
Hello world!
Hello##world!##
--------------------------------
Process exited after 12.3 seconds with return value 0
如上述代码中,第一个%s读到的是Hello不带空格,第二个%s读到了world不带空格。 空格是用来分隔两个单词的,是分隔符。 ::: tip scanf读入一个单词,和是否为一个完整的真的英文单词无关,是当scanf读到空格、tab、回车为止。 但是scanf是不安全的,原因是不知道读入内容的长度。 :::
字符串的安全输入
上述代码中的第7行和第8行两句输入代码没有给出输入内容长度限制,可能会超出数组容量,导致程序崩溃(涉及到变量在内存中的排列问题)。 可以通过修改代码,在%和s中间加上数组最大可容纳字符数。如下:
scanf("%7s",word);
scanf("%7s",word2);
::: warning %和s中间数字表示最多允许读入的字符的数量,这个数字应该比数组的大小小1。 上述代码中,数组word中有8个元素,由于数组中有结尾的\0存在,占用一个位置,数组word只能再写入7个以内的元素。 :::
#include <stdio.h>
int main(void)
{
char word[8]; //定义一个8个字符的数组
char word2[8];
scanf("%7s\n",word); //向数组中读入一些内容
scanf("%7s",word2);
printf("%s##%s##\n",word,word2); //加入#方便看清输入内容的边界
return 0;
}
123
123456789
123##1234567##
--------------------------------
Process exited after 7.923 seconds with return value 0
123456789
1234567##89##
--------------------------------
Process exited after 4.054 seconds with return value 0
123456789abcdefg
1234567##89abcde##
--------------------------------
Process exited after 11.41 seconds with return value 0
123 456789abcd
123##456789a##
--------------------------------
Process exited after 6.69 seconds with return value 0
123456789 abcd
1234567##89##
--------------------------------
Process exited after 7.593 seconds with return value 0
::: tip 在采用安全输入的写法后,一个scanf只能读取最多7个的字符,如果有超出,编译器会自动分隔,将多余的当作下一个scanf的输入内容,这样使用scanf将数组填满后,多余的字符将被丢弃,在读取7个字符的限制下,空格分隔也有效。 :::
常见错误
误认为char是字符串类型,误以为定义了char的变量就是定义了一个字符串的变量,就可以直接使用该变量,如下:
char *s;
scanf("%s",string);
实际上char *s只是定义了一个指针变量,string只是一个未来将会指向某一块内存空间(字符串数组)的指针,在此时并没有被初始化。本地变量没有默认初始值。并不是每次都一定会出错,当指针指向的内存空间里是有害值,程序就会出错。 ::: warning 可能出错的原因: 指针使用错了;指针未初始化;指针没有指向一个实际有效的地址。 :::
空字符串
char buffer[100] = "";
如上,相邻两个紧挨着的双引号表示空字符串。这是有效的字符串,但是数组中第一个元素就是0:buffer[0] == ‘\0’。
char buffer[] = "";
如上编译器会自动生成一个长度只有1的字符串。该字符串的buffer[0]是有效的,值为0,buffer[1]不存在,buffer中无法存放任何字符串。
字符串函数
C语言提供函数帮助处理字符串,如下是C语言标准库中的函数:
#include<string.h>
strlen:字符串长度
strcmp:
strcpy:
strcat:
strchr:
strstr:
所有的string函数都是以str开头的。
strlen: len表示length。strlen函数会输出字符串的长度。
size_t strlen(const char *s);
上述代码的作用是返回s的字符串长度(不包括结尾的0)。返回值的过程中不修改字符串,在类型前标注const。 ::: tip 作为参数,数组的形式和指针的形式是一样的,因为数组传进去就是指针,所以上述代码中就以*s的指针形式来表达。类型char前面的const表示函数不修改传进去的数组。 :::
#include <stdio.h>
#include <string.h>
int main(int argc,char const *argv[])
{
char line[] = "Hello";
printf("strlen = %lu\n",strlen(line));
printf("sizeof = %lu\n",sizeof(line));
return 0;
}
strlen = 5
sizeof = 6
--------------------------------
Process exited after 0.02057 seconds with return value 0
上述程序返回的strlen的值为5,原因是数组line中有Hello共5个字符,sizeof返回值为6,原因是数组line占用的空间为字符Hello加上结尾的\0。
strcmp: 用来比较两个字符串。返回值的过程中不修改字符串,在类型前标注const。
int strcmp(char *s1,const char *s2);
比较的结果不仅说明两个字符串是否相等,还可以比较字符串的大小。
#include <stdio.h>
#include <string.h>
int main(int argc,char const *argv[])
{
char s1[] = "abc";
char s2[] = "abc";
printf("strcmp = %d\n",strcmp(s1,s2));
return 0;
}
strcmp = 0
--------------------------------
Process exited after 0.01687 seconds with return value 0
上述程序输出结果为0,表示字符串s1和s2相等。 ::: warning 数组形式的字符串做比较不可以用“==”。 两个数组一定不会是相同的地址,“==”表示数组的地址相同。 :::
#include <stdio.h>
#include <string.h>
int main(int argc,char const *argv[])
{
char s1[] = "abc";
char s2[] = "bbc";
printf("strcmp = %d\n",strcmp(s1,s2));
return 0;
}
strcmp = -1
--------------------------------
Process exited after 0.01426 seconds with return value 0
上述程序s2的值改为bbc,返回的strlen值为-1,说明s1<s2(a<b,字母表中a排在b之前)。
#include <stdio.h>
#include <string.h>
int main(int argc,char const *argv[])
{
char s1[] = "abc";
char s2[] = "Abc";
printf("strcmp = %d\n",strcmp(s1,s2));
printf("%d\n",'a'-'A');
return 0;
}
strcmp = 32
32
上述代码可知,当两个字符串不相等时,strcmp返回的值就是两个字符串的差值。
#include <stdio.h>
#include <string.h>
int main(int argc,char const *argv[])
{
char s1[] = "abc";
char s2[] = "abc ";
printf("strcmp = %d\n",strcmp(s1,s2));
return 0;
}
strcmp = -32
上述程序中字符串s2的值为“abc ”,末尾多了一个括号,strcmp的返回值为-32。 ::: tip 在s1和s2比较时,s1[0]和s2[0]进行比较,s1[1]和s2[1]进行比较,以此向下进行比较。 s1[4]和s2[4]的比较中,s1[4]="\0",s2[4]=" "。 "\0" - " " = -32("\0"的值为0," "的ASCII码的值为32,即相减结果为-32。) 比较过程如下: ::: 当两个数组长度一致,比较到最后一个元素s1[n] = s2[n] == '\0'时,比较结束。
strcpy: 作用是把第二个参数中表达的字符串拷贝到第一个参数表达的空间中。
char *strcpy(char *restrict dst,const char *restrict src);
如下图,无论dst中是否有内容,都会将src中的每一个元素拷贝到dst中:
strcat: 作用是做连接。如下是将s2拷贝到s1的后面,连接成一个长的字符串。
char * strcat(char *restrict s1,const char *restrict s2);
假设s1为“Hello\0”,s2为“world\0”,s1的“\0”后仍有一定空间,strcat的作用是将s2拷贝到s1后面,使两个字符串连成一个长字符串,新的长字符串结尾仍有一个“\0”。 ::: warning 拷贝的目标位置是s1[strlen(s1)],即s1数组中“\0”所在的位置, s1[strlen(s1)] = s2[0]。 :::
::: danger
函数strcpy和strcat都存在安全问题,即要拷贝或连接的位置可能没有足够的空间。在做拷贝和连接时,代码中不会提示目标位置的空间大小。 建议尽量不使用strcpy和strcat。 ::: 安全版本如下:
char *strncpy(char *restrict dst,const char *restrict src,size_t n);
char *strncat(char *restrict s1,const char *restrict s2,size_t n);
上述代码中,strcpy和strcat变为安全版本strncpy和strncat,参数表中也多了n。 对于strcpy,参数表中的n表示最多可以拷贝进去多少个字符,多出来的丢弃。 对于strcat,参数表中的n表示最多可以连接上去多少个字符,多出来的丢弃。
strcmp也有一个新的版本strncmp。
int strncmp(const char *s1,const char *s2,size_t n);
上述代码中strncmp的作用不是为了安全,是为了判断s1和s2的前n个字符是不是一致。
在字符串中寻找字符:
char *strchr(const char *s,int c);
char *strrchr(const char *s,int c);
上述代码第一行strchr表示在字符串const char s中寻找字符c*从左向右第一次出现的位置,返回指针。 上述代码第二行strrchr表示在字符串const char *s中寻找字符c从右向左**第一次出现的位置,返回指针。 函数返回NULL表示没有找到。