在看《征服C指针》时的一些问题的汇总。
1.fgets函数
在《征服C指针》中很少用到scanf()函数,而是使用fgets函数和sscanf函数的组合,于是仔细看了下fgets函数的用法。
char *fgets(char *s, int size, FILE *stream);
fgets() reads in at most one less than size characters from stream and stores them
into the buffer pointed to by s. Reading stops after an EOF or a newline. If a new-
line is read, it is stored into the buffer. A '\0' is stored after the last charac-
ter in the buffer.
函数原型:
char *fgets(char *buf, int bufsize, FILE *stream);
参数:
*buf: 字符型指针,指向用来存储所得数据的地址。
bufsize: 整型数据,指明存储数据的大小。
*stream: 文件结构体指针,将要读取的文件流。
光看上面的说明还是不太明白fgets函数的细节问题,通过一些测试例子给出说明。
</pre><pre name="code" class="cpp">#define N 10 //定义缓冲区的最大长度是10 int main() { char buf[N]={0}; int i=0; while(fgets(buf, N, stdin)) {//从标准输入中读取一行数据到缓冲区buf中 //打印出读取的次数,输入的字符串,字符串的长度,并且手动添加了换行符 fprintf(stdout, "time i:%d\ninput string:%s\nlength:%d\n", i, buf, strlen(buf)); //将字符串中的每一个字符输出,即输出字符的十进制表示,也输出字符形式 for (int j=0;j<strlen(buf);j++) { printf("buf[%d]:%d %c\n",j,buf[j],buf[j]); } i++; } return 0; }
往里面输入12345并按回车键,会输出下面的内容:
这里需要注意下面几点:
1:最上面的12345是手动输入的,点击回车后出现了下面几行,光标跳转到了最下面。这里面多出现了两个换行符,换行符1和换行符2,(图中用红线标注的)。
这是由于输入12345并按下回车后fgets函数既读取了12345也读取了回车换行这个字符,所以在输出时将回车一起输出了,这样就出现换行符1,使用strlen函数求取buf的长度是6也就很好理解了。
2:打印输出buf的内容时,for循环里使用的是strlen(buf),而不是使用定义buf这个数组时给数组指定的长度N。这是由于指定N只是定义了buf可以使用的最大字符长度,但是buf只存储少量字符也是可以的,这种情况下后面的字符算是无效的字符了,也就没有必要进行处理了。for循环里的strlen(buf)替换成N,打印出buf缓存区中的所有内容如下:
//将字符串中的每一个字符输出,即输出字符的十进制表示,也输出字符形式 for (int j=0;j<N;j++) { printf("buf[%d]:%d %c\n",j,buf[j],buf[j]); }
从上面可以一面了然的看出buf缓冲区中的前五个字符分别是'1','2','3','4','5','\n'(回车换行符,它的十进制表示是10),然后从第七个字符开始全部变成了空字符,空字符在ANSIC码中的十进制表示是0,字符形式是'\0',在屏幕上就是什么都不会显示。所以可以使用十进制或字符形式来判断一个字符串是否结束。至于为什么第8,9,10个字符也是空字符,这是由于buf数组初始化时是使用空字符来初始化的:
char buf[N]={0};如果将上面的代码替换成只声明数组,并不用空字符来初始化:
char buf[N];输出如下:
如果数组不初始化,这个就很明显了,前6个字符是从标准输入中输入的字符,然后第7个字符是字符串结束符空字符'\0',至于buf缓存区中剩下的就是未知的了,所以只需要处理strlen(buf)长度的字符串就可以了。
从上面可以看出局部变量(即在栈中分配内存的变量初始的值是未知的,所以需要手动对它们进行初始化)
char buf[N]={0};以下是这四种不同初始化方式并打印输出的结果:从下面的测试可以看出使用数组时应该怎么进行合适正确的初始化工作,不对其进行初始化是不对的,但是对其初始化也不要弄错了。
方式1:不初始化数组,结果未知
char buf[N]; for (int j=0;j<N;j++) { printf("buf[%d]:%d %c\n",j,buf[j],buf[j]); }
char buf[N]={0}; for (int j=0;j<N;j++) { printf("buf[%d]:%d %c\n",j,buf[j],buf[j]); }
方式3:使用'\0'来初始化数组,方式2和方式3其实是一样的。
char buf[N]={'\0'}; for (int j=0;j<N;j++) { printf("buf[%d]:%d %c\n",j,buf[j],buf[j]); }
方式4:使用'0'来初始化数组,这是使用字符‘0’来初始化数组,48是字符0对应的ANSIC码
char buf[N]={'0'}; for (int j=0;j<N;j++) { printf("buf[%d]:%d %c\n",j,buf[j],buf[j]); }
从上面的结果可以看出,当输入"123456789"并按回车,这时外部输入了10个字符,但是由于buf缓冲区中只能容纳10个字符,并且这10个字符中还包括字符串结束符‘\0’,也就是说当外部输入达到10个时,它第一次只读取了9个字符,也就是"123456789",buf缓冲区中第10个位置存放字符串结束符'\0',此时还有一个回车换行符没有被读取会再次进入循环,然后读取这个回车换行字符,所以第二次读取时buf[0]的值为10(即回车换行符的ANSIC码),然后跟着一个空字符表示字符串的结束。并且第3个位置到第10个位置的值也很有意思,就是上一次buf的值。
从上面可以看出buf中至少会有一个字符,就是回车换行字符。即使标准输入中什么也不输入,但是点击回车换行时这个字符也会保存在buf中。
2.getchar函数
这个函数的原型是:
int getchar(void);
说明如下:
getchar 由宏实现:#define getchar() getc(stdin)。getchar有一个int型的返回值.当程序调用getchar时.程序就等着用户按键.用户输入的字符被存放在键盘缓冲区中.直到用户按回车为止(回车字符也放在缓冲区中).当用户键入回车之后,getchar才开始从stdio流中每次读入一个字符.getchar函数的返回值是用户输入的字符的ASCII码,如出错返回-1,且将用户输入的字符回显到屏幕.如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取.也就是说,后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完为后,才等待用户按键。
针对上面的说明,做了个很简单但感觉很有意思的测试.。
int main(){ putchar(getchar()); //putchar(getchar()); //putchar(getchar()); return 0; }
运行,输入1并按回车:
发现就是打印出了输入的1,并没有打印回车换行符。然后将上面的第一行注释给去掉:
int main(){ putchar(getchar()); putchar(getchar()); //putchar(getchar()); return 0; }再次运行,输入1并按回车:
发现多输出了一个换行符,就是第二个putchar(getchar())接收到了回车换行的输入,如果把最下面的注释也给去掉,运行结果如下:
发现又是等待输入的界面。
从上面的三次测试中可以很好理解对getchar函数的那段说明。首先用户的输入(包括回车换行符)被放在缓冲区中,只有键入回车之后才开始从这个缓冲区中读取字符,然后如用户在按回车之前输入了不止一个字符(其实输入一个字符也会有回车换行符保存在缓冲区吧?),其他字符会保留在键盘缓存区中,等待后续getchar调用读取,从使用两个getchar的测试中可以看出其它字符保存在缓冲区中,所以第二个getchar直接取得了回车换行符而没有等待用户输入,当使用三个getchar时由于缓冲区中只有两个字符,一个'1',一个回车换行符都读取完了,所以又进入了等待用户输入的界面。