C程序设计语言笔记

时间:2022-12-28 20:40:06

    本文主要针对C语言“圣经”——《C程序设计语言》的细枝末节,聊以记录。

1 导言

*  EOF (End Of File 文件结束)

   在头文件stdio.h中定义,#define EOF -1

#include <stdio.h>

int main()
{
    int c;

    while ((c = getchar()) != EOF)
        putchar();

    return 0;
}

windows环境下,得到EOF的命令是:CTRL+Z

linux环境下,得到EOF的命令是:CTRL+D


*  字符串逆序函数reverse(s)

Version 1.0
void reverse(char s[])
{
    int i,j;
    char temp;

    i = 0;
    while (s[i] != '\0')
        i++;                 /*获取字符串的长度*/
    --i;                       /*i自减1,使得s[i]为字符串结尾字符‘\0’的前一位*/
    if (s[i] == '\n')
        --i;
    j = 0;
    while (j < i)
    {
        temp = s[j];
        s[j] = s[i];
        s[i] = temp;
        --i;
        ++j;
    }

    return ;
}

2 类型、运算符与表达式

*   转义字符序列

    在计算机中,所有数据在存储或运算时都要使用二进制表示。ASCII码使用指定的7位或8位二进制数组合来表示128或256种可能的字符。标准ASCII码也叫基础ASCII码,使用7 位二进制数来表示所有的大写和小写字母,数字0到9、标点符号,以及在美式英语中使用的特殊控制字符。

    所有的ASCII码都可以用“\”加数字(一般是8进制数字)来表示,如'\013'、'\007'。而C中定义了一些字母前加"\"来表示常见的那些不能显示的ASCII字符,如\0,\t,\n等,就称为转义字符。

转义字符

意义

ASCII码值

\a

响铃(BEL)

007

\b

退格(BS),将当前位置移到前一列

008

\f

换页(FF),将当前位置移到下页开头

012

\n

换行(LF),将当前位置移到下一行开头

010

\r

回车(CR),将当前位置移到本行开头

013

\t

水平制表(HT)(跳到下一个TAB位置)

009

\v

垂直制表(VT)

011

\\

代表一个反斜线字符''\'

092

\’

代表一个单引号(撇号)字符

039

\”

代表一个双引号字符

034

\0

空字符(NULL)

000

\ddd

13位八进制数所代表的任意字符

三位八进制

\xhh

12位十六进制所代表的任意字符

二位十六进制

注释:

1> \'与\":

    在双引号内的'不需要\,在单引号内的"不需要\。\'主要用在单引号内,如putchar('\'');输出单引号' 。\"主要用在双引号内,如printf("hello,\"world\"");输出hello,"world"

2>  \ddd与\xhh:

#define VTAB '\013'   /*ASCII垂直制表符*/
#define BELL '\007'    /*ASCII响铃符*/
#define VTAB '\xb'     /*ASCII垂直制表符*/
#define BELL '\x7'      /*ASCII响铃符*/

*   三字符序列

??=   #

??(   [

??<   {

??/   \

??)   ]

??>   }

??’   ^

??!   |

??-   -

    三字符序列是ANSI标准新引入的特征,所有三字符序列都要用相应的单字符替换。


*   单精度、双精度、科学记数法

 

字节

符号位

指数位

尾数位

单精度float

4byte

1

8

23

双精度double

8byte

1

11

52

value of floating-point = significandx base ^ exponent , with sign

 浮点数值尾数 × 底数^指数,(附加正负号)

精度:

   float和double的精度是由尾数的位数来决定的。浮点数在内存中是按科学计数法来存储的,其整数部分始终是一个隐含着的“1”,由于它是不变的,故不能对精度造成影响。

float:2^23 = 8388608,一共七位,这意味着最多能有7位有效数字,但绝对能保证的为6位,也即float的精度为6~7位有效数字;

double:2^52 = 4503599627370496,一共16位,同理,double的精度为15~16位。

float的精度:#define EPSINON = 0.000001 或 #define EPSINON 1e-6

double的精度:#define EPSINON = 0.000000000000001 或 #define EPSINON 1e-15


科学记数法:

   把实数记为a×10^n的形式即是科学记数法,计算机中用e或E表示底数10

    printf("%e\n", 123.4);  输出:1.234000e+002

*   按位求反 ~

    0默认是int类型,占4个字节

    ~0                //十六进制形式为ffffffff

    (unsigned char)~0;  //十六进制形式为ff

    (unsigned shotr)~0; //十六进制形式为ffff

常量类型:

1>十进制表示及输出

       十进制数没有前缀。

       int类型常量:1234                          (没有后缀)       printf("%d",1234);

       long类型常量:123456789l或123456789L          (后缀为l或L)      printf("%ld", 123456789l);

       unsigned int类型常量:1234u或1234U            (后缀为u或U)      printf("%u", 1234u);

       unsigned long类型常量:123456789ul或123456789UL (后缀为ul或UL)    printf("%uld", 123456789ul);

       float类型常量:123.4f或123.4F                (后缀为f或F)      printf("%f", 123.4f);

       double类型常量:123.4                       (没有后缀)       printf("%f", 123.4);

2>八进制表示

    八进制数的前缀为0,例如:0123

    printf("%o", 1234);将十进制数1234以八进制的形式输出

3>十六进制表示

    十六进制数的前缀为0x,例如:0x123

     printf("%x", 1234);将十进制数1234以十六进制的形式输出

类型大小:

   C语言类型的大小随编译器的不同而不同,但char、unsigned char、signed char在所有的编译器中值都为1byte。


 

指针变量大小

16bit编译器

2byte

32bit编译器

4byte

64bit编译器

8byte


*   数组的全0初始化

    int  iarray[5];

    char carray[5];

1>          

    int iarray[5] = {0,0,0,0,0};

    int iarray[5] = {0};

    char carray[5] = {'\0', '\0', '\0', '\0', '\0'};

    char carray[5] = {'\0'};

2>

    int iarray[5];

    char carray[5];

   

    memset(iarray, 0x0, sizeof(iarray));

    memset(carray, 0x0, sizeof(carray));

3>

    int iarray[5] = {0};

    char carray[5] = "";


3 控制流

*   字符串逆序函数reverse(s)

Version 2.0                   /*e.g.1: "abcde" -> "edcba"  e.g.2: "abcde\n" -> "\nedcba"  */
void reverse(char s[])
{
    int c,i,j;
	
	for (i = 0, j = strlen(s) - 1; i < j; i++, j--)
	{
	    c = s[i];
	    s[i] = s[j];
	    s[j] = c;
	}

    return ;
}


4 函数与程序结构

* 递归函数

    C语言中的函数可以递归调用,即函数可以直接或间接调用自身,主要是通过运行时堆栈来支持实现的。

1>递归工作原理:

    递归调用执行过程如下:

    1)开始运行时,先为递归调用建立一个工作栈,其结构包括值形参、局部变量和返回地址。

    2)每次执行递归调用之前,把递归函数的值形参、局部变量的当前值和调用后的返回地址压栈。

    3)每次递归调用结束后,将栈顶元素出栈,使相应的值形参和局部变量恢复为调用前的值,然后转向返回地址指定的位置继续执行。

2>递归所需特性:

    1)存在限制条件,当符合这个条件时递归便不再继续。

    2)每次递归调用之后越来越接近这个限制条件。

3>递归阅读方法:

    阅读递归函数最容易的方法不是纠缠于它的执行过程,而是相信递归函数会顺利完成它的任务。如果你的每个步骤正确无误,你的限制条件设置正确,并且每次调用之后更接近限制条件,递归函数总是能够正确地完成任务。

4>递归适用问题

    递归通常用来解决结构自相似问题。所谓结构自相似是指构成原问题的子问题与原问题在结构上相似,可以用类似的方法解决。具体来说,原问题的解决分为两部分,第一部分是一些特殊情况的处理,第二部分与原问题相似,但规模更小的子问题的处理。

5>递归低效示例:

/*递归计算阶乘*/
 long factorial(int n)
 {
     int ret = 0;
     
     if (n <= 0)
     {
         return 1;
     }
     else
     {
        return n * factorial(n-1);
     }
 }/*递归计算斐波那契数列*/
long fabonacci(int n)
{
    if (n <= 2)
        return 1;

    return fabonacci(n-1) + fabonacci(n-2);
}

    以上两个递归应用并不是递归的良好用法。计算阶乘中递归并没有提供任何优越之处,而使用递归计算斐波那契数列的效率是极其低下的。况且,递归函数调用将涉及一些运行时开销——参数必须压到栈中,为局部变量分配内存空间,寄存器的值必须保存等。当递归函数的每次调用返回时,上述这些操作必须还原,恢复成原来的样子。所以,用递归解决上述两个问题并不是一个好的解决方案。再看代码,函数在递归调用返回之后不再执行任何任务,所以上述函数都是尾部递归。而尾部递归可以很方便地转换成一个简单循环,完成相同的任务。

/*迭代计算阶乘*/
long factorial(int n)
{
    long ret = 1;

    do
    {
        ret = ret * n;
    }
    while (--n > 0);

    return ret;
}

/*迭代计算斐波那契*/
long fabonacci_diedai(int n)
{
    long ret, next, prev;
    ret = next = prev = 1;

    while (n-- > 2)
    {
        prev = next;
        next = ret;
        ret = prev + next;
    }

    return ret;
}

6>递归说明示例:

/*将一个整数转换为字符来打印*/
void printd(int n)
{
    if (n <0)
    {
        putchar('-');
        n = -n;
    }

    if (n / 10)
    {
        printd(n/10);
    }

    putchar(n % 10 + '0');
    return ;
}

7>递归应用示例:

   字符串逆序函数reverse(s)

Version 3.0 —— 递归版本
void rvprocess(char s[], int i, int len)
{
    int c,j;
    j = len - (i + 1);
    if (i < j)
    {
        c = s[i];
        s[i] = s[j];
        s[j] = c;
        rvprocess(s, ++i, len);
    }
    return ;
}

void reverse(char s[])
{
    rvprocess(s, 0, strlen(s));
    return ;
}

* 可变参数列表

    可变参数列表是通过宏来实现的,这些宏定义于stdarg.h头文件。主要包括:

类型:va_list

宏:va_start、va_arg、va_end

/*计算指定数量的值的平均值*/
#include <stdarg.h>
float average(int n_values, ...)
{
    va_list var_arg;
    int count = 0;
    float sum = 0;

    /*准备访问可变参数*/
    va_start(var_arg, n_values);

    /*添加取自可变参数列表的值*/
    for (count = 0; count < n_values; count += 1)
    {
        sum += va_arg(var_arg, int);
    }

    /*完成处理可变参数*/
    va_end(var_arg);

    return sum / n_values;
}

va_start用于初始化,把va_list类型的var_arg变量设置为指向可变参数部分的第一个参数。

va_arg用于访问参数。

va_end用于恢复堆栈。


5 指针与数组

*   指针与数组的区别

相同点:

    作为函数的形参,数组与指针是等价的。

    int func(char *str);

    int func(char str[]);

不同点:

1) 

    int a[5];

    int *b;

    声明数组时,编译器为数组保留内存空间,然后创建数组名,它的值是一个常量,指向这段空间的起始位置。(因为a是常量,所以a++是非法的)

    声明指针变量时,编译器只为指针本身保留内存空间,它并不为任何整型值分配内存空间。

2)

    char array[] = "helloworld";    /*字符数组,内容可以修改*/

    char *pointer = "helloworld";   /*字符串常量,内容不能修改*/

*   常用库函数的实现

int strlen(char *s)
{
    char *p = s;
    while (*p != '\0')
        p++;
    return p - s;
}
void strcpy(char *s, char *t)
{
    while (*s++ = *t++)
        ;
}
int strcmp(char *s, char *t)
{
    for (; *s == *t; s++, t++)
        if (*s == '\0')
            return 0;
    return *s - *t;
}

6 结构

* 位段

    位域的声明和结构类似,但它的成员是一个或多个位的字段。这些不同长度的字段实际上存储于一个或多个整型变量中。

struct bitfield
{
    unsigned int  :0;   /*无名位段*/
    unsigned int a:1;
    unsigned int b:2;
    unsigned int c:3;
    unsigned int d:4;
}bitfield;

注意:

1>  位段成员必须是整型int。

    把位段声明为int类型,它究竟被解释为有符号还是无符号是由编译器决定的。所以,最好还是用signed或unsigned显示声明。

2>  注重可移植性的程序应该避免使用位段。

    许多编译器把位段成员的长度限制在一个整型值的长度之内,所以一个能运行于32位整数的机器上的位段声明可能在16位整数的机器上无法运行。   

3>  字段可以不命名,无名字段(只有一个冒号和宽度,如unsigned int :1;)其填充作用。0宽度可以用来强制在下一个字边界上对齐。

struct bitfield1
{
    unsigned int a:1;
    unsigned int b:2;
    unsigned int c:3;
    unsigned int d:4;
}bitfield1;
sizeof(bitfield1)的值为4
struct bitfield2
{
    unsigned int a:1;
    unsigned int b:1;
    unsigned int c:1;
    unsigned int  :30;/*填充作用*/
}bitfield2;
sizeof(bitfield2)的值为8
struct bitfield3
{
    unsigned int a:1;
    unsigned int b:2;
    unsigned int  :0; /*强制在下一个字边界对齐*/
    unsigned int d:1;
}bitfield3;
sizeof(bitfield3)的值为8


7 输入与输出

流:所有的I/O操作只是简单地从程序移进或移出字节的操作,这种字节流被称为流(stream)。

 

定义

文本流

由多行字符构成的字符序列,而每行字符则由0个或多个字符组成,行末是一个换行符。文本行的长度,标准规定至少允许254个字符。MS-DOS系统中,行末以一个回车符和一个换行符结尾。而UNIX系统中,行末以一个换行符结尾。

二进制流

二进制流中的字节将完全根据程序编写它们的形式写入到文件或设备中,且根据它们从文件或设备读取的形式读入到程序中,即二进制流未作任何改变。

文本文件

    基于字符编码(如ASCII编码、UNICODE编码)的文件,若以ASCII编码,则以8bit为一个值,即定长编码。

二进制文件

    基于值编码的文件,即把内存中的数据(二进制形式)原样存于磁盘。而多少bit为一个值是自定义的,即变长编码。

    在计算机中,所有数据在存储或运算时都要使用二进制表示。所以在物理存储上,文件文件与二进制文件是没有区别的。它们的区别仅在逻辑上,即编码层次上。

windows环境:

    windows环境下,文本文件与二进制文件读写的差别仅体现在回车换行符的处理上。文本文件写时,每遇到一个换行符'\n',就将其换成回车换行符'\r''\n',再写入文件;文本文件读时,每遇到一个回车换行符'\r''\n'时,就将其换成换行符'\n',再送到读缓冲区。而二进制文件的读写不存在任何转换,直接将写缓冲区的数据写入文件或直接将文件读到读缓冲区。

UNIX环境:

    UNIX环境下,文本文件与二进制文件的处理不存在任何差别。

执行字符、文本文件和二进制文件的函数

数据类型

输入

输出

描述

字符

getchar()

putchar()

终端读写单个字符

文本文件

int getc(FILE *fp)

fscanf

fgets

int putc(int c,FILE *fp)

fprintf

fputs

从文件返回下一个字符

文件格式化输入输出

从文件读取下一个输入行(包含换行符)

二进制文件

fread

fwrite

读写二进制数据


*   输入流、输出流、错误流

    stdin  即标准输入流,是一个宏,定义在头文件stdio.h中。      #define stdin _File[0]

    stdout 即标准输出流,是一个宏,定义在头文件stdio.h中。      #define stdout _File[1]

    stderr 即标准错误数据流,是一个宏,定义在头文件stdio.h中。  #define stderr _File[2]

    stdin、stdout、stderr都是FILE *类型的对象,但都是常量。

#include <stdio.h>
#include <stdlib.h>

void filecopy(FILE *ifp, FILE *ofp);

/*cat函数:连接多个文件.版本2 */
main(int argc, char *argv[])
{
    FILE *fp;
    char *prog = argv[0];   /*记下程序名,供错误处理用*/

    /*如果没有命令行参数,则复制标准输入*/
    if (argc == 1)
    {
        filecopy(stdin, stdout);
    }
    else
    {
        while (--argc > 0)
        {
            if ((fp = fopen(*++argv, "r")) == NULL)
            {
                fprintf(stderr, "%s: can't open %s\n", prog, *argv);
                exit(1);
            }
            else
            {
                filecopy(fp, stdout);
                fclose(fp);
            }
        }
    }

    if (ferror(stdout))
    {
        fprintf(stderr, "%s: error writing stdout\n", prog);
        exit(2);
    }
    
    exit(0);
}

/*filecopy函数:将文件ifp复制到文件ofp*/
void filecopy(FILE *ifp, FILE *ofp)
{
    int c;

    while ((c = getc(ifp)) != EOF)
        putc(c, ofp);

    return ;
}


fopen

原型:FILE *fopen(const char *filename, const char *mode);

功能:以指定的模式打开文件,并把这个文件和一个流关联起来。

返回:成功返回指向控制流的对象的指针,失败返回空指针。

示例:

FILE *fp;

fp = fopen(“program.c”, “r”);

freopen

原型:FILE *freopen(const char *filename, const char *mode, FILE *stream);

功能:以指定的模式打开名字为filename的文件,并把它和stream指向的流关联起来。函数freopen首先尝试关闭和指定的流关联的任意文件。如果不能成功关闭,就忽略这一点。然后清空流的错误指示符和文件结束符。

返回:成功指向stream流的对象的指针,失败返回空指针。

fflush

原型:int fflush(FILE *stream);

功能:如果stream指向一个输出流或者修改流,在这个流中,最近的操作不是输入,函数fflush会导致这个流所有未写入的数据传递给宿主环境,然后写入文件;否则,函数的行为是未定义的。

    如果stream是空指针,函数fflush对所有的流执行上述定义的清空行为。

返回:如果发生写入错误,返回EOF;成功返回0

fclose

原型:int fclose(FILE *stream);

功能:使stream指向的流被清空,并且和流相关的文件被关闭。

返回:成功返回0,失败返回EOF

fgets

原型:char *fgets(char *s, int n, FILE *stream);

功能:行输入

fgetsstream指向的流中读取字符,读取字符的数目最多比n指定的数目少1,然后将字符写入到s指向的数组中。读入换行符或者文件结束之后,不再读取其他的字符。最后一个字符写入数组后立即写入一个空字符。

返回:成功返回s,失败返回空指针

fputs

原型:int fputs(const char *s, FILE *stream);

功能:行输出

fputss指向的字符串写入stream指向的流中,不写入结束的空字符。

返回:如果发生了写错误返回EOF;否则返回一个非负值

gets

原型:char *gets(char *s);

功能:getsstdin指向的输入流中读取若干个字符,并将其保存到s指向的数组中,直到遇到文件结束或者读取一个换行符。所有的换行符都被丢弃,在最后一个字符读到数组中之后立即写入一个空字符。

返回: 成功返回s,失败返回空指针。

puts

原型:int puts(const char *s)

功能:putss指向的字符串写到stdout指向的流中,并且在输出最后添加一个换行符。不写入结束的空字符。

返回:如果发生写错误返回EOF,否则返回一个非负值。

fgetc

原型:int fgetc(FILE *stream);

功能:fgetcstream指向的输入流中读取下一个字符,并且把它由unsigned char类型转换为int类型,并且流的相关的文件定位符向前移动一位。

返回:返回stream指向的输入流中的下一个字符。

     如果输入流在文件结束出,则设在文件结束符,返回EOF

     如果发生了读错误,则设置流的错误指示符,返回EOF

fputc

原型:int fputc(int c, FILE *stream);

功能:把c指定的字符(转换为unsigned char类型)写到stream指向的输出流中相关的文件定位符指定的位置处,并把文件定位符向前移动适当的位置。

返回:成功返回写入的字符;失败返回EOF

ungetc

原型:int ungetc(int c, FILE *stream);

功能:把c指定的字符(转换为unsigned char类型)退回到stream指向的输入流

中。

返回:成功返回转换后的回退字符,失败返回EOF

getc

原型:int getc(FILE *stream);

功能:等价于fgetc,是宏

返回:返回stream指向的输入流中的下一个字符。

     如果到达文件尾或出现错误,返回EOF

putc

原型:int putc(int c, FILE *fp);

功能:等价于fputc,是宏

返回:成功返回写入的字符,失败返回EOF

getchar

原型:int getchar(void);

功能:等价于用参数stdin调用的getc #define getchar() getc(stdin)

返回:成功返回stdin指向的输入流的下一个字符。失败返回EOF

putchar

原型:int putchar(int c);

功能:等价于用stdout作为第二个参数调用的putc#define putchar(c) putc((c), stdout)

返回:成功返回写入的字符,失败返回EOF

fprintf

原型:int fprintf(FILE *stream, const char *format, …);

功能:格式化输出到stream指定的流

返回:成功返回输出的字符数目,失败返回负值

fscanf

原型:int fscanf(FILE *stream, const char *format, …);

功能:格式化输入到stream指定的流

返回:成返回输入的字符数目,失败返回EOF

sprintf

原型:int sprintf(char *string, char *format, argc1,arg2, …);

功能:按照format格式格式化参数序列arg1,arg2,…,并将输出结果存放到数组string中,而不是标准输出。

返回:返回写到数组中的字符的数目,不包括终止的空字符。

sscanf

原型:int sscanf(char *string, char *format, arg1, arg2, …);

功能:按照format格式扫描字符串string(而不是标准输入),并把结果分别保存到arg1,arg2,…这些参数上。

返回:成功返回输入项赋值的项目,失败返回EOF

fread

原型:size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

功能:从stream指向的流中读取最多nmemb个元素到ptr指向的数组中,nmemb的大小是由size指定的。

返回:成功返回读取的元素的数目。

     读错误或文件结束,返回值可能比nmemb小。

     如果sizenmemb为零,则fread返回0

fwrite

原型:size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

功能:从ptr指向的数组中读取最多nmemb个元素并将其写到stream指向的流中,nmemb的大小是由size指定的。

返回:成功返回写入的元素的数目。

     写错误,返回值可能比nmemb小。

fseek

原型:int fseek(FILE *stream, long int offset, int whence);

功能:为stream指向的流设置文件定位符。

     

返回:错误返回非零值

ftell

原型:long int ftell(FILE *stream);

功能:获得stream指向的流的文件定位符的当前值。

返回:成功返回文件定位符的当前值,失败返回-1

feof

原型:int feof(FILE *stream);

功能:测试stream指向的流的文件结束符

返回:当且仅当stream流设置了文件结束符时,feof返回一个非零值。

ferror

原型:int ferror(FILE *stream);

功能:测试stream指向的流的错误指示符

返回:当前仅当stream流设置了错误指示符,ferror返回非零值

perror

原型:void perror(const char *s);

功能:把整数表达式errno中的错误编号转换为一条错误信息。

返回:void


标准I/O提供三种类型的缓冲:

1> 全缓冲

2> 行缓冲

3> 不带缓冲


*   变长参数

int printf(const char *fmt, ...)
{
    int ans;
    va_list ap;
    va_start(ap, fmt);
    ans = _Printf(&prout, stdout, fmt, ap);/*_Printf是一个公共函数*/
    va_end();
    return (ans);
}

8 UNIX系统接口

    UNIX操作系统通过一系列的系统调用提供服务,这些系统调用实际上是操作系统内的函数,它们可以被用户程序调用。通过这组系统调用接口,应用程序可以访问系统硬件和各种操作系统资源。

    操作系统API通常都以C库的方式提供。C库提供了POSIX的绝大部分API,同时,内核提供的每个系统调用在C库中都具有相应的封装函数。系统调用与其C库封装函数的名称常常相同,比如,read系统调用在C库中的封装那书即为read函数。

         C程序设计语言笔记

    系统调用最终必须具有明确的操作。用户应用程序通过系统调用进入内核后,会执行各个系统调用对应的内核函数,即系统调用服务例程,比如系统调用getpid的服务例程是内核函数sys_getpid。

    系统调用表sys_call_table存储了所有系统调用的服务例程的函数地址。系统调用在内核中的执行就可以转化为从该表获取对应的服务例程并执行的过程。该过程中需要用到系统调用号。每个系统调用都拥有一个独一无二的系统调用号,用户应用通过它,而不是系统调用的名称,来指明要执行哪个系统调用。

         C程序设计语言笔记

         C程序设计语言笔记


*   示例
#include "syscalls.h"

main()
{
    char buf[BUFSIZ];
    int n;

    while ((n = read(0, buf, BUFSIZ)) > 0)
        write(1, buf, n);
    
    return 0;
}
    先看看运行结果:
[root@]# cat program.c
#include "syscalls.h"
main()
{
    char buf[BUFSIZ];
    int n;
    while ((n = read(0, buf, BUFSIZ)) > 0)
        write(1, buf, n);
    return 0;
}
[root@]# gcc program.c
program.c:1:22: 错误:syscalls.h:没有那个文件或目录
program.c: In function ‘main’:
program.c:5: 错误:‘BUFSIZ’ 未声明 (在此函数内第一次使用)
program.c:5: 错误:(即使在一个函数内多次出现,每个未声明的标识符在其
program.c:5: 错误:所在的函数内只报告一次。)
[root@]#
    运行结果显示:头文件syscalls.h不存在,宏BUFSIZ未定义。
    接下来,对头文件"syscalls.h"进行解析:
    本书写道:我们已经将系统调用的函数原型集中放在一个头文件syscalls.h中,因此,本章中的程序都将包含该头文件。不过,该文件的名字不是标准的。参数BUFSIZ也已经在syscalls.h头文件中定义。
    从上述这句话可以判断,syscalls.h头文件是自定义的,包含了系统调用的原型、BUFSIZ的宏定义等。
    查看read系统调用函数原型和BUFSIZ宏定义在哪些头文件中:
[root@]# cd /usr/include/
[root@include]# grep 'size_t read' *.h
unistd.h:extern ssize_t read (int __fd, void *__buf, size_t __nbytes) __wur;
unistd.h:extern ssize_t readlink (__const char *__restrict __path,
unistd.h:extern ssize_t readlinkat (int __fd, __const char *__restrict __path,
[root@include]# grep 'size_t write' *.h
unistd.h:extern ssize_t write (int __fd, __const void *__buf, size_t __n) __wur;
[root@include]# grep BUFSIZ *.h
_G_config.h:#define _G_BUFSIZ 8192
ldap.h:#define  LDAP_OPT_X_SASL_MAXBUFSIZE              0x6109
libdevmapper.h:#define DM_FORMAT_DEV_BUFSIZE    13      /* Minimum bufsize to handle worst case. */
libio.h:#define _IO_BUFSIZ _G_BUFSIZ
stdio.h:#ifndef BUFSIZ
stdio.h:# define BUFSIZ _IO_BUFSIZ
stdio.h:   Else make it use buffer BUF, of size BUFSIZ.  */
[root@include]#
    从以上的查询结果看,系统调用函数原型在头文件unistd.h中,宏BUFSIZ在头文件stdio.h中定义。故而,我们可以自定义syscalls.h的内容如下:
[root@]# cat syscalls.h
#include <unistd.h>
#include <stdio.h>
[root@]#