前言

在类的几个默认构造函数中,有一个拷贝用的,也叫副本构造。

1
2
3
4
5
class Box{
public:
int x;
int y;
};

默认的副本构造形式大概就是Box(const Box& box): x{ box.x }, y{ box.y }{ }
这也是为什么初始化的时候,Box b2(b1)是合法的。

1
2
3
4
5
6
7
int main(){
Box b1, b2;
b1.x = 10;
b1.y = 20;

b2 = b1;
}

而 b2 = b1 本质上就是因为运算符重载可以看到


正文

自己手写这个赋值运算符重载也不难

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Box{
public:
int x;
int y;
Box(){}
Box(const Box &box) : x{ box.x }, y{ box.y }{}
Box& operator=(const Box &box);
};

Box& Box::operator=(const Box &box){
x = box.x;
y = box.y;
return *this;
}

能够看到调用的是我们自己写的一个运算符重载。
虽然有些时候当成员变量很多的时候,没必要手动敲,就让生成默认的就行了。

c++的引用特性是个好东西,可以一定程度上节约内存

1
2
3
4
5
Box Box::operator=(const Box &box){
x = box.x;
y = box.y;
return *this;
}

如果函数返回值不是一个引用,那么每次都要构造一个临时变量充当,重复的过程多了,就会造成内存开销。


为什么要自定义

在某些情况下,希望类的某些值,通过特定方式去获取。那么就要打破常规,使用自定义的运算符重载。

1
2
3
4
5
6
7
8
9
10
11
class hstring{
private:
char *c_str; //hstring => char *c_str
unsigned short hsLen; //c_str length
public:
hstring();
hstring(const char* str);
hstring(const hstring& str);

char *rtstr(){ return c_str; }
};

拿之前hstring为例。

1
2
3
4
5
6
7
hstring::hstring(const char *str){
c_str = (char *)str;
}

int main(){
hstring str{ "123456" };
}

虽然现在初始化的时候是const char*的常量
但是如果传入一个变量。

1
2
char strA[]{ "aaabbbccc" };
hstring str{ strA };

当strA被改变时,str也会随之改变,因为本质上我们套的是char*,是传入参数的指针。

因此,要给hstring设置独立的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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);

char *rtstr(){ return c_str; }
};

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

在现有手段上,增加一个缓冲区,用来应对小内存的时候不用频繁的申请。
当然缺点是每生成一个所占的内存比较高。

然后稍微设计全一点:

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
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);

char *rtstr(){ return c_str; }
};

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);
}
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; //重置字符串长度
}

可以看到两种构造重载模式,一种是利用char*的字符串,另一种是传入一个hstring

即便是修改了原strA的值,也不会对后面的造成影响

然后在给他来个赋值运算符重载

1
2
3
4
5
6
hstring &operator=(const hstring &str);

hstring &hstring::operator=(const hstring &str){
copyStrs(c_str, str.c_str);
return *this;
}

发现赋值运算符重载没有问题

注意,当=用在初始化时,调用的必定是副本构造函数,因为类还未被实例化,没有内存,而当你已经实例化之后,=就是一种赋值运算符重载

1
hstring str3 = "我不说你不说";

这种实例化对象的时候,因为我们没有主动重写副本构造函数,所以它调用的是默认的副本构造函数。

当右值对象类型没有在类中有匹配的地方时,编译器会自动创建一个临时变量用以类型转换。所以没必要再写一个char*的运算符重载,即:

1
2
3
4
5
hstring &operator=(const char *str);

hstring &hstring::operator=(const char *str){
copyStrs(c_str, str);
}

封装一下

分文件的重要性

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; //重置字符串长度
}

扩展

转换一个longlong类型的数据为hstring

其实也不太靠谱,用的以前c语言的库函数

1
2
3
4
5
6
7
8
hstring &operator=(const long long &value);

hstring &hstring::operator=(const long long &value){
char ch[0x32];
sprintf(ch, "%lld", value);
copyStrs(c_str,ch);
return *this;
}

而且因为sprintf在vs中被认为是不安全的函数,所以要再头文件之上加一句屏蔽的
#define _CRT_SECURE_NO_WARNINGS

最后效果就是这样

这么操作肯定是有问题的,虽然说long long占用8字节
它的范围也就是-2^64 - 2^64-1 十进制也就是-9223372036854775808 至 9223372036854775807
不过好在位数上没有超过。
利用已有的函数实现虽然有点偷懒,但是能完成也不错。

还有个问题,就是正负数,要不要保留这个符号位。


结语

简单玩玩
主要区分初始化类时=和重新赋值=的区别。
前者为副本构造函数又称拷贝函数,后者乃是运算符重载的问题。

这里其实有点小坑~下一章会写出来