C语言的那些事

变量的存数类型:

1:静态变量:凡是在代码任何快之外声明的变量总是存储在静态内存内,也就是不属于堆栈的内存。

 对于这类变量。你无法对它们制指定存储类型。

2:存储于堆栈中,称为自动变量。当程序执行到声明自动变量的代码时,自动变量才被创建。

当程序的执行流离开该代码时,这些自动变量将被自行销毁。

3:寄存器变量:关键字register可以用于自动变量的声明,提示它们应该存储于机器的硬件寄存器,而不是

内存中,这类变量称为寄存器变量。

 

 

通过一个指针访问它所指向的地址的过程称为间接访问或者解引用指针。这个用于执行间接的操作符是单目操作符*。

如:d的值是100,当我们对D使用间接访问操作符时,它表示访问内存地址100,并查看那里的值。因此:*d的市里面存储

的内容,

d是一个指向整型的指针,对它进行引用操作符将产生一个整型值。类似,对float *进行间接访问将产生一个float型的值。

 

 

关于野指针和空指针:

下面的代码是很明显的错误:

int *a;

...

*a=12;

这个声明创建了一个名叫a的指针变量,后面那条赋值语句把12存储在a所指向的内存位置。

警告:

但是究竟a指向哪里呢?我们声明了这个变量,但是从来未对其进行初始化,所以我们没有办法预测12这个直讲存储于什么地方。

从这一点看,指针变量和其它变量并没有什么区别。如果变量是静态的。他会被初始化为0.但是如果变量是自动的,它根本不会被初始化。

 

空指针:

#include"stdio.h"

int main()

{

printf("%d\n",NULL);//0

}

标准定义了NULL指针,它作为一个特殊的指针变量,表示不指向任何东西。要是一个指针变量为NULL,你可以给它赋一个零值。为了测试

一个指针变量是否为NULL,你可以将它与零值进行比较。之所以选择零这个值是因为一种源代码约定。就机器内部而言,NULL指针的实际值可能

与此不同。在这种情况下,编译器将负责零值与内部值之间的转换。

  NULL指针的概念是非常有用的,因为它给了你一种用法,表示某个特定的指针目前并为之向任何东西。比如:一个用于在某个数组中查找特定值的函数,

可能返回一个指向查找到的数组元素的指针。如果该数组不包含指定条件的值,函数就返回一个NULL指针,这个技巧允许返回值传达两个不同片段的信息。

首先,有没有找到元素?其次,如果找到,它是哪个元素呢?

尽管这个技巧在C程序中,极为常用。但是他违背了软件工程的原则,用一个单一的值表示两种不同的意思是件很危险的事情,因为将来很容易无法

弄清哪个才是它真正的用意。在大型的程序中,这个问题更加严重,因为你不可能在头脑中对整个设计一览无余,一种更为方便的策略是让函数返回两个独立的值。:

首先是个状态值,用于提示查找是否成功;其次是个指针,当状态值提示查找成功时,它所指向的就是要查找的元素。

  对指针进行解引用操作可以获得它所指向的值。但是从定义上来看,NULL指针并为之向任何东西。因此,对于一个NULL指针进行解引用操作是非法的。

在对指针进行解引用之前,你必须确定它非NULL指针。

警告:如果对一个NULL指针进行间接访问会发生什么情况呢?这个视编译器而定。在有些机器上,它会访问内存位置零,便以其能够确保内存位置零

没有存储任何变量,但机器并未阻碍你访问这个位置,这种行为是非常不幸的,因为程序包含了一个错误,但是机器却隐匿了它的症状,这样就使得这个错误难以查找。

  在其他机器上,对NULL指针进行间接访问将引发一个错误,并终止程序。宣布这个错误比隐藏这个错误要好得多,因为程序员更容易修正它。

 

提示:

如 果所有的指针变量(而不仅仅是位于静态中的指针变量)能够被初始化为NULL,那确实是件幸事,但事实并非如此。不论你的机器对解引用NULL指针这种行 为作何反应,对所有的指针变量进行显示的初始化就是最好的做法。如果你已经知道指针将被初始化为什么类型的地址,就把它初始化为该地址,否则就把他初始化 为NULL。风格良好的程序会在解引用之前对它进行检查。这种初始化策略可以节省大量的调试时间。

 

指针:

*&a=25;

这句代码的含义是:把值25赋值给变量a。 

首先&操作符产生变量a的地址,它一个指针常量(注意:使用这个指针并不需要知道它的实际值),接着,*操作符访问其操作数所标示的地址。在这个表达式中,操作数是a的地址,所以值25就直接存储于a中。

另一大难点:

  假定变量a存储于位置100,那么下面这条语句的作用是:

* 100=25;

 它上去像是把25的值赋值给a,因为a是位置100所存储的变量。但是,这是错的。

这条语句上是非法的。因为字面值100的类型是整型,而间接访问操作只能作用于指针类型表达式。如果你确实想把25存储于位置100,你必须使用强制类型转换。

  *(int *)100=25;

强制类型转换把值100从“整型”转换为”整型的指针”,,这样对它进行间接访问,就是合法的。如果a存储于位置100,那么这个条语句就是把值25存储于a。

//计算一个字符串的长度

#include"stdio.h"

#include"stdlib.h"

strlen(char *string)

{

int length=0;

/*

*依次访问字符串的内容,计数字符数,直到遇见NULL终止符。

*/

while(*string++!='\0')

{

length+=1;

}

return length;

}

2:关于字符串的输入和输出:

C语言中,没有字符串类型,用字符数组处理字符串。

字符数组定义:

char 数组名[常量表达式] [,[常量表达式]];

说明:一维字符数组,用于存储和处理一个字符串,二维字符数组,用于同时存储和处理多个字符串;

因为字符型与整型是通用的,可以用int来定义字符数组,但两者有区别,如:

char c[10];    /* 在内存中占10字节 */

int c[10];     /* 在内存中占40字节 */

输入输出方法:逐个字符输入输出:%c,整个字符串输入输出:%s

一:用scanf()输入字符串,printf()输出字符串:

逐个字符处理:

#include "stdio.h "

void main()

{    char   ch[5];

      int i; 

      for(i=0;i<5;i++)

          scanf(“%c”, &ch[i]);

      for(i=0;i<5;i++)

          printf(“%c”, ch[i]);

}

整个字符串处理:

#include "stdio.h "

void main()

{    char   ch[5];

     scanf(“%s”,ch);

     printf(“%s”, ch);

}

说明:

以字符串为单位处理时,直接使用数组名,无需&; 

输入字符串时,字符个数要小于数组的长度,如输入5个字符,定义的字符数组至少应有6个元素;

输入字符串时,若遇空格或回车,输入结束,并自动在串后加上结束标志’\0’;

输出字符串时,遇到字符串结束标志’\0’,输出结束。

二:用字符串处理函数输入和输出

字符串标准函数的原型在头文件string.h中(使用字符串处理函数输入和输出时要在头文件上加入 #include<string.h>)

字符串输出函数puts

格式:puts(字符数组)

功能:向显示器输出字符串(输出完,自动换行,即用’\n’替换了’\0’)

说明:字符数组必须以‘\0’结束

 

字符串输入函数gets

格式:gets(字符数组)

功能:从键盘输入一以回车结束的字符串放入字符数组中,并自动加‘\0’

说明:输入串长度应小于字符数组维数,字符串中可以包含空格

例如:

#include<stdio.h>

#include<string.h>

void main()

{

char str[10];

int i;

printf("请输入字符串:\n");

gets(str);

printf("输入的字符串为:\n");

puts(str);

}

 

数组:

定义数组的大小必须使用整型常量或者整型常量表达式。C语言中不允许用变量下表形式对数组进行动态定义。例如:下面的数组定义语句:

  Short score[100]; //正确的定义形式

而:

Short score[N];  //不正确的定义方式

是非法的,即使在数组定义的语句前面使用赋值语句给N进行了赋值,或者使用scanf()输入了N值,上面这条语句也是非法的。

 

关于使用scanf输入数据或许你还有好多疑问,下面是一个师哥(zy)对我的疑惑的讲解。

 

Zy:字符串的本质就是个char*。scanf输入字符串,就是往指定的位置,其实就是

 

I:但是ch[5]不是定义了一个存放五个字符的数组么,我输入的超过五个且少于十个程序就没有停止。

下面给你一个案例:

int a=99,b=99;
float c=9999,d=99.99;
scanf("%d %d %f %f",&a,&b,&c,&d);

Printf("%d %d %f %f\n",a,b,c,d);
当输入内容是:1.23 4.5 6.7,知道这四个变量值怎么变化吗?

由于实在不确定它的结果,在VC环境下调试下:

当我从键盘上依次输入:1.23 4.5 6.7(回车),

输出结果却是:1 99 9999.000000 99.989998

对于小数点后的输出结果:浮点型数据,小数点后有六位有效数字,它随操作系统而变化,不是确切的数,不是直接补零。

但是为什么第三个数能够进行确切的输出,小数点后面全是0,而第四个数为什么小数点后面的数是那些呢?

因为这个你输入的是9999,本身小数点后面没有输入,所以自然补零

 

由于float是系统默认为6位有效数字,float是单精度型
double是双精度型的,最高可以到16位。

 

由计算结果可以看出:

就a被改写了。后面类型不匹配,scanf被自动终止。
但是,现在的问题是,明显就那个1被读走了,敲的后面那段内容哪去了?

 

或者这样,这个56去哪了?

 

为什么当从键盘上依次输入 12(空格)34(空格)56(空格)之后,为什么程序不等让用户输入C,就直接输出执行结果?

刚刚这个,明显是一开始要两个数,敲了三个,多一个
而第二次要求输入c的时候,程序就没停,没让输入,直接过去了

但是,这是什么原因呢:

因 为scanf语句你只让输入两个数据a和b,但是你在键盘上输入的时候却直接输入了三个整数,实际上scanf(“%d”,&c);已经执行了, 输入:12 34 56就相当于直接把56传给C,这样的执行等同于将两行scanf语句一同执行,但是在编写程序的过程中,这种输入法是不

提倡的。

补充:

对 于scanf(“%d%d”,&a,&b);中的双引号中的格式符,如果在两个%d中间加上一个空格,那么在键盘上进行输入时,一定要严 格遵从scanf中的形式,如:12(空格)34,但是如果将scanf()这条语句修改为:scanf(“%d%d”,&a,&b); 那么在键盘上输入的时候在两个int类型的数字之间,你可以任意输入空格,回车,或者Tab毽作为两个数据之间的分隔符。所以提倡使用scanf语句时, 在双引号之间只有格式符。双引号里直接写代码,别写空格。

,scanf输入的所有东西,都是先写到内存里,然后往变量里放。虽然这三个auto型变量也是在内存中,这么说很奇怪。

scanf对应的那里叫做stdin标准输入流,你敲击的东西都被顺序放入了标准输入流里面。

scanf("%d%d",&a,&b);

先把你敲击的东西写入stdin,然后再从stdin里顺次把数据转换成按照指定的类型(第一个是%d),也就是输入的字符流12转换成int型的12,写入指定的位置(第一个%d对应的是&a)

输入流内容是12 34 56
输入格式指定顺次为%d%d,也就是说一上来就要整数
基于贪心法,1是整数,合法;12是整数,合法;12空格不合法
那么%d对应的是12,把字符流12转换成int型的12,写到变量a中

 

下面拓展一下scanf的知识:

 

▪ 格式说明符 

▪ 空白符 

▪ 非空白符 

4注意问题

▪ 问题一 

▪ 问题二 

▪ 问题三 

▪ 问题四 

5发展

1函数原型编辑

1

int scanf(const char*format,...);

 

函数 scanf() 是从标准输入流stdio (标准输入设备,一般是键盘)中读内容的通用子程序,可以说明的格式读入多个字符,并保存在对应地址的变量中。

其调用形式为: scanf("<格式说明字符串>",<变量地址>);变量地址要求有效,并且与格式说明的次序一致。

2返回值编辑

scanf()函数返回成功赋值的数据项数,读到文件末尾出错时则返回EOF。

如:

1

scanf("%d %d",&a,&b);

如果a和b都被成功读入,那么scanf的返回值就是2

如果只有a被成功读入,返回值为1

如果a和b都未被成功读入,返回值为0

如果遇到错误或遇到end of file,返回值为EOF

且返回值为int型.

例:使用scanf函数输入数据。

1

2

3

4

5

6

7

8

9

10

#include<stdio.h>

int main(void)

{

int a,b,c;

printf("输入a,b,c\n");

scanf("%d%d%d",&a,&b,&c);

printf("a=%d,b=%d,c=%d\n",a,b,c);

fflush(stdin);

return 0;

}

&a,&b,&c中的&是地址运算符,&a指a在内存中的地址。scanf的作用是:按照a,b,c的内存地址将输入的数据存到a,b,c中去。变量a,b,c的地址是在编译连续阶段分配的(存储顺序由编译器决定)。

这 里注意:如果scanf中%d是连着写的如“%d%d%d”,在输入数据时,数据之间不可以加逗号,只能是空格或 tab键或者回车键—— “2 3 4” 或 “2(按tab)3(按tab)4(按tab)”。若是“%d,%d,%d”,则在输入数据时需要加“,”,如“2,3,4”.

3字符说明编辑

在 C++ 中,format 用 restrict 修饰。

format 指向的控制串由以下三类字符组成:

格式说明符

转换字符(就是%后跟的部分)

a 读浮点值(仅适用于 C++)

A 读浮点值(仅适用于 C++)

c 读单字符

d 读十进制整数

i 读十进制、八进制、十六进制整数

e 读浮点数

E 读浮点数

f 读浮点数

F 读浮点数(仅适用于 C++)

g 读浮点数

G 读浮点数

o 读八进制数

s 读字符串

x 读十六进制数

X 读十六进制数

p 读指针

n 至此已读入值的等价字符数

u 读无符号十进制整数

[ ] 扫描字符集合

% 读 % 符号(百分号)

附加格式说明字符修饰符说明

L/l 长度修饰符 输入"长"数据

h 长度修饰符 输入"短"数据

W 整型常数 指定输入数据所占宽度

* 表示本输入项在读入后不赋值给相应的变量

空白符

空白字符会使scanf()函数在读操作中略去输入中的一个或多个空白字符。

非空白符

一个非空白字符会使scanf()函数在读入时剔除掉与这个非空白字符相同的字符。

说明:

(1)%s 表示读字符串,而 %d 表示读整数。格式串的处理顺序为从左到右,格式说明符逐一与变元表中的变元匹配。为了读取长整数,可以将 L / l 放在格式说明符的前面;为了读取短整数,可以将 h 放在格式说明符的前面。这些修饰符可以与 d、i、o、u 和 x 格式代码一起使用。

(2)默认情况下,a、f、e 和 g 告诉 scanf() 为 float 分配数据。 如果将 L / l放在这些修饰符的前面,则 scanf() 为 double 分配数据。使用 L 就是告诉 scanf(),接收数据的变量是 long double 型变量。

(3)如果使用的现代编译器程序支持 1995 年增加的宽字符特性, 则可以与 c 格式代码一起,用 l 修饰符说明类型 wchar_t 的宽字符指针;也可以与 s 格式代码一起,用 l 修饰符说明宽字符串的指针。l 修饰符也可以用于修饰扫描集,以说明宽字符。

(4)控制串中的空白符使 scanf() 在输入流中跳过一个或多个空白行。空白符可以是空格(space)、制表符(tab)和新行符(newline)。 本质上,控制串中的空白符使 scanf() 在输入流中读,但不保存结果,直到发现非空白字符为止。

(5)非空白符使 scanf() 在流中读一个匹配的字符并忽略之。例如,"%d,%d" 使 scanf() 先读入一个整数,读入中放弃逗号,然后读另一个整数。如未发现匹配,scanf() 返回。

(6)scanf() 中用于保存读入值的变元必须都是变量指针,即相应变量的地址。

(7)在输入流中,数据项必须由空格、制表符和新行符分割。逗号和分号等不是分隔符,比如以下代码:

1

scanf("%d%d",&r,&c);

将接受输入 10 20,但遇到 10,20 则失败。

(8)百分号(%)与格式符之间的星号(*)表示读指定类型的数据但不保存。因此,

1

scanf("%d%*c%d",&x,&y);

对 10/20 的读入操作中,10 放入变量 x,20 放入 y。

(9)格式命令可以说明最大域宽。 在百分号(%)与格式码之间的整数用于限制从对应域读入的最大字符数。例如,希望向 address 读入不多于 20 个字符时,可以书写成如下形式:

1

scanf("%20s",address);

如果输入流的内容多于 20 个字符,则下次 scanf() 从此次停止处开始读入。 若达到最大域宽前已遇到空白符,则对该域的读立即停止;此时,scanf() 跳到下一个域。

(10)虽然空格、制表符和新行符都用做域分割符号,但读单字符操作中却按一般字符处理。例如,对输入流 "x y" 调用:

1

scanf("%c%c%c",&a,&b,&c);

返回后,x 在变量 a 中,空格在变量 b 中,y 在变量 c 中。

注意,控制串中的其它字符,包括空格、制表符和新行符,都用于从输入流中匹配并放弃字符,被匹配的字符都放弃。例如,给定输入流 "10t20",调用:

1

scanf("%dt%d",&x,&y);

将把 10 和 20 分别放到 x 和 y 中,t 被放弃,因为 t 在控制串中。

(11)ANSI C 标准向 scanf() 增加了一种新特性,称为扫描集(scanset)。 扫描集定义一个字符集合,可由 scanf() 读入其中允许的字符并赋给对应字符数组。 扫描集合由一对方括号中的一串字符定义,左方括号前必须缀以百分号。 例如,以下的扫描集使 scanf() 读入字符 A、B 和 C:

1

%[ABC]

使用扫描集时,scanf() 连续吃进集合中的字符并放入对应的字符数组,直到发现不在集合中的字符为止(即扫描集仅读匹配的字符)。返回时,数组中放置以 null 结尾、由读入字符组成的字符串。

用字符 ^ 可以说明补集。把 ^ 字符放为扫描集的第一字符时,构成其它字符组成的命令的补集合,指示 scanf() 只接受未说明的其它字符。

对于许多实现来说,用连字符可以说明一个范围。 例如,以下扫描集使 scanf() 接受字母 A 到 Z:

1

%[A-Z]

重要的是要注意扫描集是区分大小写的。因此,希望扫描大、小写字符时,应该分别说明大、小写字母。

(12) scanf() 返回等于成功赋值的域数的值,但由于星号修饰符而读入未赋值的域不计算在内。遇到文件结束则返回EOF;若出错则返回0.

(13)C99 为 scanf() 增加了几个格式修饰符:hh、ll、j、z 和 t。hh 修饰符可用于 d、i、o、u、x、X 或 n。它说明相应的变元是 signed 或 unsigned char 值,或用于 n 时, 相应的变元是指向 long char 型变量的指针。ll 修饰符也可用于 d、i、o、u、x、X 或 n。它说明相应的变元是 signed 或者 unsigned long long int 值。

j 格式修饰符应用于 d、i、o、u、x、X 或 n,说明匹配的变元是类型 intmax_t 或 uintmax_t。这些类型在 <stdint.h>; 中声明,并说明最大宽度的整数。

z 格式修饰符应用于 d、i、o、u、x、X 或 n,说明匹配的变元是指向 size_t 类型对象的指针。该类型在 <stddef.h>; 中声明,并说明 sizeof 的结构。

t 格式修饰符应用于 d、i、o、u、x、X 或 n,说明匹配的变元是指向 ptrdiff_t 类型对象的指针。该类型在 <stddef.h>; 中声明,并说明两个指针之间的差别。

4注意问题编辑

(1) 对于字符串数组或字符串指针变量,由于数组名和指针变量名本身就是地址,因此使用scanf()函数时,不需要在它们前面加上"&"操作符。

(2) 可以在格式化字符串中的"%"各格式化规定符之间加入一个整数,表示任何读操作中的最大位数。

(3) scanf()函数中没有精度控制。

如: scanf("%5.2f",&a); 是非法的。不能企图用此语句输入小数为2位的实数。

(4) scanf中要求给出变量地址,如给出变量名则会出错

如 scanf("%d",a);是非法的,应改为scanf("%d",&a);才是合法的。

(5) 在输入多个数值数据时,若格式控制串中没有非格式字符作输入数据之间的间隔,则可用空格,TAB或回车作间隔。

C编译在碰到空格,TAB,回车或非法数据(如对“%d”输入“12A”时,A即为非法数据)时即认为该数据结束。

(6) 在输入字符数据(%c)时,若格式控制串中无非格式字符,则认为所有输入的字符均为有效字符。

例如:

1

scanf("%c%c%c",&a,&b,&c);

输入为:

1

d e f

则把'd'赋予a, ' '(空格)赋予b,'e'赋予c。因为%c 只要求读入一个字符,后面不需要用空格作为两个字符的间隔,因此把' '作为下一个字符送给b。

只有当输入为:def(字符间无空格) 时,才能把'd'赋于a,'e'赋予b,'f'赋予c。 如果在格式控制中加入空格作为间隔,

1

scanf("%c %c %c",&a,&b,&c);

则输入时各数据之间可加空格。

我们用一些例子来说明一些规则:

1

2

3

4

5

6

7

8

9

#include<stdio.h>

int main(void)

{

chara,b;

printf("inputcharactera,b\n");

scanf("%c%c",&a,&b);/*注意两个%c之间没有任何符号*/

printf("%c%c\n",a,b);

return 0;

}

由于scanf函数"%c%c"中没有空格,输入M N,结果输出只有M。而输入改为MN时则可输出MN两字符,见下面的输入运行情况: input character a,b

输入:

1

 

屏幕显示:

1

 

1

2

3

4

5

6

7

8

9

#include<stdio.h>

int main(void)

{

char a,b;

printf("inputcharactera,b\n");

scanf("%c %c",&a,&b);/*注意两个%c之间有个空格*/

printf("\n%c%c\n",a,b);

return 0;

}

本例表示scanf格式控制串"%c %c"之间有空格时, 输入的数据之间可以有空格间隔。

(7) 如果格式控制串中有非格式字符则输入时也要输入该非格式字符。

例如:

1

scanf("%d,%d,%d",&a,&b,&c);

其中用非格式符“ , ”作间隔符,故输入时应为:

1

5,6,7

又如:

1

scanf("a=%d,b=%d,c=%d",&a,&b,&c);

则输入应为

1

a=5,b=6,c=7

如输入的数据与输出的类型不一致时,虽然编译能够通过,但结果将不正确。

1

2

3

4

5

6

7

8

9

#include<stdio.h>

int main(void)

{

int a;

printf("inputanumber");

scanf("%d",&a);

printf("%ld",a);

return 0;

}

由于输入数据类型整型, 而输出语句的格式串中说明为长整型,因此输出结果和输入数据不符。输出并不是输入的值。

如将scanf("%d",&a); 语句改为 scanf("%ld",&a);

输入数据为长整型,输入输出数据才相等。

问题一

如何让scanf()函数正确接受有空格的字符串?如: I love you!

1

2

3

4

5

6

7

8

#include<stdio.h>

int main(void)

{

char str[80];

scanf("%s",str);

printf("%s",str);

return 0;

}

输入:

1

I love you!

上述程序并不能达到预期目的,scanf()扫描到"I"后面的空格就认为对str的赋值结束,并忽略后面的"love you!".这里要注意是"love you!"还在键盘缓冲区(关于这个问题,网上我所见的说法都是如此,但是,我经过调试发现,其实这时缓冲区字符串首尾指针已经相等了,也就是说缓冲区清空了,scanf()函数应该只是扫描stdin流,这个残存信息是在stdin中)。我们改动一下上面的程序来验证一下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

#include<stdio.h>

#include<windows.h>

int main(void)

{

charstr[80],str1[80],str2[80];

scanf("%s",str);/*此处输入:Iloveyou!*/

printf("%s\n",str);

Sleep(5000);/*这里等待5秒,告诉你程序运行到什么地方*/

/*

不是sleep(5)

1,函数名是Sleep不是sleep。

2,C/C++中,unsignedSleep(unsigned)应该是毫秒ms.

*/

scanf("%s",str1);/*这两句无需你再输入,是对stdin流再扫描*/

scanf("%s",str2);/*这两句无需你再输入,是对stdin流再扫描*/

printf("%s\n",str1);

printf("%s\n",str2);

return 0;

}

输入:

1

I love you!

输出:

1

2

3

I

love

you!

好了,原因知道了,所以结论是:残留的信息 love you是存在于stdin流中,而不是在键盘缓冲区中。那么scanf()函数能不能完成这个任务?回答是:能!别忘了scanf()函数还有一个 %[] 格式控制符(如果对%[]不了解的请查看本文的上篇),请看下面的程序:

1

2

3

4

5

6

7

8

#include<stdio.h>

int main(void)

{

charstr[50];

scanf("%[^\n]",str);/*scanf("%s",string);不能接收空格符*/

printf("%s\n",str);

return 0;

}

问题二

键盘缓冲区残余信息问题

1

2

3

4

5

6

7

8

9

10

11

12

13

#include<stdio.h>

int main(void)

{

inta;

charc;

while(c!='N')

{

scanf("%d",&a);

scanf("%c",&c);

printf("a=%dc=%c\n",a,c);/*printf("c=%d\n",c);*/

}

return 0;

}

scanf("%c", &c); 这句不能正常接收字符,什么原因呢?我们用printf("c = %d\n", c);将C用int表示出来,启用printf("c = %d \n", c);这一句,看看scanf()函数赋给C到底是什么,结果是c=10 ,ASCII值为10是什么?换行即\n.对了,我们每击打一 下"Enter"键,向键盘缓冲区发去一个“回车”(\r),一个“换行"(\n),在这里\r被scanf()函数处理掉了(姑且这么认为吧^_^),而\n被scanf()函数“错误”地赋给了c.解决办法:可以在两个scanf()函数之后加getch()getchar(),但是要视具体scanf()语句加那个,这里就不分析了,读者自己去摸索吧。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

#include<stdio.h>

int main(void)

{

inta;

charc;

while(c!='N')

{

scanf("%d",&a);

fflush(stdin);

scanf("%c",&c);

fflush(stdin);

printf("a=%dc=%c\n",a,c);

}

return 0;

}

这里再给一个用“空格符”来处理缓冲区残余信息的示例:

版本1:运行出错的程序

1

2

3

4

5

6

7

8

9

10

#include<stdio.h>

int main(void)

{

inti;

charj;

for(i=0;i<10;++i)

scanf("%c",&j);/*这里%前没有空格*/

printf("%c",j);/*在输入十个字符之后*/

return 0;

}

版本2:使用了空格控制符后

1

2

3

4

5

6

7

8

9

10

#include<stdio.h>

int main(void)

{

inti;

charj;

for(i=0;i<10;++i)

scanf("%c",&j);/*注意这里%前有个空格*/

printf("%c",j);/*在输入十个字符之后,验证打印出来的字符是否是自己输入的最后一个字符(即输入的第十个字符)*/

return 0;

}

接着,我们运行看看,首先,运行第一个版本(错误的程序)

我们输入:

0 1 2 3 4 5 6 7 8 9

结果是一个空字符

再运行第二个版本(正确的程序)

同样输入:

0 1 2 3 4 5 6 7 8 9

这一次就显示字符9,故此程序正确。

那么为什么第二个程序就正确呢,原因何在,在%前面加一个空格就这么有用,答案是肯定的,就是%前面的空格在起作用,读者看看此文章的前面部分,在scanf的使用过程中应注意的问题中已经指出:“scanf()的格式控制串可以使用空白字符或其它非空白字符,使用空白字符会使scanf()函数在读操作中略去输入中的零个或多个空白字符。”

所以在%前面加上了空格(空格属于空白字符,此外还有像制表符等也属于空白字符),在输入过程中,将略去输入中的一个或多个空白字符,所以我们输入的0 1 2 3 4 5 6 7 8 9这些字符中的空白字符就被略去了,字符9也就正确的打印出来了,这样子解释,相信大家都看明白勒吧!

问题三

输入类型与格式化字符串不匹配导致stdin流的阻塞。

1

2

3

4

5

6

7

8

9

10

#include<stdio.h>

int main(void)

{

int a=0,b=0,c=0,ret=0;

ret=scanf("%d%d%d",&a,&b,&c);

printf("第一次读入数量:%d\n",ret);

ret=scanf("%d%d%d",&a,&b,&c);

printf("第二次读入数量:%d\n",ret);

return 0;

}

我们定义了a,b,c三个变量来接受输入的内容,定义了变量ret来接收scanf函数的返回值。

正确输入的话:

1 2 3

第一次读入数量:3

1 2 3

第二次读入数量:3

请求任意键继续......

 

但是当输入内容与格式换字符串不匹配时,结果会令人大跌眼镜(仔细分析会对scanf函数和stdin流有更深入的哦):

 1 b 2

第一次读入数量:1

第二次读入数量:0

请求任意键继续......

 

执 行到第一个scanf时,当输入字符’b’的时候与ret=scanf("%d%d%d",&a,& amp;b,&c);中的 格式化字符串不匹配,stdin流被阻塞,scanf函数不在读取后面的部分,直接将1返回,表示只将stdin流中的 1读入到了变量a中。

执行到第二个scanf时,字符’b’还是与格式化字符串不匹配,stdin流仍然被阻塞,所以没有提示输入,scanf函数将0返回。

将代码作如下修改,可以有力的证明上述结论。

1

2

3

4

5

6

7

8

9

10

#include<stdio.h>

int  main(void)

{

int a=0,b=0,c=0,ret=0;

ret=scanf("%d%d%d",&a,&b,&c);

printf("第一次读入数量:%d\n",ret);

ret=scanf("%c%d%d",&a,&b,&c);

printf("第二次读入数量:%d\n",ret);

return 0;

}

当把第二个scanf函数内的格式化字符串改为”%c%d%d”时,运行结果如下:

 1 b 2

第一次读入数量:1

6

第二次读入数量:3

请求任意键继续......

 

执行到第一个scanf函数时,由于输入’b’的原因scanf函数直接返回1,stdin流阻塞。

执行到第二个scanf函数时,字符’d’与格式化字符串”%c%d%d”中的%c匹配,stdin流终于疏通,在输入6,则将变量a,b,c分别赋值为98(‘b’的ASCII码)、2、6,scanf函数返回3。

有上述问题可知,当使用scanf函数时,如果遇到一些匪夷所思的问题,在scanf函数后正确使用fflush(stdin);,清空输入缓冲区,可以解决很多问题。以本题为例:

1

2

3

4

5

6

7

8

9

10

11

12

#include<stdio.h>

int main(void)

{

inta=0,b=0,c=0,ret=0;

ret=scanf("%d%d%d",&a,&b,&c);

fflush(stdin);

printf("第一次读入数量:%d\n",ret);

ret=scanf("%d%d%d",&a,&b,&c);

fflush(stdin);

printf("第二次读入数量:%d\n",ret);

return 0;

}

运行结果:

 1 b 2

第一次读入数量:1

1 3 6

第二次读入数量:3

请求任意键继续......

 

问题解决。

问题四

如何处理scanf()函数误输入造成程序死锁或出错

1

2

3

4

5

6

7

8

9

#include<stdio.h>

int main(void)

{

int a,b,c;

scanf("%d,%d",&a,&b);

c=a+b;/*计算a+b*/

printf("%d+%d=%d",a,b,c);

return 0;

}

如上程序,如果正确输入a,b的值,那么没什么问题,但是,你不能保证使用者每一次都能正确输入,一旦输入了错误的类型,你的程序不是死锁,就是得到一个错误的结果,呵呵,这可能所有人都遇到过的问题吧?解决方法:scanf()函数执行成功时的返回值是成功读取的变量数,也就是说,你这个scanf()函数有几个变量,如果scanf()函数全部正常读取,它就返回几。但这里还要注意另一个问题,如果输入了非法数据,键盘缓冲区就可能还个有残余信息问题。正确的例程:

1

2

3

4

5

6

7

8

9

10

#include<stdio.h>

int main(void)

{

int a,b,c;

while(scanf("%d%d",&a,&b)!=2)

fflush(stdin);

c=a+b;

printf("%d+%d=%d",a,b,c);

return 0;

}

补充

fflush(stdin)这个方法在GCC下不可用。(在VC6.0下可以)

以下是 C99 对 fflush 函数的定义:

int fflush(FILE *stream);

如果stream指向输出流或者更新流(update stream),并且这个更新流

执行的操作不是输入,那么fflush函数将把任何未被写入的数据写入stream

指向的文件(如标准输出文件stdout)。否则,fflush函数的行为是不确定的。

C和C++的标准里从来没有定义过 fflush(stdin)

fflush(NULL)清空所有输出流和上面提到的更新流。如果发生写错误,fflush

函数会给那些流打上错误标记,并且返回EOF,否则返回0。

由此可知,如果 stream 指向输入流(如 stdin),那么 fflush 函数的行为是不确定的。故而使用

fflush(stdin) 是不正确的,至少是移植性不好的。

可采用如下方法:

方法一:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

/*此函数可以和scanf函数一起使用,但使用%c输入时要注意,即此函数只能用于缓冲区非空的情况*/

#include<stdio.h>

void flush()

{

charc;

while((c=getchar())!='\n'&&c!=EOF);

}

intmain(void)

{

inta,b,c;/*计算a+b*/

while(scanf("%d%d",&a,&b)!=2)

flush();

c=a+b;

printf("%d+%d=%d",a,b,c);

return 0;

}

方法二:

使用getchar()代替fflush(stdin)

程序示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

#include<stdio.h>

int main(void)

{

inti,c;

while(1)

{

printf("Pleaseinputaninteger:");

scanf("%d",&i);

if(feof(stdin)||ferror(stdin))

{

//如果用户输入文件结束标志(或文件已被读完),或者发生读写错误,则退出循环

//dosomething

break;

}

//没有发生错误,清空输入流。通过while循环把输入流中的余留数据“吃”掉

while((c=getchar())!='\n'&&c!=EOF);

//可直接将这句代码当成fflush(stdin)的替代,直接运行可清除输入缓存流

//使用scanf("%*[^\n]");也可以清空输入流,不过会残留\n字符。

printf("%d\n",i);

}

return 0;

}

5:

使用scanf函数进行输入,必须指定输入的数据的类型和格式,不仅繁琐复杂,而且很容易出错.C++保留scanf只是为了和C兼容,以便过去用C语言写的程序可以在C++的环境下运行。C++的编程人员都愿意使用cin进行输入,很少使用scanf。

附:关于scanf的知识请参照:

http://baike.baidu.com/link?url=xiSSJUdLQKZ09LckuLsZqQTf9JwV39siqxII6TACdZHdgulY8OAfFt49b4-JYSv0U8Sm5EyC4BOUjNSbxg6AOq

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

转载于:https://www.cnblogs.com/xuyinghui/p/4477248.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值