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 可以解决这个错误。