嵌套类和运算符重载

        这篇博客主要是学习C++程序设计语言第10章和第11章内容的代码,主要有嵌套类和运算符重载。在编写代码时候也发现很多的问题,顺便也记录一下。

        代码文件包括String.h文件,String.cpp文件及main.cpp文件。主要做的就是模仿实现string.h的功能。当然了,功能和效率肯定没法和string相比了。主要涉及三个类和一个结构体。

  • 类String是主体,实现了绝大部分的运算符重载;
  • 辅助类Cref,帮助实现下标运算;
  • 辅助类Range,用于出现范围错误时抛出,没有实现。再加上在main.cpp文件中有函数f(String,String)下标运算,所以在输入两个字符串时,一个要大于2,一个要大于3,否则就会出错;
  • 结构体Srep,用于使一个实际表示能够被几个具有同样值的String所共享;

String.h文件:

#include<iostream>

class String{
	struct Srep{  //表示
		char* s; //到元素的指针
		int sz;  //字符个数
		int n;   //引用计数
		Srep(int nsz,const char* p)
		{
			n = 1;
			sz = nsz;
			s = new char[sz + 1];//为了结束符增加空间
			strcpy(s,p);
		}
		~Srep()
		{
			delete[] s;
		}
		Srep* get_own_copy()//需要时克隆
		{
			if(n == 1)
				return this;
			n--;
			return new Srep(sz,s);
		}
		void assign(int nsz,const char* p)
		{
			if(sz != nsz){
				delete[] s;
				sz = nsz;
				s = new char[sz + 1];
			}
			strcpy(s,p);
		}
	};
	Srep* rep;    
public:
	class Cref{  //引用char 
		friend class String;
		String& s;
		int i;
		Cref(String& ss,int ii):s(ss),i(ii){}
	public:
		operator char() const
		{
			return s.read(i);
		}
		void operator= (char c)
		{
			s.write(i,c);
		}
	};
	class Range{}; //用于异常

	String();             //x = ""
	String(const char*);  //x = "abc"
	String(const String&);//x = other_String
	String& operator= (const char*);
	String& operator= (const String&);
	~String();

	void check(int) const;
	char read(int) const;
	void write(int,char);
	Cref operator[] (int);
	char operator[] (int i) const;
	int size() const;

	String& operator+=(const String& x);
	String& operator+=(const char* s);
	friend String operator+(const String& x1,const String& x2)
	{
		char* stmp = new char[x1.size() + x2.size() +1];
		strcpy(stmp,x1.rep->s);
		strcat(stmp,x2.rep->s);
		String s = stmp;
		return s;
	}
	friend String operator+(const String& x1,const char* s)
	{
		char* stmp = new char[x1.size() + strlen(s) +1];
		strcpy(stmp,x1.rep->s);
		strcat(stmp,s);
		String st = stmp;
		return st;
	}
	friend std::ostream& operator<<(std::ostream& out,const String& x)
	{
		for(int i=0;i<x.rep->sz;i++)
			out<<x.rep->s[i];
		return out;
	}
	friend std::istream& operator>>(std::istream& in,String& x)
	{
		char* stmp = new char[60];
		in>>stmp;
		if(x.rep->n == 1)
			x.rep->assign(strlen(stmp),stmp);
		else{
			x.rep->n--;
			x.rep = new Srep(strlen(stmp),stmp);
		}
		delete[] stmp;
		return in;
	}
	friend bool operator==(const String&x,const String&y){
		return strcmp(x.rep->s,y.rep->s) == 0;
	}
	friend bool operator==(const String&x,const char* s){
		return strcmp(x.rep->s,s) == 0;	
	}
	friend bool operator!=(const String&x,const String&y){
		return strcmp(x.rep->s,y.rep->s) != 0;
	}
	friend bool operator!=(const String&x,const char* s){
		return strcmp(x.rep->s,s) != 0;
	}
};

String.cpp文件:

#include"String.h"
#include<string.h>

String::String()  //以空串为默认值
{
	rep = new Srep(0,"");
}
String::String(const String& x) //复制构造函数
{
	x.rep->n++;
	rep = x.rep; //共享表示
}
String::~String()
{
	if(--rep->n == 0) delete rep;
}
String& String::operator= (const String& x) //复制赋值
{
	x.rep->n++;                   //保护,防止“st=st”
	if(--rep->n == 0)
		delete rep;
	rep = x.rep;                  //共享表示
	return *this;
}

//伪装的复制运算以const char* 作为参数,以提供字符串文字量
String::String(const char* s)
{
	rep = new Srep(strlen(s),s);
}
String& String::operator= (const char* s)
{
	if(rep->n == 1)
		rep->assign(strlen(s),s);   //再利用Srep
	else{
		rep->n--;
		rep = new Srep(strlen(s),s);//使用新的Srep
	}
	return *this;
}

void String::check(int i) const
{
	if(i < 0 || rep->sz <= i)
		throw Range();
}
char String::read(int i) const
{
	return rep->s[i];
}
void String::write(int i,char c)
{
	rep = rep->get_own_copy();
	rep->s[i] = c;
}
String::Cref String::operator[] (int i)
{
	check(i);
	return Cref(*this,i);
}
char String::operator[] (int i) const
{
	check(i);
	return rep->s[i];
}
int String::size() const
{
	return rep->sz;
}
String& String::operator+=(const String& x)
{
	char* stmp = new char[this->rep->sz + x.rep->sz + 1];
	strcpy(stmp,this->rep->s); //strcat(stmp,this->rep->s)
	strcat(stmp,x.rep->s);
	if(this->rep->n == 1)
		this->rep->assign(strlen(stmp),stmp);
	else{
		this->rep->n--;
		this->rep = new Srep(strlen(stmp),stmp);
	}
	delete[] stmp;
	return *this;
}
String& String::operator+=(const char* s)
{
	char* stmp = new char[this->rep->sz + strlen(s) + 1];
	strcpy(stmp,this->rep->s);
	strcat(stmp,s);
	if(this->rep->n == 1)
		this->rep->assign(strlen(stmp),stmp);
	else{
		this->rep->n--;
		this->rep = new Srep(strlen(stmp),stmp);
	}
	delete[] stmp;
	return *this;
}

main.cpp文件:

#include"String.h"
#include<iostream>
#include<Windows.h>

String f(String a,String b)
{
	a[2] = 'x';
	char c = b[3];
	std::cout<<"in f:"<<a<<' '<<b<<' '<<c<<'\n';
	return b;
}

int main()
{
	String x,y;
	std::cout<<"请输入两个字符串\n";
	std::cin>>x>>y;
	std::cout<<"x:"<<x<<' '<<"y="<<y<<'\n';
	
	String z = x;
	y = f(x,y);
	if(x != z)
		std::cout<<"x被修改!\n";
	x[0] = '!';
	if(x == z)
		std::cout<<"x修改失败!\n";
	std::cout<<"x="<<x<<' '<<"y="<<y<<' '<<"z="<<z<<'\n';
	
	x += y;
	y += "123";
	std::cout<<"x="<<x<<' '<<"y="<<y<<' '<<"z="<<z<<'\n';

	x = x + y;
	y = y + "098";
	std::cout<<"x="<<x<<' '<<"y="<<y<<' '<<"z="<<z<<'\n';

	system("PAUSE");
	return 0;
}

其结果截图:

        在具体实现的过程中也发现了一些问题,在这里做一些总结。

1.嵌套类申明与定义问题

        因为类String中嵌套了结构体Srep和类Cref,所以就有申明和定义的问题。我开始的时候是按照书上所说的,先在String.h文件中申明(struct Srep;Srep *rep;),然后在String.cpp文件中定义(就是结构体中成员变量和成员函数)和具体实现(成员函数的函数体)。在完整的实现结构体Srep都没有问题,但在文件String.h中实现运算符重载的时候,使用结构体指针rep时,就会出现使用不完整类型等类似问题。也做其他尝试,包括在String.h文件中定义,在String.cpp文件中具体实现;使用单独的一文件Srep去定义和实现等,它们有的也会出现使用不完整类型,有的会出现其他问题。在随后的时间需要去做其他的尝试。

2.某某文件触发一个断点问题

        我在网上查找了解到,出现这样问题的主要是使用超过数组或者new申请的内存空间。出现这样问题的一般表现为程序能够正常运行部分代码,但在程序回收某些内存时就会出现问题。比如,会出现在某些要析构的对象上,而这些对象可能都是使用了new申请内存空间。像我这次出现的就是在程序末尾,在析构对象String的时触发这个问题。

        这次出现错误的根本原因是new申请的空间不足,出现错误的 >> 运算符重载的代码如下:

friend std::istream& operator>>(std::istream& in,String& x)
{
    in>>x.rep->s;
    x.rep->sz = strlen(x.rep->s);
    return in;
}

        初看可能感觉不到什么问题,但是细想就能发现问题所在。String的对象x中成员变量s是使用new申请的内存,但这里却没有考虑变量s内存是否足够的问题。结合main中代码就能把问题看透,main的代码如:

String x,y;
std::cin>>x>>y;

第一句,会使用String的无参数的构造函数创建对象x和y,而在这个无参数构造函数完成之前会调用Srep的构造函数,为变量x.rep->s申请内存空间,查看Srep的构造函数会发现这次申请的空间是1。第二句,会调用>>运算符重载,也就上面运算符重载代码。当使用这输入任意大小的字符串时,x.rep->s的空间都是不够的。

        所以,在使用new申请空间的变量时,一定需要知道它的内存空间的大小,以确保会够用。

3.使用strcat()和strcpy()的问题

        其实就是strcat()的问题。strcat(char* s1,char* s2)是合并两个字符串,并把结果存放在s1上;strcpy(char* s1,char* s2)是把s2的字符串拷贝到s1上,会覆盖s1原本的字符的。先看下面的代码块:

char* sc1 = new char[5];
char* sc2 = new char[5];
strcat(sc1,"abc");
strcpy(sc2,"abc");

运行这四行代码后,期望是sc1="abc",sc2="abc",单结果却不是这样的调试结果如下:

可以看到sc1的结果并不是所期望的,也很明显sc1的内存不够用了。也就不难理解下面的代码为什么会出错了。

String& String::operator+=(const String& x)
{
	char* stmp = new char[this->rep->sz + x.rep->sz + 1];
	strcat(stmp,this->rep->s); //改成strcpy(stmp,this->rep->s)就不会出错
	strcat(stmp,x.rep->s);
	if(this->rep->n == 1)
		this->rep->assign(strlen(stmp),stmp);
	else{
		this->rep->n--;
		this->rep = new Srep(strlen(stmp),stmp);
	}
	delete[] stmp;
	return *this;
}

4.运算符重载全局定义和定义成成员函数问题

        起初我按照书上的在String.h文件定义,如下:

#include<iostream>

class String{
    //...
};
friend String operator+(const String& x1,const String& x2)
{
	char* stmp = new char[x1.size() + x2.size() +1];
	strcpy(stmp,x1.rep->s);
	strcat(stmp,x2.rep->s);
	String s = stmp;
	return s;
}
friend String operator+(const String& x1,const char* s)
{
	char* stmp = new char[x1.size() + strlen(s) +1];
	strcpy(stmp,x1.rep->s);
	strcat(stmp,s);
	String st = stmp;
	return st;
}

项目在编译过程中会出现这两个 + 运算符重载多次定义的错误,原因是String.h文件会被其他文件include,会被多次编译。其解决方法有很多,可以使用inline或者extern(有些编译器可能也会有问题)。我使用的是将它变成类的友元函数。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值