#contents *C++とは [#sd955d2d] -C++はC言語にクラス・テンプレート・例外処理などを加えたものです。 -そういうわけで、Cをある程度理解してから勉強してからでないと、なんのこったかワカランと思うのであります。 -Cは理解したぜ! って人でも、「自分の書いたプログラムのバグが取れねー」とかいっているのなら! もしそーなのなら! C++の勉強する前に、コーヒーブレイクとして、お勧めするサイトがあるのだ。「パソコン初心者の館」の『Cプログラミング診断室』である。大笑いしながら見るのも一興、青ざめながら見るのもまた一興。 //*もしCを習得している方ならすぐに習得できると思います。 *インストール [#i9c592f9] インストールは[[C言語のページ>../C]]を参照して欲しい *"クラス・テンプレート・例外処理"以外の違い [#h90f3051] **関数のプロトタイプ [#kae7bbc7] C :引数を取らない場合、voidを明示しなければならない C++:引数を取らない場合、何も記載しないでも良い(voidを明示しても良い) **戻り値 [#bec42213] C :戻り値がある場合、値を返さなくてもコンパイル可能 C++:戻り値がある場合、必ず何らかの値を返さないとコンパイルエラー **変数の宣言位置 [#z4acd480] C :関数などのブロックの先頭で宣言しなければならない(注) C++:任意の位置で宣言可能 (注)C99規格対応により、任意位置での宣言がサポートされた **for文 [#z0b8199f] C++:for文の初期化部分で宣言可能、かつ、スコープはループ内(注) (注)VC2003の場合、"言語拡張"オプションで変更可能、かつ、デフォルトでは、スコープはループ内に収まらないので注意。 (注2)VC6の場合、スコープはループ内に収まらず、かつオプションで変更も不可能である。 (例) for ( int i = 0 ; i < 5 ; i++ ){ printf("%d", i); } i++; // ここではiは見えない。エラー。(注に記した場合を除く) **コメント [#t994b288] // による1行コメントが追加(注) (注)C99規格対応により、Cにも // がサポートされた **新しい型 [#a4553814] 新しい型として、真偽値を表す bool が追加されました。~ 代入できる値は true または false のいずれかです。true は真を、falseは偽を表します。 bool b1 = true; bool b2 = false; if(b1){ ... } // 実行される if(!b2){ ... } // 偽の否定は真なのでこれも実行される if(b2){ ... } // 実行されない **キャスト [#u6910aa1] dynamic_cast、static_cast、reinterpret_cast、const_cast が追加 **参照 [#of721aa3] 参照型や、引数の参照渡しが追加(詳しくは後述) **特定キーワードの省略 [#s28fd647] C++では、struct, enum, classのキーワードは省略できることがある。 enum _sex{male, female, unknown}; struct human{ int age; char* name; _sex sex; // Cなら enum _sex sex; と書かねばならないが、enum を省略できる }; int main(){ human Taro; // Cなら struct human Taro; と書かねばならないが、struct を省略できる return 0; } *簡単なプログラム [#r8f3394c] まずは簡単なプログラムの説明をします。 以下のコードは画面に文字の表示をするプログラムでしょう。 #include<iostream> using namespace std;//stdという名前空間を使用する int main()//メイン関数。すべての始まり { cout << "Hello,Work" <<endl;//Hello,Workと画面に表示する return 0;//システムに0を返す。 } 説明はコメントに書いてあるとおりです Hello,Workと書いてあるところを他の文字列にして試してみてください。 他の文字列を表示することができます。 *オブジェクト指向プログラミング [#a4cfe636] C++はクラス単位でプログラミングを行う言語です。 そもそもクラスって何?という方に簡単な説明をすると クラスは変数や関数を一まとめにしたものです。 何のためにクラスを使うかの?というのを説明すると プログラムの変更をしたいときその変更を最小限に抑えるためです。 クラスにまとめることによりプログラムそのものを書き換えることなくクラスを書き換えるだけでプログラムを変更できます では上の文字表示プログラムをクラスを使って書いて見ましょう。 (*注意:実際に文字を表示するだけの小さなものをクラスにまとめるのは馬鹿のすることです。) #include<iostream> using namespace std; class PrintCharactor {//PrintCharactorというクラスを宣言 public: void PutChar(char *);//PrintCharactorというクラスにPutCharという関数がありますよということを示す }; void PrintCharactor::PutChar(char *str) { cout << str <<endl;//strを表示 } int main() { PrintCharactor a;//aという名前でクラスを宣言して使えるようにする a.PutChar("Hello,Work");//PutChar関数を使って画面に表示 return 0; } これで画面に表示できます。 かなり意味不明ですね。 次にこれの説明をします。 **プログラムの詳細 [#r7053a52] 先ほどの上のプログラムをコンパイルするとHello,Workが表示されると思います。 ではそのプログラムの詳細を示して行きたいと思います。 #include<iostream> この部分はiostreamというファイルを取り込みます。こうすることでいろいろな関数を使えるようになります。 class PrintCharactor {//PrintCharactorというクラスを宣言 public: void PutChar(char *);//PrintCharactorというクラスにPutCharという関数がありますよということを示す }; この部分はPrintCharactorというクラスを宣言します。 public:というのはこれをつけることで外からもアクセスできますということを示します。 これがないと外からアクセスできません。 int main() { PrintCharactor a;//aという名前でクラスを宣言して使えるようにする a.PutChar("Hello,Work");//PutChar関数を使って画面に表示 return 0; } この部分はメイン関数です。 PrintCharactor a;はPrintCharactorクラスをaという名前で作ります。 a.PutChar("Hello,Work");この部分はaという名前で宣言されたクラスのPutcharという関数を呼びます。 これによって文字が表示できます。 *関数オーバーロード [#haae1e4f] 同じ名前で中身の違う関数を複数定義できます。~ 関数をオーバーロードするには、関数の呼び出し時に関数が区別できないと、つまり''関数の引数の型が違わないと''だめです。 //int 11 を返す関数 f() int f(){ return 11; } //a+b を返す関数 f(int,int) int f(int a,int b){ return a+b; } //こういうことも可能です int f(int a,int b,int c){ return f()+a+b+c; } ただし、無闇に多くの関数をオーバーロードすると乱雑になりやすいので注意しましょう。 *演算子のオーバーロード [#l4f68f42] 演算子の機能を、関数としてプログラマが定義できます。~ これは、自前で定義したクラスの演算を分かりやすくしたい時などに便利です。 演算子のオーバーロードをするには、キーワード''operator''を使います。~ //メートル class Metre{ public: float v; Metre(float n){ v = n; } }; //キロメートル class KMetre{ public: float v; KMetre(float n){ v = n; } }; //メートルとキロメートルの加算 Metre operator + (const Metre &m,const KMetre &km){ return Metre( m.v + (km.v/1000.0f) ); } KMetre operator + (const KMetre &km,const Metre &m){ return KMetre( (m.v*1000.0f) + km.v ); } //.......... Metre X(100); KMetre Y(4); //クラスは違うが、演算は可能 Metre Z=X+Y; KMetre W=Y+X; *参照 [#jc7a7f3a] 参照とは、エイリアスとも呼び、「変数につけられた別名」です。~ -参照の宣言など~ 参照を宣言するには、型名の後に & をつけます。~ 例えば、int型変数aの参照bを宣言するには以下のようにします。 int a = 5; int& b = a; // bにaを代入する感じで~ -参照の性質~ 参照ははじめにも書いた通り、「ある変数の別名」であり、元々の変数名を書いたのとまったく同様に動作します。例えば、 int a = 5; int& b = a; b = 3; とすると、aの値も3に変わります。( b = 3; の代わりに a = 3; と書いたとの同じ結果を生じる。)~ -参照の使い道~ これだけだとまったく使い道が無いように思いますが、参照を関数の引数として渡すと便利なことがあります。~ #include <iostream> void inc(int& x){ x++; } int main(){ int a = 5; inc(a); std::cout << a << std::endl; return 0; } このコードはどんな結果を生じるでしょうか。~ ~ xはint型変数への参照であり、そこにaを渡しているので、 int& x = a; と同じことです。~ つまり、xはaの別名になり、x++; は a++; とまったく同じ挙動をしめします。 すなわち、このプログラムを実行すると 6 が表示されます。~ このようなことはポインタを使えば実現できますが、そうすると (*x)++; としなくてはなりません。~ 一階のポインタならば混乱はおきませんが、例えば FILE* へのポインタ(FILE**)を受けて、その指す先に代入・・・などとするとコードが読みにくくなります。~ 参照はこのようなことを防ぎ、より直感的に分かりやすいコードを書くことができます。 *インライン関数 [#f980021b] C++にはインライン関数というものがあります。~ 簡単に言うと、「(コンパイラが)関数のコードを、その関数を呼び出した場所にそのまま貼り付ける」といった感じです。(通常の関数呼び出しは別の場所にかかれたコードへジャンプする)~ ~定義方法~~ インライン関数を定義するには、関数定義の頭に inline キーワードを付けます。~ inline void inc(int* x){ // 定義の頭に inline を指定 (*x)++; } int main(){ int a = 0; inc(&a); return 0; } これをコンパイルすると、次のコードをコンパイルしたのと(ほぼ)同じアセンブリコードが生成されます。 ~ int main(){ int a = 0; int* x = &a; // この部分が inc(&a) に相当 (*x)++; // この部分が inc(&a) に相当 return 0; } ちょうど inc の中身が inc を呼び出した位置に展開された形になり、これを「インライン展開」と呼びます。 ~インライン関数のメリット~ -通常の関数を使うよりも高速に動作する~ 最初にも書いたように、通常の関数呼び出しを行うと、元の関数と別の場所(場所とは生成された実行ファイル上の位置)にあるコードへジャンプします。~ このジャンプを行う際に、現在のレジスタの内容を保存したり、関数の処理が終わって戻ってくる場所を記憶したり、とジャンプの前処理を行う必要があり、これに時間がかかります。~ インライン関数を使うと上に書いたように関数のコードが呼び出し元にそのまま展開されるので、ジャンプの処理は必要なく、より高速に動作します。 ~インライン関数のデメリット~ -コードが肥大化する~ 通常の関数呼び出しがなぜ時間のかかる処理をしてまでジャンプをするかと言うと、それは「コードを共有するため」です。~ 一度 inc のコードを生成してしまえば、あとは何回 inc が呼び出されようが同じ場所へジャンプすればよいのです。~ しかしインライン関数は関数の中身をそのまま展開するため、100箇所から inc が呼び出されれば同じコードが100箇所に生成されます。~ このためコードが肥大化してしまうというデメリットがあります。 ~インライン関数を使う上での注意~ -inlineを指定した関数が必ずインライン展開されるとは限らない~ コンパイラがインライン展開をするに値する(メリットの方が大きい)と判断しなければ、inlineを指定してもインライン展開されないことがあります。~ (ただし、マイクロソフトのコンパイラでは inline の代わりに __forceinline を指定することで、gccでは inline の代わりに __attribute__((always_inline)) を指定することで強制的にインライン展開させることができます。) -inline関数はプロトタイプ宣言できない~ インライン関数は宣言をヘッダに書いて定義は別のファイルに書く といったことができません。~ 別ファイルにする場合は .h に直接本体を書いて、それを include する必要があります。 *C++からCの関数を呼び出す方法 [#z883fba1] C++はCの(ほぼ)上位互換であり、同じコンパイラでコンパイルできます。~ しかし、C++からCの関数を呼び出すには注意が必要です。 例えば、 A.c というソースコードと、A.c にある関数が宣言された A.h というヘッダがあったとして、次のような B.cpp を作ったとします。 #include <iostream> #include "A.h" int main(){ std::cout << a("foo_bar"); // A.hで char* a(char* s); と宣言されているとする return 1; } この状態で A.c と B.cpp をプロジェクトに追加して、VisualC++6.0でビルドすると、 B.obj : error LNK2001: 外部シンボル ""char * __cdecl a(char *)" (?a@@YAPADPAD@Z)" は未解決です と表示され、ビルドできません。~ では、これを解決する方法を説明します。 **とりあえず解決法 [#of734d81] -A.hの関数プロトタイプ宣言を、 「extern "C" {」 , 「}」 でくくる -ただし、こうするとC言語の仕様から外れるので、C++のコードからincludeされたときのみ有効になるように、「#ifdef __cplusplus」 , 「#endif」 を使う~ 以上をふまえて、修正された A.h は次のようになります。 #ifdef __cplusplus extern "C" { #endif char* a(char* s); // 元々あった宣言文たち ...... #ifdef __cplusplus } #endif これでめでたくビルドに成功します。 **解説 [#ua7c866c] なんでこんなことが必要なのか知りたい人のために解説です。~ まず、C、C++のコードはコンパイラによってアセンブリ言語に変換されます。~ その際、各関数にあたる部分のコードのはじめには、ラベルという目印が付きます。~ (例えば、たいていのCコンパイラなら a という関数のはじめには _a というラベルがつき、a を呼び出す時には call _a などという命令を呼びます。) このラベルのつけ方が、CとC++で異なるために、extern "C" を使う必要が発生します。~ C++では、同じ名前の関数でもオーバーロードが可能なため、 _a という簡単なラベルではなく、関数名、引数の型 の情報を持ったラベル名が使われます。~ つまり、 B.cpp の a の呼び出し部分は call _acharp のようになるわけです。(これは例で、実際のラベル名は符号化されてぱっと見ても引数の型がわかるようにはなっていませんが。)~ しかし A.c はCモードでコンパイルされているので、関数aのラベルは _a となっており、 _acharp というラベルは見つからずエラーとなります。~ では extern "C" はどのような意味があるかというと、「この関数はCのラベル名で探してね」 とコンパイラに指定する働きがあります。~ このおかげで B.cpp の a の呼び出し部分が call _acharp ではなく call _a となり、めでたく関数aを見つけることができて呼び出せる、というわけです。~ 注)以上を考えると、 extern "C" を使わなくても A.c をC++モードでコンパイルすればいいということに気づきます。実際、拡張子を .cpp に変えるかコンパイルオプションでC++としてコンパイルすれば、 A.h を修正しなくてもすみます。しかしあらかじめコンパイル済みのライブラリなどを使う(または配布する)ときにはそうはいかないので、extern "C" を使います。 *ほんのり多態性の紹介 [#nff24e0f] 例えばこんなクラスを定義(Character.h)したとして class Character { public: // virtual void Say() = 0; // 一身上の都合により純粋仮想関数にはしない virtual void Say() { std::cout << "セリフが無いんじゃよ" << std::endl;}; }; class Hero : public Character { public: void Say() { std::cout << "悪が栄えた試しなし!" << std::endl; } }; class Zako : public Character { public: void Say() { std::cout << "イー!" << std::endl; } }; class Boss : public Character { public: void Say() { std::cout << "やっておしまい!" << std::endl; } }; こんな感じだね。よく初心者サイトに載ってる例は。 でも、これだと、どんなメリットがあるのか良くわかんないんだよね。 #include<iostream> #include "Character.h" using namespace std; int main() { Hero a; Zako b; Boss c; a.Say(); b.Say(); c.Say(); return 0; } 実行例 c:\>Sample 悪が栄えた試しなし! イー! やっておしまい! ちなみに、こんなことはしちゃだめね。~ #include<iostream> #include<list> #include "Character.h" using namespace std; int main() { Hero a; Zako b; Boss c; list<Character> aryCharacter; aryCharacter.push_back(a); aryCharacter.push_back(b); aryCharacter.push_back(c); for(list<Character>::iterator iter = aryCharacter.begin(); iter != aryCharacter.end(); ++iter){ iter->Say(); } return 0; } -listはSTLに含まれるコンテナで、すさまじく簡単に言ってしまえば動的な配列の一つ。{{br}} -iteratorはコンテナにアクセスするためのもので、ポインタみたいに使える。 実行例 c:\>Sample セリフが無いんじゃよ セリフが無いんじゃよ セリフが無いんじゃよ 動的生成すると、ちゃんと動作するんだけど、メモリリーク対策が面倒くさい。 この例ではたった3行だけどね。 #include<iostream> #include<list> #include "Character.h" using namespace std; int main() { Hero a; Zako b; Boss c; list<Character*> aryCharacter; aryCharacter.push_back(new Hero); aryCharacter.push_back(new Zako); aryCharacter.push_back(new Boss); for(list<Character*>::iterator iter = aryCharacter.begin(); iter != aryCharacter.end(); ++iter){ (*iter)->Say(); } for(list<Character*>::iterator jter = aryCharacter.begin(); iter != aryCharacter.end(); ++iter){ delete *jter; } return 0; } 実行例 c:\>Sample 悪が栄えた試しなし! イー! やっておしまい! そしてメモリリーク対策としてスマートポインタ(boostライブラリのshared_ptr)を導入してみる。 (STLにはスマートポインタとしてauto_ptrがあるけど、コンテナに入れることは出来ない)~ できたー! って思ってテストしたらなんか動作がおかしい…もしかしてメモリリーク? って思ったのでは、せっかくの多態性の恩恵も台無しだから、慣れない内・使えない事情が無いならば、使っとけ! #include<iostream> #include<list> #include<boost/shared_ptr.hpp> #include "Character.h" using namespace std; int main() { typedef boost::shared_ptr<Character> pChar; list<pChar> aryCharacter; aryCharacter.push_back(pChar(new Hero)); aryCharacter.push_back(pChar(new Zako)); aryCharacter.push_back(pChar(new Boss)); for(list<pChar>::iterator iter = aryCharacter.begin(); iter != aryCharacter.end(); ++iter){ iter->get()->Say(); } return 0; } 実行例 c:\>Sample 悪が栄えた試しなし! イー! やっておしまい! で、こーなってくると、動的生成部分がもーちょっとなんとかならんかなーて思うんだよね。 戻り値がCharacter*な関数があれば、いろいろ便利になる。 しかし面倒くさくなったので、興味がある人は"factory デザインパターン"(abstractを加えても良い)で ググってくれたまえ! *参考サイト [#rb5082c0] -[[ロベールのC++教室:http://www7b.biglobe.ne.jp/~robe/]]~ -[[Programing Place C++編:http://www.geocities.jp/ky_webid/cpp/language/index.html]]~