18、对象的构造(下)

1、特殊的构造函数

  • 两个特殊的构造函数
    — 无参构造函数
       没有参数的构造函数
       当类中没有定义构造函数时,编译器默认提供一个无参构造函数,并且其函数体为空
    — 拷贝构造函数
       参数为 const class_name& 的构造函数
       当类中没有定义拷贝构造函数时,编译器默认提供一个拷贝构造函数,简单的进行成员变量的值复制
#include <stdio.h>
class Test
{
private:
	int i;
	int j;
public:
	
	int getI()
	{
		return i;
	}
	int getJ()
	{
		return j;
	}
};
int main()
{
	Test t;  

	return 0;
}

分析:创建一个类的对象必须要调用一个构造函数,但程序中并没有定义构造函数,所以编译器在编译完这个类就会主动加上这三行代码,也就是会默认提供一个无参构造函数。

经典的面试题:这个类里面有些什么东西?

class T
{
};

答:至少有一个无参构造函数

我觉得很重要的一点就是:无论是构造函数亦或是拷贝构造函数,它们在某种程度上其实都是构造函数,所以它们的使用范围都是在对象刚创建的时候才会去调用构造函数。

所以当我们只定义拷贝构造函数,而不定义构造函数时程序会报错。因为编译器提供默认的无参构造函数的条件是 “当类中没有定义构造函数时” ,而拷贝构造函数也是一种构造函数,所以编译器就不提供默认的无参构造函数。所以拷贝构造函数要和构造函数同时定义。

#include <stdio.h>
class Test
{
private:
	int i;
	int j;
public:
	Test(const Test& obj)
	{
		i = obj.i;
		j = obj.j;
	}
	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;
}

在这里插入图片描述
这就是我上面提出的问题,当我们把拷贝构造函数要和构造函数同时定义,程序就不会报错。

#include <stdio.h>
class Test
{
private:
	int i;
	int j;
public:
	Test()
	{

	}
	Test(const Test& obj)
	{
		i = obj.i;
		j = obj.j;
	}
	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;
}

在这里插入图片描述

  • 个人分析一下拷贝构造函数里面的参数:Test(const Test& obj)
    我们可以看到拷贝构造函数里面是这样写的
Test(const Test& obj)
{
	i = obj.i;
	j = obj.j;
}

这个程序让我想起了构造函数的重载

Test(int V)
{
	i = v;
}

我们再来扩展一下这个程序

Test(const Test& obj)
{
	i = obj.i;
	j = obj.j;
}
int main()
{
	Test t1;
	Test t2 = t1;  //Test t2(t1);
}

从上面这个程序的第9条语句 //后面的Test t2(t1)我们可以大概率的理解大体上是什么意思。参数代表的是一个const 引用对象,其实就是代表值拷贝的意思。

2、拷贝构造函数

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

— 浅拷贝
   拷贝后对象的物理状态相同(值的复制)
— 深拷贝
   拷贝后对象的逻辑状态相同(涉及地址)

编译器提供的拷贝构造函数只进行浅拷贝
如下面程序:

#include <stdio.h>
class Test
{
private:
	int i;
	int j;
	int* p;
public:
	Test(int v)
	{
		i = 1;
		j = 2;
		p = new int;
		*p = v;
	}

	int getI()
	{
		return i;
	}
	int getJ()
	{
		return j;
	}
	int* getP()
	{
		return p;
	}
	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;
}

在这里插入图片描述
结果:程序崩溃,因为我们对同一个内存地址 delete 了两次。

明显可以看出,只进行值的复制,而对地址值却没改变,按理说你要生成一块的新的地址来存放复制的值。
t1 对象里面的 p 指针是要指向堆空间的某个内存地址,当用 t1 来初始化 t2 的时候,t2对象里面的 p 指针也应该指向堆空间不同的内存地址。所以我们要手动调用拷贝构造函数。

Test(const Test& obj)
{
	i = obj.i;
	j = obj.j;
	p = new int;
	*p = *obj.p;
}

程序:

#include <stdio.h>
class Test
{
private:
	int i;
	int j;
	int* p;
public:
	Test(int v)
	{
		i = 1;
		j = 2;
		p = new int;
		*p = v;
	}
	Test(const Test& obj)  //obj对应Test t2 = t1的t1
	{				  	   //前面的i,j,p都是t2对象里面的值
		i = obj.i;
		j = obj.j;
		p = new int;
		*p = *obj.p;
	}
	int getI()
	{
		return i;
	}
	int getJ()
	{
		return j;
	}
	int* getP()
	{
		return p;
	}
	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;
}

在这里插入图片描述
这样地址值就会不同,这样写就是深拷贝。

  • 什么时候需要进行深拷贝?
    — 对象中有成员指代了系统中的资源
       成员指向了动态内存空间
       成员打开了外存中的文件
       成员使用了系统中的网络端口
  • 问题分析
    在这里插入图片描述
  • 一般性原则:自定义拷贝构造函数,必然需要实现深拷贝

3、数组类的改进

IntArray.h:

#ifndef _IntArray_H_
#define _IntArray_H_
#pragma once
class IntArray
{
private:
	int m_length;
	int* m_pointer;
public:
	IntArray(int len);
	IntArray(const IntArray& obj);
	int length();
	bool set(int index, int value);
	bool get(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::set(int index, int value)
{
	bool ret = ((0 <= index) && (index < m_length));
	if (ret)
	{
		 m_pointer[index] = value;
	}
	return ret;
}
bool IntArray::get(int index, int& value)
{
	bool ret = ((0 <= index) && (index < m_length));
	if (ret)
	{
		value = m_pointer[index];
	}
	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);
		}
	}
	printf("\n");
	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;
}

在这里插入图片描述

小结:

  • C++编译器会默认提供构造函数
  • 无参构造函数用于定义对象的默认初始状态
  • 拷贝构造函数在创建对象时拷贝对象的状态
  • 对象的拷贝有浅拷贝和深拷贝两种
    — 浅拷贝使得对象的物理状态相同
    — 深拷贝使得对象的逻辑状态相同

分析错误:
为什么这样写会报错?
在这里插入图片描述
这里其实涉及到后面那个const 函数的知识,const 对象 好像只能调用 const 成员函数。所以我们可以在 length()后面加上一个const 可以解决这个错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值