C++は、1979年にBjarne Stroustrupによって開発されたプログラミング言語です。元々は「C with Classes」という名前で、C言語にオブジェクト指向の機能を追加したものでした。その後、様々な機能拡張を経て、現在のC++となりました。
歴史:
- 1979年: Bjarne Stroustrupが「C with Classes」を開発開始。
- 1983年: C++と改名。
- 1998年: C++98が最初のISO/IEC標準として承認。
- 2003年: C++03が軽微な修正版としてリリース。
- 2011年: C++11が大幅な機能拡張を伴いリリース (ラムダ式、autoキーワード、範囲for文など)。
- 2014年: C++14がC++11へのマイナーアップデートとしてリリース。
- 2017年: C++17がさらに機能拡張 (constexpr if、インライン変数など) を伴いリリース。
- 2020年: C++20がコルーチン、コンセプトなどの大規模な機能拡張を伴いリリース。
- 以降: 3年ごとに新しい標準がリリースされる予定 (C++23, C++26…)
特徴:
- マルチパラダイム: オブジェクト指向プログラミング、手続き型プログラミング、ジェネリックプログラミングなど、複数のプログラミングパラダイムをサポートします。
- 高いパフォーマンス: C言語をベースとしており、低レベルな操作も可能であるため、パフォーマンスが重要なアプリケーションに適しています。
- 豊富なライブラリ: 標準テンプレートライブラリ(STL)をはじめ、様々なライブラリが利用可能です。これにより、効率的な開発が可能です。
- ハードウェア制御: メモリ管理やハードウェア制御など、低レベルな操作が可能です。
- 幅広い用途: オペレーティングシステム、ゲーム開発、組み込みシステム、科学技術計算など、幅広い分野で使用されています。
- C言語との互換性: C言語で書かれたコードをC++で利用したり、C++のコードをC言語から呼び出したりすることが比較的容易です。(ただし、完全な互換性はありません。)
- 静的型付け: コンパイル時に型チェックが行われるため、実行時のエラーを減らすことができます。
メリット:
- 高いパフォーマンス: 処理速度が速く、リソース効率が良い。
- 柔軟性: 様々なプログラミングパラダイムに対応できる。
- 豊富なライブラリ: 開発効率を向上させることができる。
- 幅広い分野での応用: 様々なアプリケーション開発に利用できる。
デメリット:
- 学習コストが高い: 複雑な機能が多く、習得に時間がかかる。
- メモリ管理が難しい: ポインタを扱うため、メモリリークなどの問題が発生しやすい。
- コンパイル時間が長い: 特に大規模なプロジェクトではコンパイルに時間がかかることがある。
C++は強力なプログラミング言語ですが、その分学習コストも高いです。しかし、そのパワーと柔軟性から、多くの分野で必要とされています。
C++でプログラミングを始めるには、開発環境を構築する必要があります。主に必要なものは、コンパイラとIDE(統合開発環境)です。
1. コンパイラ
コンパイラは、C++で書かれたソースコードをコンピュータが実行できる機械語に翻訳するソフトウェアです。代表的なC++コンパイラには以下のようなものがあります。
- GCC (GNU Compiler Collection): クロスプラットフォーム対応のフリーなコンパイラ。Linux、macOS、Windowsなど、様々なOSで利用できます。
- Clang: LLVMプロジェクトの一部として開発されているコンパイラ。GCCよりもコンパイル速度が速いとされています。macOSでは標準のコンパイラとして採用されています。
- Microsoft Visual C++ (MSVC): Microsoftが提供するコンパイラ。Windows環境での開発によく使われます。Visual Studioに付属しています。
コンパイラのインストール方法:
-
Linux: 多くのディストリビューションでは、パッケージマネージャーを使ってGCCやClangを簡単にインストールできます。例:
sudo apt install g++
(Debian/Ubuntu),sudo yum install gcc-c++
(CentOS/Red Hat) -
macOS: Xcode Command Line Toolsをインストールすることで、Clangが利用できるようになります。
xcode-select --install
- Windows: Visual Studioをインストールするか、MinGW-w64 (GCCのWindows移植版) をインストールします。
コンパイラの確認:
インストール後、ターミナルやコマンドプロンプトでコンパイラのバージョンを確認することで、正しくインストールされているか確認できます。例:g++ --version
(GCC), clang --version
(Clang), cl
(MSVC, Visual Studio Developer Command Promptで実行)
2. IDE (統合開発環境)
IDEは、コードの編集、コンパイル、デバッグなどを統合的に行うことができるソフトウェアです。C++開発でよく使われるIDEには以下のようなものがあります。
- Visual Studio (Microsoft): Windows環境で最も一般的なIDE。C++開発に必要な機能が揃っており、デバッグ機能も強力です。有料版と無料版 (Community) があります。
- CLion (JetBrains): クロスプラットフォーム対応の有料IDE。CMakeベースのプロジェクトをサポートしており、高度なコード解析機能やリファクタリング機能が充実しています。
- Eclipse CDT: フリーなクロスプラットフォームIDE。プラグインを追加することで、様々な機能を追加できます。
- Code::Blocks: フリーなクロスプラットフォームIDE。軽量で使いやすく、初心者にもおすすめです。
- Visual Studio Code (Microsoft): 軽量なテキストエディタですが、C++拡張機能をインストールすることで、IDEのような機能を利用できます。
IDEの選択:
IDEの選択は、開発するアプリケーションの種類や個人の好みに大きく依存します。
- Windows環境: Visual Studioが最も一般的でおすすめです。
- クロスプラットフォーム: CLion、Eclipse CDT、Code::Blocksなどが適しています。
- 軽量なエディタ: Visual Studio CodeにC++拡張機能をインストールするのも良い選択肢です。
IDEの設定:
IDEをインストールしたら、コンパイラの設定を行う必要があります。IDEの設定画面で、インストールしたコンパイラのパスを指定します。
簡単なプログラムのコンパイルと実行:
-
IDEで新しいプロジェクトを作成します。
-
ソースコードファイル (例:
main.cpp
) を作成し、簡単なC++コードを記述します。#include <iostream> int main() { std::cout << "Hello, World!" << std::endl; return 0; }
-
IDEのビルド機能を使ってコンパイルします。
-
IDEの実行機能を使ってプログラムを実行します。
“Hello, World!” が表示されれば、開発環境の構築は成功です。
C++プログラミングの基本となる、変数、データ型、演算子について解説します。
1. 変数
変数は、データを格納するための名前付きの記憶領域です。変数を使用する前に、型を指定して宣言する必要があります。
変数の宣言:
型 変数名;
型 変数名 = 初期値;
例:
int age; // int型の変数ageを宣言
int score = 100; // int型の変数scoreを宣言し、初期値100を代入
double pi = 3.14; // double型の変数piを宣言し、初期値3.14を代入
std::string name = "太郎"; // string型の変数nameを宣言し、初期値"太郎"を代入
変数の命名規則:
- 英字、数字、アンダースコア (_) を使用できます。
- 先頭は英字またはアンダースコアである必要があります。
- 大文字と小文字は区別されます(
age
とAge
は異なる変数)。 - 予約語 (例:
int
,class
,return
) は使用できません。 - 変数名は、その変数の役割を表すようにするのが良い習慣です。
2. データ型
データ型は、変数がどのような種類のデータを格納できるかを指定します。C++には様々なデータ型があります。
基本的なデータ型:
-
int
: 整数型。-2147483648 ~ 2147483647の範囲の整数を格納できます (環境によって範囲が異なる場合があります)。 -
float
: 浮動小数点数型。単精度浮動小数点数を格納できます。 -
double
: 浮動小数点数型。倍精度浮動小数点数を格納できます。float
よりも精度が高いです。 -
char
: 文字型。1文字を格納できます。 -
bool
: 真偽値型。true
(真) またはfalse
(偽) のいずれかを格納できます。 -
std::string
: 文字列型。文字列を格納できます。std::string
を使うには、#include <string>
が必要です。
修飾子:
int
型には、short
, long
, unsigned
などの修飾子を付けて、表現できる値の範囲を変更できます。
-
short int
: 通常、int
よりも小さい範囲の整数を格納できます。 -
long int
: 通常、int
と同じか、それよりも大きい範囲の整数を格納できます。 -
unsigned int
: 負の値を持たない整数を格納できます。
例:
short int smallNumber = 10;
long int largeNumber = 1000000000;
unsigned int positiveNumber = 20;
3. 演算子
演算子は、変数や値に対して様々な操作を行う記号です。
算術演算子:
-
+
: 加算 -
-
: 減算 -
*
: 乗算 -
/
: 除算 -
%
: 剰余 (余り)
例:
int x = 10 + 5; // xは15になる
int y = 20 - 3; // yは17になる
int z = 4 * 6; // zは24になる
int a = 10 / 2; // aは5になる
int b = 10 % 3; // bは1になる
比較演算子:
-
==
: 等しい -
!=
: 等しくない -
>
: より大きい -
<
: より小さい -
>=
: 以上 -
<=
: 以下
例:
bool result1 = (5 == 5); // result1はtrueになる
bool result2 = (10 != 5); // result2はtrueになる
bool result3 = (8 > 3); // result3はtrueになる
論理演算子:
-
&&
: 論理積 (AND) -
||
: 論理和 (OR) -
!
: 論理否定 (NOT)
例:
bool result4 = (true && true); // result4はtrueになる
bool result5 = (true || false); // result5はtrueになる
bool result6 = !true; // result6はfalseになる
代入演算子:
-
=
: 代入 -
+=
: 加算代入 (例:x += 5
はx = x + 5
と同じ) -
-=
: 減算代入 -
*=
: 乗算代入 -
/=
: 除算代入 -
%=
: 剰余代入
インクリメント/デクリメント演算子:
-
++
: インクリメント (値を1増やす) -
--
: デクリメント (値を1減らす)
例:
int i = 5;
i++; // iは6になる (後置インクリメント)
++i; // iは7になる (前置インクリメント)
int j = 10;
j--; // jは9になる (後置デクリメント)
--j; // jは8になる (前置デクリメント)
これらの基本構文を理解することで、C++プログラミングの基礎を築くことができます。
プログラムの実行フローを制御するための、条件分岐と繰り返しについて解説します。
1. 条件分岐
条件分岐は、特定の条件が満たされるかどうかによって、異なるコードブロックを実行する構造です。C++では、if
文、else if
文、else
文、switch
文が条件分岐に使用されます。
if
文:
最も基本的な条件分岐です。
if (条件式) {
// 条件式がtrueの場合に実行されるコード
}
else
文:
if
文の条件式がfalseの場合に実行されるコードブロックを指定します。
if (条件式) {
// 条件式がtrueの場合に実行されるコード
} else {
// 条件式がfalseの場合に実行されるコード
}
else if
文:
複数の条件を順番に評価し、最初にtrueになった条件に対応するコードブロックを実行します。
if (条件式1) {
// 条件式1がtrueの場合に実行されるコード
} else if (条件式2) {
// 条件式1がfalseで、条件式2がtrueの場合に実行されるコード
} else {
// すべての条件式がfalseの場合に実行されるコード
}
例:
int score = 80;
if (score >= 90) {
std::cout << "A評価" << std::endl;
} else if (score >= 80) {
std::cout << "B評価" << std::endl;
} else if (score >= 70) {
std::cout << "C評価" << std::endl;
} else {
std::cout << "D評価" << std::endl;
}
switch
文:
変数の値に応じて、複数の処理の中から1つを選択して実行します。
switch (変数) {
case 値1:
// 変数が値1の場合に実行されるコード
break;
case 値2:
// 変数が値2の場合に実行されるコード
break;
default:
// どの値にも一致しない場合に実行されるコード
break;
}
注意点: switch
文では、各case
の最後にbreak
文を記述する必要があります。break
文がない場合、一致したcase
から下のcase
の処理も実行されてしまいます (フォールスルー)。
例:
int day = 3;
switch (day) {
case 1:
std::cout << "月曜日" << std::endl;
break;
case 2:
std::cout << "火曜日" << std::endl;
break;
case 3:
std::cout << "水曜日" << std::endl;
break;
default:
std::cout << "その他の曜日" << std::endl;
break;
}
2. 繰り返し
繰り返しは、同じコードブロックを複数回実行する構造です。C++では、for
文、while
文、do-while
文が繰り返しに使用されます。
for
文:
初期化、条件式、更新式を記述することで、特定の回数だけコードブロックを繰り返します。
for (初期化; 条件式; 更新式) {
// 繰り返されるコード
}
例:
for (int i = 0; i < 10; i++) {
std::cout << i << std::endl; // 0から9まで出力
}
while
文:
条件式がtrueの間、コードブロックを繰り返します。
while (条件式) {
// 繰り返されるコード
}
例:
int i = 0;
while (i < 5) {
std::cout << i << std::endl;
i++; // iをインクリメントしないと無限ループになる
}
do-while
文:
while
文と似ていますが、コードブロックを少なくとも1回は実行します。条件式はコードブロックの実行後に評価されます。
do {
// 繰り返されるコード
} while (条件式);
例:
int i = 5;
do {
std::cout << i << std::endl;
i++;
} while (i < 3); // このループは一度だけ実行される (i=5が出力される)
break
文とcontinue
文:
-
break
文: ループを強制的に終了します。 -
continue
文: 現在のループのイテレーションをスキップし、次のイテレーションに進みます。
例:
for (int i = 0; i < 10; i++) {
if (i == 5) {
break; // iが5になったらループを終了
}
if (i % 2 == 0) {
continue; // iが偶数の場合、coutをスキップして次のイテレーションへ
}
std::cout << i << std::endl;
} // 1, 3が出力される
条件分岐と繰り返しを組み合わせることで、複雑なプログラムのロジックを記述することができます。ループの終了条件や分岐条件を適切に設定することが、プログラムの正しい動作に不可欠です。
関数は、特定の処理をまとめた再利用可能なコードブロックです。C++では、関数を定義し、必要に応じて呼び出すことで、コードの可読性と再利用性を高めることができます。
1. 関数の定義
関数の定義は、関数の名前、引数、戻り値の型、そして関数の処理内容(関数本体)を指定します。
基本的な構文:
戻り値の型 関数名(引数1の型 引数1の名前, 引数2の型 引数2の名前, ...) {
// 関数の処理内容
return 戻り値; // 戻り値がない場合は、return; または return void;
}
-
戻り値の型: 関数が返す値の型を指定します。値を返さない場合は
void
を指定します。 - 関数名: 関数を識別するための名前です。変数名と同様に命名規則があります。
-
引数: 関数に渡される値です。引数の型と名前を指定します。引数がない場合は、
( )
の中を空にします。 -
関数本体: 関数が実行する処理を記述します。
{}
で囲みます。 -
return
文: 関数から値を返すために使用します。戻り値の型がvoid
の場合は、return;
またはreturn void;
で関数を終了します。
例:
// 2つの整数を足し合わせる関数
int add(int x, int y) {
int sum = x + y;
return sum;
}
// Hello, World! を表示する関数 (戻り値なし)
void printHelloWorld() {
std::cout << "Hello, World!" << std::endl;
}
2. 関数の呼び出し
関数を呼び出すには、関数名と引数を指定します。
基本的な構文:
関数名(引数1, 引数2, ...);
例:
int result = add(5, 3); // add関数を呼び出し、戻り値をresultに代入
std::cout << result << std::endl; // resultの値 (8) を出力
printHelloWorld(); // printHelloWorld関数を呼び出す
3. 引数
関数に渡される値を引数と呼びます。引数には、値渡し、参照渡し、ポインタ渡しなどの方法があります。
-
値渡し: 引数のコピーが関数に渡されます。関数内で引数の値を変更しても、呼び出し元の変数の値は変更されません。
void increment(int x) { x++; // xは関数内のローカル変数なので、呼び出し元の変数は変更されない } int main() { int num = 10; increment(num); std::cout << num << std::endl; // 10が出力される return 0; }
-
参照渡し: 引数の参照が関数に渡されます。関数内で引数の値を変更すると、呼び出し元の変数の値も変更されます。
void increment(int& x) { x++; // xは呼び出し元の変数の参照なので、呼び出し元の変数も変更される } int main() { int num = 10; increment(num); std::cout << num << std::endl; // 11が出力される return 0; }
-
ポインタ渡し: 引数のポインタが関数に渡されます。関数内でポインタを通して引数の値を変更すると、呼び出し元の変数の値も変更されます。
void increment(int* x) { (*x)++; // xが指すアドレスの値をインクリメントする } int main() { int num = 10; increment(&num); // 変数numのアドレスを渡す std::cout << num << std::endl; // 11が出力される return 0; }
4. 関数のオーバーロード
同じ名前で、引数の型や数が異なる複数の関数を定義することができます。これを関数のオーバーロードと呼びます。
int add(int x, int y) {
return x + y;
}
double add(double x, double y) {
return x + y;
}
int main() {
std::cout << add(5, 3) << std::endl; // int版のadd関数が呼ばれる
std::cout << add(2.5, 1.5) << std::endl; // double版のadd関数が呼ばれる
return 0;
}
5. デフォルト引数
関数定義時に、引数にデフォルト値を設定することができます。関数呼び出し時に引数を省略した場合、デフォルト値が使用されます。
int add(int x, int y = 0) { // yにデフォルト値0を設定
return x + y;
}
int main() {
std::cout << add(5) << std::endl; // yが省略された場合、y=0として扱われる (5が出力される)
std::cout << add(5, 3) << std::endl; // yが指定された場合、y=3として扱われる (8が出力される)
return 0;
}
関数を効果的に使用することで、コードを構造化し、保守性を高めることができます。
オブジェクト指向プログラミング(OOP)は、プログラムを「オブジェクト」という単位で構成するプログラミングパラダイムです。C++はオブジェクト指向プログラミングを強力にサポートしており、クラスとオブジェクトはその中心的な概念です。
1. クラス
クラスは、オブジェクトの設計図またはテンプレートです。クラスは、オブジェクトが持つ属性(データ)と操作(メソッド)を定義します。
クラスの定義:
class クラス名 {
private:
// メンバー変数(属性)
// (通常、外部から直接アクセスできない)
protected:
// メンバー変数(属性)
// (派生クラスからアクセスできる)
public:
// メンバー変数(属性)
// メンバー関数(メソッド)
// (通常、外部からアクセスできる)
};
-
class
キーワード: クラスを定義するために使用します。 - クラス名: クラスを識別するための名前です。
-
private
,protected
,public
キーワード: アクセス修飾子と呼ばれ、メンバー変数やメンバー関数へのアクセス範囲を制御します。-
private
: クラス内でのみアクセス可能です。 -
protected
: クラス自身とその派生クラスからアクセス可能です。 -
public
: どこからでもアクセス可能です。
-
例:
#include <iostream>
#include <string>
class Dog {
private:
std::string name;
int age;
public:
// コンストラクタ
Dog(std::string dogName, int dogAge) : name(dogName), age(dogAge) {}
// ゲッター (name)
std::string getName() const { return name; }
// ゲッター (age)
int getAge() const { return age; }
// バークするメソッド
void bark() const {
std::cout << "Bow Wow!" << std::endl;
}
};
2. オブジェクト
オブジェクトは、クラスのインスタンスです。クラスに基づいてメモリ上に作成された実体を指します。
オブジェクトの生成:
クラス名 オブジェクト名(引数); // コンストラクタに引数を渡す場合
クラス名 オブジェクト名; // コンストラクタに引数を渡さない場合
例:
int main() {
Dog myDog("ポチ", 3); // DogクラスのオブジェクトmyDogを生成
Dog yourDog("ジョン", 5); // DogクラスのオブジェクトyourDogを生成
std::cout << myDog.getName() << "の年齢: " << myDog.getAge() << std::endl;
myDog.bark();
yourDog.bark();
return 0;
}
3. メンバー変数とメンバー関数
- メンバー変数: クラスに属する変数のことです。オブジェクトの状態を表します。
- メンバー関数: クラスに属する関数のことです。オブジェクトの振る舞いを定義します。
例:
上記のDog
クラスでは、name
とage
がメンバー変数、getName()
, getAge()
, bark()
がメンバー関数です。
4. コンストラクタ
コンストラクタは、オブジェクトが生成される際に自動的に呼び出される特別なメンバー関数です。オブジェクトの初期化を行います。
- コンストラクタの名前はクラス名と同じです。
- 戻り値の型はありません (voidも記述しません)。
- 引数を持たないコンストラクタはデフォルトコンストラクタと呼ばれます。
例:
上記のDog
クラスのコンストラクタは以下の通りです。
Dog(std::string dogName, int dogAge) : name(dogName), age(dogAge) {}
5. アクセス修飾子 (private, protected, public)
アクセス修飾子は、クラスのメンバー変数やメンバー関数へのアクセス範囲を制御します。
-
private
: クラス内でのみアクセス可能です。 -
protected
: クラス自身とその派生クラスからアクセス可能です。 -
public
: どこからでもアクセス可能です。
カプセル化を実現するために、通常、メンバー変数はprivate
に、メンバー関数(オブジェクトに対する操作を提供する関数)はpublic
に設定します。これにより、オブジェクトの状態を外部から直接変更することを防ぎ、オブジェクトの整合性を保つことができます。
6. オブジェクト指向プログラミングの利点
- カプセル化: データと処理を1つにまとめ、外部からの不正なアクセスを防ぎます。
- 継承: 既存のクラスの機能を再利用し、新しいクラスを作成できます。
- ポリモーフィズム: 同じ名前のメソッドを異なるクラスで異なる動作をさせることができます。
- 再利用性: クラスを再利用することで、開発効率を向上させることができます。
- 保守性: コードの変更や修正が容易になります。
クラスとオブジェクトを理解し、オブジェクト指向プログラミングの原則に従ってコードを記述することで、より高品質で保守性の高いプログラムを作成することができます。
オブジェクト指向プログラミング(OOP)の重要な3つの概念、継承、ポリモーフィズム、カプセル化について解説します。
1. 継承 (Inheritance)
継承は、既存のクラス(親クラスまたは基底クラス)の属性と操作を新しいクラス(子クラスまたは派生クラス)が引き継ぐことができる機能です。これにより、コードの再利用性が向上し、クラス間の階層構造を表現することができます。
継承の構文:
class 派生クラス名 : アクセス指定 親クラス名 {
// 派生クラスのメンバー変数とメンバー関数
};
-
アクセス指定:
public
,protected
,private
のいずれかを指定します。-
public
: 親クラスのpublic
メンバーは派生クラスでもpublic
になります。 -
protected
: 親クラスのpublic
メンバーは派生クラスではprotected
になります。 -
private
: 親クラスのpublic
メンバーは派生クラスではアクセスできなくなります。 通常、protected
かpublic
が使用されます。
-
- 親クラス名: 継承するクラスの名前を指定します。
例:
#include <iostream>
#include <string>
// 親クラス (基底クラス)
class Animal {
protected:
std::string name;
public:
Animal(std::string animalName) : name(animalName) {}
void eat() const {
std::cout << name << " is eating." << std::endl;
}
};
// 子クラス (派生クラス)
class Dog : public Animal {
private:
std::string breed;
public:
Dog(std::string dogName, std::string dogBreed) : Animal(dogName), breed(dogBreed) {}
void bark() const {
std::cout << "Woof!" << std::endl;
}
void displayInfo() const {
std::cout << "Name: " << name << ", Breed: " << breed << std::endl;
}
};
int main() {
Dog myDog("ポチ", "柴犬");
myDog.eat(); // 親クラスのeat()メソッドを呼び出す
myDog.bark(); // 子クラスのbark()メソッドを呼び出す
myDog.displayInfo(); // 子クラスのdisplayInfo()メソッドを呼び出す
return 0;
}
2. ポリモーフィズム (Polymorphism)
ポリモーフィズムは、異なるクラスのオブジェクトを同じインターフェースで扱えるようにする機能です。「多態性」とも呼ばれます。C++では、主に仮想関数と抽象クラスによって実現されます。
-
仮想関数 (Virtual Function): 親クラスで
virtual
キーワードを付けて宣言された関数です。派生クラスでオーバーライド(再定義)することができます。仮想関数を呼び出すと、実行時にオブジェクトの実際の型に基づいて適切な関数が呼び出されます。 - 抽象クラス (Abstract Class): 1つ以上の純粋仮想関数 (pure virtual function) を持つクラスです。抽象クラスはインスタンス化できません。派生クラスは、純粋仮想関数をオーバーライドする必要があります。
例:
#include <iostream>
// 親クラス (基底クラス)
class Shape {
public:
virtual double area() const = 0; // 純粋仮想関数
virtual void display() const {
std::cout << "This is a shape." << std::endl;
}
};
// 子クラス (派生クラス)
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double area() const override {
return 3.14159 * radius * radius;
}
void display() const override {
std::cout << "This is a circle." << std::endl;
}
};
class Square : public Shape {
private:
double side;
public:
Square(double s) : side(s) {}
double area() const override {
return side * side;
}
void display() const override {
std::cout << "This is a square." << std::endl;
}
};
int main() {
// Shape shape; // エラー:抽象クラスはインスタンス化できない
Circle circle(5.0);
Square square(4.0);
Shape* shape1 = &circle;
Shape* shape2 = □
shape1->display(); // Circleのdisplay()が呼ばれる
std::cout << "Area: " << shape1->area() << std::endl; // Circleのarea()が呼ばれる
shape2->display(); // Squareのdisplay()が呼ばれる
std::cout << "Area: " << shape2->area() << std::endl; // Squareのarea()が呼ばれる
return 0;
}
3. カプセル化 (Encapsulation)
カプセル化は、データ(メンバー変数)とそれらのデータを操作するメソッド(メンバー関数)を1つのユニット(クラス)にまとめ、外部からの不正なアクセスから保護する概念です。
-
アクセス修飾子:
private
,protected
,public
を使用して、メンバー変数やメンバー関数へのアクセス範囲を制御します。 -
ゲッター (Getter) とセッター (Setter): メンバー変数の値を読み書きするためのメソッドを提供します。通常、メンバー変数は
private
にして、ゲッターとセッターを通してアクセスします。
例:
#include <iostream>
#include <string>
class BankAccount {
private:
std::string accountHolder;
double balance;
public:
BankAccount(std::string holderName, double initialBalance) : accountHolder(holderName), balance(initialBalance) {}
// ゲッター (accountHolder)
std::string getAccountHolder() const {
return accountHolder;
}
// ゲッター (balance)
double getBalance() const {
return balance;
}
// セッター (deposit)
void deposit(double amount) {
if (amount > 0) {
balance += amount;
std::cout << "Deposited " << amount << ". New balance: " << balance << std::endl;
} else {
std::cout << "Invalid deposit amount." << std::endl;
}
}
// セッター (withdraw)
void withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
std::cout << "Withdrew " << amount << ". New balance: " << balance << std::endl;
} else {
std::cout << "Insufficient funds or invalid withdrawal amount." << std::endl;
}
}
};
int main() {
BankAccount myAccount("太郎", 1000);
std::cout << "Account Holder: " << myAccount.getAccountHolder() << std::endl;
std::cout << "Balance: " << myAccount.getBalance() << std::endl;
myAccount.deposit(500);
myAccount.withdraw(200);
myAccount.withdraw(2000); // 残高不足
return 0;
}
継承、ポリモーフィズム、カプセル化は、オブジェクト指向プログラミングの中核となる概念であり、これらの概念を理解し、適切に活用することで、より柔軟で再利用性が高く、保守しやすいプログラムを作成することができます。
C++におけるポインタとメモリ管理は、プログラムのパフォーマンスと安定性に深く関わる重要な概念です。適切に理解し、使用することで、効率的なプログラムを作成できますが、誤った使い方をするとメモリリークや不正なメモリアクセスを引き起こし、プログラムのクラッシュにつながる可能性があります。
1. ポインタ (Pointer)
ポインタは、メモリ上のアドレスを格納する変数です。ポインタを使用することで、メモリ上のデータを直接操作したり、間接的にアクセスしたりすることができます。
ポインタの宣言:
データ型 *ポインタ名;
-
データ型
: ポインタが指す変数の型を指定します。 -
*
: ポインタ変数であることを示します。 -
ポインタ名
: ポインタ変数の名前です。
例:
int num = 10;
int *ptr; // int型の変数を指すポインタptrを宣言
ptr = # // ptrにnumのアドレスを代入 (アドレス演算子&)
ポインタの操作:
- アドレス演算子 (&): 変数のアドレスを取得します。
- 間接参照演算子 (*): ポインタが指すメモリ上の値にアクセスします。
例:
#include <iostream>
int main() {
int num = 10;
int *ptr = #
std::cout << "numの値: " << num << std::endl; // 10
std::cout << "numのアドレス: " << &num << std::endl; // 0x... (アドレス)
std::cout << "ptrの値: " << ptr << std::endl; // 0x... (numのアドレスと同じ)
std::cout << "*ptrの値: " << *ptr << std::endl; // 10 (numの値)
*ptr = 20; // ポインタを通してnumの値を変更
std::cout << "numの値(変更後): " << num << std::endl; // 20
return 0;
}
2. 動的メモリ割り当て (Dynamic Memory Allocation)
プログラムの実行中に、必要なメモリ領域を確保することを動的メモリ割り当てといいます。C++では、new
演算子とdelete
演算子を使って動的にメモリを割り当てたり、解放したりすることができます。
-
new
演算子: 指定された型のオブジェクトを格納するのに十分な大きさのメモリ領域をヒープ上に確保し、そのメモリ領域のアドレスを返します。 -
delete
演算子:new
演算子で割り当てられたメモリ領域を解放します。
例:
#include <iostream>
int main() {
int *ptr = new int; // int型のメモリ領域を動的に割り当て
*ptr = 50; // 割り当てられたメモリ領域に値を格納
std::cout << "*ptrの値: " << *ptr << std::endl;
delete ptr; // 割り当てられたメモリ領域を解放
// ptr = nullptr; // 解放後のポインタにはnullptrを代入するのが安全 (C++11以降)
return 0;
}
注意点:
-
new
演算子で割り当てられたメモリ領域は、必ずdelete
演算子で解放する必要があります。解放を忘れるとメモリリークが発生します。 - 解放済みのメモリ領域にアクセスしたり、二重解放 (同じメモリ領域を2回解放すること) を行うと、プログラムが不正な動作をする可能性があります。
- C++11以降では、解放後のポインタには
nullptr
を代入することが推奨されます。これにより、解放済みのメモリ領域へのアクセスを検出することができます。
3. メモリリーク (Memory Leak)
メモリリークは、new
演算子で割り当てられたメモリ領域が、delete
演算子で解放されないまま放置される状態のことです。メモリリークが蓄積すると、利用可能なメモリが減少し、最終的にはプログラムがクラッシュする可能性があります。
メモリリークの例:
void allocateMemory() {
int *ptr = new int;
// ptrをdeleteするのを忘れた場合、メモリリークが発生する
}
int main() {
for (int i = 0; i < 100000; i++) {
allocateMemory(); // allocateMemory()を何度も呼び出すと、メモリリークが蓄積する
}
return 0;
}
メモリリークの防止:
-
new
演算子とdelete
演算子を必ずペアで使用する。 - 例外が発生する可能性がある場合は、例外処理を使ってメモリを解放する。
- スマートポインタ (後述) を使用する。
4. スマートポインタ (Smart Pointer)
スマートポインタは、メモリ管理を自動化するためのクラスです。スマートポインタは、オブジェクトのライフサイクルを管理し、オブジェクトが不要になったときに自動的にメモリを解放します。C++には、std::unique_ptr
, std::shared_ptr
, std::weak_ptr
などのスマートポインタがあります。
-
std::unique_ptr
: 1つのオブジェクトに対して1つのポインタだけが存在することを保証します。オブジェクトの所有権を明確にしたい場合に適しています。std::unique_ptr
はコピーできませんが、ムーブできます。 -
std::shared_ptr
: 複数のポインタが1つのオブジェクトを共有できます。参照カウンタを使って、オブジェクトが参照されなくなったときに自動的にメモリを解放します。循環参照に注意する必要があります。 -
std::weak_ptr
:std::shared_ptr
が管理するオブジェクトへの非所有参照を提供します。std::weak_ptr
からstd::shared_ptr
を生成することで、オブジェクトがまだ有効かどうかを確認できます。循環参照を解決するために使用されます。
例:
#include <iostream>
#include <memory>
int main() {
// std::unique_ptr の例
std::unique_ptr<int> ptr1 = std::make_unique<int>(10); // C++14以降推奨
std::cout << "*ptr1: " << *ptr1 << std::endl;
// std::shared_ptr の例
std::shared_ptr<int> ptr2 = std::make_shared<int>(20);
std::shared_ptr<int> ptr3 = ptr2; // ptr2とptr3が同じメモリ領域を共有
std::cout << "ptr2.use_count(): " << ptr2.use_count() << std::endl; // 2
// std::weak_ptr の例
std::weak_ptr<int> weakPtr = ptr2;
if (auto sharedPtr = weakPtr.lock()) { // オブジェクトがまだ有効か確認
std::cout << "*sharedPtr: " << *sharedPtr << std::endl;
}
ptr2.reset(); // ptr2が指すオブジェクトを解放
ptr3.reset(); // ptr3が指すオブジェクトを解放
return 0;
}
スマートポインタを使用することで、メモリリークのリスクを大幅に減らすことができます。
ポインタとメモリ管理は、C++プログラミングにおいて避けて通れない重要な概念です。スマートポインタを積極的に活用し、メモリリークや不正なメモリアクセスを防ぐように心がけましょう。
標準テンプレートライブラリ(STL)は、C++の標準ライブラリの一部であり、汎用的なデータ構造とアルゴリズムを提供するテンプレートの集まりです。STLを使用することで、効率的なコードを簡単に記述でき、開発時間を短縮することができます。
STLの主要な構成要素:
- コンテナ (Containers): データを格納するためのデータ構造です。
- イテレータ (Iterators): コンテナ内の要素にアクセスするための汎用的な方法を提供します。
- アルゴリズム (Algorithms): コンテナ内の要素に対して様々な操作を行うための関数です。
- 関数オブジェクト (Function Objects): 関数のように振る舞うオブジェクトで、アルゴリズムに渡してカスタマイズした動作をさせることができます (ファンクタとも呼ばれます)。
1. コンテナ (Containers)
STLには、様々な種類のコンテナが用意されています。
-
シーケンスコンテナ (Sequence Containers): 要素が特定の順序で格納されます。
-
vector
: 動的配列。末尾への要素の追加・削除が高速です。#include <vector>
-
deque
: 両端キュー。両端への要素の追加・削除が高速です。#include <deque>
-
list
: 双方向連結リスト。要素の挿入・削除が高速です。#include <list>
-
forward_list
: 単方向連結リスト (C++11)。list
よりも省メモリです。#include <forward_list>
-
array
: 固定長配列 (C++11)。コンパイル時にサイズが決定します。#include <array>
-
-
連想コンテナ (Associative Containers): キーと値のペアを格納し、キーに基づいて要素を高速に検索できます。
-
set
: キーを格納します。キーは重複しません。#include <set>
-
multiset
: キーを格納します。キーは重複できます。#include <set>
-
map
: キーと値のペアを格納します。キーは重複しません。#include <map>
-
multimap
: キーと値のペアを格納します。キーは重複できます。#include <map>
-
-
順序なし連想コンテナ (Unordered Associative Containers): ハッシュテーブルを使用して、キーに基づいて要素を高速に検索できます。キーの順序は保証されません (C++11)。
-
unordered_set
: キーを格納します。キーは重複しません。#include <unordered_set>
-
unordered_multiset
: キーを格納します。キーは重複できます。#include <unordered_set>
-
unordered_map
: キーと値のペアを格納します。キーは重複しません。#include <unordered_map>
-
unordered_multimap
: キーと値のペアを格納します。キーは重複できます。#include <unordered_map>
-
コンテナの基本的な操作:
-
size()
: コンテナの要素数を返します。 -
empty()
: コンテナが空かどうかを判定します。 -
begin()
: コンテナの先頭要素を指すイテレータを返します。 -
end()
: コンテナの末尾の次の要素を指すイテレータを返します。 -
push_back(value)
: (一部のコンテナ) 末尾に要素を追加します。 -
push_front(value)
: (一部のコンテナ) 先頭に要素を追加します。 -
insert(iterator, value)
: 指定された位置に要素を挿入します。 -
erase(iterator)
: 指定された位置の要素を削除します。 -
clear()
: コンテナのすべての要素を削除します。
2. イテレータ (Iterators)
イテレータは、コンテナ内の要素にアクセスするための汎用的な方法を提供します。イテレータは、ポインタのように振る舞い、*
演算子を使って要素の値にアクセスしたり、++
演算子を使って次の要素に進んだりすることができます。
イテレータの種類:
- 入力イテレータ (Input Iterator): コンテナから要素を読み取ることができます。
- 出力イテレータ (Output Iterator): コンテナに要素を書き込むことができます。
- 前方イテレータ (Forward Iterator): 前方向にのみ移動できます。
- 双方向イテレータ (Bidirectional Iterator): 前方向と後ろ方向に移動できます。
- ランダムアクセスイテレータ (Random Access Iterator): 任意の要素に直接アクセスできます。
例:
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
// イテレータを使って要素にアクセス
for (std::vector<int>::iterator it = numbers.begin(); it != numbers.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// C++11以降の範囲ベースfor文を使用
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
3. アルゴリズム (Algorithms)
STLには、ソート、検索、コピー、変換など、様々なアルゴリズムが用意されています。アルゴリズムは、イテレータを使ってコンテナ内の要素にアクセスし、操作を行います。
主なアルゴリズム:
-
sort()
: コンテナの要素をソートします。#include <algorithm>
-
find()
: コンテナ内で指定された値を持つ要素を検索します。#include <algorithm>
-
copy()
: コンテナの要素を別のコンテナにコピーします。#include <algorithm>
-
transform()
: コンテナの要素を変換します。#include <algorithm>
-
remove()
: コンテナから指定された値を持つ要素を削除します。#include <algorithm>
-
for_each()
: コンテナの各要素に対して関数を適用します。#include <algorithm>
例:
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> numbers = {5, 2, 8, 1, 9};
// ソート
std::sort(numbers.begin(), numbers.end());
// 検索
auto it = std::find(numbers.begin(), numbers.end(), 8);
if (it != numbers.end()) {
std::cout << "8が見つかりました" << std::endl;
}
// 出力
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl; // 1 2 5 8 9
return 0;
}
4. 関数オブジェクト (Function Objects)
関数オブジェクトは、関数のように振る舞うオブジェクトです。クラス内でoperator()
をオーバーロードすることで、オブジェクトを関数のように呼び出すことができます。関数オブジェクトは、アルゴリズムに渡してカスタマイズした動作をさせることができます。
例:
#include <iostream>
#include <vector>
#include <algorithm>
// 関数オブジェクト
class IsEven {
public:
bool operator()(int num) const {
return num % 2 == 0;
}
};
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6};
// 関数オブジェクトを使って偶数だけをカウント
int evenCount = std::count_if(numbers.begin(), numbers.end(), IsEven());
std::cout << "偶数の数: " << evenCount << std::endl; // 3
return 0;
}
ラムダ式 (Lambda Expression): (C++11以降)
関数オブジェクトをより簡潔に記述するための機能です。名前のない関数オブジェクトをその場で定義することができます。
例:
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6};
// ラムダ式を使って偶数だけをカウント
int evenCount = std::count_if(numbers.begin(), numbers.end(), [](int num) { return num % 2 == 0; });
std::cout << "偶数の数: " << evenCount << std::endl; // 3
return 0;
}
STLは、C++プログラミングにおいて非常に強力なツールです。様々なデータ構造とアルゴリズムを簡単に利用できるため、開発効率を大幅に向上させることができます。
C++はその高いパフォーマンス、ハードウェア制御性、柔軟性から、ゲーム開発や組み込みシステムといった分野で広く利用されています。
1. ゲーム開発
C++は、ゲーム開発において最も一般的なプログラミング言語の一つです。
C++がゲーム開発に適している理由:
- 高いパフォーマンス: ゲームはリアルタイム性が要求されるため、高いパフォーマンスが不可欠です。C++は、他の高水準言語に比べて処理速度が速く、メモリ管理も細かく制御できるため、パフォーマンスが重要なゲーム開発に適しています。
- ハードウェア制御: グラフィックボードやサウンドカードなどのハードウェアを直接制御できるため、ゲームのパフォーマンスを最大限に引き出すことができます。
- 豊富なライブラリ: DirectX、OpenGL、SDLなどのグラフィックライブラリや、物理エンジン、サウンドエンジンなど、ゲーム開発に必要な様々なライブラリがC++で提供されています。
- 既存のゲームエンジンのサポート: UnityやUnreal Engineなどの著名なゲームエンジンは、C++で記述されたコードを統合できます。これにより、エンジンの強力な機能を利用しつつ、C++でパフォーマンスが重要な部分を実装できます。
- 長い歴史とコミュニティ: C++は長い歴史を持つ言語であり、ゲーム開発に関する豊富な情報やコミュニティが存在します。
C++を使ったゲーム開発の例:
- 大規模なAAAタイトル: Call of Duty、Grand Theft Auto、Cyberpunk 2077などの大規模なゲームは、C++で開発されています。
- インディーゲーム: 小規模なインディーゲームも、C++とSDLなどのライブラリを組み合わせて開発されることがあります。
- ゲームエンジンの開発: UnityやUnreal Engineなどのゲームエンジン自体もC++で開発されています。
ゲーム開発におけるC++の課題:
- メモリ管理の複雑さ: メモリリークや不正なメモリアクセスを防ぐために、メモリ管理を慎重に行う必要があります。スマートポインタの活用や、メモリ管理ツールを利用することが重要です。
- 学習コスト: C++は、他の高水準言語に比べて学習コストが高い傾向があります。
2. 組み込みシステム
組み込みシステムは、特定の機能を実現するために電子機器に組み込まれたコンピュータシステムです。家電製品、自動車、産業機器など、様々な分野で使用されています。
C++が組み込みシステムに適している理由:
- ハードウェア制御: 組み込みシステムでは、センサーやアクチュエータなどのハードウェアを直接制御する必要があります。C++は、低レベルなハードウェア制御を容易に行うことができます。
- リアルタイム性: 多くの組み込みシステムでは、リアルタイム性が要求されます。C++は、リアルタイムOS (RTOS) 上で動作するプログラムを開発するのに適しています。
- メモリ効率: 組み込みシステムでは、メモリ容量が限られていることが多いため、メモリ効率が重要です。C++は、メモリ使用量を細かく制御できるため、メモリ効率の高いプログラムを作成できます。
- クロスプラットフォーム開発: 組み込みシステムは、様々なプラットフォーム上で動作します。C++は、クロスプラットフォーム開発に適しており、異なるプラットフォーム向けのコードを比較的容易に作成できます。
- 既存のコードの再利用: 組み込みシステム開発では、既存のコードを再利用することが重要です。C++は、オブジェクト指向プログラミングをサポートしており、コードの再利用性を高めることができます。
C++を使った組み込みシステムの開発例:
- 自動車の制御システム: エンジン制御、ブレーキ制御、エアバッグ制御など。
- 家電製品: 冷蔵庫、洗濯機、電子レンジなどの制御。
- 産業機器: ロボット、PLC (プログラマブルロジックコントローラ) などの制御。
- 医療機器: 医療用画像診断装置、人工呼吸器などの制御。
- IoTデバイス: センサー、アクチュエータ、通信モジュールなどの制御。
組み込みシステムにおけるC++の課題:
- リソース制約: 組み込みシステムは、メモリ容量、処理能力、消費電力などのリソースが限られていることが多いです。リソースを効率的に使用するプログラミングが求められます。
- リアルタイム性: リアルタイム性が要求されるシステムでは、割り込み処理やタスクスケジューリングなどを適切に行う必要があります。
- デバッグの難しさ: 組み込みシステムは、ホストコンピュータ上で動作するプログラムに比べてデバッグが難しい場合があります。ICE (インサーキットエミュレータ) などのデバッグツールを利用することが重要です。
C++は、ゲーム開発と組み込みシステムという異なる分野で、その高いパフォーマンスとハードウェア制御性を活かして広く利用されています。これらの分野でC++を使いこなすには、メモリ管理やリアルタイム性に関する知識など、専門的な知識が必要になります。
この講義ノートでは、C++の基礎から応用まで、幅広い内容を学習しました。C++は、その歴史、特徴、オブジェクト指向プログラミングの概念、標準テンプレートライブラリ(STL)、そしてゲーム開発や組み込みシステムへの応用など、非常に奥深い言語です。
この講義ノートで学んだことのまとめ:
- C++の歴史と特徴: C言語を基盤とし、オブジェクト指向プログラミングの機能を追加した言語であること。高いパフォーマンス、ハードウェア制御性、豊富なライブラリが特徴であること。
- 開発環境の構築: コンパイラ (GCC, Clang, MSVC) と IDE (Visual Studio, CLion, Eclipse CDT) の設定方法。
- C++の基本構文: 変数、データ型、演算子、制御構造 (条件分岐、繰り返し)。
- 関数: 関数の定義、呼び出し、引数、オーバーロード、デフォルト引数。
- オブジェクト指向プログラミング: クラス、オブジェクト、継承、ポリモーフィズム、カプセル化。
-
ポインタとメモリ管理: ポインタの概念、動的メモリ割り当て (
new
,delete
)、メモリリーク、スマートポインタ (std::unique_ptr
,std::shared_ptr
)。 - 標準テンプレートライブラリ(STL): コンテナ、イテレータ、アルゴリズム、関数オブジェクト。
- C++の応用: ゲーム開発、組み込みシステム。
今後の学習:
この講義ノートは、C++の学習の出発点に過ぎません。C++をより深く理解し、実践的なスキルを身につけるためには、継続的な学習が必要です。以下に、今後の学習の方向性についていくつか提案します。
-
C++の標準規格の学習: C++は定期的に標準規格が更新されています。C++11, C++14, C++17, C++20などの新しい規格を学習することで、よりモダンなC++の機能を利用できるようになります。特に、C++11で導入されたラムダ式やスマートポインタは、現代的なC++プログラミングにおいて不可欠な知識です。
-
実践的なプログラミング: 実際にC++でプログラムを書いてみることが、最も効果的な学習方法です。簡単なプログラムから始めて、徐々に複雑なプログラムに挑戦してみましょう。例えば、以下のようなプロジェクトに挑戦してみるのも良いでしょう。
- コンソールアプリケーション: 簡単なテキストベースのゲーム (数当てゲーム、じゃんけんゲームなど)
- GUIアプリケーション: GUIフレームワーク (Qt, wxWidgets) を利用したアプリケーション
- ゲーム開発: SDLなどのライブラリを利用した簡単なゲーム
- アルゴリズムの実装: ソートアルゴリズムやグラフアルゴリズムなどをC++で実装
- ライブラリの作成: 汎用的なデータ構造やアルゴリズムをまとめたライブラリ
-
C++の書籍やオンライン教材の利用: C++に関する書籍やオンライン教材は数多く存在します。自分に合った教材を選び、体系的に学習を進めることが重要です。
- 書籍:
- 『プログラミング言語C++』 (Bjarne Stroustrup著): C++の設計者自身による解説書。やや難解ですが、C++の深い理解に役立ちます。
- 『Effective C++』 (Scott Meyers著): C++プログラミングのベストプラクティスを紹介する書籍。
- 『More Effective C++』 (Scott Meyers著): 『Effective C++』の続編。
- オンライン教材:
- cppreference.com: C++の標準ライブラリのリファレンス。
- LearnCpp.com: C++のチュートリアル。
- Coursera, edX, Udemyなどのオンライン学習プラットフォームのC++コース。
- 書籍:
-
コミュニティへの参加: C++に関するコミュニティに参加することで、他のプログラマと交流したり、質問したり、情報交換したりすることができます。
- オンラインフォーラム: Stack Overflow, Redditのr/cppなど。
- Meetupイベント: C++に関する勉強会や交流会。
-
ゲーム開発や組み込みシステムの専門知識の学習: C++をゲーム開発や組み込みシステムに応用するためには、それぞれの分野に関する専門知識を習得する必要があります。
- ゲーム開発: DirectX, OpenGL, SDLなどのグラフィックライブラリ、物理エンジン、サウンドエンジンなどを学習。
- 組み込みシステム: マイコン、センサー、アクチュエータ、リアルタイムOS (RTOS) などを学習。
おわりに:
C++は学習に時間のかかる言語ですが、その分習得した際の達成感も大きい言語です。諦めずに学習を継続し、C++の知識とスキルを身につけて、創造的なプログラミングを楽しんでください。