• 0717-----C++Primer听课笔记----------复制控制


    1.默认拷贝构造函数

    1.1 编译器自动为我们合成一个拷贝构造函数。A(const A &).

    1.2 象复制的时机

    a) 显式复制。

    b) 使用对象做形参

    c) 使用对象做返回值

    d) 往容器中放入对象

    #include <iostream>
    #include <string>
    #include <vector>
    using namespace std;
    
    /*
     *用一个对象初始化另一个对象时 需要调用拷贝构造函数
     *这里 编译器 自动提供了一个默认的拷贝构造函数
     */
    class Student{
        public:
            Student();
            void setStudent(const string &name, int age, int score);
            void print() const;
        private:
            string name_;
            int age_;
            int score_;
    };
    
    Student::Student()
        :name_(""),
         age_(0),
         score_(0)
    {}
    
    void Student::setStudent(const string &name, int age, int score){
        name_ = name;
        age_ = age;
        score_ = score;
    }
    
    void Student::print()const{
        cout << name_ << " " << age_ << " " << score_ << endl;
    }
    
    int main(int argc, const char *argv[])
    {
        Student s;
        s.setStudent("monica", 22, 99);
    
        Student s2(s); //此时需要调用拷贝构造函数
        s2.print();
    
        Student s3("copy", 33, 89);
        return 0;
    }

    1.3 拷贝构造函数不能为A(A a)的形式,因为A a这种形参能够触发拷贝语义(copy semantics),而拷贝构造函数本身是为了解决拷贝语义而存在的,所以它自身要避免拷贝。

    2.深拷贝和浅拷贝

    2.1 概念:浅拷贝和深拷贝是对类中持有指针而言,如果对象复制的时候,仅仅去拷贝指针的值,这称为浅拷贝(shallow copy),如果不是去拷贝指针的值,而是去拷贝指针指向的内存空间,称为深拷贝(deep copy)。

    2.2 浅拷贝的例子 ,自己实现一个 string类,并用一个对象去初始化另一个对象,这里没有写拷贝构造函数,因此编译器会自动生成默认的拷贝构造函数,但是请注意,这里的string 成员变量是用指针实现的,默认的拷贝构造函数只复制了指针,因此两个对象将指向同一个内存区域,这样,在调用析构函数的时候,同一片内存就会delete两次,这就造成了错误。

    #include <iostream>
    #include <string.h>
    
    using namespace std;
    /*
     * 自定义string类 调用默认构造函数时,
     * 仅仅复制了str的值,复制后,两个string对象
     * 指向同一块内存,在析构的时候 该内存被delete
     * 两次 因此出错
     */
    
    class  String{
        public:
            String();
            String(const char* str);
            ~String();
            void debug() const;
        private:
            char *str_;
    };
    
    String::String()
        :str_ (new char[1])
    {
         str_[0] = 0;
    }
    
    String::String(const char* str) //用C 字符串初始化一个string 对象
        :str_(new char[strlen(str) + 1]);
    {
        strcpy(str_, str);
    }
    
    String::~String(){
        delete[] str_;
    }
    
    void String::debug()const{
        cout << str_ << endl;
    }
    
    int main(int argc, const char *argv[])
    {
        String s1("apple");
        s1.debug();
    
        String s2(s1);
        s2.debug();
    
        return 0;
    }

    图像 2

    2.3 深拷贝的例子,还是上例,定义自己的拷贝构造函数,在初始化另一个对象时,为对象分配新的内存空间,并且将值拷贝过去。这里在执行完深拷贝之后,两个对象之间没有任何关联。

    #include <iostream>
    #include <string.h>
    
    using namespace std;
    /*
     * 深拷贝
     */
    
    class  String{
        public:
            String();
            String(const char* str);
            String(const String &other); //自定义 拷贝构造函数
            ~String();
            void debug() const;
        private:
            char *str_;
    };
    
    String::String()
        :str_ (new char[1])
    {
         str_[0] = 0;
    }
    
    String::String(const char* str) //用C 字符串初始化一个string 对象
        :str_(new char[strlen(str) + 1])
    {
        strcpy(str_, str);
    }
    
    String::String(const String &other)
        :str_(new char[strlen(other.str_) + 1])
    {
        strcpy(str_, other.str_);
    }
    
    String::~String(){
        delete[] str_;
    }
    
    void String::debug()const{
        cout << str_ << endl;
    }
    
    int main(int argc, const char *argv[])
    {
        String s1("apple");
        s1.debug();
    
        String s2(s1);
        s2.debug();
    
        return 0;
    }

    3. 赋值运算
    3.1 对象的赋值,调用的是类的赋值运算符。编译器自动为我们合成一个赋值运算符。这里我为string类编写一个复制运算符函数,但是没有考虑自身赋值的问题。

    #include <iostream>
    #include <string.h>
    
    using namespace std;
    /*
     * 编译器会自动为我们合成一个赋值运算符
     * 也可以自定义一个 赋值运算符
     */
    
    class  String{
        public:
            String();
            String(const char* str);
            String(const String &other);
            String &operator= (const String &other); // 自定义赋值运算符
            ~String();
            void debug() const;
        private:
            char *str_;
    };
    
    String::String()
        :str_ (new char[1])
    {
         str_[0] = 0;
    }
    
    String::String(const char* str)
        :str_(new char[strlen(str) + 1])
    {
        strcpy(str_, str);
    }
    
    String::String(const String &other)
        :str_(new char[strlen(other.str_) + 1])
    {
        cout << " call copy construction " << endl;
        strcpy(str_, other.str_);
    }
    
    String& String::operator=(const String &other){
        cout << "call operator= func " << endl;
        delete[] str_;
        str_ = new char[strlen(other.str_) + 1];
        strcpy(str_, other.str_);
        return *this;
    }
    
    String::~String(){
        delete[] str_;
    }
    
    void String::debug()const{
        cout << str_ << endl;
    }
    
    int main(int argc, const char *argv[])
    {
        String s1("apple"); //调用第二个构造函数
        s1.debug();
    
        String s2(s1); // 调用拷贝构造函数
        s2.debug();
    
        String s3 ;//调用自定义的赋值运算符
        s3 = s1;
        s3.debug();
        return 0;
    }

    3.2 编写赋值运算符和拷贝构造函数的区别:

    a) 赋值运算符可能需要先释放资源

    b) 赋值预算符需要考虑自身赋值问题。

    #include <iostream>
    #include <string.h>
    
    using namespace std;
    /*
     * 编写赋值运算符 必须考虑 自身赋值的问题
     * 并且 返回值必须是自身的引用
     */
    
    class  String{
        public:
            String();
            String(const char* str);
            String(const String &other);
            String &operator= (const String &other); // 自定义赋值运算符
            ~String();
            void debug() const;
        private:
            char *str_;
    };
    
    String::String()
        :str_ (new char[1])
    {
         str_[0] = 0;
    }
    
    String::String(const char* str)
        :str_(new char[strlen(str) + 1])
    {
        strcpy(str_, str);
    }
    
    String::String(const String &other)
        :str_(new char[strlen(other.str_) + 1])
    {
        cout << " call copy construction " << endl;
        strcpy(str_, other.str_);
    }
    
    String& String::operator=(const String &other){
        cout << "call operator= func " << endl;
        if(&other != this){ //这里比较用的是指针 而不是那对象本身直接去比较
            delete[] str_;
            str_ = new char[strlen(other.str_) + 1];
            strcpy(str_, other.str_);
        }
        return *this;
    }
    
    String::~String(){
        delete[] str_;
    }
    
    void String::debug()const{
        cout << str_ << endl;
    }
    
    int main(int argc, const char *argv[])
    {
        String s1("apple"); //调用第二个构造函数
        s1.debug();
    
    
        String s3 ;//调用自定义的赋值运算符
        s3 = s1;
        s3.debug();
        s3 = s3;
        s3.debug();
        return 0;
    }

    3.3 总结

    3.3.1 编写赋值预算符的准则:

    a) 必须处理自身赋值问题;

    b) 必须返回自身引用。

    3.3.2 三法则:拷贝构造函数、赋值运算符、析构函数,三者都与类内部的指针密切相关。当需要编写其中一个时,一般需要编写其他两个。

    3.3.3 当需要禁止一个类进行复制或者赋值时,只需将类的拷贝构造函数和赋值运算符设为私有,而且只提供声明(注意设为私有后 该类的友元还可以访问,因此要只提供声明,这样友元类在要调用这两个函数时,会在链接时导致错误)。根据谷歌编程规范(http://zh-google-styleguide.readthedocs.org/en/latest/google-cpp-styleguide/contents/),可以将其定义为宏。

    #include <iostream>
    #include <string>
    #include <vector>
    using namespace std;
    /*
     * 禁止 拷贝和赋值
     * 将对函数设为私有
     */
    class Student{
        public:
            Student();
            void setStudent(const string &name, int age, int score);
            void print() const;
        private:
            Student(const Student &other);
            Student &operator= (const Student &other);
            string name_;
            int age_;
            int score_;
    };
    
    Student::Student()
        :name_(""),
         age_(0),
         score_(0)
    {}
    
    void Student::setStudent(const string &name, int age, int score){
        name_ = name;
        age_ = age;
        score_ = score;
    }
    
    void Student::print()const{
        cout << name_ << " " << age_ << " " << score_ << endl;
    }
    
    int main(int argc, const char *argv[])
    {
        Student s;
        s.setStudent("monica", 22, 99);
    
        Student s2(s);
        s2.print();
    
        return 0;
    }

    该宏定义如下:

    #define DISALLOW_COPY_AND_ASSIGN(TypeName)
         TypeName(const TypeName&);
         void operator=(const TypeName&)

  • 相关阅读:
    错误提示窗口-“操作系统当前的配置不能运行此应用程序”
    打印机无法打印的10种解决方法
    开发进度三
    人月神话阅读笔记二
    开发进度二
    开发进度1
    人月神话阅读笔记一
    库存物资管理系统
    四则运算
    动手动脑5
  • 原文地址:https://www.cnblogs.com/monicalee/p/3852251.html
Copyright © 2020-2023  润新知