std :: định dạng chuỗi như sprintf


454

Tôi phải định dạng std::stringvới sprintfvà gửi nó vào luồng tập tin. Tôi có thể làm cái này như thế nào?


6
câu chuyện dài sử dụng boost::format(như giải pháp của kennytm sử dụng ở đây ). boost::formatcũng đã hỗ trợ các nhà khai thác luồng C ++! ví dụ : cout << format("helloworld. a=%s, b=%s, c=%s") % 123 % 123.123 % "this is a test" << endl;. boost::formatcó ít dòng mã nhất ... được đánh giá ngang hàng và tích hợp độc đáo với các luồng C ++.
Trevor Boyd Smith

@Ockonal - Vì lợi ích của cộng đồng (Tôi không quan tâm đến người đại diện của mình) Tôi khuyên bạn nên thay đổi lựa chọn của mình. Cái hiện được chọn, trong đoạn trích đầu tiên, đưa ra một lỗi đang chờ xảy ra khi sử dụng độ dài tối đa tùy ý. Đoạn mã thứ hai hoàn toàn bỏ qua mong muốn đã nêu của bạn để sử dụng các varg như sprintf. Tôi đề nghị bạn chọn câu trả lời DUY NHẤT ở đây sạch sẽ, an toàn, chỉ dựa trên các tiêu chuẩn C ++, đã được kiểm tra và nhận xét tốt. Đó là của tôi không liên quan. Đó là sự thật khách quan. Xem stackoverflow.com/questions/2342162/ trên .
Douglas Daseeco

@TrevorBoydSmith a std::formatđã được thêm vào C ++ 20 BTW: stackoverflow.com/a/57286312/895245 Tuyệt vời!
Ciro Santilli 郝海东 冠状 病 事件

1
@CiroSantilli tôi đã đọc một bài báo về C++20ngày hôm qua và tôi đã thấy nó C++20được sao chép boost(lần thứ một triệu bây giờ) bằng cách thêm std::formatvào C++20thông số kỹ thuật! Tôi đã rất rất hạnh phúc! Hầu như mọi tệp C ++ tôi đã viết trong 9 năm qua đã sử dụng boost::format. việc thêm đầu ra kiểu printf chính thức vào các luồng trong C ++ sẽ đi một chặng đường dài IMO cho tất cả C ++.
Trevor Boyd Smith

Câu trả lời:


333

Bạn không thể thực hiện trực tiếp vì bạn không có quyền ghi vào bộ đệm bên dưới (cho đến C ++ 11; xem bình luận của Dietrich Epp ). Bạn sẽ phải làm điều đó trước tiên trong chuỗi c, sau đó sao chép nó vào chuỗi std :::

  char buff[100];
  snprintf(buff, sizeof(buff), "%s", "Hello");
  std::string buffAsStdStr = buff;

Nhưng tôi không chắc tại sao bạn sẽ không sử dụng một chuỗi chuỗi? Tôi cho rằng bạn có lý do cụ thể để không chỉ làm điều này:

  std::ostringstream stringStream;
  stringStream << "Hello";
  std::string copyOfStr = stringStream.str();

17
Cookie ma thuật trong char buf[100];làm cho giải pháp này không mạnh mẽ. Nhưng ý tưởng thiết yếu là ở đó.
John Dibling

18
John, dòng không chậm. Lý do duy nhất khiến các luồng có vẻ chậm là theo mặc định, các iostream đang đồng bộ hóa với đầu ra C FILE để cout và printfs được trộn lẫn được xuất ra chính xác. Vô hiệu hóa liên kết này (với một cuộc gọi đến cout.sync_with_stdio (false)) làm cho các luồng của c ++ vượt trội hơn stdio, ít nhất là của MSVC10.
Jimbo

72
Lý do để sử dụng các định dạng là để cho một người bản địa hóa xây dựng lại cấu trúc của câu cho ngoại ngữ, thay vì cứng mã hóa ngữ pháp của câu.
Martijn Courteaux

216
Vì một số lý do, các ngôn ngữ khác sử dụng cú pháp giống như printf: Java, Python (cú pháp mới vẫn gần với printf hơn là luồng). Chỉ có C ++ gây ra sự ghê tởm dài dòng này cho con người vô tội.
quant_dev

9
Thậm chí tốt hơn, sử dụng asprintf, phân bổ một chuỗi mới có đủ không gian để giữ kết quả. Sau đó sao chép nó vào một std::stringnếu bạn thích, và nhớ về freebản gốc. Ngoài ra, có thể đặt điều này trong một macro để bất kỳ trình biên dịch tốt nào cũng sẽ giúp xác thực định dạng cho bạn - bạn không muốn đặt một doublenơi %sđược mong đợi
Aaron McDaid

286

C ++ hiện đại làm cho siêu đơn giản này.

C ++ 20

C ++ 20 giới thiệu std::format, cho phép bạn làm chính xác điều đó. Nó sử dụng các trường thay thế tương tự như trong python :

#include <iostream>
#include <format>

int main() {
    std::cout << std::format("Hello {}!\n", "world");
}

Kiểm tra các tài liệu đầy đủ ! Đó là một cải tiến chất lượng cuộc sống rất lớn.


C ++ 11

Với C ++ 11 s std::snprintf, điều này đã trở thành một nhiệm vụ khá dễ dàng và an toàn.

#include <memory>
#include <string>
#include <stdexcept>

template<typename ... Args>
std::string string_format( const std::string& format, Args ... args )
{
    size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0'
    if( size <= 0 ){ throw std::runtime_error( "Error during formatting." ); }
    std::unique_ptr<char[]> buf( new char[ size ] ); 
    snprintf( buf.get(), size, format.c_str(), args ... );
    return std::string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
}

Đoạn mã trên được cấp phép theo Muff 1.0 .

Từng dòng giải thích:

Mục đích: Viết thành achar*bằng cách sử dụng std::snprintfvà sau đó chuyển đổi nó thành astd::string.

Đầu tiên, chúng tôi xác định độ dài mong muốn của mảng char bằng một điều kiện đặc biệt trong snprintf. Từ cppreference.com :

Giá trị trả về

[...] Nếu chuỗi kết quả bị cắt bớt do giới hạn buf_size, hàm sẽ trả về tổng số ký tự (không bao gồm byte kết thúc null) sẽ được ghi, nếu giới hạn không được áp đặt.

Điều này có nghĩa là kích thước mong muốn là số lượng ký tự cộng với một ký tự , do đó, bộ kết thúc null sẽ ngồi sau tất cả các ký tự khác và nó có thể bị cắt bởi hàm tạo chuỗi. Vấn đề này đã được giải thích bởi @ alexk7 trong các bình luận.

size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1;

snprintfsẽ trả về số âm nếu xảy ra lỗi, vì vậy chúng tôi sẽ kiểm tra xem định dạng có hoạt động như mong muốn không. Không làm điều này có thể dẫn đến các lỗi im lặng hoặc phân bổ một bộ đệm lớn, như được chỉ ra bởi @ead trong các bình luận.

if( size <= 0 ){ throw std::runtime_error( "Error during formatting." ); }

Tiếp theo, chúng tôi phân bổ một mảng ký tự mới và gán nó cho a std::unique_ptr. Điều này thường được khuyên, vì bạn sẽ không phải tự làm deletelại.

Lưu ý rằng đây không phải là cách an toàn để phân bổ một loại unique_ptrdo người dùng xác định vì bạn không thể phân bổ bộ nhớ nếu hàm tạo ném ngoại lệ!

std::unique_ptr<char[]> buf( new char[ size ] );

Sau đó, tất nhiên chúng ta có thể chỉ sử dụng snprintfcho mục đích sử dụng của nó và viết chuỗi được định dạng vào char[].

snprintf( buf.get(), size, format.c_str(), args ... );

Cuối cùng, chúng tôi tạo và trả lại một cái mới std::stringtừ đó, đảm bảo bỏ qua bộ kết thúc null ở cuối.

return std::string( buf.get(), buf.get() + size - 1 );

Bạn có thể xem một ví dụ trong hành động ở đây .


Nếu bạn cũng muốn sử dụng std::stringtrong danh sách đối số, hãy xem ý chính này .


Thông tin bổ sung cho người dùng Visual Studio :

Như đã giải thích trong câu trả lời này , Microsoft đổi tên std::snprintfđể _snprintf(có, không std::). MS tiếp tục đặt nó là không dùng nữa và khuyên nên sử dụng _snprintf_sthay vào đó, tuy nhiên _snprintf_ssẽ không chấp nhận bộ đệm bằng 0 hoặc nhỏ hơn đầu ra được định dạng và sẽ không tính toán độ dài đầu ra nếu điều đó xảy ra. Vì vậy, để loại bỏ các cảnh báo khấu hao trong quá trình biên dịch, bạn có thể chèn dòng sau vào đầu tệp có chứa việc sử dụng _snprintf:

#pragma warning(disable : 4996)

Suy nghĩ cuối cùng

Rất nhiều câu trả lời cho câu hỏi này đã được viết trước thời điểm C ++ 11 và sử dụng độ dài bộ đệm hoặc varg cố định. Trừ khi bạn bị mắc kẹt với các phiên bản cũ của C ++, tôi không khuyên bạn nên sử dụng các giải pháp đó. Lý tưởng nhất là đi theo con đường C ++ 20.

Bởi vì giải pháp C ++ 11 trong câu trả lời này sử dụng các mẫu, nó có thể tạo ra khá nhiều mã nếu nó được sử dụng nhiều. Tuy nhiên, trừ khi bạn đang phát triển cho một môi trường có không gian nhị phân rất hạn chế, đây sẽ không phải là vấn đề và vẫn là một cải tiến lớn so với các giải pháp khác về cả sự rõ ràng và bảo mật.

Nếu hiệu quả không gian là cực kỳ quan trọng, hai giải pháp này với vargs và vsnprintf có thể hữu ích. KHÔNG SỬ DỤNG bất kỳ giải pháp nào có độ dài bộ đệm cố định, đó chỉ là vấn đề.


2
Xin nhấn mạnh trong câu trả lời của bạn cho người sử dụng Visual Studio rằng phiên bản của VS phải có ít nhất năm 2013. Từ này bài viết, bạn có thể thấy rằng nó chỉ làm việc với phiên bản VS2013: Nếu bộ đệm là một con trỏ null và đếm là zero, len được trả về như số lượng ký tự được yêu cầu để định dạng đầu ra, không bao gồm null kết thúc. Để thực hiện cuộc gọi thành công với cùng tham số và tham số ngôn ngữ, hãy phân bổ bộ đệm giữ ít nhất len ​​+ 1 ký tự.
cha

3
@moooeeeep Nhiều lý do. Đầu tiên, mục tiêu ở đây là trả về chuỗi std :: chứ không phải chuỗi c, vì vậy bạn có thể có ý nghĩa return string(&buf[0], size);hoặc một cái gì đó tương tự. Thứ hai, nếu bạn trả về một chuỗi c như thế, nó sẽ gây ra hành vi không xác định bởi vì vectơ giữ các giá trị bạn trỏ đến sẽ bị vô hiệu khi trả về. Thứ ba, khi tôi bắt đầu học C ++, tiêu chuẩn không xác định được các phần tử thứ tự nào phải được lưu trữ bên trong std::vector, vì vậy việc truy cập vào bộ lưu trữ của nó thông qua một con trỏ là hành vi không xác định. Bây giờ nó hoạt động, nhưng tôi thấy không có lợi ích gì khi làm theo cách đó.
iFreilicht

2
@iFreilicht Một cái mới std::stringsẽ được xây dựng từ vectơ được chuyển đổi ngầm định ( khởi tạo bản sao ), sau đó được trả về dưới dạng bản sao, như chữ ký hàm cho thấy. Ngoài ra, các yếu tố của a std::vectorlà và luôn luôn được dự định, được lưu trữ liên tục . Nhưng tôi có quan điểm của bạn rằng có thể không có lợi ích khi làm như vậy.
moooeeeep

4
Tôi thực sự thích giải pháp này, tuy nhiên tôi nghĩ rằng dòng return string(buf.get(), buf.get() + size);này sẽ return string(buf.get(), buf.get() + size - 1);khác nếu bạn nhận được một chuỗi có ký tự null ở cuối. Tôi thấy đây là trường hợp trên gcc 4.9.
Phil Williams

3
Truyền chuỗi std :: cho% s gây ra lỗi biên dịch ( lỗi: không thể truyền đối tượng thuộc loại không tầm thường 'std :: __ cxx11 :: basic_opes <char>' thông qua chức năng matrixdic; cuộc gọi sẽ hủy bỏ khi chạy [-Wnon-pod -varargs] ) trong clang 3.9.1, nhưng trong CL 19, nó biên dịch tốt và thay vào đó là sự cố. Bất kỳ cờ cảnh báo nào tôi có thể bật để có được điều đó trong thời gian biên dịch trong cl quá không?
Zitrax

241

Giải pháp C ++ 11 sử dụng vsnprintf()nội bộ:

#include <stdarg.h>  // For va_start, etc.

std::string string_format(const std::string fmt, ...) {
    int size = ((int)fmt.size()) * 2 + 50;   // Use a rubric appropriate for your code
    std::string str;
    va_list ap;
    while (1) {     // Maximum two passes on a POSIX system...
        str.resize(size);
        va_start(ap, fmt);
        int n = vsnprintf((char *)str.data(), size, fmt.c_str(), ap);
        va_end(ap);
        if (n > -1 && n < size) {  // Everything worked
            str.resize(n);
            return str;
        }
        if (n > -1)  // Needed size returned
            size = n + 1;   // For null char
        else
            size *= 2;      // Guess at a larger size (OS specific)
    }
    return str;
}

Một cách tiếp cận an toàn và hiệu quả hơn (tôi đã thử nghiệm và nó nhanh hơn):

#include <stdarg.h>  // For va_start, etc.
#include <memory>    // For std::unique_ptr

std::string string_format(const std::string fmt_str, ...) {
    int final_n, n = ((int)fmt_str.size()) * 2; /* Reserve two times as much as the length of the fmt_str */
    std::unique_ptr<char[]> formatted;
    va_list ap;
    while(1) {
        formatted.reset(new char[n]); /* Wrap the plain char array into the unique_ptr */
        strcpy(&formatted[0], fmt_str.c_str());
        va_start(ap, fmt_str);
        final_n = vsnprintf(&formatted[0], n, fmt_str.c_str(), ap);
        va_end(ap);
        if (final_n < 0 || final_n >= n)
            n += abs(final_n - n + 1);
        else
            break;
    }
    return std::string(formatted.get());
}

Các fmt_strtruyền theo tham trị cho phù hợp với các yêu cầu của va_start.

LƯU Ý: Phiên bản "an toàn hơn" và "nhanh hơn" không hoạt động trên một số hệ thống. Do đó cả hai vẫn được liệt kê. Ngoài ra, "nhanh hơn" phụ thuộc hoàn toàn vào bước phân bổ là chính xác, nếu không thì strcpylàm cho nó chậm hơn.


3
chậm Tại sao tăng kích thước lên 1? Và khi nào funciton này trở lại -1?
0xDEAD BEEF

27
Bạn đang ghi đè str.c_str ()? Điều đó có nguy hiểm không?
lượng tử

8
va_start với một đối số tham chiếu có vấn đề trên MSVC. Nó thất bại âm thầm và trả lại con trỏ vào bộ nhớ ngẫu nhiên. Để khắc phục, hãy sử dụng std :: string fmt thay vì std :: string & fmt hoặc viết một đối tượng trình bao bọc.
Steve Hanov

6
Tôi + 1 vì tôi biết điều này có thể sẽ hoạt động dựa trên cách hầu hết các chuỗi std :: được triển khai, tuy nhiên c_str không thực sự là nơi để sửa đổi chuỗi bên dưới. Nó được cho là chỉ đọc.
Doug T.

6
Và để có được độ dài chuỗi kết quả trước đó, hãy xem: stackoverflow.com/a/7825892/908336 Tôi không thấy điểm tăng sizetrong mỗi lần lặp, khi bạn có thể nhận được nó bằng lệnh gọi đầu tiên vsnprintf().
Massood Khaari

107

boost::format() cung cấp các chức năng bạn muốn:

Từ bản tóm tắt thư viện định dạng Boost:

Một đối tượng định dạng được xây dựng từ một chuỗi định dạng và sau đó được đưa ra các đối số thông qua các cuộc gọi lặp lại cho toán tử%. Mỗi đối số sau đó được chuyển đổi thành chuỗi, lần lượt được kết hợp thành một chuỗi, theo chuỗi định dạng.

#include <boost/format.hpp>

cout << boost::format("writing %1%,  x=%2% : %3%-th try") % "toto" % 40.23 % 50; 
// prints "writing toto,  x=40.230 : 50-th try"

5
bạn cũng có thể cắt tỉa các thư viện bạn cần tăng cường. Sử dụng một công cụ hỗ trợ.
Hassan Syed

7
Boost Format không chỉ lớn, mà còn rất chậm. Xem zverovich.net/2013/09/07/ trênboost.org/doc/libs/1_52_0/libs/spirit/doc/html/spirit/karma/ từ
vitaut

14
Bao gồm tăng bất cứ nơi nào trong dự án của bạn ngay lập tức tăng thời gian biên dịch đáng kể. Đối với các dự án lớn, hầu hết có lẽ không thành vấn đề. Đối với các dự án nhỏ, boost là một lực cản.
quant_dev

2
@vitaut Trong khi đó là tiêu thụ tài nguyên khủng khiếp khi so sánh với các lựa chọn thay thế. Bạn có thường định dạng chuỗi không? Xem xét chỉ mất vài giây và hầu hết các dự án có thể chỉ sử dụng vài chục lần, điều đó không đáng chú ý trong một dự án không tập trung nhiều vào định dạng chuỗi, phải không?
AturSams 17/08/2015

2
Không may, định dạng boost :: không hoạt động theo cùng một cách: không chấp nhận var_args. Một số người muốn có tất cả các mã liên quan đến một chương trình giống nhau / sử dụng cùng một thành ngữ.
xor007

88

C ++ 20 sẽ bao gồm std::formatnhững gì giống với sprintfAPI nhưng hoàn toàn an toàn về loại, hoạt động với các loại do người dùng xác định và sử dụng cú pháp chuỗi định dạng giống như Python. Đây là cách bạn sẽ có thể định dạng std::stringvà ghi nó vào một luồng:

std::string s = "foo";
std::cout << std::format("Look, a string: {}", s);

hoặc là

std::string s = "foo";
puts(std::format("Look, a string: {}", s).c_str());

Ngoài ra, bạn có thể sử dụng thư viện {fmt} để định dạng một chuỗi và ghi nó vào stdouthoặc một luồng tệp trong một lần:

fmt::print(f, "Look, a string: {}", s); // where f is a file stream

Đối với sprintfhoặc hầu hết các câu trả lời khác ở đây, thật không may, họ sử dụng các vararg và vốn không an toàn trừ khi bạn sử dụng một cái gì đó như formatthuộc tính của GCC chỉ hoạt động với các chuỗi định dạng bằng chữ. Bạn có thể thấy tại sao các chức năng này không an toàn trong ví dụ sau:

std::string format_str = "%s";
string_format(format_str, format_str[0]);

nơi string_formatthực hiện từ câu trả lời của Erik Aronesty. Mã này biên dịch, nhưng rất có thể nó sẽ bị sập khi bạn cố chạy nó:

$ g++ -Wall -Wextra -pedantic test.cc 
$ ./a.out 
Segmentation fault: 11

Tuyên bố miễn trừ trách nhiệm : Tôi là tác giả của {fmt} và C ++ 20 std::format.


IMHO bạn bỏ lỡ bao gồm error: 'fmt' has not been declared
Sérgio

Đây chỉ là một đoạn mã, không phải là một mã hoàn chỉnh. Rõ ràng bạn cần bao gồm <fmt / format.h> và đặt mã vào một hàm.
vitaut

Đối với tôi không quá rõ ràng, IMHO bạn nên đưa nó vào đoạn trích, cảm ơn vì đã phản hồi
Sérgio

1
Một fmttriển khai như đã được thêm vào C ++ 20! stackoverflow.com/a/57286312/895245 fmt hiện yêu cầu hỗ trợ cho nó. Công việc tuyệt vời
Ciro Santilli 郝海东 冠状 病 事件

2
@vitaut Cảm ơn bạn đã làm việc này!
Curt Nichols


15

Tôi đã tự viết bằng cách sử dụng vsnprintf để nó trả về chuỗi thay vì phải tạo bộ đệm của riêng tôi.

#include <string>
#include <cstdarg>

//missing string printf
//this is safe and convenient but not exactly efficient
inline std::string format(const char* fmt, ...){
    int size = 512;
    char* buffer = 0;
    buffer = new char[size];
    va_list vl;
    va_start(vl, fmt);
    int nsize = vsnprintf(buffer, size, fmt, vl);
    if(size<=nsize){ //fail delete buffer and try again
        delete[] buffer;
        buffer = 0;
        buffer = new char[nsize+1]; //+1 for /0
        nsize = vsnprintf(buffer, size, fmt, vl);
    }
    std::string ret(buffer);
    va_end(vl);
    delete[] buffer;
    return ret;
}

Vì vậy, bạn có thể sử dụng nó như

std::string mystr = format("%s %d %10.5f", "omg", 1, 10.5);

Đây là bản sao đầy đủ của dữ liệu, có thể sử dụng vsnprintftrực tiếp vào chuỗi.
Vịt Mooing

1
Sử dụng mã trong stackoverflow.com/a/7825892/908336 để có được độ dài chuỗi kết quả trước đó. Và bạn có thể sử dụng con trỏ thông minh cho mã an toàn ngoại lệ:std::unique_ptr<char[]> buffer (new char[size]);
Massood Khaari

Tôi không chắc điều này đúng trong trường hợp dự phòng; Tôi nghĩ rằng bạn cần phải thực hiện một va_copy của vl cho vsnprintf () thứ hai để xem các đối số chính xác. Để biết ví dụ, hãy xem: github.com/haberman/upb/blob/ Kẻ
Josh Haberman

15

Để định dạng std::stringtheo cách 'sprintf', hãy gọi snprintf(đối số nullptr0) để lấy chiều dài bộ đệm cần thiết. Viết hàm của bạn bằng cách sử dụng mẫu biến đổi C ++ 11 như thế này:

#include <cstdio>
#include <string>
#include <cassert>

template< typename... Args >
std::string string_sprintf( const char* format, Args... args ) {
  int length = std::snprintf( nullptr, 0, format, args... );
  assert( length >= 0 );

  char* buf = new char[length + 1];
  std::snprintf( buf, length + 1, format, args... );

  std::string str( buf );
  delete[] buf;
  return str;
}

Biên dịch với hỗ trợ C ++ 11, ví dụ như trong GCC: g++ -std=c++11

Sử dụng:

  std::cout << string_sprintf("%g, %g\n", 1.23, 0.001);

std :: snprintf không có sẵn trong VC ++ 12 (Visual Studio 2013). Thay thế nó bằng _snprintf thay thế.
Shital Shah

Tại sao bạn không sử dụng char buf[length + 1];thay vì char* buf = new char[length + 1];?
Behrouz.M

Sự khác biệt giữa sử dụng char[]char*mới, là trong trường hợp trước, buf sẽ được phân bổ trên stack. Nó là OK cho bộ đệm nhỏ, nhưng vì chúng tôi không thể đảm bảo kích thước của chuỗi kết quả, nên sử dụng tốt hơn một chút new. Ví dụ: trên máy của tôi string_sprintf("value: %020000000d",5), in số lượng số 0 đứng đầu trước số 5, số lõi bị bỏ khi sử dụng mảng trên ngăn xếp, nhưng hoạt động tốt khi sử dụng mảng được phân bổ độngnew char[length + 1]
user2622016

ý tưởng rất thông minh để có được kích thước buff thực tế cần thiết cho đầu ra được định dạng
Chris

1
@ user2622016: Cảm ơn giải pháp! Xin lưu ý rằng đó std::move là thừa .
Mihai Todor

14

[chỉnh sửa: 20/05/25] vẫn tốt hơn ...:
Trong tiêu đề:

// `say` prints the values
// `says` returns a string instead of printing
// `sayss` appends the values to it's first argument instead of printing
// `sayerr` prints the values and returns `false` (useful for return statement fail-report)<br/>

void PRINTSTRING(const std::string &s); //cater for GUI, terminal, whatever..
template<typename...P> void say(P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str(); PRINTSTRING(r); }
template<typename...P> std::string says(P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str(); return r; }
template<typename...P> void sayss(std::string &s, P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str();  s+=r; } //APPENDS! to s!
template<typename...P> bool sayerr(P...p) { std::string r{}; std::stringstream ss("ERROR: "); (ss<<...<<p); r=ss.str(); PRINTSTRING(r); return false; }

Chức PRINTSTRING(r)năng là để phục vụ cho GUI hoặc thiết bị đầu cuối hoặc bất kỳ nhu cầu đầu ra đặc biệt nào bằng cách sử dụng #ifdef _some_flag_, mặc định là:

void PRINTSTRING(const std::string &s) { std::cout << s << std::flush; }

[chỉnh sửa '17 / 8/31] Thêm phiên bản templated 'vtspf (..)':

template<typename T> const std::string type_to_string(const T &v)
{
    std::ostringstream ss;
    ss << v;
    return ss.str();
};

template<typename T> const T string_to_type(const std::string &str)
{
    std::istringstream ss(str);
    T ret;
    ss >> ret;
    return ret;
};

template<typename...P> void vtspf_priv(std::string &s) {}

template<typename H, typename...P> void vtspf_priv(std::string &s, H h, P...p)
{
    s+=type_to_string(h);
    vtspf_priv(s, p...);
}

template<typename...P> std::string temp_vtspf(P...p)
{
    std::string s("");
    vtspf_priv(s, p...);
    return s;
}

đó thực sự là một phiên bản được phân cách bằng dấu phẩy (thay vào đó) của các trình điều khiển đôi khi gây trở ngại <<, được sử dụng như sau:

char chSpace=' ';
double pi=3.1415;
std::string sWorld="World", str_var;
str_var = vtspf("Hello", ',', chSpace, sWorld, ", pi=", pi);


[sửa] Thích nghi để sử dụng kỹ thuật trong câu trả lời của Erik Aronesty (ở trên):

#include <string>
#include <cstdarg>
#include <cstdio>

//=============================================================================
void spf(std::string &s, const std::string fmt, ...)
{
    int n, size=100;
    bool b=false;
    va_list marker;

    while (!b)
    {
        s.resize(size);
        va_start(marker, fmt);
        n = vsnprintf((char*)s.c_str(), size, fmt.c_str(), marker);
        va_end(marker);
        if ((n>0) && ((b=(n<size))==true)) s.resize(n); else size*=2;
    }
}

//=============================================================================
void spfa(std::string &s, const std::string fmt, ...)
{
    std::string ss;
    int n, size=100;
    bool b=false;
    va_list marker;

    while (!b)
    {
        ss.resize(size);
        va_start(marker, fmt);
        n = vsnprintf((char*)ss.c_str(), size, fmt.c_str(), marker);
        va_end(marker);
        if ((n>0) && ((b=(n<size))==true)) ss.resize(n); else size*=2;
    }
    s += ss;
}

[câu trả lời trước]
Một câu trả lời rất muộn, nhưng đối với những người, giống như tôi, thích cách 'chạy nước rút': Tôi đã viết và đang sử dụng các chức năng sau. Nếu bạn thích nó, bạn có thể mở rộng% -options để phù hợp hơn với các sprintf; những cái trong đó hiện đủ cho nhu cầu của tôi. Bạn sử dụng stringf () và stringfappend () giống như bạn sẽ chạy nước rút. Chỉ cần nhớ rằng các tham số cho ... phải là các loại POD.

//=============================================================================
void DoFormatting(std::string& sF, const char* sformat, va_list marker)
{
    char *s, ch=0;
    int n, i=0, m;
    long l;
    double d;
    std::string sf = sformat;
    std::stringstream ss;

    m = sf.length();
    while (i<m)
    {
        ch = sf.at(i);
        if (ch == '%')
        {
            i++;
            if (i<m)
            {
                ch = sf.at(i);
                switch(ch)
                {
                    case 's': { s = va_arg(marker, char*);  ss << s;         } break;
                    case 'c': { n = va_arg(marker, int);    ss << (char)n;   } break;
                    case 'd': { n = va_arg(marker, int);    ss << (int)n;    } break;
                    case 'l': { l = va_arg(marker, long);   ss << (long)l;   } break;
                    case 'f': { d = va_arg(marker, double); ss << (float)d;  } break;
                    case 'e': { d = va_arg(marker, double); ss << (double)d; } break;
                    case 'X':
                    case 'x':
                        {
                            if (++i<m)
                            {
                                ss << std::hex << std::setiosflags (std::ios_base::showbase);
                                if (ch == 'X') ss << std::setiosflags (std::ios_base::uppercase);
                                char ch2 = sf.at(i);
                                if (ch2 == 'c') { n = va_arg(marker, int);  ss << std::hex << (char)n; }
                                else if (ch2 == 'd') { n = va_arg(marker, int); ss << std::hex << (int)n; }
                                else if (ch2 == 'l') { l = va_arg(marker, long);    ss << std::hex << (long)l; }
                                else ss << '%' << ch << ch2;
                                ss << std::resetiosflags (std::ios_base::showbase | std::ios_base::uppercase) << std::dec;
                            }
                        } break;
                    case '%': { ss << '%'; } break;
                    default:
                    {
                        ss << "%" << ch;
                        //i = m; //get out of loop
                    }
                }
            }
        }
        else ss << ch;
        i++;
    }
    va_end(marker);
    sF = ss.str();
}

//=============================================================================
void stringf(string& stgt,const char *sformat, ... )
{
    va_list marker;
    va_start(marker, sformat);
    DoFormatting(stgt, sformat, marker);
}

//=============================================================================
void stringfappend(string& stgt,const char *sformat, ... )
{
    string sF = "";
    va_list marker;
    va_start(marker, sformat);
    DoFormatting(sF, sformat, marker);
    stgt += sF;
}

@MooingDuck: Thay đổi tham số chức năng theo nhận xét của Dan cho câu trả lời của Aronesty. Tôi chỉ sử dụng Linux / gcc và với fmttham chiếu, nó hoạt động tốt. (Nhưng tôi cho rằng mọi người sẽ muốn chơi với đồ chơi, vì vậy ...) Nếu có bất kỳ 'lỗi' nào khác được cho là bạn có thể giải thích không?
slashmais

Tôi đã hiểu nhầm cách một phần mã của anh ấy hoạt động và nghĩ rằng nó đang làm cho nhiều kích thước thay đổi. Reexamining cho thấy tôi đã nhầm. Mã của bạn là chính xác.
Vịt Mooing

Xây dựng câu trả lời của Erik Aronesty là một cá trích đỏ. Mẫu mã đầu tiên của anh ta không an toàn và mẫu thứ hai của anh ta không hiệu quả và vụng về. Việc triển khai sạch được biểu thị rõ ràng bởi thực tế là, nếu buf_siz của bất kỳ họ hàm vprintf nào bằng 0, thì không có gì được ghi và bộ đệm có thể là một con trỏ null, tuy nhiên giá trị trả về (số byte sẽ được ghi không bao gồm bộ kết thúc null) vẫn được tính toán và trả về. Câu trả lời về chất lượng sản xuất có tại đây: stackoverflow.com/questions/2342162/ từ
Douglas Daseeco

10

Đây là cách google thực hiện: StringPrintf(Giấy phép BSD)
và facebook thực hiện theo cách khá giống nhau: StringPrintf(Giấy phép Apache)
Cả hai cũng cung cấp một cách thuận tiện StringAppendF.


10

Hai xu của tôi về câu hỏi rất phổ biến này.

Để trích dẫn trang chủ của các printfchức năng giống như :

Khi trả về thành công, các hàm này trả về số lượng ký tự được in (không bao gồm byte null được sử dụng để kết thúc đầu ra thành chuỗi).

Các hàm snprintf () và vsnprintf () không ghi nhiều hơn byte kích thước (bao gồm cả byte null kết thúc ('\ 0')). Nếu đầu ra bị cắt bớt do giới hạn này thì giá trị trả về là số ký tự (không bao gồm byte null kết thúc) sẽ được ghi vào chuỗi cuối cùng nếu có đủ không gian. Do đó, giá trị trả về của kích thước hoặc nhiều hơn có nghĩa là đầu ra bị cắt ngắn.

Nói cách khác, việc triển khai C ++ 11 lành mạnh phải như sau:

#include <string>
#include <cstdio>

template <typename... Ts>
std::string fmt (const std::string &fmt, Ts... vs)
{
    char b;
    size_t required = std::snprintf(&b, 0, fmt.c_str(), vs...) + 1;
        // See comments: the +1 is necessary, while the first parameter
        //               can also be set to nullptr

    char bytes[required];
    std::snprintf(bytes, required, fmt.c_str(), vs...);

    return std::string(bytes);
}

Nó hoạt động khá tốt :)

Các mẫu biến thể chỉ được hỗ trợ trong C ++ 11. Câu trả lời từ pixelpoint cho thấy một kỹ thuật tương tự sử dụng các kiểu lập trình cũ hơn.

Thật kỳ lạ khi C ++ không có thứ như vậy ngoài hộp. Họ gần đây đã thêm to_string(), mà theo tôi là một bước tiến tuyệt vời. Tôi tự hỏi liệu cuối cùng họ sẽ thêm một .formatnhà điều hành std::string...

Biên tập

Như alexk7 đã chỉ ra, A +1là cần thiết cho giá trị trả về của std::snprintf, vì chúng ta cần có khoảng trống cho \0byte. Theo trực giác, trên hầu hết các kiến ​​trúc bị thiếu +1sẽ khiến requiredsố nguyên bị ghi đè lên một phần bằng a 0. Điều này sẽ xảy ra sau khi đánh giá requirednhư là tham số thực tế std::snprintf, vì vậy hiệu ứng sẽ không được nhìn thấy.

Tuy nhiên, vấn đề này có thể thay đổi, ví dụ với tối ưu hóa trình biên dịch: nếu trình biên dịch quyết định sử dụng một thanh ghi cho requiredbiến thì sao? Đây là loại lỗi đôi khi dẫn đến các vấn đề bảo mật.


1
snprintf luôn nối thêm một byte kết thúc nhưng trả về số lượng ký tự không có nó. Không phải mã này luôn bỏ qua ký tự cuối cùng sao?
alexk7

@ alexk7, Bắt đẹp quá! Tôi đang cập nhật câu trả lời. Mã không bỏ qua ký tự cuối cùng, nhưng viết vượt quá cuối bytesbộ đệm, có thể qua requiredsố nguyên (điều may mắn là tại thời điểm đó đã được đánh giá).
Dacav

1
Chỉ cần một gợi ý nhỏ: Với kích thước bộ đệm bằng 0, bạn có thể chuyển một nullptrđối số làm bộ đệm, loại bỏ char b;dòng trong mã của bạn. ( Nguồn )
iFreilicht 20/03/2015

@iFreilicht, đã sửa. Ngoài ra +1
Dacav 22/03/2015

2
Sử dụng "char byte [required]" sẽ được phân bổ trên stack thay vì heap, nó có thể nguy hiểm trên các chuỗi định dạng lớn. Cân nhắc sử dụng một cái mới thay thế. Yann
Yannuth

9
template<typename... Args>
std::string string_format(const char* fmt, Args... args)
{
    size_t size = snprintf(nullptr, 0, fmt, args...);
    std::string buf;
    buf.reserve(size + 1);
    buf.resize(size);
    snprintf(&buf[0], size + 1, fmt, args...);
    return buf;
}

Sử dụng C99 snprintf và C ++ 11


9

Đã kiểm tra, trả lời chất lượng sản xuất

Câu trả lời này xử lý trường hợp chung với các kỹ thuật tuân thủ tiêu chuẩn. Cách tiếp cận tương tự được đưa ra như một ví dụ trên CppReference.com gần cuối trang của họ. Không giống như ví dụ của họ, mã này phù hợp với yêu cầu của câu hỏi và được thử nghiệm trong các ứng dụng robot và vệ tinh. Nó cũng đã được cải thiện bình luận. Chất lượng thiết kế được thảo luận thêm dưới đây.

#include <string>
#include <cstdarg>
#include <vector>

// requires at least C++11
const std::string vformat(const char * const zcFormat, ...) {

    // initialize use of the variable argument array
    va_list vaArgs;
    va_start(vaArgs, zcFormat);

    // reliably acquire the size
    // from a copy of the variable argument array
    // and a functionally reliable call to mock the formatting
    va_list vaArgsCopy;
    va_copy(vaArgsCopy, vaArgs);
    const int iLen = std::vsnprintf(NULL, 0, zcFormat, vaArgsCopy);
    va_end(vaArgsCopy);

    // return a formatted string without risking memory mismanagement
    // and without assuming any compiler or platform specific behavior
    std::vector<char> zc(iLen + 1);
    std::vsnprintf(zc.data(), zc.size(), zcFormat, vaArgs);
    va_end(vaArgs);
    return std::string(zc.data(), iLen); }

#include <ctime>
#include <iostream>
#include <iomanip>

// demonstration of use
int main() {

    std::time_t t = std::time(nullptr);
    std::cerr
        << std::put_time(std::localtime(& t), "%D %T")
        << " [debug]: "
        << vformat("Int 1 is %d, Int 2 is %d, Int 3 is %d", 11, 22, 33)
        << std::endl;
    return 0; }

Hiệu quả tuyến tính dự đoán

Hai lượt là cần thiết cho một chức năng tái sử dụng an toàn, đáng tin cậy và có thể dự đoán theo các thông số kỹ thuật câu hỏi. Giả định về việc phân phối kích thước của các varg trong một chức năng có thể sử dụng lại là phong cách lập trình xấu và nên tránh. Trong trường hợp này, các biểu diễn độ dài biến lớn tùy ý của các varg là một yếu tố chính trong việc lựa chọn thuật toán.

Thử lại khi tràn là không hiệu quả theo cấp số nhân, đó là một lý do khác được thảo luận khi ủy ban tiêu chuẩn C ++ 11 thảo luận về đề xuất trên để cung cấp một hoạt động khô khi bộ đệm ghi là null.

Trong quá trình thực hiện sẵn sàng sản xuất ở trên, lần chạy đầu tiên là một lần chạy khô như vậy để xác định quy mô phân bổ. Không có phân bổ xảy ra. Phân tích cú pháp các chỉ thị printf và đọc vargs đã được thực hiện cực kỳ hiệu quả trong nhiều thập kỷ. Mã tái sử dụng nên được dự đoán trước, ngay cả khi một sự không hiệu quả nhỏ cho các trường hợp tầm thường phải được hy sinh.

Bảo mật và độ tin cậy

Andrew Koenig nói với một nhóm nhỏ chúng tôi sau bài giảng của anh ấy tại một sự kiện ở Cambridge, "Các chức năng của người dùng không nên dựa vào việc khai thác lỗi cho chức năng không ngoại lệ." Như thường lệ, trí tuệ của anh đã được thể hiện đúng trong hồ sơ kể từ đó. Các sự cố lỗi bảo mật đã sửa và đóng thường chỉ ra các bản hack thử lại trong phần mô tả về lỗ hổng được khai thác trước khi sửa.

Điều này được đề cập trong đề xuất sửa đổi tiêu chuẩn chính thức cho tính năng bộ đệm null trong Thay thế cho chạy nước rút , Đề xuất sửa đổi C9X , Tài liệu ISO IEC WG14 N645 / X3J11 96-008 . Một chuỗi dài tùy ý được chèn vào mỗi lệnh in, "% s", trong các ràng buộc về tính khả dụng của bộ nhớ động, không phải là ngoại lệ và không nên được khai thác để tạo ra "Chức năng ngoại lệ".

Hãy xem xét đề xuất cùng với mã ví dụ được đưa ra ở dưới cùng của trang C ++ Reference.org được liên kết trong đoạn đầu tiên của câu trả lời này.

Ngoài ra, việc kiểm tra các trường hợp thất bại hiếm khi mạnh mẽ như các trường hợp thành công.

Tính di động

Tất cả các nhà cung cấp hệ điều hành lớn đều cung cấp trình biên dịch hỗ trợ đầy đủ std :: vsnprintf như một phần của tiêu chuẩn c ++ 11. Lưu trữ các sản phẩm đang chạy của các nhà cung cấp không còn duy trì phân phối nên được trang bị g ++ hoặc clang ++ vì nhiều lý do.

Sử dụng ngăn xếp

Việc sử dụng ngăn xếp trong cuộc gọi đầu tiên đến std :: vsnprintf sẽ nhỏ hơn hoặc bằng cuộc gọi của cuộc gọi thứ 2 và nó sẽ được giải phóng trước khi cuộc gọi thứ 2 bắt đầu. Nếu cuộc gọi đầu tiên vượt quá khả năng sẵn có của ngăn xếp, thì std :: fprintf cũng sẽ thất bại.


Ngắn gọn và mạnh mẽ. Nó có thể thất bại trên HP-UX, IRIX, Tru64 có vsnprintf-s không tuân thủ. EDIT: đồng thời, xem xét cách hai đường chuyền có thể ảnh hưởng đến màn trình diễn, đặc biệt. đối với hầu hết các định dạng chuỗi nhỏ, phổ biến, bạn đã xem xét dự đoán cho lần vượt qua ban đầu, có thể đủ lớn chưa?
Kỹ sư

FWIW, việc đoán tôi đã đề cập đến việc sử dụng bộ đệm được cấp phát ngăn xếp trong đó lần chạy đầu tiên xảy ra. Nếu nó phù hợp, nó sẽ tiết kiệm chi phí của lần chạy thứ hai và phân bổ động xảy ra ở đó. Có lẽ, các chuỗi nhỏ được sử dụng thường xuyên hơn các chuỗi lớn. Trong tiêu chuẩn thô của tôi, chiến lược (gần như) giảm một nửa thời gian chạy cho các chuỗi nhỏ và nằm trong một vài phần trăm (có thể là chi phí cố định?) Của chiến lược trên. Bạn có thể giải thích chi tiết về thiết kế C ++ 11 sử dụng chạy khô, v.v.? Tôi muốn đọc về nó.
Kỹ sư

@Engineerist, câu hỏi của bạn đã được giải quyết trong phần thân của câu trả lời, bên trên và bên dưới mã. Các chủ đề phụ có thể được thực hiện dễ dàng hơn để đọc theo cách đó.
Douglas Daseeco

6

C ++ 20 std::format

Nó có đến! Tính năng này được mô tả tại: http://www.open-std.org/jtc1/sc22/wg21/docs/ con / 2019 / p0645r9.html và sử dụng .format()cú pháp giống như Python .

Tôi hy vọng rằng việc sử dụng sẽ như sau:

#include <format>
#include <string>

int main() {
    std::string message = std::format("The answer is {}.", 42);
}

Tôi sẽ dùng thử khi hỗ trợ đến GCC, GCC 9.1.0 mà g++-9 -std=c++2avẫn không hỗ trợ.

API sẽ thêm một std::formattiêu đề mới :

API định dạng được đề xuất được xác định trong tiêu đề mới <format>và sẽ không có tác động đến mã hiện có.

fmtThư viện hiện có yêu cầu triển khai nó nếu bạn cần polyfill: https://github.com/fmtlib/fmt

Thực hiện C ++ 20 std::format.

và đã được đề cập trước đây tại: std :: định dạng chuỗi như sprintf


5

Dựa trên câu trả lời được cung cấp bởi Erik Aronesty:

std::string string_format(const std::string &fmt, ...) {
    std::vector<char> str(100,'\0');
    va_list ap;
    while (1) {
        va_start(ap, fmt);
        auto n = vsnprintf(str.data(), str.size(), fmt.c_str(), ap);
        va_end(ap);
        if ((n > -1) && (size_t(n) < str.size())) {
            return str.data();
        }
        if (n > -1)
            str.resize( n + 1 );
        else
            str.resize( str.size() * 2);
    }
    return str.data();
}

Điều này tránh sự cần thiết phải bỏ đi constkết quả .c_str()trong câu trả lời ban đầu.


1
Xây dựng câu trả lời của Erik Aronesty là một cá trích đỏ. Mẫu mã đầu tiên của anh ta không an toàn và mẫu thứ hai của anh ta, với vòng lặp không hiệu quả và vụng về. Việc triển khai sạch được biểu thị rõ ràng bởi thực tế là, nếu buf_siz của bất kỳ họ hàm vprintf nào bằng 0, thì không có gì được ghi và bộ đệm có thể là một con trỏ null, tuy nhiên giá trị trả về (số byte sẽ được ghi không bao gồm bộ kết thúc null) vẫn được tính toán và trả về. Câu trả lời về chất lượng sản xuất có tại đây: stackoverflow.com/questions/2342162/ từ
Douglas Daseeco

Câu trả lời của Erik Aronesty đã được chỉnh sửa kể từ khi tôi được thêm vào. Tôi muốn làm nổi bật tùy chọn sử dụng vectơ <char> để lưu trữ chuỗi khi chúng được xây dựng. Tôi sử dụng kỹ thuật này thường xuyên khi gọi các hàm C từ mã C ++. Điều thú vị là câu hỏi hiện có 34 câu trả lời.
ChetS

Ví dụ cppreference.com trên trang vfprintf đã được thêm vào sau đó. Tôi tin rằng câu trả lời tốt nhất là câu trả lời hiện được chấp nhận, sử dụng các chuỗi chuỗi thay vì một biến thể printf là cách thức của C ++. Tuy nhiên, câu trả lời của tôi đã làm tăng giá trị khi nó được cung cấp; Nó đã tăng dần tốt hơn so với các câu trả lời khác tại thời điểm đó. Bây giờ tiêu chuẩn có chuỗi_view, gói tham số và mẫu Variadic một câu trả lời mới có thể bao gồm các tính năng đó. Đối với câu trả lời của tôi, mặc dù nó có thể không còn xứng đáng với số phiếu tăng thêm nữa, nhưng nó không đáng bị xóa hoặc bỏ phiếu, vì vậy tôi sẽ bỏ nó.
ChetS

5
inline void format(string& a_string, const char* fmt, ...)
{
    va_list vl;
    va_start(vl, fmt);
    int size = _vscprintf( fmt, vl );
    a_string.resize( ++size );
    vsnprintf_s((char*)a_string.data(), size, _TRUNCATE, fmt, vl);
    va_end(vl);
}

1
+1 cho ý tưởng thông minh, nhưng nó không rõ ràng lắm _vscprintf. Tôi nghĩ bạn nên giải thích về câu trả lời này.
Dacav

3

chuỗi không có những gì bạn cần, nhưng std :: stringstream thì có. Sử dụng một chuỗi dòng để tạo chuỗi và sau đó trích xuất chuỗi. Dưới đây là một danh sách toàn diện về những điều bạn có thể làm. Ví dụ:

cout.setprecision(10); //stringstream is a stream like cout

sẽ cung cấp cho bạn 10 vị trí thập phân chính xác khi in gấp đôi hoặc nổi.


8
mà vẫn không cung cấp cho bạn bất cứ điều gì gần printf điều khiển mang lại cho bạn ... nhưng thật tuyệt.
Erik Aronesty

3

Bạn có thể thử điều này:

string str;
str.resize( _MAX_PATH );

sprintf( &str[0], "%s %s", "hello", "world" );
// optionals
// sprintf_s( &str[0], str.length(), "%s %s", "hello", "world" ); // Microsoft
// #include <stdio.h>
// snprintf( &str[0], str.length(), "%s %s", "hello", "world" ); // c++11

str.resize( strlen( str.data() ) + 1 );

3

Nếu bạn đang ở trên một hệ thống có asprintf (3) , bạn có thể dễ dàng bọc nó:

#include <iostream>
#include <cstdarg>
#include <cstdio>

std::string format(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));

std::string format(const char *fmt, ...)
{
    std::string result;

    va_list ap;
    va_start(ap, fmt);

    char *tmp = 0;
    int res = vasprintf(&tmp, fmt, ap);
    va_end(ap);

    if (res != -1) {
        result = tmp;
        free(tmp);
    } else {
        // The vasprintf call failed, either do nothing and
        // fall through (will return empty string) or
        // throw an exception, if your code uses those
    }

    return result;
}

int main(int argc, char *argv[]) {
    std::string username = "you";
    std::cout << format("Hello %s! %d", username.c_str(), 123) << std::endl;
    return 0;
}

2
Tôi sẽ thêm dòng này như một tuyên bố trước đây format, vì nó bảo gcc kiểm tra các loại đối số và đưa ra cảnh báo đàng hoàng với -Wall:std::string format(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
Aaron McDaid

2
Tôi vừa thêm một cuộc gọi đến va_end. "Nếu va_end không được gọi trước khi hàm gọi va_start hoặc va_copy trả về, thì hành vi không được xác định." - docs
Aaron McDaid

1
Bạn nên kiểm tra kết quả trả về của vasprintf vì giá trị con trỏ không được xác định khi thất bại. Vì vậy, có thể bao gồm <new> và thêm: if (size == -1) {throw std :: bad_alloc (); }
Neil McGill

Thật tốt, tôi đã sửa đổi câu trả lời cho phù hợp, tôi đã quyết định chỉ đưa ra nhận xét ở đó thay vì thực hiện throw std::bad_alloc();, vì tôi không sử dụng ngoại lệ C ++ trong cơ sở mã của mình và đối với những người thực hiện, họ có thể dễ dàng thêm nó dựa trên về nguồn bình luận và bình luận của bạn ở đây.
Thomas Perl

2

Đây là mã tôi sử dụng để thực hiện điều này trong chương trình của mình ... Không có gì lạ mắt, nhưng nó có mẹo ... Lưu ý, bạn sẽ phải điều chỉnh kích thước của mình nếu có. MAX_BUFFER đối với tôi là 1024.

std::string Format ( const char *fmt, ... )
{
    char textString[MAX_BUFFER*5] = {'\0'};

    // -- Empty the buffer properly to ensure no leaks.
    memset(textString, '\0', sizeof(textString));

    va_list args;
    va_start ( args, fmt );
    vsnprintf ( textString, MAX_BUFFER*5, fmt, args );
    va_end ( args );
    std::string retStr = textString;
    return retStr;
}

4
Việc khởi tạo textString đã đặt toàn bộ bộ đệm thành không. Không cần phải nhớ ...
EricSchaefer

Đây là bản sao đầy đủ của dữ liệu, có thể sử dụng vsnprintftrực tiếp vào chuỗi.
Vịt Mooing

2

Lấy ý tưởng từ Dacavcâu trả lời pixelpoint của . Tôi đã chơi xung quanh một chút và nhận được điều này:

#include <cstdarg>
#include <cstdio>
#include <string>

std::string format(const char* fmt, ...)
{
    va_list vl;

    va_start(vl, fmt);
    int size = vsnprintf(0, 0, fmt, vl) + sizeof('\0');
    va_end(vl);

    char buffer[size];

    va_start(vl, fmt);
    size = vsnprintf(buffer, size, fmt, vl);
    va_end(vl);

    return std::string(buffer, size);
}

Với thực hành lập trình lành mạnh, tôi tin rằng mã là đủ, tuy nhiên tôi vẫn mở cho các lựa chọn thay thế an toàn hơn, vẫn đủ đơn giản và không yêu cầu C ++ 11.


Và đây là một phiên bản khác sử dụng bộ đệm ban đầu để ngăn cuộc gọi thứ hai đến vsnprintf()khi bộ đệm ban đầu đã đủ.

std::string format(const char* fmt, ...)
{

    va_list vl;
    int size;

    enum { INITIAL_BUFFER_SIZE = 512 };

    {
        char buffer[INITIAL_BUFFER_SIZE];

        va_start(vl, fmt);
        size = vsnprintf(buffer, INITIAL_BUFFER_SIZE, fmt, vl);
        va_end(vl);

        if (size < INITIAL_BUFFER_SIZE)
            return std::string(buffer, size);
    }

    size += sizeof('\0');

    char buffer[size];

    va_start(vl, fmt);
    size = vsnprintf(buffer, size, fmt, vl);
    va_end(vl);

    return std::string(buffer, size);
}

(Nó chỉ ra rằng phiên bản này chỉ là tương tự như câu trả lời Piti Ongmongkolkul của , duy nhất mà nó không sử dụng newdelete[], và cũng có thể chỉ định kích thước khi tạo std::string.

Ý tưởng ở đây là không sử dụng newdelete[]là ngụ ý việc sử dụng stack trong heap vì nó không cần gọi các hàm phân bổ và phân bổ, tuy nhiên nếu không được sử dụng đúng cách, có thể nguy hiểm khi tràn bộ đệm trong một số (có thể cũ, hoặc có lẽ chỉ dễ bị tổn thương) hệ thống. Nếu đây là một mối quan tâm, tôi rất khuyên bạn nên sử dụng newdelete[]thay vào đó. Lưu ý rằng mối quan tâm duy nhất ở đây là về các phân bổ vsnprintf()đã được gọi với các giới hạn, vì vậy việc chỉ định giới hạn dựa trên kích thước được phân bổ trên bộ đệm thứ hai cũng sẽ ngăn chặn các phân bổ đó.)


2

Tôi thường sử dụng cái này:

std::string myformat(const char *const fmt, ...)
{
        char *buffer = NULL;
        va_list ap;

        va_start(ap, fmt);
        (void)vasprintf(&buffer, fmt, ap);
        va_end(ap);

        std::string result = buffer;
        free(buffer);

        return result;
}

Nhược điểm: không phải tất cả các hệ thống đều hỗ trợ vasprint


vasprintf là tốt - tuy nhiên bạn cần kiểm tra mã trả lại. Trên bộ đệm -1 sẽ có giá trị không xác định. Cần: if (size == -1) {throw std :: bad_alloc (); }
Neil McGill

2

Bên dưới phiên bản sửa đổi của câu trả lời @iFreilicht, được cập nhật lên C ++ 14 (sử dụng make_uniquehàm thay vì khai báo thô) và thêm hỗ trợ cho các std::stringđối số (dựa trên bài viết của Kenny Kerr )

#include <iostream>
#include <memory>
#include <string>
#include <cstdio>

template <typename T>
T process_arg(T value) noexcept
{
    return value;
}

template <typename T>
T const * process_arg(std::basic_string<T> const & value) noexcept
{
    return value.c_str();
}

template<typename ... Args>
std::string string_format(const std::string& format, Args const & ... args)
{
    const auto fmt = format.c_str();
    const size_t size = std::snprintf(nullptr, 0, fmt, process_arg(args) ...) + 1;
    auto buf = std::make_unique<char[]>(size);
    std::snprintf(buf.get(), size, fmt, process_arg(args) ...);
    auto res = std::string(buf.get(), buf.get() + size - 1);
    return res;
}

int main()
{
    int i = 3;
    float f = 5.f;
    char* s0 = "hello";
    std::string s1 = "world";
    std::cout << string_format("i=%d, f=%f, s=%s %s", i, f, s0, s1) << "\n";
}

Đầu ra:

i = 3, f = 5.000000, s = hello world

Hãy hợp nhất câu trả lời này với câu trả lời ban đầu nếu muốn.



1

Bạn có thể định dạng đầu ra C ++ trong cout bằng tệp tiêu đề iomanip. Đảm bảo rằng bạn bao gồm tệp tiêu đề iomanip trước khi bạn sử dụng bất kỳ chức năng trợ giúp nào như setprecision, setfill, v.v.

Đây là đoạn mã tôi đã sử dụng trong quá khứ để in thời gian chờ trung bình trong vectơ mà tôi đã "tích lũy".

#include<iomanip>
#include<iostream>
#include<vector>
#include<numeric>

...

cout<< "Average waiting times for tasks is " << setprecision(4) << accumulate(all(waitingTimes), 0)/double(waitingTimes.size()) ;
cout << " and " << Q.size() << " tasks remaining" << endl;

Dưới đây là một mô tả ngắn gọn về cách chúng ta có thể định dạng các luồng C ++. http://www.cprogramming.com/tutorial/iomanip.html


1

Có thể có vấn đề, nếu bộ đệm không đủ lớn để in chuỗi. Bạn phải xác định độ dài của chuỗi được định dạng trước khi in một tin nhắn được định dạng trong đó. Tôi tự trợ giúp cho việc này (đã thử nghiệm trên Windows và Linux GCC ) và bạn có thể thử sử dụng nó.

Chuỗi.cpp: http://pastebin.com/DnfvzyKP Chuỗi.h
: http://pastebin.com/7U6iCUMa

Chuỗi.cpp:

#include <cstdio>
#include <cstdarg>
#include <cstring>
#include <string>

using ::std::string;

#pragma warning(disable : 4996)

#ifndef va_copy
#ifdef _MSC_VER
#define va_copy(dst, src) dst=src
#elif !(__cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__))
#define va_copy(dst, src) memcpy((void*)dst, (void*)src, sizeof(*src))
#endif
#endif

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ap Variable argument list
///
void toString(string &dst, const char *format, va_list ap) throw() {
  int length;
  va_list apStrLen;
  va_copy(apStrLen, ap);
  length = vsnprintf(NULL, 0, format, apStrLen);
  va_end(apStrLen);
  if (length > 0) {
    dst.resize(length);
    vsnprintf((char *)dst.data(), dst.size() + 1, format, ap);
  } else {
    dst = "Format error! format: ";
    dst.append(format);
  }
}

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ... Variable argument list
///
void toString(string &dst, const char *format, ...) throw() {
  va_list ap;
  va_start(ap, format);
  toString(dst, format, ap);
  va_end(ap);
}

///
/// \breif Format message
/// \param format Format of message
/// \param ... Variable argument list
///
string toString(const char *format, ...) throw() {
  string dst;
  va_list ap;
  va_start(ap, format);
  toString(dst, format, ap);
  va_end(ap);
  return dst;
}

///
/// \breif Format message
/// \param format Format of message
/// \param ap Variable argument list
///
string toString(const char *format, va_list ap) throw() {
  string dst;
  toString(dst, format, ap);
  return dst;
}


int main() {
  int a = 32;
  const char * str = "This works!";

  string test(toString("\nSome testing: a = %d, %s\n", a, str));
  printf(test.c_str());

  a = 0x7fffffff;
  test = toString("\nMore testing: a = %d, %s\n", a, "This works too..");
  printf(test.c_str());

  a = 0x80000000;
  toString(test, "\nMore testing: a = %d, %s\n", a, "This way is cheaper");
  printf(test.c_str());

  return 0;
}

Chuỗi.h:

#pragma once
#include <cstdarg>
#include <string>

using ::std::string;

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ap Variable argument list
///
void toString(string &dst, const char *format, va_list ap) throw();
///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ... Variable argument list
///
void toString(string &dst, const char *format, ...) throw();
///
/// \breif Format message
/// \param format Format of message
/// \param ... Variable argument list
///
string toString(const char *format, ...) throw();

///
/// \breif Format message
/// \param format Format of message
/// \param ap Variable argument list
///
string toString(const char *format, va_list ap) throw();

Liên quan đến dòng vsnprintf((char *)dst.data(), dst.size() + 1, format, ap);- Có an toàn không khi giả sử bộ đệm của chuỗi có chỗ cho ký tự null kết thúc? Có triển khai không phân bổ kích thước + 1 ký tự. Nó sẽ an toàn hơn để làmdst.resize(length+1); vsnprintf((char *)dst.data(), dst.size(), format, ap); dst.resize(length);
drwatsoncode

Rõ ràng câu trả lời cho nhận xét trước đây của tôi là: Không an toàn khi cho rằng có một ký tự null. Cụ thể liên quan đến thông số C ++ 98: "Truy cập giá trị tại data () + size () tạo ra hành vi không xác định : Không có gì đảm bảo rằng một ký tự null kết thúc chuỗi ký tự được chỉ ra bởi giá trị được hàm này trả về. :: c_str cho một hàm cung cấp sự đảm bảo như vậy. Một chương trình sẽ không thay đổi bất kỳ ký tự nào trong chuỗi này. "Tuy nhiên, thông số C ++ 11 chỉ ra rằng datac_strlà từ đồng nghĩa.
drwatson


1

Giải pháp rất đơn giản.

std::string strBuf;
strBuf.resize(256);
int iCharsPrinted = sprintf_s((char *)strPath.c_str(), strPath.size(), ...);
strBuf.resize(iCharsPrinted);

1

Tôi nhận ra điều này đã được trả lời nhiều lần, nhưng điều này ngắn gọn hơn:

std::string format(const std::string fmt_str, ...)
{
    va_list ap;
    char *fp = NULL;
    va_start(ap, fmt_str);
    vasprintf(&fp, fmt_str.c_str(), ap);
    va_end(ap);
    std::unique_ptr<char[]> formatted(fp);
    return std::string(formatted.get());
}

thí dụ:

#include <iostream>
#include <random>

int main()
{
    std::random_device r;
    std::cout << format("Hello %d!\n", r());
}

Xem thêm http://rextester.com/NJB14150


1

CẬP NHẬT 1 : fmt::formatkiểm tra thêm

Tôi đã thực hiện cuộc điều tra của riêng mình xung quanh các phương pháp đã được giới thiệu ở đây và thu được kết quả ngược chiều so với đề cập ở đây.

Tôi đã sử dụng 4 hàm trên 4 phương thức:

  • chức năng biến đổi + vsnprintf+std::unique_ptr
  • chức năng biến đổi + vsnprintf+std::string
  • chức năng mẫu matrixdic + std::ostringstream+ std::tuple+utility::for_each
  • fmt::formatchức năng từ fmtthư viện

Đối với các phụ trợ thử nghiệm googletestđã sử dụng.

#include <string>
#include <cstdarg>
#include <cstdlib>
#include <memory>
#include <algorithm>

#include <fmt/format.h>

inline std::string string_format(size_t string_reserve, const std::string fmt_str, ...)
{
    size_t str_len = (std::max)(fmt_str.size(), string_reserve);

    // plain buffer is a bit faster here than std::string::reserve
    std::unique_ptr<char[]> formatted;

    va_list ap;
    va_start(ap, fmt_str);

    while (true) {
        formatted.reset(new char[str_len]);

        const int final_n = vsnprintf(&formatted[0], str_len, fmt_str.c_str(), ap);

        if (final_n < 0 || final_n >= int(str_len))
            str_len += (std::abs)(final_n - int(str_len) + 1);
        else
            break;
    }

    va_end(ap);

    return std::string(formatted.get());
}

inline std::string string_format2(size_t string_reserve, const std::string fmt_str, ...)
{
    size_t str_len = (std::max)(fmt_str.size(), string_reserve);
    std::string str;

    va_list ap;
    va_start(ap, fmt_str);

    while (true) {
        str.resize(str_len);

        const int final_n = vsnprintf(const_cast<char *>(str.data()), str_len, fmt_str.c_str(), ap);

        if (final_n < 0 || final_n >= int(str_len))
            str_len += (std::abs)(final_n - int(str_len) + 1);
        else {
            str.resize(final_n); // do not forget to shrink the size!
            break;
        }
    }

    va_end(ap);

    return str;
}

template <typename... Args>
inline std::string string_format3(size_t string_reserve, Args... args)
{
    std::ostringstream ss;
    if (string_reserve) {
        ss.rdbuf()->str().reserve(string_reserve);
    }
    std::tuple<Args...> t{ args... };
    utility::for_each(t, [&ss](auto & v)
    {
        ss << v;
    });
    return ss.str();
}

Việc for_eachthực hiện được thực hiện từ đây: lặp đi lặp lại

#include <type_traits>
#include <tuple>

namespace utility {

    template <std::size_t I = 0, typename FuncT, typename... Tp>
    inline typename std::enable_if<I == sizeof...(Tp), void>::type
        for_each(std::tuple<Tp...> &, const FuncT &)
    {
    }

    template<std::size_t I = 0, typename FuncT, typename... Tp>
    inline typename std::enable_if<I < sizeof...(Tp), void>::type
        for_each(std::tuple<Tp...> & t, const FuncT & f)
    {
        f(std::get<I>(t));
        for_each<I + 1, FuncT, Tp...>(t, f);
    }

}

Các bài kiểm tra:

TEST(ExternalFuncs, test_string_format_on_unique_ptr_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format(0, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_unique_ptr_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format(256, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_std_string_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format2(0, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_std_string_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format2(256, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_on_variadic_tuple_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format3(0, "test test test", "+", 12345, "\n");
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_on_variadic_tuple_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format3(256, "test test test", "+", 12345, "\n");
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_inline_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        std::ostringstream ss;
        ss << "test test test" << "+" << 12345 << "\n";
        const std::string v = ss.str();
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_inline_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        std::ostringstream ss;
        ss.rdbuf()->str().reserve(256);
        ss << "test test test" << "+" << 12345 << "\n";
        const std::string v = ss.str();
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_fmt_format_positional)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = fmt::format("{0:s}+{1:d}\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_fmt_format_named)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = fmt::format("{first:s}+{second:d}\n", fmt::arg("first", "test test test"), fmt::arg("second", 12345));
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

Các UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR.

chưa có.hpp :

#define UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(var)   ::utility::unused_param(&var)

namespace utility {

    extern const volatile void * volatile g_unused_param_storage_ptr;

    extern void
#ifdef __GNUC__
    __attribute__((optimize("O0")))
#endif
        unused_param(const volatile void * p);

}

chưa sử dụng.cpp :

namespace utility {

    const volatile void * volatile g_unused_param_storage_ptr = nullptr;

    void
#ifdef __GNUC__
    __attribute__((optimize("O0")))
#endif
        unused_param(const volatile void * p)
    {
        g_unused_param_storage_ptr = p;
    }

}

KẾT QUẢ :

[ RUN      ] ExternalFuncs.test_string_format_on_unique_ptr_0
[       OK ] ExternalFuncs.test_string_format_on_unique_ptr_0 (556 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_unique_ptr_256
[       OK ] ExternalFuncs.test_string_format_on_unique_ptr_256 (331 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_std_string_0
[       OK ] ExternalFuncs.test_string_format_on_std_string_0 (457 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_std_string_256
[       OK ] ExternalFuncs.test_string_format_on_std_string_256 (279 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_0
[       OK ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_0 (1214 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_256
[       OK ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_256 (1325 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_inline_0
[       OK ] ExternalFuncs.test_string_format_on_string_stream_inline_0 (1208 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_inline_256
[       OK ] ExternalFuncs.test_string_format_on_string_stream_inline_256 (1302 ms)
[ RUN      ] ExternalFuncs.test_fmt_format_positional
[       OK ] ExternalFuncs.test_fmt_format_positional (288 ms)
[ RUN      ] ExternalFuncs.test_fmt_format_named
[       OK ] ExternalFuncs.test_fmt_format_named (392 ms)

Như bạn có thể thấy việc thực hiện thông qua vsnprintf+ std::stringbằng fmt::format, nhưng nhanh hơn thông qua vsnprintf+ std::unique_ptr, nhanh hơn thông qua std::ostringstream.

Các bài kiểm tra được biên dịch Visual Studio 2015 Update 3và chạy tại Windows 7 x64 / Intel Core i7-4820K CPU @ 3.70GHz / 16GB.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.