C++类中成员有指针时的注意事项

关于类中有new初始化的指针成员.可能出现的问题

分享的内容

  1. 默认拷贝(复制)构造函数、赋值运算符,以及显式拷贝构造函数、赋值运算符
  2. int* p = new int[0]
  3. algrithm库中的sort函数


拷贝(复制)构造函数、赋值运算符

类对象的成员经常需要动态分配内存。例如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++类中成员有指针时的注意事项_第1张图片

对象c和对象a的成员指针path存储的地址相同,对象c析构后内存释放,对象a指针仍指向009F3BA0,a的指针就成了野指针。



拷贝(复制)构造函数声明:Complex(const Complex & c);    

赋值运算符声明Complex & operator=(const Complex & c);  

  1. 赋值是二元运算,包含两个形参,其中一个形参为隐含的this指针,对应左操作数
  2. 返回一个指向调用对象的引用。这是与复制构造函数的一个区别
  3.  对象A=B,实际上是A.operator=(B)。
    也可以A=B=C等价于A.operator=(B.operator=(C));
  4. 仍然是与复制构造函数的区别复制构造函数只能初始化一个正在声明的对象,而赋值运算符还可以给一个已经初始化了的对象赋值,因此可能需要清理已经有的数据。
  5. 结合第4点,防止赋值给自身,因为如果定义了释放操作,会释放自己的数据

改正后的程序:

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

写的不好,但至少比之前的好

这是输出:

C++类中成员有指针时的注意事项_第2张图片

关于第二段输出的test_new(),等会讲。。

先讲为什么说“拷贝构造函数.可能还有赋值运算符”。

    C++ Primer plus中写的:

    StringBad metoo = Bob; //Bob为已初始化的对象

一种情况下,可能使用复制构造函数初始化metoo;

还有可能使用复制构造函数初始化一个临时对象,再通过赋值将临时对象的值复制到metoo中,再析构临时对象。


int* p = new int[0]

一般来讲,不会写出new int[0]。上段程序用无参构造的对象a初始化对象b,由于a.length=0,出现new int[0]。main()中最后的一段证明,new int[0]确实返回了地址,说明申请了内存块。

引用:点击打开链接

但是这显然不是想要的结果。对象a的指针为空,用a初始化的b的指针不为空,可能引起程序出错,还难排错。这就是上段代码注释中可能需要修改的原因。


algrithm库中的sort函数

    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 补充:

C++11新特性——容器相关(二)swap点击打开链接

array外,交换两个容器内容的操作保证会很快——元素本身并未交换,swap只是交换了两个容器的内部数据结构

如果交换的是两个容器,例如:

Test  s1[]={a,b,c};    //a,b,c仍为上面的类对象

Test  s2[]={d,e,f};

swap(s1,s2);

这时候不需要复制构造函数和赋值运算符



你可能感兴趣的:(C++)