C语言开发总结(十六)

 

sizeof() 与strlen()

 sizeof 是C/C++的一种运算符,用来返回数据类型占用内存的字节数。返回类型是 size_t 其实就是 typedef unsigned int size_t;

     它有三种形式:

     1) sizeof( object ); // sizeof( 对象 );

 2) sizeof( type_name ); // sizeof( 类型 );

 3) sizeof object; // sizeof 对象;

   比如 int a;

     sizeof(a) ,sizeof(int )  ,sizeof  a 都是合法的。一般在程序中使用sizeof() 较多。 由于sizeof() 是返回数据类型的内存占用字节,所以sizeof(a) 其实等价于其数值类型int 

    的占用内存。同样,sizeof(3) sizeof(5) 的返回值一样都为int类型的内存字节。

    sizeof也可以对一个函数调用求值,其结果是函数返回类型的大小,函数并不会被调用,我们来看一个完整的例子:

  char foo()

  {

  printf("foo() has been called.\n");

  return 'a';

  }

  int main()

  {

  size_t sz = sizeof( foo() ); // foo() 的返回值类型为char,所以sz = sizeof(char ),foo()并不会被调用

  printf("sizeof( foo() ) = %d\n", sz);

  }

       C99标准规定,函数、不能确定类型的表达式以及位域(bit-field)成员不能被计算sizeof值,即下面这些写法都是错误的:

  sizeof( foo );// error

  void foo2() { } //函数返回类型为空指针,不确定

  sizeof( foo2() );// error

       C语言基本数据类型 ,int   long int  float double 等,这些数据类型的sizeof()返回值与特定的系统平台相关,比如在16位的平台下,sizeof(int) 为2,在32位平台下,它位4.

      所以不确定当前的数据类型占用字节时,可以用sizeof 算一下。

       .指针变量的sizeof

  学过数据结构的你应该知道指针是一个很重要的概念,它记录了另一个对象的地址。既然是来存放地址的,那么它当然等于计算机内部地址总线的宽度。所以在32位计算机中,一个指针变量的返回值必定是4(注意结果是以字节为单位),可以预计,在将来的64位系统中指针变量的sizeof结果为8。

  char* pc = "abc";

  int* pi;

  string* ps;

  char** ppc = &pc;

  void (*pf)();//函数指针

  sizeof( pc ); // 结果为4

  sizeof( pi ); // 结果为4

  sizeof( ps ); // 结果为4

  sizeof( ppc ); // 结果为4

  sizeof( pf );// 结果为4

  指针变量的sizeof值与指针所指的对象没有任何关系,正是由于所有的指针变量所占内存大小相等,所以MFC消息处理函数使用两个参数WPARAM、LPARAM就能传递各种复杂的消息结构(使用指向结构体的指针)。

       数组的sizeof

      数组是内存中连续的一块单元,它的每一个元素所在的内存单元地址是相邻的连续的,数组名代表这块内存的首地址,这样才可以通过首地址调用所有元素。

      此时,sizeof  数组元素所在连续内存单元存放的数据类型的累加和。

      char  a[ ]="hello";

      sizeof(a) =6; 末尾还有一个空字符。

     int  a1[5] ;

      sizeof(a1)= 4*5=20;

      有时候可以通过sizeof确定数组元素个数=sizeof(a1)/sizeof(int);

      函数指针类型的形参的sizeof

     void fun(int arr[4])

    {

       int len=sizeof(arr);

     }

   有人会认为len=16;但sizeof(arr)=4;

   为什么会这样呢?

   在C语言函数的传参中,有传值和传地址两种方式。

   而上面函数的形参的定义形式编译器默认是传的数组的地址,既数组的首地址

  void main()

  {

      int a[4[={1,2,3,4};

     fun(a[4]);    此时传给函数形参的只是数组a的地址,与元素的值无关,所以也可以写为 fun(a) 或fun(a[0]);

  }

   当编译器读到fun(int arr[4])这一行时,并不关心【】的内容,它默认将arr[] 当做一个指针,用来保存传给它的数组的地址。所以 [ ]是个摆设,int arr[ ] 和int  arr[4] ,int arr[10], int * arr都是等价的。

  因此,arr是指针变量,sizeof (arr) 也就等于4了。

   结构体的sizeof

   首先得了解一下结构体的内存分配原则

     不得不提一个术语 :内存对齐

    所谓内存对齐是相对于CPU的访问内存说的。

    struct A

{

    char a;

    int  b;

     char c;

}

  这个结构体sizeof(a)的返回值为12,而不是三种类型的空间相加 1+4+1=6;

   为什么呢?

    这就关系到CPU从内存中读取的方式了。

    从开始的8位机,8080时代,数据总线的带宽为8位,所以,CPU每次从内存中最多可以读取8位(1个字节)

    以后的8086,数据总线扩到了16位,地址总线也到了20位,其寻址能力达到1M,由于CPU内部数据一次最多

处理16位,对于20位宽的地址,用十六位是表示不完全的,因此,后来Inter 工程师发明的内存地址=段地址*16+偏移地址   通过此方法,就可以表示出全部的1M 内存空间单元的地址。通常扩展带宽都是 8位,16位,32位   ,64位等,都是2的某次方,以为最初的计算机的指令是1个字节8位,从而奠定了,后来计算机的数据单元都是以字节位基础的,而每一次性能扩展都应该是8的倍数,这很好理解。但偏偏8086弄了个不伦不类的20位地址总线,当然它也想一次弄32位的,迫于当时CPU的技术有限(8086有40根引线),如果是16位的地址总线,寻址能力最大为64KB,显然有点不能满足当时内存以及其他外设的地址分配,因而Inter 把地址总线带宽扩到了20位,这样,从过把一个16位二进制数移四位达到20位,因为是移位,所以它的低四位都是0,也就相当于把一个16进制的数移一位,以移位后的20位数(二进制)作为段地址,再加上一个偏移地址,便得到一个20位的内存地址,关于CPU寻址,可以通过计算机组成原理了解一下,有详细的解释。这样的话就把内存分成了好几个段,每个段是连续的,为了在寻址前在CPU内部保存每个段的段基址,8086在CPU里设计了几个段寄存器,用来存储每个段的段首地址,当进行寻址时,CPU取出段寄存器里的段基址再加上偏移地址(16位),通过在加法累计器里的累加,成为20位的实际内存地址,然后传给地址总线,而8086也成为80X86的鼻祖。

     而偏移地址是相对于每段的段基址来说的, 因为偏移地址是16位,每段地址范围为0到2的16次方64KB,

 加上段基址就为 段基址+0~段基址+0xffff.

     可以看到每个段的段地址为16的倍数(16位移到20位相当于乘以16),而每段的地址范围也为16的倍数,8的倍数, 而数据总线每次从内村传给CPU数据时,8的倍数,8088数据总线为8位(每次传一个字节),8086数据总线为16位,每次传2个字节。可以看到每段如果从段首址开始读数据的话,不论是每次8个(字节),16位(字),32位(双字),等都能恰好把一个段取完,不会浪费空间,假设段首址位0,段尾址位15,内存单元地址为0~15,如果从0开始读(每次8位),刚好读两次就读完且不会浪费空间,如果从1开始读,1~8的地址为一个字节,从9开始读的时候,9~15,为7位,不够8位,因此就会被浪费,所以CPU一般是偶数位读的,这样一来,读字节,字,双字,四字时,刚好占用空间位8的整数,不会剩余空间,也称为自然边界,如果读的地址跨越了自然边界,就会是CPU用两个总线周期读数,为了让CPU的效率不降低,便产生了内存对齐的方法。

     来看一下对内存对齐的解释,以结构体为例。

     为什么会有C++内存对齐

以下内容节选自《Intel Architecture 32 Manual》。

为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。

一个字或双字操作数跨越了4字节边界,或者一个四字操作数跨越了8字节边界,被认为是未对齐的,从而需要两次总线周期来访问内存。一个字起始地址是奇数但却没有跨越字边界被认为是对齐的,能够在一个总线周期中被访问。

二、C++内存对齐规则

每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。

对齐规则:

1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照 #pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。

2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。

3、结合1、2推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。

4.各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。

5.各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节自动填充。

6.同时为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节。

三、pragma pack 宏

VC中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;否则必须为n的倍数。下面举例说明其用法。

    
    
    
    
  1. #pragma pack(push) //保存对齐状态  
  2. #pragma pack(4)//设定为4字节对齐  
  3. struct test  
  4. {  
  5. char m1;  
  6. double m4;  
  7. int m3;  
  8. };  
  9. #pragma pack(pop)//恢复对齐状态 

以上结构的大小为16,下面分析其存储情况,首先为m1分配空间,其偏移量为0,满足我们自己设定的对齐方式(4字节对齐),m1占用1个字节。接着开始为m4分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于n),m4占用8个字节。接着为m3分配空间,这时其偏移量为12,满足为4的倍数,m3占用4个字节。这时已经为所有成员变量分配了空间,共分配了16个字节,满足为n的倍数。如果把上面的#pragma pack(4)改为#pragma pack(16),那么我们可以得到结构的大小为24。

    再来看一下strlen()

     strlen 是一个函数, 原型为  extern unsigned int strlen(char *s);在Visual C++ 6.0中,原型为size_t strlen(const char *string);  

     它的运行机制是从内存的某个位置(可以是字符串开头,中间某个位置,甚至是某个不确定的内存区域)开始扫描,直到碰到第一个字符串结束符'\0'为止,然后返回计数器值。   

  如果你只定义字符串没有给它赋初值,这个结果是不定的,它会从aa首地址一直找下去,直到遇到'\0'停止。

   strlen 与sizeof的区别

 

char * fun(char *str)
{
memset(str, 0, sizeof(str));  //用strlen和sizeof()有什么区别
...
return str;
}
int main(int argc, char* argv[])
{
char *a, b[400];
a = fun(b);
}
strlen()和sizeof()出来的长度是不一样的,但结果好像都一样,memset()有那么聪明吗?

sizeof 这个是在汇编里面就存在的一个指令,可以直接返回你要判断的变量战局的内存的大?gt;>?br>这个是在编译器就确定的,一个要注意的问题是,看下面的代码
char* str=new char[100]
sizeof(str)
这个可是会返回4哦,可不是你要的400
而 char str[100]
sizeof(str)是会返回400的。
但是,无论如何strlen()都是一个函数,它返回的是一个字符串的长度,也就是说从你给的字符串
首地址开始,一直到'\0'为止的这一段长度。
memset真的没有那么智能,但是它确实高效。

strlen 返回的是实际串长
sizeof 如果*str是指针则返回 char *的大小 如果是用数组声明 返回的是空间的大小
char *sz = "abcde";
char sz1[20] = "abcde";
cout<<sizeof(sz)<<endl;
cout<<strlen(sz)<<endl;
cout<<sizeof(sz1)<<endl;
cout<<strlen(sz1)<<endl;
输出:
4
5
20
5

memset(str, 0, sizeof(str));  //用strlen和sizeof()有什么区别?
答:用sizeof的话,只给str所指向的内存块连续4个字节清0;
    用strlen的话,是给str所指向的字符串全部清0;
     
    sizeof(str)返回str这个指针变量所占的内存字节数目;
    strlen(str) 返回str所指向的字符串的长度

sizeof()应该是编译时处理的。strlen是函数,不一样的

char * fun(char *str)
{
memset(str, 0, sizeof(str));  //sizeof(str))求得是指针str的大小,指针占空间是一
                                      //样的4个字节;str指向的是数组的首地址,这样相当于
                                     //将数组前四个元素至为‘\0’,用printf("%s")的话,遇
                                    //到第一个'\0',即退出。
                                   //如果用memset(str, 0, strlen(str));就得看运气了,
                                  //str指向数组b[400]首地址,b[400]没有显示初始化,
                                 //strlen是遇到'\0'退出,有可能b[0]就是'\0'
                                //strlen(str)的结果就为0,用printf("%s")就打印不出来了;
                                //strlen(str)也有可能是其他值,得看'\0'在b[400]的哪个位置了
return str;
}
int main(int argc, char* argv[])
{
char *a, b[400];
a = fun(b);
}

char * fun(char *str)
{
memset(str, 0, sizeof(str)); //sizeof(str))求得是指针str的大小,指针占空间是一
                                  //样的4个字节;str指向的是数组的首地址,这样相当于
                                 //将数组前四个元素至为‘\0’,用printf("%s")的话,遇
                                //到第一个'\0',即退出。
                               //如果用memset(str, 0, strlen(str));就得看运气了,
                              //str指向数组b[400]首地址,b[400]没有显示初始化,
                             //strlen是遇到'\0'退出,有可能b[0]就是'\0'
                            //strlen(str)的结果就为0,用printf("%s")就打印不出来了;
                           //strlen(str)也有可能是其他值,得看'\0'在b[400]的哪个位置了
return str;
}
int main(int argc, char* argv[])
{
char *a, b[400];
a = fun(b);
}

sizeof(str))求得是指针str的大小


strlen---------测“字符个数”(包括:不可见字符,如:空格等)
sizeof---------测“BYTE个数”

sizeof返回对象所占用的字节大小.
strlen返回字符个数.
在使用sizeof时,有一个很特别的情况,就是数组名到指针蜕变,
char Array[3] = {'0'};
sizeof(Array) == 3;
char *p = Array;
sizeof(p) == 1;

在传递一个数组名到一个函数中时,它会完全退化为一个指针

C 语言 switch 语句

 在有多个选择分支条件的情况下,用if 语句难免会繁琐且效率不高,此时便是switch语言的用武之地。

     int  i=1;

     switch(i)

{

    case 0:

             printf("0");

    case 1:
            printf("1");

    case 2:

           printf("2");

    default:

           printf("default");

}

     有些人会认为运行的结果是 2, 但是实际情况是  12default.

     这也是初学者常犯的错误。按照常理,switch是选择分支,即满足那个case 执行那个case 块的语句,但是C语言中的switch有它的个性。来看看MSDN 的定义。

     You can use the break statement  to end processing of a particular case within the switch statement and to branch to the end of the switch statement. Without break,the program continues to the next case, executing the statements until a break or the end of the statement is reached. In some situations, this continuation may be desirable。

     意思是,用break 语句 终止 当前的case ,直接到达switch 的末尾,执行完当前case 若有break则跳出switch。

     如果没有break, 程序继续流向下一个case ,直到遇到break,或者 到达了switch末尾。

     所以如果没有break语句,找到匹配条件的case 后,会从这里继续执行下面的case 直到最后一个case 或default。

     明白了这个我们在上面的代码应该这样写。

        int  i=1;

     switch(i)

{

    case 0:

             printf("0");

            break;

    case 1:
            printf("1");

           break;

    case 2:

           printf("2");

          break;

    default:

           printf("default");

          break;

}

     这样就会输出  1;

     一定要养成良好的习惯,给每一个case 加上break,以免造成疏忽的错误。

     但是C语言的这种switch 特性也有它的优点。

     比如设置每一个月的天数 Days 可以这样用switch

     switch(month)

 {

     case 1:

     case 3:

      case 5:

      case 7:

     case 8:
     case 10:

      case 12   :

                  Days=31;

                  break;

     case 2:

                //根据是否是闰年判断。

               break;

      case 4:

       case 6:

      case 9:

        case 11:

                     Days=30;

                    break;

     default:    break;

 

}

    这样便不用给每个case 都写语句了,case 1 3 5  7 为空,会自动到下一个CASE ,知道case 12 ,执行完跳出。

   关于 defaut的位置。

    这个问题相信有一大部分人没有考虑过,因为常见的程序中default 都在最后,这要造成了一些人认为default 在最后。

    MSDN 的说法:

    There can be at most one default statement. The default statement need not come at the end; it can appear anywhere in the body of the switch statement. In fact it is often more efficient if it appears at the beginning of the switch statement. A case or default label can only appear inside a switch statement. 

   最多有一个default 语句在一个switch 中,default 声明不一定在末尾,它可以出现在switch的任何地方那个,实际上如果default出现在switch 的开头会更有效率。

   所以

   int i=2;

   switch(i)

   {

       default :break;

        case 0:

                  printf("0");

                 break;

        case 1:

                 printf("0");

                 break;

        case 2:

               printf(“2”);

              break;

   }    也是合理的。

  

     关于switch()参数值的类型

  

     参数值类型必须是这几种类型之一:int,long,short ,byte,char.

    switch为什么只能用int,short,byte,char,long,因为switch 只能使用 int 类型或者是可以转换为 int类型的参数(char,char 和 int 通过ascii转换)。

     C语言没有规定一定是int 类型,但是要求是完整的,因此只能是上面几种了。

      关于switch 中case 的个数。

     标准的C编译器至少允许一个switch中最多257个case 标签。这是为了允许switch满足一个字符8位的所有情况,总共是2的8次方256,再加上EOF.

     而Miscrosoft C 编译器的switch中case 是这样定义的。

      MSDN :

     Microsoft C does not limit the number of case values in aswitch statement. The number is limited only by the available memory. ANSI C requires at least 257 case labels be allowed in aswitch statement.

     它不限制case 的个数。 only limited by available memory.

    关于switch 中声明变量初始化的问题。

     int i=2;

     switch(i)

     {

        case 1:

                    break;

        case 2:

                   int sum=5;

                   sum=sum*i;

                   printf("%d",sum);

                  break;

         default:

                 break;

    }

   运行的结果是什么呢。有人可能认为是  10,然而这样写编译器会报错,在VC6,下 为  error C2361: initialization of 'sum' is skipped by 'default' label

  意思是sum变量的初始化被default 跳过 ,没有被初始化。

   来看MSDN 的说明:

   

   Note   Declarations can appear at the head of the compound statement forming theswitch body, but initializations included in the declarations are not performed.Theswitch statement transfers control directly to an executable statement within the body, bypassing the lines that contain initializations.

   声明可以出现在switch 块中复合语句的头部(这是C语言的规则,不允许在语句的中间声明变量,C++可以 ,当然在VC6下可以的通过的,因为它是C/C++的混合编译器),但是包含在声明中的初始化不会被执行。

    当写成 int sum;   不报错,运行结果为  -858993460,随机的,因为没有初始化。

    解决的方法为给case 下的语句加上{} 号,便可初始化。

        int i=2;

     switch(i)

     {

        case 1:

                    break;

        case 2:

                 {

                   int sum=5;

                   sum=sum*i;

                  printf("%d",sum);

                  break;

                }

         default:

                 break;

    }

     运行结果为10,已初始化。



C语言文件操作之fgets()

 来说一说fgets(..)函数。

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

    参数:

         s: 字符型指针,指向存储读入数据的缓冲区的地址。

         n: 从流中读入n-1个字符

         stream : 指向读取的流。

   返回值:

          1. 当n<=0 时返回NULL,即空指针。

          2. 当n=1 时,返回空串"".

          3. 如果读入成功,则返回缓冲区的地址。

          4. 如果读入错误或遇到文件结尾(EOF),则返回NULL.

          看看这个函数的官方说明:

                       /*** 
                    *char *fgets(string, count, stream) - input string from a stream 
                    * 
                    *Purpose:  
                    * get a string, up to count-1 chars or '\n', whichever comes first, 
                    * append '\0' and put the whole thing into string. the '\n' IS included 
                    * in the string. if count<=1 no input is requested. if EOF is found 
                    * immediately, return NULL. if EOF found after chars read, let EOF 
                    * finish the string as '\n' would. 
                    * 
                    *Entry: 
                    * char *string - pointer to place to store string 
                    * int count - max characters to place at string (include \0) 
                    * FILE *stream - stream to read from 
                    * 
                    *Exit: 
                    * returns string with text read from file in it. 
                    * if count <= 0 return NULL 
                    * if count == 1 put null string in string 
                    * returns NULL if error or end-of-file found immediately 
                    * 
                    *Exceptions: 
                    * 
                    *******************************************************************************/ 

            标准库中fgets(...)的实现:

             /****************************************************

              char *fgets(char *s, int n,  FILE *stream)

               {

                    register int c;

                    register char *cs;

                    cs=s;

                    while(--n>0 &&(c = getc(stream))!=EOF)

                         if ((*cs++=  c) =='\n')

                           break;

                     *cs ='\0';

                     return (c == EOF && cs == s) ?NULL :s ;

                   }

            /********************************************************

             在用fgets(..)读入数据时,先定义一个字符数组或字符指针,如果定义了字符指针 ,那么一定要初始化。

        example:

              char s[100]; //可以。

              char *s;  //不可以,因为只是声明了一个指针。但并没有为它分配内存缓冲区。

              所以,如果要用指针,则  char *s=(char *)malloc(100*sizeof(char)); 为其分配内存空间,c++中用char *s=new char [100];      如果为分配内存空间,编译时不会检查出问题,但运行时会出现未知错误。。

        fgets(...)读入文本行时的两种情况。

          1。    如果n大于一行的字符串长度,那么当读到字符串末尾的换行符时,fgets(..)会返回。并且在s的最后插入字符串结束标志'\0'。 而s缓冲区剩余的位置不会再填充。

           example:

              123abc

              fgets(s,10,fp);

              此时,读入七个字符,123abc\n,实际上还有最后的'\0',所以,strlen(s)=7; 如果要去除末尾的\n,s[strlen(s)-1]='\0';便可。

          2.     如果n小于等于一行的字符串的长度,那么读入n-1个字符,此时并没有读入\n因为并没有到行尾 ,同样在最后会插入'\0'.

          example:

            123abc

            char  s[5];

            fgets(s,5,fp);

            这时读入4个字符,123a,并没有换行符,所以strlen(s)=4.

       fgets(...)读入整个文件内容

          通常用while()循环来使fges()读入文本全部内容,并按行读入。

           char s[1024];

            while((fgets(s,1024,fp))!=NULL)

             {

                   printf(s);

             }

         当然如果n小于每行的字符个数,也可以读,只不过读的次数要多。

          假设一行为 : 123456789

           char s[2];

           int  num=0;

           while((fgets(s,2,fp))!=NULL)

             {

                  printf(s);

                  n++;

             }

            每次读入一个字符, 最后也会读完一行,num=10,读了十次,所以,fgets若没遇到换行符,会接着从前一次的位置继续读入n-1个字符,只要是文本流没关闭。

         读入空行的情况:

            第一行   abcdef123

            第二行                       

            第三行  helloworld

            其中第二行为空,fget(..)会把第二行也读入,因为并未到文件结尾。

            有时我们并不需要空行,可以这样做。

            while((fgets(s,n,fp))!=NULL)

               {

                    if(strlen(s)!=1)    //注意这儿是1不是0,因为尽管是空行,它也会读入换行符,strlen(s)=1;

                        printf(s);

               }

         fgets(...)从标准设备读数据。

            用fgets(...)还也读入标准输入设备(一般为键盘)的信息

            原型  :  fgets(s,n,stdin);

            假设在控制台下,我们可以用fgets(...)替代gets(),读入键盘输入的信息,fgets()是安全的,因为不会像gets()有溢出的可能。。

            比如 :输入 abc

            fgets(s,n,stdin)也会读入n-1个字符。但是只是从stdin流读入。。。



C语言输入输出函数之 fputs(...)

  C语言中fgets(...)从流中读入输入,相反fputs(...)向文件写入数据。

     对于ANSI C 程序,运行时系统会打开至少三个流,这3个流包括:

      1.   标准输入    standard input .    标准定义为stdin.

      2    标准输出    standard output.   标准定义为stdout

      3.   标准错误    standard  error.     标准定义为stderr.

      同时用FILE 结构指向这三个流。。

       fputs(...)用于向这三个流写入数据。

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

        s 为字符指针, 这儿既可以用字符数组,还可以字符指针,也可以直接使用字符串常量做为参数。

       example:

          FILE *fp=fopen("test.txt","w");

          char s1[20]="hello world";

          char *s2="hello C";

   fputs(s1,fp);   // 数组名

          fputs(s2,fp);   //字符指针

         fputs("hello",fp);  //字符串常量

       上面三种用法都是可以的,实质上, C语言中字符串的直接值其实都是指针。

     返回值:

         如果写入成功,则返回非0,此时编译器默认为返回1.

         如果写入错误,则返回EOF。

        注意: fputs(char *s, FILE *stream) 函数向文件写入数据成功后,文件位置指针会自动向后移。

        fputs(...)向屏幕输出数据。

       既然FILE 结构可以指向三种流,当然也可以指向stdout 流

        所以:

           fputs("hello world",stdout);

        就是想屏幕输出hello word.

      最后来看看fputs(...)函数的标准库实现:

        int  fputs(char *s, FILE *stream)

  {      

       int c;

       while(c =*s++)   //从这儿可以看出,fputs不会向流写入字符串结尾的空字符。

          putc(c,stream);

       return ferror(stream)? EOF: 非负值

  }     





你可能感兴趣的:(C语言开发总结(十六))