将向量 < int > 转换为字符串

我有一个包含整数(例如{1,2,3,4})的 vector<int>容器,我想转换为表单的字符串

"1,2,3,4"

在 C + + 中做到这一点的最干净的方法是什么? 在 Python 中,我是这样做的:

>>> array = [1,2,3,4]
>>> ",".join(map(str,array))
'1,2,3,4'
114879 次浏览

Definitely not as elegant as Python, but nothing quite is as elegant as Python in C++.

You could use a stringstream ...

#include <sstream>
//...


std::stringstream ss;
for(size_t i = 0; i < v.size(); ++i)
{
if(i != 0)
ss << ",";
ss << v[i];
}
std::string s = ss.str();

You could also make use of std::for_each instead.

Using std::for_each and lambda you can do something interesting.

#include <iostream>
#include <sstream>


int main()
{
int  array[] = {1,2,3,4};
std::for_each(std::begin(array), std::end(array),
[&std::cout, sep=' '](int x) mutable {
out << sep << x; sep=',';
});
}

See this question for a little class I wrote. This will not print the trailing comma. Also if we assume that C++14 will continue to give us range based equivalents of algorithms like this:

namespace std {
// I am assuming something like this in the C++14 standard
// I have no idea if this is correct but it should be trivial to write if it  does not appear.
template<typename C, typename I>
void copy(C const& container, I outputIter) {copy(begin(container), end(container), outputIter);}
}
using POI = PrefexOutputIterator;
int main()
{
int  array[] = {1,2,3,4};
std::copy(array, POI(std::cout, ","));
// ",".join(map(str,array))               // closer
}

Another alternative is the use of std::copy and the ostream_iterator class:

#include <iterator>  // ostream_iterator
#include <sstream>   // ostringstream
#include <algorithm> // copy


std::ostringstream stream;
std::copy(array.begin(), array.end(), std::ostream_iterator<>(stream));
std::string s=stream.str();
s.erase(s.length()-1);

Also not as nice as Python. For this purpose, I created a join function:

template <class T, class A>
T join(const A &begin, const A &end, const T &t)
{
T result;
for (A it=begin;
it!=end;
it++)
{
if (!result.empty())
result.append(t);
result.append(*it);
}
return result;
}

Then used it like this:

std::string s=join(array.begin(), array.end(), std::string(","));

You might ask why I passed in the iterators. Well, actually I wanted to reverse the array, so I used it like this:

std::string s=join(array.rbegin(), array.rend(), std::string(","));

Ideally, I would like to template out to the point where it can infer the char type, and use string-streams, but I couldn't figure that out yet.

This is just an attempt to solve the riddle given by 1800 INFORMATION's remark on his second solution lacking genericity, not an attempt to answer the question:

template <class Str, class It>
Str join(It begin, const It end, const Str &sep)
{
typedef typename Str::value_type     char_type;
typedef typename Str::traits_type    traits_type;
typedef typename Str::allocator_type allocator_type;
typedef std::basic_ostringstream<char_type,traits_type,allocator_type>
ostringstream_type;
ostringstream_type result;


if(begin!=end)
result << *begin++;
while(begin!=end) {
result << sep;
result << *begin++;
}
return result.str();
}

Works On My Machine(TM).

I like 1800's answer. However I would move the first iteration out of the loop as as the result of the if statement only changes once after the first iteration

template <class T, class A>
T join(const A &begin, const A &end, const T &t)
{
T result;
A it = begin;
if (it != end)
{
result.append(*it);
++it;
}


for( ;
it!=end;
++it)
{
result.append(t);
result.append(*it);
}
return result;
}

This can of course be reduced down to fewer statements if you like:

template <class T, class A>
T join(const A &begin, const A &end, const T &t)
{
T result;
A it = begin;
if (it != end)
result.append(*it++);


for( ; it!=end; ++it)
result.append(t).append(*it);
return result;
}

There are some interesting attempts at providing an elegant solution to the problem. I had an idea to use templated streams to effectively answer the OP's original dilemma. Though this is an old post, I'm hoping future users who stumble upon this will find my solution beneficial.

First, some answers (including the accepted answer) do not promote re-usability. Since C++ doesn't provide an elegant way to join strings in the standard library (that I have seen), it becomes important to create one that is flexible and reusable. Here's my shot at it:

// Replace with your namespace //
namespace my {
// Templated join which can be used on any combination of streams, iterators and base types //
template <typename TStream, typename TIter, typename TSeperator>
TStream& join(TStream& stream, TIter begin, TIter end, TSeperator seperator) {
// A flag which, when true, has next iteration prepend our seperator to the stream //
bool sep = false;
// Begin iterating through our list //
for (TIter i = begin; i != end; ++i) {
// If we need to prepend a seperator, do it //
if (sep) stream << seperator;
// Stream the next value held by our iterator //
stream << *i;
// Flag that next loops needs a seperator //
sep = true;
}
// As a convenience, we return a reference to the passed stream //
return stream;
}
}

Now to use this, you could simply do something like the following:

// Load some data //
std::vector<int> params;
params.push_back(1);
params.push_back(2);
params.push_back(3);
params.push_back(4);


// Store and print our results to standard out //
std::stringstream param_stream;
std::cout << my::join(param_stream, params.begin(), params.end(), ",").str() << std::endl;


// A quick and dirty way to print directly to standard out //
my::join(std::cout, params.begin(), params.end(), ",") << std::endl;

Note how the use of streams makes this solution incredibly flexible as we can store our result in a stringstream to reclaim it later, or we can write directly to the standard out, a file, or even to a network connection implemented as a stream. The type being printed must simply be iteratable and compatible with the source stream. STL provides various streams which are compatible with a large range of types. So you could really go to town with this. Off the top of my head, your vector can be of int, float, double, string, unsigned int, SomeObject*, and more.

Lots of template/ideas. Mine's not as generic or efficient, but I just had the same problem and wanted to throw this into the mix as something short and sweet. It wins on shortest number of lines... :)

std::stringstream joinedValues;
for (auto value: array)
{
joinedValues << value << ",";
}
//Strip off the trailing comma
std::string result = joinedValues.str().substr(0,joinedValues.str().size()-1);

You can use std::accumulate. Consider the following example

if (v.empty()
return std::string();
std::string s = std::accumulate(v.begin()+1, v.end(), std::to_string(v[0]),
[](const std::string& a, int b){
return a + ',' + std::to_string(b);
});

If you want to do std::cout << join(myVector, ",") << std::endl;, you can do something like:

template <typename C, typename T> class MyJoiner
{
C &c;
T &s;
MyJoiner(C &&container, T&& sep) : c(std::forward<C>(container)), s(std::forward<T>(sep)) {}
public:
template<typename C, typename T> friend std::ostream& operator<<(std::ostream &o, MyJoiner<C, T> const &mj);
template<typename C, typename T> friend MyJoiner<C, T> join(C &&container, T&& sep);
};


template<typename C, typename T> std::ostream& operator<<(std::ostream &o, MyJoiner<C, T> const &mj)
{
auto i = mj.c.begin();
if (i != mj.c.end())
{
o << *i++;
while (i != mj.c.end())
{
o << mj.s << *i++;
}
}


return o;
}


template<typename C, typename T> MyJoiner<C, T> join(C &&container, T&& sep)
{
return MyJoiner<C, T>(std::forward<C>(container), std::forward<T>(sep));
}

Note, this solution does the join directly into the output stream rather than creating a secondary buffer and will work with any types that have an operator<< onto an ostream.

This also works where boost::algorithm::join() fails, when you have a vector<char*> instead of a vector<string>.

With Boost and C++11 this could be achieved like this:

auto array = {1,2,3,4};
join(array | transformed(tostr), ",");

Well, almost. Here's the full example:

#include <array>
#include <iostream>


#include <boost/algorithm/string/join.hpp>
#include <boost/range/adaptor/transformed.hpp>


int main() {
using boost::algorithm::join;
using boost::adaptors::transformed;
auto tostr = static_cast<std::string(*)(int)>(std::to_string);


auto array = {1,2,3,4};
std::cout << join(array | transformed(tostr), ",") << std::endl;


return 0;
}

Credit to Praetorian.

You can handle any value type like this:

template<class Container>
std::string join(Container const & container, std::string delimiter) {
using boost::algorithm::join;
using boost::adaptors::transformed;
using value_type = typename Container::value_type;


auto tostr = static_cast<std::string(*)(value_type)>(std::to_string);
return join(container | transformed(tostr), delimiter);
};

as @capone did ,

std::string join(const std::vector<std::string> &str_list ,
const std::string &delim=" ")
{
if(str_list.size() == 0) return "" ;
return std::accumulate( str_list.cbegin() + 1,
str_list.cend(),
str_list.at(0) ,
[&delim](const std::string &a , const std::string &b)
{
return a + delim + b ;
}  ) ;
}


template <typename ST , typename TT>
std::vector<TT> map(TT (*op)(ST) , const vector<ST> &ori_vec)
{
vector<TT> rst ;
std::transform(ori_vec.cbegin() ,
ori_vec.cend() , back_inserter(rst) ,
[&op](const ST& val){ return op(val)  ;} ) ;
return rst ;
}

Then we can call like following :

int main(int argc , char *argv[])
{
vector<int> int_vec = {1,2,3,4} ;
vector<string> str_vec = map<int,string>(to_string, int_vec) ;
cout << join(str_vec) << endl ;
return 0 ;
}

just like python :

>>> " ".join( map(str, [1,2,3,4]) )

I use something like this

namespace std
{


// for strings join
string to_string( string value )
{
return value;
}


} // namespace std


namespace // anonymous
{


template< typename T >
std::string join( const std::vector<T>& values, char delimiter )
{
std::string result;
for( typename std::vector<T>::size_type idx = 0; idx < values.size(); ++idx )
{
if( idx != 0 )
result += delimiter;
result += std::to_string( values[idx] );
}
return result;
}


} // namespace anonymous

I've created an helper header file to add an extended join support.

Just add the code below to your general header file and include it when needed.

Usage Examples:

/* An example for a mapping function. */
ostream&
map_numbers(ostream& os, const void* payload, generic_primitive data)
{
static string names[] = {"Zero", "One", "Two", "Three", "Four"};
os << names[data.as_int];
const string* post = reinterpret_cast<const string*>(payload);
if (post) {
os << " " << *post;
}
return os;
}


int main() {
int arr[] = {0,1,2,3,4};
vector<int> vec(arr, arr + 5);
cout << vec << endl; /* Outputs: '0 1 2 3 4' */
cout << join(vec.begin(), vec.end()) << endl; /* Outputs: '0 1 2 3 4' */
cout << join(vec.begin(), vec.begin() + 2) << endl; /* Outputs: '0 1 2' */
cout << join(vec.begin(), vec.end(), ", ") << endl; /* Outputs: '0, 1, 2, 3, 4' */
cout << join(vec.begin(), vec.end(), ", ", map_numbers) << endl; /* Outputs: 'Zero, One, Two, Three, Four' */
string post = "Mississippi";
cout << join(vec.begin() + 1, vec.end(), ", ", map_numbers, &post) << endl; /* Outputs: 'One Mississippi, Two mississippi, Three mississippi, Four mississippi' */
return 0;
}

The code behind the scene:

#include <iostream>
#include <vector>
#include <list>
#include <set>
#include <unordered_set>
using namespace std;


#define GENERIC_PRIMITIVE_CLASS_BUILDER(T) generic_primitive(const T& v) { value.as_##T = v; }
#define GENERIC_PRIMITIVE_TYPE_BUILDER(T) T as_##T;


typedef void* ptr;


/** A union that could contain a primitive or void*,
*    used for generic function pointers.
* TODO: add more primitive types as needed.
*/
struct generic_primitive {
GENERIC_PRIMITIVE_CLASS_BUILDER(int);
GENERIC_PRIMITIVE_CLASS_BUILDER(ptr);
union {
GENERIC_PRIMITIVE_TYPE_BUILDER(int);
GENERIC_PRIMITIVE_TYPE_BUILDER(ptr);
};
};


typedef ostream& (*mapping_funct_t)(ostream&, const void*, generic_primitive);
template<typename T>
class Join {
public:
Join(const T& begin, const T& end,
const string& separator = " ",
mapping_funct_t mapping = 0,
const void* payload = 0):
m_begin(begin),
m_end(end),
m_separator(separator),
m_mapping(mapping),
m_payload(payload) {}


ostream&
apply(ostream& os) const
{
T begin = m_begin;
T end = m_end;
if (begin != end)
if (m_mapping) {
m_mapping(os, m_payload, *begin++);
} else {
os << *begin++;
}
while (begin != end) {
os << m_separator;
if (m_mapping) {
m_mapping(os, m_payload, *begin++);
} else {
os << *begin++;
}
}
return os;
}
private:
const T& m_begin;
const T& m_end;
const string m_separator;
const mapping_funct_t m_mapping;
const void* m_payload;
};


template <typename T>
Join<T>
join(const T& begin, const T& end,
const string& separator = " ",
ostream& (*mapping)(ostream&, const void*, generic_primitive) = 0,
const void* payload = 0)
{
return Join<T>(begin, end, separator, mapping, payload);
}


template<typename T>
ostream&
operator<<(ostream& os, const vector<T>& vec) {
return join(vec.begin(), vec.end()).apply(os);
}


template<typename T>
ostream&
operator<<(ostream& os, const list<T>& lst) {
return join(lst.begin(), lst.end()).apply(os);
}


template<typename T>
ostream&
operator<<(ostream& os, const set<T>& s) {
return join(s.begin(), s.end()).apply(os);
}


template<typename T>
ostream&
operator<<(ostream& os, const Join<T>& vec) {
return vec.apply(os);
}

I started out with @sbi's answer but most of the time ended up piping the resulting string to a stream so created the below solution that can be piped to a stream without the overhead of creating the full string in memory.

It is used as follows:

#include "string_join.h"
#include <iostream>
#include <vector>


int main()
{
std::vector<int> v = { 1, 2, 3, 4 };
// String version
std::string str = join(v, std::string(", "));
std::cout << str << std::endl;
// Directly piped to stream version
std::cout << join(v, std::string(", ")) << std::endl;
}

Where string_join.h is:

#pragma once


#include <iterator>
#include <sstream>


template<typename Str, typename It>
class joined_strings
{
private:
const It begin, end;
Str sep;


public:
typedef typename Str::value_type char_type;
typedef typename Str::traits_type traits_type;
typedef typename Str::allocator_type allocator_type;


private:
typedef std::basic_ostringstream<char_type, traits_type, allocator_type>
ostringstream_type;


public:
joined_strings(It begin, const It end, const Str &sep)
: begin(begin), end(end), sep(sep)
{
}


operator Str() const
{
ostringstream_type result;
result << *this;
return result.str();
}


template<typename ostream_type>
friend ostream_type& operator<<(
ostream_type &ostr, const joined_strings<Str, It> &joined)
{
It it = joined.begin;
if(it!=joined.end)
ostr << *it;
for(++it; it!=joined.end; ++it)
ostr << joined.sep << *it;
return ostr;
}
};


template<typename Str, typename It>
inline joined_strings<Str, It> join(It begin, const It end, const Str &sep)
{
return joined_strings<Str, It>(begin, end, sep);
}


template<typename Str, typename Container>
inline joined_strings<Str, typename Container::const_iterator> join(
Container container, const Str &sep)
{
return join(container.cbegin(), container.cend(), sep);
}

Here's a generic C++11 solution that will let you do

int main() {
vector<int> v {1,2,3};
cout << join(v, ", ") << endl;
string s = join(v, '+').str();
}

The code is:

template<typename Iterable, typename Sep>
class Joiner {
const Iterable& i_;
const Sep& s_;
public:
Joiner(const Iterable& i, const Sep& s) : i_(i), s_(s) {}
std::string str() const {std::stringstream ss; ss << *this; return ss.str();}
template<typename I, typename S> friend std::ostream& operator<< (std::ostream& os, const Joiner<I,S>& j);
};


template<typename I, typename S>
std::ostream& operator<< (std::ostream& os, const Joiner<I,S>& j) {
auto elem = j.i_.begin();
if (elem != j.i_.end()) {
os << *elem;
++elem;
while (elem != j.i_.end()) {
os << j.s_ << *elem;
++elem;
}
}
return os;
}


template<typename I, typename S>
inline Joiner<I,S> join(const I& i, const S& s) {return Joiner<I,S>(i, s);}

I have wrote the following code. It is based in C# string.join. It works with std::string and std::wstring and many type of vectors. (examples in comments)

Call it like this:

 std::vector<int> vVectorOfIds = {1, 2, 3, 4, 5};


std::wstring wstrStringForSQLIn = Join(vVectorOfIds, L',');

Code:

// Generic Join template (mimics string.Join() from C#)
// Written by RandomGuy (stackoverflow) 09-01-2017
// Based on Brian R. Bondy anwser here:
// http://stackoverflow.com/questions/1430757/c-vector-to-string
// Works with char, wchar_t, std::string and std::wstring delimiters
// Also works with a different types of vectors like ints, floats, longs
template<typename T, typename D>
auto Join(const std::vector<T> &vToMerge, const D &delimiter)
{
// We use std::conditional to get the correct type for the stringstream (char or wchar_t)
// stringstream = basic_stringstream<char>, wstringstream = basic_stringstream<wchar_t>
using strType =
std::conditional<
std::is_same<D, std::string>::value,
char,
std::conditional<
std::is_same<D, char>::value,
char,
wchar_t
>::type
>::type;


std::basic_stringstream<strType> ss;


for (size_t i = 0; i < vToMerge.size(); ++i)
{
if (i != 0)
ss << delimiter;
ss << vToMerge[i];
}
return ss.str();
}

The following is a simple and practical way to convert elements in a vector to a string:

std::string join(const std::vector<int>& numbers, const std::string& delimiter = ",") {
std::ostringstream result;
for (const auto number : numbers) {
if (result.tellp() > 0) { // not first round
result << delimiter;
}
result << number;
}
return result.str();
}

You need to #include <sstream> for ostringstream.

Expanding on the attempt of @sbi at a generic solution that is not restricted to std::vector<int> or a specific return string type. The code presented below can be used like this:

std::vector<int> vec{ 1, 2, 3 };


// Call modern range-based overload.
auto str     = join( vec,  "," );
auto wideStr = join( vec, L"," );


// Call old-school iterator-based overload.
auto str     = join( vec.begin(), vec.end(),  "," );
auto wideStr = join( vec.begin(), vec.end(), L"," );

In the original code, template argument deduction does not work to produce the right return string type if the separator is a string literal (as in the samples above). In this case, the typedefs like Str::value_type in the function body are incorrect. The code assumes that Str is always a type like std::basic_string, so it obviously fails for string literals.

To fix this, the following code tries to deduce only the character type from the separator argument and uses that to produce a default return string type. This is achieved using boost::range_value, which extracts the element type from the given range type.

#include <string>
#include <sstream>
#include <boost/range.hpp>


template< class Sep, class Str = std::basic_string< typename boost::range_value< Sep >::type >, class InputIt >
Str join( InputIt first, const InputIt last, const Sep& sep )
{
using char_type          = typename Str::value_type;
using traits_type        = typename Str::traits_type;
using allocator_type     = typename Str::allocator_type;
using ostringstream_type = std::basic_ostringstream< char_type, traits_type, allocator_type >;


ostringstream_type result;


if( first != last )
{
result << *first++;
}
while( first != last )
{
result << sep << *first++;
}
return result.str();
}

Now we can easily provide a range-based overload that simply forwards to the iterator-based overload:

template <class Sep, class Str = std::basic_string< typename boost::range_value<Sep>::type >, class InputRange>
Str join( const InputRange &input, const Sep &sep )
{
// Include the standard begin() and end() in the overload set for ADL. This makes the
// function work for standard types (including arrays), aswell as any custom types
// that have begin() and end() member functions or overloads of the standalone functions.
using std::begin; using std::end;


// Call iterator-based overload.
return join( begin(input), end(input), sep );
}

Live Demo at Coliru

string s;
for (auto i : v)
s += (s.empty() ? "" : ",") + to_string(i);

Here is an easy way to convert a vector of integers to strings.

#include <bits/stdc++.h>
using namespace std;
int main()
{
vector<int> A = {1, 2, 3, 4};
string s = "";
for (int i = 0; i < A.size(); i++)
{
s = s + to_string(A[i]) + ",";
}
s = s.substr(0, s.length() - 1); //Remove last character
cout << s;
}

join using template function

I used a template function to join the vector items, and removed the unnecessary if statement by iterating through only the first to penultimate items in the vector, then joining the last item after the for loop. This also obviates the need for extra code to remove the extra separator at the end of the joined string. So, no if statements slowing down the iteration and no superfluous separator that needs tidying up.

This produces an elegant function call to join a vector of string, integer, or double, etc.

I wrote two versions: one returns a string; the other writes directly to a stream.

#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;


// Return a string of joined vector items.
template<typename T>
string join(const vector<T>& v, const string& sep)
{
ostringstream oss;
const auto LAST = v.end() - 1;
// Iterate through the first to penultimate items appending the separator.
for (typename vector<T>::const_iterator p = v.begin(); p != LAST; ++p)
{
oss << *p << sep;
}
// Join the last item without a separator.
oss << *LAST;
return oss.str();
}


// Write joined vector items directly to a stream.
template<typename T>
void join(const vector<T>& v, const string& sep, ostream& os)
{
const auto LAST = v.end() - 1;
// Iterate through the first to penultimate items appending the separator.
for (typename vector<T>::const_iterator p = v.begin(); p != LAST; ++p)
{
os << *p << sep;
}
// Join the last item without a separator.
os << *LAST;
}


int main()
{
vector<string> strings
{
"Joined",
"from",
"beginning",
"to",
"end"
};
vector<int> integers{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
vector<double> doubles{ 1.2, 3.4, 5.6, 7.8, 9.0 };


cout << join(strings, "... ") << endl << endl;
cout << join(integers, ", ") << endl << endl;
cout << join(doubles, "; ") << endl << endl;


join(strings, "... ", cout);
cout << endl << endl;
join(integers, ",  ", cout);
cout << endl << endl;
join(doubles, ";  ", cout);
cout << endl << endl;


return 0;
}

Output

Joined... from... beginning... to... end


1, 2, 3, 4, 5, 6, 7, 8, 9, 10


1.2; 3.4; 5.6; 7.8; 9


Joined... from... beginning... to... end


1, 2, 3, 4, 5, 6, 7, 8, 9, 10


1.2; 3.4; 5.6; 7.8; 9
#include <iostream>
#include <vector>


int main()
{
std::vector<int> v\{\{1,2,3,4}};
std::string str;


// ----->
if (! v.empty())
{
str = std::to_string(*v.begin());
for (auto it = std::next(v.begin()); it != v.end(); ++it)
str.append("," + std::to_string(*it));
}
// <-----
    

std::cout << str << "\n";
}

why answers here are so ridiculously complex

string vec2str( vector<int> v){
string s="";
for (auto e: v){
s+=to_string(e);
s+=',';
}
s.pop_back();
return s;
}

Yet another way to solve this using std::accumulate from numeric library (#include <numeric>):

std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto comma_fold = [](std::string a, int b) { return std::move(a) + ',' + std::to_string(b); };
std::string s = std::accumulate(std::next(v.begin()), v.end(),
std::to_string(v[0]), // start with first element
comma_fold);
std::cout << s << std::endl; // 1,2,3,4,5,6,7,8,9,10

Simple solution/hack... not elegant but it works

const auto vecToString = [](std::vector<int> input_vector)
{
std::string holder = "";


for (auto s : input_vector){
holder += std::to_string(s);
if(input_vector.back() != s){
holder += ", ";
}
}


return holder;
};