16 に答える
質問が違いを求めただけであっても、この質問の誰もが よりもはるかにstd::cout
優れていると主張していることに驚いています。printf
ここで違いがあります -std::cout
は C++ であり、printf
C です (ただし、C の他のほとんどのものと同様に、C++ でも使用できます)。ここで正直に言います。両方printf
にstd::cout
利点があります。
本当の違い
拡張性
std::cout
拡張可能です。それも拡張可能だと人々が言うことprintf
は知っていますが、そのような拡張はC標準では言及されていません(したがって、非標準機能を使用する必要があります-しかし、一般的な非標準機能は存在しません)。 (そのため、既存の形式と競合するのは簡単です)。
とは異なりprintf
、std::cout
完全に演算子のオーバーロードに依存するため、カスタム フォーマットに問題はありませんstd::ostream
。最初の引数としてサブルーチンを定義し、2 番目に型を定義するだけです。そのため、名前空間の問題はありません。クラス (1 文字に限定されない) がある限り、そのクラスのstd::ostream
オーバーロードを機能させることができます。
しかし、多くの人が拡張を望んでいるとは思えostream
ません (正直なところ、簡単に作成できる拡張機能であっても、そのような拡張機能はめったに見ませんでした)。ただし、必要な場合はここにあります。
構文
簡単にわかるように、printf
とstd::cout
は異なる構文を使用します。printf
パターン文字列と可変長引数リストを使用する標準関数構文を使用します。実際、printf
これが C にそれらがある理由printf
です。形式は複雑すぎて、それらなしでは使用できません。ただし、std::cout
別の API (operator <<
それ自体を返す API) を使用します。
一般に、これは C バージョンの方が短くなることを意味しますが、ほとんどの場合は問題になりません。多くの引数を出力すると、違いが顕著になります。エラー番号を想定して のようなものを記述する必要がError 2: File not found.
あり、その説明がプレースホルダーである場合、コードは次のようになります。どちらの例も同じように機能します (つまり、std::endl
実際にバッファーをフラッシュします)。
printf("Error %d: %s.\n", id, errors[id]);
std::cout << "Error " << id << ": " << errors[id] << "." << std::endl;
これはそれほどおかしくはありませんが (2 倍の長さです)、引数を出力するだけでなく、実際にフォーマットすると、さらにおかしなことになります。たとえば、次のような印刷は0x0424
クレイジーです。これは、std::cout
状態と実際の値が混在していることが原因です。のようなものが型になる言語を見たことがありませんstd::setfill
(もちろん、C++ 以外)。printf
引数と実際の型を明確に分離します。私はそれのバージョン(ノイズが多すぎるため)と比較して、そのバージョンを維持することを本当に好みprintf
ます(たとえそれがちょっと不可解に見えるとしても)。iostream
printf("0x%04x\n", 0x424);
std::cout << "0x" << std::hex << std::setfill('0') << std::setw(4) << 0x424 << std::endl;
翻訳
これが本当の利点ですprintf
。printf
フォーマット文字列はまあ...文字列です。operator <<
これにより、の乱用と比較して、翻訳が非常に簡単になりiostream
ます。gettext()
関数が変換され、 を表示したいと仮定するとError 2: File not found.
、前に表示された書式文字列の変換を取得するコードは次のようになります。
printf(gettext("Error %d: %s.\n"), id, errors[id]);
ここで、エラー番号が説明の後にある Fictionish に翻訳すると仮定しましょう。翻訳された文字列は次のようになります%2$s oru %1$d.\n
。では、C++ でそれを行うにはどうすればよいでしょうか。まあ、私にはわかりません。翻訳の目的で、に渡すことができるiostream
構造体などを偽造できると思います。もちろん、C標準ではありませんが、非常に一般的であるため、安全に使用できると思います。printf
gettext
$
特定の整数型構文を覚えたり調べたりする必要がない
C には多くの整数型があり、C++ も同様です。std::cout
はすべての型を処理しますがprintf
、整数型に応じて特定の構文が必要です (非整数型もありますが、実際に使用する非整数型printf
は(C 文字列、メソッド をconst char *
使用して取得できます) のみです)。たとえば、 を印刷するには を使用する必要がありますが、を使用する必要があります。テーブルはhttp://en.cppreference.com/w/cpp/io/c/fprintfおよびhttp://en.cppreference.com/w/cpp/types/integerで入手できます。to_c
std::string
size_t
%zu
int64_t
%"PRId64"
NUL バイトを印刷することはできません。\0
printf
C++ 文字列ではなく C 文字列を使用するため、特定のトリックなしでは NUL バイトを出力できません。場合によっては%c
with'\0'
を引数として使用できますが、これは明らかにハックです。
誰も気にしない違い
パフォーマンス
更新:iostream
は非常に遅いため、通常はハード ドライブよりも遅いことがわかります (プログラムをファイルにリダイレクトする場合)。大量のデータを出力する必要がある場合は、 との同期を無効にするとstdio
役立つ場合があります。(STDOUT に複数の行を書き込むのではなく) パフォーマンスが本当に重要な場合は、単にprintf
.
誰もがパフォーマンスを気にかけていると思っていますが、わざわざ測定する人はいません。printf
私の答えは、またはを使用するかどうかに関係なく、とにかく I/O がボトルネックであるということですiostream
。アセンブリを簡単に調べると、より高速になるprintf
可能性があると思います(-O3
コンパイラオプションを使用してclangでコンパイル)。私のエラーの例を仮定すると、printf
例は例よりもはるかに少ない呼び出しを行いcout
ます。これは次int main
のprintf
とおりです。
main: @ @main
@ BB#0:
push {lr}
ldr r0, .LCPI0_0
ldr r2, .LCPI0_1
mov r1, #2
bl printf
mov r0, #0
pop {lr}
mov pc, lr
.align 2
@ BB#1:
2 つの文字列と(数値) が引数2
としてプッシュされていることがすぐにわかります。printf
それだけです。他に何もありません。比較のために、これはiostream
アセンブリにコンパイルされます。いいえ、インライン化はありません。すべての単一operator <<
の呼び出しは、別の引数セットを持つ別の呼び出しを意味します。
main: @ @main
@ BB#0:
push {r4, r5, lr}
ldr r4, .LCPI0_0
ldr r1, .LCPI0_1
mov r2, #6
mov r3, #0
mov r0, r4
bl _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
mov r0, r4
mov r1, #2
bl _ZNSolsEi
ldr r1, .LCPI0_2
mov r2, #2
mov r3, #0
mov r4, r0
bl _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
ldr r1, .LCPI0_3
mov r0, r4
mov r2, #14
mov r3, #0
bl _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
ldr r1, .LCPI0_4
mov r0, r4
mov r2, #1
mov r3, #0
bl _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
ldr r0, [r4]
sub r0, r0, #24
ldr r0, [r0]
add r0, r0, r4
ldr r5, [r0, #240]
cmp r5, #0
beq .LBB0_5
@ BB#1: @ %_ZSt13__check_facetISt5ctypeIcEERKT_PS3_.exit
ldrb r0, [r5, #28]
cmp r0, #0
beq .LBB0_3
@ BB#2:
ldrb r0, [r5, #39]
b .LBB0_4
.LBB0_3:
mov r0, r5
bl _ZNKSt5ctypeIcE13_M_widen_initEv
ldr r0, [r5]
mov r1, #10
ldr r2, [r0, #24]
mov r0, r5
mov lr, pc
mov pc, r2
.LBB0_4: @ %_ZNKSt5ctypeIcE5widenEc.exit
lsl r0, r0, #24
asr r1, r0, #24
mov r0, r4
bl _ZNSo3putEc
bl _ZNSo5flushEv
mov r0, #0
pop {r4, r5, lr}
mov pc, lr
.LBB0_5:
bl _ZSt16__throw_bad_castv
.align 2
@ BB#6:
ただし、正直なところ、I/O がボトルネックであるため、これは何の意味もありません。iostream
「タイプセーフ」であるため、高速ではないことを示したかっただけです。ほとんどの C 実装はprintf
、計算された goto を使用してフォーマットを実装するため、printf
コンパイラが認識していなくても、 は可能な限り高速ですprintf
(そうではないというわけではありません - 一部のコンパイラは特定のケースで最適化できます-通常、 でprintf
終わる定数文字列は に最適化されます) 。 .\n
puts
継承
なぜ継承したいのかわかりませんがostream
、気にしません。とFILE
も可能です。
class MyFile : public FILE {}
型安全性
確かに、可変長の引数リストには安全性がありませんが、それは問題ではありませんprintf
。警告を有効にすると、一般的な C コンパイラはフォーマット文字列の問題を検出できるからです。実際、Clang は警告を有効にせずにそれを行うことができます。
$ cat safety.c
#include <stdio.h>
int main(void) {
printf("String: %s\n", 42);
return 0;
}
$ clang safety.c
safety.c:4:28: warning: format specifies type 'char *' but the argument has type 'int' [-Wformat]
printf("String: %s\n", 42);
~~ ^~
%d
1 warning generated.
$ gcc -Wall safety.c
safety.c: In function ‘main’:
safety.c:4:5: warning: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘int’ [-Wformat=]
printf("String: %s\n", 42);
^
C++ FAQから:
[15.1]
<iostream>
従来の の代わりに使用する必要があるのはなぜ<cstdio>
ですか?型の安全性を高め、エラーを減らし、拡張性を許可し、継承性を提供します。
printf()
間違いなく壊れておらscanf()
ず、エラーが発生しやすいにもかかわらず、おそらく住みやすいですが、どちらも C++ I/O ができることに関して制限されています。C++ I/O (<<
とを使用>>
) は、C (printf()
とを使用scanf()
) に関連しています。
- よりタイプセーフ: を使用
<iostream>
すると、I/O されるオブジェクトのタイプがコンパイラによって静的に認識されます。対照的に、<cstdio>
「%」フィールドを使用して型を動的に把握します。- エラーが発生しにくい: では
<iostream>
、I/O される実際のオブジェクトと一致する必要がある冗長な "%" トークンはありません。冗長性を削除すると、エラーのクラスが削除されます。- 拡張可能: C++
<iostream>
メカニズムにより、既存のコードを壊すことなく、新しいユーザー定義型を I/O できます。誰もが互換性のない新しい「%」フィールドを同時に と に追加した場合の混乱を想像してみてprintf()
くださいscanf()
。- 継承可能: C++メカニズムは、や
<iostream>
などの実際のクラスから構築されます。の とは異なり、これらは実際のクラスであるため、継承可能です。これは、ストリームのように見えて動作する他のユーザー定義のものを作成できることを意味します。知らないユーザーが書いた膨大な数の I/O コードを自動的に使用できるようになり、ユーザーはあなたの「拡張ストリーム」クラスについて知る必要がありません。std::ostream
std::istream
<cstdio>
FILE*
一方、は非常に高速であるため、非常に特殊で限られたケースよりもprintf
優先して使用することが正当化される場合があります。常に最初にプロファイルします。(たとえば、http://programming-designs.com/2009/02/c-speed-test-part-2-printf-vs-cout / を参照)cout
人々はしばしばそれprintf
がはるかに速いと主張します。これは主に神話です。テストしたところ、次の結果が得られました。
cout with only endl 1461.310252 ms
cout with only '\n' 343.080217 ms
printf with only '\n' 90.295948 ms
cout with string constant and endl 1892.975381 ms
cout with string constant and '\n' 416.123446 ms
printf with string constant and '\n' 472.073070 ms
cout with some stuff and endl 3496.489748 ms
cout with some stuff and '\n' 2638.272046 ms
printf with some stuff and '\n' 2520.318314 ms
結論: 改行のみが必要な場合は、printf
;を使用します。それ以外の場合cout
は、ほぼ同じか、さらに高速です。詳細については、私のブログをご覧ください。
明確にするために、私はs が常に;iostream
よりも優れていると言いたいわけではありません。printf
私が言いたいのは、実際のデータに基づいて十分な情報に基づいた決定を下すべきであり、よくある誤解を招くような仮定に基づいた大雑把な推測ではありません。
更新: テストに使用した完全なコードは次のとおりです。g++
追加のオプションなしでコンパイルされ-lrt
ました (タイミングは別として)。
#include <stdio.h>
#include <iostream>
#include <ctime>
class TimedSection {
char const *d_name;
timespec d_start;
public:
TimedSection(char const *name) :
d_name(name)
{
clock_gettime(CLOCK_REALTIME, &d_start);
}
~TimedSection() {
timespec end;
clock_gettime(CLOCK_REALTIME, &end);
double duration = 1e3 * (end.tv_sec - d_start.tv_sec) +
1e-6 * (end.tv_nsec - d_start.tv_nsec);
std::cerr << d_name << '\t' << std::fixed << duration << " ms\n";
}
};
int main() {
const int iters = 10000000;
char const *text = "01234567890123456789";
{
TimedSection s("cout with only endl");
for (int i = 0; i < iters; ++i)
std::cout << std::endl;
}
{
TimedSection s("cout with only '\\n'");
for (int i = 0; i < iters; ++i)
std::cout << '\n';
}
{
TimedSection s("printf with only '\\n'");
for (int i = 0; i < iters; ++i)
printf("\n");
}
{
TimedSection s("cout with string constant and endl");
for (int i = 0; i < iters; ++i)
std::cout << "01234567890123456789" << std::endl;
}
{
TimedSection s("cout with string constant and '\\n'");
for (int i = 0; i < iters; ++i)
std::cout << "01234567890123456789\n";
}
{
TimedSection s("printf with string constant and '\\n'");
for (int i = 0; i < iters; ++i)
printf("01234567890123456789\n");
}
{
TimedSection s("cout with some stuff and endl");
for (int i = 0; i < iters; ++i)
std::cout << text << "01234567890123456789" << i << std::endl;
}
{
TimedSection s("cout with some stuff and '\\n'");
for (int i = 0; i < iters; ++i)
std::cout << text << "01234567890123456789" << i << '\n';
}
{
TimedSection s("printf with some stuff and '\\n'");
for (int i = 0; i < iters; ++i)
printf("%s01234567890123456789%i\n", text, i);
}
}
そして私は引用します:
大まかに言うと、主な違いはタイプ セーフ (cstdio にはありません)、パフォーマンス (ほとんどの iostream 実装は cstdio 実装より遅い)、および拡張性 (iostream はカスタム出力ターゲットとユーザー定義型のシームレスな出力を可能にします) です。
1 つは stdout に出力する関数です。もう 1 つは、いくつかのメンバー関数とoperator<<
その出力を stdout にオーバーロードするオブジェクトです。列挙できる違いは他にもたくさんありますが、あなたが何を求めているのかわかりません。
私にとって、「printf」ではなく「cout」を選択する本当の違いは次のとおりです。
1) <<演算子は、クラスに対してオーバーロードできます。
2) cout の出力ストリームは、簡単にファイルに変更できます: (: コピー ペースト :)
#include <iostream>
#include <fstream>
using namespace std;
int main ()
{
cout << "This is sent to prompt" << endl;
ofstream file;
file.open ("test.txt");
streambuf* sbuf = cout.rdbuf();
cout.rdbuf(file.rdbuf());
cout << "This is sent to file" << endl;
cout.rdbuf(sbuf);
cout << "This is also sent to prompt" << endl;
return 0;
}
3) 特に多くのパラメータがある場合、cout の方が読みやすいと思います。
1 つの問題はcout
、書式設定オプションです。データ (精度、正当化など) の書式設定printf
は簡単です。
プリミティブの場合、どちらを使用するかはおそらく問題ではありません。複雑なオブジェクトを出力したい場合に便利です。
たとえば、クラスがある場合、
#include <iostream>
#include <cstdlib>
using namespace std;
class Something
{
public:
Something(int x, int y, int z) : a(x), b(y), c(z) { }
int a;
int b;
int c;
friend ostream& operator<<(ostream&, const Something&);
};
ostream& operator<<(ostream& o, const Something& s)
{
o << s.a << ", " << s.b << ", " << s.c;
return o;
}
int main(void)
{
Something s(3, 2, 1);
// output with printf
printf("%i, %i, %i\n", s.a, s.b, s.c);
// output with cout
cout << s << endl;
return 0;
}
上記はそれほど素晴らしいものではないように見えるかもしれませんが、これをコードの複数の場所に出力する必要があるとしましょう。それだけでなく、「int d」フィールドを追加するとします。cout を使用すると、1 か所で変更するだけで済みます。ただし、printf を使用すると、おそらく多くの場所で変更する必要があり、それだけでなく、出力する場所を思い出す必要があります。
そうは言っても、cout を使用すると、コードのメンテナンスに費やす時間を大幅に削減できます。それだけでなく、オブジェクト「何か」を新しいアプリケーションで再利用する場合、出力について心配する必要がありません。
私はプログラマーではありませんが、人間工学のエンジニアでした。プログラミング言語は簡単に習得でき、理解しやすく、使いやすいものであるべきだと思います。そのためには、シンプルで一貫した言語構造が必要です。すべての言語は記号的であり、したがって、その核心は恣意的ですが、慣習があり、それらに従うことで、言語の習得と使用が容易になります。
C++ やその他の言語には、関数 (パラメーター) として記述された膨大な数の関数があります。この構文は、コンピューター以前の時代に数学の関数関係に使用されていました。printf()
この構文に従い、C++ の作成者がファイルを読み書きするための論理的に異なる方法を作成したい場合は、同様の構文を使用して別の関数を作成するだけで済みます。
Python では、変数はオブジェクトであるため、かなり標準的なobject.method
構文、つまり variablename.print を使用してもちろん印刷できますが、C++ ではそうではありません。
<< 演算子は規則に従わないため、cout 構文は好きではありません。これはメソッドまたは関数です。つまり、パラメーターを取り、それに対して何かを行います。ただし、数学的な比較演算子であるかのように記述されています。これは、人的要因の観点からは不十分なアプローチです。
その他の違い: 「printf」は整数値 (印刷された文字数に等しい) を返し、「cout」は何も返しません。
と。
cout << "y = " << 7;
アトミックではありません。
printf("%s = %d", "y", 7);
アトミックです。
cout は型チェックを実行しますが、printf は実行しません。
に相当する iostream はありません"% d"
cout<< "Hello";
printf("%s", "Hello");
どちらも値を出力するために使用されます。それらは完全に異なる構文を持っています。C++ には両方があり、C には printf しかありません。
もちろん、メンテナンスを維持するために「何か」をもう少しうまく書くことができます。
#include <iostream>
#include <cstdlib>
using namespace std;
class Something
{
public:
Something(int x, int y, int z) : a(x), b(y), c(z) { }
int a;
int b;
int c;
friend ostream& operator<<(ostream&, const Something&);
void print() const { printf("%i, %i, %i\n", a, b, c); }
};
ostream& operator<<(ostream& o, const Something& s)
{
o << s.a << ", " << s.b << ", " << s.c;
return o;
}
int main(void)
{
Something s(3, 2, 1);
// Output with printf
s.print(); // Simple as well, isn't it?
// Output with cout
cout << s << endl;
return 0;
}
また、cout と printf のテストを少し拡張し、「double」のテストを追加しました (Visual Studio 2008、実行可能ファイルのリリース バージョン)。
#include <stdio.h>
#include <iostream>
#include <ctime>
class TimedSection {
char const *d_name;
//timespec d_start;
clock_t d_start;
public:
TimedSection(char const *name) :
d_name(name)
{
//clock_gettime(CLOCK_REALTIME, &d_start);
d_start = clock();
}
~TimedSection() {
clock_t end;
//clock_gettime(CLOCK_REALTIME, &end);
end = clock();
double duration = /*1e3 * (end.tv_sec - d_start.tv_sec) +
1e-6 * (end.tv_nsec - d_start.tv_nsec);
*/
(double) (end - d_start) / CLOCKS_PER_SEC;
std::cerr << d_name << '\t' << std::fixed << duration * 1000.0 << " ms\n";
}
};
int main() {
const int iters = 1000000;
char const *text = "01234567890123456789";
{
TimedSection s("cout with only endl");
for (int i = 0; i < iters; ++i)
std::cout << std::endl;
}
{
TimedSection s("cout with only '\\n'");
for (int i = 0; i < iters; ++i)
std::cout << '\n';
}
{
TimedSection s("printf with only '\\n'");
for (int i = 0; i < iters; ++i)
printf("\n");
}
{
TimedSection s("cout with string constant and endl");
for (int i = 0; i < iters; ++i)
std::cout << "01234567890123456789" << std::endl;
}
{
TimedSection s("cout with string constant and '\\n'");
for (int i = 0; i < iters; ++i)
std::cout << "01234567890123456789\n";
}
{
TimedSection s("printf with string constant and '\\n'");
for (int i = 0; i < iters; ++i)
printf("01234567890123456789\n");
}
{
TimedSection s("cout with some stuff and endl");
for (int i = 0; i < iters; ++i)
std::cout << text << "01234567890123456789" << i << std::endl;
}
{
TimedSection s("cout with some stuff and '\\n'");
for (int i = 0; i < iters; ++i)
std::cout << text << "01234567890123456789" << i << '\n';
}
{
TimedSection s("printf with some stuff and '\\n'");
for (int i = 0; i < iters; ++i)
printf("%s01234567890123456789%i\n", text, i);
}
{
TimedSection s("cout with formatted double (width & precision once)");
std::cout << std::fixed << std::scientific << std::right << std::showpoint;
std::cout.width(8);
for (int i = 0; i < iters; ++i)
std::cout << text << 8.315 << i << '\n';
}
{
TimedSection s("cout with formatted double (width & precision on each call)");
std::cout << std::fixed << std::scientific << std::right << std::showpoint;
for (int i = 0; i < iters; ++i)
{ std::cout.width(8);
std::cout.precision(3);
std::cout << text << 8.315 << i << '\n';
}
}
{
TimedSection s("printf with formatted double");
for (int i = 0; i < iters; ++i)
printf("%8.3f%i\n", 8.315, i);
}
}
結果は次のとおりです。
cout with only endl 6453.000000 ms
cout with only '\n' 125.000000 ms
printf with only '\n' 156.000000 ms
cout with string constant and endl 6937.000000 ms
cout with string constant and '\n' 1391.000000 ms
printf with string constant and '\n' 3391.000000 ms
cout with some stuff and endl 9672.000000 ms
cout with some stuff and '\n' 7296.000000 ms
printf with some stuff and '\n' 12235.000000 ms
cout with formatted double (width & precision once) 7906.000000 ms
cout with formatted double (width & precision on each call) 9141.000000 ms
printf with formatted double 3312.000000 ms
printf
拡張性の欠如は完全に真実ではないと言いたいです:
C では、それは真実です。しかし、C には実際のクラスはありません。
C++ では、キャスト演算子をオーバーロードできるため、演算子をオーバーロードして次のよう char*
に使用します。printf
Foo bar;
...;
printf("%s",bar);
Foo が適切な演算子をオーバーロードする場合は可能です。または、あなたが良い方法を作った場合。要するに、私 printf
と同じくらい拡張可能です。cout
C++ストリーム(一般的に... coutだけでなく)について私が見ることができる技術的な議論は次のとおりです。
タイプセーフ。
'\n'
(ちなみに、私が使用する単一のものを印刷したい場合はputchar('\n')
...昆虫を殺すために核爆弾を使用しません。)より簡単に学べます。(「複雑な」パラメーターを学習する必要はなく、
<<
and>>
演算子を使用するだけです)でネイティブに動作します
std::string
(printf
あるためstd::string::c_str()
、しかしscanf
?)
私が見るためprintf
に:
より簡単に、または少なくとも (書かれた文字に関して) より短い複雑な書式設定。私にとっては、はるかに読みやすいです(好みの問題だと思います)。
関数が何を作成したかのより良い制御 (書き込まれた文字数とフォーマッタがある場所を返します
%n
。- C++ リファレンス)より良いデバッグの可能性。最後の引数と同じ理由で。
私の個人的な好みは、主に短い行が好きで、テキストを印刷する際のタイプの問題を回避するのが本当に難しいとは思わないため、printf
(および) 関数に行きます。scanf
C スタイルの関数で私が嘆いているのstd::string
は、サポートされていないことだけです。char*
に渡す前に を通過する必要がありますprintf
(std::string::c_str()
読みたい場合は を使用しますが、どのように書くのですか?)
printf
は関数cout
ですが、変数です。