对象的构造

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

小实验:下面的类定义中成员变量i和j的初始值是多少?

这里写图片描述

编程实验:成员变量的初始值

#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\n", gt.getI());
    printf("gt.j = %d\n", gt.getJ());

    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;
}
  • 从程序设计的角度,对象只是变量,因此:
    • 在栈上创建对象时,成员变量初始为随机值;
    • 在堆上创建变量时,成员变量初始为随机值;
    • 在静态存储区创建变量时,成员变量初始为0值。

2 对象的初始化

  • 生活中的对象都是在初始化之后上市的。
  • 初始状态(出厂设置)是对象普遍存在的一个状态。

问题:程序中如何对一个对象进行初始化?

  • 一般而言,对象都需要一个确定的初识状态。
  • 解决方案:
    • 在类中提供一个public的initialize函数;
    • 对象创建后立即调用intialize函数进行初始化。

这里写图片描述

编程实验:初始化函数

#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());

    t1.initialize();

    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函数,运行结果是不确定的。

3 构造函数

  • C++ 中可以定义与类名相同的特殊成员函数:这种特殊的成员函数叫做构造函数。
    • 构造没有任何返回类型的声明。
    • 构造函数在对象定义时自动被调用。

编程实验:构造函数初探

#include <stdio.h>

class Test
{
private:
    int i;
    int j;
public:
    int getI() { return i; }
    int getJ() { return j; }
    Test()
    {
        printf("Test() Begin\n");

        i = 1;
        j = 2;

        printf("Test() End\n");
    }
};

Test gt;

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

    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;
}

小结

  • 每个对象在使用之前都应该初始化。
  • 类的构造函数用于对象的初始化。
  • 构造函数与类同名并且没有返回值。
  • 构造函数在对象定义时自动被调用。

4 带参构造函数

  • 带有参数的构造函数:
    • 构造函数可以根据需要定义参数;
    • 一个类中可以存在多个重载的构造函数;
    • 构造函数的重载遵循C++重载的规则。

这里写图片描述

友情提醒

  • 对象定义和对象声明不同:
    • 对象定义:申请对象的空间并调用构造函数;
    • 对象声明:告诉编译器存在这样一个对象。

这里写图片描述

构造函数的自动调用

这里写图片描述

注意:构造函数必须为public。

编程实验:带参数的构造函数

#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(100);

    printf("i = %d\n", i);

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

问题:如何创建一个对象数组?

编程实验:构造函数的手动调用

#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;
    }
    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\n", i , ta[i].getValue());
    }

    Test t = Test(100);

    printf("t.getValue() = %d\n", t.getValue());

    return 0;
}

对象初始化的几种方式

  • Test a; // Test()
  • Test a = 1; or Test a(1); // Test(int v)
  • Test a = Test(1); // Test(v)

小实例

  • 需求:开发一个数组类解决原生数组的安全性问题
    • 提供函数获取数组长度;
    • 提供函数获取数组元素;
    • 提供函数设置数组元素。

编程实验:数组类的实现

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\n", i, value);
        }
    }

    a.free();

    return 0;
}

小结

  • 构造函数可以根据需要定义参数。
  • 构造函数之间可以存在重载关系。
  • 构造函数遵循C++中重载函数的规则。
  • 对象定义时会触发构造函数的调用。
  • 在一些情况下可以手动调用构造函数。

5 特殊的构造函数

两个特殊的构造函数

  • 无参构造函数
    • 没有参数的构造函数。
    • 当类中没有定义构造函数(包括拷贝构造函数)时,编译器默认提供一个无参构造函数,并且函数体为空。
  • 拷贝构造函数
    • 参数为cosnt class_name&的构造函数。
    • 当类中没有定义拷贝构造函数时,编译器默认提供一个拷贝构造函数,简单的进行成员变量的值复制。

这里写图片描述

编程实验:特殊的构造函数

#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\n", t1.getI(), t1.getJ());
    printf("t2.i = %d, t2.j = %d\n", t2.getI(), t2.getJ());

    return 0;
}

6 拷贝构造函数

  • 拷贝构造函数的意义
    • 兼容C语言的初始化方式。
    • 初始化行为能够符合预期的逻辑。

拷贝构造函数的分类

  • 浅拷贝:拷贝后对象的物理状态相同。
  • 深拷贝:拷贝后对象的逻辑状态相同。

注意:编译器提供的拷贝构造函数只进行浅拷贝!

编程实验:对象的初始化

#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\n", t1.getI(), t1.getJ(), *t1.getP());
    printf("t2.i = %d, t2.j = %d, *t2.p = %d\n", t2.getI(), t2.getJ(), *t2.getP());

    t1.free();
    t2.free();

    return 0;
}

什么时候需要进行深拷贝

  • 对象中有成员指代了系统中的资源:
    • 成员指向了动态内存空间;
    • 成员打开了外存中的文件;
    • 成员使用了系统中的网络端口;
    • ……

问题分析

这里写图片描述

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

数组类的改进

增加深拷贝构造函数

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];
    }
}

小结

  • C++编译器会默认提供构造函数。
  • 无参构造函数用于定义对象的默认初始状态。
  • 拷贝构造函数在创建对象时拷贝对象的状态。
  • 对象的拷贝有浅拷贝和深拷贝两种方式:
    • 浅拷贝使得对象的物理状态相同;
    • 深拷贝使得对象的逻辑状态相同。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值