コンテンツにスキップ

C++ の基礎 Day 5

1. 名前空間

1.1 名前空間を定義する

  • 変数や関数の名前の衝突を避けるために、名前空間を使うことができる。
  • 名前空間は、namespace(ネームスペース)キーワードを使って定義する。
  • 名前空間の指定には、スコープ解決演算子 ::(コロンコロン)を使う。
  • 同じ名前空間の中では、名前空間を指定しなくてもよい。
  • 標準ライブラリの関数やクラスは、std 名前空間に定義されている。

#include <print>
#include <string>

namespace cat
{
	std::string name = "Tama";

	void F()
	{
		std::println("{}", name);
	}
}

namespace dog
{
	std::string name = "Pochi";

	void F()
	{
		std::println("{}", name);
	}
}

int main()
{
	cat::F();
	dog::F();
}
出力
Tama
Pochi

1.2 名前空間のネスト

  • 名前空間はネストできる。

#include <print>
#include <string>

namespace animal
{
	namespace cat
	{
		std::string name = "Tama";

		void F()
		{
			std::println("{}", name);
		}
	}
}

int main()
{
	animal::cat::F();
}
出力
Tama

  • 名前空間のネストは次のようにまとめて書くこともできる。

#include <print>
#include <string>

namespace animal::cat
{
	std::string name = "Tama";

	void F()
	{
		std::println("{}", name);
	}
}

int main()
{
	animal::cat::F();
}
出力
Tama

1.3 名前空間の using 宣言

  • 限られた範囲で、特定の名前空間の中の名前を頻繁に使う場合、using namespace(ユージング・ネームスペース)宣言を使うことで、名前空間の指定を省略できるようになる。
  • 名前の衝突のリスクを考慮して、できるだけ使わないようにすることが望ましい。

#include <print>
#include <string>

namespace cat
{
	std::string name = "Tama";

	void F()
	{
		std::println("{}", name);
	}
}

namespace dog
{
	std::string name = "Pochi";

	void F()
	{
		std::println("{}", name);
	}
}

int main()
{
	using namespace cat;
	F(); // cat:: を省略できる

	cat::F(); // 明示的に指定してもよい
	dog::F();
}
出力
Tama
Tama
Pochi

2. キャスト

  • C++ では C 言語スタイルのキャスト (Type)expression と、次の 4 種類の C++ スタイルのキャストがある。
    • static_cast<Type>(expression)(スタティックキャスト)
    • dynamic_cast<Type>(expression)(ダイナミックキャスト)
    • const_cast<Type>(expression)(コンストキャスト)
    • reinterpret_cast<Type>(expression)(リインタープリットキャスト)
  • double 型の変数の値を int 型の変数に代入する場合に使うキャストは static_cast

#include <print>

int main()
{
	double d = 3.14;
	int i = static_cast<int>(d);

	std::println("i: {}", i);
}
出力
i: 3

  • 派生関係のあるクラス間でポインタまたは参照をキャストする際は、確実に成功する場合(チェックが不要な場合)は static_cast を使い、実行時にチェックが必要な場合は dynamic_cast を使う。
  • dynamic_cast は失敗時に次のように動作する。
    • 参照のキャスト: std::bad_cast(バッドキャスト)例外を投げる。
    • ポインタのキャスト: nullptr(ヌルポインター)を得る。

#include <print>

struct Base
{
	virtual ~Base() = default;
};

struct Derived1 : Base
{

};

struct Derived2 : Base
{

};

int main()
{
	Base* p1 = new Derived1;
	Base* p2 = new Derived2;

	Derived1* d1 = static_cast<Derived1*>(p1);
	Derived2* d2 = static_cast<Derived2*>(p2);

	std::println("d1: {}", (void*)d1);
	std::println("d2: {}", (void*)d2);

	if (Derived1* d3 = dynamic_cast<Derived1*>(p2)) // Derived2* から Derived1* へのキャストはできない
	{
		std::println("d3: {}", (void*)d3);
	}
	else
	{
		std::println("d3 is nullptr");
	}
}
出力
d1: 0x...
d2: 0x...
d3 is nullptr

3. 関数

3.1 関数の基本

  • 関数は、関数名、引数リスト、戻り値の型、関数本体から構成される。

戻り値の型 関数名(引数リスト)
{
	関数本体

	return 戻り値;
}
int Add(int a, int b)
{
	return (a + b);
}

  • 引数がない場合は引数リストを省略できる。
  • 戻り値がない場合は、void(ヴォイド)型を指定する。
void Hello()
{
	std::println("Hello");
}
  • 複数の値を返す場合は、クラスやタプルを使う。

#include <print>
#include <tuple>

std::pair<int, int> F()
{
	return{ 1, 2 };
}

std::tuple<int, int, double> G()
{
	return{ 1, 2, 3.14 };
}

int main()
{
	auto [a, b] = F();
	std::println("a: {}, b: {}", a, b);

	auto [c, d, e] = G();
	std::println("c: {}, d: {}, e: {}", c, d, e);
}
出力
a: 1, b: 2
c: 1, d: 2, e: 3.14

3.2 デフォルト引数

  • 関数の引数にデフォルト値を指定することができる。
  • ある引数より前の引数にデフォルト値を指定する場合、その引数以降の引数にもデフォルト値を指定する必要がある。

#include <print>

void F(int a, int b = 10, int c = 20)
{
	std::println("a: {}, b: {}, c: {}", a, b, c);
}

int main()
{
	F(1);
	F(1, 2);
	F(1, 2, 3);
}
出力
a: 1, b: 10, c: 20
a: 1, b: 2, c: 20
a: 1, b: 2, c: 3

3.3 関数オーバーロード

  • 仮引数の型や個数が異なる場合、同じ関数名を使って複数の関数を定義することができる。
  • 関数を呼び出すときに、引数の型や個数によって、どの関数を呼び出すかが決定されるが、適した関数が見つからないか、複数の関数が適合する場合はコンパイルエラーとなる。

#include <print>

void F(int a)
{
	std::println("int: {}", a);
}

void F(double a)
{
	std::println("double: {}", a);
}

int main()
{
	F(1);
	F(3.14);
}
出力
int: 1
double: 3.14

3.4 関数の前方宣言

  • 関数を定義する前に、関数の宣言を書くことができる(前方宣言)。
  • 関数の宣言は、関数の戻り値の型、関数名、引数リストから構成される。
  • 関数の宣言を書けば、関数の定義がそのソースファイルの後ろや、別のソースファイル、あるいは外部ライブラリにあっても、その関数を使うことができる。

#include <print>

void F(int a);

int main()
{
	F(1);
}

void F(int a)
{
	std::println("a: {}", a);
}
出力
a: 1


  • 関数の定義が別のソースファイルにあるケース:

Main.cpp
#include <print>
#include "MyLib.hpp"

int main()
{
	F(1);
}
MyLib.hpp
#pragma once

void F(int a);
MyLib.cpp
#include <print>
#include "MyLib.hpp"

void F(int a)
{
	std::println("a: {}", a);
}
出力
a: 1

3.5 引数の種類

  • C++ では、関数に引数を渡す方法として、次の 3 種類がある。
    • 値渡し
    • 参照渡し
    • ポインタ渡し
  • ポインタのサイズ以下の小さなデータ(基本型など)は値渡しを使い、大きなデータは参照渡しを使うのが一般的。
  • 参照渡しとポインタ渡しは、仮引数経由で呼び出し側の実引数を変更することができる。それを禁止したい場合は、const(コンスト)修飾子を使う。
  • 参照渡しはポインタ渡しをラップしたもので、ヌルポインタを渡すことができない制約がある。
引数の種類 適した場面
値渡し 基本型(入力)、基本型に相当する小さなデータ (列挙型)、std::string_view
参照渡し クラス、基本型(出力)
ポインタ渡し ポインタで渡す必要があるデータ、ヌルポインタを許容する入出力

#include <print>
#include <string_view>
#include <string>
#include <vector>

int Add(int a, int b)
{
	return (a + b);
}

void Show(std::string_view s)
{
	std::println("{}", s);
}

void Swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}

void Show(const std::vector<int>& v)
{
	std::print("[");

	bool first = true;

	for (int x : v)
	{
		if (!first)
		{
			std::print(", ");
		}

		std::print("{}", x);

		first = false;
	}

	std::println("]");
}

int main()
{
	int a = 1;
	int b = 2;

	std::println("Add: {}", Add(a, b));

	std::string s = "Hello";
	Show(s);

	Swap(a, b);
	std::println("a: {}, b: {}", a, b);

	std::vector<int> v = { 1, 2, 3 };
	Show(v);
}
出力
Add: 3
Hello
a: 2, b: 1
[1, 2, 3]

4. 関数テンプレート

  • 関数テンプレートを使うことで、複数の型に対して同じような処理を行う関数を書く手間を省くことができる。
  • 任意の型の平方を返す関数テンプレートは次のように書ける。
    • T がテンプレートパラメータで、名前は任意。

#include <print>

template <class T>
T Square(T x)
{
	return (x * x);
}

int main()
{
	std::println("int: {}", Square(3));
	std::println("double: {}", Square(3.14));
}
出力
int: 9
double: 9.8596

  • 戻り値を auto で型推論させることもできる。型はコンパイル時に決定される。

#include <print>

template <class T, class U>
auto Add(T a, U b)
{
	return (a + b);
}

int main()
{
	std::println("int: {}", Add(1, 2));
	std::println("double: {}", Add(1.25, 2));
	std::println("double: {}", Add(1, 1.25));
	std::println("double: {}", Add(1.25, 1.25));
}
出力
int: 3
double: 3.25
double: 2.25
double: 2.5

  • 関数テンプレートは、引数の型を auto で指定して簡易的に書くことができる。
  • 上記のコードは次のように書き換えることができる。

#include <print>

auto Add(auto a, auto b)
{
	return (a + b);
}

int main()
{
	std::println("int: {}", Add(1, 2));
	std::println("double: {}", Add(1.25, 2));
	std::println("double: {}", Add(1, 1.25));
	std::println("double: {}", Add(1.25, 1.25));
}
出力
int: 3
double: 3.25
double: 2.25
double: 2.5

5. 列挙型

  • 関連する一連の定数(整数)をまとめて扱うための機能。
  • 次のサンプルでは、int 型を基底クラスとする列挙型 Language を定義している。
  • 列挙子は 0 から始まり、1 ずつ増加する。

#include <print>

enum class Language
{
	Japanese,	// 0
	English,	// 1
	French,		// 2
};

int main()
{
	Language lang = Language::Japanese;

	switch (lang)
	{
	case Language::Japanese:
		std::println("Japanese");
		break;
	case Language::English:
		std::println("English");
		break;
	case Language::French:
		std::println("French");
		break;
	}
}
出力
Japanese