C++は、1979年にBjarne Stroustrupによって開発された、汎用性の高いプログラミング言語です。C言語を拡張する形で誕生し、オブジェクト指向プログラミング(OOP)の概念を取り入れたことで、より複雑で大規模なソフトウェア開発を可能にしました。
- C言語からの発展: C++は、C言語を基盤としており、C言語との互換性を保ちながら、オブジェクト指向の機能を追加しました。C言語の効率性と制御能力を受け継ぎつつ、より高度な抽象化を可能にしています。
- オブジェクト指向の導入: クラス、オブジェクト、継承、ポリモーフィズムなどのオブジェクト指向プログラミングの概念を導入したことで、コードの再利用性、保守性、拡張性が向上しました。
- 標準化の進展: ANSI/ISO規格によって標準化が進められ、異なるコンパイラ間での互換性が確保されました。C++98、C++11、C++14、C++17、C++20など、定期的なバージョンアップによって、言語機能が拡張・改善されています。
- 現代的なC++: 近年のC++は、ラムダ式、スマートポインタ、constexpr関数など、より効率的で安全なプログラミングを支援する機能が充実しています。
- 汎用性: オペレーティングシステム、ゲーム、組み込みシステム、Webアプリケーションなど、幅広い分野で利用されています。
- パフォーマンス: C言語同様に、ハードウェアに近いレベルでの制御が可能であり、高速な実行速度を実現できます。パフォーマンスが重要なアプリケーションに適しています。
- オブジェクト指向: クラス、オブジェクト、継承などのオブジェクト指向プログラミングの機能をサポートしており、複雑なソフトウェアを効率的に開発できます。
- 豊富なライブラリ: 標準テンプレートライブラリ(STL)をはじめとする豊富なライブラリが提供されており、さまざまな処理を簡単に実装できます。
- 移植性: 多くのオペレーティングシステムやプラットフォームで動作し、高い移植性を備えています。
- 学習コスト: C言語を基盤としているため、C言語の知識があると比較的容易に学習できますが、オブジェクト指向の概念や高度な機能も多いため、習得には一定の時間がかかります。
- メモリ管理: ポインタを利用したメモリ管理が必要となるため、メモリリークなどのバグが発生しやすい側面もあります。ただし、スマートポインタなどの機能を利用することで、安全なメモリ管理を支援します。
C++は、現代のソフトウェア開発において、依然として重要な役割を果たしているプログラミング言語です。パフォーマンスと柔軟性を両立させることができ、大規模なプロジェクトや高性能なアプリケーションの開発に適しています。
C++プログラミングを始めるには、コードを書くエディタと、コードを機械語に変換するコンパイラが必要です。ここでは、Visual Studio Code(VS Code)をエディタとして、主要なC++コンパイラを組み合わせて開発環境を構築する方法を解説します。
VS Codeは、Microsoftが提供する無料の高性能なテキストエディタです。豊富な拡張機能により、C++開発を強力にサポートします。
- ダウンロード: VS Codeの公式サイト (https://code.visualstudio.com/) から、お使いのOSに合ったインストーラをダウンロードします。
- インストール: ダウンロードしたインストーラを実行し、指示に従ってインストールを進めます。
C++コンパイラは、記述したC++のソースコードをコンピュータが実行できる機械語に変換するツールです。主なコンパイラとして、以下のものがあります。
-
GCC (GNU Compiler Collection): Linux、macOS、Windowsなど、幅広いプラットフォームで利用できるフリーのコンパイラです。
-
Linux: ほとんどのLinuxディストリビューションにデフォルトでインストールされています。未インストールの場合は、
sudo apt-get install g++
(Debian/Ubuntu) やsudo yum install gcc-c++
(CentOS/Fedora) などでインストールできます。 -
macOS: Xcode Command Line ToolsをインストールすることでGCCが利用可能になります。ターミナルで
xcode-select --install
を実行します。 - Windows: MinGW-w64をインストールすることでGCCを利用できます。MinGW-w64の公式サイト (https://www.mingw-w64.org/) からインストーラをダウンロードし、指示に従ってインストールします。環境変数の設定も必要です。
-
Linux: ほとんどのLinuxディストリビューションにデフォルトでインストールされています。未インストールの場合は、
-
Clang: LLVMプロジェクトの一部であるコンパイラで、GCC同様に幅広いプラットフォームで利用できます。
- Linux/macOS: 多くのディストリビューションで利用可能です。インストール方法はGCCと同様です。
- Windows: LLVMの公式サイト (https://releases.llvm.org/download.html) からインストーラをダウンロードし、指示に従ってインストールします。
- Microsoft Visual C++ (MSVC): Microsoftが提供するVisual Studioに含まれるコンパイラです。Windows環境での開発に最適化されています。Visual Studioをインストールする際に、C++によるデスクトップ開発ワークロードを選択する必要があります。
VS CodeでC++開発を行うための便利な拡張機能をインストールします。
- C/C++: Microsoftが提供する拡張機能で、C++のコード補完、デバッグ、フォーマットなどの機能を提供します。VS Codeの拡張機能マーケットプレイスで “C/C++” を検索してインストールします。
- Code Runner: コードを簡単に実行できる拡張機能です。VS Codeの拡張機能マーケットプレイスで “Code Runner” を検索してインストールします。
C++のコンパイラをVS Codeに認識させるために、設定ファイル(tasks.json
, launch.json
)を作成する必要があります。
-
tasks.json: コードのコンパイル方法を定義します。
.vscode
フォルダ内にtasks.json
ファイルを作成し、以下のような内容を記述します。
{
"version": "2.0.0",
"tasks": [
{
"type": "cppbuild",
"label": "C/C++: g++.exe build active file",
"command": "C:\\mingw64\\bin\\g++.exe", // 自身の環境に合わせてパスを修正
"args": [
"-g",
"${file}",
"-o",
"${fileDirname}\\${fileBasenameNoExtension}.exe"
],
"options": {
"cwd": "${fileDirname}"
},
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "コンパイラ: C:\\mingw64\\bin\\g++.exe" // 自身の環境に合わせてパスを修正
}
]
}
-
launch.json: デバッグの設定を定義します。
.vscode
フォルダ内にlaunch.json
ファイルを作成し、以下のような内容を記述します。
{
"version": "0.2.0",
"configurations": [
{
"name": "g++.exe - アクティブ ファイルのデバッグ",
"type": "cppdbg",
"request": "launch",
"program": "${fileDirname}\\${fileBasenameNoExtension}.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerPath": "C:\\mingw64\\bin\\gdb.exe", // 自身の環境に合わせてパスを修正
"setupCommands": [
{
"description": "gdb の再フォーマットを有効にするには、次を有効にします",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}
注意点:
-
command
およびmiDebuggerPath
は、ご自身の環境に合わせてコンパイラ(g++.exe
など)とデバッガ(gdb.exe
など)のパスを正確に設定してください。 - 上記はMinGW-w64 (GCC) を使用する例です。他のコンパイラを使用する場合は、設定ファイルを適切に修正する必要があります。
以下のC++のコードを hello.cpp
という名前で保存し、VS Codeで開きます。
#include <iostream>
int main() {
std::cout << "Hello, C++!" << std::endl;
return 0;
}
VS Codeのターミナルで Ctrl + Shift + B
(Build) を押してコンパイルし、Ctrl + Shift + D
(Debug) を押してデバッグ実行します。ターミナルに “Hello, C++!” と表示されれば、開発環境の構築は成功です。 Code Runner拡張機能をインストールしている場合は、右クリックで “Run Code” を選択しても実行できます。
以上の手順で、VS CodeとC++コンパイラを使った開発環境を構築できます。この環境を使って、C++プログラミングの学習を進めていきましょう。
C++のプログラミングを始めるにあたり、変数、データ型、演算子は非常に重要な要素です。これらを理解することで、プログラムにデータを格納し、操作できるようになります。
変数は、プログラム内でデータを一時的に格納するための名前付きの記憶領域です。変数は、プログラムの実行中に値を変更できます。
-
宣言: 変数を使用する前に、その名前とデータ型を宣言する必要があります。
int age; // int型の変数ageを宣言 double salary; // double型の変数salaryを宣言 std::string name; // string型の変数nameを宣言
-
初期化: 変数を宣言する際に、初期値を代入することもできます。
int age = 30; // int型の変数ageを30で初期化 double salary = 50000.0; // double型の変数salaryを50000.0で初期化 std::string name = "John Doe"; // string型の変数nameを"John Doe"で初期化
-
命名規則: 変数名は、英字、数字、アンダースコア (_) で構成されます。数字で始めることはできません。また、C++のキーワード(
int
,double
,class
など)を変数名として使用することはできません。int myAge; // OK double _salary; // OK std::string userName123; // OK // int 123userName; // エラー: 数字で始まる // int class; // エラー: キーワードを使用
データ型は、変数に格納できるデータの種類を定義します。C++には、さまざまなデータ型が用意されています。
-
整数型: 整数値を格納します。
-
int
: 通常、32ビットの整数を格納します。
int age = 30;
-
short
: 通常、16ビットの整数を格納します。
short count = 100;
-
long
: 通常、32ビットまたは64ビットの整数を格納します。
long population = 1000000000;
-
long long
: 通常、64ビットの整数を格納します。
long long veryLargeNumber = 9223372036854775807;
-
-
浮動小数点型: 小数を含む数値を格納します。
-
float
: 通常、32ビットの浮動小数点を格納します。
float temperature = 25.5f; // fサフィックスが必要
-
double
: 通常、64ビットの浮動小数点を格納します。float
よりも精度が高いです。
double pi = 3.14159265359;
-
-
文字型: 1つの文字を格納します。
-
char
: 1つの文字を格納します。
char initial = 'J';
-
-
ブール型: 真偽値(
true
またはfalse
)を格納します。-
bool
: 真偽値を格納します。
bool isAdult = true;
-
-
文字列型: 文字列を格納します。
-
std::string
: 文字列を格納します。#include <string>
が必要です。
#include <string> std::string name = "John Doe";
-
-
その他のデータ型:
-
void
: 値を持たないことを意味します。関数の戻り値の型としてよく使用されます。 - ポインタ型:メモリのアドレスを格納します。
- 配列:同じ型の複数の要素を格納します。
-
演算子は、変数や値に対してさまざまな操作を実行するために使用されます。
-
算術演算子: 数値演算を実行します。
-
+
: 加算
int sum = 10 + 5; // sumは15
-
-
: 減算
int difference = 10 - 5; // differenceは5
-
*
: 乗算
int product = 10 * 5; // productは50
-
/
: 除算
int quotient = 10 / 5; // quotientは2
-
%
: 剰余(余り)
int remainder = 10 % 3; // remainderは1
-
-
代入演算子: 変数に値を代入します。
-
=
: 代入
int age = 30;
-
+=
,-=
,*=
,/=
,%=
: 複合代入演算子
int x = 10; x += 5; // x = x + 5; と同じ。xは15になる
-
-
比較演算子: 2つの値を比較します。結果はブール値(
true
またはfalse
)です。-
==
: 等しい
bool isEqual = (10 == 5); // isEqualはfalse
-
!=
: 等しくない
bool isNotEqual = (10 != 5); // isNotEqualはtrue
-
>
: より大きい
bool isGreaterThan = (10 > 5); // isGreaterThanはtrue
-
<
: より小さい
bool isLessThan = (10 < 5); // isLessThanはfalse
-
>=
: 以上
bool isGreaterThanOrEqual = (10 >= 10); // isGreaterThanOrEqualはtrue
-
<=
: 以下
bool isLessThanOrEqual = (10 <= 10); // isLessThanOrEqualはtrue
-
-
論理演算子: 複数の条件を組み合わせます。結果はブール値(
true
またはfalse
)です。-
&&
: 論理AND(両方の条件が真の場合に真)
bool isAdultAndEmployed = (age >= 20) && (isEmployed == true);
-
||
: 論理OR(少なくとも一方の条件が真の場合に真)
bool isWeekendOrHoliday = (isWeekend == true) || (isHoliday == true);
-
!
: 論理NOT(条件を否定)
bool isNotAdult = !(age >= 20); // ageが20以上でない場合にtrue
-
-
インクリメント/デクリメント演算子: 変数の値を1増減させます。
-
++
: インクリメント(1増やす)
int i = 0; i++; // iは1になる
-
--
: デクリメント(1減らす)
int j = 10; j--; // jは9になる
-
- ビット演算子: ビット単位で演算を行います。(ここでは省略)
これらの基本構文を理解することで、C++で簡単なプログラムを作成できるようになります。練習を通して、これらの要素を使いこなせるようにしましょう。
C++における制御構造は、プログラムの実行フローを制御するために不可欠です。条件分岐を使うことで、特定の条件に応じて異なる処理を実行できます。繰り返しを使うことで、同じ処理を複数回実行できます。
条件分岐は、指定された条件に基づいてプログラムの実行パスを選択するために使用されます。C++では、if
文、else if
文、else
文、およびswitch
文を使用して条件分岐を実装します。
-
if
文: 指定された条件が真(true
)の場合に、特定のコードブロックを実行します。int age = 20; if (age >= 18) { std::cout << "成人です。" << std::endl; }
-
if-else
文:if
文の条件が偽(false
)の場合に、else
ブロックのコードを実行します。int age = 15; if (age >= 18) { std::cout << "成人です。" << std::endl; } else { std::cout << "未成年です。" << std::endl; }
-
if-else if-else
文: 複数の条件を順番に評価し、最初に真となる条件に対応するコードブロックを実行します。どの条件も真でなければ、else
ブロックのコードを実行します。int score = 85; 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
文: ある変数の値に基づいて複数のケースに分岐します。case
ラベルで指定された値と変数の値が一致した場合、そのケースのコードが実行されます。break
文を使用して、次のケースへのフォールスルーを防ぎます。default
ラベルは、どの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; }
繰り返しは、同じコードブロックを複数回実行するために使用されます。C++では、for
文、while
文、およびdo-while
文を使用して繰り返しを実装します。
-
for
文: 指定された回数だけコードブロックを繰り返します。初期化式、条件式、更新式が必要です。for (int i = 0; i < 5; i++) { std::cout << "繰り返し回数: " << i << std::endl; }
-
while
文: 指定された条件が真である限り、コードブロックを繰り返します。int count = 0; while (count < 5) { std::cout << "カウント: " << count << std::endl; count++; }
-
do-while
文: コードブロックを少なくとも1回実行し、その後、指定された条件が真である限り、コードブロックを繰り返します。int num = 0; do { std::cout << "数: " << num << std::endl; num++; } while (num < 5);
-
break
文とcontinue
文:-
break
文: ループの実行を完全に終了し、ループの外側の次の文に制御を移します。 -
continue
文: 現在のイテレーションをスキップし、次のイテレーションを開始します。
for (int i = 0; i < 10; i++) { if (i == 5) { break; // iが5になったらループを終了 } if (i % 2 == 0) { continue; // iが偶数の場合、現在のイテレーションをスキップ } std::cout << "奇数: " << i << std::endl; }
-
これらの制御構造を効果的に使用することで、複雑なロジックを持つプログラムを作成できます。条件分岐と繰り返しを組み合わせて、さまざまな問題を解決するプログラムを作成してみましょう。
C++における関数は、特定のタスクを実行するコードのまとまりであり、プログラムの再利用性とモジュール性を高めるための重要な要素です。関数を使うことで、コードをより整理しやすく、保守しやすくすることができます。
関数は、以下の要素で構成されます。
-
戻り値の型: 関数が返す値のデータ型を指定します。値を返さない場合は
void
を指定します。 - 関数名: 関数を識別するための名前です。命名規則に従って、わかりやすい名前を付けます。
-
引数リスト: 関数に渡す引数(入力)のリストです。引数は、データ型と変数名で構成されます。引数がない場合は
void
または空の括弧()
を使用します。 -
関数本体: 関数の処理内容を記述するコードブロックです。
{}
で囲みます。 -
return
文: 関数が値を返す場合に、返す値を指定します。戻り値の型がvoid
の場合は、return;
と記述するか、省略することができます。
// 関数の定義
int add(int a, int b) {
int sum = a + b;
return sum; // 戻り値を返す
}
void printMessage(std::string message) {
std::cout << message << std::endl; // 引数messageを表示
// return; // void型なので省略可能
}
int main() {
// 関数の呼び出し
int result = add(5, 3); // add関数を呼び出し、戻り値をresultに代入
std::cout << "合計: " << result << std::endl;
printMessage("Hello, functions!"); // printMessage関数を呼び出し
return 0;
}
C++には、さまざまな種類の関数があります。
-
標準ライブラリ関数: C++標準ライブラリに含まれる関数です。例:
std::cout
,std::cin
,std::sqrt
,std::string
など。これらの関数を使用するには、適切なヘッダーファイルをインクルードする必要があります。 -
ユーザー定義関数: プログラマーが自分で定義する関数です。プログラムの特定のタスクを実行するために作成します。
-
インライン関数: 関数呼び出しのオーバーヘッドを削減するために、コンパイラによって関数呼び出し箇所に直接コードが展開される関数です。
inline
キーワードを使用します。inline int square(int x) { return x * x; }
-
再帰関数: 自分自身を呼び出す関数です。複雑な問題を解決するために使用されることがあります。ただし、再帰呼び出しが無限に続かないように、終了条件を適切に設定する必要があります。
int factorial(int n) { if (n == 0) { return 1; } else { return n * factorial(n - 1); // 再帰呼び出し } }
関数に引数を渡す方法には、主に以下の3つがあります。
-
値渡し (Pass by Value): 引数のコピーが関数に渡されます。関数内で引数の値を変更しても、呼び出し元の変数の値は変わりません。
void modifyValue(int x) { x = x * 2; // 関数内でxの値を変更 std::cout << "関数内のx: " << x << std::endl; } int main() { int num = 10; modifyValue(num); // 値渡し std::cout << "main関数のnum: " << num << std::endl; // numは10のまま return 0; }
-
参照渡し (Pass by Reference): 引数の参照(アドレス)が関数に渡されます。関数内で引数の値を変更すると、呼び出し元の変数の値も変更されます。参照渡しを行うには、引数の型に
&
を付けます。void modifyReference(int &x) { x = x * 2; // 関数内でxの値を変更 std::cout << "関数内のx: " << x << std::endl; } int main() { int num = 10; modifyReference(num); // 参照渡し std::cout << "main関数のnum: " << num << std::endl; // numは20になる return 0; }
-
ポインタ渡し (Pass by Pointer): 引数のアドレスが関数に渡されます。関数内でポインタを通して引数の値を変更すると、呼び出し元の変数の値も変更されます。
void modifyPointer(int *x) { *x = *x * 2; // ポインタを通してxの値を変更 std::cout << "関数内のx: " << *x << std::endl; } int main() { int num = 10; modifyPointer(&num); // ポインタ渡し std::cout << "main関数のnum: " << num << std::endl; // numは20になる return 0; }
C++では、同じ名前で引数の型や数が異なる複数の関数を定義することができます。これを関数のオーバーロードといいます。コンパイラは、関数呼び出し時に渡された引数に基づいて、適切な関数を自動的に選択します。
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
int main() {
int sum1 = add(5, 3); // int型のadd関数が呼び出される
double sum2 = add(5.5, 3.2); // double型のadd関数が呼び出される
std::cout << "intの合計: " << sum1 << std::endl;
std::cout << "doubleの合計: " << sum2 << std::endl;
return 0;
}
関数を効果的に使用することで、プログラムの可読性、再利用性、保守性を向上させることができます。関数を使って、プログラムをモジュール化し、より複雑な問題を解決するプログラムを作成してみましょう。
オブジェクト指向プログラミング(OOP)は、現実世界のエンティティをモデル化するプログラミングパラダイムです。C++は、オブジェクト指向プログラミングをサポートしており、クラスとオブジェクトはOOPの重要な概念です。
クラスは、オブジェクトの設計図またはテンプレートです。クラスは、オブジェクトが持つ属性(データ)と、オブジェクトが行うことができる操作(メソッド)を定義します。
- 属性(データメンバ): クラス内の変数のことで、オブジェクトの状態を表します。
- メソッド(メンバ関数): クラス内の関数のことで、オブジェクトの振る舞いを定義します。
クラスは、class
キーワードを使用して定義します。
#include <iostream>
#include <string>
class Dog {
public: // アクセス修飾子:public(どこからでもアクセス可能)
// 属性(データメンバ)
std::string name;
int age;
// メソッド(メンバ関数)
void bark() {
std::cout << "Woof!" << std::endl;
}
void displayInfo() {
std::cout << "名前: " << name << std::endl;
std::cout << "年齢: " << age << "歳" << std::endl;
}
};
-
アクセス修飾子: クラスメンバ(属性とメソッド)へのアクセスを制御するために使用されます。
-
public
: どこからでもアクセス可能です。 -
private
: クラス内からのみアクセス可能です。 -
protected
: クラス自身、派生クラス、および同じパッケージ内のクラスからアクセス可能です。(継承の項目で詳しく説明します)
-
オブジェクトは、クラスのインスタンスです。クラスを元にメモリ上に作成された実体を指します。オブジェクトは、クラスで定義された属性を持ち、メソッドを実行することができます。
オブジェクトは、クラス名を使って宣言します。
int main() {
// Dogクラスのオブジェクトを生成
Dog myDog;
// オブジェクトの属性に値を代入
myDog.name = "ポチ";
myDog.age = 3;
// オブジェクトのメソッドを呼び出す
myDog.displayInfo(); // 名前: ポチ 年齢: 3歳
myDog.bark(); // Woof!
return 0;
}
- コンストラクタ: オブジェクトが作成される際に自動的に呼び出される特別なメソッドです。オブジェクトの初期化を行います。コンストラクタの名前はクラス名と同じです。
-
デストラクタ: オブジェクトが破棄される際に自動的に呼び出される特別なメソッドです。オブジェクトが使用していたリソースを解放するために使用されます。デストラクタの名前はクラス名の前に
~
を付けたものです。
#include <iostream>
#include <string>
class Dog {
public:
std::string name;
int age;
// コンストラクタ
Dog(std::string name, int age) {
this->name = name; // thisポインタ: オブジェクト自身を指すポインタ
this->age = age;
std::cout << name << "が生まれました。" << std::endl;
}
// デストラクタ
~Dog() {
std::cout << name << "が天国へ行きました。" << std::endl;
}
void bark() {
std::cout << "Woof!" << std::endl;
}
void displayInfo() {
std::cout << "名前: " << name << std::endl;
std::cout << "年齢: " << age << "歳" << std::endl;
}
};
int main() {
// コンストラクタが呼び出される
Dog myDog("ハチ", 5);
myDog.displayInfo();
myDog.bark();
// オブジェクトがスコープを抜けるとデストラクタが呼び出される
return 0;
}
カプセル化は、データ(属性)とそれを操作するメソッドを1つの単位(クラス)にまとめ、外部からの不正なアクセスを防ぐための仕組みです。private
アクセス修飾子を使用して、データメンバを外部から直接アクセスできないようにすることで、カプセル化を実現します。
#include <iostream>
#include <string>
class Dog {
private: // private: クラス内からのみアクセス可能
std::string name;
int age;
public:
Dog(std::string name, int age) {
this->name = name;
this->age = age;
}
// ゲッター(Getter):privateメンバの値を取得するためのメソッド
std::string getName() const { // const: このメソッドはオブジェクトの状態を変更しないことを示す
return name;
}
// セッター(Setter):privateメンバの値を設定するためのメソッド
void setAge(int age) {
if (age >= 0) {
this->age = age;
} else {
std::cout << "年齢は0以上でなければなりません。" << std::endl;
}
}
void bark() {
std::cout << "Woof!" << std::endl;
}
void displayInfo() {
std::cout << "名前: " << name << std::endl;
std::cout << "年齢: " << age << "歳" << std::endl;
}
};
int main() {
Dog myDog("レオ", 2);
// myDog.name = "ムギ"; // エラー: privateメンバに直接アクセスできない
std::cout << "名前: " << myDog.getName() << std::endl; // ゲッターを使ってアクセス
myDog.setAge(3); // セッターを使って年齢を設定
myDog.displayInfo();
return 0;
}
継承は、既存のクラス(親クラスまたはスーパークラス)の属性とメソッドを新しいクラス(子クラスまたはサブクラス)が受け継ぐことができるメカニズムです。継承によって、コードの再利用性と拡張性が向上します。
#include <iostream>
#include <string>
// 親クラス
class Animal {
public:
std::string name;
Animal(std::string name) {
this->name = name;
}
void eat() {
std::cout << name << "は食事をします。" << std::endl;
}
};
// 子クラス:DogはAnimalを継承する
class Dog : public Animal { // public継承: AnimalのpublicメンバはDogのpublicメンバになる
public:
Dog(std::string name) : Animal(name) {} // 親クラスのコンストラクタを呼び出す
void bark() {
std::cout << "Woof!" << std::endl;
}
};
int main() {
Dog myDog("ハスキー");
myDog.eat(); // Animalクラスのeat()メソッドを呼び出す
myDog.bark(); // Dogクラスのbark()メソッドを呼び出す
return 0;
}
-
継承の種類:
-
public
継承: 親クラスのpublic
メンバは子クラスのpublic
メンバになります。 -
protected
継承: 親クラスのpublic
メンバは子クラスのprotected
メンバになります。 -
private
継承: 親クラスのpublic
メンバは子クラスのprivate
メンバになります。
-
ポリモーフィズムは、異なるクラスのオブジェクトを同じ方法で扱うことができる能力です。C++では、仮想関数(virtual functions)と抽象クラス(abstract classes)を使用してポリモーフィズムを実現します。
#include <iostream>
#include <string>
// 親クラス
class Animal {
public:
std::string name;
Animal(std::string name) {
this->name = name;
}
// 仮想関数:子クラスでオーバーライドすることを想定
virtual void makeSound() {
std::cout << "動物は音を出す。" << std::endl;
}
};
// 子クラス
class Dog : public Animal {
public:
Dog(std::string name) : Animal(name) {}
// 仮想関数をオーバーライド
void makeSound() override {
std::cout << "犬は「Woof!」と鳴きます。" << std::endl;
}
};
// 子クラス
class Cat : public Animal {
public:
Cat(std::string name) : Animal(name) {}
// 仮想関数をオーバーライド
void makeSound() override {
std::cout << "猫は「Meow!」と鳴きます。" << std::endl;
}
};
int main() {
Animal* animal1 = new Dog("ハスキー");
Animal* animal2 = new Cat("ミケ");
animal1->makeSound(); // 犬は「Woof!」と鳴きます。
animal2->makeSound(); // 猫は「Meow!」と鳴きます。
delete animal1;
delete animal2;
return 0;
}
-
仮想関数:
virtual
キーワードで宣言された関数です。子クラスでオーバーライドできます。 -
抽象クラス: 1つ以上の純粋仮想関数(
virtual void func() = 0;
)を持つクラスです。抽象クラスはインスタンス化できません。抽象クラスを継承する子クラスは、純粋仮想関数をオーバーライドする必要があります。
これらの概念を理解することで、C++でオブジェクト指向プログラミングを行うための基盤を築くことができます。クラス、オブジェクト、カプセル化、継承、ポリモーフィズムを組み合わせて、より複雑で洗練されたプログラムを作成してみましょう。
C++におけるポインタは、メモリのアドレスを格納する変数です。ポインタを理解し、適切にメモリを管理することは、効率的で安全なプログラムを作成するために非常に重要です。しかし、ポインタは扱いを間違えるとメモリリークや不正なメモリアクセスを引き起こす可能性があるため、注意が必要です。
-
ポインタの宣言: ポインタを宣言するには、変数の型名の後に
*
を付けます。int *ptr; // int型のポインタptrを宣言 double *dPtr; // double型のポインタdPtrを宣言
-
アドレス演算子
&
: 変数のアドレスを取得するには、アドレス演算子&
を使用します。int num = 10; int *ptr = # // ptrにnumのアドレスを代入
-
間接参照演算子
*
: ポインタが指すメモリの値を参照するには、間接参照演算子*
を使用します。int num = 10; int *ptr = # std::cout << *ptr << std::endl; // *ptrはnumの値(10)を出力 *ptr = 20; // ptrが指すメモリ(num)の値を20に変更 std::cout << num << std::endl; // numは20になる
-
ヌルポインタ: どのメモリ位置も指していないポインタをヌルポインタと呼びます。ヌルポインタは
nullptr
で表現します。int *ptr = nullptr; // ヌルポインタを代入 if (ptr == nullptr) { std::cout << "ポインタはヌルです。" << std::endl; }
C++では、new
演算子を使用して、プログラムの実行中に動的にメモリを割り当てることができます。割り当てられたメモリは、不要になったらdelete
演算子で解放する必要があります。
-
new
演算子: 指定された型のメモリをヒープ領域に割り当て、そのメモリのアドレスを返します。int *ptr = new int; // int型のメモリを動的に割り当て *ptr = 10; std::cout << *ptr << std::endl; // 10 int *arr = new int[5]; // int型の配列(要素数5)のメモリを動的に割り当て for (int i = 0; i < 5; i++) { arr[i] = i * 2; }
-
delete
演算子:new
演算子で割り当てられたメモリを解放します。delete ptr; // 単一のメモリを解放 ptr = nullptr; // 解放後、ポインタをヌルポインタに設定するのが推奨される delete[] arr; // 配列のメモリを解放 arr = nullptr;
注意点:
-
new
演算子で割り当てられたメモリは、必ずdelete
演算子で解放する必要があります。解放し忘れると、メモリリークが発生します。 - 配列を割り当てた場合は、
delete[]
演算子で解放する必要があります。delete
演算子のみを使用すると、メモリ破壊が発生する可能性があります。 - 解放済みのメモリに再度アクセスすると、未定義の動作を引き起こします。
- ポインタを使用する際は、必ず初期化し、解放後はヌルポインタに設定することを推奨します。
メモリリークは、プログラムが動的に割り当てられたメモリを解放し忘れることによって発生します。メモリリークが発生すると、使用可能なメモリが減少し、最終的にはプログラムがクラッシュしたり、システム全体のパフォーマンスが低下したりする可能性があります。
void memoryLeakExample() {
int *ptr = new int;
*ptr = 10;
// delete ptr; // 解放を忘れた!
// ptr = nullptr;
}
ダングリングポインタは、すでに解放されたメモリ位置を指しているポインタです。ダングリングポインタを使用してメモリにアクセスすると、未定義の動作を引き起こす可能性があります。
void danglingPointerExample() {
int *ptr = new int;
*ptr = 10;
delete ptr;
// ptr = nullptr; // 解放後、ヌルポインタに設定するべき
std::cout << *ptr << std::endl; // ダングリングポインタ!未定義の動作
}
C++11以降では、スマートポインタと呼ばれる、自動的にメモリを管理してくれる便利なクラスが導入されました。スマートポインタを使用することで、メモリリークやダングリングポインタのリスクを軽減できます。
-
std::unique_ptr
: 所有権が単一のポインタに限定されるスマートポインタです。unique_ptr
がスコープから外れると、自動的にメモリが解放されます。コピーは禁止されていますが、ムーブは可能です。#include <memory> void uniquePtrExample() { std::unique_ptr<int> ptr(new int); // または std::unique_ptr<int> ptr = std::make_unique<int>(); C++14以降 *ptr = 10; std::cout << *ptr << std::endl; // 10 // ptrがスコープを抜けると自動的にメモリが解放される }
-
std::shared_ptr
: 複数のポインタで共有できるスマートポインタです。参照カウンタを使用して、どのポインタもオブジェクトを指していない場合にメモリを解放します。#include <memory> void sharedPtrExample() { std::shared_ptr<int> ptr1 = std::make_shared<int>(10); std::shared_ptr<int> ptr2 = ptr1; // 参照カウンタが増加 std::cout << *ptr1 << std::endl; // 10 std::cout << *ptr2 << std::endl; // 10 // ptr1とptr2がスコープを抜けると、参照カウンタが0になり、自動的にメモリが解放される }
-
std::weak_ptr
:shared_ptr
が指すオブジェクトを参照するものの、オブジェクトの所有権には関与しないスマートポインタです。weak_ptr
は、オブジェクトがまだ存在するかどうかを確認するために使用できます。#include <memory> void weakPtrExample() { std::shared_ptr<int> sharedPtr = std::make_shared<int>(10); std::weak_ptr<int> weakPtr = sharedPtr; if (auto lockedPtr = weakPtr.lock()) { // オブジェクトがまだ存在するか確認 std::cout << *lockedPtr << std::endl; // 10 } else { std::cout << "オブジェクトはすでに破棄されています。" << std::endl; } }
スマートポインタを使用することで、手動でのメモリ管理を大幅に簡素化し、メモリ関連のエラーを減らすことができます。
- 可能な限りスマートポインタを使用する。
-
new
演算子とdelete
演算子を使用する場合は、メモリリークが発生しないように、必ずdelete
を呼び出す。 - メモリを解放した後、ポインタをヌルポインタに設定する。
- 配列を割り当てた場合は、
delete[]
演算子で解放する。 - メモリ割り当てと解放の処理を慎重に設計する。
- メモリリーク検出ツールを使用して、プログラム内のメモリリークを検出する。
ポインタとメモリ管理は、C++プログラミングの中でも難しい部分ですが、正しく理解し、適切に管理することで、より効率的で信頼性の高いプログラムを作成することができます。
C++標準ライブラリ(Standard Template Library, STL)は、C++で利用できる豊富なテンプレートクラスと関数群です。STLを活用することで、データ構造やアルゴリズムを効率的に実装し、コードの再利用性と保守性を高めることができます。
STLは主に以下の3つの要素で構成されています。
- コンテナ(Containers): データを格納するためのデータ構造です。配列、リスト、マップなど、さまざまな種類のコンテナが用意されています。
- イテレータ(Iterators): コンテナ内の要素を順番にアクセスするための機構です。ポインタのように振る舞い、コンテナの要素を走査することができます。
- アルゴリズム(Algorithms): コンテナ内の要素に対して特定の処理を行うための関数です。ソート、検索、コピーなど、さまざまなアルゴリズムが用意されています。
-
vector
: 動的配列です。要素へのランダムアクセスが可能で、末尾への要素の追加・削除が高速です。#include <iostream> #include <vector> int main() { std::vector<int> numbers; // int型のvectorを宣言 numbers.push_back(10); // 末尾に要素を追加 numbers.push_back(20); numbers.push_back(30); std::cout << "要素数: " << numbers.size() << std::endl; // 要素数: 3 for (int i = 0; i < numbers.size(); i++) { std::cout << numbers[i] << " "; // 10 20 30 } std::cout << std::endl; return 0; }
-
list
: 双方向連結リストです。要素の挿入・削除が高速ですが、ランダムアクセスは遅いです。#include <iostream> #include <list> int main() { std::list<std::string> names; // string型のlistを宣言 names.push_back("Alice"); // 末尾に要素を追加 names.push_front("Bob"); // 先頭に要素を追加 for (const auto& name : names) { // 範囲for文 std::cout << name << " "; // Bob Alice } std::cout << std::endl; return 0; }
-
deque
: 両端キューです。先頭と末尾への要素の追加・削除が高速で、ランダムアクセスも可能です。#include <iostream> #include <deque> int main() { std::deque<int> numbers; numbers.push_back(10); numbers.push_front(20); std::cout << numbers.front() << " " << numbers.back() << std::endl; // 20 10 return 0; }
-
set
: 要素を一意に格納するコンテナです。要素は自動的にソートされます。#include <iostream> #include <set> int main() { std::set<int> numbers; numbers.insert(30); numbers.insert(10); numbers.insert(20); numbers.insert(10); // 重複は無視される for (const auto& num : numbers) { std::cout << num << " "; // 10 20 30 } std::cout << std::endl; return 0; }
-
map
: キーと値のペアを格納するコンテナです。キーは一意である必要があり、要素はキーに基づいてソートされます。#include <iostream> #include <map> #include <string> int main() { std::map<std::string, int> scores; scores["Alice"] = 90; scores["Bob"] = 80; scores["Charlie"] = 70; std::cout << "Aliceのスコア: " << scores["Alice"] << std::endl; // Aliceのスコア: 90 return 0; }
イテレータは、コンテナ内の要素を指し示すオブジェクトです。イテレータを使用することで、コンテナの種類に関わらず、同じ方法で要素を走査することができます。
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {10, 20, 30, 40, 50};
// イテレータを使って要素を走査
for (std::vector<int>::iterator it = numbers.begin(); it != numbers.end(); ++it) {
std::cout << *it << " "; // 10 20 30 40 50
}
std::cout << std::endl;
// 範囲for文(C++11以降)を使うと、より簡潔に記述できる
for (const auto& num : numbers) {
std::cout << num << " "; // 10 20 30 40 50
}
std::cout << std::endl;
return 0;
}
-
std::sort
: コンテナ内の要素をソートします。#include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> numbers = {30, 10, 50, 20, 40}; std::sort(numbers.begin(), numbers.end()); // 昇順にソート for (const auto& num : numbers) { std::cout << num << " "; // 10 20 30 40 50 } std::cout << std::endl; return 0; }
-
std::find
: コンテナ内の要素を検索します。#include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> numbers = {10, 20, 30, 40, 50}; auto it = std::find(numbers.begin(), numbers.end(), 30); if (it != numbers.end()) { std::cout << "要素が見つかりました: " << *it << std::endl; // 要素が見つかりました: 30 } else { std::cout << "要素は見つかりませんでした。" << std::endl; } return 0; }
-
std::copy
: コンテナの要素を別のコンテナにコピーします。#include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> numbers1 = {10, 20, 30}; std::vector<int> numbers2(numbers1.size()); // コピー先のvectorを準備 std::copy(numbers1.begin(), numbers1.end(), numbers2.begin()); for (const auto& num : numbers2) { std::cout << num << " "; // 10 20 30 } std::cout << std::endl; return 0; }
-
std::transform
: コンテナの要素に対して関数を適用し、結果を別のコンテナに格納します。#include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> numbers1 = {1, 2, 3, 4, 5}; std::vector<int> numbers2(numbers1.size()); std::transform(numbers1.begin(), numbers1.end(), numbers2.begin(), [](int x){ return x * 2; }); for (const auto& num : numbers2) { std::cout << num << " "; // 2 4 6 8 10 } std::cout << std::endl; return 0; }
STLは、C++プログラミングにおいて非常に強力なツールです。STLを理解し、活用することで、効率的で保守性の高いコードを作成することができます。
ここまでの知識を活かして、簡単なC++アプリケーションを開発してみましょう。今回は、コンソール上で動作するシンプルな「TODOリスト管理アプリ」を作成します。
- ユーザーは、TODOリストにタスクを追加できます。
- ユーザーは、TODOリストに登録されているタスクを確認できます。
- ユーザーは、TODOリストからタスクを削除できます。
- ユーザーは、タスクを完了済みとしてマークできます。
- C++の基本構文(変数、データ型、演算子)
- 制御構造(条件分岐、繰り返し)
- 関数
- 標準ライブラリ(
iostream
,vector
,string
)
#include <iostream>
#include <vector>
#include <string>
#include <algorithm> // std::remove
// タスクを表す構造体
struct Task {
std::string description;
bool completed;
};
// タスクを追加する関数
void addTask(std::vector<Task>& todoList, const std::string& description) {
Task newTask;
newTask.description = description;
newTask.completed = false;
todoList.push_back(newTask);
std::cout << "タスクを追加しました: " << description << std::endl;
}
// タスクを表示する関数
void viewTasks(const std::vector<Task>& todoList) {
if (todoList.empty()) {
std::cout << "TODOリストは空です。" << std::endl;
return;
}
for (size_t i = 0; i < todoList.size(); ++i) {
std::cout << i + 1 << ". ";
if (todoList[i].completed) {
std::cout << "[完了] ";
} else {
std::cout << "[未完了] ";
}
std::cout << todoList[i].description << std::endl;
}
}
// タスクを完了済みにする関数
void completeTask(std::vector<Task>& todoList, int taskIndex) {
if (taskIndex >= 0 && taskIndex < todoList.size()) {
todoList[taskIndex].completed = true;
std::cout << "タスクを完了済みにしました: " << todoList[taskIndex].description << std::endl;
} else {
std::cout << "無効なタスク番号です。" << std::endl;
}
}
// タスクを削除する関数
void removeTask(std::vector<Task>& todoList, int taskIndex) {
if (taskIndex >= 0 && taskIndex < todoList.size()) {
std::cout << "タスクを削除しました: " << todoList[taskIndex].description << std::endl;
todoList.erase(todoList.begin() + taskIndex); // erase関数を使用
} else {
std::cout << "無効なタスク番号です。" << std::endl;
}
}
int main() {
std::vector<Task> todoList;
int choice;
do {
std::cout << "\nTODOリスト管理アプリ" << std::endl;
std::cout << "1. タスクを追加" << std::endl;
std::cout << "2. タスクを表示" << std::endl;
std::cout << "3. タスクを完了" << std::endl;
std::cout << "4. タスクを削除" << std::endl;
std::cout << "0. 終了" << std::endl;
std::cout << "選択: ";
std::cin >> choice;
std::cin.ignore(); // 改行文字を読み飛ばす
switch (choice) {
case 1: {
std::string description;
std::cout << "タスクの説明: ";
std::getline(std::cin, description); // スペースを含む文字列を読み込む
addTask(todoList, description);
break;
}
case 2:
viewTasks(todoList);
break;
case 3: {
int taskIndex;
std::cout << "完了するタスクの番号: ";
std::cin >> taskIndex;
completeTask(todoList, taskIndex - 1); // インデックスは0から始まるため-1する
break;
}
case 4: {
int taskIndex;
std::cout << "削除するタスクの番号: ";
std::cin >> taskIndex;
removeTask(todoList, taskIndex - 1); // インデックスは0から始まるため-1する
break;
}
case 0:
std::cout << "終了します。" << std::endl;
break;
default:
std::cout << "無効な選択です。" << std::endl;
}
} while (choice != 0);
return 0;
}
-
struct Task
: タスクの情報を格納するための構造体です。description
(タスクの説明)とcompleted
(完了フラグ)の2つのメンバを持っています。 -
addTask()
関数: TODOリストに新しいタスクを追加します。 -
viewTasks()
関数: TODOリストに登録されているタスクを表示します。完了済みのタスクには「[完了]」、未完了のタスクには「[未完了]」と表示します。 -
completeTask()
関数: 指定されたタスクを完了済みにします。 -
removeTask()
関数: 指定されたタスクをTODOリストから削除します。std::vector::erase()
メソッドを使用します。 -
main()
関数: メインループです。ユーザーからの入力を受け付け、対応する処理を実行します。
上記のコードをtodo.cpp
という名前で保存し、以下のコマンドでコンパイルします。
g++ todo.cpp -o todo
コンパイルが成功したら、以下のコマンドで実行します。
./todo
TODOリスト管理アプリ
1. タスクを追加
2. タスクを表示
3. タスクを完了
4. タスクを削除
0. 終了
選択: 1
タスクの説明: 本を読む
タスクを追加しました: 本を読む
TODOリスト管理アプリ
1. タスクを追加
2. タスクを表示
3. タスクを完了
4. タスクを削除
0. 終了
選択: 2
1. [未完了] 本を読む
TODOリスト管理アプリ
1. タスクを追加
2. タスクを表示
3. タスクを完了
4. タスクを削除
0. 終了
選択: 3
完了するタスクの番号: 1
タスクを完了済みにしました: 本を読む
TODOリスト管理アプリ
1. タスクを追加
2. タスクを表示
3. タスクを完了
4. タスクを削除
0. 終了
選択: 2
1. [完了] 本を読む
TODOリスト管理アプリ
1. タスクを追加
2. タスクを表示
3. タスクを完了
4. タスクを削除
0. 終了
選択: 4
削除するタスクの番号: 1
タスクを削除しました: 本を読む
TODOリスト管理アプリ
1. タスクを追加
2. タスクを表示
3. タスクを完了
4. タスクを削除
0. 終了
選択: 2
TODOリストは空です。
TODOリスト管理アプリ
1. タスクを追加
2. タスクを表示
3. タスクを完了
4. タスクを削除
0. 終了
選択: 0
終了します。
このアプリケーションをさらに発展させるために、以下の機能を追加することを検討してみてください。
- タスクの優先度を設定する機能
- タスクの期限を設定する機能
- タスクをファイルに保存・読み込みする機能
- GUI (Graphical User Interface) を追加して、より使いやすいアプリケーションにする
この簡単なTODOリスト管理アプリを開発することで、C++の知識を実践的に活用し、アプリケーション開発の基礎を学ぶことができます。ぜひ、自分自身のアイデアを加えて、さらに機能豊富なアプリケーションに挑戦してみてください。
C++の学習は奥深く、継続的な学習が必要です。初心者から上級者まで、レベルに合わせた書籍やオンラインリソースを活用することで、効率的にスキルアップできます。
-
書籍:
- 明快C++入門編: C++の基礎をわかりやすく解説している入門書です。プログラミング初心者でも無理なく学習を進められます。豊富な図解と丁寧な説明が特徴です。
- スッキリわかるC++入門: C++の基本構文からオブジェクト指向プログラミングまで、幅広い内容をカバーしています。サンプルコードが豊富で、手を動かしながら学習できます。
- 独習C++: 網羅的な内容でC++の基礎をしっかりと学習できます。練習問題も充実しており、理解度を確認しながら進められます。
-
オンラインリソース:
- Progate: ブラウザ上でC++の基本を学習できるオンライン学習サービスです。イラストやアニメーションが多く、視覚的に理解しやすいのが特徴です。
- ドットインストール: 3分動画でC++の基礎を学習できます。短時間で効率的に知識を習得したい方におすすめです。
- paizaラーニング: ブラウザ上でコードを書きながらC++を学習できるオンライン学習プラットフォームです。実践的な課題を通してスキルアップできます。
-
書籍:
- Effective C++: C++プログラミングにおけるベストプラクティスや注意点を解説した書籍です。より効率的で安全なコードを書くための知識を習得できます。
- More Effective C++: Effective C++の続編で、さらに高度なC++のテクニックを解説しています。
- プログラミング言語C++: C++の設計者であるBjarne Stroustrup自身が執筆した書籍です。C++の深い理解を得るのに役立ちます。
-
オンラインリソース:
- cpprefjp – C++日本語リファレンス: C++の標準ライブラリに関する詳細な情報が掲載されている日本語リファレンスサイトです。
- C++ Core Guidelines: C++のコードを作成するためのガイドラインです。安全で効率的なC++コードを書くための指針を提供します。
- LeetCode: プログラミングの練習問題が豊富に掲載されているオンラインプラットフォームです。C++を使ってアルゴリズムやデータ構造のスキルを磨くことができます。
-
書籍:
- Modern C++ Design: テンプレートメタプログラミングやデザインパターンなど、高度なC++のテクニックを解説した書籍です。
- C++ Concurrency in Action: C++における並行処理に関する書籍です。マルチスレッドプログラミングの知識を深めることができます。
-
オンラインリソース:
- Stack Overflow: プログラミングに関する質問と回答が集まるQ&Aサイトです。C++に関する高度な質問や問題解決に役立ちます。
- GitHub: オープンソースのC++プロジェクトが多く公開されています。コードを読んだり、コントリビューションすることで、実践的なスキルを向上させることができます。
- C++Now: C++に関する最新情報を発信するカンファレンスのウェブサイトです。講演資料や動画などが公開されています。
- 基礎をしっかりと理解する: C++の基本構文やオブジェクト指向プログラミングの概念をしっかりと理解することが重要です。
- 手を動かしてコードを書く: 書籍やオンラインリソースを読むだけでなく、実際にコードを書いて実行することで、理解を深めることができます。
- エラーメッセージを理解する: コンパイルエラーや実行時エラーが発生した場合、エラーメッセージをよく読み、原因を特定して修正することが重要です。
- コミュニティに参加する: C++のコミュニティに参加することで、他のプログラマーと交流したり、質問したりすることができます。
- 継続的に学習する: C++は常に進化しているため、最新の情報をキャッチアップし、継続的に学習することが重要です。
- コンパイラ: 常に最新版のコンパイラを使用することで、最新のC++規格に対応したコードを記述できます。
- デバッガ: デバッガを使用することで、プログラムの実行中に変数の値やメモリの状態を調べることができます。
- IDE: Visual Studio Code, CLionなどのIDEを使用すると、C++の開発効率が向上します。
これらの書籍やオンラインリソースを参考に、自分に合った学習方法を見つけて、C++のスキルアップを目指しましょう。