昨天师兄又出了道测试题,让我们实现类似于string类的没有MyString类,刚开始很头疼,可是真正在自己写代码的时候又很兴奋的发现,这个过程真的是个很宝贵的机会,让我又有机会可以很好的熟悉回顾C++的很多知识—类设计,构造析构函数,成员函数,友元函数,引用,重载,字符串操作,动态内存分布。。。。。于是昨天花了半天时间写了300多行代码,并认真的进行了相关测试、修改和总结。因为内容有点丰富,所以想分几次写出来,条理也清楚些。
类的空间分配:类给它的每个对象都分配了独立的空间去存储它的数据成员,所有的对象公共的访问类方法进行操作。同时在对象的独立空间中,不包括数据成员动态分配的空间,对象只是记录了动态分配空间的地址(所以在析构函数调用的时候只是删除了对像空间,同时需要用new来删除动态分配的地址)。
一、类声明—mystring.h:
1. 构造函数:
专门用于构建新对象,给成员数据分配必要的内存空间并将值赋给新对象的成员数据。
默认构造函数:
在未提供显式初始化值时,被用来创建对象的构造函数(所以它一般没有参数)
MyString();
复制构造函数:
用于将一个对象复制到新创建的对象中(当然这个被复制的对象必须已经存在)。
MyString(const MyString &str);
给定了一定初始化参数的构造函数:
参数列表中的值会一次赋给新创建对象的各个成员函数:
MyString(const char*str);
2.析构函数:
当对象过期时删除对象所占的内存空间,并且当对象创建时有用New请求的内存空时,在析构函数中同时要调用delete对原来分配的 内存空间进行释放,以防止内存泄露。
~MyString();
3.成员函数:
重载赋值成员函数:
MyString &operator=(const MyString &str); //利用已有的string对象通过=给一个对象进行赋值 MyString &operator=(const char*str); //直接用常量字符串进行赋值
一般赋值函数:
MyString &assign(const MyString&str); MyString &assign(const char*sstr);
几个处理字符串的成员函数:
size_t getsize()const; //返回字符串大小 void clear(); //把字符串清空 bool empty(); //判断字符串是否为空 void swap(MyString &str); //交换两个字符串 int compare(const MyString &str)const; //比较2个字符串的大小 //第一个const说明显式调用的字符串不可更改,括号外面的const说明隐式调用的字符串不可更改,只读数据 int compare(const char*str);
追加函数:
MyString &operator+=(const MyString&str); MyString &operator+=(const char*str); MyString &append(const MyString&str); MyString &append(const char *str);
生成字串:
MyString substr(size_t pos = 0,n=npos) const;生成字串,从第0个位置开始长度为n,若N超过长度,则为输出整个字符串的长度
4.友元函数(运算符重载):
友元函数一般都是在类得声明中进行定义,它不属于类得成员函数,但是它和类得成员函数一样同样的可以对类得所有数据成员进行访问。
friend bool operator==(const MyString &str1,const MyString &str2); friend bool operator==(const char *str,const MyString &str2); friend bool operator==(const MyString &str1,const MyString *str2); friend bool operator>(const MyString &str1,const MyString &str2); friend bool operator>(const char*str1,const MyString &str2); friend bool operator>(const MyString &str1,const char*str2);
同样还有<等各种比较。
friend MyString operator+(const MyString &str1,const MyString &str2); friend MyString operator+(const char*str1,const MyString &str2); //两个字符串进行相加 friend MyString operator+(const MyString &str1,const char*str2); friend ostream & operator<<(ostream &os,const MyString &str); //输出命令符的重载
5.成员数据变量:
char *string; //指向字符串的指针 int length; //字符串的长度 static const int string_number = 0; //计数创建的字符串的数目
二、实现.cpp文件:
1.构造函数和析构函数:
MyString::MyString() { length = 0; string = new char; char *s = "/0"; memcpy(string,s,1); ++string_number; } MyString::MyString(const char*str) { length = strlen(str); string = new char(length+1); memcpy(string,s,length); ++string_number; } MyString::MyString(MyString &str) { length = str.length; string = str.string; ++string_number; } MyString::~MyString() { delete[]string; --string_number; }
几个注意的问题:
1)构造函数中必须给所有的数据成员进行初始化。
2)注意在给指向字符串的指针赋值时,左右类型的对应。
char *s代表一个指向字符串的指针,所有右边必须是一个字符串常量“/0”,而不能是‘/0'.
3)一个指针只能指向一个地址,不能同时指向两个。
在给string分配了地址之后,下一步我们肯定是确定分配的地址中存放的具体内容,那么这个时候我们都是使用strcpy()或者是
memcpy()把对应的字符串存入地址中。
如果原来我们成这样实现:
MyString::MyString() { length = 0; string = new char; string = "/0"; ++string_number; }
那么我们在编译和实现的时候都不会发现有什么错,但是析构函数使用delete【】释放内存使执行结果会出现乱码,因为string=“/0”
让它指向了一个字符串,并没有分配内存空间,所以在释放的时候就会出现错误。
4)析构函数中的重要语句 delete【】不要忘
析构函数在使用的时候只会释放为对象分配的空间,但是对象的空间中只是存储了数据成员分配内存的地址,所以并没有释放数据成员
的内存空间,必须使用delete[]来进行释放,防止内存泄露
2.重载运算符的成员函数:
MyString &MyString::operator+=(const MyString&str) { char *dest; dest = new char[str.length+length+1]; memcpy(dest,string,length); memcpy(dest+length,str.string,str.length+1); delete[]string; length = length+str.length; string = dest; return*this; } MyString &MyString::operator+=(const char*str) { char *dest; dest = new char[strlen(str)+length+1]; memcpy(dest,string,length); memcpy(dest+length,str,strlen(str)+1); delete[]string; string = dest; return *this; } //字符串赋值 MyString &MyString::operator=(const MyString&str) { if(&str == this) return *this; delete[]string; string = new char[str.length]; memcpy(string,str.string,str.length); length = str.length; return *this; }
注意的幾個問題:
1)+=运算中,调用函数的对象最终字符串长度肯定大于其原来的长度,所以在这个函数调用过程中,我们必须给字符串重新分配一块
两个字符串长度和大小的内存区域。但是因为函数返回值又必须是原调用对象的引用,所以我们要定义一个中间变量指针来存储2个
字符串合并结果,最后释放string指针原来的内存区域后,再让它指向合并字符串。
2)“=”赋值运算肯定和构造函数的初始化不一样。
在使用这个方法以前,那么对象肯定至少已经通过调用构造函数数据成员有了一定的值。所以这个时候我们首先判断两个对象是否
相等。相等的话返回原对象,如果不等:
那么两个对象的字符串长度肯定不同。所以我们先释放原字符串内存空间,然后根据赋值对象的字符串长度分配内存空间并把字符
串内存拷贝过去。
3.字符串处理的几个函数:
size_t MyString::getsize(MyString &str) { return strlen(str.string); } void MyString::clear() { length = 0; while(string!='/0') *string ='/0'; } bool MyString::empty() { return strlen(string)==0; } int MyString ::compare(const MyString &str) { return compare(string,str.string); } void MyString::swap(MyString &str) { char *temp; temp = string; string = str.string; str = temp; } Mystring MyString::substr(sizez_t pos=0,size_t n )const { MyString string; delete[]string.string; if(n>length) { string.length = length; string.string = new char[length+1]; memcpy(string.string,string,length+1); return string; } length = n; string.string = new char[length+1]; memcpy(string.string,string,length+1); return string; }
注意的几个问题:
1)在这几个函数的实现中,我们可以直接调用c语言中几个对字符串处理的<string.h>函数进行实现
2)clear()函数中注意,只是把每个内存区域的字符置为0,并不能通过delete[]来释放内存空间,这样很容易和析构函数一起造成
两次释放内存空间引起错误。
3)swap()实现交换的这个函数中,我们可以直接定义中间变量指针实现,不用重新分配内存空间全部进行转存,这个对于析构函数
的析构没有影响。
4.友元函数:
friend bool operator==(const MyString &str1,const MyString &str2) return strcmp(str1.string,str2.string)==0; friend MyString operator+(const MyString &str1,const MyString &str2) { MyString mystring; char *dest; dest = new char[str1.length+str2.length+1]; memcpy(dest,str1.string,str1.length); memcpy(dest+str1.length,str2.string,str2.length+1); delete[]mystring.string; mystring.string = dest; mystring.length = str1.length+str2.length; return mystring; } friend ostream &operator<<(ostream &os,const MyString &str) { os<<str.string; return os; }
注意的问题和上面差不多,这里就不重复了~~~
其他几个函数实现都基本雷同~
主要内容:虚继承(Virtual Inheritance),虚基类成员的可见性多继承(Multiple Inheri tance)是指从多个直接基类中产生派生类的能力,多继承的派生类继承了所有父类的成员。尽管概念上非常简单,但是多个基类的相互交织可能会带来错综复杂的设计问题,命名冲突就是不可回避的一个。 多继承时很容易产生命名冲突,即使我们很小心地将所有类中的成员变量和成员函数都命名为不同的名字,命名冲突依然有可能发生,比如典型的是菱形继承,如下图所示: 图1:菱形继承 类
本文向大家介绍C++/java 继承类的多态详解及实例代码,包括了C++/java 继承类的多态详解及实例代码的使用技巧和注意事项,需要的朋友参考一下 C++/java 继承类的多态详解 学过C++和Java的人都知道,他们二者由于都可以进行面向对象编程,而面向对象编程的三大特性就是封装、继承、多态,所有今天我们就来简单了解一下C++和Java在多态这方面的不同。 首先我们各看一个案例。 C++
例如,我有一个名为Animal的基类。 子类可以实现以下一个或多个接口: 我有3个子类: 现在我有了一个方法,我想接受我知道的动物(狗或猫)。有没有一种方法可以在不确定是哪种动物的情况下到达界面?这样做会很好: 我不想把IShed接口变成另一个抽象类,因为Cat和Snake都需要实现IBeAJerk接口。但是蛇不会蜕皮。(实际上我猜蛇会蜕皮,但你明白我的意思。) 谢谢你的帮助!
本文向大家介绍浅谈C# 类的继承,包括了浅谈C# 类的继承的使用技巧和注意事项,需要的朋友参考一下 继承 一个类可以继承自另一个类。在 C#中,类与类之间只存在单一继承。也就是说,一个类的直接基类只能有一个。当类与类之间实现继承的时候,子类可以将它的直接基类的所有成员当做自己的成员,除了类的静态构造方法、实例构造方法和析构方法。但是,虽然基类的所有成员都可以当做子类的成员,但是如果基类的成员设置了
本文向大家介绍C++实现不能被继承的类实例分析,包括了C++实现不能被继承的类实例分析的使用技巧和注意事项,需要的朋友参考一下 本文实例展示了C++实现不能被继承的类的方法,对于C++初学者而言有一定的学习借鉴价值。具体实现方法如下: 方法一: 该方法其实就是把构造函数、析构函数private了,这样的话当想派生一个类时,派生类无法构造一个父类,所以就不行了。 方法二: 类B设置为类A的友元,这样
本文向大家介绍C# 特殊的string类型详解,包括了C# 特殊的string类型详解的使用技巧和注意事项,需要的朋友参考一下 1.前言 string是属于引用类型的,这个大家都知道吧?但是平常在使用的过程中,发现它还是拥有一些值类型的特征的,这到底是为什么呢? 原因就是.Net考虑到假如大量的操作string对象的时候,大量对引用对象进行操作的时候,性能肯定不如值类型来的爽快。.Net为了提高这