文章目录
字符串的输入与输入
1.字符串初始化
1⃣️:用足够的空间的数组存储字符串:
const char word[15] ="Hello world ";
这里用const,表示该字符串不会被更改。
注意⚠️:在指定数组大小时,要确保数组的元素个数至少比字符长度多1,因为要容纳" \0 "。所有未被使用的元素都被自动初始化为\0。
2⃣️:省略数组初始化声明中的大小
const char word[] ="Hello world!This is my first program! ";
让编译器计算数组大小只能用在初始化数组时,如果创建一个数组,稍后在填充,就必须在声明时指定大小。
2.数组与指针
字符数组名与其他数组名一样,是该数组首元素的地址。
char word[10] = "Hello!";
以下表达式都为真
word == &word[0] //字符数组名为是该数组首元素的地址
*word == 'H' //数组首元素的地址解引用为首元素
*(word +1 ) == car[1] == 'H' // 数组首元素的地址+1后解引用为数组第二个元素
1⃣️:指针创建字符串
const char *pt1 = " Something is poiting at me.";
2⃣️:数组与指针的区别
- 初始化数组把静态存储区的字符串拷贝到数组中,而初始化指针只把字符串的地址拷贝给指针。
- 数组名为常量,指针名为变量
例:
char heart[] = "I love you";
const char *head = "I love you"; //为什么使用const,在下文‘使用指针的优缺点’中说明原因
数组名heart是常量,指针名head为变量
- heart是地址常量,不能更改heart,如果更改了heart就意味着改变了数组的存储位置(地址)。可以进行类似heart+1这样的操作,标示数组的下一个元素。但不允许++heart,递增运算只用于变量前。
- head为变量,该变量最初指向该字符串的首字符,但是他的值可以改变。可以递增运算。
- char *head是一个指针,根本没分配内存,他指向的 “I love you” 是只读的,不能改变,在下面给他赋值肯定是错的。而char heart[]是一个数组,已经分配内存,是将 "I love you"复制到该内存里面,这个内存是可读写的 。
- 指针是不分配内存的,它指向的是系统的只读的内存,而数组是分配内存的,就是将系统的只读的内存里面的值复制到它的内存里面,因此可读写
3⃣️:使用指针的优缺点
-
缺点:
-双引号括起来的字符串是字符串字面量(string literal),是静态对象,因此从语义上来说,指针指向它之后字符串内容自然不可修改,推荐使用const修饰。 -
优点:
const char *pt[5] , char arrays[5][8]
pt数组是一个内含5个指针的数组,占用40字节。arrays是一个内含5个数组的数组,每个数组内含40个char类型值,占用200字节。所以指针效率高。
3.scanf()与printf()
#include <stdio.h>
int main(void){
char word[8];
scanf("%s",word);
printf("%s##\n",word);
}
定义一个char类型的长度为8的字符串,然后接收输入并打印输出,用##可以清晰的看出字符串结尾的边界。
解析:
当我们输入hello world!后回车,发现控制台只有输出了hello##,这说明scanf读一个单词时到空格就结束。
为了再次证明整个原理,下面我们使用两次scanf读入输入的字符然后打印。
#include <stdio.h>
int main(void){
char word[8];
char word2[8];
scanf("%s",word);
scanf("%s",word2);
printf("%s##%s##\n",word,word2);
}
我们可以从下图中看出,第一次scanf发现hello后面有空格就读取完毕,并打印时添加了##。第二次scanf继续读取,发现world!后面有回车也同样读取完毕,并在打印时添加了##。
所以,scanf读取一个单词(到空格,tab或者回车为止),但这也意味着scanf不安全,因为不知道要读入内容的长度。
但是我们可以通过以下的方法,来控制scanf读取的长度。
#include <stdio.h>
int main(void){
char word[8];
char word2[8];
scanf("%7s",word);
scanf("%7s",word2);
printf("%s##%s##\n",word,word2);
}
我们在scanf中的%后面加入一个具体的数字,表示scanf最多读取的长度,在这里我们限制scanf最多读取7个字符。注意:这里是最多读取的长度,如果停止读取时字符串长度不满足,则直接取字符串。
如下图所示,我们第一行输入123,不满足7个字符串长度,prinf打印出来的就只有123.
第二行输入12345678超过7个,则scanf截断第8个字符串,prinf只打印出来1234567.
这就相当于,scanf不是以回车,空格或tab来读取,而是以你所需要的字符串长度来读取,从而实现了安全输入。
4.gets()与puts()
- gets()函数简单易用,它读取整行输入,直到遇到换行符,然后丢弃换行符,储存其他字符,并在这些字符末尾添加一个空字符使其成为一个C字符串。
- puts()函数,只需把字符串的地址作为参数传递给他即可,注意puts函数会在字符串后面加上换行符。
但是gets()无法检查数组是否能装得下输入行,gets函数并不知道数组中有多少元素,如果输入的字符串过长,会导致缓冲区溢出。
因此gets函数也是不安全的,不建议使用gets()。
5.fgets()与fputs()
- fgets()函数的第二个参数指明了读入字符的最大数量。如果该参数为n,那么fgets函数将读入n-1个字符。如果fgets()函数读到一个换行符,会把它储存在字符串中。这点与gets不同,gets会丢弃换行符。fgets()函数的第三个参数指明要读入的文件。如果读入从键盘输入的数据,则以stdin作为参数。
- fputs()函数的第二个参数指明他要写入的文件。如果要在计算机显示器上打印,则使用stdout作为参数。与puts()函数不同,fputs()函数不会在待输出字符串末尾添加一个换行符。
例:
#include <stdio.h>
#define LEN 14
int main(void){
char words[LEN];
puts("Enter a String");
fgets(words,LEN, stdin);
puts(words); //puts()函数会添加换行符\n
fputs(words, stdout);
return 0;
}
输入apple后,apple\n\0 被存储在数组中。
我们看到控制台输出的内容发现输出的两个apple之间有一行空白,因为puts()函数会添加换行符\n。
1⃣️fgets()优缺点:
fgets()储存换行符有好有坏
- 缺点是你可能并不想把换行符储存在字符串中,这样的换行符会带来一些麻烦。
- 优点是对于储存的字符串而言,检查末尾是否有换行符可以判读是否读取了一整行。如果不是一整行,要处理好一行中剩下的字符。
2⃣️fgets()返回值:
读取成功,返回读取到的字符串,即string;失败或读到文件结尾返回NULL。
下面的程序验证读到文件结尾返回NULL。读入并显示用户输入的内容,直到fgets()读到文件结尾或空行(即,首字符为换行符)。
3⃣️fgets()操作实例:
#include <stdio.h>
#define LEN 10
int main(void){
char words[LEN];
puts("Enter Strings (empty line to quit): ");
while(fgets(words, LEN, stdin) != NULL && words[0] != '\n'){
fputs(words, stdout);
}
puts("Done!");
return 0;
}
LEN设置的为10,所以fgets()一次读取9个字符(剩余一个字符留给’\0’),第一次读取到" I’m Kevin ",并存储为I’m Kevin\0,接着fputs()打印出来,并且没有换行。然后while进入下一轮迭代,fgets()继续读取,第二次读取到“ ,from Chi ",并存储为,from Chi\0,接着fputs()打印出来,并且没有换行。直到读取完所有字符为止。最后一次输入时,直接键入回车,所以跳出while循环,程序结束并打印Done!
3⃣️fgets()操作进阶:
如果说我们想第一次的输入 “I’m Kevin,from China.” 只读取前9个字符,然后丢弃掉其他的字符,第二次输入也是如此,那该怎么办???
换句话说该想法就是:按照设定的大小读取输入行,并删除储存在字符串中的换行符,如果没有换行符,则丢弃数组装不下的字符。
再拆分成小的问题则就是以下的两个问题:
- 那如果不希望把换行符储存在字符串中,如何处理掉换行符呢??
我们可以在已储存的字符串中查找换行符,并将其替换为空字符:
while(words[i] != 'n' ) {
i++;
}
words[i] = '\0\;
- 如果仍然有字符串留在输入行怎么办??
丢弃掉其余的字符串即可。
详细原理可以查看主页文章《C语言 getchar()原理及易错点解析》
while(getchar() != '\n')
continue;
我们把这两个问题整合到上面的代码中:
#include <stdio.h>
#define LEN 10
int main(void){
char words[LEN];
puts("Enter Strings (empty line to quit): ");
while(fgets(words, LEN, stdin) != NULL && words[0] != '\n'){
int i=0;
while(words[i] != '\n' && words[i] != '\0'){
i++;
}
if(words[i] == '\n'){
words[i] = '\0';
}else{
while(getchar() != '\n')
continue;
}
puts(words);
}
puts("Done!");
return 0;
}
while(words[i] != '\n' && words[i] != '\0'){
i++;
}
这段代码说的是,遍历字符串,直至遇到换行符或者空字符。如果先遇到换行符,下面的if语句将其换成空字符;如何先遇到空字符,else就将输入行舍弃。
注意⚠️:空字符与空格不一样!!!!空字符是’ \0 ',ASCII码为0;空格的ASCII为32. 下面程序可以验证:
#include <stdio.h>
void main()
{
char a=' ';
char b='\0';
printf ("%d\n",a);
printf ("%d\n",b);
}
Output:
32
0