1、引言
本篇内容主要构造一个string.h的头文件(用于构造一个字符串类String),与01篇内容不同的是该头文件中包含指针数据。因此类的构造中涉及(three big)即拷贝构造copy ctor、拷贝复制copy op、析构函数的用法。
2、string.h的头文件编写
1)头文件使用防卫式声明结构
与01篇类似,头文件构造要使用防卫式声明,如下:
#ifndef __MYSTRING__
#define __MYSTRING__
#include <cstring>
/*
主要内容部分
*/
#endif //__MYSTRING__
说明:代码中cstring是为了完成下面string类构造而引入的标准库头文件,与防卫式声明无关。
2)在头文件中定义String类结构
class String; //前置声明
class String
{
public:
String(const char * cstr = 0); //#1
String(const String& str); //#2
String& operator = (const String& str); //#3
~String(); //#4
char * get_c_str() const { return m_data; } //#5
private:
char * m_data; //#6
};
注意点:
a、在String类结构中,#6中char * m_data是指向字符串第一个字符地址,是C语言字符串类型的表示方法;
b、#1是构造函数,构造字符串时默认传入为空地址,即空字符;
c、#2是拷贝构造函数,也是构造字符串的一种方法。如Sting s1(s2),即将s2的字符串拷贝构造给s1,传入的是s2字符串的常引用;
d、#3是拷贝赋值方法,是将=右边的字符串拷贝给左边的字符串,如Sting s1 = s2,考虑到有可能用到s1 = s2 = s3的用法,所以返回Sting类型引用;
e、#4是析构函数,用途是释放动态分配的内存;
f、#5是一个简单的内联函数,主要是返回字符串的地址。
3)实现构造函数方法#1
inline
String::String(const char * cstr)
{
if (cstr){
m_data = new char[strlen(cstr) + 1];
strcpy(m_data, cstr);//深复制
}
else{
m_data = new char[1];
*m_data = '\0';
}
}
注意点:
a、因构造函数比较长,所以没有像01篇在类内部实现,而是在外部实现,但函数要加上类声明String::String()的结构;
b、实现方法中考虑传入字符串地址为空和非空两种情况,而字符串是以\0为结束符的,如“hello”用字符表示是:‘h’ 'e' 'l' 'l' 'o' '\0'共6个字符;
c、实现方法中使用了strlen()和strcpy()方法,是头文件cstring中标准库的方法,一个用于返回字符串长度,另一个进行字符串深复制。
3)实现析构函数#4
inline
String::~String()
{
delete[] m_data;
}
注意点:
a、释放数据有两种方式即array new 和非array new。该例中m_data是new了一个数组,所以用delete [ ] m_data的形式;而如果m_data不是new一个数组,则用delete m_data形式;
b、创建了析构函数后,声明字符串,使用字符串后会自动释放相应的动态内存;
4)实现拷贝构造函数#2
inline
String::String(const String& str)
{
m_data = new char[strlen(str.m_data) + 1];
strcpy(m_data, str.m_data);
}
注意点:
a、拷贝构造函数没有返回值;
b、拷贝构造传入的是本身String类对象或者引用对象;
5)实现拷贝赋值函数#3
inline
String& String::operator = (const String& str)
{
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;
}
注意点:
a、拷贝复制就是将一个String对象复制给另一个String对象,具体思路是先释放被赋值的对象(delete [ ] m_data),然后被赋值对象创建赋值对象的内存空间大小来放置对象内容,最后用深复制的方法进行拷贝;
b、要考虑到对象自己赋值给自己的情况,即要先判断是否自己赋值自己,若是出现这种情况,则直接返回对象。一方面可以加快代码的运行速度、另一方面避免程序出现bug,因为如果没有这一步操作,释放完被赋值的对象后,赋值对象也就被释放了,程序深复制失败。
6)实现重载<<函数
#include <iostream>
using namespace std;
ostream& operator << (ostream& os, const String& str)
{
os << str.get_c_str();
return os;
}
说明:cout使用中原先并没有定义输出String类型结构,所以要进行函数重载,且与01篇类似,该函数的重载要在非成员函数中实现。
3、具体代码实现
1)string.h头文件#ifndef __MYSTRING__
#define __MYSTRING__
#include <cstring>
class String; //前置声明
class String
{
public:
String(const char * cstr = 0); //#1
String(const String& str); //#2
String& operator = (const String& str); //#3
~String(); //#4
char * get_c_str() const { return m_data; } //#5
private:
char * m_data; //#6
};
inline
String::String(const char * cstr)
{
if (cstr){
m_data = new char[strlen(cstr) + 1];
strcpy(m_data, cstr);//深复制
}
else{
m_data = new char[1];
*m_data = '\0';
}
}
inline
String::~String()
{
delete[] m_data;
}
inline
String::String(const String& str)
{
m_data = new char[strlen(str.m_data) + 1];
strcpy(m_data, str.m_data);
}
inline
String& String::operator = (const String& str)
{
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;
}
#include <iostream>
using namespace std;
ostream& operator << (ostream& os, const String& str)
{
os << str.get_c_str();
return os;
}
#endif //__MYSTRING__
2)主程序实现对头文件的操作
// string_Test.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "string.h"
#include <iostream>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
String s1("hello");
String s2("world");
String s3(s2);
cout << s3 << endl;
s3 = s1;
cout << s3 << endl;
cout << s2 << endl;
cout << s1 << endl;
return 0;
}
/*
输出:
world
hello
world
hello
*/
说明:在头文件中引用#include "string.h"