一. 函数的调用过程
函数的调用过程要为函数开辟栈空间,用于本次函数的调用中临时变量的保存、现场保护。这块栈空间称之为函数栈帧。
在函数调用的过程中ebp和esp这两个寄存器存放了维护这个栈的栈底和栈顶指针。ebp存放指向函数栈帧栈底的地址。 esp存放指向函数栈帧栈顶的地址。
eg:
#include <stdio.h>
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
int ret = Add(a, b);
printf("ret = %d\n", ret);
return 0;
}
二. 函数递归
函数调用自身称为递归。(大事化小)
使用递归的条件:
1.存在限制条件,当满足这个限制条件时,停止递归;
2.每次递归调用后越来越接近这个限制条件。
eg1:接受一个整型值,把它转换为字符并且打印。如输入1234,输出1 2 3 4
#include<stdio.h>
void Print(int n)
{
if (n > 9)
{
Print(n/10);
}
printf("%d ",n%10);
}
int main()
{
int num = 1234;
Print(num);
return 0;
}
eg2:编写函数不允许创建临时变量,求字符串长度
#include <stdio.h>
int Strlen(const char*str)
{
if (*str == '\0')
return 0;
else return 1 + Strlen(str + 1);
}
int main()
{
char *p = "abcdef";
int len = Strlen(p);
printf("%d\n", len);
return 0;
}
eg3:求n的阶乘
#include <stdio.h>
int factorial(int n)
{
if (n <= 1)
return 1;
else
return n * factorial(n - 1);
}
int main()
{
int n = 10;
printf("%d\n",factorial(n));
}
eg4:求第n个斐波那契数列
#include <stdio.h>
int fib(int n)
{
if (n <= 2)
return 1;
else
return fib(n - 1) + fib(n - 2);
}
int main()
{
int n = 20;
printf("%d\n", fib(20));
return 0;
}
以上例子,当参数特别大时会报错:栈溢出。
系统分配给程序的栈空间是有限的,但是如果出现了死循环或者死递归,可能导致一直开辟栈空间,最终将栈空间耗尽,这样的现象称为栈溢出。
为了解决栈溢出,可以将递归写成非递归。或者使用static对象替代nonstatic局部对象(栈对象),不仅可以减少每次递归调用和返回产生和释放nonstatic对象的开销,而且static对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。
例如求n个斐波那契数列:
#include <stdio.h>
int fib(int n)
{
int result;
int pre_result;
int next_older_result;
result = pre_result = 1;
while (n > 2)
{
n -= 1;
next_older_result = pre_result;
pre_result = result;
result = pre_result + next_older_result;
}
return result;
}
int main()
{
int n = 500;
printf("%d\n", fib(n));//315178285
return 0;
}
三. main函数的参数
int main( int argc, char *argv[ ], char *envp[ ] )
{
program-statements
}
第一个参数:argc是个整型变量,表示命令行参数的个数(含第一个参数)。
第二个参数:argv是个字符指针的数组,每个元素是一个字符指针,指向一个字符串。这些字符串就是命令行中的每一个参数(字符串)。
第三个参数:envp是字符指针的数组,数组的每一个原元素是一个指向一个环境变量(字符串) 的字符指针。
#include <stdio.h>
int main(int argc, char* argv[], char* envp[])
{
int i = 0;
for(i=0; i<argc; i++)
{
printf("%s\n", argv[i]);
}
return 0;
}
//F:\C\test0718\Debug\test0718.exe
#include <stdio.h>
int main(int argc, char* argv[], char* envp[])
{
int i = 0;
while (envp[i] != NULL)
{
printf("%s\n", envp[i]);
i++;
}
return 0;
}
/*
COMPUTERNAME=DESKTOP-QV5439H
ComSpec=C:\Windows\system32\cmd.exe
FPS_BROWSER_APP_PROFILE_STRING=Internet Explorer
FPS_BROWSER_USER_PROFILE_STRING=Default
HOMEDRIVE=C:
HOMEPATH=\Users\Yuri
LOCALAPPDATA=C:\Users\Yuri\AppData\Local
LOGONSERVER=\\DESKTOP-QV5439H
MSBuildLoadMicrosoftTargetsReadOnly=true
NUMBER_OF_PROCESSORS=4
OneDrive=C:\Users\Yuri\OneDrive
OS=Windows_NT
Path=C:\Program Files (x86)\Intel\iCLS Client\;C:\Program Files\Intel\iCLS Client\;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;F:\Git\cmd;C:\Program Files\TortoiseGit\bin;C:\Users\Yuri\AppData\Local\Microsoft\WindowsApps;
PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
PkgDefApplicationConfigFile=C:\Users\Yuri\AppData\Local\Microsoft\VisualStudio\15.0_97bd02f3\devenv.exe.config
PROCESSOR_ARCHITECTURE=x86
PROCESSOR_ARCHITEW6432=AMD64
PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 61 Stepping 4, GenuineIntel
*/
//以上只是部分
结果显示都是环境变量。envp数组的最后一个元素存放NULL指针。
四. 可变参数列表
通过将函数实现为可变参数的形式,可以使得函数可以接受1个以上的任意多个参数(不固定)。
eg:实现一个函数可以求任意个参数的平均值
#include <stdio.h>
#include <stdarg.h>
int average(int n, ...)
{
va_list arg;
int i = 0;
int sum = 0;
va_start(arg, n);
for(i=0; i<n; i++)
{
sum += va_arg(arg, int);
}
return sum / n;
va_end(arg);
}
int main()
{
int a = 1;
int b = 2;
int c = 3;
int avg1 = average(2, a, c);
int avg2 = average(3, a, b, c);
printf("avg1 = %d\n", avg1);
printf("avg2 = %d\n", avg2);
return 0;
}
/*
avg1 = 2
avg2 = 2
*/
声明一个 va_list 类型的变量 arg ,它用于访问参数列表的未确定部分。
这个变量是调用 va_start 来初始化的。它的第一个参数是 va_list 的变量名,第2个参数是省略号前后一个有名字的参数。初始化过程把 arg 变量设置为指向可变参数部分的第一 个参数。
为了访问参数,需要使用 va_arg ,这个宏接受两个参数: va_list 变量和参数列表中下一 个参数的类型。在这个例例子中所有的可变数都是整型。 va_arg 返回这个参数的值,并使用 va_arg 指向下⼀个可变参数。
当访问完最后一个可变参数之后,我们需要调用 va_end。
可变参数的限制:
可变参数必须从头到尾逐个访问。如果在访问了几个可变参数之后想半途终止,这是可以的,但是,如果想⼀开始就访问参数列表中间的参数,那是不行的。
参数列表中至少有一个命名参数。如果连一个命名参数都没有,就无法使用va_start。
这些宏是无法直接判断实际存在参数的数量。
这些宏无法判断每个参数的类型。
如果在va_arg中指定了错误的类型,那么其后果是不可预测的。
五. 字符函数与字符串函数
1.求字符串长度
(1)strlen
size_t strlen ( const char * str );
字符串已 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包含 '\0' )。
参数指向的字符串必须要以 '\0' 结束。
注意函数的返回值为size_t,是无符号的。
模拟实现:
计数器方式:
int my_strlen(const char * str)
{
int count = 0;
while(*str)
{
count++;
str++;
}
return count;
}
不创建临时变量计数器方式:
int my_strlen(const char * str)
{
if (*str == '\0')
return 0;
else
return 1 + my_strlen(str + 1);
}
指针方式:
int my_strlen(char *s)
{
char *p = s;
while (*p != '\0')
p++;
return p - s;
}
2.长度不受限制的字符串函数
(1)strcpy字符串拷贝
char* strcpy(char * destination, const char * source );
源字符串必须以 '\0' 结束。
会将源字符串中的 '\0' 拷贝到目标空间。
目标空间必须足够大,以确保能存放源字符串。
目标空间必须可变。
不能将字符串拷贝到常量字符串中。
模拟实现:
char *my_strcpy(char *dest, const char*src)
{
char *ret = dest;
assert(dest != NULL);
assert(src != NULL);
while ((*dest++ = *src++))
{ ; }
return ret;
}
(2)strcat 字符串连接
char * strcat ( char * destination, const char * source );
源字符串、目标字符串都必须以 '\0' 结束。
目标空间必须有足够的大,能容纳下源字符串的内容。
目标空间必须可修改。
模拟实现:
char *my_strcat(char *dest, const char*src)
{
char *ret = dest;
assert(dest != NULL);
assert(src != NULL);
while (*dest)
{
dest++;
}
while ((*dest++ = *src++))
{ ; }
return ret;
}
(3)strcmp字符串比较
int strcmp ( const char * str1, const char * str2 );
标准规定:
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数字
#include<stdio.h>
int main()
{
char *p = "abcdef";
char *q = "abcdef";
if (p == q)
{
printf("p==q");
}
else
{
printf("p!=q");
}
return 0;
}
//输出p==q
//常量字符串存一份,p与q指向同一块空间。若是两个内容一样的字符串数组,则不相等。
3.长度受限制的字符串函数
(1)strncpy
char * strncpy ( char * destination, const char * source, size_t num );
拷贝num个字符从源字符串到目标空间。 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
(2)strncat
char * strncat ( char * destination, const char * source, size_t num );
#include <stdio.h>
#include <string.h>
int main() {
char str1[20];
char str2[20];
strcpy(str1, "Hello ");
strcpy(str2, "i am a student");
strncat(str1, str2, 7);
puts(str1);
return 0;
}
//Hello i am a
(3)strncmp
int strncmp ( const char * str1, const char * str2, size_t num );
比较到出现另一个字符不一样或者另一个字符串结束或者num个字符全部比较完.
eg:
#include <stdio.h>
#include <string.h>
int main() {
char str[][5] = { "R2D2" , "C3PO" , "R2A6" };
int n;
printf("Looking for R2 astromech droids :\n");
for (n = 0; n < 3; n++)
if (strncmp(str[n], "R2xx", 2) == 0)
{
printf("found %s\n", str[n]);
}
return 0;
}
/*
Looking for R2 astromech droids :
found R2D2
found R2A6
*/
4.字符串查找
(1)strstr
char * strstr ( const char *, const char * );
返回str1中str2的第1次出现的指针或空指针。
函数返回一个指针,指向字符串中第一个出现的str2,如果str2没有出现在str1中,则返回NULL。如果str2指向长度为零的字符串,则函数返回字符串。
eg:
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "This is a simple string";
char * pch;
pch = strstr(str, "simple");
strncpy(pch, "sample", 6);
printf("%s\n", str);
return 0;
}
//This is a sample string
模拟实现:
char *my_strstr(const char* str1, const char* str2)
{
assert(str1);
assert(str2);
char *cp = (char*)str1;
char *substr = (char *)str2;
char *s1 = NULL;
if (*str2 == '\0')
return NULL;
while (*cp)
{
s1 = cp;
substr = str2;
while (*s1 && *substr && (*s1 == *substr))
{
s1++;
substr++;
}
if (*substr == '\0')
return cp;
cp++;
}
}
(2)strtok(在字符串中查找下一个标记)
char * strtok ( char * str, const char * sep );
第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
如果字符串中不存在更多的标记,则返回 NULL 指针。
eg:
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "- This, a sample string.";
char * pch;
printf("Splitting string \"%s\" into tokens:\n", str);
pch = strtok(str, " ,.-");
while (pch != NULL)
{
printf("%s\n", pch);
pch = strtok(NULL, " ,.-");
}
return 0;
}
/*
Splitting string "- This, a sample string." into tokens:
This
a
sample
string
*/
#include <stdio.h>
int main()
{
char *p = "Taylor@163.com";
const char* sep = ".@";
char arr[30];
char *str = NULL;
strcpy(arr, p);//将数据拷贝一份,处理arr数组的内容
for (str = strtok(arr, sep); str != NULL; str = strtok(NULL, sep))
{
printf("%s\n", str);
}
}
/*
Taylor
163
com
*/
5.错误信息报告
(1)strerror
char * strerror ( int errnum );
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
FILE * pFile;
pFile = fopen("unexist.ent", "r");
if (pFile == NULL)
printf("Error opening file unexist.ent: %s\n", strerror(errno));
//errno: Last error number
return 0;
}
/*
Error opening file unexist.ent: No such file or directory
*/
6.内存操作函数
(1)memcpy
void * memcpy ( void * destination, const void * source, size_t num );
函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
这个函数在遇到 '\0' 的时候并不会停下来。
如果source和destination有任何的重叠,复制的结果都是未定义的。
#include <stdio.h>
#include <string.h>
struct {
char name[40];
int age;
}person, person_copy;
int main()
{
char myname[] = "Taylor de Fermat";
//using memcpy to copy string:
memcpy(person.name, myname, strlen(myname) + 1);
person.age = 22;
//using memcpy to copy structure:
memcpy(&person_copy, &person, sizeof(person));
printf("person_copy: %s, %d \n", person_copy.name, person_copy.age);
return 0;
}
//person_copy: Taylor de Fermat, 22
模拟实现:
void * memcpy(void * dst, const void * src, size_t count)
{
void * ret = dst;
assert(dst);
assert(src);
//copy from lower addresses to higher addresses
while (count--)
{
*(char *)dst = *(char *)src;
dst = (char *)dst + 1;
src = (char *)src + 1;
}
return(ret);
}
(2)memmove
void * memmove ( void * destination, const void * source, size_t num );
和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
如果源空间和目标空间出现重叠,就得使用memmove函数处理。
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "memmove can be very useful......";
memmove(str + 20, str + 15, 11);
puts(str);
return 0;
}
//memmove can be very very useful.
模拟实现:
void * memcpy(void * dst, const void * src, size_t count)
{
void * ret = dst;
if (dst <= src || (char *)dst >= ((char *)src + count))
{
//Non-Overlapping Buffers
//copy from lower addresses to higher addresses
while (count--)
{
*(char *)dst = *(char *)src;
dst = (char *)dst + 1;
src = (char *)src + 1;
}
}
else {
//Overlapping Buffers
//copy from higher addresses to lower addresses
dst = (char *)dst + count - 1;
src = (char *)src + count - 1;
while (count--) { *(char *)dst = *(char *)src;
dst = (char *)dst - 1;
src = (char *)src - 1;
}
}
return(ret);
}
(3)memcmp
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
比较从ptr1和ptr2指针开始的num个字节。
返回值:
<0 | ptr1 less than ptr2 |
0 | ptr1 identical to ptr2 |
>0 |
ptr1 greater than ptr2 |
#include <stdio.h>
#include <string.h>
int main()
{
char buffer1[] = "DWgaOtP12df0";
char buffer2[] = "DWGAOTP12DF0";
int n;
n = memcmp(buffer1, buffer2, sizeof(buffer1));
if (n > 0)
printf("'%s' is greater than '%s'.\n", buffer1, buffer2);
else if (n < 0)
printf("'%s' is less than '%s'.\n", buffer1, buffer2);
else printf("'%s' is the same as '%s'.\n", buffer1, buffer2);
return 0;
}
//'DWgaOtP12df0' is greater than 'DWGAOTP12DF0'.
六. 输入输出函数
1.错误报告函数
void perror ( const char * str );
perror 以一种简单、统一的方式报告错误。ANSIC函数库的许多函数调用操作系统来完成某些任务,I/O函数尤其如此,当操作系统按照要求执行某些任务的时候就可能会失败,例如:尝试打开⼀个根本就不存在的文件,读取内容。操作系统的做法就是发生错误的时候,能够提示发生了错误。标准库函数提供了一个全局的变量 errno (在errno.h中定义),来记录错误的错误码,交给用户程序,用于提示错误的准确原因。
eg:
#include <stdio.h>
int main ()
{
FILE * pFile;
pFile=fopen ("unexist.ent","rb");
if (pFile == NULL)
perror ("The following error occurred");
else
fclose (pFile);
return 0;
}
//The following error occurred: No such file or directory
如果perror函数的参数str指针不是NULL,并指向一个非空的字符串,perror函数先打印这个字符串,然后跟着输出一个分号和一个空格,接着输出错误信息提示。
errno这个值只有在调用库函数的时候发生问题了,才能会被设置。而函数调用成功之后也不会修改,所以不能使用errno来判断一个函数是否执行成功。
2.终止执行
void exit (int status);
参数status返回给操作系统,用于提示程序是否正常完成。
C语言定义了预定义符号: EXIT_SUCCESS 和 EXIT_FAILURE 来表示成功或者失败返回。
#include <stdio.h> /* printf, fopen */
#include <stdlib.h> /* exit, EXIT_FAILURE */
int main()
{
FILE * pFile;
pFile = fopen("myfile.txt", "r");
if (pFile == NULL)
{
printf("Error opening file");
exit(EXIT_FAILURE);
}
else
{
/* file operations here */
}
return 0;
}
//Error opening file
3.I/O函数
功能 | 函数名 |
适用于 |
字符输入函数 |
getchar |
标准输入流 |
字符输出函数 |
putchar |
标准输出流 |
字符输入函数 |
fgetc, getc |
所有输入流 |
字符输出函数 |
fputc, putc |
所有输出流 |
文本行输入函数 |
fgets, gets |
所有输入流 |
文本行输出函数 |
fputs, puts |
所有输出流 |
格式化输入函数 |
scanf |
标准输入流 |
格式化输出函数 |
printf |
标准输出流 |
格式化输入函数 |
fscanf |
所有输入流 |
格式化输出函数 |
fprintf |
所有输出流 |
二进制输入 |
fread |
文件 |
二进制输出 |
fwrite |
文件 |
4.打开流
FILE * fopen ( const char * filename, const char * mode );
#include <stdio.h>
int main ()
{
FILE * pFile;
pFile = fopen ("myfile.txt","w");
if (pFile!=NULL)
{
fputs ("fopen example",pFile);
fclose (pFile);
}
return 0;
}
5.关闭流
int fclose ( FILE * stream );
#include <stdio.h>
int main ()
{
FILE * pFile;
pFile = fopen ("myfile.txt","wt");
fprintf (pFile, "fclose example");
fclose (pFile);
return 0;
}
文件使用方式 |
含义 |
如果指定文件不存在 |
r(只读) |
为了输入数据,打开一个已经存在的文本文件 |
出错 |
w(只写) |
为了输出数据,打开一个文本文件 |
建立一个新文件 |
a(追加) |
向文本文件尾添加数据 |
出错 |
rb(只读) |
为了输入数据,打开一个二进制文件 |
出错 |
wb(只写) |
为了输出数据,打开一个二进制文件 |
建立一个新文件 |
ab(追加) |
向一个二进制文件尾添加数据 |
出错 |
r+(读写) |
为了读和写,打开一个文本文件 |
出错 |
w+(读写) |
为了读和写,建议一个新的文件 |
建立一个新文件 |
a+(读写) |
打开一个文件,在文件尾进行读写 |
建立一个新文件 |
rb+(读写) |
为了读和写打开一个二进制文件 |
出错 |
wb+(读写) |
为了读和写,新建一个新的二进制文件 |
建立一个新文件 |
ab+(读写) |
打开一个二进制文件,在文件尾进行读和写 |
建立一个新文件 |
6.字符I/O
(1)getchar和putchar函数
int getchar ( void );
int putchar ( int character );
#include<stdio.h>
int main()
{
int c;
puts("Enter text. Include a dot ('.') in a sentence to exit");
do
{
//getchar 是从标准输⼊入获取⼀一个字符
c=getchar();
//putchar 是向标准输出流输出⼀一个字符
putchar (c);
} while (c != '.');
return 0;
}
/*
Enter text. Include a dot ('.') in a sentence to exit
edd
edd
cc
cc
vv
vv
*/
(2)getc和putc函数
int getc ( FILE * stream );
int putc ( int character, FILE * stream );
#include <stdio.h>
int main ()
{
FILE * pFile;
int c;
int n = 0;
pFile=fopen ("myfile.txt","r");
if (pFile==NULL)
perror ("Error opening file");
else
{
do
{
c = getc (pFile);
if (c == '$') n++;
}
while (c != EOF);
fclose (pFile);
printf ("File contains %d$.\n",n);
}
return 0;
}
//File contains 0$.
(3)fgetc和fputc函数
int fgetc ( FILE * stream );
int fputc ( int character, FILE * stream );
getc和fgetc功能一样。 putc和fputc功能一样。
有些编译器将getc和putc实现为宏。
7.未格式化的行I/O
(1)gets和puts
char * gets ( char * str );
int puts ( const char * str );
#include <stdio.h>
int main()
{
char string[256];
printf("Insert your full address: ");
gets(string);
// warning: unsafe (see fgets instead)
printf ("Your address is: %s\n",string);
puts(string);
return 0;
}
/*
Insert your full address: jjsd8294rhv
Your address is: jjsd8294rhv
jjsd8294rhv
*/
(2)fgets和fputs
char * fgets ( char * str, int num, FILE * stream );
int fputs ( const char * str, FILE * stream );
#include <stdio.h>
int main() {
FILE * pFile;
char mystring[100];
pFile = fopen("myfile.txt", "r");
if (pFile == NULL)
perror("Error opening file");
else
{
if (fgets(mystring, 100, pFile) != NULL)
puts(mystring);
fclose(pFile);
}
return 0;
}
//fclose example
8.格式化的行I/O
(1)scanf和printf
int scanf ( const char * format, ... );
int printf ( const char * format, ... );
(2)fscanf和fprintf
int fscanf ( FILE * stream, const char * format, ... );
int fprintf ( FILE * stream, const char * format, ...);
#include <stdio.h>
int main()
{
char str[80];
float f;
FILE * pFile;
pFile = fopen("myfile.txt", "w+");
fprintf(pFile, "%f %s", 3.1416, "PI");
rewind(pFile);
fscanf(pFile, "%f", &f);
fscanf(pFile, "%s", str);
fclose(pFile);
printf("I have read: %f and %s \n", f, str);
return 0;
}
//I have read: 3.141600 and PI
9.二进制I/O
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream ) ;
#include <stdio.h>
int main()
{
FILE * pFile;
char buffer[] = { 'x' , 'y' , 'z' };
pFile = fopen("myfile.bin", "wb");
fwrite(buffer, sizeof(char), sizeof(buffer), pFile);
fclose(pFile);
return 0;
}
10.文件结束判定
在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。
文本文件读取是否结束,判断返回值是否为EOF (fgetc),或者NULL(fgets)
例如: fgetc判断是否为EOF。fgets判断返回值是否为NULL。
二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。
例如: fread判断返回值是否小于实际要读的个数。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int c;
FILE* fp = fopen("test.txt", "r");
if(!fp) {
perror("File opening failed");
return EXIT_FAILURE;
}
//fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF
while ((c = fgetc(fp)) != EOF) // 标准C I/O读取文件循环
{
putchar(c);
}
//判断是什么原因结束的
if (ferror(fp))
puts("I/O error when reading");
else if (feof(fp))
puts("End of file reached successfully");
fclose(fp);
}