分享的内容
类对象的成员经常需要动态分配内存。例如int* path.用于保存遍历的节点,申请不同的字节长度满足规模大小不同的需求。
如同默认构造函数,当没有定义拷贝(复制)构造函数、赋值运算符时,系统提供默认的拷贝(复制)构造函数、赋值运算符,但是只是简单地将一个对象的数据成员给另一个对象的数据成员进行初始化/赋值(浅拷贝)。当成员有指针时,指针的值(指向的内存块地址)也会一致,两个指针就指向同一内存块。需要显式定义,在函数中重新申请内存(深拷贝),让指针指向申请的内存块,再赋值。
错误的程序例子:
#include
#include
using namespace std;
class Test
{
public:
Test();
Test(int n);
~Test();
void show();
private:
int length;
int* path;
};
Test::Test():
length(0)
{
cout << "Copy_String::Default Constrution Called." << endl;
path = NULL;
}
Test::Test(int n)
{
cout << "Copy_String::Constrution Called." << endl;
length = n;
path = NULL;//看上去可能多此一举,但是如果new数据内存分配失败,还可以在其他地方先行判断if(path==NULL)
path = new int[n];
if (path != NULL)
{
int i;
for (i = 0; i < length; i++)
{
path[i] = i;
}
}
else {
cout << "path[] alloc failed.\n";
}
}
Test::~Test()
{
cout << "Copy_String::Destruction Called." << endl;
cout << endl;
if (path != NULL)
{
delete[] path;
path = NULL;
}
}
void Test::show()
{
cout << "length:" << length << endl;
cout << "path:";
int i;
for (i = 0; i < length; i++)
{
cout << path[i] << " ";
}
cout << endl;
cout << "path.address = " << path << endl;
}
int main()
{
cout << "无参构造函数.构造对象a" << endl;
Test a;
a.show();
cout << endl;
{
cout << "默认赋值运算符.带参对象c给对象a赋值.对象c在函数体内\n";
Test c(5);
a = c;
cout << "c.show()\n";
c.show();
cout << "a.show()\n";
a.show();
cout << endl;
}
cout << "对象c生命周期结束...";
cout << "a.show()\n";
a.show();
cin.get();
return 0;
}
程序输出:
对象c和对象a的成员指针path存储的地址相同,对象c析构后内存释放,对象a指针仍指向009F3BA0,a的指针就成了野指针。
拷贝(复制)构造函数声明:Complex(const Complex & c);
赋值运算符声明:Complex & operator=(const Complex & c);
改正后的程序:
#include
#include
using namespace std;
class Test
{
public:
Test();
Test(int n);
~Test();
Test & operator=(Test &assignment);
Test(const Test ©);
void show();
void test_new();
private:
int length;
int* path;
};
Test::Test():
length(0)
{
cout << "Copy_String::Default Constrution Called." << endl;
path = NULL;
}
Test::Test(int n)
{
cout << "Copy_String::Constrution Called." << endl;
length = n;
path = NULL;
path = new int[n];
if (path != NULL)
{
int i;
for (i = 0; i < length; i++)
{
path[i] = i;
}
}
else {
cout << "path[] alloc failed.\n";
}
}
Test::~Test()
{
cout << "Copy_String::Destruction Called." << endl;
if (path != NULL)
{
delete[] path;
path = NULL;
}
}
Test & Test::operator=(Test &assignment)
{
cout << "Copy_String::operator=() Called.\n";
if (this == &assignment)
{
return *this;
}
length = assignment.length;
if (path != NULL)
{
delete[] path;
path = NULL;
}
//这里之后需要修改。length==0情况下
path = new int[length];
int i;
for (i = 0; i < length; i++)
{
path[i] = assignment.path[i];
}
return *this;
}
Test::Test(const Test ©)
{
cout << "Copy_String::Copy() Called.\n";
//这里之后需要修改
length = copy.length;
path = new int[length];
if (path != NULL)
{
int i;
for (i = 0; i < length; i++)
{
path[i] = copy.path[i];
}
}
}
void Test::show()
{
cout << "path:";
int i;
for (i = 0; i < length; i++)
{
cout << path[i] << " ";
}
cout << endl;
cout << "path.address = " << path << endl;
}
void Test::test_new()
{
cout << "test_new()::path[0] = ";
if (path != NULL)
{
cout << path[0] << endl;
}
else
{
cout << endl;
}
}
int main()
{
cout << "无参构造函数.构造对象a" << endl;
Test a;
a.show();
a.test_new();
cout << endl;
cout << "拷贝构造函数.可能还有赋值运算符.由无参构造的对象a初始化对象b" << endl;
Test b = a;
b.show();
b.test_new();
cout << endl;
{
cout << "显式赋值运算符.带参对象c给对象a赋值.对象c在函数体内\n";
Test c(5);
a = c;
cout << "c.show()\n";
c.show();
cout << "a.show()\n";
a.show();
cout << endl;
}
cout << "对象c生命周期结束...";
cout << "a.show()\n";
a.show();
cout << endl;
int* p = new int[0];
if (p != NULL)
{
cout << "success.\n";
cout << p;
}
else {
cout << "failed.\n";
}
cin.get();
return 0;
}
写的不好,但至少比之前的好
这是输出:
关于第二段输出的test_new(),等会讲。。
先讲为什么说“拷贝构造函数.可能还有赋值运算符”。
C++ Primer plus中写的:
StringBad metoo = Bob; //Bob为已初始化的对象
一种情况下,可能使用复制构造函数初始化metoo;
还有可能使用复制构造函数初始化一个临时对象,再通过赋值将临时对象的值复制到metoo中,再析构临时对象。
一般来讲,不会写出new int[0]。上段程序用无参构造的对象a初始化对象b,由于a.length=0,出现new int[0]。main()中最后的一段证明,new int[0]确实返回了地址,说明申请了内存块。
引用:点击打开链接
但是这显然不是想要的结果。对象a的指针为空,用a初始化的b的指针不为空,可能引起程序出错,还难排错。这就是上段代码注释中可能需要修改的原因。
sort时间复杂度接近快排,主要是不用自己写。。
sort函数内部详解:点击打开链接
RAIterator next = last;
正是复制构造函数。如果成员有指针又没有显式定义的话,会报错哦。类似的知道的还有swap()。其他未知。。
my redundant code: 按sum给对象排序
#include
#include
using namespace std;
class Test;
class Test
{
public:
Test(int n);
~Test();
Test & operator=(Test &assignment);
Test(const Test ©);
void show();
int size;
int sum;
int* path;
};
Test::Test(int n)
{
size = n;
sum = rand() % n;
path = NULL;
path = new int[n];
if (path != NULL)
{
int i;
for (i = 0; i < n; i++)
{
path[i] = i;
}
}
else {
cout << "path[] alloc failed.\n";
}
}
Test::~Test()
{
if (path != NULL)
{
delete[] path;
path = NULL;
}
}
Test & Test::operator=(Test &assignment)
{
if (this == &assignment)
{
return *this;
}
size = assignment.size;
sum = assignment.sum;
if (path != NULL)
{
delete[] path;
path = NULL;
}
path = new int[size];
int i;
for (i = 0; i < size; i++)
{
path[i] = assignment.path[i];
}
return *this;
}
Test::Test(const Test ©)
{
size = copy.size;
sum = copy.sum;
path = new int[size];
if (path != NULL)
{
int i;
for (i = 0; i < size; i++)
{
path[i] = copy.path[i];
}
}
}
void Test::show()
{
cout << "path= ";
int i;
for (i = 0; i < size; i++)
{
cout << path[i] << " ";
}
cout << endl;
cout << "sum = " << sum << endl;
}
bool CMP(Test &a, Test &b)
{
return a.sum > b.sum;
}
int main()
{
cout << "对象a(5)" << endl;
Test a(5);
a.show();
cout << endl;
cout << "对象b(6)" << endl;
Test b(6);
b.show();
cout << endl;
cout << "对象c(7)" << endl;
Test c(7);
c.show();
//c = b;
//c.show();
cout << endl;
Test array[3] = { a,b,c };
sort(array, array + 3, CMP);
//swap(array[0], array[1]);
array[0].show();
array[1].show();
array[2].show();
cin.get();
return 0;
}
要注意的是,
复制构造函数和赋值运算符都需要。
5.15 15.29 补充:
除array外,交换两个容器内容的操作保证会很快——元素本身并未交换,swap只是交换了两个容器的内部数据结构。
如果交换的是两个容器,例如:
Test s1[]={a,b,c}; //a,b,c仍为上面的类对象
Test s2[]={d,e,f};
swap(s1,s2);
这时候不需要复制构造函数和赋值运算符