C++语言篇 字符数组与字符串输入

一、在字符数组输入中,常用到cin , scanf() , gets() , cin.getline()和cin.get()函数。

注意:gets() , cin.getline()和cin.get()不能用在string类型中

cin>>通常只能读取一个单词。cin.getline()和cin.get()可以读取固定长度的字符串,含空格等符号。

1、使用cin函数(不接受空格,不接受回车,空格和回车都结束输入)

https://blog.csdn.net/zhanghaotian2011/article/details/8868577

由于cin通过空格、制表符、换行符来界定字符串的。故cin在获取字符时只读取一个单词长度,不能读入空格。


例如:读取姓名

#include <bits/stdc++.h> 
using namespace std;
int main()
{ const int size=20;
  char name[size];
  char add[size];
  cout<<"enter name:"<<endl;
  cin>>name;
  cout<<"enter address:"<<endl;
  cin>>add;
  cout<<"your name is "<<name<<" and your address is "<<add<<endl;
  return 0;
}

输入:HSING   HSU

运行结果:

enter name:
HSING HSU
enter address:
WU HAN
your name is HSING and your address is HSU

该运行结果不是用户所需的结果。故需要用下面的表示方法。

 

2、scanf()函数详解

除了在定义字符数组时初始化外,还可以通过scanf从键盘输入字符串。下面写一个程序:

# include <bits/stdc++.h>
using namespace std;
int main()
{  char str[10]; //str是string的缩写, 即字符串
   printf("请输入字符串:");
   scanf("%s", str); /*输入参数是已经定义好的“字符数组名”, 不用加&, 因为在C语言中数组名就代表该
                       数组的起始地址*/
   printf("输出结果:%s\n", str);
   return 0;
}

输出结果是:
请输入字符串:爱你一生一世
输出结果:爱你一生一世

用 scanf 给字符数组赋值不同于对数值型数组赋值。给数值型数组赋值时只能用 for 循环一个一个地赋值,不能整体赋值。而给字符数组赋值时可以直接赋值,不需要使用循环。此外从键盘输入后,系统会自动在最后添加结束标志符 '\0'。但是用 scanf 输入字符串时有一个地方需要注意:如果输入的字符串中带空格,比如“i love you”,那么就会有一个问题。我们将上面程序运行时输入的字符串改一下:

请输入字符串:i love you
输出结果:i

我们看到,输入的是“i love you”,而输出的只有“i”。原因是系统将空格作为输入字符串之间的分隔符。也就是说,只要一“敲”空格,系统就认为当前的字符串已经结束,接下来输入的是下一个字符串,所以只会将空格之前的字符串存储到定义好的字符数组中。

那么这种情况该怎么办?那么就以空格为分隔符,数数有多少个字符串,有多少个字符串就定义多少个字符数组。比如“i love you”有两个空格,表示有三个字符串,那么就定义三个字符数组:

# include <bits/stdc++.h>
using namespace std;
int main()
{  char str1[10], str2[10], str3[10];
   printf("请输入字符串:");
   scanf("%s%s%s", str1, str2, str3);
   printf("输出结果:%s %s %s\n", str1, str2, str3); //%s间要加空格
   return 0;
}

输出结果是:
请输入字符串:i love you
输出结果:i love you

需要注意的是,用 scanf 输入时,不管输入什么,最后“敲”的回车都会被留在缓冲区,这里也不例外。输入字符串时最后“敲”的回车也会被留在缓冲区,如果紧接着要给一个字符变量赋值的话,那么还没等你输入系统就自动退出来了。因为系统自动将回车产生的字符 '\n' 赋给该字符变量了,所以此时对字符变量赋值前要首先清空缓冲区。

 

3、gets函数详解

使用 gets() 函数。该函数的原型为:

# include <stdio.h>
char *gets(char *str);gets() 函数的功能是从输入缓冲区中读取一行字符串存储到字符指针变量 str 所指向的内存空间。

一行文本由一串字符组成,以一个换行符('\n')结尾。在返回之前,gets函数丢弃换行符('\n'),取而代之的是以'\0'结尾。

# include <stdio.h>

int main()
{  char str[20] = "\0"; //字符数组初始化\0
   printf("请输入字符串:");
   gets(str);
   printf("%s\n", str);
   return 0;
}

输出结果是:
请输入字符串:i love you
i love you

可见,gets() 函数不仅比 scanf 简洁,而且,就算输入的字符串中有空格也可以直接输入,不用像 scanf 那样要定义多个字符数组。也就是说:

# include <stdio.h>
int main()
{  char str[30];
   char *string = str; //一定要先将指针变量初始化
   printf("请输入字符串:");
   gets(string); //也可以写成gets(str);
   printf("%s\n", string); //输出参数是已经定义好的“指针变量名”
   return 0;
}

输出结果是:
请输入字符串:Hi i...like you
Hi i...like you
 

gets()和scanf()函数的区别

scanf( )函数和gets( )函数都可用于输入字符串,但在功能上有区别。若想从键盘上输入字符串"hi hello",则应该使用gets()函数。

gets可以接收空格;而scanf遇到空格、回车和Tab键都会认为输入结束,所以它不能接收空格。

char string[15];

gets(string); /*遇到回车认为输入结束*/

scanf("%s",string); /*遇到空格认为输入结束*/

1.不同点:

scanf()不能接受空格、制表符Tab、回车等;

而gets()能够接受空格、制表符Tab和回车等;

scanf()可以读取所有类型的变量

gets()只用作读取字符数组,用回车结束输入 

scanf ():当遇到回车,空格和tab键会自动在字符串后面添加'\0',回车,空格和tab键仍会留在输入的缓冲区中。

gets():可接受回车键之前输入的所有字符,并用'\n'替代 '\0'.回车键不会留在输入缓冲区中

2.相同点:

字符串接受结束后自动加'\0'。

例一、

#include <stdio.h>
main()
{  char ch1[10],ch2[10];
   scanf("%s",ch1);
   gets(ch2);
}

输入:

asd fg

asd fg

则输出:ch1="asd\0",ch2="asd fg\0"。

例二、

#include <stdio.h>
main()
{  char ch1[10],ch2[10],c1,c2;
   scanf("%s",ch1);
   c1=getchar();
   gets(ch2);
   c2=getchar();
}

输入:

asdfg

asdfg

则:ch1="asdfg\0",c1='\n',ch2="asdfg\0",c2需输入。

注意:关于使用 gets() 函数需要注意:使用 gets() 时,系统会将最后“敲”的换行符从缓冲区中取出来,然后丢弃,所以缓冲区中不会遗留换行符。这就意味着,如果前面使用过 gets(),而后面又要从键盘给字符变量赋值的话就不需要吸收回车清空缓冲区了,因为缓冲区的回车已经被 gets() 取出来扔掉了。下面写一个程序验证一下:

# include <stdio.h>
int main()
{  char str[30];
   char ch;
   printf("请输入字符串:");
   gets(str);
   printf("%s\n", str);
   scanf("%c", &ch);
   printf("ch = %c\n", ch);
   return 0;
}

输出结果是:
请输入字符串:i love you
i love you
Y
ch = Y

我们看到,没有清空缓冲区照样可以输入'Y',因为 gets() 已经将缓冲区中的回车取出来丢掉了。如果前面使用的不是 gets() 而是 scanf,那么通过键盘给 ch 赋值前就必须先使用 getchar() 清空缓冲区。

# include <stdio.h>
int main()
{  char str[30];
   char ch;
   printf("请输入字符串:");
   scanf("%s",&str);
   printf("%s\n", str);
   scanf("%c", &ch);
   printf("ch = %c\n", ch);
   return 0;
}

输出结果是:
请输入字符串:i love you
i love you
ch = 

4、fgets()函数

gets()从标准输入设备读字符串函数,可以无限读取,不会判断上限,以回车结束读取。所以如果输入的字符串超过100个,它也不会做检测,此时就会发生溢出。(对1988 年的“互联网蠕虫”,病毒的实现来说, gets 函数的功劳不可小视。)

所以程序员应该确保 buffer 的空间足够大,以便在执行读操作时不发生溢出。也就是说,gets 函数并不检查缓冲区 buffer 的空间大小,事实上它也无法检查缓冲区的空间。

如果函数的调用者提供了一个指向堆栈的指针,并且 gets 函数读入的字符数量超过了缓冲区的空间(即发生溢出),gets 函数会将多出来的字符继续写入堆栈中,这样就覆盖了堆栈中原来的内容,破坏一个或多个不相关变量的值。如下面的示例代码所示:

# include <bits/stdc++.h>
using namespace std;
int main()
{ char buffer[11];
  gets(buffer);
  printf("输出: %s\n",buffer);
  return 0;
}

示例代码的运行结果为:
aaa
输出: aaa

根据运行结果,当用户在键盘上输入的字符个数大于缓冲区 buffer 的最大界限时,gets () 函数也不会对其进行任何检查,因此我们可以将恶意代码多出来的数据写入堆栈。由此可见,gets 函数是极其不安全的,可能成为病毒的入口,因为 gets () 函数没有限制输入的字符串长度。所以我们应该使用 fgets ()函数来替换 gets () 函数,实际上这也是大多程序员所推荐的做法。
 

相对于 gets ()函数,fgets () 函数最大的改进就是能够读取指定大小的数据,从而避免 gets ()函数从 stdin 接收字符串而不检查它所复制的缓冲区空间大小导致的缓存溢出问题。当然,fgets 函数主要是为文件 I/O 而设计的(注意,不能用 fgets () 函数读取二进制文件,因为 fgets () 函数会把二进制文件当成文本文件来处理,这势必会产生乱码等不必要的麻烦)。

该函数的第二个参数 bufsize 用来指示最大读入字符数。如果这个参数值为 n,那么 fgets 函数就会读取最多 n-1 个字符或者读完一个换行符为止,在这两者之中,最先满足的那个条件用于结束输入。

与 gets () 函数不同的是如果 fgets () 函数读到换行符,就会把它存储到字符串中,而不是像 gets () 函数那样丢弃它。即给定参数 n,fgets () 函数只能读取 n-1 个字符(包括换行符)。如果有一行超过 n-1 个字符,那么 fgets () 函数将返回一个不完整的行(只读取该行的前 n-1 个字符)。但是,缓冲区总是以 null('\0') 字符结尾,对 fgets 函数的下一次调用会继续读取该行。

也就是说,每次调用时,fgets 函数都会把缓冲区的最后一个字符设为 null('\0'),这意味着最后一个字符不能用来存放需要的数据。所以如果某一行含有 size 个字符(包括换行符),要想把这行读入缓冲区,要把参数 n 设为 size+1,即多留一个位置存储 null('\0')。

最后,它还需要第 3 个参数来说明读取哪个文件。如果是从键盘上读入数据,可以使用 stdin 作为该参数,如下面的代码所示:

# include <bits/stdc++.h>
using namespace std;
int main()
{ char buffer[11];
  fgets(buffer,11,stdin);
  printf("输出: %s\n",buffer);
  return 0;
}

对于上面的示例代码,如果输入的字符串小于或等于 10 个字符,那么程序将完整地输出结果;如果输入的字符串大于 10 个字符,那么程序将截断输入的字符串,最后只输出前 10 个字符。示例代码运行结果为:

aaaaaaaaaaaaaaaa
输出: aaaaaaaaaa
 

fgets() 的原型为:

# include <stdio.h>
char *fgets(char *s, int size, FILE *stream);

fgets() 虽然比 gets() 安全,但安全是要付出代价的,代价就是它的使用比 gets() 要麻烦一点,有三个参数。它的功能是从 stream 流中读取 size 个字符存储到字符指针变量 s 所指向的内存空间。它的返回值是一个指针,指向字符串中第一个字符的地址。

其中:s 代表要保存到的内存空间的首地址,可以是字符数组名,也可以是指向字符数组的字符指针变量名。size 代表的是读取字符串的长度。stream 表示从何种流中读取,可以是标准输入流 stdin,也可以是文件流,即从某个文件中读取,标准输入流就是输入缓冲区。所以如果是从键盘读取数据的话就是从输入缓冲区中读取数据,即从标准输入流 stdin 中读取数据,所以第三个参数为 stdin。

下面写一个程序:

# include <bits/stdc++.h>
using namespace std;
int main()
{  char str[20]; /*定义一个最大长度为19, 末尾是'\0'的字符数组来存储字符串*/
   printf("请输入一个字符串:");
   fgets(str, 7, stdin); /*从输入流stdin即输入缓冲区中读取7个字符到字符数组str中*/
   printf("%s\n", str);
   return 0;
}

输出结果是:
请输入一个字符串:i love you
i love

我们发现输入的是“i love you”,而输出只有“i love”。原因是 fgets() 只指定了读取 7 个字符放到字符数组 str 中。“i love”加上中间的空格和最后的 '\0' 正好是 7 个字符。

“用 fgets() 是不是每次都要去数有多少个字符呢?这样不是很麻烦吗?”   不用数!

fget() 函数中的 size 如果小于字符串的长度,那么字符串将会被截取;如果 size 大于字符串的长度则多余的部分系统会自动用 '\0' 填充。所以假如你定义的字符数组长度为 n,那么 fgets() 中的 size 就指定为 n–1,留一个给 '\0' 就行了。

但是需要注意的是,如果输入的字符串长度没有超过 n–1,那么系统会将最后输入的换行符 '\n' 保存进来,保存的位置是紧跟输入的字符,然后剩余的空间都用 '\0' 填充。所以此时输出该字符串时 printf 中就不需要加换行符 '\n' 了,因为字符串中已经有了。

下面写一个程序看一下:

# include <bits/stdc++.h>
using namespace std;
int main()
{  char str[30];
   char *string = str; //一定要先给指针变量初始化
   printf("请输入字符串:");
   fgets(string, 29, stdin); //size指定为比字符数组元素少一就行了
   printf("%s", string); //printf中不需要添加'\n', 因为字符串中已经有了
   return 0;
}

输出结果是:
请输入字符串:i love studying C语言
i love studying C语言

我们看到,printf 中没有添加换行符 '\n',输出时也自动换行了。

所以 fgets() 和 gets() 一样,最后的回车都会从缓冲区中取出来。只不过 gets() 是取出来丢掉,而 fgets() 是取出来自己留着。但总之缓冲区中是没有回车了!所以与 gets() 一样,在使用 fgets() 的时候,如果后面要从键盘给字符变量赋值,那么同样不需要清空缓冲区。下面写一个程序验证一下。

# include <bits/stdc++.h>
using namespace std;
int main()
{  char str[30];
   char ch;
   printf("请输入字符串:");
   fgets(str, 29, stdin);
   printf("%s", str); //后面不要加'\n'
   scanf("%c", &ch);
   printf("ch = %c\n", ch);
   return 0;
}

输出结果是:
请输入字符串:i love you
i love you
Y
ch = Y


5、使用cin.getline()函数。(接受空格,不接受回车,回车结束输入)


cin.getline(name,size,'c')函数,第一个参数表示数组名,传递的是字符串首地址,第二个参数是字符长度,包括最后一个空字符的长度,因此只能读取size-1个字符。第三个参数表示遇到字符‘c’则中止输入。由于cin.getline(name,size)返回的是一个cin对象,因此可以将两个成员函数拼接起来。使用cin.getline(name,size).getline(add,size);

例一

#include <iostream>
using namespace std;
int main()
{
const int size=20;
char name[size];
char add[size];
cout<<"enter name:"<<endl;
cin.getline(name,size);
cout<<"enter address:"<<endl;
cin.getline(add,size);
cout<<"your name is "<<name<<" and your address is "<<add<<endl;
}

运行结果:

enter name:
HSING HSU
enter address:
WU HAN
your name is HSING HSU and your address is WU HAN

例二

#include <iostream>
using namespace std;
int main()
{
const int size=20;
char name[size];
char add[size];
cout<<"enter name and address:"<<endl;
cin.getline(name,size).getline(add,size);
cout<<"your name is "<<name<<" and your address is "<<add<<endl;
return 0;
}

运行结果:

enter name and address:
HISNG HSU
WU HAN
your name is HISNG HSU and your address is WU HAN
Press any key to continue


6、使用cin.get()函数(接受空格,不接受回车,回车结束输入)

1、cin.get()函数与cin.getline()函数类似。但cin.get(name,size);读取到行尾后丢弃换行符,因此读取一次后换行符任留在输入队列中。

#include <iostream>
using namespace std;
int main()
{const int size=20;
 char name[size];
 char add[size];
 cout<<"enter name:"<<endl;
 cin.get(name,size);
 cout<<"enter address:"<<endl;
 cin.get(add,size);
 cout<<"your name is "<<name<<" and your address is "<<add<<endl;
}

运行结果:

enter name:
HSING HSU
enter address:
your name is HSING HSU and your address is

运行结果不是用户所需要的结果。


注:第二次直接读取的是换行符 ,也就是cin.get(add,size)接收的是上面HSING HSU后的回车;

2、程序修改:

在cin.get(name,size);后面加一条语句:  cin.get();该函数可以读取一个字符。将换行符读入。
 

#include <iostream>
using namespace std;
int main()
{ const int size=20;
  char name[size];
  char add[size];
  cout<<"enter name:"<<endl;
  cin.get(name,size);
  cin.get();
  cout<<"enter address:"<<endl;
  cin.get(add,size);
  cout<<"your name is "<<name<<" and your address is "<<add<<endl;
  return 0;
}

运行结果:

enter name:
HSING HSU
enter address:
WU HAN
your name is HSING HSU and your address is WU HAN

运行正确。

3、也可以将两个成员函数拼接起来cin.get(name,size).get() 。使用cin.get()接受后面留下的换行符。
 

#include <iostream>
using namespace std;
int main()
{
const int size=20;
char name[size];
char add[size];
cout<<"enter name"<<endl;
cin.get(name,size).get();
cout<<"enter address:"<<endl;
cin.get(add,size);
cout<<"your name is "<<name<<" and your address is "<<add<<endl;
return 0;
}

运行结果:

enter name:
HSING HSU
enter address:
WU HAN
your name is HSING HSU and your address is WU HAN

运行正确。

cin.getline()和cin.get()区别

分为三种情况来看:
1、输入的字符串不超过 size 限定大小
cin.get(str,size):读取所有字符,遇到'\n'时止,并且将'\n'留在输入缓冲区中,其将被下一个读取输入的操作捕获,影响下面的输入输入处理;
cin.getline(str,Size):读取所有字符,遇到'\n'时止,并且将'\n'直接从输入缓冲区中删除掉,不会影响下面的输入处理。

2、输入的字符数超出  size  限定的大小
cin.get(str,Size):读取Size-1个字符,并将str[Size-1]置为'\0',然后将剩余字符(包括'\n')留在输入缓冲区中,这些字符将被下一个读取输入的操作捕获,影响下面的输入处理
cin.getline(str,Size):读取Size-1个字符,并将str[Size-1]置为'\0',剩余字符(包括'\n')留在输入缓冲区中,随即设置cin实效位(即if(!cin)的判断为真),关闭输入。其后的所有输入都无法得到任何东西当然也无法得到输入缓冲区中剩余的字符串

但用clear()重置cin,其后的输入便可用并会得到遗留在输入缓冲区中的字符。


3、输入一个空行(即直接回车)
cin.get(str,Size):str将得到'\0',并设置cin实效位,关闭输入,但回车依然留在输入缓冲区中,因此如果我们用clear()重置cin,其下一个读取输入的操作将捕获'\n';
cin.getline(str,Size):str将得到'\0',并将'\n'删除掉,不置实效位,不关闭输入。所以对于cin.getline来说空行是合法的输入,且不会影响下面的输入处理。

至于使用那个更好,可能因人习惯不同而不同,仁者见仁智者见智。对于我们编程来说,总希望能有更好的容错性,即便用户输入了不合理的输入,程序也应该能够 提示并能够重新输入或继续正常处理,而因为用户的输入问题而导致程序错误或其后的所有输入都不可用显然不是我们希望的。使用get(str,Size)和 getline(str,Size),都可能碰到设置失效位,关闭输入的情况,故都是需要考虑到相应的防错处理的。

 

 

小知识:\n 与\r的区别

一.知其然

\n是换行,英文是New line

\r是回车,英文是Carriage return

二.知其所以然
 

机械打字机有回车和换行两个键作用分别是:
换行就是把滚筒卷一格,不改变水平位置。   
回车就是把水平位置复位,不卷动滚筒。

Enter = 回车+换行(\r\n)  注:\r\n连用时,不能调换顺序

======

unix换行:\n(0x0A)

MAC回车:\r(0x0D)

WIN回车换行:\r\n(0x0D,0x0A)

======

关于“回车”(carriage return)和“换行”(line feed)这两个概念的来历和区别。
在计算机还没有出现之前,有一种叫做电传打字机(Teletype Model 33)的玩意,每秒钟可以打10个字符。但是它有一个问题,就是打完一行换行的时候,要用去0.2秒,正好可以打两个字符。要是在这0.2秒里面,又有新的字符传过来,那么这个字符将丢失。于是,研制人员想了个办法解决这个问题,就是在每行后面加两个表示结束的字符。一个叫做“回车”,告诉打字机把打印头定位在左边界;另一个叫做“换行”,告诉打字机把纸向下移一行。这就是“换行”和“回车”的来历,从它们的英语名字上也可以看出一二。后来,计算机发明了,这两个概念也就被般到了计算机上。那时,存储器很贵,一些科学家认为在每行结尾加两个字符太浪费了,加一个就可以。

  于是,就出现了分歧。Unix系统里,每行结尾只有“<换行>”,即“\n”;Windows系统里面,每行结尾是“<换行><回车>”,即“\n\r”;Mac系统里,每行结尾是“<回车>”。一个直接后果是,Unix/Mac系统下的文件在Windows里打开的话,所有文字会变成一行;而Windows里的文件在Unix/Mac下打开的话,在每行的结尾可能会多出一个^M符号。

 

  • 25
    点赞
  • 123
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值