两个特殊的构造函数
无参构造函数
- 没有参数的构造函数
拷贝构造函数
- 参数为 const class_name& 的构造函数
无参构造函数
- 当类中没有定义构造函数时,编译器提供一个无参构造函数,并且函数体为空
拷贝构造函数
- 当类中没有定义拷贝构造函数时,编译器默认提供一个拷贝构造函数,简单的进行成员变量的值复制
编程实验: 特殊的构造函数
Test_1.cpp
#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;
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 = 134514464, t1.j = -1081276856
t2.i = 2528036, t2.j = 2527220
第二次输出:
t1.i = 134514464, t1.j = -1081626328
t2.i = 3298084, t2.j = 3297268
第三次输出:
t1.i = 134514464, t1.j = -1080045768
t2.i = 9716516, t2.j = 9715700
分析:
类中没有定义构造函数,编译器提供一个无参构造函数,函数体为空,无法对成员变量初始值。打印栈空间中的随机值。
编译器在类中未发现构造函数,会在类最后添加无参构造函数:
Test()
{
}
test_2.cpp
#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; // 注意这里!
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 = 134514480, t1.j = -1081214552
t2.i = 134514480, t2.j = -1081214552
第二次输出:
t1.i = 134514480, t1.j = -1082006488
t2.i = 134514480, t2.j = -1082006488
第三次输出:
t1.i = 134514480, t1.j = -1077369784
t2.i = 134514480, t2.j = -1077369784
分析:类中没有定义拷贝构造函数,编译器提供一个拷贝构造函数,简单的进行成员变量的值复制。
t1.i == t2.i
t1.i == t2.i
编译器在类中未发现拷贝构造函数,会在类最后添加简单的拷贝构造函数:
Test(const Test& t)
{
i = t.i;
j = t.j;
}
class Test
{
private:
int i;
int j;
public:
int getI()
{
return i;
}
int getJ()
{
return j;
}
};
<==>
class Test
{
private:
int i;
int j;
public:
int getI()
{
return i;
}
int getJ()
{
return j;
}
Test() // 无参构造函数,函数体为空
{
}
Test(const Test& t) // 拷贝构造函数,进行简单的成员变量值复制
{
i = t.i;
j = t.j;
}
};
经典的面试问题
class T
{
};
==>
class T
{
public:
T();
T(const T&);
T& operator = (const T&);
~T();
};
问: T 中包含什么?
答: 无参构造函数、拷贝构造函数、析构函数、赋值操作符重载函数。
拷贝构造函数
拷贝构造函数的意义
- 兼容 C 语言的初始化方式
- 初始化行为符合预期的逻辑
浅拷贝
- 拷贝后对象的物理状态相同(两对象占用的内存空间中,每个字节的值相等)
深拷贝
- 拷贝后对象的逻辑状态相同
编译器提供的拷贝构造函数只进行浅拷贝!
编程实验: 对象的初始化
浅拷贝的现象:test_1.cpp
#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; // ==> 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());
return 0;
}
输出:
t1.i = 1, t1.j = 2, t1.p = 0x950c008, *t1.p = 3
t2.i = 1, t2.j = 2, t2.p = 0x950c008, *t2.p = 3
分析:
类中没有定义拷贝构造函数,编译器提供一个拷贝构造函数,简单的进行成员变量的复制。
t1.p = 0x950c008,t2.p = 0x950c008 指向同一内存空间,而没有为 t2.p 重新分配空间,不是期望结果。
浅拷贝的问题: test_2.cpp
#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; // ==> 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;
}
输出:
内存错误
深拷贝实例: test_3.cpp
#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;
}
Test(const Test& t)
{
i = t.i;
j = t.j;
p = new int; // 深拷贝,重新申请内存空间
*p = *t.p;
}
void free()
{
delete p;
}
};
int main()
{
Test t1(3);
Test t2 = t1; // ==> 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;
}
输出:
t1.i = 1, t1.j = 2, t1.p = 0x927b008, *t1.p = 3
t2.i = 1, t2.j = 2, t2.p = 0x927b018, *t2.p = 3
分析:
t1.p = 0x927b008, t2.p = 0x927b018 指向不同的内存空间。指向的内存空间中的值相同。
什么时候需要进行深拷贝?
对象中有成员指代了系统中的资源
- 成员指向了动态内存空间
- 成员打开了外存中的文件
- 成员使用了系统中的网络端口
- 。。。。。
问题分析
浅拷贝只进行简单的成员变量值复制,所以发生 t2.m_pointer=t1.m_pointer;
两对象成员变量指向同一片内存空间而没有发生新的内存申请,在 free 时,对同一片内存空间释放两次,导致运行时内存出错。
一般原则
- 自定义拷贝构造函数,必须需要实现深拷贝!!
编程实验:数组类的改进
IntArray.h
#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
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::get(int index, int& value)
{
bool ret = (index >= 0) && (index < length());
if( ret )
{
value = m_pointer[index];
}
return ret;
}
bool IntArray::set(int index, int value)
{
bool ret = (index >= 0) && (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);
}
}
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;
}
输出:
a.[0] = 1
a.[1] = 2
a.[2] = 3
a.[3] = 4
a.[4] = 5
b.[0] = 1
b.[1] = 2
b.[2] = 3
b.[3] = 4
b.[4] = 5
小结
- C++ 编译器会默认提供构造函数
- 无参构造函数用于定义对象的默认初始状态
- 拷贝构造函数在创建对象时拷贝对象的状态
对象的拷贝有浅拷贝和深拷贝两种方式
- 浅拷贝使得对象的物理状态相同
- 深拷贝使得对象的逻辑状态相同
以上内容参考狄泰软件学院系列课程,请大家保护原创!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。