十七、对象的构造

1、成员变量的初始值

#include <stdio.h>

class Test
{
private:
    int i;
    int j;
public:
    int getI() { return i; }
    int getJ() { return j; }
};

Test gt;        // 全局对象 全局区,统一初始值为0

int main()
{
    printf("gt.i = %d\n", gt.getI()); // 0
    printf("gt.j = %d\n", gt.getJ()); // 0
    
    Test t1;    // 局部对象 栈区
    
    printf("t1.i = %d\n", t1.getI()); // 随机值
    printf("t1.j = %d\n", t1.getJ()); // 随机值
    
    Test* pt = new Test;    // 类也是一个数据类型,堆区
    
    printf("pt->i = %d\n", pt->getI()); // 堆区应该也是随机值
    printf("pt->j = %d\n", pt->getJ());
    
    delete pt;
    
    return 0;
}

2、对象的初始化

从程序设计的角度,对象只是变量,因此:

  • 在栈上创建对象时,成员变量初始为随机值
  • 在堆上创建对象时,成员变量初始为随机值
  • 在静态存储区创建对象时,成员变量初始为0

静态存储区包括了全局变量和static修饰的局部变量

需要解决的问题:使类的成员变量不管在哪个存储区进行定义,它的初始值都是固定的。

对象的初始化:

  • 一般而言,对象都需要一个确定的初始状态
  • 解决方案:
    • 在类中提供一个publicinitialize函数
    • 在对象创建后立即调用initialize函数进行初始化
#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\n", gt.getI());
    printf("gt.j = %d\n", gt.getJ());
    
    Test t1; 
    t1.initialize();  
    printf("t1.i = %d\n", t1.getI());
    printf("t1.j = %d\n", t1.getJ());
    
    Test* pt = new Test; 
    pt->initialize();
    printf("pt->i = %d\n", pt->getI());
    printf("pt->j = %d\n", pt->getJ());
    delete pt;

    return 0;
}

这种方式存在的问题:

  • initialize只是一个普通函数,必须显示调用
  • 如果未调用initialize函数,运行结果是不确定的

这个初始化函数在对象创建之手就必须马上调用,新建对象之手,需要人工手动添加initialize()函数,如果可以有一个函数在创建对象后自动调用,初始化成员变量就是极好的。

于是C++出现了构造函数来解决这个问题

3、构造函数

C++中可以定义与类名相同的特殊成员函数:构造函数

  • 构造函数没有任何返回类型的声明
  • 构造函数在对象定义时自动被调用
#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() {
        i = 1;
        j = 2;
    }
};

Test gt;

int main()
{
    //gt.initialize();
    printf("gt.i = %d, gt.j = %d\n", gt.getI(), gt.getJ());

    Test t1;
    //t1.initialize();
    printf("t1.i = %d, t1.j = %d\n", t1.getI(), t1.getJ());

    Test * pt = new Test;
    //pt->initialize();
    printf("pt->i = %d, pt->j = %d\n", pt->getI(), pt->getJ());

    return 0;
}

4、带参数的构造函数

构造函数和普通函数的差别:构造函数没有返回值,名字和类型一样

此时就只剩下参数可以讨论:构造函数也可以带参数

带有参数的构造函数:

扫描二维码关注公众号,回复: 3174796 查看本文章
  • 构造函数可以根据需要定义参数
  • 一个类中可以存在多个重载的构造函数
  • 构造函数的重载遵循C++重载的规则
class Test
{
public:
    Test(int v)
    {
        // use v to initialize member
    }
};

注意:

对象定义和对象声明不同:

  • 对象定义——申请对象的空间并调用构造函数
  • 对象声明——告诉编译器存在这样一个对象
Test t; // 定义对象并调用构造函数

int main()
{
    // 告诉编译器存在名为t的Test对象
    extern Test t;
    
    return 0;
}

构造函数的自动调用

class Test {
public:
    Test(){}
    Test(int v) { }
    Test(const int& cv){}   // 拷贝构造函数
};  

Test t;         // 调用构造函数Test()
Test t1(1);     // 定义了一个对象t1,并调用带有参数的构造函数,传入参数为1,根据重载规则,构造函数为Test(int v)
Test t2 = 1;    // 用 1 来初始化对象t2,初始化需要借助构造函数,根据重载规则,选择Test(int v)
/*这里的过程其实是:
首先调用构造函数Test(int v)创建一个临时对象,参数为1;
然后就变成了用一个对象初始化另一个对象,此时应该是要调用拷贝构造函数进行成员变量值的复制,将这个临时对象作为参数用来构造对象t2。
但是编译器发现,可以通过重载的构造函数Test(int v)来直接初始化对象,而达到相同效果,所以将这条语句优化为Test t1(1)
    
*/

初始化和赋值:

#include <stdio.h>

class Test
{
public:
    Test() 
    { 
        printf("Test()\n");
    }
    Test(int v) 
    { 
        printf("Test(int v), v = %d\n", v);
    }
};

int main()
{
    Test t;      // 调用 Test()
    Test t1(1);  // 调用 Test(int v)
    Test t2 = 2; // 调用 Test(int v)
    
    
    int i = 1;  // 用1来初始化变量i
    i = 2;      // 用2对变量i进行赋值
    
    t = t2;     // 用对象t2对对象t进行赋值
    
    int i(100); // 用100来初始化i
    
    printf("i = %d\n", i);
    
    return 0;
}

初始化和赋值是不一样的,C语言中差别不大,C++中差别很大,因为对象的初始化要调用构造函数

构造函数的调用:

  • 一般情况下,构造函数在对象定义时被自动调用
  • 一些特殊情况下,需要手工调用构造函数

5、创建一个数组

#include <stdio.h>

class Test
{
private:
    int m_value;
public:
    Test()
    { 
        printf("Test()\n");
        
        m_value = 0;
    }
    Test(int v) 
    { 
        printf("Test(int v), v = %d\n", v);
        
        m_value = v;
    }
    void getValue()
    {
        return m_value;
    }
};

int main()
{
    Test ta[3]; // 调用3次Test() ,每个数组元素中的m_value都按Test()来处理,不一定需要这样的结果
    Test ta2[3] = {Test(), Test(1), Test(2)};   // 手工调用构造函数,3个数组元素调用不同的构造函数
    
    for (int i = 0; i < 3; i++)
    {
        printf("ta[%d].getValue() = %d\n", i, ta[i].getValue());    
        // 手工调用构造函数后,m_value初始化成不同值
    }
    
    Test t = Test(100); // 创建对象之后,调用构造函数来初始化对象
    
    return 0;
}

需求:开发一个数组类解决原生数组的安全性问题

  • 提供函数获取数组长度
  • 提供函数获取函数元素
  • 提供函数设置数组元素
// 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.c
#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.c
#include <stdio.h>
#include "IntArray.h"
int main()
{
    IntArray a(5);  // 定义了一个对象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\n", i, value);
        }
    }

    a.free();
    
    return 0;
}

6、小结

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

构造函数之间可以存在重载关系

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

对象定义时会触发构造函数的调用

在一些情况下可以手动调用构造函数

猜你喜欢

转载自www.cnblogs.com/chenke1731/p/9643695.html