自2003年开始,断断续续用了12年C++,直到这两年做物联网嵌入式开发,感觉对C++的掌握仅有10%左右。 习惯了C#开发,C++倒显得难以下手!今天就一个函数返回问题跟辉月兄弟讨论一番,大有所获,足以解决我们目前80%的问题,感觉对C++的掌握上升到了20%。 背景,现有字节数组ByteArray和字符串String,(不要激动,单片机嵌入式C++很难用起来标准类库) 我们需要实现函数String& ByteArray::ToHex() 其实这是我们在C#上非常常用的函数,把一个字节数组转为字符串,然后别的地方使用或者显示出来。C#原型String ToHex(this Byte[] buf) 这里有一个老大难题: 1,如果ToHex内部栈分配字符串空间,把字节数组填充进去,那么离开ToHex的时候栈回收,对象数据无效 2,如果ToHex内部堆分配空间,字节数组填充,离开ToHex的时候得到指针。但是这样违背了C/C++谁申请谁释放的原则,其它小伙伴使用ToHex的时候可能忘了释放 3,最后只能折中,做成String& ByteArray::ToHex(String& str); 别提多憋屈!最受不了的是,外部分配str的时候,还得考虑数组有多长!这些本来最好由ToHex内部解决的问题。 总之,这个问题就这样折腾了我12年! 知道今天,跟辉月兄弟聊起这个问题,他也有十多年C++历史,用得比我要多一些。他有一段常用代码大概如下:
CString Test()
{
CString a = "aaaa";
CString b = "bbbb";
CString c = a + b;
return c;
}
按他说法,就这样子写了十多年! 我说c不是栈分配吗?离开的时候会被析构吧,外部怎么可能拿到?他说是哦,从来没有考虑过这个问题。 我们敏锐的察觉到,C++一定可以实现类似的做法,因为字符串相加就是最常见的例子。 经过一番探讨,我们发现关键点出在拷贝构造函数上面 测试环境:编译器Keil MDK 5.14,处理器STM32F407VG 1、进出两次拷贝 做了一个测试代码,两次调用拷贝构造函数
class A
{
public:
char* str;
A(char* s)
{
str = s;
debug_printf("A %s 0x%08X\r\n", str, this);
}
A(const A &a)
{
debug_printf("A.Copy %s 0x%08X => %s 0x%08X\r\n", a.str, &a, str, this);
}
~A()
{
debug_printf("~A %s 0x%08X\r\n", str, this);
}
};
class B : public A
{
public:
B(char* s) : A(s)
{
debug_printf("B %s 0x%08X\r\n", str, this);
}
B(const B &b) : A(b.str)
{
debug_printf("B.Copy %s 0x%08X => %s 0x%08X\r\n", b.str, &b, str, this);
}
~B()
{
debug_printf("~B %s 0x%08X\r\n", str, this);
}
B& operator=(const B &b)
{
debug_printf("B.Assign %s 0x%08X => %s 0x%08X\r\n", b.str, &b, str, this);
return *this;
}
};
B fun(B c)
{
c.str = "c";
return c;
}
void CtorTest()
{
B a("a"), b("b");
debug_printf("start \r\n");
b = fun(a);
debug_printf("end \r\n");
}
执行结果如下:
A a 0x2001FB78
B a 0x2001FB78
A b 0x2001FB74
B b 0x2001FB74
start
A a 0x2001FB7C
B.Copy a 0x2001FB78 => a 0x2001FB7C
A c 0x2001FB80
B.Copy c 0x2001FB7C => c 0x2001FB80
B.Assign c 0x2001FB80 => b 0x2001FB74
~B c 0x2001FB80
~A c 0x2001FB80
~B c 0x2001FB7C
~A c 0x2001FB7C
end
~B b 0x2001FB74
~A b 0x2001FB74
~B a 0x2001FB78
~A a 0x2001FB78
2、进去拷贝出来引用 修改func函数,返回引用,少一次拷贝构造
B& fun(B c)
{
c.str = "c";
return c;
}
执行结果如下:
A a 0x2001FB70
B a 0x2001FB70
A b 0x2001FB6C
B b 0x2001FB6C
start
A a 0x2001FB74
B.Copy a 0x2001FB70 => a 0x2001FB74
B.Assign c 0x2001FB74 => b 0x2001FB6C
~B c 0x2001FB74
~A c 0x2001FB74
end
~B b 0x2001FB6C
~A b 0x2001FB6C
~A a 0x2001FB70
3、引用进去引用出来 修改参数传入引用,再少一次拷贝构造
B& fun(B& c)
{
c.str = "c";
return c;
}
执行结果如下:
A a 0x2001FB88
B a 0x2001FB88
A b 0x2001FB84
B b 0x2001FB84
start
B.Assign c 0x2001FB88 => b 0x2001FB84
end
~B b 0x2001FB84
~A b 0x2001FB84
~B c 0x2001FB88
~A c 0x2001FB88
End