コンテンツにスキップ

第 11 回 | 2024-12-13

  • 関数について復習する
  • クラスとメンバ変数について学ぶ
  • 関数でクラスを使う方法を学ぶ
  • クラスを配列やハッシュテーブルで使う方法を学ぶ

1. 関数の復習

1.1 正方形の面積を求める関数

#include <iostream>

int SquareArea(int sideLength)
{
	return sideLength * sideLength;
}

int main()
{
	std::cout << SquareArea(3) << '\n';
}
出力
9

1.2 偶数であるかどうかを判定する関数

  • std::coutstd::boolalpha を渡しておくと、bool 型の値を true または false として出力できる。

#include <iostream>

bool IsEven(int n)
{
	return (n % 2) == 0;
}

int main()
{
	std::cout << std::boolalpha;
	std::cout << IsEven(3) << '\n';
	std::cout << IsEven(4) << '\n';
}
出力
false
true

1.3 大文字と小文字を入れ替える関数

#include <iostream>

char SwapCase(char ch)
{
	if (('a' <= ch) && (ch <= 'z'))
	{
		return (ch - 32);
	}
	else if (('A' <= ch) && (ch <= 'Z'))
	{
		return (ch + 32);
	}
	else
	{
		return ch;
	}
}

int main()
{
	std::cout << SwapCase('a') << '\n';
	std::cout << SwapCase('D') << '\n';
	std::cout << SwapCase('1') << '\n';
}
出力
A
d
1

2. クラスとメンバ変数

2.1 クラスを作る

  • 次のような文法で、0 個以上の変数をグループ化し、新しい型を作ることができる。
struct 型名
{
    メンバ変数;

    ...
};
  • こうして作った新しい型をクラスといい、そのクラスに含まれる個々の変数をメンバ変数という。
// 座標クラス
struct Position
{
	int x;
	int y;
};
// 色クラス
struct Color
{
	double r;
	double g;
	double b;
};
// 商品クラス
struct Item
{
	std::string name;
	int price;
};
// 日付クラス
struct Date
{
	int year;
	int month;
	int day;
};

2.2 クラスの変数

  • クラスは通常の型(int など)と同じように、その型の変数を作れる。
  • メンバ変数には .メンバ変数名 を使ってアクセスする。

#include <iostream>

// 色クラス
struct Color
{
	double r;
	double g;
	double b;
};

int main()
{
	Color color;
	color.r = 1.0;
	color.g = 0.5;
	color.b = 0.0;

	std::cout << color.r << ", " << color.g << ", " << color.b << '\n';
}
出力
1, 0.5, 0

  • クラスの変数をそのまま出力することはできない。
コンパイルエラーになるコード
#include <iostream>

struct Color
{
	double r;
	double g;
	double b;
};

int main()
{
	Color color;
	color.r = 1.0;
	color.g = 0.5;
	color.b = 0.0;

	std::cout << color << '\n'; // コンパイルエラー
}

2.3 (参考)クラスの変数をそのまま出力する方法

  • 本講義では詳しく扱わないが、クラスの変数をそのまま出力するためには、次のように演算子のオーバーロードという機能を使って、出力ストリームに対する演算子 << を定義する。

#include <iostream>

struct Color
{
	double r;
	double g;
	double b;

	friend std::ostream& operator<<(std::ostream& os, const Color& color)
	{
		return os << '(' << color.r << ", " << color.g << ", " << color.b << ')';
	}
};

int main()
{
	Color color;
	color.r = 0.1;
	color.g = 0.2;
	color.b = 0.3;

	std::cout << color << '\n';
}
出力
(0.1, 0.2, 0.3)

2.4 メンバ変数の初期値

  • メンバ変数は、= を使って初期値を決めておくことができる。

#include <iostream>

struct Color
{
	double r = 0.0;
	double g = 0.0;
	double b = 0.0;
};

int main()
{
	Color color; // .r も .g も .b も 0.0 で初期化される
	color.r = 1.0;

	std::cout << color.r << ", " << color.g << ", " << color.b << '\n';
}
出力
1, 0, 0

2.5 { } による初期化

  • クラスは、変数の作成時に { } を使ってすべてのメンバ変数に対して一斉に初期値を与えることができる。

#include <iostream>

struct Color
{
	double r = 0.0;
	double g = 0.0;
	double b = 0.0;
};

int main()
{
	Color color{ 0.1, 0.2, 0.3 }; // .r に 0.1, .g に 0.2, .b に 0.3 を代入

	std::cout << color.r << ", " << color.g << ", " << color.b << '\n';
}
出力
0.1, 0.2, 0.3

2.6 クラスと標準入力

  • 個々のメンバ変数に対して、これまでどおり std::cin を使って標準入力ができる。

#include <iostream>

struct Color
{
	double r = 0.0;
	double g = 0.0;
	double b = 0.0;
};

int main()
{
	Color color;
	std::cin >> color.r >> color.g >> color.b;

	std::cout << color.r << ", " << color.g << ", " << color.b << '\n';
}
入力
0.4 0.5 0.6
出力
0.4, 0.5, 0.6

2.7 代入

  • クラスの変数は、代入の記号 = を使って代入することができる。

#include <iostream>

struct Color
{
	double r = 0.0;
	double g = 0.0;
	double b = 0.0;
};

int main()
{
	Color color1{ 0.1, 0.2, 0.3 };
	Color color2{ 0.4, 0.5, 0.6 };

	color1 = color2;
	
	std::cout << color1.r << ", " << color1.g << ", " << color1.b << '\n';
}
出力
0.4, 0.5, 0.6

  • 変数を介さず、新しく構築したクラスをそのまま代入することもできる。

#include <iostream>

struct Color
{
	double r = 0.0;
	double g = 0.0;
	double b = 0.0;
};

int main()
{
	Color color1{ 0.1, 0.2, 0.3 };

	color1 = Color{ 0.7, 0.8, 0.9 };
	
	std::cout << color1.r << ", " << color1.g << ", " << color1.b << '\n';
}
出力
0.7, 0.8, 0.9

3. 関数でクラスを使う

3.1 関数の引数

  • クラスを関数の引数にすることができる。
  • クラスを関数の引数とする場合、型名の前後に const& を付けて const 参照渡しにするのが一般的。
    • そのほうが効率的な処理になることが多いため。
// よくない書き方
void Show(Color color)
{
	std::cout << "(" << color.r << ", " << color.g << ", " << color.b << ")\n";
}

// よい書き方
void Show(const Color& color)
{
	std::cout << "(" << color.r << ", " << color.g << ", " << color.b << ")\n";
}

#include <iostream>

struct Color
{
	double r = 0.0;
	double g = 0.0;
	double b = 0.0;
};

void Show(const Color& color)
{
	std::cout << "(" << color.r << ", " << color.g << ", " << color.b << ")\n";
}

int main()
{
	const Color color1{ 0.1, 0.2, 0.3 };
	const Color color2{ 0.4, 0.5, 0.6 };

	Show(color1);
	Show(color2);
}
出力
(0.1, 0.2, 0.3)
(0.4, 0.5, 0.6)

3.2 関数の戻り値

  • クラスを関数の戻り値にすることもできる。

#include <iostream>

struct Color
{
	double r = 0.0;
	double g = 0.0;
	double b = 0.0;
};

void Show(const Color& color)
{
	std::cout << "(" << color.r << ", " << color.g << ", " << color.b << ")\n";
}

Color MakeRed()
{
	return Color{ 1.0, 0.0, 0.0 };
}

int main()
{
	const Color color1{ 0.1, 0.2, 0.3 };
	const Color color2{ 0.4, 0.5, 0.6 };

	Show(color1);
	Show(color2);
	Show(MakeRed());
}
出力
(0.1, 0.2, 0.3)
(0.4, 0.5, 0.6)
(1, 0, 0)

4. クラスを配列やハッシュテーブルで使う

4.1 配列

  • クラスを配列の要素にできる。

#include <iostream>
#include <vector>
#include <string>

struct Prefecture
{
	std::string name;
	std::string capital;
};

int main()
{
	std::vector<Prefecture> prefectures =
	{
		{"北海道", "札幌"},
		{"青森", "青森"},
		{"沖縄", "那覇"},
	};

	prefectures.push_back(Prefecture{ "愛知", "名古屋" });

	for (const auto& prefecture : prefectures)
	{
		std::cout << prefecture.name << ": " << prefecture.capital << '\n';
	}
}
出力
北海道: 札幌
青森: 青森
沖縄: 那覇
愛知: 名古屋

4.2 ハッシュテーブル

  • クラスをハッシュテーブルの「値」として使うことができる。

#include <iostream>
#include <unordered_map>
#include <string>

struct Date
{
	int month;
	int day;
};

void Show(const Date& date)
{
	std::cout << date.month << " 月 " << date.day << " 日\n";
}

int main()
{
    // キー: 祝日名
    // 値: 日付
	std::unordered_map<std::string, Date> holidays;
	holidays["正月"] = Date{ 1, 1 };
	holidays["成人の日"] = Date{ 1, 10 };
	holidays["みどりの日"] = Date{ 5, 4 };
	holidays["こどもの日"] = Date{ 5, 5 };
	
	Show(holidays["こどもの日"]);
}
出力
5 月 5 日

5. 関数の発展的な使い方

5.1 同名の関数(オーバーロード)

  • 関数は、引数が異なれば同じ名前の関数を複数作って自動的に使い分けることができる。
  • これを関数のオーバーロードという。

#include <iostream>

struct Color
{
	double r = 0.0;
	double g = 0.0;
	double b = 0.0;
};

void Show(int n)
{
	std::cout << n << '\n';
}

void Show(double x)
{
	std::cout << x << '\n';
}

void Show(const Color& color)
{
	std::cout << "(" << color.r << ", " << color.g << ", " << color.b << ")\n";
}

int main()
{
	Show(123);
	Show(3.14);
	Show(Color{ 0.1, 0.2, 0.3 });
}
出力
123
3.14
(0.1, 0.2, 0.3)

5.2 デフォルト引数

  • 関数の引数で後ろから順に、デフォルト値を指定することができる。
  • 関数の呼び出しの際に、その引数を省略すると、デフォルト値が使われる。
  • これをデフォルト引数という。

#include <iostream>

void DrawLine(int length, char ch = '-')
{
	for (int i = 0; i < length; ++i)
	{
		std::cout << ch;
	}

	std::cout << '\n';
}

int main()
{
	DrawLine(10);
	DrawLine(20, '*');
}
出力
----------
********************

5.3 文字列や配列を引数に取る関数

  • std::stringstd::vector などのクラスを引数に取る関数を作ることができる。
  • これらも、クラスの一種であるため const 参照渡し が一般的。

#include <iostream>
#include <string>
#include <vector>

void Show(const std::string& str)
{
	std::cout << str << '\n';
}

void Show(const std::vector<int>& vec)
{
	for (const auto& n : vec)
	{
		std::cout << n << ' ';
	}

	std::cout << '\n';
}

int main()
{
	std::string s = "Hello, World!";
	std::vector<int> v = { 1, 2, 3, 4, 5 };

	Show(s);
	Show(v);
}
出力
Hello, World!
1 2 3 4 5

5.4 (参考)クラスのメンバ関数

  • この講義では詳しく扱わないが、クラスにはメンバ関数を作成できる。
  • .メンバ関数() のように、クラスの変数に対して直接呼び出すことができる。
  • メンバ関数は、そのクラスのメンバ変数にアクセスできる。
  • メンバ関数名は小文字で始めるのが一般的。

#include <iostream>

struct Point
{
	int x = 0;
	int y = 0;

	void show() const
	{
		std::cout << "(" << x << ", " << y << ")\n";
	}

	void clear()
	{
		x = 0;
		y = 0;
	}
};

int main()
{
	Point p{ 1, 2 };

	p.show();

	p.clear();

	p.show();
}
出力
(1, 2)
(0, 0)

6. グラフィックスプログラミングの開発環境

  • 第 10 回の講義で伝えたとおり、第 12 回の講義から、グラフィックスプログラミングを行うための開発環境が必要になります。
  • Siv3D のインストールを完了し、次のような画面までたどり着けていれば OK です。

✅ 振り返りチェックリスト

  • 関数について復習した
  • クラスとメンバ変数について学んだ
  • 関数でクラスを使う方法を学んだ
  • クラスを配列やハッシュテーブルで使う方法を学んだ

📝 課題

📝 提出課題

2024 年の話題やニュースに関連した情報を表現するクラスを作り、「引数または戻り値としてそのクラスを使う関数」と、「その関数の動作を実際に確認できる」プログラムを作ってください。標準入力(std::cin)を使う必要はありません。

コードまたはコードの共有 URL(Wandbox)を提出してください。

次の見本では、

  • サッカーのチームを表現するクラス Country を作り、そのクラスを使って「国名」「FIFA ランク」「(FIFA の)スコア」を表現しています。
  • そのクラスを使う、「チーム情報を表示する関数 ShowCountry」「2 つのチームの対戦をスコアに基づいて分析する関数 ShowGame」を作っています。
  • 実際に 2 つの国の情報を表示し、3 つの対戦について分析するサンプルプログラムを作っています。
  • 課題の参考として、配列ではなくハッシュテーブル(std::unordered_map)を使う場合のコードもコメントに記載しています。

見本: 2022 年に開催された「FIFA ワールドカップ」をテーマにした場合
#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>

// サッカーのチームを表現するクラス
struct Country
{
	// 国名
	std::string name;

	// FIFA ランク
	int fifaRank;

	// スコア
	double score;
};

// チーム情報を表示する関数
void ShowCountry(const Country& country)
{
	std::cout << "--------------------------------\n";
	std::cout << country.name << '\n';
	std::cout << "FIFA ランク " << country.fifaRank << " 位 (" << country.score << ")\n";
	std::cout << "--------------------------------\n";
}

// 対戦を分析する関数
void ShowGame(const Country& a, const Country& b)
{
	std::cout << "--------------------------------\n";
	std::cout << a.name << "(" << a.fifaRank << ") vs " << b.name << "(" << b.fifaRank << ")\n";

	double diff = (a.score - b.score);

	if (100.0 <= diff)
	{
		std::cout << a.name << "が勝つ可能性が高い\n";
	}
	else if (diff <= -100.0)
	{
		std::cout << b.name << "が勝つ可能性が高い\n";
	}
	else
	{
		std::cout << "同レベルの実力\n";
	}

	std::cout << "--------------------------------\n";
}

int main()
{
	// データの出典: https://www.fifa.com/fifa-world-ranking/men?dateId=id13792
	const std::vector<Country> countries =
	{
		{ "ブラジル", 1, 1841.3 },
		{ "ベルギー", 2, 1816.71 },
		{ "アルゼンチン", 3, 1773.88 },
		{ "日本", 24, 1559.54 },
	};

	ShowCountry(countries[0]);
	ShowCountry(countries[1]);

	ShowGame(countries[0], countries[1]);
	ShowGame(countries[1], countries[2]);
	ShowGame(countries[3], countries[2]);

	// 別解: ハッシュテーブルを使う場合
	/*
	std::unordered_map<std::string, Country> countries =
	{
		{ "ブラジル", { "ブラジル", 1, 1841.3 } },
		{ "ベルギー", { "ベルギー", 2, 1816.71 } },
		{ "アルゼンチン", { "アルゼンチン", 3, 1773.88 } },
		{ "日本", { "日本", 24, 1559.54 } },
	};

	ShowCountry(countries["ブラジル"]);
	ShowCountry(countries["ベルギー"]);

	ShowGame(countries["ブラジル"], countries["ベルギー"]);
	ShowGame(countries["ベルギー"], countries["アルゼンチン"]);
	ShowGame(countries["日本"], countries["アルゼンチン"]);
	*/
}
出力
--------------------------------
ブラジル
FIFA ランク 1 位 (1841.3)
--------------------------------
--------------------------------
ベルギー
FIFA ランク 2 位 (1816.71)
--------------------------------
--------------------------------
ブラジル(1) vs ベルギー(2)
同レベルの実力
--------------------------------
--------------------------------
ベルギー(2) vs アルゼンチン(3)
同レベルの実力
--------------------------------
--------------------------------
日本(24) vs アルゼンチン(3)
アルゼンチンが勝つ可能性が高い
--------------------------------