Effective STL:Item 16:如何将vector和string的数据传给传统的API函数

 

Item 16:如何将vector和string的数据传给传统的API函数

因为 C++语言已经于1998年被标准化,C++的中坚分子在试图推动程序员从数组转到vector时就没什么顾虑了。同样的情况也发生于从char *指针转到string对象的过程中。有很好的理由来做这些转变,比如可以消除常见的编程错误(Item 13),和有机会获得STL泛型算法的全部强大能力 (参见,比如,Item 31)。

但是,麻烦还是有的,而最常见的一个就是已经存在的传统C风格API函数接受的是数组和char *指针,而不是vector和string对象。这样的API函数还将会存在很长时间,如果我们要高效使用STL的话,就必须和它们和平共处。

幸运的是,这很容易。如果你有一个vector对象v,而你需要得到一个指向v中数据的指针,以使得它可以被当作一个数组,只要使用&v[0]就可以了。对于string对象s,相应的语法是很简单的s.c_str()。但只能从上面读。如广告时常指明的,有几个限制。。

给定一个

vector<int> v;

表达式v[0]生产一个指向vector中首元素的引用,所以,&v[0]是指向那个首元素的指针。vector中的元素被C++标准限定为存储在连续内存中,就象是一个数组,所以,如果我们传递v给如此形式的C风格API函数

void doSomething(const int* pInts, size_t numInts);

我们可以这么做:

doSomething(&v[0],v.size());

也许吧。可能吧。唯一的问题就是,如果v是空的。如果这样的话,v.size()是0,而&v[0]试图产生一个指向根本就不存在的东西的指针。这不是件好事。其结果未定义。一个较安全的方法是这样:

if (!v.empty()) {

doSomething(&v[0], v.size());

}

如果走得不对路你可能会碰到些半瓶子的人物他们会告诉你说可以用v.begin()代替&v[0],因为(这些讨厌的家伙将会告诉你)begin()返回指向vector内部的iterator,而对于vector,iterators实际上是指针。那经常是正确的,但如Item 50所说,并不总是如此,你不该依赖于此。begin()的返回类型是iterator,而不是一个指针,当你需要一个指向vector内部数据的指针时绝不该使用begin()。如果你基于某些原因决定键入v.begin(),就键入&*v.begin(), 因为这将会产生和&v[0]相同的指针,虽然它让你有更多的击键工作且让代码读起来更晦涩。坦白地说,如果你正被告诉你使用v.begin()代替&v[0]的人围绕的话,你该重新考虑一下你的社交圈了。

类似的从vector上获取指向内部数据的指针的方法,对string是不可靠的,因为 (1) string中的数据并没有承诺被存储在连续内存中,(2)string的内部表示形式并没承诺以一个空字符结束。这解释了string的成员函数c_str()存在的原因,它返回一个按C风格设计指针,指向string的值(which returns a pointer to the value of the string in a form designed for C)。 我们可以如此传递一个string对象s给这个函数,

void doSomething(const char *pString);

就象这样

doSomething(s.c_str());

即使是字符串的长度为0,它都能工作。在那种情况下c_str()将返回一个指向结束符的指针。即使字符串内部自己存在结束符时,它同样能工作。然而,如果真的这样,doSomething很可能将第一个结束符解释为字符串结束。string对象不在意是否容纳了结束符,但基于char *的C风格API函数在意。

再看一下doSomething()的申明

void doSomething(const int* pInts, size_t numInts);

void doSomething(const char *pString);

在两种形式下,指针都被传递为指向const的指针。vector和string的数据被传给只读取而不修改它们的API函数。到目前为止都做的是最安全的事情。对于string,这也是唯一可做的,因为没有承诺说c_str()产生的指针指在string数据的内部表示形式上;它可以返回一个指针指向数据的一个不可修改的拷贝,这个拷贝满足C风格API函数对格式的要求。(如果这个恐吓令你寒毛都立起来的话,还请宽心,因为它也许不成立。我没听说目前哪个运行库的实现是使用了这个自由权的。)

对于vector,有更多一点点灵活性。如果你将v传给一个修改其元素的C风格API函数的话,典型情况都是没问题,但被调用的函数绝不能试图改变vector中元素的个数。比如,它绝不能试图在vector还未使用的容量(capacity)上“创建”新的元素。如果这么干了,v将会变得内部状态不一致,因为它再也不知道自己的正确大小(size)了。v.size()将会得到一个不正确的结果。并且,如果被调用的函数试图在一个大小和容量(见Item 14)相等的vector上追加数据的话,真的会发生灾难性事件。我甚至根本就不愿去想象它。实在太可怕了。

你注意到我在前面用的是“典型地”一词吗?你当然注意到了。有些vector对其数据有些额外的限制,你一定要确保这些额外限制继续被满足。举个例子,Item 23解释了排序的vector常用来实现关联容器,但对这些vector而言,保持排序非常重要。如果你将一个排序的vector传给一个可能修改其数据的API函数,你需要重视vector在调用返回后不再保持排序的情况。

如果你想用C风格API函数返回的元素初始化一个vector,你可以利用vector和数组内在的相容性,通过将存储vecotr的元素的空间传给API函数:

// C API: this function takes a pointer to an array of at most arraySize

// doubles and writes data to it. It returns the number of doubles written,

// which is never more than maxNumDoubles.

size_t fillArray(double *pArray, size_t arraySize);

vector<double> vd(maxNumDoubles);                     // create a vector whose

// size is maxNumDoubles

vd.resize(fillArray(&vd[0], vd.size()));              // have fillArray write data

// into vd, then resize vd

// to the number of

// elements fillArray wrote

这个技巧只能工作于vector,因为只有vector承诺了与数组具有相同的内在内存分布。但是,如果你想用来自C风格API函数的数据初始化string对象,你可以做得足够简单。只要让API函数将数据放入一个vector<char>,然后从vector中将数据拷到string:

// C API: this function takes a pointer to an array of at most arraySize

// chars and writes data to it. It returns the number of chars written,

// which is never more than maxNumChars.

size_t fillString(char *pArray, size_t arraySize);

vector<char> vc(maxNumChars);                         // create a vector whose

// size is maxNumChars

size_t charsWritten = fillString(&vc[0], vc.size());  // have fillString write

// into vc

string s(vc.begin(), vc.begin()+charsWritten);        // copy data from vc to s

// via range constructor

// ( see Item 5)

事实上,这个主意总是有效的:让C风格API函数将数据放入一个vector,然后拷到你实际想要的STL容器中。

size_t fillArray(double *pArray, size_t arraySize);   // as above

vector<double> vd(maxNumDoubles);                     // also as above

vd.resize(fillArray(&vd[0], vd.size());

deque<double> d(vd.begin(), vd.end());                // copy data into

// deque

list<double> l(vd.begin(), vd.end());                 // copy data into list

set<double> s(vd.begin(), vd.end());                  // copy data into set

此外,这也提示了vector和string以外的STL容器如何将它们的数据传给C风格API函数。只要将容器的每个数据拷到vector,然后将它们传给API函数:

void doSomething(const int* pInts, size_t numInts);   // C API (from above)

set<int> intSet;                                      // set that will hold

...                                                   // data to pass to API

vector<int> v(intSet.begin(), intSet.end());          // copy set data into

// a vector

if (!v.empty()) doSomething(&v[0], v.size());         // pass the data to

// the API

你也可以将数据拷进一个数组,然后将数组传给C风格的API,但你为什么想这样做?除非你在编译期就知道容器的大小,否则你不得不分配动态数组,而Item 13解释了为什么你应该总是使用vector来取代动态分配的数组。

 

你可能感兴趣的:(C++,String,vector,api,iterator,Constructor)