17-18-19 - 对象的构造

---- 整理自狄泰软件唐佐林老师课程

1.问题

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

在这里插入图片描述

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

#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.getI() = %d\n", gt.getI());
    printf("gt.getJ() = %d\n", gt.getJ());
    
    Test lt;
	printf("lt.getI() = %d\n", lt.getI());
    printf("lt.getJ() = %d\n", lt.getJ());
	
	Test* pt = new Test;
	printf("pt->getI() = %d\n", pt->getI());
    printf("pt->getJ() = %d\n", pt->getJ());
	delete pt;
    
    return 0;
}

在这里插入图片描述

1.2 对象的初始化

  • 从程序设计的角度,对象只是 变量,因此:
    • 上创建对象时,成员变量初始为 随机值
    • 上创建对象时,成员变量初始为 随机值
    • 静态存储区 创建对象时,成员变量初始为 0

1.2.1 问题:如何对一个对象进行初始化?

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

在这里插入图片描述

1.2.2 编程实验:初始化函数

#include <stdio.h>

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

Test gt;

int main()
{
	gt.init();
    printf("gt.getI() = %d\n", gt.getI());
    printf("gt.getJ() = %d\n", gt.getJ());
    
    Test lt;
	lt.init();
	printf("lt.getI() = %d\n", lt.getI());
    printf("lt.getJ() = %d\n", lt.getJ());
	
	Test* pt = new Test;
	pt->init();
	printf("pt->getI() = %d\n", pt->getI());
    printf("pt->getJ() = %d\n", pt->getJ());
	delete pt;
    
    return 0;
}

在这里插入图片描述

2. 构造函数

2.1 上述实验存在的问题

  • initialize 只是一个普通函数,必须显示调用
  • 如果未调用 initialize 函数,运行结果是不确定的
  • C++ 中可以定义 与类名相同 的特殊函数,这种特殊的成员函数叫做 构造函数
    • 构造函数 与类同名并且没有返回值
    • 构造函数在 对象定义时自动被调用

2.2 编程实验:构造函数

#include <stdio.h>

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

Test gt;

int main()
{
    printf("gt.getI() = %d\n", gt.getI());
    printf("gt.getJ() = %d\n", gt.getJ());
    
    Test lt;
	printf("lt.getI() = %d\n", lt.getI());
    printf("lt.getJ() = %d\n", lt.getJ());
	
	Test* pt = new Test;
	printf("pt->getI() = %d\n", pt->getI());
    printf("pt->getJ() = %d\n", pt->getJ());
	delete pt;
    
    return 0;
}

在这里插入图片描述

2.3 带有参数的构造函数

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

在这里插入图片描述

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

在这里插入图片描述

2.4 构造函数的自动调用

在这里插入图片描述

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

#include <stdio.h>

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

int main()
{    
    Test t;
    Test t1(1);
    Test t2 = 2;
	
	int i(100); // 等价于 int = 100;
	int j = 100;
	printf("i = %d\n", i);
	printf("j = %d\n", j);

	return 0;
}

在这里插入图片描述

2.6 构造函数的调用

  • 一般情况下,对象定义时会自动触发构造函数的调用
  • 一些特殊情况下,需要手动调用构造函数

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

  • 问题:如何创建一个对象数组?
#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 get_value() {
		return m_value;
	}
};

int main()
{    
    Test t[3];
	
	for (int i = 0; i < 3; i++) {
        printf("t[%d].get_value() = %d\n", i, t[i].get_value());
	}

	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 get_value() {
		return m_value;
	}
};

int main()
{    
    Test t[3] = {Test(), Test(1), Test(2)}; // 手动调用构造函数
	
	for (int i = 0; i < 3; i++) {
        printf("t[%d].get_value() = %d\n", i, t[i].get_value());
	}

	return 0;
}

在这里插入图片描述

2.8 小实例:开发一个数组类解决原生数组的安全性问题

  • 提供函数获取数组长度
  • 提供函数获取数组元素
  • 提供函数设置数组元素

2.8.1 编程实验:数组类的实现

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

2.8.2 实验结果

在这里插入图片描述

2.9 特殊的构造函数

  • 两个特殊的构造函数
    • 无参构造函数:没有参数的构造函数
    • 拷贝构造函数:参数为 const class_name& 的构造函数

2.9.1 无参构造函数

  • 当类中 没有定义构造函数 时,编译器 默认提供 一个无参构造函数,并且其 函数体

2.9.2 拷贝构造函数

  • 当类中 没有定义拷贝构造函数 时,编译器 默认提供 一个拷贝构造函数,简单的进行 成员变量的值复制

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

#include <stdio.h>

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

int main()
{    
    Test t1;
	Test t2 = t1; // 用一个对象给另一个对象初始化
				  // 兼容了 C 语言的初始化方式
    
	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;
}

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

2.10 拷贝构造函数

2.10.1 拷贝构造函数的意义

  • 兼容 C 语言的初始化方式
  • 初始化行为能够符合预期的逻辑

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

注:
  深拷贝和浅拷贝的区别:浅拷贝 主要是对 指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝 需要不但对指针进行拷贝,并对 指针指向的内容 进行拷贝,经过深拷贝后的指针是指向两个不同地址的指针。
  浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

2.10.2 编程实验:对象的初始化

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

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());
    
    return 0;
}

在这里插入图片描述

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

在这里插入图片描述

  • 解决方法:
#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 = %p, *t1.p = %d\n", 
			t1.getI(), t1.getJ(), t1.getP(), *t1.getP());
    printf("t2.i = %d, t2.j = %d, t2.p = %p, *t2.p = %d\n", 
			t2.getI(), t2.getJ(), t2.getP(), *t2.getP());
    
    t1.free();
    t2.free();
    
    return 0;
}

在这里插入图片描述

2.10.3 什么时候进行深拷贝?

  • 对象中 有成员指代了系统中的资源 ==> 深拷贝
    • 成员指向了动态内存空间(如上例中的堆空间)
    • 成员打开了外存中的文件
    • 成员使用了系统中的网络端口
    • ……

2.10.4 问题分析

在这里插入图片描述

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

2.10.5 编程实验:数组类的改进

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

    IntArray b(a);
    for(int i=0; i<b.length(); i++)
    {
        int value = 0;
        if (b.get(i, value))
        {
            printf("b[%d] = %d\n", i, value);
        }
    }

    a.free();
    b.free();
    return 0;
}

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

uuxiang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值