条目十六《如何将vector和string的数据传给遗留的API》
优秀的代码是可以延续的,所以并非所有的代码都是重构的,而且有时候重构会对整个系统影响较大,投入巨大,得不偿失。然而,也不必为了系统的稳定而抛弃先进和方便的技术。
正如如果你想在遗留的老代码中想传vector
和string
给形参是数组的接口,那么可以按以下的方法做:
接口:void dosomething(const char* ptr, size_t size);
传vector
给dosomething
接口方法:
- 1、&vec[0]———————-可行
- 2、vec.begin()—————不正确
- 3、&*vec.begin()————可行
方法1是ok,这是因为vector在内存上是连续分布的,和数组的内存分布是一致的,所以对vector的首元素取地址就是获得vector分配内存的首地址,通过首地址就可以得到连续的整块内存。
但是需要注意的是,vecto可能为空的情况,这样传参过去,形参是null, size为0,这样是错误的。可以在调用函数的时候就判断vector是否非空。
if(!vec.empty())
{
dosomething(&vev[0], vec.size());
}
这才是标准的做法。
方法2是不正确的,这样做可能是因为有人觉得vec.begin()得到的是指向vector容器的首地址的迭代器,就相当于是容器的地址。这里有个误区,迭代器不是指针,二者并非是等价的。
方法3是正确的,对比方法2,方法3先取得容器的首个元素的迭代器,然后解引用获得首个元素,再对其取地址。这样就等同于&vec[0]。
##传string
给dosomething
接口方法:
- 1、
s.c_str()
——————正确 - 2、
&s[0]
——————————错误 - 3、
&*vec.begin()
————错误
string传参给char*需要注意了,由于string
的实现是多种多样的,string
对象的首个地址并非一定是字符串值的地址,详细的请看条目15的分析。所以直接传递string
的首元素的地址过去都是不正确,不管是直接对首元素取地址还是对取迭代器起始位置的解引用的地址,统统都不正确。
正确的做法是方法1,直接调用string.c_str()
接口,直接帮我们实现string
传参给char*
。
通过上面的分析知道,vector
可以很好的帮助我们实现容器传参给char*
的目的。从这可以展开来,可以把所有的容器先转换为vector
,然后再传递给以char*的形参的老接口。
但是在传递容器类型给以指针为形参的遗留API时需要注意,基本是传递const类型的,因为在函数内部对外部容器新增元素,容器是不知道它的size会发生变化的,因为不是通过容器本身改变新增元素,内部没有对size自增的,所以size真正没有改变。所以这样会造成容器数据的混乱,发生未定义行为。
上面举例子的都是以char*
为例子,真正情况下任何的基本类型的指针都是可以的。
例子:
void dosomething(const int* ptr, size_t size);
set<int> set;
for(int i = 0; i < 10; ++i)
{
set.insert(i);
}
vector<int> vec1(set.begin(), set.end());
if(!vec1.empty())
{
dosomething(&vec1[0], vec1.size());
}
list<int> list;
for(int i = 0; i < 10; ++i)
{
list.push_back(i);
}
vector<int> vec2(list.begin(), list.end());
if(!vec2.empty())
{
dosomething(&vec2[0], vec2.size());
}