C++/C++特有の概念
C++とは[編集]
C++は、1979年、米国ニュージャージー州マレーヒルのベル研究所で、ビャーネ・ストロヴストルップによって開発された、オブジェクト指向プログラミング言語である。 数回の改訂を経て、アレクサンダー・ステパノフによって初期の設計が行われ、その後多くの人々によって改良が加えられた標準テンプレートライブラリ(STL)の作成も行われ、1994年7月のANSI/ISO委員会で最終承認標準化された[1]。
その後も、
- C++98 ISO/IEC 14882:1998
- C++03 ISO/IEC 14882:2003
- C++11 ISO/IEC 14882:2011
- C++14 ISO/IEC 14882:2014
- C++17 ISO/IEC 14882:2017
- C++20 ISO/IEC 14882:2020
- C++23 ISO/IEC 14882:2023(未発行)
と改定をつづけている。
現在、多くのプログラミング初学者が(Cではなく)C++から学び始めます。C++はオブジェクト指向プログラミングの概念や機能を含んでいるため、より高度で効率的なプログラミングを学ぶことができます。
C++の特徴として、以下の点が挙げられます:
- クラスの定義や継承
- C++では、クラスとオブジェクト指向プログラミング(OOP)の概念が導入されています。これにより、データとそれに対する操作をひとまとめにしてコードを構造化できます。また、継承を用いることで既存のクラスを基にして新しいクラスを作成できます。
class Animal { public: void eat() { // 食事の共通処理 } }; class Dog : public Animal { public: void bark() { // 犬独自の処理 } };
- 仮想関数
- 仮想関数を使用することで、派生クラスで基底クラスのメンバ関数を上書きでき、ポリモーフィズムを実現できます。
class Shape { public: virtual void draw() { // 描画処理 } }; class Circle : public Shape { public: void draw() override { // 円を描画する処理 } };
- 関数や演算子のオーバーロード
- C++では、同じ名前の関数や演算子に異なる引数を持たせることができる関数オーバーロードが可能です。これにより、同じ処理を異なる型や引数で使用できます。
int add(int a, int b) { return a + b; } double add(double a, double b) { return a + b; }
- テンプレート
- テンプレートを使用することで、ジェネリックプログラミングが可能になります。同じ処理を異なるデータ型に対して使い回すことができます。
template <typename T> T add(T a, T b) { return a + b; }
- STL (Standard Template Library)
- STLは、C++に組み込まれた標準のデータ構造やアルゴリズムのライブラリです。これには、ベクターやリストなどのデータ構造、ソートや検索などのアルゴリズムが含まれています。
#include <vector> #include <algorithm> int main() { std::vector<int> numbers = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3}; std::sort(numbers.begin(), numbers.end()); // 他にも様々なSTLの機能が利用可能 return 0; }
- 例外処理
- C++では例外処理をサポートしており、プログラムの実行中に発生するエラーに対処できます。
try { // 例外が発生する可能性のある処理 throw std::runtime_error("An error occurred"); } catch (const std::exception& e) { // 例外がキャッチされたときの処理 std::cerr << "Exception: " << e.what() << std::endl; }
- 名前空間
- 名前空間を使用することで、同じ名前の要素が衝突するのを避けることができます。
namespace Math { int add(int a, int b) { return a + b; } }
- 型推論
- C++11以降では
auto
キーワードを使用して変数の型を自動的に推論することができます。 auto x = 5; // int型として推論 auto y = 3.14; // double型として推論 auto str = "Hello"; // const char*型として推論
- ラムダ関数
- ラムダ関数を使用すると、無名関数を簡潔に表現できます。
auto sum = [](int a, int b) { return a + b; };
ただし、C言語を学ぶことは、プログラミングの基本的な概念やメモリ管理に関する理解を深める助けになります。C++はC言語の機能を拡張しているため、C++を学びながらC言語の基礎も理解することは、より広範で深いプログラミングのスキルを身につける一助となります。
CとC++の相違点[編集]
要点[編集]
- Cでは引数がない関数の関数原型の引数のリストにはvoidを指定する。一方、C++ではvoidの指定を省略できる。
- Cでは関数原型の記述は推奨されるが任意である。一方、C++では必ず記述しなければならない。
- Cでは関数の戻り値の型がvoid以外の時でもreturn文で値を指定しなくともよい。一方、C++では必ず指定しなければならない[2]。
- Cでは関数の戻り値の型を省略すると整数型が使われる。一方、C++では必ず型を指定しなければならない。
- Cではかつてローカル変数の宣言はブロックの先頭で行う必要があった(この制限は、C99 で撤廃された)。一方、C++ではどこで行ってもよい。
- Cではbool型が標準設定には無い(C99で_Bool型がキーワードに追加されたが、ヘッダー stdbool.h をインクルードする必要がある)。一方、C++では何らヘッダーをインクルードすることなくbool型を使える。
Cの基礎知識については、『C言語/基礎知識』を参照してください。 ただしprintf関数とscanf関数については、後述する#コンソール入出力で、より改善されたとC++開発者が主張する方法を用いるため、学習する必要はあまりありません。
Hello, World!を実行する[編集]
はじめてソースコードを書くときは、 まず「hello, world」プログラムと呼ばれる 画面に「hello, world(改行)」とだけ表示するプログラムを、 書くことにしましょう。
C++はCを拡張して作られているため、 Cと似た部分が多くあります。 例えば、次のCで書かれたプログラムはC++でも正しいプログラムです。 このプログラムの解説はC言語/はじめに#Hello, World!を実行するを参照してください。
#include <stdio.h>
int main(void)
{
printf("hello, world\n");
return 0;
}
しかし、C++では、一部機能が追加されて、また入出力はより直観的な方法を使うことができるようになりました。 以下は同じ内容をC++の機能を用いて書き直したものです。
#include <iostream>
using namespace std;
int main()
{
cout << "hello, world" << endl;
return 0;
}
ここではこのソースコードについて簡単に説明します。
- 1行目は、新しいスタイルのヘッダです。
Cでは#include <stdio.h>
のようにファイル名を指定しましたが、これは古いスタイルのヘッダで、C++では新しいスタイルのヘッダを使い、標準識別子を指定します。新しいスタイルのヘッダは、ファイル名ではないので「.h」拡張子がありません。古いスタイルのヘッダは、引き続き使用できますが、推奨されません。標準Cヘッダを新しいスタイルで書くと、頭にcが付きます。例えば、#include <stdio.h>
は#include <cstdio>
となります。 - 2行目は、std名前空間で定義された識別子(この場合は
cout
及びendl
)をstd::
前置句なしに参照可能にしています。 - 3行目は、任意の空白行です。
- 4行目から8行目は、main関数の定義です。
- 5行目から8行目は、ブロックというまとまりです。
- 6行目は、出力演算子による出力操作です。
Cではコンソール出力に、printf関数を用いましたが、C++では「<<
演算子」を使って、改善された方法で出力を行うことができます。Cでは<<
は左シフト演算子でしたが、C++ではその機能に加えて、出力演算子として用いることができます。これは後で解説する演算子のオーバーロードの例でもあります。 - 7行目は、return文です。
- 6と7行目が、空白によって右側に偏っているのは、インデントのためです。
オブジェクト指向との関連[編集]
クラスを用いたプログラムをオブジェクト指向プログラムと呼びます[3]。 そのため、上のC++のhello, worldの例はオブジェクト指向プログラムの例です。 この例で用いられているオブジェクト指向について簡単にまとめます。
この例では、オブジェクト指向を導入した結果として、
関数printfを、文字列 "hello, world" を引数として呼出す
という考えから、
cout に、文字列 "hello, world" を送る
という考えへの移行が行われています。
この cout はストリーム操作を抽象化したものですが、この様な抽象化された実体をオブジェクトと呼びます。 例えば、Cでは標準出力に出力する時には次のようにprintf関数を使いました。
printf("abc");
一方、ファイルに出力する時には次のようにfprintfという異なる関数を使う必要があります。
fprintf(f, "abc"); // fはFILE型へのポインタ
これは、扱う対象が異なっている以上当然ですが、出力する対象の数が増えて来るとわかりづらくなります[4]。
一方、C++を用いると、標準出力に対しては、
std::cout << "abc";
ファイルに対しても、
fs << "abc"; // fsはファイルストリーム
となり、同じ名前の関数 operator<< を用いることが出来ます。
ここで、 operator<< は、引数のオブジェクト(実際にはオブジェクトが所属するクラス)によって動作が変化していることが重要です。 このように、演算子を含む関数がオブジェクトの種類によって振舞いを変えることで、様々なデータに対する操作を統一的に扱うことが出来ます。
演算子のオーバーロード[編集]
C++では、演算子のオーバーロードによって定義型に演算子を追加することができます。
cout << "abc";
- は
operator<<(cout, "abc");
という関数呼出しと等価です(実際に動くコードです)。
演算子のオーバーロードは、「新しい数値型」を定義する場合などでは素晴らしい効果を発揮しますが、ファイルストリームにシフト演算子を適用するようなケースは「予め了解された体系」ではないので賛否両論があります。
オブジェクト指向[編集]
オブジェクト指向プログラミング(Object-Oriented Programming : OOP)は、 コンピュータプログラムの構造、構成法の一つで、 関連するデータの集合体と、 それを操作する手続きを「オブジェクト」(object)と呼ばれるひとまとまりの単位として一体化し、 オブジェクトの組み合わせとしてプログラムを記述する手法である。 [5]
プログラミングでは、どんどん複雑になっていくプログラムに対応するために、新しい手法が次々と作られてきた。
最初は、フロントパネルのスイッチをオンオフすることによって、ごく短いプログラムが作成された。
次に、アセンブリ言語によって、より長いプログラムが作成できるようになった。
そして、1950年代、最初の高レベル言語(FORTRAN)が開発されたことによって、数千行にわたるプログラムが作成できるようになった。 しかし、何でも許される方式であったため、大きなプログラムを書こうとすると、読みづらく保守しにくい「スパゲティコード」となってしまった。
1960年代には、構造化プログラミング言語(structured programming language)の登場で、スパゲティコードを排除し、五万行に及ぶプログラムを作成し、保守できるようになった。
Cも広い意味では構造化プログラミング言語である。構造化プログラミング言語では、制御構造を使い、プログラムを構成要素に分割して記述する。
しかし、構造化プログラミング言語も、プログラムの長さが一定に達すると限界に達するだろうと危惧されるようになった(しかし現実は2020年でも、たとえばLinuxのカーネルは標準C言語で記述されているが)。
オブジェクト指向プログラミングでは、前述したとおり、プログラムを自己完結したオブジェクトとして記述することで、複雑さを軽減し、より巨大なプログラムを記述できるようにした。
C++におけるオブジェクト指向プログラミングには、カプセル化、ポリモーフィズム、継承の3つの特色がある。 これらについて説明していく。
カプセル化[編集]
カプセル化(encapsulation)とは、 データとそれを操作する手続きを一体化して「オブジェクト」として定義し、 オブジェクト内の細かい仕様や構造を外部から隠蔽することである。 [6]
手続きとデータを一体化し、手続きによってしかデータを操作できないようにすることで、ブラックボックスなオブジェクトを作成し、独立性を高めている。
さらにC++におけるカプセル化では、データと手続きを、非公開(private)と公開(public)に分けて、 オブジェクトの外部からは、非公開のものにはアクセスできず、公開のもののみにアクセスできるようにしている。
カプセル化によって、保守性や開発効率を挙げることができ、コードの再利用も簡単になる。
ポリモーフィズム[編集]
ポリモーフィズム(polymorphism、多相性、多態性、多様性)とは、同名のメソッドや型などをオブジェクトの種類に応じて使い分けることができる性質のことである[7]
ポリモーフィズムでは、同一の名前で複数の異なる動作を作成し使うことができる。
例えば、Cで絶対値を求めるには、引数のデータ型に合わせて abs(), labs(), llabs(), fabsf(), fabs(), fabsl(), cabsf(), cabs(), cabsl() の9つの関数を使い分ける必要がある[8]。 一方、C++で絶対値を求めるには、引数がどのデータ型であっても std::abs() という1つの関数で求めることができる。
このように1つの関数名を複数の異なる目的で使うことを、関数のオーバーロード(function overloading)という。
ポリモーフィズムの利点は、メソッド名を統一することで記述ミスを減らせるなどが挙げられる。
継承[編集]
継承(inheritance)とは、 既に定義されているクラスをもとに、拡張や変更を加えた新しいクラスを定義することである。 [9]
継承を使うことで、 あるものの性質を受け継ぎ独自の機能を追加することができる。
例えば、哺乳類を継承し人、犬、猫を新たに作るといった考え方ができる。
関数などによってもコードの再利用は可能だが、さらに継承によって、より自由にコードの再利用ができるようになる。
継承には「カプセル化を破壊する」との批判もあり、継承あるいは仮想メンバー関数のオーバーライドを禁止する修飾子 final
が生まれた。
より新興のオブジェクト指向言語 Kotlin では、class は open 修飾子で明示的に継承を許可されなければ、継承することが出来ない。
脚注[編集]
- ^ The Standard Template Library Alexander Stepanov October 31, 1995
- ^ main()の戻り値がintであった場合に限り省略可能で return 0; が仮定される。
- ^ オブジェクト指向プログラミングには、クラスを用いずプロトタイプを用いる別の手法もあります。
- ^
printf("abc");
- は
fprintf(stdout, "abc");
- と等価で、他にも sprintf() などもあり、手続き型言語なりの派生体系が作られています。
- そもそも型FILEは、内部構造の隠蔽が図られている考えられています。
- ^ オブジェクト指向プログラミングとは|OOP|Object Oriented Programming - 意味/定義 : IT用語辞典
- ^ カプセル化とは|encapsulation - 意味/定義 : IT用語辞典
- ^ ポリモーフィズムとは|polymorphism|ポリモルフィズム|多相性 - 意味/定義 : IT用語辞典
- ^ C言語でも、C99から総称型数学関数<tgmath.h>を使えば引数の型に応じて対応する数学関数が呼び出される。
- ^ 継承とは|inheritance|インヘリタンス - 意味/定義 : IT用語辞典
参考文献[編集]
- 神林靖 監修 Herbert Schildt著『独習C++ 第3版』トップスタジオ訳、翔泳社、2005年11月05日 第3版第10刷発行