简体   繁体   中英

How to iterate over std::vector<char> and find null-terminated c-strings

I have three questions based on the following code fragments
I have a list of strings. It just happens to be a vector but could potentially be any source

vector<string> v1_names = boost::assign::list_of("Antigua and Barbuda")( "Brasil")( "Papua New Guinea")( "Togo");

The following is to store lengths of each name

vector<int> name_len;

the following is where I want to store the strings

std::vector<char> v2_names;

estimate memory required to copy names from v1_names

v2_names.reserve( v1_names.size()*20 + 4 );

Question: is this the best way to estimate storage? I fix the max len at 20 that is ok, then add space for null treminator
Now copy the names

for( std::vector<std::string>::size_type i = 0; i < v1_names.size(); ++i)
{
    std::string val( v1_names[i] );
    name_len.push_back(val.length());
    for(std::string::iterator it = val.begin(); it != val.end(); ++it)
    {
        v2_names.push_back( *it );
    }
    v2_names.push_back('\0');
}

Question: is this the most efficient way to copy the elements from v1_name to v2_names?
Main Question: How do I iterate over v2_names and print the country names contained in v2_names

Use simple join, profit!

#include <boost/algorithm/string/join.hpp>
#include <vector>
#include <iostream>

int main(int, char **)
{
    vector<string> v1_names = boost::assign::list_of("Antigua and Barbuda")( "Brasil")( "Papua New Guinea")( "Togo");

    std::string joined = boost::algorithm::join(v1_names, "\0");
}

To estimate storage, you should probably measure the strings, rather than rely on a hard-coded constant 20. For example:

size_t total = 0;
for (std::vector<std::string>::iterator it = v1_names.begin(); it != v1_names.end(); ++it) {
    total += it->size() + 1;
}

The main inefficiency in your loop is probably that you take an extra copy of each string in turn: std::string val( v1_names[i] ); could instead be const std::string &val = v1_names[i]; .

To append each string, you can use the insert function:

v2_names.insert(v2_names.end(), val.begin(), val.end());
v2_names.push_back(0);

This isn't necessarily the most efficient, since there's a certain amount of redundant checking of available space in the vector, but it shouldn't be too bad and it's simple. An alternative would be to size v2_names at the start rather than reserving space, and then copy data (with std::copy ) rather than appending it. But either one of them might be faster, and it shouldn't make a lot of difference.

For the main question, if all you have is v2_names and you want to print the strings, you could do something like this:

const char *p = &v2_names.front();
while (p <= &v2_names.back()) {
    std::cout << p << "\n";
    p += strlen(p) + 1;
}

If you also have name_len :

size_t offset = 0;
for (std::vector<int>::iterator it = name_len.begin(); it != name_len.end(); ++it) {
    std::cout << &v2_names[offset] << "\n";
    offset += *it + 1;
}

Beware that the type of name_len is technically wrong - it's not guaranteed that you can store a string length in an int . That said, even if int is smaller than size_t in a particular implementation, strings that big will still be pretty rare.

The best way to compute the required storage is to sum up the length of each string in v1_names .

For your second question instead of using the for loop for you could just use the iterator, iterator append method of vector with begin and end on the string.

For your third question: Just don't do that. Iterate over v1_names 's strings instead. The only reason to ever create such a thing as v2_names is to pass it into a legacy C API and then you don't have to worry about iterating over it.

If you want to concatenate all the strings, you could just use a single pass and rely on amortized O(1) insertions:

name_len.reserve(v1_names.size());

// v2_names.reserve( ??? ); // only if you have a good heuristic or
                            // if you can determine this efficiently

for (auto it = v1_names.cbegin(); it != v1_names.cend(); ++it)
{
  name_len.push_back(it->size());
  v2_names.insert(v2_names.end(), it->c_str(), it->c_str() + it->size() + 1);
}

You could precompute the total length by another loop before this and call reserve if you think this will help. It depends on how well you know the strings. But perhaps there's no point worrying, since in the long run the insertions are O(1).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM