好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

【02C语言】09流程控制

【02C语言】09流程控制

【零基础学习iOS开发】【02-C语言】09-流程控制

 

回到顶部

 

前言

 

1.默认的运行流程

 

默认情况下,程序的运行流程是这样的:运行程序后,系统会按书写顺序执行程序中的每一行代码。比如下面的程序

 

  1  #include <stdio.h>
  2  
  3   int   main()
   4   {
   5      
  6      printf( "  Hello-1\n  "  );
   7      printf( "  Hello-2\n  "  );
   8      printf( "  Hello-3\n  "  );
   9      
 10       return   0  ;
  11  }

 

程序运行后,会按顺序执行第6、7、8行语句,于是输出结果为:

 

 

 

2.其他运行流程

 

但 很多时候,我们并不想要按照默认的运行流程去走,比如想在某个条件成立的情况下才执行某一段代码,否则不执行。比如微信的这个界面:

 

 

如果用户点击了注册按钮,我们就执行“跳转到注册界面”的代码;如果用户点击了登录按钮,我们就执行“跳转到登录界面”的代码。如果用户没做出任何操作,就不执行前面所说的两段代码。要想实现这种功能,那就要学会如何去控制程序的运行流程。

 

 

3.流程结构

 

为了方便我们控制程序的运行流程,C语言提供3种流程结构,不同的流程结构可以实现不同的运行流程。这3种流程结构分别是:

  顺序结构:默认的流程结构。按照书写顺序执行每一条语句。 选择结构:对给定的条件进行判断,再根据判断结果来决定执行哪一段代码。 循环结构:在给定条件成立的情况下,反复执行某一段代码。

 

下面是这3种结构的流程图,大致预览一下即可

 

 

 

回到顶部

 

一、顺序结构

 

顺序结构是3种结构中最简单的,也是默认的流程结构:程序中的语句是按照书写顺序执行的。在 文章开头 开始列出的代码段,就是顺序结构,这里就不多介绍了。

 

 

回到顶部

 

二、选择结构1-if语句

 

C语言中选择结构的实现方式有两种:if语句和switch语句。先来看下if语句的使用,而if语句的形式是有好多种的。

 

1.形式1

 

先来看看if语句最简单的形式

  1> 简介

 

 1   if   ( 条件 )
  2   {
  3       语句1;
  4       语句2;
  5       ....
  6  }

 

如果if右边小括号()中的条件成立,也就是为“真”时,就会执行第2~6行大括号{}中的语句;如果条件为假,就不执行大括号{}中的语句。这里的if是关键字。

  2> 举例

 

 1   int  a =  7  ;
  2  
 3   if   ( a )
  4   {
  5      printf( "  条件a成立\n  "  );
  6      printf( "  a的值为真  "  );
  7  }

 

C语言规定所有非0值都为“真”,而a的值是7,因此第3行的条件是成立的,接着就会执行第5、6行代码。输出结果如下:

 

 1   条件a成立
  2  a的值为真

 

如果将a的值改为0,那么第3行的条件就不成立,就不会执行第5、6行代码

  3> 省略大括号{}

 

如果if后面大括号{}中只有一行代码时,可以省略大括号。形式如下:

 

 if   ( 条件 )
    语句1; 

 

注意:如果条件成立,只会执行if后面的第1条语句;如果条件不成立,就不会执行if后面的第1条语句。

 

 1   int  a =  7  ;
  2  
 3   if  ( a >  9   )
  4      printf( "  aaa  "  );
  5      printf( "  bbb  " );

 

因为第3行的a>9是不成立的,所以不会执行第4行代码。而第5行代码跟if语句是没有任何练习的,因此,第5行代码照常执行。于是会看到屏幕上只输出: 。

 

由于第5行代码跟if语句是没有任何联系的,所以一般会把代码写成下面这样:

 

 1   int  a =  7  ;
  2  
 3   if  ( a >  9   )
  4      printf( "  aaa  "  );
  5  printf( "  bbb  " );

 

为了保证代码的可读性,不建议省略大括号!!!

  4> 语句嵌套

 

if语句内部是可以嵌套其他if语句的,如下面的例子

 

  1   int  a =  7  ;
   2  
  3   if  ( a >  0   )
   4   {
   5      printf( "  a的值大于0\n  "  );
   6      
  7       if  ( a< 9   )
   8       {
   9          printf( "  a的值小于9  "  );
  10       }
  11  }

 

第3行的a>0是成立的,因此会按顺序执行第4~11大括号中的代码。执行到第7行的时候,a<9也是成立的,因此会执行第9行代码。输出结果:

 

 1   a的值大于0
  2  a的值小于9

  5> 使用注意1

 

有些人习惯写完一行代码就在后面加个分号";",于是写if语句的时候,他们可能会这样写:

 

 1   int  a =  6  ;
  2   if  ( a> 8   );
  3   {
  4      printf( "  a大于8  "  );
  5  }

 

如果第2行尾部的分号,其实一个分号也是一条语句,这个叫做“空语句”。第2行的a>8不成立,所以不会执行后面的“空语句”。而后面的大括号{}跟if语句是没有联系的,因此会正常执行,于是会看到输出:

 

a大于8

 

所以要非常小心, 千万不要在if的小括号后面添加分号 。

 

第3~5行的内容是一个独立的“代码块”:

 

 1   {
  2      printf( "  a大于8  "  );
  3  }

  6> 使用注意2

 

下面的写法从语法的角度看是对的:

 

 int  a =  10  ;

  if  (a =  0  ) {
    printf(  "  条件成立  "  );
}   else   {
    printf(  "  条件不成立  "  );
} 

 

上述代码是完全合理的,编译器不会报错,只是个警告而已。因 为a为0,所以为"假", 输出结果是: " 条件不成立 "

 

这里隐藏着 很大的陷阱在:

 

假设你本来是想判断a是否为0,那么本应该写if (a == 0),若你误写成了if (a = 0),那将是一件非常可怕的事情,因为编译器又不报错,这样的BUG就难找了。因此, 像a==0这样的表达式,最好写成0==a,若你误写成0=a,编译器会直接报错的 。

 

 //   不推荐 
 if  (a ==  0  ) {
}

  //   推荐 
 if  ( 0  ==  a) {
} 

  7> 使用注意3

 

在C语言中,可以不保存关系运算的结果。因此,下面的写法是合法的:

 

 1   int  a =  10  ;
  2  a >  10  ;
  3  a ==  0 ;

 

这里又是一个陷阱,假设你的本意是想给 a赋值为0,那么本应该写a = 0; ,若你误写成a == 0; ,那将又是一个非常难找的BUG,因为编译器根本不会报错。在1993年的时候,这个BUG差点让一桩价值2000万美元的硬件产品生意告吹,因为如果这个BUG不解决,这个产品就没办法正常使用

 

 

2.形式2

 

if还可以跟关键字else一起使用

  1> 简介

 

 1   if   ( 条件 )
  2   {
  3       语句1;
  4   }
  5   else 
 6   {
  7       语句2;
  8  }

 

如果条件成立,就会执行if后面大括号{}中的语句;如果条件不成立,就会执行else后面大括号{}中的语句。总之,两个大括号中一定会有1个被执行,而且只能执行的1个。

 

为了减少代码行数,你也可以写成下面的格式:

 

 1   if   ( 条件 ) {
  2       语句1;
  3  }  else   {
  4       语句2;
  5  }

 

当然,也可以省略大括号,写成下面的格式:

 

 1   if   ( 条件 )
  2       语句1;
  3   else 
 4      语句2;

 

如果条件成立,就执行if后面的第1条语句;如果条件不成立,就执行else后面的第1条语句。但还是不建议省略大括号{}。

  2> 举例

 

 1   int  a =  10  ;
  2   if  ( a== 0   ) {
  3      printf( "  a等于0  "  );
  4  }  else   {
  5      printf( "  a不等于0  "  );
  6  }

 

第2行的a==0不成立,所以会执行第5行代码,输出结果:

 

a不等于0

 

 

3.形式3

 

if和else还有一种比较复杂的用法

  1> 简介

 

  1   if   ( 条件1 )
   2   {
   3       语句1;
   4   }
   5   else   if   ( 条件2 )
   6   {
   7       语句2;
   8   }
   9   else   if   ( 条件3 )
  10   {
  11       语句3;
  12   }
  13   ...
  14   else 
 15   {
  16       其他语句;
  17  }

  如果条件1成立,就执行条件1后面大括号{}中的内容:第2~4行 如果条件1不成立,条件2成立,就执行条件2后面大括号{}中的内容:第6~8行 如果条件1、条件2都不成立,条件3成立,就执行条件3后面大括号{}中的内容:第10~12行 第13行的...表示可以有无限个else if 如果所有的条件都不成立,就会执行else后面大括号{}中的内容:第15~17行

 

注意:这么多大括号中,只有1个大括号内的代码会被执行。跟之前一样,所有的大括号都可以省略,但是不建议省略。必要的时候,最后面的else那一段(第14~17行)是可以省略的。

  2> 举例

 

 1   int  a =  10  ;
  2   if  ( a== 0   ) {
  3      printf( "  a等于0  "  );
  4  }  else   if ( a> 0   ) {
  5      printf( "  a大于0  "  );
  6  }  else   {
  7      printf( "  a小于0  "  );
  8  }

 

第2行中的a==0不成立,接着会检查第4行。第4行的a>0成立,因此会执行第5行代码。输出结果:

 

a大于0

 

如果a的值是负数,那么第2、4行的条件都不成立,于是就会执行第7行代码。

 

 

回到顶部

 

三、选择结构2-switch语句

 

1.形式

 

先来看看switch语句的使用形式:

 

  1   switch  (整型表达式)
   2   {
   3       case   数值1:
   4           语句1;
   5           break  ;
   6       case   数值2:
   7           语句2;
   8           break  ;
   9       ... ...
  10       case   数值n:
  11           语句n;
  12           break  ;
  13       default   :
  14          语句n+ 1  ;
  15           break  ;
  16  }

  当整型表达式的值等于“数值1”时,就会执行“语句1”,后面的break表示退出整个switch语句,也就是直接跳到第16行代码; 当整形表达式的值等于“数值2”时,就会执行“语句2”;后面的以此类推。如果在数值1~数值n中,没有一个值等于整型表达式的值,那么就会执行default中的语句n+1。 由于所有的case后面都有个break,因此执行完任意一个case中的语句后,都会直接退出switch语句

 

 

2.举例

 

  1   int  a =  10  ;
   2  
  3   switch   (a) {
   4       case   0  :
   5          printf( "  这是一个0  "  );
   6           break  ;
   7       case   5  :
   8          printf( "  这是一个5  "  );
   9           break  ;
  10       case   10  :
  11          printf( "  这是一个10  "  );
  12           break  ;
  13       default  :
  14          printf( "  什么也不是  "  );
  15           break  ;
  16  }

 

因为a的值刚好等于第10行case后面的10,所以会执行第11行代码,输出结果:

 

这是一个10

 

 

3.break

 

break关键字的作用是退出整个switch语句。默认的格式中,每个case后面都有个break,因此执行完case中的语句后,就会退出switch语句。

 

1> 如果某个case后面没有break,意味着执行完这个case中的语句后,会按顺序执行后面所有case和default中的语句,直到遇到break为止

 

  1   int  a =  0  ;
   2  
  3   switch   (a) {
   4       case   0  :
   5          printf( "  这是一个0\n  "  );
   6       case   5  :
   7          printf( "  这是一个5\n  "  );
   8       case   10  :
   9          printf( "  这是一个10\n  "  );
  10           break  ;
  11       default  :
  12          printf( "  什么也不是\n  "  );
  13           break  ;
  14  }

  由于变量a的值等于第4行case后面的0,因此肯定会执行第5行代码。 由于case 0中没有break语句,就不会退出switch语句,继续往下执行代码。 由于a的值已经等于第4行case的值,接着不会再判断a的值是否等于其他case的值了,直接按顺序执行第7、9行代码。在第10行有个break,接着就会退出switch语句。 输出结果为:

 

 1   这是一个0
  2   这是一个5
  3  这是一个10

 

如果把a的值改为5,输出结果为:

 

 1   这是一个5
  2  这是一个10

 

 

2> 在某些时候,我们确实没有必要在每一个case后面添加break。下面举一个例子:判断分数的优良中差等级(100分满分)。

 

  1   int  score =  77  ;
   2  
  3   switch  (score/ 10  ) {
   4       case   10  :
   5       case   9  :
   6          printf( "  优秀  "  );
   7           break  ;
   8          
  9       case   8  :
  10          printf( "  良好  "  );
  11           break  ;
  12          
 13       case   7  :
  14       case   6  :
  15          printf( "  中等  "  );
  16           break  ;
  17          
 18       default  :
  19          printf( "  差劲  "  );
  20           break  ;
  21  }

  当score的范围是90~100,score/10的值为10或9时,就会执行第6行代码,然后退出switch语句; 当score的范围是80~89,score/10的值为8时,就会执行第10行代码,然后退出switch语句; 当score的范围是60~79,score/10的值为7或6时,就会执行第15行代码,然后退出switch语句; 当score的范围并不是60~100,score/10的值并不在6~10范围内时,就会执行第19行代码,然后退出switch语句; score的值是77,所以score/10的值是7,输出结果: 中等

 

 

4.在case中定义变量

 

有时候,我们可能会想在case中定义一些变量,这个时候,就必须用大括号{}括住case中的所有语句。

 

  1   int  a =  10  ;
   2   int  b =  4  ;
   3  
  4   char  op =  '  -  '  ;
   5  
  6   switch   (op)
   7   {
   8       case   '  +  '  :
   9       {
  10           int  sum = a +  b;
  11          printf( "  a+b=%d\n  "  , sum);
  12           break  ;
  13       }
  14          
 15       case   '  -  '  :
  16       {
  17           int  minus = a -  b;
  18          printf( "  a-b=%d\n  "  , minus);
  19           break  ;
  20       }
  21          
 22       default  :
  23          printf( "  不能识别的符号  "  );
  24           break  ;
  25  }

 

在第10、17分别定义两个变量。输出结果:

 

a-b= 6 

 

 

回到顶部

 

四、循环结构1-while循环

 

假如要你在屏幕上重复输出10次Hello World,你会怎么做?简单,把下面的代码拷贝10份就行了。

 

 1  printf( "  Hello World\n  " );

 

没错,把上次代码写10遍,确实能实现功能。但是这样的代码太垃圾了,有很多的重复的代码,这样会使得代码非常地臃肿,复用率低。因此,不建议这么做。

 

下次遇到像上面那样重复执行某个操作时,首先要想到的应该是循环结构。所谓循环,就是重复执行某一个操作,C语言中有多种方式可以实现循环结构。先来看看while循环。

 

1.形式

 

 1   while   ( 条件 )
  2   {
  3       语句1;
  4       语句2;
  5       ....
  6  }

  如果条件成立,就会执行循环体中的语句(“循环体”就是while后面大括号{}中的内容)。然后再次判断条件,重复上述过程,直到条件不成立就结束while循环 while循环的特点: 如果while中的条件一开始就不成立,那么 循环体中的语句 永远不会被执行

 

可以省略大括号{},但是只会影响到while后面的第一条语句。不建议省略大括号。

 

 1   while   ( 条件 )
  2      语句1;

 

 

2.举例

 

在屏幕上重复输出10次Hello World,每输出一次的换行。

 

 1   while  ( count <  10   )
  2   {
  3      printf( "  Hello World\n  "  );
  4      
 5      count++ ;
  6  }

 

如果省略第6行的count++,count就一直是0,那么count<10一直都是成立的,这个while循环将会陷入“死循环”,一直在重复执行第4行代码。

 

 

3.注意

 

如果写成下面这样,也会让程序进入“死循环”

 

 1   int  count =  0  ;
  2  
 3   while  ( count <  10   );
  4   {
  5      printf( "  Hello World\n  "  );
  6      
 7      count++ ;
  8  }

  注意第3行,while后面不小心加了个分号; ,一个分号表示一条空语句。 可以看出:while循环只会影响到第3行的空语句,而第4~8行的代码块是不受while循环影响的 由于count是0,那么count<10一直都是成立的,程序将会一直重复执行第3行的空语句,陷入死循环。

 

 

回到顶部

 

五、循环结构2-do while循环

 

形式如下:

 

 1   do   {
  2       语句1;
  3       语句2;
  4       ...
  5  }  while  (条件);

  注意第5行,后面是加上一个分号;的 当执行到do-while循环时,首先会执行一遍循环体中的语句(“循环体”就是do后面大括号{}中的内容)。接着判断while中的条件,如果条件成立,就执行循环体中的语句。然后再次判断条件,重复上述过程,直到条件不成立就结束while循环 do-while循环的特点: 不管while中的条件是否成立, 循环体中的语句 至少会被执行一遍 其实do while循环的用法跟while循环是差不多的,这里就不举例子了。

 

 

回到顶部

 

六、循环结构3-for循环

 

1.形式

 

 for循环是所有循环结构中最复杂的。

 

 1   for   (语句1 ;  条件 ;  语句2) {
  2       语句3;
  3       语句4;
  4       ...
  5  }

  for循环开始时,会先执行语句1,而且 在整个循环过程中只执行一次语句1 接着判断条件,如果条件成立,就会执行循环体中的语句(“循环体”就是for后面大括号{}中的内容) 循环体执行完毕后,接下来会执行语句2,然后再次判断条件,重复上述过程,直到条件不成立就结束for循环

 

 

2.举例

 

 1   for  ( int  i =  0 ; i< 5 ; i++ )
  2   {
  3      printf( "  %d    "  , i);
  4  }

 

输出结果为:

 

 0    1    2    3    4   

 

需要注意的是:变量i的作用域是第1~4行。一旦离开了这个for循环,变量i就失效了。

 

 

3.补充

 

如果for循环的初始化语句和循环一次后执行的语句是由多条语句组成的,就用逗号,隔开

 

 1   for  ( int  x =  0 , y = 0 ; x< 3 ; x++, y+= 2  )
  2   {
  3      printf( "  x=%d, y=%d \n  "  , x, y);
  4  }

 

输出结果:

 

x= 0 , y= 0   
x = 1 , y= 2   
x = 2 , y= 4  

 

 

回到顶部

 

七、break和continue

 

接下来,介绍两个比较重要的语句:break和continue。

 

1.break

 

前面在switch语句中已经用到了break,它的作用是跳出switch语句。它也可以用在循环结构中,这时候它的作用是跳出整个循环语句。

  1> 举例

 

这里以for循环为例子,break也可以用在while循环、do-while循环中。

 

 1   for  ( int  i =  0 ; i< 5 ; i++ ) {
  2      printf( "  i=%d \n  "  , i);
  3      
 4       if  (i> 2  ) {
  5           break  ;
  6       }
  7  }

 

上面代码的意思是当i>2时,就跳出整个for循环,也就是结束for循环,所以输出结果是:

 

i= 0   
i = 1   
i = 2   
i = 3  

 

  2> for循环嵌套

 

先来看一个for循环嵌套的例子,嵌套的意思就是:for循环内部又一个for循环

 

 1   for  ( int  x =  0 ; x< 2 ; x++ ) {
  2       for  ( int  y =  0 ; y< 2 ; y++ ) {
  3          printf( "  x=%d, y=%d \n  "  , x, y);
  4       }
  5  }

 

输出结果是:

 

 1  x= 0 , y= 0  
 2  x= 0 , y= 1  
 3  x= 1 , y= 0  
 4  x= 1 , y= 1  

 

 

这个时候如果在for循环中加入一个break,那么这个break究竟是跳出里面还是外面的for循环呢?

 

 1   for  ( int  x =  0 ; x< 2 ; x++ ) {
  2       for  ( int  y =  0 ; y< 2 ; y++ ) {
  3          printf( "  x=%d, y=%d \n  "  , x, y);
  4          
 5           break  ;
  6       }
  7  }

 

注意第5行的break,这个break的作用是跳出里面的for循环,并非外面的for循环。所以输出结果是:

 

x= 0 , y= 0   
x = 1 , y= 0  

 

 

如果改变一下break的位置

 

 1   for  ( int  x =  0 ; x< 2 ; x++ ) {
  2       for  ( int  y =  0 ; y< 2 ; y++ ) {
  3          printf( "  x=%d, y=%d \n  "  , x, y);
  4       }
  5      
 6       break  ;
  7  }

 

注意第6行的break,这个break的作用是跳出外面的for循环,并非里面的for循环。所以输出结果是:

 

x= 0 , y= 0   
x = 0 , y= 1  

 

 

规律已经很明显了:break只会影响它所在的那个for循环

 

 

2.continue

 

continue只能使用在循环结构中,它的作用是跳过这一次循环,直接进入下一次循环。

 

这里以for循环为例子,continue也可以用在while循环、do-while循环中。

 

 1   for  ( int  x =  0 ; x< 10 ; x++ ) {
  2       if  (x% 2 == 0  ) {
  3           continue  ;
  4       }
  5      
 6      printf( "  x=%d \n  "  , x);
  7  }

 

注意第2行,当x%2==0,也就是当x是2的倍数时,就跳过这次循环,不执行第6行语句,直接进入下一次循环。输出结果:

 

 1  x= 1  
 2  x= 3  
 3  x= 5  
 4  x= 7  
 5  x= 9  

 

跟break一样,continue只会影响它所在的那个for循环

 

 

 

 

 

 

 

分类:  零基础学习iOS开发

标签:  ios ,  iphoe应用开发 ,  ipad应用开发 ,  objective-c

编写简单的c运行库(三)

 在 编写简单的c运行库(二) 中主要实现了对有关文件操作函数的实现,接下来主要实现有关字符串的函数,如itoa,strcmp,strcpy,strlen函数,这些函数并没有用到系统调用,所以也就不用向实现文件操作的函数那样使用内嵌汇编,这些函数的定义都放在string.h中。实现了字符串函数之后,就大概实现了一个小型的c运行库,虽然很简略,但对于理解c库函数运行原理、所用的关键技术有了比较深刻的认识。最后用这个小的c运行库来编译运行一个简单的测试程序,用以测试我们的库能否正常的工作。

1 字符串函数

  字符串函数中主要是实现itoa函数有点难度,其它的都还比较的简单,所以这里主要讲下itoa函数的实现。

  1   char  *itoa( int  n,  char  *str,  int   radix)
   2   {
   3       char  digit[] =  "  0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ  "  ;
   4       char  *ptr = str, * base  ;
   5  
  6       if  (!str || radix <  2  || radix >  36  )
   7           return   str;
   8       if  (radix !=  10  && n <  0  )
   9           return   str;
  10       if  (! n)
  11       {
  12          *ptr++ =  '  0  '  ;
  13          *ptr =  0  ;
  14       }
  15       if  (radix ==  10  && n <  0  )
  16       {
  17          *ptr++ =  '  -  '  ;
  18          n = - n;
  19       }
  20       base  =  ptr;
  21       while   (n)
  22       {
  23          *ptr++ = digit[n %  radix];    
  24          n /=  radix;
  25       }
  26      *ptr =  0  ;
  27       for  (-- ptr;  base  < ptr;  base  ++, ptr -- )
  28       {
  29          *ptr ^= * base  ;
  30          * base  ^= * ptr;
  31          *ptr ^= * base  ;
  32       }
  33       return   str;
  34  }

  itoa函数功能是把一个整数转换为字符串,我们在编写前面vfprintf函数的时候其实就已经用到过,它在c编程中也是经常用到的。从上面的代码中可以看到itoa支持2-36进制的整数转换为字符串。在这个函数中只认为十进制的数才能带有"-"号,所以在代码的第15行判断该整数是否满足是十进制的负数,如果满足在数的最前面加个"-"号,其它进制的负数默认不带"-"号。21-25行根据数的进制把数的低位到高位一个一个的分离并保存到ptr字符数组中,但是输出字符串中高位应该放在前面,所以27-32行主要是对ptr字符数组做一个倒置操作。

2 测试库

  接下来用一个简单的程序来测试编写的运行库,测试程序如下:

  1  #include  "  minicrt.h  " 
  2  
  3  
  4   extern   char  ** environ;
   5  
  6   int  main (  int  argc,  char  * argv[] )
   7   {
   8       int   i;
   9      FILE * fp;
  10       char  **v = malloc(argc *  sizeof ( char  * ));
  11       for  (i =  0 ; i < argc; i ++ )
  12       {
  13          v[i] = malloc(strlen(argv[i]) +  1  );
  14           strcpy(v[i], argv[i]);
  15       }
  16  
 17      fp = fopen( "  text.txt  " ,  "  w  "  );
  18       for  (i =  0 ; i < argc; i ++ )
  19       {
  20           int  len =  strlen(v[i]);    
  21          printf( "  %d %s\n  "  , len, v[i]);
  22          fwrite(&len,  1 ,  sizeof ( int  ), fp);
  23          fwrite(v[i],  1  , len, fp);
  24       }
  25       fclose(fp);
  26  
 27      fp = fopen( "  text.txt  " ,  "  r  "  );
  28       for  (i =  0 ; i < argc; i ++ )
  29       {
  30           int   len;
  31           char  * buf;
  32  
 33          fread(&len,  1 ,  sizeof ( int  ), fp);
  34          buf = malloc(len +  1  );
  35          fread(buf,  1  , len, fp);
  36          buf[len] =  0  ;
  37          printf( "  %d %s\n  "  , len, buf);
  38           free(buf);
  39           free(v[i]);
  40       }
  41       free(v);
  42       fclose(fp);
  43  
 44       while  (* environ)
  45          printf( "  %s\n  " , *environ ++ );    
  46  
 47       return   0  ;
  48  }

  所有库中函数的声明、类型的声明都放在了头文件minicrt.h中,没有像标准的库那样对每类库函数的声明放在单独的头文件中,如文件操作放在stdio.h中。测试程序中基本上都用到了我们前面编写过的函数,所以对于测试我们的库是最适合不过了。

  要使用库,首先我们先要用前面编写的代码文件建立一个库,怎么建立呢?我们可以用linux下的ar命令来建立一个静态库,具体的可以见下面的命令。之所以用静态库,因为这样可以省略很多不必要的工作,我们的目的仅仅为了了解库的原理和关键技术。而动态库还有很多其它方面的知识,包括装载、运行时链接等,不过了解这些工作原理正是下面要做的工作了。

cc -c -g -fno-builtin -nostdlib -fno-stack-protector entry.c malloc.c stdio.c  string .c test.c
ar -rs minicrt.a malloc.o stdio.o string.o

  “-fno-builtin”指关闭GCC内置函数功能,默认情况下GCC会把strlen、strcmp等这些常用函数展开成它内部的实现。

  "-nostdlib"不使用任何来自Glibc、GCC的库文件和启动文件,它包含了-nostartfiles这个参数。

  "-fno-stack-protector"是指关闭堆栈保护功能,最近版本的GCC会在vfprintf这样的变长参数中插入堆栈保护函数,如果不关闭,使用自己写的库时会报“__stack_chk_fail”函数未定义错误。

  其中entry.c是在 编写简单的c运行库(一) 中说的入口函数实现,malloc.c中是有关堆的初始化和申请释放堆的函数,stdio.c包含 编写简单的c运行库(二) 中有关文件操作的函数,string.c包含本文中说的字符串函数的实现,test.c中则是我们的测试代码。

  链接测试程序时不能使用c的标准库,要用自己写的minicrt.a库,具体命令为:

ld - static  -g -e MiniCrtEntry entry.o test.o minicrt.a -o test

  "-e"参数是指定入口函数,我们使用自己实现的入口函数MiniCrtEntry。

  运行的结果如下:

cc@localhostmimicrt]$./ test
  6  ./ test
  6  ./ test
XDG_SESSION_ID = 248  
HOSTNAME = localhost.localdomain
TERM = xterm
SHELL =/bin/ bash
HISTSIZE = 1000  
SSH_CLIENT = 192.168 . 1.161   62555   22  
SSH_TTY =/dev/pts/ 0  
USER = cc
LD_LIBRARY_PATH =/usr/local/lib
.
.
.

  正如测试程序所希望的那样,程序打印出了命令行参数的总字节数,命令行参数,环境变量。可以说这个库基本上是正确的。

3 总结

  编写简单的c运行库到这里基本就结束了,虽然只是实现了一个很小的库,不过麻雀虽小,五脏俱全,虽然没有真实c标准库那么的高效、完全,但至少这个库实现了c标准库的核心部分,有了这个小型库,对于扩展它的其它功能还是比较容易的。实现这个库还是比较的简单,因为有《程序员自我修养》这本书作为参考,不过这边书中所实现的linux中c++运行库的全局构造和析构机制,我在linux中按它说的实现,却发现结果和它说的不太一样,test.o中的.ctors节并没有合并到crtbegin.o和crtend.o的.ctors节之间,而是合并到crtbegin.o和crtend.o的.ctors节的下面去了,至于为什么会这样,我依然没有找到这个答案,希望有人按《程序员自我修养》实现过linux下的c++库的人帮忙解惑或者讨论下。

附件:minicrt

 

 

分类:  c ,  linux编程

作者: Leo_wl

    

出处: http://HdhCmsTestcnblogs测试数据/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于【02C语言】09流程控制的详细内容...

  阅读:97次