zoukankan      html  css  js  c++  java
  • strcpy函数的实现

    流行的strcpy函数写法是:

    [cpp] view plaincopy
     
    1. char *my_strcpy(char *dst,const char *src)  
    2. {  
    3.     assert(dst != NULL);  
    4.     assert(src != NULL);  
    5.     char *ret = dst;  
    6.     while((* dst++ = * src++) != '')   
    7.         ;  
    8.     return ret;  
    9. }  

    如果注意到:

    1,检查指针有效性;

    2,返回目的指针des;

    3,源字符串的末尾 '' 需要拷贝。

    写出上面实现函数就不在话下。

    然而这样的实现没有考虑拷贝时内存重叠的情况,下面的测试用例就能使调用my_strcp函数的程序崩溃:

    [cpp] view plaincopy
     
    1. char str[10]="abc";  
    2. my_strcpy(str+1,str);  


    然而调用系统的strcpy函数程序正常运行,打印str结果为“aabc”!可见系统strcpy函数的实现不是这样的。

    strcpy的正确实现应为:

    [cpp] view plaincopy
     
    1. char *my_strcpy(char *dst,const char *src)  
    2. {  
    3.     assert(dst != NULL);  
    4.     assert(src != NULL);  
    5.     char *ret = dst;  
    6.     memcpy(dst,src,strlen(src)+1);  
    7.     return ret;  
    8. }  

    memcpy函数实现时考虑到了内存重叠的情况,可以完成指定大小的内存拷贝,它的实现方式建议查看文章“卓越的教练是如何训练高手的?”,会获益良多,这里仅粘帖函数memcpy函数的实现:

    [cpp] view plaincopy
     
    1. void * my_memcpy(void *dst,const void *src,unsigned int count)  
    2. {  
    3.      assert(dst);  
    4.      assert(src);  
    5.      void * ret = dst;  
    6.      if (dst <= src || (char *)dst >= ((char *)src + count))//源地址和目的地址不重叠,低字节向高字节拷贝  
    7.      {  
    8.          while(count--)  
    9.          {  
    10.              *(char *)dst = *(char *)src;  
    11.              dst = (char *)dst + 1;  
    12.              src = (char *)src + 1;  
    13.          }  
    14.      }  
    15.      else                       //源地址和目的地址重叠,高字节向低字节拷贝  
    16.      {   
    17.          dst = (char *)dst + count - 1;  
    18.          src = (char *)src + count - 1;   
    19.          while(count--)   
    20.          {  
    21.              *(char *)dst = *(char *)src;  
    22.              dst = (char *)dst - 1;  
    23.              src = (char *)src - 1;  
    24.          }  
    25.     }  
    26.     return ret;  
    27. }  

    两者结合才是strcpy函数的真正实现吧。

    已知strcpy函数的原型是:

    char *strcpy(char *dst, const char *src);

    1. 实现strcpy函数
    2. 解释为什么要返回char *
    3. 假如考虑dst和src内存重叠的情况,strcpy该怎么实现

    1.strcpy的实现代码

    复制代码
    char * strcpy(char *dst,const char *src)   //[1]
    {
        assert(dst != NULL && src != NULL);    //[2]
    
        char *ret = dst;  //[3]
    
        while ((*dst++=*src++)!=''); //[4]
    
        return ret;
    }
    复制代码

    [1]const修饰

    源字符串参数用const修饰,防止修改源字符串。

    [2]空指针检查

    (A)不检查指针的有效性,说明答题者不注重代码的健壮性。

    (B)检查指针的有效性时使用assert(!dst && !src);

    char *转换为bool即是类型隐式转换,这种功能虽然灵活,但更多的是导致出错概率增大和维护成本升高。

    (C)检查指针的有效性时使用assert(dst != 0 && src != 0);

    直接使用常量(如本例中的0)会减少程序的可维护性。而使用NULL代替0,如果出现拼写错误,编译器就会检查出来。

    [3]返回目标地址

    (A)忘记保存原始的strdstt值。

    [4]''

    (A)循环写成while (*dst++=*src++);明显是错误的。

    (B)循环写成while (*src!='') *dst++=*src++;

    循环体结束后,dst字符串的末尾没有正确地加上''。

    2.为什么要返回char *?

    返回dst的原始值使函数能够支持链式表达式。

    链式表达式的形式如:

    int l=strlen(strcpy(strA,strB));

    又如:

    char * strA=strcpy(new char[10],strB);

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

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

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

    其三,把const char *作为char *返回,类型不符,编译报错。

    3.假如考虑dst和src内存重叠的情况,strcpy该怎么实现

    char s[10]="hello";

    strcpy(s, s+1); //应返回ello,

    //strcpy(s+1, s); //应返回hhello,但实际会报错,因为dst与src重叠了,把''覆盖了

    所谓重叠,就是src未处理的部分已经被dst给覆盖了,只有一种情况:src<=dst<=src+strlen(src)

    C函数memcpy自带内存重叠检测功能,下面给出memcpy的实现my_memcpy。

    复制代码
    char * strcpy(char *dst,const char *src)
    {
        assert(dst != NULL && src != NULL);
    
        char *ret = dst;
    
        my_memcpy(dst, src, strlen(src)+1);
    
        return ret;
    }
    复制代码

    my_memcpy的实现如下

    复制代码
    char *my_memcpy(char *dst, const char* src, int cnt)
    {
        assert(dst != NULL && src != NULL);
    
        char *ret = dst; 
    
        if (dst >= src && dst <= src+cnt-1) //内存重叠,从高地址开始复制
        {
            dst = dst+cnt-1;
            src = src+cnt-1;
            while (cnt--)
                *dst-- = *src--;
        }
        else    //正常情况,从低地址开始复制
        {
            while (cnt--)
                *dst++ = *src++;
        }
        
        return ret;
    }
    复制代码

    C/C++——strcpy函数的实现 和解析

    题目: 
        已知strcpy函数的原型是: 
            char * strcpy(char * strDest,const char * strSrc); 
        1.不调用库函数,实现strcpy函数。 
        2.解释为什么要返回char *。

    (一)高质量c++编程上的答案

    五、编写strcpy函数(10分)

    已知strcpy函数的原型是

           char *strcpy(char *strDest, const char *strSrc);

           其中strDest是目的字符串,strSrc是源字符串。

    (1)不调用C++/C的字符串库函数,请编写函数 strcpy

    char *strcpy(char *strDest, const char *strSrc)

    {

        assert((strDest!=NULL) && (strSrc !=NULL));    // 2分

        char *address = strDest;                                          // 2分

        while( (*strDest++ = * strSrc++) != '' )         // 2分

                  NULL ;

        return address ;                                                  // 2分

    }

    (2)strcpy能把strSrc的内容复制到strDest,为什么还要char * 类型的返回值?

    答:为了实现链式表达式。                                              // 2分

    例如       int length = strlen( strcpy( strDest, “hello world”) );

     

    【规则6-2-3】不要将正常值和错误标志混在一起返回。正常值用输出参数获得,而错误标志用return语句返回。错误信息可以用throw抛出。

    2        建议6-2-1】有时候函数原本不需要返回值,但为了增加灵活性如支持链式表达,可以附加返回值。

    例如字符串拷贝函数strcpy的原型:

    char *strcpy(char *strDest,const char *strSrc);

    strcpy函数将strSrc拷贝至输出参数strDest中,同时函数的返回值又是strDest。这样做并非多此一举,可以获得如下灵活性:

        char str[20];

        int  length = strlen( strcpy(str, “Hello World”) );

    ()

     

     (二)程序员面试宝典中的答案

    char* strcpy1(char *strDest, const char* strSrc)

    {

           assert(strSrc != NULL );

           assert(strDest != NULL);

           int i;

           char *address = strDest;

     

        for(i = 0; strSrc[i] != ''; i++)

                  strDest[i] = strSrc[i];

           strDest[i] = '';

     

           return address;

    }

    (三)带有异常抛出的答案和解析
        解说: 
        1.strcpy的实现代码 

     

    char * strcpy3(char * strDest,const char * strSrc /*[0]*/)

    {    

           if ((NULL == strDest)||(NULL == strSrc)) //[1]

                  throw "Invalid argument(s)"; //[2]

     

           char * strDestCopy=strDest;  //[3]

     

           while ((*strDest++=*strSrc++)!=''); //[4]

     

           return strDestCopy;    

    }

    /*[0]:   规则6-1-3】如果参数是指针,且仅作输入用,则应在类型前加const,以防止该指针在函数体内被意外修改。


        错误的做法: 
        //[1]
        (A)如果不检查指针的有效性,说明答题者不注重代码的健壮性。 
        (B)如果检查指针的有效性时使用((!strDest)||(!strSrc))或(!(strDest&&strSrc)),说明答题者对C语言中类型的隐式转换没有深刻认识。在本例中((!strDest)是将char *转换为bool即是类型隐式转换,这种功能虽然灵活,但更多的是导致出错概率增大和维护成本升高。所以C++专门增加了bool、true、false三个关键字以提供更安全的条件表达式。 
        (C)如果检查指针的有效性时使用((strDest==0)||(strSrc==0)),说明答题者不知道使用常量的好处。直接使用字面常量(如本例中的0)会减少程序的可维护性。0虽然简单,但程序中可能出现很多处对指针的检查,万一出现笔误,编译器不能发现,生成的程序内含逻辑错误,很难排除。而使用NULL代替0,如果出现拼写错误,编译器就会检查出来。

    (D)NULL == strDest是将 常量写在表达式的左边,如果将表达式写错了,写成了赋值,则马上报错;如果 将表达式改成 strDest  ==NULL,在写的过程中 漏写了 一个=,变成了 strDest = NULL,则检查不出错误来,可能会出现意想不到的错误
        //[2]
        (A)return new string("Invalid argument(s)");,说明答题者根本不知道返回值的用途,并且他对内存泄漏也没有警惕心。从函数中返回函数体内分配的内存是十分危险的做法,他把释放内存的义务抛给不知情的调用者,绝大多数情况下,调用者不会释放内存,这导致内存泄漏。 
        (B)return 0;,说明答题者没有掌握异常机制。调用者有可能忘记检查返回值,调用者还可能无法检查返回值(见后面的链式表达式)。妄想让返回值肩负返回正确值和异常值的双重功能,其结果往往是两种功能都失效。应该以抛出异常来代替返回值,这样可以减轻调用者的负担、使错误不会被忽略、增强程序的可维护性。 
       //[3]
        (A)如果忘记保存原始的strDest值(即忘记 保留strDest的头指针),说明答题者逻辑思维不严密。 
        //[4] 
        (A)循环写成while (*strDest++=*strSrc++);,同[1](B)。 
        (B)如果循环写成while (*strSrc!='') *strDest++=*strSrc++;,说明答题者对边界条件的检查不力。这样的话,就是先判断是否为零,然后再赋值,肯定是不会把结尾零赋给strDest的,而[4]中的循环是先赋值,再判断是否为零,是会给strDest赋上结尾零的,如此这样,这个形式循环体结束后,strDest字符串的末尾没有正确地加上''。

    要正常的话应该写成:

    while (*strSrc!='/0') *strDest++=*strSrc++;

    strDest[i] = '';// strDest字符串的末尾加上''

    这里还有说一句: = 和 * 和 right ++ 这三个符号中,* 和  右++的优先级都是2级,是对等的,其次是 = , 由于 右++的特点:是滞后使用, 造成 会先赋值,再指针++

    也就是说:*strDest++=*strSrc++;

          *strDest++:会拆成 *strDest 和 strDest++两个对等的部分

    其让人感觉到的执行顺序

    l       *strDest = *strSrc;

    l       然后才能是 strDest++,strSrc++

    举个例子如下:

    #define product(x) (x * x)

     int i = 3, j, k;

    j = product(i++);

    k = product(++i);

    cout << j << " " << k << endl;//输出为9和49

    即使定义为#define product(x) ((x) * (x))得到的结果还是一样
        2.返回strDest的原始值使函数能够支持链式表达式,增加了函数的“附加值”。同样功能的函数,如果能合理地提高的可用性,自然就更加理想。 
        链式表达式的形式如: 
            int iLength=strlen(strcpy(strA,strB)); 
        又如: 
            char * strA=strcpy(new char[10],strB); 
        返回strSrc的原始值是错误的。其一,源字符串肯定是已知的,返回它没有意义。其二,不能支持形如第二例的表达式。其三,为了保护源字符串,形参用const限定strSrc所指的内容,把const char *作为char *返回,类型不符,编译报错。

    关于链式表达式

     

    已知strcpy函数的原型是 char *strcpy(char *strDest, const char *strSrc);

    其中strDest是目的字符串,strSrc是源字符串。

    (1)不调用C++/C的字符串库函数,请编写函数

    strcpy char *strcpy(char *strDest, const char *strSrc);

    {

          assert((strDest!=NULL) && (strSrc !=NULL)); // 2分

          char *address = strDest; // 2分

          while( (*strDest++ = * strSrc++) != ‘’ ) // 2分

           NULL ;

           return address ; // 2分

    }

    (2)strcpy能把strSrc的内容复制到strDest,为什么还要char * 类型的返回值?

     答:为了实现链式表达式。 // 2分

    例如 int length = strlen( strcpy( strDest, “hello world”) );

     --------------------------------------------------------------------------------------------------------------------------------

    看到了这个词:链式表达式,之前没有听过,所以去百度了一下,发现有人解释的还算明白:

    -------------------------------------------------------------------------------------------------------------------------------- http://topic.csdn.net/t/20061123/21/5180993.html 1,

    就是方便一些,否则就要这样写:

    char strDest[12];

    strcpy( strDest, “hello world”);

    int length = strlen(strDest);

    什么链式就是一连串写下来的意思。。。

     --------------------------------------------------------------------------------------------------------------------------------

     其实,说白了,就是如果上面的字符串拷贝函数strcopy的返回值是void,那么,上面那句:

     int length = strlen( strcpy( strDest, “hello world”) );

     就要像上面那位的回答,写成好几句了:

    char strDest[12];

     strcpy( strDest, “hello world”);

    int length = strlen(strDest);

    而这种直接返回char *的手段,就是为了后来函数调用者方便而设计的.不用你这么麻烦用上述方法去使用了,而直接可以使用拷贝后的dest字符串了.这种方便的实现方法,看起来就是链子链在一起的,所以称为 链式表达式

    如此而已.呵呵.

    关于链式表达式

     

    已知strcpy函数的原型是 char *strcpy(char *strDest, const char *strSrc);

    其中strDest是目的字符串,strSrc是源字符串。

    (1)不调用C++/C的字符串库函数,请编写函数

    strcpy char *strcpy(char *strDest, const char *strSrc);

    {

          assert((strDest!=NULL) && (strSrc !=NULL)); // 2分

          char *address = strDest; // 2分

          while( (*strDest++ = * strSrc++) != ‘’ ) // 2分

           NULL ;

           return address ; // 2分

    }

    (2)strcpy能把strSrc的内容复制到strDest,为什么还要char * 类型的返回值?

     答:为了实现链式表达式。 // 2分

    例如 int length = strlen( strcpy( strDest, “hello world”) );

     --------------------------------------------------------------------------------------------------------------------------------

    看到了这个词:链式表达式,之前没有听过,所以去百度了一下,发现有人解释的还算明白:

    -------------------------------------------------------------------------------------------------------------------------------- http://topic.csdn.net/t/20061123/21/5180993.html 1,

    就是方便一些,否则就要这样写:

    char strDest[12];

    strcpy( strDest, “hello world”);

    int length = strlen(strDest);

    什么链式就是一连串写下来的意思。。。

     --------------------------------------------------------------------------------------------------------------------------------

     其实,说白了,就是如果上面的字符串拷贝函数strcopy的返回值是void,那么,上面那句:

     int length = strlen( strcpy( strDest, “hello world”) );

     就要像上面那位的回答,写成好几句了:

    char strDest[12];

     strcpy( strDest, “hello world”);

    int length = strlen(strDest);

    而这种直接返回char *的手段,就是为了后来函数调用者方便而设计的.不用你这么麻烦用上述方法去使用了,而直接可以使用拷贝后的dest字符串了.这种方便的实现方法,看起来就是链子链在一起的,所以称为 链式表达式

    如此而已.

     
  • 相关阅读:
    Java 基础 ------- 面向对象(三) 继承
    虚拟机安装系统--- 安装VMware Tools(五)
    'sqlplus'不是内部或外部命令,也不是可运行的程序
    点餐系统(java基础、数据库、jsp、servlet)
    jsp页面pageContext.setAttribute报错
    Spring事务管理(编码式、配置文件方式、注解方式)
    并发控制
    Spring整合JDBC(连接池、JDBC模板、Dao配置到Spring容器、配置文件的优化)
    Spring的aop思想(名词、aop的配置)
    spring:spring的核心API
  • 原文地址:https://www.cnblogs.com/Vae1990Silence/p/4457856.html
Copyright © 2011-2022 走看看