对象和类——对象的构造

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

示例代码:成员变量的初始值

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

输出结果:
gt.i = 0
gt.j = 0
t1.i = 14998400
t1.j = 134514203
pt->i = 0
pt->j = 0

分析:
从程序设计的角度来看,对象也只是变量的一种。根据C语言变量的存储方式,我们也可以推测得出
1. Test gt; 是全局变量,也就是在静态存储区创建对象时,成员变量初始为0值。
2. Test t1; 是局部变量,也就是在栈上创建对象时,成员变量初始为随机值
3. Test t2; 是在堆空间申请的变量,成员变量初始为随机值

如何对对象的成员变量进行初始化?

C语言的解决方案

一般而言,我们在C语言是如何初始化结构体里的变量的呢?
就是专门写一个函数,在函数体里对结构体内的成员进行初始化。

根据C语言的方法,我们可以在类中提供一个初始化函数
那么我们在C++类中也可以提供一个初始化函数,专门将成员变量进行初始化。
但是这种方式也有缺陷:每次定义一个对象,我们就需要调用这个初始化函数。一旦我们忘记调用这个初始化函数,导致运行结果是不确定的。

C++构造函数解决成员变量初始化问题

为了解决上面的问题。C++提供了一个与类名相同的特殊成员函数,这个特殊的成员函数叫做构造函数。
1. 构造函数没有任何返回类型的声明
2. 构造函数在对象定义时自动被调用

示例代码:构造函数初探

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

输出结果:
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

分析:
1. 在程序中,我们可以看出,构造函数在对象定义时自动被调用
2. 类的构造函数用于对象的初始化
3. 构造函数与类同名并且没有返回值

多种类型的构造函数

我们上面介绍的构造函数也有缺陷的。因为除了定义类的对象如何初始化之外,类还需要控制拷贝、赋值和销毁对象时发生的行为。

那么我们如何让构造函数能适应各种各样的场合呢?
1. 类对象直接的赋值
2. 构造函数可以根据需要定义参数

带参数的构造函数

因为当我们需要创建多个对象时,每个对象的成员变量的初始值都是一样的。这样并不能适用一些特定的情况。

示例代码:带参数的构造函数

#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)
    Test t3 = Test(3);  // 调用 Test(int v)

    int i(100);

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

    return 0;
}

输出结果:

Test()
Test(int v), v = 1
Test(int v), v = 2
Test(int v), v = 3
i = 100

分析:
1. 一个类中可以存在多个构造函数,前提是参数个数和参数类型不同(基于函数重载规则)
2. 构造函数的初始化与普通变量的初始化一样。(构造函数参数有多少个,初始化参数也必须有多少个)
3. 当没有定义任何构造函数时,编译器才会提供默认构造函数。所以当我们定义了带有参数的构造函数时,必须为它提供一个默认构造函数,否则编译出错。

拷贝构造函数

对于普通类型的对象来说,他们之间的复制是很简单的,例如:

int a = 100;
int b = a;

问题:那么我们类对象也可以像普通对象一样,直接将类对象的值拷贝过去吗?

示例代码:类对象拷贝的简单例子

#include <stdio.h>

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

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

输出结果:
t1.i = 1, t1.j = 2
t2.i = 1, t2.j = 2

分析:
系统是如何给test 对象里的成员函数对应拷贝的呢?如何准确的吧各个成员变量进行拷贝的呢?
实际上,编译器会我们提供一个拷贝构造函数来完成整个复制了

问题:编译器为我们提供的拷贝构造函数并不能完全满足我们的工作需求,那么我们如何显示定义拷贝构造函数呢?

示例代码:自定义拷贝构造函数

//在上面代码的类成员中,加入下面代码即可
    Test(const Test& t)
    {
        i = t.i;
        j = t.j;
    }

Test(const Test& t)就是我们自定义的拷贝构造函数。
可见,拷贝构造函数是一种特殊的构造函数,函数名称必须和类名相同,它的参数必须是本类型的一个引用变量
实际上,我们自定义的拷贝构造函数可以选择哪些参数需要拷贝,哪些参数不拷贝。

注意:在编程中我们尽量要使用自定义拷贝函数,不仅可以选择需要拷贝的类成员,也可以阻止编译器形成默认的拷贝构造函数

浅拷贝(拷贝构造函数资源冲突)

问题:当类中有指针成员时,使用默认拷贝构造函数会有什么问题吗?

示例代码:带指针成员的拷贝构造函数

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

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

    return 0;
}

输出结果:
t1.i = 1, t1.j = 2, *t1.p = 0x871f008
t2.i = 1, t2.j = 2, *t2.p = 0x871f008
* glibc detected ./a.out: double free or corruption (fasttop): 0x0871f008 **
//后面还有一大串数据,关于内存的

分析:
1. 从输出结果可以知道:当类中有指针成员时,使用系统默认创建该拷贝构造函数会出现内存操作错误。
2. 从t1.getP()和t2.getP()打印数据可以知道:指针指向地址是相同的,都是0x871f008。所以当后面我们free()释放内存时,就会出现内存重复释放的情况。导致运行出错。

C++对于这种情况解析为:浅拷贝,也就是说拷贝后对象的物理状态相同(共用一个资源)。所以编译器提供的拷贝构造函数只进行浅拷贝。

深拷贝(解决构造函数资源问题)

问题:我们如何解决浅拷贝造成的问题?

依然是自定义拷贝构造函数,在拷贝构造里需要重新申请资源。

示例代码:浅拷贝的解决方案

   //添加这段代码到上面代码的类中即可
    Test(const Test& t)
    {
        i = t.i;
        j = t.j;
        p = new int;

        *p = *t.p;
    }
深拷贝的应用场景
  1. 对象中有成员指代了系统中的资源
  2. 成员指向了动态内存空间
  3. 成员打开了外存中的文件
  4. 成员使用了系统中的网络端口
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值