• 11 对象的构造


    1 对象的初始化

    • 问题:对象中成员变量初始值是多少?

      • Demo

        #include <stdio.h>
        
        class Test
        {
        private:
            int i;
            int j;
        public:
            int getI() { return i; }
            int getJ() { return j; }
        };
        
        Test gt;  //全局对象:静态存储区
        
        int main()
        {
            printf("gt.i = %d
        ", gt.getI());  //0
            printf("gt.j = %d
        ", gt.getJ());  //0
            
            Test t1;  //栈
            
            printf("t1.i = %d
        ", t1.getI());  //134513995
            printf("t1.j = %d
        ", t1.getJ());  //5984244
            
            Test* pt = new Test;  //堆
            
            printf("pt->i = %d
        ", pt->getI());  //
            printf("pt->j = %d
        ", pt->getJ());  //
            
            delete pt;
            
            return 0;
        }
        
    • 从程序设计的角度,对象只是变量,因此

      • 上创建对象时,成员变量初始为随机值
      • 上创建对象时,成员变量初始为随机值
      • 静态存储区上创建对象时,成员变量初始为 0 值
    • 问题:程序中如何对一个对象进行初始化,使得不管在什么地方创建类对象,其成员变量的初始值都为固定值?

    • 解决方案1

      • 在类中提供一个 publicinitialize 函数

      • 对象创建后立即调用 initialize 函数进行初始化

      • 示例:初始化函数

        • Demo

          #include <stdio.h>
          
          class Test
          {
          private:
              int i;
              int j;
          public:
              int getI() { return i; }
              int getJ() { return j; }
              //初始化函数
              void initialize()
              {
                  i = 1;
                  j = 2;
              }
          };
          
          Test gt;
          
          int main()
          {
              //显式调用初始化函数
              gt.initialize();
              
              printf("gt.i = %d
          ", gt.getI());  //1
              printf("gt.j = %d
          ", gt.getJ());  //2
              
              Test t1;
              
              //没有显式调用初始化函数,运行结果未知
              //t1.initialize();
              
              printf("t1.i = %d
          ", t1.getI());  //7469952
              printf("t1.j = %d
          ", t1.getJ());  //134514267
              
              //没有立即调用,运行结果未知
              t1.initialize();
              
              Test* pt = new Test;
              
              //显式调用初始化函数
              pt->initialize();
              
              printf("pt->i = %d
          ", pt->getI());  //1
              printf("pt->j = %d
          ", pt->getJ());  //2
              
              delete pt;
              
              return 0;
          }
          
      • 存在的问题

        • initialize 只是一个普通函数,必须显式调用
        • 如果未调用 initialize 函数,运行结果是不确定的
        • 如果没有在生成类对象后立即调用 initialize 函数 ,运行结果是不确定的
    • 解决方案2:构造函数

    2 构造函数

    2.1 构造函数简介

    • C++ 中可以定义与类名相同的特殊的成员函数,这种特殊的成员函数叫做构造函数

      • 构造函数没有任何返回类型的声明
      • 构造函数在对象定义时自动被调用
    • 示例:构造函数

      • Demo

        #include <stdio.h>
        
        class Test
        {
        private:
            int i;
            int j;
        public:
            int getI() { return i; }
            int getJ() { return j; }
            //构造函数
            Test()
            {
                printf("Test() Begin
        ");
                
                i = 1;
                j = 2;
                
                printf("Test() End
        ");
            }
        };
        
        Test gt;
        
        int main()
        {
            printf("gt.i = %d
        ", gt.getI());
            printf("gt.j = %d
        ", gt.getJ());
            
            Test t1;
            
            printf("t1.i = %d
        ", t1.getI());
            printf("t1.j = %d
        ", t1.getJ());
            
            Test* pt = new Test;
            
            printf("pt->i = %d
        ", pt->getI());
            printf("pt->j = %d
        ", pt->getJ());
            
            delete pt;
            
            return 0;
        }
        
      • 编译运行

        Test() Begin
        Test() End
        gt.i = 1
        gt.j = 2
        Test() Begin
        Test() End
        t1.i = 1
        t1.j = 2
        Test() Begin
        Test() End
        pt->i = 1
        pt->j = 2
        

    2.2 带参数的构造函数

    • 带有参数的构造函数

      • 构造函数可以根据需要定义参数

      • 一个类中可以存在多个重载的构造函数

      • 构造函数的重载遵循 C++ 重载的规则

        class Test
        {
        public:
            Test(int c)
            {
                //use v to initialize member
            }
        };
        
    • 注意:对象定义对象声明不同

      • 对象定义:申请对象的空间并调用构造函数

      • 对象声明:告诉编译器存在这样一个对象

        Test t;  //定义对象并调用构造函数
        
        int main()
        {
            extern Test t;  //告诉编译器存在名为t的Test对象,通过链接器在各个目标文件中寻找t的定义
            
            return 0;
        }
        
    • 构造函数的自动调用

      class Test
      {
      public:
          Test(){}
          Test(int v){}
      };
      
      int main()
      {
          Test t;  //调用Test()
          Test t1(1);  //调用Test(int v)
          Test t2 = 1;  //调用Test(int v)
      }
      
    • 示例:带参数的构造函数

      • Demo

        #include <stdio.h>
        
        class Test
        {
        public:
            Test() 
            { 
                printf("Test()
        ");
            }
            Test(int v) 
            { 
                printf("Test(int v), v = %d
        ", v);
            }
        };
        
        int main()
        {
            Test t;      // 调用 Test()
            Test t1(1);  // 调用 Test(int v)
            Test t2 = 2; // 调用 Test(int v)
            
            int i(100);  //初始化(如果是类类型,会调用构造函数)
            
            int j;
            j = 2;  //赋值操作
            
            printf("i = %d
        ", i);
            
            return 0;
        }
        
      • 编译运行

        Test()
        Test(int v), v = 1
        Test(int v), v = 2
        i = 100
        
    • 构造函数的调用

      • 一般情况下,构造函数在对象定义时被自动调用
      • 一些特殊情况下,需要手工调用构造函数,例如创建一个对象数组
    • 示例:构造函数的手动调用 => 创建一个对象数组

      • Demo

        #include <stdio.h>
        
        class Test
        {
        private:
            int m_value;
        public:
            Test() {
                printf("Test()
        ");
                m_value = 0;
            }
            Test(int v) {
                printf("Test(int v),v = %d
        ", v);
                m_value = v;
            }
            int getValue() {
                return m_value;
            }
        };
        
        int main()
        {
            Test ta[3];
        
            for (int i = 0; i < 3; ++i) {
                printf("ta[%d].getValue() = %d
        ", i, ta[i].getValue());
            }
        
            return 0;
        }
        
      • 编译运行:可以看到对象数组 ta 中的每一个类对象的成员变量 m_value 值都相同:0

        Test()
        Test()
        Test()
        ta[0].getValue() = 0
        ta[1].getValue() = 0
        ta[2].getValue() = 0
        
      • 修改:创建对象值不同的对象数组

        #include <stdio.h>
        
        class Test
        {
        private:
            int m_value;
        public:
            Test() { 
                printf("Test()
        ");
                m_value = 0;
            }
            Test(int v) { 
                printf("Test(int v), v = %d
        ", v);
                m_value = v;
            }
            int getValue() {
                return m_value;
            }
        };
        
        int main()
        {
            Test ta[3] = {Test(), Test(1), Test(2)};  //手动调用构造函数
            
            for(int i=0; i<3; i++) {
                printf("ta[%d].getValue() = %d
        ", i , ta[i].getValue());
            }
            
            Test t = Test(100);  //手动调用构造函数 => 完成定义对象
            
            printf("t.getValue() = %d
        ", t.getValue());
            
            return 0;
        }
        
      • 编译运行

        Test()
        Test(int v), v = 1
        Test(int v), v = 2
        ta[0].getValue() = 0
        ta[1].getValue() = 1
        ta[2].getValue() = 2
        Test(int v), v = 100
        t.getValue() = 100
        
    • 需求:开发一个数组类解决原生数组的安全性问题

      • 提供函数获取数组长度

      • 提供函数获取数组元素

      • 提供函数设置数组元素

      • Demo:IntArray

        //IntArray.h
        #ifndef _INTARRAY_H_
        #define _INTARRAY_H_
        
        class IntArray
        {
        private:
            int m_length;  //长度
            int* m_pointer;  //数据
        public:
            IntArray(int len);  //构造函数
            int length();  //获取长度
            bool get(int index, int& value);  //获取数组指定位置的元素
            bool set(int index ,int value);  //设置数组指定位置的元素
            void free();  //释放所申请的堆空间
        };
        
        #endif
        
        
        //IntArray.cpp
        #include "IntArray.h"
        
        IntArray::IntArray(int len)
        {
            m_pointer = new int[len];
            
            for(int i=0; i<len; i++)
            {
                m_pointer[i] = 0;
            }
            
            m_length = len;
        }
        
        int IntArray::length()
        {
            return m_length;
        }
        
        bool IntArray::get(int index, int& value)
        {
            bool ret = (0 <= index) && (index < length());
            
            if( ret )
            {
                value = m_pointer[index];
            }
            
            return ret;
        }
        
        bool IntArray::set(int index, int value)
        {
            bool ret = (0 <= index) && (index < length());
            
            if( ret )
            {
                m_pointer[index] = value;
            }
            
            return ret;
        }
        
        void IntArray::free()
        {
            delete[] m_pointer;
        }
        
        
        //main.cpp
        #include <stdio.h>
        #include "IntArray.h"
        
        int main()
        {
            IntArray a(5);    
            
            for(int i=0; i<a.length(); i++)
            {
                a.set(i, i + 1);
            }
            
            for(int i=0; i<a.length(); i++)
            {
                int value = 0;
                
                if( a.get(i, value) )
                {
                    printf("a[%d] = %d
        ", i, value);
                }
            }
            
            a.free();
            
            return 0;
        }
        
      • 编译运行

        a[0] = 1
        a[1] = 2
        a[2] = 3
        a[3] = 4
        a[4] = 5
        

    2.3 无参构造函数和拷贝构造函数

    • 两个特殊的构造函数

      • 无参构造函数
        • 没有参数的构造函数
        • 当类中没有定义任何构造函数时,编译器会默认提供一个无参构造函数,并且其函数体为空
      • 拷贝构造函数
        • 参数为 const class_name& 的构造函数
        • 当类中没有定义拷贝构造函数时,编译器默认提供一个拷贝构造函数,简单的进行成员变量的值复制(浅拷贝)
    • 示例:特殊的构造函数

      • Demo

        #include <stdio.h>
        
        class Test
        {
        private:
            int i;
            int j;
        public:
            int getI()
            {
                return i;
            }
            int getJ()
            {
                return j;
            }
            /*Test(const Test& t)
            {
                i = t.i;
                j = t.j;
            }
            Test()
            {
            }*/
        };
        
        int main()
        {
            Test t1;
            Test t2 = t1;
            
            printf("t1.i = %d, t1.j = %d
        ", t1.getI(), t1.getJ());
            printf("t2.i = %d, t2.j = %d
        ", t2.getI(), t2.getJ());
            
            return 0;
        }
        
    • 拷贝构造函数的意义

      • 兼容 C 语言的初始化方式,形如:int i = 1; int j = i;
      • 初始化行为能够符合预期的逻辑:两个对象的状态一样
    • 深浅拷贝

      • 浅拷贝:拷贝后对象的物理状态相同,编译器提供的拷贝构造函数只进行浅拷贝
      • 深拷贝:拷贝后对象的逻辑状态相同
    • 示例:对象的初始化

      • Demo

        #include <stdio.h>
        
        class Test
        {
        private:
            int i;
            int j;
            int* p;
        public:
            int getI()
            {
                return i;
            }
            int getJ()
            {
                return j;
            }
            int* getP()
            {
                return p;
            }
            /*
            Test(const Test& t)
            {
                i = t.i;
                j = t.j;
                p = new int;
                
                *p = *t.p;
            }
            */
            Test(int v)
            {
                i = 1;
                j = 2;
                p = new int;
                
                *p = v;
            }
            void free()
            {
                delete p;
            }
        };
        
        int main()
        {
            Test t1(3);
            Test t2(t1);
            
            printf("t1.i = %d, t1.j = %d, *t1.p = %d
        ", t1.getI(), t1.getJ(), *t1.getP());
            printf("t2.i = %d, t2.j = %d, *t2.p = %d
        ", t2.getI(), t2.getJ(), *t2.getP());
            
            t1.free();
            t2.free();  //内存泄漏:0x8e6a008被释放了两次
            
            return 0;
        }
        
      • 编译运行

        t1.i = 1, t1.j = 2, t1.p = 0x8e6a008
        t2.i = 1, t2.j = 2, t2.p = 0x8e6a008
        
    • 问题:什么时候需要进行深拷贝?

      • 对象中有成员指代了系统中的资源

        • 成员指向了动态内存空间
        • 成员打开了外存中的文件
        • 成员使用了系统中的网络端口
        • 。。。。。。
      • 分析:t1t2m_pointer 指向同一块内存,之后会被释放两次

    • 一般性原则:自定义拷贝构造函数,必然需要实现深拷贝

    • IntArray 类的改进

      • Demo

        //IntArray.h
        #ifndef _INTARRAY_H_
        #define _INTARRAY_H_
        
        class IntArray
        {
        private:
            int m_length;
            int* m_pointer;
        public:
            IntArray(int len);
            IntArray(const IntArray& obj);  //拷贝构造函数
            int length();
            bool get(int index, int& value);
            bool set(int index ,int value);
            void free();
        };
        
        #endif
        
        
        //IntArray.cpp
        #include "IntArray.h"
        
        IntArray::IntArray(int len)
        {
            m_pointer = new int[len];
            
            for(int i=0; i<len; i++)
            {
                m_pointer[i] = 0;
            }
            
            m_length = len;
        }
        
        IntArray::IntArray(const IntArray& obj)
        {
            m_length = obj.m_length;
            
            m_pointer = new int[obj.m_length];
            
            for(int i=0; i<obj.m_length; i++)
            {
                m_pointer[i] = obj.m_pointer[i];
            }
        }
        
        int IntArray::length()
        {
            return m_length;
        }
        
        bool IntArray::get(int index, int& value)
        {
            bool ret = (0 <= index) && (index < length());
            
            if( ret )
            {
                value = m_pointer[index];
            }
            
            return ret;
        }
        
        bool IntArray::set(int index, int value)
        {
            bool ret = (0 <= index) && (index < length());
            
            if( ret )
            {
                m_pointer[index] = value;
            }
            
            return ret;
        }
        
        void IntArray::free()
        {
            delete[] m_pointer;
        }
        
        
        //main.cpp
        #include <stdio.h>
        #include "IntArray.h"
        
        int main()
        {
            IntArray a(5);    
            
            for(int i=0; i<a.length(); i++)
            {
                a.set(i, i + 1);
            }
            
            for(int i=0; i<a.length(); i++)
            {
                int value = 0;
                
                if( a.get(i, value) )
                {
                    printf("a[%d] = %d
        ", i, value);
                }
            }
            
            IntArray b = a;
            
            for(int i=0; i<b.length(); i++)
            {
                int value = 0;
                
                if( b.get(i, value) )
                {
                    printf("b[%d] = %d
        ", i, value);
                }
            }
            
            a.free();
            b.free();
            
            return 0;
        }
        
      • 编译运行

        1
        2
        3
        4
        5
        1
        2
        3
        4
        5
        
  • 相关阅读:
    DOM对象和jQuery对象的区别
    scrollLeft,scrollWidth,clientWidth,offsetWidth详解
    js数组去重
    变量和作用域的小结
    JS练习题之字符串一
    css实现布局
    将字符串或者数字转化成英文格式输出
    css元素居中实现方法
    不同的函数调用模式
    一个apply的实例
  • 原文地址:https://www.cnblogs.com/bky-hbq/p/13715195.html
Copyright © 2020-2023  润新知