前言

在c++里 std::cout << 和 std::cin >>用多了可能都要忘了<<>>本质上是位移运算符。
只不过std本质上是一个命名空间,可能在里面也定义了类也带上了重载。


正文

1
2
3
4
5
//方法
返回类型 operator>>(类型 操作数);

//全局函数
返回类型 operator>>(类型 左操作数,类型 右操作数);

这里还是以昨天hstring为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//hstring.h
#pragma once
class hstring{
private:
char *c_str; //hstring => char *c_str
unsigned short hsLen; //c_str length
unsigned short hsmLen; //hstring 内存长度
unsigned short gethsLen(const char *str) const; //hsLen属于私有成员,故此调用函数
void copyStrs(char *dest, const char *source);

public:
hstring();
hstring(const char *str);
hstring(const hstring &str);
hstring &operator=(const hstring &str);

char *rtstr(){ return c_str; }

};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//hstring.cpp
#include "hstring.h"
#include<iostream>

hstring::hstring(){
hsmLen = 0x32; //设置缓冲区
hsLen = 0; //length = 0
c_str = new char[hsmLen];
}

hstring::hstring(const char *str){
copyStrs(c_str, str);
}
hstring::hstring(const hstring &str){
copyStrs(c_str, str.c_str);
}
hstring &hstring::operator=(const hstring &str){
copyStrs(c_str, str.c_str);
return *this;
}
unsigned short hstring::gethsLen(const char *str) const{
unsigned short len = 0;
while (str[len++] != '\0');

return len;
}

void hstring::copyStrs(char *dest, const char *source){
unsigned short len = gethsLen(source);
if (len > hsmLen){
c_str = new char[len]; //如果字符串的长度大于缓冲区就重新设置
hsmLen = len; //重置内存长度
}
memcpy(c_str, source, len);
hsLen = len; //重置字符串长度
}

小问题1

再次之前,先解决下之前有个函数可能存在内存泄漏

1
2
3
4
5
6
7
8
9
10
void hstring::copyStrs(char *dest, const char *source){
unsigned short len = gethsLen(source);
if (len > hsmLen){
delete[] c_str; //如果长度超出缓冲区,那么就要清理掉之前的内存
c_str = new char[len]; //如果字符串的长度大于缓冲区就重新设置
hsmLen = len; //重置内存长度
}
memcpy(c_str, source, len);
hsLen = len; //重置字符串长度
}

就是重新分配内存前,应该清理掉原有的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
hstring &operator<<(const hstring &str);
//....

hstring &hstring::operator<<(const hstring &str){
unsigned short len = gethsLen(str.c_str);
len += hsLen - 1; //因为字符串\0结尾,两个字符串长度相加有一个\0就行
if (len > hsmLen){
delete[] c_str; //清理内存
c_str = new char[len]; //重新分配字符串空间
hsmLen = len; //重置缓冲区长度
}
memcpy(this->c_str + hsLen -1, str.c_str, len - hsLen + 1);
/*
this加不加无所谓,我调试的时候加上了。
从c_str + c_str的长度-1 就是 \0那个位置覆盖
然后写入形参传入的c_str
最后长度就是len - this.c_str长度 + 1 这个+1就是给后面的字符串保留\0
*/
hsLen = len;

return *this;
}

小问题2

在调试的时候发现了一个问题,就是构造函数,我们初始化的时候只写在了默认构造函数里,导致使用其它构造函数实例化的时候,成员变量没有初始化的问题,会有随机值产生。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
hstring::hstring(){
hsmLen = 0x32; //设置缓冲区
hsLen = 0; //length = 0
c_str = new char[hsmLen];
}

hstring::hstring(const char *str){
hsmLen = 0x32; //设置缓冲区
hsLen = 0; //length = 0
c_str = new char[hsmLen];
copyStrs(c_str, str);
}
hstring::hstring(const hstring &str){
hsmLen = 0x32; //设置缓冲区
hsLen = 0; //length = 0
c_str = new char[hsmLen];
copyStrs(c_str, str.c_str);
}

所以要注意,无论是哪个构造函数都要进行初始化,毕竟实例化的时候不管这些。

当然这样写看着又很雷同,于是可以用列表初始化替换掉一些:

1
2
3
4
5
6
7
8
9
10
11
hstring::hstring(char ch=0x32){
hsmLen = ch; //设置缓冲区
hsLen = 0; //length = 0
c_str = new char[hsmLen];
}
hstring::hstring(const char *str):hstring(){
copyStrs(c_str, str);
}
hstring::hstring(const hstring &str):hstring(){
copyStrs(c_str, str.c_str);
}
1
2
//记得修改.h头文件里的构造函数 
hstring(char ch);

这样一来解决了一些重复的事情~~~~也确实是我上一章没注意到。

实际效果也ok,能够完整的显示出来。

而且重载可以连续套用。

那么说<< 相当于拼接字符串了,数学角度跟加法就差不多了。所以加号运算符重载的话可以套用了

1
2
3
4
5
hstring &operator+(const hstring &str);
//...
hstring &hstring::operator+(const hstring &str){
return *this << str;
}

效果也是一样的,毕竟底层一样了。


缝缝补补

然后是析构函数,析构函数也不用干啥,就是释放c_str就行

1
2
3
hstring::~hstring(){
if (c_str != nullptr) delete[] c_str;
}

还有个就是std::cout 的问题,他不能直接输出,而是通过我们的函数返回的。
学了重载就要给它优化一下。

看到std::后面其实也是个参数,一个流的称呼。

1
2
3
4
std::ostream &operator<<(std::ostream &_cout, hstring _str){
_cout << _str.rtstr();
return _cout;
}

通过重写输出流的<<重载,将hstring的参数直接拼接到输出流,然后返回。

调用的时候也能看到是用的我们写的重载

实际效果也没有差别。


1
std::cout << str << "1111" << "222" << std::endl;

这个时候,str后面的字符串所用的<<重载是属于std::cout的
它本质上就是operator<<(operator<<(operator<<(std::cout,str),"1111"),"222");
指定了输出流,然后参数一个一个通过<<写入。

指定了输出,就可以尝试一下输入

1
2
3
std::istream &operator>>(std::istream &_cin, hstring &_str){

}

流要使用istream,i和o的区别就是input和output,输入输出的意思。

只不过这里输入的时候其实要注意缓冲区。。默认的输入流还真不知道要给多少合适。

1
2
3
4
5
6
7
std::istream &operator>>(std::istream &_cin, hstring &_str){
char _len[0xff];
_cin >> _len;
_str = _len;

return _cin;
}

缓冲区不好定,就随便给个大的先做一下效果。
这样通过这个缓冲的字符串,可以给str有空计算然后写入。

效果就是这样了~~~


大致完善后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#pragma once
class hstring{
private:
char *c_str; //hstring => char *c_str
unsigned short hsLen; //c_str length
unsigned short hsmLen; //hstring 内存长度
unsigned short gethsLen(const char *str) const; //hsLen属于私有成员,故此调用函数
void copyStrs(char *dest, const char *source);

public:
hstring(char ch);
hstring(const char *str);
hstring(const hstring &str);
hstring &operator=(const hstring &str);
hstring &operator<<(const hstring &str);
hstring &operator+(const hstring &str);
~hstring();

char *rtstr(){ return c_str; }

};

std::ostream &operator<<(std::ostream &_cout, hstring _str);
std::istream &operator>>(std::istream &_cin, hstring &_str);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//hstring.cpp
#include "hstring.h"
#include<iostream>

hstring::hstring(char ch=0x32){
hsmLen = ch; //设置缓冲区
hsLen = 0; //length = 0
c_str = new char[hsmLen];
}

hstring::hstring(const char *str):hstring(){
copyStrs(c_str, str);
}
hstring::hstring(const hstring &str):hstring(){
copyStrs(c_str, str.c_str);
}
hstring &hstring::operator=(const hstring &str){
copyStrs(c_str, str.c_str);
return *this;
}
hstring &hstring::operator<<(const hstring &str){
unsigned short len = gethsLen(str.c_str);
len += hsLen - 1; //因为字符串\0结尾,两个字符串长度相加有一个\0就行
if (len > hsmLen){
delete[] c_str; //清理内存
c_str = new char[len]; //重新分配字符串空间
hsmLen = len; //重置缓冲区长度
}
memcpy(this->c_str + hsLen -1, str.c_str, len - hsLen + 1);
hsLen = len;

return *this;
}
hstring &hstring::operator+(const hstring &str){
return *this << str;
}
hstring::~hstring(){
if (c_str != nullptr) delete[] c_str;
}
unsigned short hstring::gethsLen(const char *str) const{
unsigned short len = 0;
while (str[len++] != '\0');

return len;
}

void hstring::copyStrs(char *dest, const char *source){
unsigned short len = gethsLen(source);
if (len > hsmLen){
delete[] c_str;
c_str = new char[len]; //如果字符串的长度大于缓冲区就重新设置
hsmLen = len; //重置内存长度
}
memcpy(c_str, source, len);
hsLen = len; //重置字符串长度
}

std::ostream &operator<<(std::ostream &_cout, hstring _str){
_cout << _str.rtstr();
return _cout;
}

std::istream &operator>>(std::istream &_cin, hstring &_str){
char _len[0xff];
_cin >> _len;
_str = _len;

return _cin;
}

结语

字符串最重要的就是缓冲区的问题,尽量减少内存分配浪费的时间和内存。
同时也要注意安全。