突然C++が必要になったので付け焼き刃した(基本編)
業務で急にC++需要が(自分の中で)出てきたので、ヘッダファイルとかサンプルコードの内容が理解できる程度には基礎を身につけるべく付け焼き刃することにした。
私はデベロッパーではないので、基本的に自分で何かコードを書くことはないのだけれども、CAEエンジニアというのは往往にしてプログラミング言語をはじめ色々な知識・経験が求められるのである。
背景
C++は、学生時代にオブジェクト指向を学ぼうと思って取り組んで一瞬で挫折した、個人的にはあまり近寄りたくない言語である。
なんたって、いわゆる "Hello World!" がこれである:
#include <iostream>
int main(){
std::cout << "Hello World!" << std::endl;
return 0;
}
選んだ書籍が良くなかったのもあるが、Cを教養程度に身につけただけの当時の私には刺激が強すぎた。C++はCを拡張したものである、はずなのにprintf()の影も形もないって拡張しすぎだろお前。
かくしてC++のことは忘れることにしていたのだが、業務で扱っているアプリのユーザーサブルーチンの使い方を理解するのにC++の知識が前提として必要になってきた。トラウマを克服すべく、C++をマジで勉強することにする。
(どうでもいいのだけど、最近は「本気」を「ガチ」ということが多くて、「マジ」と言うことがなくなった気がする)
参考サイト探し
ありがたいことに、世の中で人気の言語に関しては、学習のための参考サイトを誰かが作ってくれていることが多い。Pythonについて勉強したいと思ったら「Python 学習」とかググれば、初学者向けの情報が沢山得られる。
しかしながら、「C++ 学習」でググったら、一番トップに現れたのがこれだ(さすがに笑った):
この時点で、Google先生お墨付きで「C++は独学は難しい」と言っているようなものである。蘇るトラウマ。
しかしここで諦めてはいけない。どうにか自分を奮い立たせて、Google検索結果の次を表示すると、こちらである:
なんと心強いタイトルだろうか。成功でも失敗でも、とにかく一週間で終えられるのはありがたい。ということで、今回はまずこちらのサイトの内容を一通りこなしていくことに決めた。
で、基本編を一通り終えた現時点の結論としては、非常に良かった。
とてもよくまとまっていて、解説やサンプルコードも丁寧で、演習もこなすことで理解が深まる。
少なくとも、C++に対する漠然とした苦手意識についてはほぼ払拭できた。自分でもびっくりしている。
そういうわけで、学習した内容を頭の中にとどめておくために、この場で各ページの内容を簡単におさらいしていこうと思う。
なお「一週間で」となっているが、三日間程度で終えられてしまった。
基本編0日目: C++言語とは
よくあるC++の概要の説明と、オブジェクト指向について。
正直、オブジェクト指向に関する説明はだいたいどこも似たり寄ったりで、よくわからない。言葉の説明だけで初学者にすんなり理解できるものでは無いように思う。
この時点では、「クラス」と「オブジェクト(インスタンス)」の用語の使い分け(クラスからインスタンスを生成して使う)と、クラスには変数だけでなく関数を持たせることができて、このようなクラス内の関数を「メソッド」と呼ぶ、程度のことが頭に入れば良いのではないかと思う。
基本編1日目: 名前空間
初心者殺しのHello World登場。
ただ、自分が知っていたC++のHello Worldとは少し違い、コンソール出力行がちょっとすっきり:
#include <iostream>
using namespace std;
int main(){
cout << "HelloWorld." << endl;
return 0;
}
ここでnamespaceとはなんぞや?coutやendlとはなにか?また << の役割は?といったところがわかりやすく説明されており、ようやく初見殺しHello Worldの仕組みを感覚的に理解できた。最初からこの説明に出会っていたら、挫折しなくて済んだかもしれない。
coutは、コンソール出力を指す特殊な変数と思えば良い。
これに対して、出力したい情報を<<によって渡している(左向き矢印)。
複数の情報を続けて渡すこともできて、上記では"HelloWorld."という文字列に続けてさらにendlを送っている。
endlは、改行を示す特殊な変数と思えば良い。
なので、「コンソールに、"HelloWorld."+(改行)を出力する」という操作に相当する。
またcoutおよびendlは何由来の変数かというと、ヘッダ <iostream>のインクルードによって使用可能になる。ただしこの際、名前空間を明確にする必要がある。
coutとendlは、stdという標準名前空間にて使用可能な変数であるので、using namespace stdによって、std名前空間を使用することを事前に宣言することで使用可能になる。
名前空間の指定方法はもう一つあり、::を使う方法がある。この場合は、using namespace stdを使用せずに、std::coutやstd::endlのように記述することで同じ挙動となる。
昔読んだ本では、::による名前空間の指定を「おまじない」としてあまり説明せずに済ませていたのでとても受け入れ難かったが、今回これを知ってかなりスッキリした。
基本編2日目: クラス
オブジェクト指向の要ともいえるクラスについて。
構造体との比較の話題が個人的には非常にわかりやすく目からウロコだったので引用:
C++言語におけるクラスは、その構造だけを見ると、C言語の構造体とよく似ています。構造体は、複数の変数を1つにまとめ たものでした。配列と違い、それぞれの変数はデータ型が異なっても構いません。
それに対し、クラスは、構造体の中に、さらに関数まで加えたものだといえばわかり易いでしょう。つまり、メンバとして変数と関数の両方を含めることができるという構造です。
また、この章で初めてソースコードが複数に分かれる。クラスの宣言・定義をするヘッダファイルと、クラスの内容を実装するソースファイルと、クラスを利用した実際の処理を記述するメインファイルの3種類を以後扱っていく。
これら各ファイルの依存関係(役割分担)が明確になったことで、既存コードをどうやって読んだらいいかがかなりわかりやすくなった。
メンバ関数(メソッド)実装の際には、ClassName::methodName()のようにクラス名をつけるのを忘れないように・・・。
基本編3日目: アクセス指定子
クラス定義の際に指定するpublic, private, protectedについて。
クラス(インスタンス)の外部から直接利用できるようにしたいものはpublic指定。なのでメソッドは基本的にはpublicとなる場合が多い。
クラス(インスタンス)の外部から直接利用されたくないものはprivate指定。メンバ変数は基本的にはprivateとする場合が多い。
結果、private指定したメンバは、そのクラス内のメソッドからしかアクセスできなくなる。これをカプセル化と呼ぶらしい。
クラスの外部から間接的にメンバ変数にアクセスできるように、セッターやゲッターと呼ばれるメソッドを用意することも多い。
クラス内の変数に外部から直接値を代入するのではなく、クラス内のprivate変数に値を代入するpublicメソッド(セッター)を用意しておくと、クラス外部からはこのpublicメソッドを介してprivate変数に値を代入することができる。
また、クラス内の変数を外部で直接参照するのではなく、クラス内のprivate変数を返り値として戻すようなpublicメソッド(ゲッター)を用意しておくと、クラス外部からはこのpublicメソッドを介してprivate変数の値を取得できる。
stringはいわゆる「string型」という印象があるがC++における実体は「stringクラス」なので、ユーザー定義クラスと同じようにstringクラスにもメソッドが用意されている。例えばlength()メソッドにより、その文字列の長さ(文字数)を取得できる。またappend()メソッドにより、引数に与えた文字列を後ろに追加できる。
基本編4日目: 生成と消去
いわゆるコンストラクタとデストラクタの解説。
あるクラスからインスタンスが生成されるときに自動的に実行されるメソッドがコンストラクタ。クラス名と同じ名前で、返り値が無い(void指定もなし)メソッドとして定義する。
あるインスタンスが削除されるときに自動的に実行されるメソッドがデストラクタ。クラス名の先頭に~を追加した名前のメソッドとして定義する。コンストラクタと同様、返り値無し、void指定も無し。
インスタンスの生成と削除を任意に行いたい場合は、newやdeleteを使う。
ここまで、インスタンスの生成は以下のように行ってきたが、
SomeClass sc;
newを使う場合は、まずクラスのポインタを生成(0で初期化)して、そのポインタに対してnew コンストラクタにより生成されるインスタンスのアドレスを適用する:
SomeClass* pSc = 0;
pSc = new SomeClass();
この場合、pScのメソッドを利用する際はアロー演算子(->)を使う:
pSc -> m_func();
基本編5日目: 静的メンバ
クラス内に定義したメンバ変数やメンバ関数は、基本的にインスタンスを生成しないと使用できないが、静的メンバという形式で定義するとインスタンス生成無しで使える。
初心者的には、静的っていう名前が誤解を招くというか、良くないよね(ただの文句)。あるクラス内(NOT インスタンス内)で共通に使えるメンバ、ということでグローバル変数みたいな扱いが可能になるもの。これまでのようにインスタンス生成して初めて使えるのはインスタンスメンバ。
静的メンバ変数/関数の定義は、staticを最初につけることで行う。
静的メンバ変数の初期値は、ソースファイルにてint ClassName::var=0のように与えるのが一般的。
静的メンバ関数を使用(呼び出し)するときは、ClassName::func()のようにする。
基本編6日目: 継承
オブジェクト指向といえば、継承。基本となる親クラスを用意しておいて、それを継承した上で機能を追加した子クラスを生成することができる。このとき、追加部分のみを記述すれば良いので効率的。
3日目で一旦脇に置かれていたアクセス修飾子protectedが登場。protectedメンバは、継承したサブクラスでは利用可能だが外部からは利用できない、という位置づけとなる。
C++の特徴として多重継承が可能だが、親クラスが複数あると色々と難しくなるので基本的には単一継承を推奨。
なお継承を用いる場合は、デストラクタはvirtual指定にするのが原則らしい。理由は今の時点では難しいらしいのでとりあえずおまじないで。
基本編7日目: ポリモーフィズム
オーバーロードとオーバーライド。言葉は似てるけどけっこう違う。
オーバーロードは、あるクラス内に同じ名前のメンバ関数が複数定義されていること。ただし、引数の内容や返り値の型は異なる。これによって、引数の与え方を変えることで同じメソッド名でもそれぞれに対応した処理内容を扱うことができる。(intを2つ与えたら足し算した値をintで返す、stringを2つ与えたら1つに結合したstringを返す、とか)
オーバーライドは、親クラスで定義されているメソッドの内容を、子クラスで上書きすること。引数の内容や返り値の型は同一。
オーバーロードはコンストラクタにも適用可能だが、注意点がある。引数ありのコンストラクタを定義した場合は、必ず引数なしのコンストラクタ(デフォルトコンストラクタ)も定義されていなくてはならない。デフォルトコンストラクタのみの場合は、省略可能。
次なる一週間へ
基本編を一通り学習したので、このあとは同じサイトの応用編に取り組んでいく。
追伸:
ハッシュタグつけようと思ったら、#c++はNG(記号は使えないらしい)だわ、#cxxは誰もまだ使ってないわ、ほんとpythonとの格差すごい感。(#cppはいくらか使われてるみたいだけどC++の話題なのか不明)