私の理解では、string
は std
名前空間のメンバーですが、なぜ次のようなことが起こるのでしょうか?
#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文字の文字列を表示します.
これは、printf
がC言語の意味での変数引数を使用しているため、型安全ではないからです1。printf
には std::string
というオプションはなく、C 言語風の文字列しか使えません。期待するものの代わりに別のものを使うと、間違いなく望む結果にはなりません。実際には未定義の動作なので、何が起こっても不思議ではありません。
C++を使用しているので、これを解決する最も簡単な方法は、std::cout
で普通に印刷することです。
std::cout << "Follow this command: " << myString;
何らかの理由で C スタイルの文字列を抽出する必要がある場合には,std::string
の c_str()
メソッドを使って,ヌル終端の 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の場合、これは
intを意味する
%d` のようにエンコードされた型情報を持つ文字列を意味します。型や数について嘘をついた場合、関数はそれを知る標準的な方法がありませんが、コンパイラによっては、嘘をついた場合にチェックして警告を出す機能を持っているものもあります。
くれぐれも printf("%s", your_string.c_str());
を使わないでください。
代わりにcout << your_string;
を使ってください。短くて、シンプルで、タイプセーフです。実際、C++を書いているときは、一般的にprintf
は完全に避けたいものです。
なぜprintf
ではなくcout
を使わなければならないのかというと、理由はたくさんあります。ここでは、その中でも特にわかりやすいものをいくつか紹介します。
1.質問にあるように、printf
は型安全ではありません。渡された型が変換指定子で与えられたものと異なる場合, printf
はスタック上で見つけたものを指定された型であるかのように使おうとし, 未定義の動作をします。コンパイラの中には、ある状況下でこれを警告することができるものもありますが、まったく警告できないものもありますし、すべての状況下で警告できるものはありません。
2.2. printf
は拡張性がありません。2. printf
は拡張性がなく、プリミティブな型しか渡すことができません。理解できる変換指定子のセットは実装でハードコードされており、追加や別のものを追加することはできません。よく書かれたC++では,これらの型は主に,解決しようとしている問題に適した型を実装するために使われるべきです。
3.3. まともなフォーマットを作るのが難しくなる。例えば、人が読むために数字を印刷する場合、通常は数桁ごとに数千個のセパレータを挿入したいと思います。正確な桁数やセパレータとして使用する文字は様々ですが、cout
はそれもカバーしています。例えば、以下のようになります。
std::locale loc("");
std::cout.imbue(loc);
std::cout << 123456.78;
名前のないロケール("")は、ユーザー'の設定に基づいてロケールを選択します。したがって、私のマシン(アメリカ英語に設定されている)では、これは`123,456.78`と表示されます。ドイツ語に設定している人は、`123.456.78`のように表示されます。インド用に設定している人には、`1,23,456.78`と出力されます(もちろん、他にもいろいろあります)。一方、`printf`では、`123456.78`という正確な結果が得られます。これは一貫していますが、どこの誰にとっても一貫して間違っています。基本的に、これを回避する唯一の方法は、フォーマットを個別に行い、その結果を文字列として `printf` に渡すことです。
4.4. printf
形式の文字列は非常にコンパクトですが、非常に読みづらい場合があります。ほぼ毎日 printf
を使っている C プログラマーでも、少なくとも 99% は、%#x
の #
が何を意味するのか、%#f
の #
が何を意味するのかとどう違うのかを確認するために、いろいろと調べる必要があると思います (そう、これらはまったく違う意味なのです)。
printf で使用するための c-like な文字列 (const char*
) が必要な場合は、myString.c_str()
を使用してください。
ありがとうございます