这篇博客主要是学习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(有些编译器可能也会有问题)。我使用的是将它变成类的友元函数。