std :: stringを使用したprintf?


157

私はそれstringstd名前空間のメンバーであることを理解しているのに、なぜ以下が発生するのですか?

#include <iostream>

int main()
{
    using namespace std;

    string myString = "Press ENTER to quit program!";
    cout << "Come up and C++ me some time." << endl;
    printf("Follow this command: %s", myString);
    cin.get();

    return 0;
}

ここに画像の説明を入力してください

プログラムが実行されるたびに、myString上記の出力のように、ランダムに見える3文字の文字列が出力されます。


8
ただ知らせておくと、多くの人その本を批判しています。オブジェクト指向プログラミングについてはあまりないので理解できますが、人々が主張するほど悪くはないと思います。
Jesse Good

おお!まあ、私が本を読んでいる間、これを覚えておくことは良いことです。来年以降に読んでいくC ++の本はこれだけではないので、あまりダマンジュしないことを願っています:)
TheDarkIn1978

最高のコンパイラ警告を使用すると、gccでコンパイルする場合の質問に答えることになります。MSVCがこれを処理する方法-わかりません。
Peter VARGA

回答:


237

printfCの意味で可変引数を使用するため、タイプセーフではないため、コンパイルされます1printfにはオプションがstd::stringなく、Cスタイルの文字列のみです。それが期待するものの代わりに何か他のものを使用しても、あなたが望む結果を確実に与えることはありません。これは実際には未定義の動作であるため、何でも発生する可能性があります。

C ++を使用しているため、これを修正する最も簡単な方法はstd::coutstd::string通じオペレータがオーバーロードしていることをサポート:

std::cout << "Follow this command: " << myString;

何らかの理由でCスタイルの文字列を抽出する必要がある場合は、のc_str()メソッドを使用してnullで終了std::stringするを取得できconst char *ます。あなたの例を使用して:

#include <iostream>
#include <string>
#include <stdio.h>

int main()
{
    using namespace std;

    string myString = "Press ENTER to quit program!";
    cout << "Come up and C++ me some time." << endl;
    printf("Follow this command: %s", myString.c_str()); //note the use of c_str
    cin.get();

    return 0;
}

printfタイプセーフであるがタイプセーフな関数が必要な場合は、可変テンプレート(C ++ 11、MSVC12以降のすべての主要なコンパイラでサポート)を調べてください。ここで例を見つけることができます。そのように標準ライブラリに実装することについて私が知っていることは何もありませんが、Boost、特にboost::format


[1]:これは、任意の数の引数を渡すことができることを意味しますが、関数は、それらの引数の数と型を通知する必要があります。の場合printf、それは意味のようなエンコードされたタイプ情報を%d持つ文字列を意味しintます。タイプまたは数値について嘘をついている場合、関数には標準の知る方法がありませんが、一部のコンパイラーは、嘘をついたときにチェックして警告を出す機能を備えています。


@MooingDuck、良い点。それはジェリーの答えの中にありますが、受け入れられた答えであるため、これは人々が見るものであり、彼らは他の人に会う前に去るかもしれません。最初に見たソリューションであり、推奨されるソリューションになるように、このオプションを追加しました。
chris

43

使用しないでください printf("%s", your_string.c_str());

cout << your_string;代わりに使用してください。短く、シンプルでタイプセーフ。実際、C ++を記述しているときは、通常、printf完全に回避する必要があります。これは、C ++でめったに必要とされない、またはC ++で役立つCからの残り物です。

なぜあなたが使用する必要があるcout代わりにprintf、理由は数多くあります。最も明白なもののいくつかのサンプルを以下に示します。

  1. 質問が示すように、printfはタイプセーフではありません。渡すタイプが変換指定子で指定されたタイプと異なる場合、printfはスタック上で見つかったものをすべて指定されたタイプであるかのように使用しようとし、未定義の動作を提供します。一部のコンパイラーは、特定の状況下でこれについて警告することができますが、一部のコンパイラーはまったく/まったくできないか、まったくできません。
  2. printf拡張可能ではありません。プリミティブ型のみを渡すことができます。それが理解する変換指定子のセットは、その実装でハードコードされており、さらに/他のものを追加する方法はありません。ほとんどの適切に作成されたC ++は、主にこれらの型を使用して、解決される問題に向けられた型を実装する必要があります。
  3. それはまともなフォーマットをはるかに困難にします。明白な例として、人が読むための数字を印刷する場合、通常は数桁ごとに数千の区切り文字を挿入する必要があります。正確な桁数と区切り記号として使用される文字は異なりますが、coutもカバーされています。例えば:

    std::locale loc("");
    std::cout.imbue(loc);
    
    std::cout << 123456.78;

    名前のないロケール( "")は、ユーザーの構成に基づいてロケールを選択します。したがって、私のマシン(米国英語用に構成)では、これはとして出力され123,456.78ます。コンピュータが(たとえば)ドイツ向けに構成されている人にとっては、のように出力され123.456,78ます。インド向けに構成された人にとっては、次のように出力されます1,23,456.78(もちろん他にもたくさんあります)。printf私は、1つの結果を得ます:123456.78。それは一貫していますが、それは一貫して間違っていますどこにてもています。基本的にそれを回避するには唯一の方法は、その後、個別に書式設定を行うに文字列として結果を渡すことですprintfので、printfそれ自体が単純になりません正しく仕事をします。

  4. 非常にコンパクトですが、printfフォーマット文字列は非常に読みにくい場合があります。でも使うCプログラマの間でprintf毎日、事実上、私は、少なくとも99%が何を確認するために、物事をルックアップする必要があるだろうと思うだろう#%#x手段、そしてどのように何からの異なり#%#f、はい、彼らは完全に異なるものを意味する手段(および)。

11
@ TheDarkIn1978:あなたはおそらく忘れていました#include <string>。VC ++のヘッダーにはいくつかの奇妙な点があり、文字列を定義できますがcout<string>ヘッダーを含めずにに送信することはできません。
Jerry Coffin

28
@ジェリー:大きなデータを扱う場合、printfを使用するとcoutを使用するよりもはるかに高速であることを指摘したいだけです。したがって、それが役に立たないことを言わないでください:D
プログラマ

7
@プログラマー:stackoverflow.com/questions/12044357/…を参照してください。概要:ほとんどのcout場合、それは遅くなります。それは、使用std::endlすべきでない場所で使用したことが原因です。
Jerry Coffin 2013

29
典型的なC ++エキスパートの傲慢さ。printfが存在する場合は、なぜそれを使用しないのですか?
kuroi neko 2014

6
さて、気の利いたコメントを申し訳ありません。それでも、printfはデバッグに非常に便利です。ストリームは非常に強力ですが、コードが実際の出力をまったく認識しないという欠点があります。フォーマットされた出力の場合、printfは依然として実行可能な代替手段であり、両方のシステムがうまく連携できないのは残念です。もちろん私の意見です。
kuroi neko 14



1

主な理由は、C ++文字列が、0バイトで終了する文字のシーケンスのアドレスだけでなく、現在の長さの値を含む構造体であることです。Printfとその親族は、構造体ではなくそのようなシーケンスを見つけることを期待しているため、C ++文字列に混乱します。

私自身について言えば、htmlのテーブル構造にdivで簡単に埋めることができない場所があるように、printfにはC ++構文機能で簡単に埋めることができない場所があると思います。Dykstraがgotoについて後で書いたように、彼は宗教を始めるつもりはなく、設計が不十分なコードを埋め合わせるためにそれを陰謀として使用することに反対するだけでした。

GNUプロジェクトがprintfファミリーを彼らのg ++​​拡張に追加するなら、それは非常に素晴らしいことです。


1

サイズが重要な場合、Printfは実際に使用するとかなり良いです。メモリが問題であるプログラムを実行している場合は、printfは実際には非常に優れた評価者ソリューションです。Coutは基本的にビットをシフトして文字列のためのスペースを作りますが、printfはある種のパラメーターを取り込んで画面に出力します。単純なhello worldプログラムをコンパイルする場合、printfはそれをcoutとは対照的に60、000ビット未満でコンパイルでき、コンパイルには100万ビット以上かかります。

あなたの状況では、idはcoutを使用することをお勧めします。ただし、printfは知っておくと良いと思います。


1

printf可変数の引数を受け入れます。それらは、Plain Old Data(POD)タイプのみを持つことができます。printfコンパイラーが正しいフォーマットを持っているとコンパイラーが想定しているため、POD以外のものをコンパイルのみに渡すコード。%sは、それぞれの引数がへのポインタであることになっていることを意味しますchar。あなたの場合はそうではありstd::stringませんconst char*printf引数の型が失われ、formatパラメータから復元されることになっているため、これはわかりません。そのstd::string議論をconst char*結果のポインターにすると、目的のC文字列ではなく、メモリの無関係な領域がポイントされます。そのため、コードが意味不明に出力されます。

一方でprintf、ISフォーマットされたテキストをプリントアウトするための優れた選択肢あなたは、コンパイラの警告を有効にしていない場合は、(あなたはパディングを持っているつもり場合は特に)、それは危険なことができます。このような間違いは簡単に回避できるため、常に警告を有効にします。家族が同じタスクをより速く、よりきれいな方法で行うことができるstd::cout場合、不器用なメカニズムを使用する理由はありませんprintf。すべての警告(-Wall -Wextra)を有効にしていることを確認してください。独自のカスタムprintf実装を使用する場合は、コンパイラが提供されたパラメータに対してフォーマット文字列をチェックできるようにする__attribute__メカニズムで宣言する必要があります

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.