【c++ primer】深入解析 strcpy() 笔试面试高频题

时间:2022-01-21 05:23:27

原文

http://blog.csdn.net/tianshuai1111/article/details/7624419

一,strcpy用法及注意事项    

     1)strcpy( )概述
            原型: extern char *strcpy(char *dest,char *src); //注意把谁复制给谁                 
            用法: #include <string.h>                   
            功能:把src所指以NULL结束的字符串复制到dest所指的数组中。              
            注意: src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳 src的字符串.返回指向dest的指针。                   
            例如: char a[100],b[50];
                        strcpy(a,b);
             如用   strcpy(b,a); 要注意a中的字符串长度(第一个‘\0’之前)是否超过50位,如超过,则会造成b的内存地址溢出

     2)实例解析

          假如有如下代码,请问输出为多少?

[html] view plain copy
  1. #include "stdio.h"  
  2.  #include <string.h>  
  3.  intmain()  
  4.  {   
  5.       char dest[3];  
  6.       char str[6]="Hello";  
  7.       strcpy(dest,str);  
  8.       printf("%s\n",dest);  
  9.  }  
【c++ primer】深入解析 strcpy() 笔试面试高频题



        编译输出如下结果: Hello                 //没有输出乱码,有点怪怪的为什么?
        分析:
               str中字符串的长度大于dest 的内存长度3.为什么str字符串还能完拷贝到dest 中呢?
               来看看 strcpy的 源代码实现:

[html] view plain copy
  1. char  *strcpy(char   *strDest,const   char  *strSrc)    
  2. {    
  3.        assert((strDest!=NULL)&&(strSrc  !=NULL))   //判断指针是否合法,即分配内存,指向某块确定区域  
  4.        char   *address   =  strDest;               //记住目标地址的起始值  
  5.        while((*strDest++   =   *strSrc++)!='\0')   //先拷贝,后判断,这样就不用在拷贝完了后,再加一句  
  6.               NULL;                   // *strDest = '\0'; -->即加一个结束符.因为字符串结束已拷贝了.  
  7.        return   address;      //返回目标首地址的值。              
  8. }  



        从上面的代码可以看出,strcpy函数,假定strDest的内存空间是足够可以放下strSrc的内容的.也就是说使用者,在使用strcpy函数时,应使 (strDest内存空间)>=  (strSrc内存空间)     
        在调用   strcpy(dest,str);  时,while((*strDest++   =   *strSrc++)!='\0')   没有判断strDest的内存是否够,而是将srtSrc的内容直接拷贝到strDest,当拷到'\0'就结束.
        因此在使用printf("%s\n",dest);  时输出遇到'\0'时停此输出.所以会输出: Hello

        虽然会输出正确结果,但是会产生内存溢出的错误
      

      3)笔试、面试高频题

            1>  不调用库函数,实现strcpy函数。 
            2>  解释为什么要返回char *。 

 
        问题1>解答:

        strcpy的实现代码 

[html] view plain copy
  1. char * strcpy(char * strDest,const char * strSrc)  
  2.     {  
  3.             if((strDest==NULL)||(strSrc==NULL)) //[1]  
  4.                    throw "Invalid argument(s)";         //[2]  
  5.             char *strDestCopy=strDest;               //[3]  
  6.             while((*strDest++=*strSrc++)!='\0');    //[4]  
  7.             return strDestCopy;  
  8.     }  



    错误的做法: 
    [1] 

          (A)不检查指针的有效性,说明答题者不注重代码的健壮性。 
           (B)检查指针的有效性时使用((!strDest)||(!strSrc))或(!(strDest&&strSrc)),说明答题者对C语言中类型的隐式转换没有深刻认识。在本例中char *转换为bool即是类型隐式转换,这种功能虽然灵活,但更多的是导致出错概率增大和维护成本升高。所以C++专门增加了bool、true、false三个关键字以提供更安全的条件表达式。 
           (C)检查指针的有效性时使用((strDest==0)||(strSrc==0)),说明答题者不知道使用常量的好处。直接使用字面常量(如本例中的0)会减少程序的可维护性。0虽然简单,但程序中可能出现很多处对指针的检查,万一出现笔误,编译器不能发现,生成的程序内含逻辑错误,很难排除。而使用NULL代替0,如果出现拼写错误,编译器就会检查出来。 
    [2] 
           (A)return new string("Invalidargument(s)");,说明答题者根本不知道返回值的用途,并且他对内存泄漏也没有警惕心。从函数中返回函数体内分配的内存是十分危险的做法,把释放内存的义务抛给不知情的调用者,绝大多数情况下,调用者不会释放内存,这导致内存泄漏。 
           (B)return 0;,说明答题者没有掌握异常机制。调用者有可能忘记检查返回值,调用者还可能无法检查返回值(见后面的链式表达式)。妄想让返回值肩负返回正确值和异常值的双重功能,其结果往往是两种功能都失效。应该以抛出异常来代替返回值,这样可以减轻调用者的负担、使错误不会被忽略、增强程序的可维护性。 
    [3] 
           (A)忘记保存原始的strDest值,说明答题者逻辑思维不严密。 
    [4] 
           (A)循环写成while (*strDest++=*strSrc++);,同[1](B)。 

          (B)循环写成while(*strSrc!='\0')     *strDest++=*strSrc++;  说明答题者对边界条件的检查不力。循环体结束后,strDest字符串的末尾没有正确地加上'\0'。 



    问题2>解答:

      返回strDest的原始值使函数能够支持链式表达式,增加了函数的“附加值”。同样功能的函数,如果能合理地提高的可用性,自然就更加理想。 
        

        链式表达式的形式如: intiLength=strlen(strcpy(strA,strB)); 
                                 又如: char * strA=strcpy(new char[10],strB); 
     

        返回strSrc的原始值是错误的。

                   其一,源字符串肯定是已知的,返回它没有意义。

                   其二,不能支持形如第二例的表达式。

                    其三,为了保护源字符串,形参用const限定strSrc所指的内容,把const char *作为char *返回,类型不符,编译报错。

二,Memset、Memcpy、Strcpy的区别


        1) Memset
             原型:   extern void *memset(void *buffer, int c, int count);             
             用法:   #include <string.h>    或者  <memory.h>   
             功能:  把buffer所指内存区域的前count个字节设置成字符 c。          
             说明:  返回指向buffer的指针。用来对一段内存空间全部设置为某个字符
             例如:    
                           char  a[10];                        
                           memset(a,'\0', sizeof(a));                
                           memset可以方便的清空一个结构类型的变量或数组。              
             如:struct    _test{  char s[10];      
                                               int x;     
                                               int y;
                                           };       
            变量    struct _test    st;             
                     1)一般清空st的方法如下: st.s[0] = '\0'; st.x =0;   st.y =0;              
                     2) 用memset方法如下: memset(&st,0,sizeof(struct_test));         
           数组: struct _test     st[10];               
                       memset(st,0,sizeof(struct _test)*10);  //清空方法 


           1>  memset 源码的实现 C语言

[html] view plain copy
  1. #include <mem.h>   
  2. void* memset(void* s, int c, size_t n)  
  3. {  
  4.      unsigned char* p = (unsigned char*) s;  
  5.      while (n > 0)   
  6.        {  
  7.                *p++ = (unsigned char)c;  
  8.                 --n;  
  9.        }  
  10.       return s;  
  11. }  




          2> 示例:

[html] view plain copy
  1. #include "string.h"  
  2. #include "stdio.h"  
  3. int main()  
  4. {  
  5.        charbuffer[] = "Hello world\n";  
  6.        printf("Bufferbefore memset: %s\n", buffer);  
  7.        memset(buffer,'*', strlen(buffer) );  
  8.        printf("Bufferafter memset: %s\n", buffer);  
  9.    
  10.        return0;  
  11. }  



         2)memcpy
             原型: extern   void    *memcpy(void   *dest,void  *src,unsigned   int   count);             

             用法: #include <string.h>             
             功能: 由src所指内存区域复制count个字节到dest所指内存区域。            
             说明:  src和dest所指内存区域不能重叠,函数返回指向dest的指针.可以拿它拷贝任何数据类型的对象。    
             例如:  char a[10],b[5];             
                           memcpy(b, a, sizeof(b));             /*注意如果用sizeof(a),会造成b的内存地址溢出*/

 三,区别                    
           memset    主要应用是初始化某个内存空间。                 
           memcpy   是用于copy源空间的数据到目的空间中。                 
           strcpy        用于字符串copy,遇到‘\0’,将结束。                  

       实例:

                 
       例如初始化某块空间的时候,用到memcpy,那么就显得笨拙                
       int    m[100]                     
       memset( (void*)m,  0x00,  sizeof(int*100));            //Ok!                    
       memcpy((void*)m,"\0\0\0\0....", sizeof(int)*100);    //NotOk

        memset 用来对一段内存空间全部设置为某个字符,一般用于在对定义的字符串初始化为 '  '  或者  '\0';    
           例: char a[100];          memset(a,'\0',sizeof(a));    

    
        memcpy 是用来做内存拷贝,可以用来拷贝任何数据类型的对象,可以指定拷贝的数据长度;    
           例: char a[100],b[50];          memcpy(b,a,sizeof(b));   //注意:如果使用sizeof(a),会造成内存溢出 (sizeof(b)是要复制的个数)