C 语言概览

[来源] 达内    [编辑] 达内   [时间]2012-09-12

本片文章主要是介绍C语言的基础只是,用尽量短和精湛的语言把C语言的主要特性介绍给大家,本篇文章不涉及到C语言的原理,关于具体的细节和异常处理等等问题在后续的文章中将会详细介绍

本片文章主要是介绍C语言的基础只是,用尽量短和精湛的语言把C语言的主要特性介绍给大家,本篇文章不涉及到C语言的原理,关于具体的细节和异常处理等等问题在后续的文章中将会详细介绍

第一个程序

  学习程序的最开始都是要写一个hello world,通过这个简单的程序,你将会了解到如何创建一个程序代码块;如何成功编译;如何加载以及如何运行这个程序,和这个程序输出什么东西,输出到哪里等等。学习C也是一样,打印hello world的代码如下:

 #include <stdio.h>    main()    {      printf(
"hello, world\n
");    }

如何运行这段程序同你的系统有关系,在UNIX下,创建一个以.c结尾的文件,如hello.c,然后在命令行中编译:cc hello.c 如果程序没有错误通过编译之后,会生成一个可执行文件a.out,如果在命令行中运行a.out,那么将会看到屏幕上打印出hello,world。

  现在我们来解释一下这个程序。一个C程序,不管他的大小如何,总是包含函数和变量这两个东西。一个函数指定了电脑执行哪些操作,变量用来存储在执行过程中会用到的数值。通常你可以给函数起任何你喜欢的名字,但是main是个例外,这个函数是程序执行的入口,也就是不管你的程序怎么样,总要 一个main函数。main函数中调用其他的函数(可以是你自己写的,也可以是系统库中的)。例如在本例中的第一句,就引入了一个标准的输入/输出哭,这个句话告诉编译器包含这个库的相关信息,关于库的详细信息会在本系列接下来的文章中提到。

  被传递到函数中进行操作的叫做参数,放在函数名称后面的圆括号内,在这个例子中,main函数被定义为没有参数的函数,因此圆括号内是空的。函数的主体部分被大括号括起来,这个例子中只有一句话,printf("hello, world\n");通过函数的名称来调用函数,后面的圆括号内放置需要传递的参数列表。这个函数中传递的参数就是双引号之间的内容。在双引号之间的字符序列被称之为字符串常量,这里这个字符串常量作为函数参数传递,并且被打印出来。

  \n是一个转移字符,表示另起一行,也就是当打印完字符串之后光标移移动到下一行的左边起始的位置。如果不加\n,那么你就会发现屏幕上打印完字符串之后不会另起一行。如果你把程序写成下面的样子:

printf("hello, world
   ");

C语言的编译器会报错。pritf函数不会自动提供换行字符,如果需要分批进行显示的话,则需要多次调用printf函数,最开始的示例我们可以写成下面的样子:

 #include <stdio.h>     main()    {      printf(
"hello, 


");      printf(
"

world"
);      printf("
\n"
);    }

这段程序会显示一模一样的结果,但是需要注意 \n仅仅表示一个字符,也就是换行字符。像这样的\加一个字母通常用来代表不能用键盘敲出来的字符(如换行)或者不可见的字符等。关于这一点在后面的系列文章中会继续深入讲解。

变量和数学表达式

  下面的一个程序使用公式C=(5/9)(F-32)来计算出华氏摄氏度和摄氏度之间的对照表,这个程序包含了较多的内容,注释,声明,变量以及数学表达式和循环,格式化输出等等,请看代码:

   #include <stdio.h>

   /*
 print Fahrenheit-Celsius table        for fahr = 0, 20, ..., 300 */
    main()    {      int


 fahr, celsius;      int
 lower, upper, step;       lower = 0
;      /*
 lower limit of temperature scale 

*/      upper 
= 

300;    /* upper limit 

*/      step 
= 20;      /*
 step size */
       fahr = lower;      
while (fahr <=
 upper) {          celsius 

= 5 * (fahr-32
) / 9;          printf(
"%d\t%d\n
"
, fahr, celsius);          fahr 

= fahr + step;      }    }

  代码中使用/*..*/标注的是注释,注释可以出现在任何空白,tab或者新行出现的位置,注释主要是使得程序更加具有可读性。在C语言中,任何变量在使用之前都需要声明,通常是在函数中开始执行计算之前,写在函数的最前面。声明标注了变量的属性,他可以包含一个变量也可以包含一个变量的列表,例如int fahr, celsius; int lower, upper, step。

  int类型是同浮点型相对应的。int和float类型的范围与机器有关系,16位的整型在-32768到32767之间,通常我们的机器是32位的。一个32位的浮点型通常至少有六位的有效数字,范围通常在10-38到10 38 之间。此外C语言还包括其他的类型,如char表示单个字符;short表示短整形;long表示长整型;double表示双精度的浮点型。此外还有基于这些类型的数组,结构和unions等类型,还有指针以及返回这些类型的函数等。所有这些在后面都会慢慢讲到。

  lower=0;是一个赋值语句,用分号结束。再往下就用到了循环,循环可以重复执行同一个语句,这里使用了while循环,while循环首先判断园括号内的条件,如果为真,那么就执行大括号内的语句,执行完之后再次判断园括号内的语句,如果为真,继续执行,知道判断园括号内的判断为假的时候,则跳出while循环,继续执行下面的语句。如果while循环体的语句只有一句,那么可以不用大括号,如:

while
 (i < j)        i = 2
 * i;

  这里使用了缩进,为了更好的让读代码的人认识到这句话是while循环的内容。虽然C编译器不对格式有严格的要求,但是合适的缩进和空格对于程序的可读性非常关键。通常我们推荐每行只写一个语句,在运算符两边加上空格来表示分割。大括号的位置并没有那么重要,在上面的示例中我们使用了两种风格,选择一种你喜欢的就可以。

  计算表达式5 * (fahr - 32) / 9,是对转换公式的应用,这里先乘以5再除以9,而不是直接乘以 5/9 是因为C语言同其他的很多语言一样,整数的除法会发生截断,也就是小数点的部分会被抛弃,整数5除以整数9的结果是0,再乘以任何数都是0,因此这样计算出来的结果是错误的。

  这个例子中还展示了一些格式化输出的用法。关于printf的详细用户后面会有专门的文章来介绍。这里我们只需要知道在第一个参数中每个%代表了一个需要打印的参数,后面的参数分别按照顺序对应了第一个参数中的%符号,%d表示打印整型变量。编译的时候第一个变量中的%类型必须与后面的参数类型对 ,否则会报错。另外需要说明一点的是,printf函数并不是C语言的一部分,C语言并没有提供输入和输出函数,他只是一个在标准库中定义的函数,方便程序员的调用而已。printf函数的行为是使用ANSI标准定义的,因此,可以在任何遵守了这个标准的编译器和类库中使用。

  上面的程序中显示的数字排列有些乱,我们可以通过制定数据的显示宽度来使得数据显示得更加美观一些,如下printf("%3d %6d\n", fahr, celsius);这样显示出来的数据就会按照指定的宽度进行右边对齐了。上面的程序一个更加严重的问题是数据精度的问题,因为我们使用整数进行计算,因此小数点的部分会被抛弃,我们可以通过改变数据类型为浮点型,从而使得结果更加精确:

  #include <stdio.h>

   /*
 print Fahrenheit-Celsius table        for fahr = 0, 20, ..., 300; floating-point version */
    main()    {      float
 fahr, celsius;      float
 lower, upper, step;       lower = 
0

;      /*
 lower limit of temperatuire scale 

*/      upper 
= 300;    /*
 upper limit */


      step = 20;      
/*

 step size */
       fahr = lower;      
while (fahr <=
 upper) {          celsius 

= (5.0/9.0
) * (fahr-32.0
);          printf(

"%3.0f %6.1f\n
"
, fahr, celsius);          fahr 

= fahr + step;      }    }

  在这次的修改中,之前不可以的5/9整型运算可以使用5.0/9.0自动转换为浮点型进行运算。在C语言中,如果两个运算变量都是整型,那么将按照整型规则进行运算,如果一个整型一个浮点型,那么会先将整型转换为浮点型进行运算。在上面程序的格式化输出中%6.1f表示数值以六个数字宽度打印,有一个小 位,如果是%.2f表示有两个小数位,但是没有指定数值的宽度,下面是一些关于%f的格式化输出方式:%d作为整数打印;%6d六个字符宽度的整数打印;%f作为浮点数打印;%6f六个字符宽度的浮点数打印;%.2f小数点后保留两位小数的浮点数打印;%62f六个字符宽度的浮点数打印,保留两位小数。另外,还有%o表示八进制数打印;%x表示十六进制数打印;%s打印字符串和%%打印%符号本身。

for语句

  完成一个任务可以有不同的写法,下面是用for语句进行循环:

#include <stdio.h>

   /*
 print Fahrenheit-Celsius table */
    main()    {        

int fahr;         
for (fahr = 0
; fahr <= 300; fahr = fahr + 
20

)            printf("
%3d %6.1f\n"
, fahr, (5.0/9.0
)*(fahr-32));    }

  这个程序完成与上一节相同的任务,但是看起来却有很大的不同。这个程序中只声明了一个变量,循环的上下限在for语句中进行声明,输出结果直接使用公式进行表示。最大的特点是可以使用表达式来代替参数或者变量的位置,因为最后printf的第三个参数为浮点类型,因此任何返回浮点类型的表达式都可以出现在这个位置上。

  for循环的圆括号内,第一个参数定义一个表达式,只执行一次,然后第二个语句定义了每次执行之前进行的逻辑判断表达式;第三个语句定义了每次执行完循环之后的操作。for语句看起来更加简洁,但与while之间的选择全凭个人喜好。

符号常量

   在上面的代码中,我们将温度的上限和下限硬编码为了300和20,这样限制了程序的可读性,同时也不不方便程序将来的更改,一个很好的方式是使用符号常量,用一个符号来表示这两个数字,如下面的代码:

   #include <stdio.h>

   #define LOWER  0     /* lower limit of table */
   #define UPPER  300   /* upper limit */
   #define STEP   20    /* step size */

   /*
 print Fahrenheit-Celsius table */
    main()    {        

int fahr;         
for
 (fahr = LOWER; fahr <= UPPER; fahr = fahr +

 STEP)            printf("


%3d %6.1f\n", fahr, (
5.0/9.0)*(fahr-32
));
}

定义符号常量的语法是#define name replacement list,符号名称可以是任何变量名称,以字母开头的任何字母和数字的组合序列,替换文本不仅仅是数字,可以是任何序列的字符。被定义的字符常量并不是变量声明,所以他们不会出现在声明语句当中,通常这些符号常量大写以方便与变量的区分,同时注意语句的后面没有分号。

字符输入和输出

  下面我们来看一下处理字符数据的程序,这里讨论的程序其实是很多程序的原型。字符的输入和输出模型非常简单,不管你的字符从哪里来要到哪里去,都被当作文本流或字符流来处理。一个文本流就是一连串的字符被分成多行,每一行都有零个或者多个字符,后面跟着一个换行字符。标准库负责每个输入或者输出流都遵循这个模型,C语言的程序员通过使用标准库,而不用关心字符在程序外是如何按行显示的。

  标准库中提供了一些函数用来读取和显示字符,其中getchar和putchar是最简单的,getchar从流中读取下一个字符,返回读取的值,如c=getchar();通常这个被读取的字符是通过键盘输入的,函数putchar每次在屏幕上显示一个字符putcharh(c),将c的值按照字符显示

  当了解了这两个函数的时候就可以写出很多复杂的程序,例如下面的例子就是拷贝输入到输出的程序:

   #include <stdio.h>

   /*
 copy input to output; 1st version  */


    main()    {        int
 c;         c 

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

 getchar();        }    }

  任何可以显示的在屏幕上或者键盘输入的字符都被存储为二进制。字符是使用二进制存储,但是任何整数类型也可以用来表示字符。这里我们就是将字符表示为整数。这里为的是能够区分输入的结尾和有效的数据。getchar每次都会返回一个输入的字符,因此需要有一个东西将标识结尾的字符和真正需要 字符区分开来,这里这个用于区分的值是EOF,也叫做end of file。因此需要将c声明为一个足够大的类型能够包括任何getchar返回的值。我们不能用char类型,因为c除了要表示char类型还要表示出EFO,因此这里使用int。EOF是一个在<stdio.h>中定义的整型,他的值是多少不要紧,重要的是他的值不会同任何char重复。通过使用字符常量,我们确认程序里面不会依赖于具体的值。

  这段程序可以被有经验的程序员用更加简短的方法写出来,因为在复制语句中c=getchar();是一个表达式,同时这个表达式是有值的,这个值就是等号左边变量被赋予的值。这意味着这个表达式可以作为某个更加复杂表达式的一部分,在这里,这个赋值语句就可以作为while循环中的一部分:

   #include <stdio.h>

   /*
 copy input to output; 2nd version  */


    main()    {        int
 c;         

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

现在这个程序看起来更加紧凑,这种写法非常常见。但是左边的圆括号不能省略,因为!=的运算等级高于=,如果不加括号的话,那么表达式将会类似于c=(getchar()!=EOF);这样的话c的值将会只有可能为0或者1,程序将不能正确运行。

  下面我们把这个程序更改为对输入的字符进行计数的程序,代码如下:

   #include <stdio.h>

   /*
 count characters in input; 1st version */


    main()    {        long
 nc;         nc 

= 0;        
while (getchar() !=
 EOF)            

++nc;        printf("
%ld\n"
, nc);    }

这个程序中涉及到了一个新的运算符++nc,这个运算符表示每次加一,效果同nc=nc+1一样,但是++nc的写法更加简洁和有效率,与++对应的还有一个--运算符,表示每次减一。运算符++和--可以在变量的前面也可以在变量的后面,在前面和在后面都可以达到变量加一或者减一的效果,但是还是有这很大的区别,这个是后话了。

  这里字符数量的计数变量是一个long类型,long类型通常是至少32位,输出的时候通过使用%ld来表示输出的参数是long类型。如果需要更大的数据范围,可以使用双精度浮点类型double,下面的例子使用了double类型,同时使用for语句来实现循环

   #include <stdio.h>

   /*
 count characters in input; 2nd version */


    main()    {        double
 nc;         

for (nc = 0
; gechar() != EOF; ++nc) 
      ; printf(
"%.0f\n ", nc); }

  printf中的%f可以表示浮点型和双精度浮点型,%.0f格式化了小数点的部分,也就是没有小数点。这里循环的主体是空的,因为所有的任务都在for的圆括号内的声明中完成了,但是C语言规定了for语句必须有循环体,因此这里另起一行值写了一个分号,也就是空语句以满足要求。在进行下一个程序之前,我 首先来看下如果输入中不包含字符的时候,会怎样,while或者for循环会在第一次调用getchar的时候失败,程序输出0,正确的结果。这点非常重要,while或者for循环的一个好处是会在进入循环体之前先进行判断,如果判断为假,那么有可能循环体一次都不被执行。程序应该能够在输入为空的情况下正常运行,while和for循环可以帮助保证程序在边界状态保持正确的输出。

  下面我们再来看一下行计数,之前我们提到过标准库保证了输入的文本流按照行进行显示,每一行终止于一个换行符,可以用计算换行符个数的方法来计算行数:

   #include <stdio.h>

   /* count lines in input 
*/    main()    {        
int c, nl;         nl 
= 0;        
while ((c = getchar()) !=
 EOF)            

if (c == '
\n'
)                ++
nl;        printf(

"%d\n
", nl);    }

这里需要注意的是两点,第一点==是一个比较操作,很多新学C的人都会经常性的将==和=无意间错用,因为复制语句如果执行成功的话将会返回true,因此并不会收到编译错误,非常不容易检查。另外,在单引号中的字符是用一个整型表示的,例如'A'在ASCII中的值是65,因此在内部使用65来表示‘A’。当然在编程的时候使用'A'更好,因为这样更加直观,也不容易与其他的字符集相混淆。之前提到过的转移字符也是同样的原理,因此‘\n’实际上是表示一个字符,他在内部是用整数10来表示,如果是使用的ASCII编码。

  最后我们来看一下UNIX下的用来计算输入中字符数,行数和单词数的程序:

   #include <stdio.h>

   #define IN   1  /* inside a word */
   #define OUT  0  /* outside a word */

   /*
 count lines, words, and characters in input */


    main()    {        int
 c, nl, nw, nc, state;         state = OUT;        nl 
= nw = nc = 0;        


while ((c = getchar()) != EOF) {            
++nc;            if
 (c == 

'\n'
)                ++nl;            

if (c == '
 ' || c == '
\n'
 || c = '\t
')                state 
= OUT;            else
 if (state ==
 OUT) {                state 

= IN;                ++
nw;            }        }        printf(

"%d %d %d\n
", nl, nw, nc);    }

  程序的逻辑在这里不做讨论,但是可以看到程序中的一个复制语句nl=nw=nc=0;根据之前讲到的也可以写成nl=(nw=(nc=0))。值得一提的是,在判断语句(也同样适用于&&)if (c == ' ' || c == '\n' || c = '\t')中,编译器从左到右进行判断,同时一旦能够确认结果将不再进行接下来的判断,例如这里如果C已经满足了是空格,那么将不会再去判断它是否是缩进字符或者是换行字符了。

数组

  为了引入数组的介绍,首先更改函数的要求,要求函数能够显示输入的0-9中每个数字字符的个数,然后空格、缩进以及换行加起来的个数,最后再显示其余字符的个数。首先上代码看一下:

   #include <stdio.h>

   /*
 count digits, white space, others */


    main()    {        int
 c, i, nwhite, nother;        int
 ndigit[10

];         nwhite = nother = 0

;        for
 (i = 0; i < 10
; ++i)            ndigit[i] = 
0

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

 (c >= '0
' && c <= '
9'
)                ++ndigit[c-'
0'


];            else if
 (c == ' '
 || c == '\n
' || c == '
\t'
)                ++
nwhite;            

else

               ++nother;         printf(
"digits =
"

);        for
 (i = 0; i < 10
; ++i)            printf("
 %d"
, ndigit[i]);        printf(
"

, white space = %d, other = %d\n
"

,            nwhite, nother);    }

  程序中定义了一个整型数组 int ndigit[10];C语言中的数组下标总是从0开始,如从ndigit[0]到ndigit[9]。这里在计算数字字符个数的时候同样是将数字字符当作整型来看待,因为0-9的数字字符的整型数值是连续的,因此获取到字符的整型之后直接通过与‘0’相减就可以知道是那个数字了。

函数

  函数可以看作是C语言中的执行子路径,函数的好处是可以封装一些计算和处理,可以直接使用函数达到目的而不用关心他是如何实现的。C语言中可以很方便地定义函数,如下面的例子定义了一个函数pow(x,y),返回结果xy。先看代码:

   #include <stdio.h>

   int power(int
 m, int n);      
/* test power function 
*/     main()     {         
int i;          
for (i = 0
; i < 10; ++
i)             printf(

"%d %d %d\n
", i, power(2
,i), power(-3,i));         
return 0
;     }      /*


 power:  raise base to n-th power; n >= 0 */

    int power(int
 base, int
 n)     {         int
 i,  p;          p = 1
;         for
 (i = 1; i <= n; ++
i)             p 

= p * base;         
return p;     }

  函数可以定义在任何位置,也可以定义在同一个文件内,也可以定义在不同的文件内,当然这个时候可能源程序的编译需要麻烦一点,但是这是操作系统的活。函数定义的第一行int power(int base, int n)声明了函数的原型,包括名字和参数类型,这里的参数名称是本地的,同外界没有任何关系,在函数内部的base,n,i和p等都是函数定义的内部的,外界可以使用同样的名称来定义任何类型而不会发生冲突。

  这里的函数中定义了返回值,函数也可以不返回值,只写一个return语句,但是后面不跟表达式,表示将控制权返回给函数的调用者。这里我们还看到了main函数返回了一个值,同其他函数一样,main也是函数,通常返回0表示程序正常结束,返回非零值表示程序出错或者异常。mian函数的返回值是返回给程序运行的环境,虽然前面的main函数我们一直没有加返回值,但是从这里开始我们要养成为main函数添加返回值的习惯,因为mian需要向运行环境返回自己的运行状态。

参数值调用

  C语言中需要注意的一点是,函数的参数通常是值调用而不是引用传递,也就是当将一个变量i传递给函数power(int base, int n)中的第二个参数的时候,是将i的值复制给函数中的临时变量n,所以如果函数的实现过程如下,在执行完函数的时候值改变了临时变量n的值,但是i的值不变:

   /*
 power:  raise base to n-th power; n >= 0; version 2 */

   int power(int
 base, int
 n)    {        int
 p;         for
 (p = 1; n > 0
; --n)            p = p * base
;        return
 p;    }

  但是有些时候我们需要传递变量的地址,这样在函数内部改变参数的时候,作为参数传递的变量的值也跟着改变,这个时候参数的类型应该设置为变量的地址,具体的细节会有另外的文章进行讨论。需要特别注意的是,如果参数的类型是数组的话,那么默认传递数组中第一个元素的地址给函数,因此当函数内部对参数进行改变的时候,被传递为参数的变量的值也是会改变的。

字符数组

  字符数组可能是C语言中使用最多的数组了,关于字符数组的东西,首先看一个程序,这个程序要求显示出输入中字符数最多的行:

   #include <stdio.h>
   #define MAXLINE 1000   /* maximum input line length */

   int getline(char
 line[], int maxline);    
void copy(char
 to[], char from
[]);     /*
 print the longest input line 
*/

    main()    {        int
 len;            /*
 current line length 

*/

       int max;            /*
 maximum length seen so far */       char line[MAXLINE];    
/* current input line 
*/       char
 longest[MAXLINE]; 

/* longest line saved here 
*/

         max = 0
;        while
 ((len = getline(line, MAXLINE)) > 0
)            

if (len >
 max) {                max 

= len;                copy(longest, line);            }        
if (max > 0
)  /* there was a line 


*/            printf(
"%s"
, longest);        return
 0;    }     
/*
 getline:  read a line into s, return length  

*/

   int getline(char
 s[],int lim)    {        
int c, i;         
for (i=0
; i < lim-1
 && (c=getchar())!=EOF && c!='

\n'
; ++i)            s[i] =
 c;        

if (c == '
\n'
) {            s[i] =
 c;            

++i;        }        s[i] = '
\0'
;        return


 i;    }     /*
 copy:  copy 'from' into 'to'; assume to is big enough */

   void copy(char
 to[], char from
[])    {        int
 i;         i = 0
;        while
 ((to[i] = from[i]) != '
\0'
)            ++i;    }

  首先上面的程序说明了上一节末尾提到的,当用数组作为参数的时候传递的是引用,更加详细的说是数组第一个元素的地址,因此当在函数内改变参数的时候,被当作参数传递的数组也得到了改变。另外需要注意的一点是getline函数中将‘\0’附加到了结尾,用来表示字符串的结尾,这个是C语言的规范,字符串“hello\n”存储在的时候实际上在内存中为hello\n\0。%s用该格式化输出,也同样是根据\0来识别字符串的结尾。

  值得一提的是,这个城市忽略了行中字符数大于程序规定的最大数目的情况。可以在getline中先测试行的长度和最后一个返回的字符,然后确定这个行是不是太长,再执行对应的操作,这里我们忽略这些考虑。

外部变量和作用域

  函数中的变量只有在函数内部才起作用,函数与函数之间不可以互相调用内部声明的变量,变量的声明周期随着函数的结束而结束。这些变量可以称作本地变量,或者自动变量。因为自动变量的生命周期是跟着函数的调用走的,不能从一个函数过渡到另一个函数,因此必须在函数的入口出显示的设置,如果没有设置,那么称之为垃圾变量。

  与自动变量相对应,可以设置一些变量对所有的函数都起作用,这些变量可以被所有函数通过变量名称进行访问和设置。这些变量的声明周期不会随着函数调用结束而结束,当函数对他们的值进行改变了之后,他们依然保持这他们的值,可以被继续使用。这种变量称之为外部变量。外部变量是全局的,必须要在函数外部定义一次,为他们分配存储空间。同时在函数内部使用之前,也要再声明一次,使用extern语句声明他们的类型,下面我们使用全局变量来更改上面的计算最长行的代码,如下:

   #include <stdio.h>

   #define MAXLINE 1000    /* maximum input line size */

   int max;                /*
 maximum length seen so far */   char line[MAXLINE];     /*
 current input line */

   char longest[MAXLINE];  /*
 longest line saved here */
   int getline(void

);    void
 copy(void);     
/*
 print longest input line; specialized version 

*/    main()    {        
int len;        
extern int
 max;        extern
 char longest[];         max 
= 0;        
while ((len = getline()) > 
0

)            if
 (len > max) {                max =
 len;                copy();            }        

if (max > 0
)  /* there was a line 
*/            printf(
"%s
", longest);        


return 0;    }     
/*
 getline:  specialized version 

*/

   int getline(void
)    {        int
 c, i;        extern
 char line[];         
for (i = 0
; i < MAXLINE - 1

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

i)                 line[i] = c;        
if (c == '
\n

') {            line[i] 
= c;            ++
i;        }        line[i] 

= '\0
';        
return i;    }     
/* copy: specialized version 
*/

   void copy(void
)    {        int
 i;        extern
 char
 line[], longest[];         i 

= 0;        
while ((longest[i] = line[i]) != 
'

\0'
)            ++i;    }

  在main函数的外部,getline和copy首先被声明,分配给这两个函数空间,然后还声明了外部全局变量,在函数内部的时候同样要首先声明这些全局变量,使用extern语句,这样才能够使用全局变量。如果全局变量的定义发生在源文件,且放在了使用它的函数前面,那么就可以在函数中省去对全局变量的声明,例如上面的例子中,就可以省去这些声明。实际上,一般的编程习惯都是将所有的外部全局变量定义在文件文件的开头,这样在后面所有的函数中都不需要再次进行extern声明。

  如果你的程序分为好几个文件,文件1,文件2和文件3,同时又需要在文件2和文件3中使用这些外部变量,通常的做法是将这些全局变量和函数放在一个单独的文件内,成为头文件,使用#include语法在源文件的头部进行引用。文件后缀.h通常是头文件的后缀。标准库中的函数通常是声明在头文件中,例如<stdio.h>。

  因为这个版本的getline和copy没有参数,因此在声明函数的时候可以直接声明getline()和copy(),这里加入了void为的是与老式的程序相兼容,这是后话,后面会有文章讲到。同时还要注意定义和声明的不同,定义表示变量创建和内存空间的分配;声明仅仅表示变量的类型,但是并没有分配空间。

  顺便需要提及一下的是,使用extern声明全局变量被认为是一个好习惯,因为这样能够让你在需要使用他们的时候更加清楚哪些是全局变量,哪些是自动变量。但是如果程序过多的依赖于全局变量,那么程序的之间的数据连接将会非常大,不利于程序的阅读和更改,因为有时候这个变量在某个没有被你注意到的地方被更改后会给你的调式带来非常大的障碍。例如上面的第二个版本的计算最长行的程序,因为使用了全局变量,因此getline和copy这两个函数之间的耦合性增加了,这为程序的扩展埋下了隐患。

PS:如何在linux下编写编译和运行你的C程序

  我只能告诉你学会这个非常重要,至于为什么重要,自己去搜索去,这里只是介绍一下最简单的方法

  首先,打开vi,在linux终端输入:vi,进入vi编辑器之后,输入你的c代码,然后按esc键,从编辑状态退出到命令状态,运行 :wq hello.c存档离开,然后运行 gcc hello.c -o hello,编译,最后执行:./hello,就可以看到你的程序被执行了

资源下载