带指针的类的处理

    前几天,又复习了一点关于c++的内容。觉得收获的还是挺多的。所以,将此次的收获记录下来。
    就像侯捷老师所说,c++中的类可以分为两种,一种是成员变量没有指针的,还有一种就是成员变量中含有指针的。
    如果你的类中有指针,那么是一定得自己重写拷贝赋值函数(赋值运算符的重载),拷贝构造函数,析构函数。因为,难免你在操作类的时候会让编译器自动调用这些函数。但是你如果不重写的话,使用编译器自带的这三个函数,就可能带来一些麻烦。
    先贴出代码,然后我细细道来。

//这里是String.h
#ifndef _STRING_H_
#define _STRING_H_
#include<iostream>

using namespace std;

class String
{
	private:
		char *m_data;
	public:
		String();
		String(const char *str);
		String(const String& str);
		String& operator=(const String&str);
		~String();
		char* get_char() const{return m_data;}
};

#endif
//这里是String,c
#include<iostream>
#include<string.h>
#include"String.h"
using namespace std;

/*
	因为编译的原因,最好在vs上面也测试一哈,比较保险。
*/ 


int main()
{
	String A("hello");
	String B(A);
	String C("world");
	String* D=new String("hahaha"); 
	C=B;
	cout<<"i am string A: "<<A.get_char()<<endl;
	cout<<"i am string B: "<<B.get_char()<<endl;
	cout<<"i am string C: "<<C.get_char()<<endl;
	delete D;
	 
	return 0; 
}

String::String()
{
	//这一步也是十分重要的 (如果不要的话,就会导致你在想要输出m_data时遇到麻烦!)
	m_data=NULL;
	cout<<"default constructer function"<<endl;
}


String::String(const char* str=0)
{
	cout<<"i am construct function"<<endl;
	if(str){
		m_data=new char[strlen(str)+1];
		strcpy(m_data,str);
	}
	else
	{
		m_data=new char[1];
		m_data[0]='\0';
	}
}

//拷贝赋值函数 
String& String::operator=(const String& str)
{
	cout<<"copy assignment function"<<endl;
	if(this==&str)
	{
		return *this;
	}
	
	delete[] m_data;
	m_data=new char[strlen(str.m_data)+1];
	strcpy(m_data,str.m_data);
	return *this;
}


//拷贝构造函数 
String::String(const String& str)
{
	cout<<"copy construct function"<<endl; 
	m_data=new char[strlen(str.m_data)+1];
	strcpy(m_data,str.m_data);
}

//析构函数
String::~String()
{
	cout<<"disconstruct function"<<m_data<<endl;
	delete[] m_data;
}

    这里谈谈关于几个函数的手法:

  • 拷贝赋值函数(赋值运算符重载)

    首先检查时候是自我赋值。这个是侯捷老师说的。写出这条语句的都是一些老手。我们得锻炼成这种有大家风范的代码。(特别喜欢老师说这个话的时候的表情)大家的第一感觉就是不写的话不就是会多做几次复制而已吧,但是的情况不是这样的。原因我下面说。
    其次,释放自己的赋值运算符左边的内容。因为左值是要被修改的,所以,先释放自己的空间,避免内容混乱
    然后根据赋值运算符的右值,去计算要给m_data分配多少的空间。然后实现复制内容
    注意到第二步,我们是要释放自己的空间的。如果你一开始没有做自我赋值的判断,然后程序运行先释放了自己的空间,之后的strlen(str.m_data)就会报错的。

这个是运行结果:
在这里插入图片描述
    这个结果相信大家都是很清楚怎么来的,并且这个代码看起来也是健壮性十分强大的。但是我这里提出几个问题,看看程序是否会运行出错。
    如果在主函数中增加如下代码:

//这里假设上面的代码中主函数什么都没有写(不然变量名就重复了)
String A;
String B;
String C("hello");
C=A;			//①
B=C;			//②
A=B;			//③
String *p=new String[3];		//④
delete[] p;								//④

    大家觉得会报错吗(当然会报错,不然我写出来干嘛)?
    语句①:显然,这里会调用我们的拷贝赋值函数。但是看到函数内部是会计算字符串的长度,但是我的A调用的是无参构造函数,m_data=NULL;所以,程序报错。
    语句②:发现其实他是没有问题的。这里我想要强调一点的是:
int *p=NULL;delete p;是没有问题的,此时编译器看到是delete NULL;就什么都不会做。因为我一开始是认为他会报错的。
    语句③:原理和①是一样的,也会报错。我之后将修改后的函数补上去。
    语句④:这个我主要是想要强调一个delete *p此时应该会做的一些事情。
    首先,在书本,可能会说:这个时候,delete关键字会做两件事情:一:调用析构函数;二:释放你new出来的空间。我画一个图就好理解了。
在这里插入图片描述
    可以看到,其实这条语句在分配了两次堆区域的内容。因为new在堆分配空间,所以String[0],String[1],String[2]都在堆的位置,因为String[0],String[1],String[2]的成员变量m_data也是使用new关键字,所以分配的内存也在堆内存中。
    这里还涉及到一个问题,为什么new []后要配合delete[]释放内存空间。就像侯捷老师说的一样,很多仅仅知道不这样搭配会造成内存泄漏,但是可能不是你所想的内存泄漏。不管你使用的是delete[]还是delete都是会将0x1122这个地址的内存释放,因为,p指向的这个位置。但是你如果使用的是delete,那么就仅仅是String[0]指向的内存hello\0会被释放,后面的都不会被释放。仅仅调用一次析构函数。(如图一)但是你写的是delete[]话,编译器就会知道,后面还有需要释放的内存,所以,将会调用三次析构函数。(如图二)
在这里插入图片描述
在这里插入图片描述
    注意:这里我使用的是不同的编辑器,因为我发现dev c++对于你写的delete p还是会调用了三次析构函数。应该编译器自己做出了优化。但是,理论上分析是没有问题的。
    这里给出优化后的代码:

#include<iostream>
#include<string.h>
#include"String.h"
using namespace std;

/*
	因为编译的原因,最好在vs上面也测试一哈,比较保险。
	 
*/ 


int main()
{
	/* 
	String A("hello");
	String B(A);
	String C("world");
	String* D=new String("suliangkuan"); 
	C=B;
	cout<<"i am string A: "<<A.get_char()<<endl;
	cout<<"i am string B: "<<B.get_char()<<endl;
	cout<<"i am string C: "<<C.get_char()<<endl;
	delete D;
	*/
	
	String *p=new String[3];
	delete p; 
	p=NULL;
	cout<<p;
	
	/*
	String *p=new String[3]{String("hello"),String("world"),String("su")};
	delete []p;
	*/
	
	/*
	String A;
	String B("hello");
	A=B;
	*/
	
	/* 
	String a;
	String b;
	a = b;
	*/
	
	 
	return 0; 
}

String::String()
{
	//这一步也是十分重要的 (如果不要的话,就会导致你在想要输出m_data时遇到麻烦!)
	m_data=NULL;
	cout<<"default constructer function"<<endl;
}


String::String(const char* str=0)
{
	cout<<"i am construct function"<<endl;
	if(str){
		m_data=new char[strlen(str)+1];
		strcpy(m_data,str);
	}
	else
	{
		m_data=new char[1];
		m_data[0]='\0';
	}
}

/*
//拷贝赋值函数 
String& String::operator=(const String& str)
{
	cout<<"copy assignment function"<<endl;
	if(this==&str)
	{
		return *this;
	}
	
	delete[] m_data;
	m_data=new char[strlen(str.m_data)+1];
	strcpy(m_data,str.m_data);
	return *this;
}
*/
//最完美的拷贝赋值函数

String& String::operator=(const String& str)
{
	cout<<"copy assignment function"<<endl;
	if(this==&str)
	{
		return *this;
	}
	
	delete[] m_data;
	
	//String A("hello");String B; A=B; 
	if(str.m_data==NULL)
	{
		m_data=new char[1];
		m_data[0]='\0';
	}
	
	//String A("hello");String B("wolrd");A=B;  or String A; A=B; 
	else
	{
		m_data=new char[strlen(str.m_data)+1];
		strcpy(m_data,str.m_data);
	}

	return *this;
}


//拷贝构造函数 
String::String(const String& str)
{
	cout<<"copy construct function"<<endl; 
	m_data=new char[strlen(str.m_data)+1];
	strcpy(m_data,str.m_data);
}

//析构函数
String::~String()
{
	cout<<"disconstruct function";
	
	/*
		因为调用默认初始化的时候,m_data赋值为了NULL后
		在这里就十分容易判断了。如果m_data==NULL
		你直接cout<<m_data<<endl;就是会报错的 
	*/ 
	if(m_data!=NULL)
	{
		cout<<m_data<<endl;
	 } 
	 else
	 {
	 	cout<<endl;
	 }

	if (m_data != NULL)
	{
		delete[] m_data;
	}
}

    后面的就是一些小细节了,我记录下来以便之后查看:

    一般来说,因为自己肯定要写一个构造函数的,所以为了避免无参对象的无法构建,所以,自己一定得还写一个无参的构造函数。而且里面的指针一定得赋值为NULL。这个是一个好习惯。至于为什么,可以去看自己那篇有关指针的文章。

    在show()这种输出函数的时候,如果有涉及的指针数据的输出,一定记得先去判断这个指针是否为空,因为cout<<m_data<<endl;如果m_data==NULL的话。但是你直接cout<<NULL; int *p=NULL; cout<<p;都是不会报错,输出为0.应该是编译器做了隐士转化。

这篇文章可以和指针那篇文章一起看。

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值