zoukankan      html  css  js  c++  java
  • C++

    本文将介绍对类使用new和delete 以及如何处理由于 使用动态内存而引起的一些微妙问题,它将影响构造函数的析构函数的设计和运算符的重载。

    注意:不要在类声明文件(.h文件)中初始化类静态成员。在包含类方法的文件(.cpp文件)中初始化。但是如果静态成员是整型或枚举类型const,则可以在类声明中初始化。(c因为onst 定义的常量属于局部作用域,不是静态变量所属的全局区)

    下面代码在每个构造函数都有num++。使得每创建一个str对象,静态变量的值就+1记录对象总数。完整代码实现部分如下:

    #ifndef STR_H
    #define STR_H
    
    
    class str
    {
        public:
            str();
            str(const char *s);
            virtual ~str();
            friend std::ostream & operator<<(std ::ostream & os,const str & st);
            str(const str& st);
        protected:
    
        private:
            char * str;
            int len;
            static int num;
    };
    
    #endif // STR_H
    

      

    #include "str.h"
    
    str::str()
    {
        //ctor
         int len=4;
        str =new char[4];
        std::strcpy(str,"C++");
        num++;
    }
    str::str(const char *s){
        len = std::strlen(s);
        str = new char[len+1];
        std::strcpy(str,s);
        num++;
    }
    str::~str()
    {
        //dtor
        --num;
        delete[] str;
    }
    std::ostream & operator<<(std::ostream & os,const str &st){   //重载一下<<输出str对象
        os<<st.str;
        return os;
    }
    str & str::operator=( const str& st){
        if(this == &st)
            return *this;  //判断一下赋值的对象同一个则返回自身
            delete[] str;   //如果不同,把旧的字符串指的内存释放,如果此时不释放,
            //上面的字符串一直在内存中,因为str不指向它了,该内存浪费了,稍后把一个新的字符串地址赋给str,
        len = st.len;
        str = new char[len+1];
        std::strcpy(str,st.str);
        return *this;  //此次对象赋值会把旧的内存delete,而使str指向一个新new的字符串,不会创建新的对象因此num不会变化
    
    }
    int str::num=0;
    str::str(const str& st){
        num++;
        len = st.len;
        str =new char [len +1];
        std::strcpy(str,st.str);
    }
    int main(){
        str sport("dsdsss");
    }
    

      

    分析: 类的成员str是一个指针,指向字符串。所以构造函数必须提供内存来存字符串,初始化对象时,给构造函数传一个字符串指针,(字符串可变)

    Str(“String”);

    构造函数必须分配足够的内存来存储字符串,然后把字符串复制到内存中

    首先,用strlen()函数计算字符串的大小,并对成员len初始化,接着,用new分配足够的空间来保存字符串,然后新的内存地址(new函数返回值就是地址char  *)给str 成员

    strlen()返回字符串长度,不包括末尾的空字符,所以得+1,使得分配的内存能存储包含了空字符的字符串。

    构造函数用strcpy()将传递的字符串复制到新的内存,并更新对象计数num

    必须知道字符串并不保存在对象中,单独保存在堆内存中(new的动态分配的内存都在堆区),对象中str成员指针仅保存了指出到哪里查找字符串的地址

    默认构造函数,虽然无形参,但实现中strcpy 提供了一个默认字符串“C++”,复制到新建的内存中。

    该析构函数成员str指向new分配的内存,Str对象过期了,指针也过期若无delete

    但str指向new分配的内存仍被分配。删除对象可以释放对象本身占的内存,但并不能自动释放属于对象成员指针指向的内存。

    所以必须用析构函数里确保对象过期了,由构造函数new分配的内存也被释放。)(两件事,不能用默认的析构函数)

    总结:str成员是指向一块新建内存的指针。该内存长度运行时候确定,是传入字符串strlen()+1,构造函数中new一个内存,再用std空间的成员方法strcpy把形参的复制到新建内存中

    若用默认析构函数只会使str对象消亡,不会使得成员str指向的堆内存的字符串释放空间

    复制构造函数引出:

    int main 中有如下语句:

        str sport("dsdsss");

     str  sailor=sports;                    //句子1

    上面是把一个对象赋值给另一个对象是赋值运算符的重载吗 (不对)

    句子1 不是调用默认也不是调用有参构造函数。

    句子1 与 str  sailor =str(sports);    //句子2  等效

    句2把对象作为实参传入str中,是有参构造函数调用吗?(不对)

    若是有参构造函数,形参应该是str &,因为实参是个对象

    正解:句子2原型是str(const str &)

    这就是复制构造函数

    复制构造函数形参是该类的引用

    用于将一个对象复制到新创建的对象中。也就是说,它用于初始化过程,而不是常规的赋值过程

    它接收一个对象的常量引用作为参数

    对复制构造函数需要知道什么时候调用,有啥作用。下面是会调用复制构造函数几种形式

    1.  str ditto(motto); //该实参是一个对象,在实参传给形参过程中调用了复制构造函数
    2.  str metoo =motto;
    3.  str also =Str(motto);   //该函数返回值是一个对象

        4    str* ptring =new Str(motto);  // new出了一个匿名对象,并把这个对象地址给了pting这个指针

    作用:

    每当程序产生了一个对象副本,编译器都将使用复制构造函数

    具体就是:按值传递对象,或返回对象都将用复制构造函数。按值传递意味着创建原始变量的一个副本

    介绍了复制构造函数,

    再说如果用默认复制构造函数对str类复制

    会有什么问题?

    str类有个成员是指针类型。

    这样  sailior.str = sports.str;是复制了指针还是字符串?

    是指针,只是把内存地址复制了一遍。并没把指针指向的字符串内容复制下来。所有复制的对象副本str指针指向的仍然是原来的内存,并没有一个新的内存被他指向、所以当析构函数调用两次删除对象时,会两次delete同一片内存空间,而产生错误。

    正确做法是:重新编写新的复制构造函数,不用默认的。使得str指向一片新的内存空间。

    如下代码:

    str::str(const str& st){
    
        num++;
    
        len = st.len;   //与有参构造函数这句有差别
    
        str =new char [len +1];
    
        std::strcpy(str,st.str);
    
    }
    

     调用有参构造函数,会创建一个全新的对象。虽然看起来和复制构造函数形式有点类似(这体现引用的特点:指向一个对象,名称是该已有的对象的别名)。 

     有参构造函数

    str::str(const char *s){
    
        len = std::strlen(s);
    
        str = new char[len+1];
    
        std::strcpy(str,s);
    
        num++;
    
    }
    

      

    实参给一个字符串,用指针指向它,

    str析构函数使用默认的会出问题,也会出现在

    赋值运算符重载 中

    函数未定义的话,c++会自动添加重载的赋值运算符,

    若未重载赋值运算符,把对象赋值给对象是不允许的。

    重载的原型是

    str & str::operator=(const str &)

    该函数接收一个指向类对象的引用

    什么时候用赋值运算符?

    在把一个已有的对象赋值给一个对象时:

    如 

    str kont;(“sds”);
    
    Str head;
    
    Kont=head;

    该处用赋值问题和用隐含的复制构造函数错误相同,成员复制问题。

    =复制只会head.str与knot指向相同的地址。两次调用析构函数时,删除同一片内存出错;

    初始化对象时,不一定会用上赋值运算符

    str  sailor=sports;  //此处用复制构造函数,不是赋值重载

    上面句sailor 是一个新创建的对象,被初始化为sport的值,所以用复制构造函数

    实现时也可以使用复制构造函数创建一个临时对象,然后通过赋值将临时对象的值赋到新对象中去。

    总结就是初始化一定会用上复制构造函数,用=也可能调用赋值运算符(不一定是初始化过程)

    赋值运算符与复制构造函数过程 类似,赋值运算符也是对成员进行逐个复制

    如果成员本身就是类对象,则程序将用为这个类定义的赋值运算符来赋值该成员

    自己写个重载的赋值运算符可以避免出现上述问题。如下

    str & str::operator=( const str& st){
    
        if(this == &st)
    
            return *this;  //判断一下赋值的对象同一个则返回自身
    
            delete[] str;   //如果不同,把旧的字符串指的内存释放,如果此时不释放,
    
            //上面的字符串一直在内存中,因为str不指向它了,该内存浪费了。稍后把一个新的字符串地址赋给str,
    
        len = st.len;
    
        str = new char[len+1];
    
        std::strcpy(str,st.str);
    
        return *this;  //此次对象赋值会把旧的内存delete,而使str指向一个新new的字符串,不会创建新的对象。因此num不会变化
    
    }
    
  • 相关阅读:
    Windows 运行 中的命令
    Base64 实现。名家手笔
    熊猫烧香病毒专杀及手动修复方案
    pdf病毒的源代码(VBS)
    Base64 实现。名家手笔
    pdf病毒的源代码(VBS)
    Code:关于加密解密 Base64 and URL and Hex Encoding and Decoding
    wmDrawer:实用的步骤启动器
    gnormalize:音频转换对象
    Avidemux:视频编纂软件
  • 原文地址:https://www.cnblogs.com/yizhizhangBlog/p/13946168.html
Copyright © 2011-2022 走看看