前言
在类的几个默认构造函数中,有一个拷贝用的,也叫副本构造。
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; unsigned short hsLen; 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; unsigned short hsLen; unsigned short hsmLen; unsigned short gethsLen(const char *str) const; 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; 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; unsigned short hsLen; unsigned short hsmLen; unsigned short gethsLen(const char *str) const; 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; 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
| #pragma once class hstring{ private: char *c_str; unsigned short hsLen; unsigned short hsmLen; unsigned short gethsLen(const char *str) const; 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
| #include "hstring.h" #include<iostream>
hstring::hstring(){ hsmLen = 0x32; hsLen = 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
不过好在位数上没有超过。
利用已有的函数实现虽然有点偷懒,但是能完成也不错。
还有个问题,就是正负数,要不要保留这个符号位。
结语
简单玩玩
主要区分初始化类时=
和重新赋值=
的区别。
前者为副本构造函数又称拷贝函数,后者乃是运算符重载的问题。
这里其实有点小坑~下一章会写出来