9. 复习String类是实现过程

1. 设计一个class,总是先思考需要什么样的(私有)数据,字符串里面一定会放很多字符。想法1是利用数组存放字符,但是这种想法不好,因为不知道要存放的字符有多少,占多少空间不知道,所以预先设定的大小不好确定。所以私有数据应该是一个指针(第12行),将来放多大的内容,用new的方式动态分配内存大小。32位电脑上,一个指针占4个字节的大小(网友1:64位系统,vc中的指针变量大小也是4bytes)。

 1 #pragma once
 2 class String
 3 {
 4 public:
 5     String(const char* cstr = 0);
 6     String(const String& str);
 7     String& operator=(const String& str);
 8     ~String();
 9     char* get_cstr() const { return m_data; }
10 
11 private:
12     char* m_data;
13 };
14 
15 inline
16 String::String(const char* cstr = 0)
17 {
18     if (cstr)
19     {
20         m_data = new char[strlen(cstr) + 1];
21         strcpy(m_data, cstr);
22     }
23     else
24     {//未指定初值
25         m_data = new char[1];
26         *m_data = '\0';//空字符串也有最后的结束符
27     }
28 }
29 //拷贝构造函数
30 inline
31 String::String(const String& str)
32 {
33     m_data = new char[strlen(str.m_data) + 1];
34     strcpy(m_data, str.m_data);
35 }
36 
37 //拷贝赋值函数
38 inline
39 String& String::operator=(const String& str)
40 {
41     if (this == &str)
42         return *this;
43     delete[] m_data;//先把自己(目的端)杀掉,
44     m_data = new char[strlen(str.m_data) + 1];//重新分配一块和来源端大小相同的空间
45     strcpy(m_data, str.m_data);//拷贝字符串
46     return *this;
47 }
48 
49 //操作符重载,输出字符对象
50 #include<iostream>
51 ostream& operator << (ostream& os, const String& str)
52 {
53     os << str.get_cstr();//函数调用,取得字符对象的指针
54     return os;
55 }
56 
57 inline
58 String::~String()
59 {
60     delete[] m_data;//m_data = new char[strlen(str.m_data) + 1];属于array new,所以应该delete[] 指针
61 }

2. 接下来思考public:区域中需要设计哪些函数开放给外界调用。首先需要准备构造函数,main()函数中创建字符串,一定会调用构造函数(第5行),构造函数永远没有return type. 所以返回类型什么都不写。参数设计成指向字符串的指针,默认是0,对象一经构造出来,就不会改变,所以参数前面加上const修饰符。

3. 类里面含有指针,所以一定要考虑三大函数(拷贝构造,拷贝赋值,析构函数),拷贝构造函数也是构造函数,所以没有返回类型,既然是拷贝构造,参数就是它自己这种类型的东西(第6行),首先考虑用reference 传参,要不要加const? 传进来的知识一个等待赋给别人的蓝本,函数里面不会去改传进来的蓝本,所以加上const修饰符。

4. 拷贝赋值函数,参数是字符串类,用引用传参,赋值是把来源端拷贝到目的端,所以来源端不会被改变,所以加上const。来源端拷贝到目的端,得到的结果就是目的端这种东西,所以返回类型是String类的对象(第7行),返回值能不能传引用?因为函数执行的结果是放在了目的端对象上,而目的端对象本来就存在,并不需要在这个函数中去创建,所以不是临时对象,所以可以传引用。

5. 析构函数(第8行)。至此,三大函数的接口设计完毕。

6. 考虑下还要不要设计什么辅助函数了,由于后续希望把字符串丢到cout进行打印输出,所以应该再设计一个取得私有数据(字符串)的辅助函数(第9行)。m_data是指针指向字符的字符串,是C风格的字符串,所以函数的名字起作get_cstr(). 此函数如此简单,所以可以直线在class body内实现。要不要写成常量成员函数?答:要。因为只是取得私有数据,没有改变私有数据。构造函数,拷贝构造函数,拷贝赋值函数要不要设计成常量成员函数呢?答:不可以,因为其实那三个函数都有更改指针,构造函数就是要写私有数据的,拷贝构造也是写私有数据,拷贝赋值也是要写私有数据。实际上,所有类的big three都不能设计成常量成员函数。

7. 构造函数的实现

考虑分配足够的内存空间放初始的字符串,如“hello!”,判断传进来的指针是否指向空的内容,如果指向不为空的话,就动态分配传进来的字符串的长度+1这样长度的空字符数组,用来存放传进来的字符串,然后使用C的字符串拷贝函数把传进来的字符串拷贝到私有指针变量指向的新分配的空间中来。如果传进来的是空,也要准备一个字节的内存空间,放字符串结束符'\0';即空字符串有个含有一个字符,它是结束符'\0'。最后再写上inline修饰符,建议编译器帮忙做成内联函数。

8. 析构函数的实现

析构就是要把自己清理干净(第60行),也就是释放之前动态分配的内存。new的时候用的是数组,delete的时候也要这样写:delete[] m_data;而不要这样写:delete m_data; 最后要求编译器把它做成inline函数。

9. 拷贝构造函数的实现

参数:自己这种类型。

返回类型:无,因为拷贝构造函数也属于构造函数。

来源端当成蓝本,拷贝到目的端,实现过程跟上一个构造函数的思考差不多。

10. 拷贝赋值函数的实现

参数:字符串类型。

返回类型:来源端赋值到目的端,目的端就是自己这种东西,就是字符串类,传引用。

目的端是本来已经存在的东西,所以先把自己杀掉,杀掉后重新分配一块足够盛放来源端大小的内存,最后把来源端拷贝到目的端。如果是连续赋值,那么就要有返回值,返回值就是自己这种东西。如果不是连续赋值,可以把返回值设计成void。

拷贝赋值一定要关注自我赋值,即来源端和目的端本来就指向同一块内存。此时不需要做下面的拷贝动作,直接返回自己就行。

 1 //拷贝赋值函数
 2 inline
 3 String& String::operator=(const String& str)
 4 {
 5     if (this == &str)
 6         return *this;
 7     delete[] m_data;//先把自己(目的端)杀掉,
 8     m_data = new char[strlen(str.m_data) + 1];//重新分配一块和来源端大小相同的空间
 9     strcpy(m_data, str.m_data);//拷贝字符串
10     return *this;
11 }

注:C++中,&符号出现的位置不同,其含义不同,第3行,代表一个类的引用,typename&,第5行代表取地址,得到是一个指针。

 

posted on 2020-02-14 14:16  一杯明月  阅读(250)  评论(0编辑  收藏  举报