C++
C++ とは & 使用用途
-
プログラミング言語
-
コンパイル言語であり、生成された実行ファイルは CPU 上で直接動作するため高速
-
C 言語の超拡張言語。3 年ごとにバージョンアップが行われ、次は C++26
-
身近なとこで使われています。身近過ぎて使われていることすら気づかない程です。
使用例
-
Chromium (Chrome, Edge 等ブラウザの基盤) https://github.com/chromium/chromium
-
clang (C++コンパイラ) https://github.com/llvm/llvm-project
-
V8 JavaScript エンジン https://github.com/v8/v8
Visual Studio の使い方
Microsoft 社が提供する統合開発環境 (IDE)
デバッグ機能 (変数の中を覗く、ステップ動作) が強力
🌟 プロジェクトを作成
Visual Studio を起動
保存場所を選択し、プロジェクト名を入力 (ソリューション名は同じで大丈夫です)
ソリューションとプロジェクトの関係
ソリューション:プロジェクトの集まり
プロジェクト:ソースコードの集まり
上記の写真の例では、次のような階層構造ができます。
ソリューションの開き方
ソリューションフォルダに含まれるソリューションファイル (.sln) を開くと、ソリューションが開かれます。
🌟 実行
再生ボタンを押すと、コンパイルと実行が行われます。
🌟 ステップ実行
行単位でプログラムを実行し、変数の中身を確認できます。
F10 キーを押すと、ステップ実行モードに入ります。F10 キーを押すたびに、次の行に進みます。
変数の上にカーソルを合わせると、その変数の中身を確認できます。
超入門編
-
🌟 main 関数について
-
🌟 変数
-
🌟 型
-
🌟 配列
-
🌟 配列の要素へのアクセス
-
🌟 標準出力
-
🌟 算術演算、比較演算、論理演算、ビット演算
🌟main 関数について
C++のプログラムの開始地点で、エントリーポイントとも呼ばれます。
main 関数の戻り値は実行環境側が受け取ります。
豆知識
実はプログラムの起点は main 関数ではありません。
実行環境を意識せず書けるように、mainから始まると決められているだけです!
↓ mainまでの呼び出し履歴 環境:MSVC(Visual Studio)
🌟 変数
値を保持しておく機能。メモリ上に保存されます。
グローバル変数とローカル変数の違い
グローバル変数:0 に初期化される
ローカル変数:初期化されない(ゴミの値が入ってる) → ローカル変数は必ず初期化!!!
🌟 型
変数の仕様を決めるもの。
auto 型
初期化値を見て、コンパイル時に型を推論してくれる機能。
uintx_t, intx_t 型
int
型や long
型などの型は実行環境によってサイズが変わります。サイズが変わると扱える値の範囲も変わりバグの素なので、常にサイズが一定な型が提供されています。
ビット数 | 符号無し整数 | 符号付き整数 |
---|---|---|
8 | uint8_t | int8_t |
16 | uint16_t | int16_t |
32 | uint32_t | int32_t |
64 | uint64_t | int64_t |
🌟 配列
-
変数の集まり
-
🌟 同じ型の値のみ サイズ固定
-
🌟 {} (集成体初期化)によってゼロクリアできる
よくあるゼロクリアのアンチパターン
memset を用いたゼロクリア(非効率)
クラスでこれをすると問題になる
C言語風 (ダメではない)
🌟 配列の要素へのアクセス
先頭の要素を読む
末尾の要素に書き込む
※0 番目から始まるので、末尾は(要素数-1)番目となることに注意
🌟 標準出力
🌟 算術演算
+
-
*
/
%
剰余算
かっこ
整数同士の除算に注意
整数同士の割り算は小数が切り捨てられるため、期待した答えと違う値になります。
次のように左辺か右辺のどちらかを小数にする必要があります。
因みに次のように浮動小数点型で結果を受たとしても、右辺が先に評価されるため、割り算の評価に影響せず無意味です。
🌟 比較演算
主に if 文で使われます。
==
!=
<
>
<=
>=
🌟 論理演算
主に if 文で使われ、複数の条件を判定したい場合に用います。
&&
||
!
🌟 ビット演算
&
|
^
~
<<
>>
各ビットに対して論理演算を行います。
論理演算演算は bool 型同士の演算なのに対し、ビット演算は整数型同士の演算です。
ポートの制御や、バイト列への変換などで使われます。
uint8_t a = 0b1010;
uint8_t b = 0b1100;
uint8_t AND = a & b; // AND == 0b1000
uint8_t OR = a | b; // OR == 0b1110
uint8_t XOR = a ^ b; // XOR == 0b0110
uint8_t NOT = ~a; // NOT == 0b0101
uint8_t LSH = a << 1; // LSH == 0b10100
uint8_t RSH = a >> 1; // RSH == 0b0101
演算は筆算を思い出すとわかりやすいです。
シフト演算はビットを左右にずらす演算です。変数の表現範囲からはみ出たビットは捨てられます。
初級編 (制御構文)
- 🌟 if 文
- 🌟 while 文
- 🌟 for 文
- 🌟 参照型
- 🌟 範囲 for 文
- 🌟 break 文
- 🌟 continue 文
- 🌟 switch 文
- 🌟 enum class
🌟 if 文
条件分岐をする機能。
if 文自体が条件式を評価するため、比較演算を使わない if 文も書けます。
else if 文を使うことで、条件を次々絞っていくように判定できます。
int value = 1;
if (value == 1)
{
// value が 1 の場合
}
else if (value < 10)
{
// value が 1 でなく、value が 10 未満の場合
}
Note
他の言語には elif
のような構文がありますが、C、C++ にはありません。
実は else if 文というものは存在せず、else
と if
文を組み合わせてあるように見せているだけです。
if 文のネストを深くしないコツ
実際のプログラムでは、条件分岐が複雑になり、ネストが深くなりがちです。
論理演算を組み合わせる方法もありますが、早期リターンを使うと複雑な処理の場合にも対応できます。
早期リターンは条件に合わない場合を先に処理し、条件に合う場合の処理を後に書く方法です。
int main()
{
int left = 0;
if (std::cin >> left)
{
int right = 0;
if (std::cin >> right)
{
if (right)
{
std::cout << left / right << std::endl;
}
else
{
std::cout << "[ERROR] right is zero" << std::endl;
}
}
else
{
std::cout << "[ERROR] invalid right" << std::endl;
}
}
else
{
std::cout << "[ERROR] invalid left" << std::endl;
}
}
int main()
{
int left = 0;
if (!(std::cin >> left))
{
std::cout << "[ERROR] invalid left" << std::endl;
return 1;
}
int right = 0;
if (!(std::cin >> right))
{
std::cout << "[ERROR] invalid right" << std::endl;
return 1;
}
if (right == 0)
{
std::cout << "[ERROR] right is zero" << std::endl;
}
std::cout << left / right << std::endl;
}
🌟 while 文
繰り返し処理をする機能です。
ロボコンでは通信の受信処理に使うことが多いです。
while 文でやりがちなバグ
ループ毎にカウントアップするようなプログラムの場合、カウントアップのタイミングを間違えると値の変化が予期しないものになります。
次に紹介する for 文を使うことでこのようなバグを防げます。
🌟 for 文
繰り返し処理をする機能です。主に配列を操作する際に使われます。
構文や動作が少々複雑ですが、カウンタ i の値の変化の仕方が分かれば大丈夫です。
配列の全要素にアクセスする場合、次のように書けます。
クイズ
🌟 参照型
次の範囲 for 文で参照を使うので、ここで紹介します。
参照は変数に別名をつける機能です。参照型の変数の値を変更すると、参照先の変数の値も変わります。
配列の要素に対しても同じく参照を使うことができます。
🌟 範囲 for 文
通常の for 文の進化系で、配列の全要素に簡単にアクセス出来ます。
範囲 for 文は配列の要素をコピーして取り出すため、このままでは配列の要素を書き換えることはできません。
int array[3] = { 10 , 20, 30 };
for (int value : array)
{
value = 0;
}
for (int value : array)
{
std::cout << value << std::endl;
}
参照を使うと配列の要素を書き換えられます。
int array[3] = { 10 , 20, 30 };
for (int& value : array)
{
value = 0;
}
for (int value : array)
{
std::cout << value << std::endl;
}
通常 for 文と範囲 for 文の使い分け
範囲 for 文は配列の全要素に同じような処理をする場合に使います。
通常の for 文とは違い、カウントアップする変数がないため、二つの配列を同時に処理することができません。このような場合は通常の for 文を使います。
範囲 for 文の正体
範囲 for 文は実は通常の for 文の糖衣構文です。
※ 糖衣構文:プログラムを楽に書けるように、本質的な部分を隠蔽した構文
↓ 変換後のイメージ
int array[3] = { 10 , 20, 30 };
for (int i = 0; i < 3; i++)
{
int value = array[i];
std::cout << value << std::endl;
}
※実際は次のようにイテレーターを使ったコードに変換されます。
🌟 break 文
繰り返し処理を途中で終了したい場合に使います。
int array[3] = { 10 , 20, 30, 40, 50 };
for (int value : array)
{
if (value == 40)
{
break;
}
std::cout << value << std::endl;
}
🌟 continue 文
繰り返し処理をスキップしたい場合に使います。
int array[3] = { 10 , 20, 30, 40, 50 };
for (int value : array)
{
if (value == 40)
{
continue; // 次のループへ
}
std::cout << value << std::endl;
}
🌟 switch 文
条件分岐をする機能です。if 文に似ていますが、条件が整数型のみです。モード切替や、状態遷移(自動制御)に使われます。
int mode = 1;
switch (mode)
{
case 0:
// mode が 0 の場合
break;
case 1:
// mode が 1 の場合
break;
default:
// mode が 0 でも 1 でもない場合
break;
}
break 忘れに注意
break を書かないと、次の case に進んでしまいます。
🌟 enum class
列挙型と呼ばれ、値に名前をつける機能です。switch 文と組み合わせて使うことが多いです。値に名前をつけることで、プログラムの可読性が向上します。
※ enum class と class は別物です。
enum class Mode
{
Start,
PickFruit,
GotoNext,
};
int main()
{
Mode mode = Mode::PickFruit;
switch (mode)
{
case Mode::Start:
// mode が Start の場合
break;
case Mode::Mode1:
// mode が Mode1 の場合
break;
case Mode::GotoNext:
// mode が GotoNext の場合
break;
}
}
中級編
- 🌟 関数
- 🌟 構造体 !超大事!
- 🌟 クラス
- 🌟 名前空間
- 🌟 インクルード
- 🌟 マクロ
- 🌟 プリプロセス時条件分岐
🌟 関数
処理をまとめて名前をつける機能です。同じ処理を何度も書いている場合、関数にまとめることでコードの重複を減らすことができます。
かっこ ()
の中は引数と呼ばれ、関数に渡す値を指定します。
処理結果は戻り値と呼ばれ、return
で返します。
戻り値が少しややこしいですが、計算の順番を意識すると理解しやすいです。
規格で初期化子 (=) の右辺が先に計算されると決まっているので、次のように計算されます。
複数の値を戻り値にしたい場合
※今は分からなくても大丈夫です。
関数は一つの値しか返すことができません。複数の値を返したい場合は構造体を使って一つの値にまとめるか、参照を使うことで実現できます。
C++ では構造体を使うことが多いです。参照型の方が理解しやすいですが、関数は処理が一方向になるような設計が望ましいため、参照型は避けることが多いです。
void divide(int a, int b, int& result, bool& success)
{
if (b == 0)
{
success = false;
return;
}
result = a / b;
success = true;
}
int main()
{
int result = 0;
bool success = false;
divide(10, 2, result, success);
if (success)
{
std::cout << result << std::endl;
}
else
{
std::cout << "divide by zero" << std::endl;
}
}
🌟 構造体
変数をグループ化する機能です。座標や、ピン番号など、関連する変数をまとめるときに使います。
型を自分で作る機能とも言えます。(ユーザー定義型)
変数名を単純にできるなど、デカすぎるメリットが数多くあります。構造体を進化させるとクラスになります。
メンバ変数には .
でアクセスします。
書き換えも変数同様にできます。
std::cout に構造体を渡す
※今は分からなくても大丈夫です。
構造体の値を出力する際、毎度メンバ変数を指定するのは面倒です。構造体に標準出力への <<
演算子をオーバーロードすることで、構造体をそのまま出力できるようになります。
🌟 クラス
次章で説明します!
🌟 名前空間
名前空間は変数や関数の名前の衝突を防ぐ機能です。主にライブラリ内で使われます。
:: で名前空間を指定します。std::cout
の std
が名前空間です。
namespace Robocon
{
int value = 100;
}
int main()
{
std::cout << Robocon::value << std::endl; // 100
}
🌟 インクルード
#
が頭につく命令はプリプロセッサ命令と呼ばれ、コンパイル前に実行される命令です。主にソースコードの置換を行う機能です。
インクルード文は他のファイルの内容を取り込む機能です。ファイル分割した際や、ライブラリの読み込みに使われます。通常はヘッダーファイル(.h, .hpp)をインクルードします。
// iostream standard header
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0
#ifndef _IOSTREAM_
#define _IOSTREAM_
#pragma pack(push, _CRT_PACKING)
#pragma warning(push, _STL_WARNING_LEVEL)
#pragma warning(disable : _STL_DISABLED_WARNINGS)
...
int main()
{
std::cout << "Hello, World!" << std::endl;
}
要らん知識
ただの文字列置き換えの為、ヘッダーファイル以外もインクルードできます。
このようにすると、data.txt の内容が data 配列に展開されます。
🌟 マクロ
主に定数の定義(C 言語のみ)に使われます。
マクロは定数だけでなく、関数のような使い方もできます。ただ型のチェックがされなかったり、予期しない置換が起こるため、マクロは嫌われています。
🌟 プリプロセス時条件分岐
条件によってソースコードを切り替える機能です。主にデバッグ用のログ出力や、プラットフォームによって処理を変える場合に使われます。
マクロの定義、未定義によって処理を変える場合
プラットフォームによって処理を変える場合