C语言风格字符串的概念、定义、输入字符串、输出字符串
字符串: C语言中最有用、最重要的数据类型之一。
字符串:是以\0字符结尾的char类型数组。所以可以把数组和指针知识应用于字符串。
如何在程序定义字符串:
1、字符串字面量
用双引号括起来的内容称为字符串字面量,也叫作字符串常量。双引号中的字符和编译器自动加入末尾的\0字符,都作为字符串储存在内存中。
如果要在字符串内部使用引号,必须要在双引号前面加上一个反斜杠(\)。
字符串字面量被视为const,就不能更改了。
字符串常量属于静态存储类别。这说明如果在函数中使用字符串常量,该字符串只会被储存一次,在整个程序的生命期内存在,即使函数被调用多次。用双引号括起来的内容被视为指向该字符串储存位置的指针。
2、字符串数组和初始化
定义字符串数组时,必须让编译器知道需要多少空间,一种方法是足够空间的数组储存字符串。
指定数组大小的时候,必须确保数组的元素个数至少比字符串长度多1;
省略数组初始化声明中的大小,编译器会自动计算数组的大小;
数组表示法_创建字符串:char words[MAXLENGTH]=“I am a string in an array.”
指针表示法_创建字符串:char * pt1 = “Something is pointing at me.”
3、数组和指针
数组表示法_创建字符串:char words[MAXLENGTH]=“I am a string in an array.”
这种表示法,字符串字面量被存储在静态存储区。数组只有在运行时才会被分配内存。此时,才将字符串拷贝到数组中。此时字符串有两个副本,一个是在静态内存中的字符串字母量,另一个是储存在words数组中的字符串。
指针表示法_创建字符串:char * pt1 = “Something is pointing at me.”
编译器为字符串在静态存储区预留了29个元素的空间。一旦开始运行程序,它就会为指针变量pt1留出一个储存位置,并把字符串的地址储存在指针变量中。
4、数组和指针的区别
char heart[] = “I love Tillie!”; ->数组名heart是常量;
const char *head = “I love Millie!”; ->指针名是head变量;
head =heart ->可以;
heart =head –>不可以,非法构造,赋值运算符左边必须是变量;
5、字符串数组
const char *mytalents[LIM]={
"Adding number swiftly",
"Multiplying accurately",
"Stashing data",
"Following instructions to the letter",
"Understanding the C language"
};
char yourtalents[LIM][SLEN]={
"Walking in a straight line",
"Sleeping","Watching television",
"Mailing letters","Reading email"
};
字符串数组分配内存的使用率低。因为数组中储存着字符串字面量的副本。每个字符串都被储存了两次。
如果要使用数组表示待显示的字符串,建议使用指针数组。指针数组也有缺陷,就是字符串字面量不能更改。如果要改变或者为字符串输入预留空间,不要使用指向字符串字面量的指针。
++++++++++++++++++++++++++++++++++++++++++++++++++++++
字符串的输入:
想把字符串读入程序,首先必须预留储存该字符串的空间。然后用输入函数获取该字符串。
如何分配空间:
要做的第1件事就是分配空间。以储存稍后读入的字符串。意味着必须为字符串分配足够的空间。不能指望计算机在读取字符串时顺便计算它的长度。
char *name;
scanf(“%s”,name);
name是未初始化的指针,name可能指向任何地方。所以可能会擦写掉程序中的数据或代码,从而导致程序异常中止。
最简单的办法:在声明时显式指明数组的大小。
char name[81];
现在name是一个已分配块(81字节)的地址。还有一种办法就是使用C库函数来分配内存。
空字符:
用于标记C字符串末尾的字符;对应的字符ASCII编码是0;
空指针:
有一个值,该值不会与任何有效的地址对应。
本质上讲空字符是整数类型,占1个字节;
空指针是指针类型,是一个地址,占4个字节。
丢弃输入行中余下的字符,是因为,输入行中多出来的字符会被留在缓冲区中。成为下一次读取语句的输入。
1、gets() 函数 这个函数不安全,被摒弃了
scanf()和转换说明%s只能读取一个单词,可是在程序中经常要读取一整行输入,而不仅仅是一个单词。
很久前gets()就用于处理这种情况。gets()函数简单易用,它读取整行输入,直至遇到换行符。
gets()函数有个问题,无法检查数组是否装得下行。数组名会被转换成数组首元素的地址。get()函数只知道数组的开始处,并不知道数组中有多少个元素。
如果输入字符串过长,会导致缓冲区溢出(buffer overflow)。多余的字符超出了指定的目标空间。如果这些多余的字符只是占用了尚未使用的内存,就不会立即出现问题。但是一旦擦写掉程序中的其他数据,就会导致程序异常中止。
C99标准建议不要使用get()函数,甚至被C11标准摒弃了。
2、fgets()函数
函数原型:char *fgets(char *buf, int bufsize, FILE *stream); ---->从指定的文件读取一个字符串
buf 字符型指针,指向存储读入数据的缓冲区的地址;---->存到哪
n 从流中读入n-1个字符;--->读多少个
stream 指向读取的流; --->从哪读
会在读入的最后一个字符后加上串结束标志'\0'
返回值:读取失败或读到文件结尾返回NULL;输入成功时,返回 char 型指针,指向读入的字符串内容,含换行键;
为什么会有换行符问题:因为输入时会敲回车嘛,就产生换行符。函数会把换行符一并存储起来。(读一行字符串的范畴)
fgets()会存储换行符,有好处也有坏处。好处就是:检查末尾有没有换行符就知道是否读取了一整行。坏处就是:你可能并不想把换行符储存在字符串中,这样的换行符会带来一些麻烦。
如何处理掉换行符:在已经储存的字符串中查找换行符,将其替换成空字符
while (words[i] != ‘\n’)
i++;
words[i]=’\0’;
3、gets_s()函数
函数原型: gets_s(words,STLEN);
只从标准输入中读取数据,不需要第3个参数;
如果读到换行符,会丢弃它,而不是储存它;(不存换行符)
如果读到最大STLEN字符数的话,.....很复杂;
没有fgets()函数灵活,易用;不建议用;
4、s_gets()函数
函数实现如下:
1 char * s_gets(char * st, int n) 2 { 3 char * ret_val; 4 int i=0; 5 6 ret_val = fgets(st, n, stdin); //读取成功,返回一个指针,指向输入字符串的首字符; 7 if(ret_val) 8 { 9 while(st[i]!='\n' && st[i]!='\0') 10 i++; 11 if(st[i] =='\n') //fgets会把换行符也吃进来了,fgets会在末尾自动加上\0; 12 st[i]='\0'; 13 else //其实是'\0' 14 while(getchar() != '\n') //会把缓冲区后续的字符都清空 15 continue; 16 } 17 return ret_val; 18 }
特性:1、会吃进换行符,但是会将其替换成\0空字符;2、会把\0后续的缓冲区内容都清空,为的是不干扰下次输入;
5、scanf()函数
函数原型:int
scanf
(
const
char
* restrict format,...);
返回一个整数值:该值等于sanf()成功读取的项数或EOF(文件结尾)。文件结尾不存在于文件中,而仅仅是一种标志,流的状态的标志。
scanf ( "%d %d" ,&a,&b); |
++++++++++++++++++++++++++++++++++++++++++++++++++++++
字符串的输出:
1、puts()函数
自动在字符串末尾加上换行符,\n
函数只用来输出字符串,没有格式控制,里面的参数可以直接是字符串或者是存放字符串的字符数组名。
2、fputs()函数
int fputs(const char *str, FILE *stream); ---->向指定的文件写入一个字符串;
str 这是一个数组;
stream 指向FILE对象的指针,该FILE对象标识了要被写入字符串的流。
3、printf()函数
函数的输出格式很多,可以根据不同格式加转义字符,达到格式化输出。