将std::vector<std::string>转换为char*数组

36

我有一个std::vector<std::string>,我需要将其用作读取char* fooC函数参数。我已经看到如何std::string转换为char*。作为一个新手,我正在尝试组合如何对向量的每个元素执行此转换并生成char*数组。

我已经看到了几个相关的SO问题,但大多数似乎是展示如何沿着相反的方向创建std::vector<std::string>


3
什么是准确的C接口?根据const的位置和函数在使用内存时的处理方式,我们可以做出几种不同的操作(C函数可能会执行像调用realloc这样的恶性操作)。 - Martin York
模型* ModelInitialize(char *fnames,int nterms) - Christopher DuBois
1
该函数接受的是 char*,而不是你问题中提到的char**。哪一个是正确的? - Luc Danton
抱歉造成混淆。我当时误看了一个以char ** fnames作为参数并在后续调用ModelInitialize函数的函数。 - Christopher DuBois
为什么 fnames 参数不是const类型?对于一个初始化器修改其参数似乎很奇怪。 - Mankarse
2
仅凭函数签名显然没有足够的信息来确定正确的操作方式。 fnames 的所有权是否转移到了 ModelInitialize?(如果是这样:它必须如何分配?)调用代码是否打算从 ModelInitialize 返回的 Model 进行 deletefree 或其他释放操作?(如果是这样:它必须如何被释放?)fnames 必须是以空字符结尾的字符串吗? fnames 可以以哪些方式进行修改? - Mankarse
6个回答

38

你可以使用 std::transform 如下:

std::transform(vs.begin(), vs.end(), std::back_inserter(vc), convert);  

这要求你实现convert()函数:

char *convert(const std::string & s)
{
   char *pc = new char[s.size()+1];
   std::strcpy(pc, s.c_str());
   return pc; 
}

测试代码:

int main() {
       std::vector<std::string>  vs;
       vs.push_back("std::string");
       vs.push_back("std::vector<std::string>");
       vs.push_back("char*");
       vs.push_back("std::vector<char*>");
       std::vector<char*>  vc;

       std::transform(vs.begin(), vs.end(), std::back_inserter(vc), convert);   

       for ( size_t i = 0 ; i < vc.size() ; i++ )
            std::cout << vc[i] << std::endl;

       for ( size_t i = 0 ; i < vc.size() ; i++ )
            delete [] vc[i];
}

输出:

std::string
std::vector<std::string>
char*
std::vector<char*>

在线演示:http://ideone.com/U6QZ5

在需要char**的任何地方,您都可以使用&vc[0]

请注意,由于我们正在使用new为每个std::string(在convert函数中)分配内存,因此我们必须在最后处理内存。这使您可以更改向量vs;您可以将更多字符串push_back到其中,从vs删除现有字符串,并且vc(即vector<char*>)仍将有效!

但是,如果您不想要这种灵活性,则可以使用此convert函数:

const char *convert(const std::string & s)
{
   return s.c_str();
}

你需要将 std::vector<char*> 改为 std::vector<const char*>

现在进行转换后,如果你通过向 vs 中插入新字符串或从中删除旧字符串来更改它,那么所有在 vc 中的 char* 可能会变得无效。这是一个重要的点。另一个重要的点是你不再需要在代码中使用 delete vc[i] 了。


快速问题:如果我们使用std::vector,为什么需要删除[] vc部分? - Christopher DuBois
1
如果convert中的新代码抛出异常,那么这段代码就会泄漏。最好使用std::vector<char> - Mankarse
@Christopher:请看我的回答。现在它有更多的解释。 - Nawaz
谢谢你的回答!有没有办法将vector<string>转换为char,以便我可以将其复制到其他地方?在你的方法中,vc.size()看起来是4,但字符串的长度各不相同。在这种情况下,我该如何将char**转换为char?我应该在其他地方记住各个字符串的长度吗? - Legend
@传说:vs.size()vs包含的各个字符串的长度毫无关系。此外,请阅读有关空终止字符串的内容。请注意,有效的c字符串必须以空终止符结尾,这有助于计算任何c字符串的长度。 - Nawaz
显示剩余3条评论

11

您最好的选择是分配一个与您的vector大小相同的std::vector,其中每个元素都是const char*类型。然后遍历vector中的每个元素,调用c_str()方法获取字符串数组,并将其存储在数组的相应元素中。然后,您可以将指向此向量第一个元素的指针传递给相关函数。

代码如下:

std::vector<const char *> cStrArray;
cStrArray.reserve(origVector.size());
for(int index = 0; index < origVector.size(); ++index)
{
  cStrArray.push_back(origVector[index].c_str());
}

//NO RESIZING OF origVector!!!!

SomeCFunction(&cStrArray[0], cStrArray.size());

请注意,你不能在从std::strings获取const char*的时间和调用C函数的时间之间改变原始字符串向量的大小。


c_str()不是返回const char吗?如果我只需要char*,那会有问题吗?(我在注释中包含了精确的接口。) - Christopher DuBois
你也可以使用std::vector<const char *>cStrArray(origVector.size()+1, NULL);,然后在迭代器中使用cStrArray[i]=origVector[i].c_str(); 这可以帮助处理execv()等函数。但正如上面的注释所说,我们需要更多关于ModelInitialize的信息。 - don bright

8

这应该可以正常工作:

char ** arr = new char*[vec.size()];
for(size_t i = 0; i < vec.size(); i++){
    arr[i] = new char[vec[i].size() + 1];
    strcpy(arr[i], vec[i].c_str());
}

编辑:

以下是释放这些数据结构的方法,假设vec仍然具有正确数量的元素,如果您的C函数以某种方式修改了此数组,则可能需要以其他方式获取大小。

for(size_t i = 0; i < vec.size(); i++){
    delete [] arr[i];
}
delete [] arr;

再次编辑:

如果你的C函数不修改字符串,那么复制这些字符串可能是不必要的。如果你能详细说明你的接口长什么样子,我们肯定能够提供更好的帮助。


你需要展示如何删除那个数组,特别是因为它非常复杂。不要忘记使用 delete[] - Nicol Bolas
1
如果for中的new抛出异常,这将会泄漏。最好使用std::vector - Mankarse

0
一个C++0x的解决方案,其中std::string的元素保证被连续存储:
std::vector<std::string> strings = /* from somewhere */;
int nterms = /* from somewhere */;

// using std::transform is a possibility depending on what you want
// to do with the result of the call
std::for_each(strings.begin(), string.end(), [nterms](std::string& s)
{ ModelInitialize(&s[0], nterms); }

如果函数以空终止其参数,则调用后(s.begin(), s.end())可能没有意义。您可以进行后处理以解决此问题:
s = std::string(s.begin(), std::find(s.begin(), s.end(), '\0'));

一个更详细的版本,它将每个字符串分别复制到char[]中:
typedef std::unique_ptr<char[]> pointer;
std::vector<pointer> args;
std::transform(strings.begin(), strings.end()
               , std::back_inserter(args)
               , [](std::string const& s) -> pointer
{
    pointer p(new char[s.size()]);
    std::copy(s.begin(), s.end(), &p[0]);
    return p;
});

std::for_each(args.begin(), args.end(), [nterms](pointer& p)
{ ModelInitialize(p.get(), nterms); });

-1

const char* 与 char* 是相同的,只是在 const_ness 上有所不同,您的接口方法接受 const 和非 const 字符串。

c_str() 不是返回 const char 吗?如果我只需要 char*,这会有问题吗?

是的,它返回一个 const 字符串,但不应该有任何问题。

const char*a="something";
////whatever it is here
const char* retfunc(const char*a)
{
   char*temp=a;
   //process then return temp
}

返回本地对象并不被许多人接受,这个小例子仅供参考。


4
这段代码无法编译。将指向常量的指针赋值给指向非常量的指针既不合法也不安全。 - Mankarse
1
“返回本地对象不被许多人接受” // 不,这是垃圾。返回对本地对象的引用或指针不被语言或编译器接受。但这并不相同。 - Lightness Races in Orbit
1
这段代码不仅不合法,而且即使您实际上去掉了“const”关键字,也是一件极为愚蠢的事情。 - Lightness Races in Orbit
感谢Tomalak的纠正和评论,请不要太苛刻,这只是另一个想法,如何使其更安全仍取决于OP的实际编码经验... - Marc
"const char* 也是和 char* 一样的,只是在 const 的程度上有所不同。这就像说“苹果和橙子是一样的,只是不同而已”,并没有什么帮助。" - 463035818_is_not_a_number

-2

向量的元素连续存储,因此最好且简单的方法是:

std::vector<char> v;
char* c = &v[0];

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接