コンテンツにスキップ

C++ の基礎 Day 4

1. 範囲 for 文

  • ある Range(レンジ)に対する読みやすいループ構文。
  • ここでの Range は配列やコンテナ、ビューなどの連続したデータの集まり。
  • for (要素宣言: Range) という構文で、Range の要素に順番にアクセスする。

配列に対する範囲 for 文
#include <print>

int main()
{
	int a[] = { 1, 2, 3, 4, 5 };

	for (int i : a)
	{
		std::println("{}", i);
	}
}
出力
1
2
3
4
5


std::vector に対する範囲 for 文
#include <print>
#include <vector>

int main()
{
	std::vector<int> v = { 1, 2, 3, 4, 5 };

	for (int i : v)
	{
		std::println("{}", i);
	}
}
出力
1
2
3
4
5


std::string に対する範囲 for 文
#include <print>
#include <string>

int main()
{
	std::string s = "Hello";

	for (char c : s)
	{
		std::println("{}", c);
	}
}
出力
H
e
l
l
o


ビュー(std::views::reverse_view)に対する範囲 for 文
#include <print>
#include <ranges>
#include <vector>

int main()
{
	std::vector<int> v = { 1, 2, 3, 4, 5 };

	for (int i : v | std::views::reverse)
	{
		std::println("{}", i);
	}
}
出力
5
4
3
2
1


ビュー(std::views::take_view)に対する範囲 for 文
#include <print>
#include <ranges>
#include <vector>

int main()
{
	std::vector<int> v = { 1, 2, 3, 4, 5 };

	for (int i : v | std::views::take(3))
	{
		std::println("{}", i);
	}
}
出力
1
2
3

  • Range の要素が基本型でない場合、コピーのコストを避けるために、要素へは const(コンスト)参照でアクセスする。
  • 基本型の要素に対して const 参照でアクセスしても問題ないので、const auto&(コンストオートアンド)を基本の選択肢とすると良い。

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

int main()
{
	std::vector<std::string> v = { "Hello", "World" };

	for (const auto& s : v) // s は const std::string&
	{
		std::println("{}", s);
	}
}
出力
Hello
World

  • 要素を変更したい場合は、参照 auto&(オートアンド)でアクセスする。

#include <print>
#include <vector>

int main()
{
	std::vector<int> v = { 1, 2, 3, 4, 5 };

	for (auto& i : v) // i は int&
	{
		i *= 2;
	}

	for (const auto& i : v) // i は const int&
	{
		std::println("{}", i);
	}
}
出力
2
4
6
8
10

  • 範囲 for 文は、コンパイル時に次のような通常の for 文に展開される。
  • ループ内で要素数が増減すると __begin__end に齟齬が生じるため、範囲 for 文の中で要素数を変更することはできない。
// for (item-declaration : range-initializer) statement

{
	auto&& __range = range-initializer;
	auto __begin = std::begin(__range);
	auto __end = std::end(__range);
	
	for ( ; __begin != __end; ++__begin)
	{
		item-declaration = *__begin;
		statement
	}
}

2. 配列

  • 同じ型の複数の要素を連続したメモリ領域に格納するデータ構造。
  • 通常はスタック領域に確保される。
  • 配列の要素数はコンパイル時に決定され、実行時に要素数を変更することはできない。

#include <print>

int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };

	for (const auto& i : a)
	{
		std::println("{}", i);
	}
}
出力
1
2
3
4
5

3. std::array(アレイ)

  • 配列を C++ のコンテナインタフェースに適合するようラップしたクラス。
  • 通常はスタック領域に確保される。
  • .size()(サイズ)や .begin()(ビギン)、.end()(エンド)などの便利なメンバ関数を持つ。
  • 実行時性能は配列と同じなので、C++ では配列よりも std::array を使うことが推奨される。

#include <print>
#include <array>

int main()
{
	std::array<int, 5> a = { 1, 2, 3, 4, 5 };

	std::println("size: {}", a.size());

	for (const auto& i : a)
	{
		std::println("{}", i);
	}

	a.fill(0);

	for (const auto& i : a)
	{
		std::println("{}", i);
	}
}
出力
size: 5
1
2
3
4
5
0
0
0
0
0

4. std::vector(ベクター)

4.1 概要

  • 要素数を動的に変更できる配列。
  • 通常の配列と同様、同じ型の要素を連続したメモリ領域に格納する。
  • メモリはヒープ領域に確保される。
  • 初期状態でのメモリは、必要な要素数分だけ確保される。要素数が増えると、より大きなメモリ領域が確保され、既存の要素は新しい領域にコピーされる。この際、新たに確保するメモリ領域のサイズは通常、旧領域の 1.5 倍から 2 倍に設定されており、これによって頻繁なメモリの再確保を抑制している。.push_back()(プッシュバック)による要素追加は、償却計算量(ならし計算量) O(1)。
  • std::vector の要素は、常に、確保したメモリ領域の先頭から連続して配置される。配列の末尾への要素の追加や削除は、他の要素の移動が不要であるため高速(末尾への追加は償却計算量 O(1), 末尾からの削除は O(1))。

  • 一方で、配列の先頭または途中での要素の追加・削除は、それ以降の要素が移動する必要があるため、非効率(計算量 O(N))。

  • 基本的に、std::vector は末尾での要素の追加・削除を高速に行う設計となっている。

4.2 std::vector の構築

  • リスト { ... } に用意した値から std::vector を構築する。
  • リスト内の値は std::vector<Type> の要素の型 Type に合わせる。
#include <vector>
#include <string>

int main()
{
	std::vector<int> v1 = { 1, 2, 3, 4, 5 };
	std::vector<std::string> v2 = { "Hello", "World" };
}
  • std::vector<Type> v(n, value); は、n 個の Type(value) からなる配列を構築する。
#include <vector>
#include <string>

int main()
{
	std::vector<int> v1(5, -1); // { -1, -1, -1, -1, -1 }
	std::vector<std::string> v2(3, "Hello"); // { "Hello", "Hello", "Hello" }
}
  • std::vector<Type> v(n); は、n 個の Type() からなる配列を構築する。
  • Type() は型によって次のような値になる。
Type()
bool() false
int() 0
double() 0.0
std::string() 空(要素数が 0)の文字列
std::vector<int>() 空(要素数が 0)の vector
std::pair<int, int>() std::pair<int, int>(0, 0)
struct Point { int x, y; };
のときの
Point()
Point{ 0, 0 }
struct Point { int x = -1, y = -1; };
のときの
Point()
Point{ -1, -1 }
#include <vector>
#include <string>

int main()
{
	std::vector<int> v1(5); // { 0, 0, 0, 0, 0 }
	std::vector<std::string> v2(3); // { "", "", "" }
}
  • 別の std::vector<Type> オブジェクトをコピーして新しい std::vector<Type> を構築できる。
  • コピー元とコピー先の要素の型 Type は一致している必要がある。
#include <vector>
#include <string>

int main()
{
	std::vector<int> v1 = { 1, 2, 3, 4, 5 };
	std::vector<int> v2 = v1; // { 1, 2, 3, 4, 5 }

	std::vector<std::string> v3 = { "Hello", "World" };
	std::vector<std::string> v4 = v3; // { "Hello", "World" }
}
  • std::vector 型のデフォルトコンストラクタは、要素数が 0 である空の std::vector を構築する。

#include <print>
#include <vector>

int main()
{
	std::vector<int> v;

	std::println("size: {}", v.size()); // size: 0

	for (const auto& i : v)
	{
		// 1 回も実行されない
		std::println("{}", i);
	}
}
出力
size: 0

4.3 std::vector<Type> の主要なメンバ関数

メンバ関数 戻り値 説明
.size() size_t 要素数を返す。
.capacity() size_t 内部で確保しているメモリ領域に格納可能な要素数を返す。
.reserve(size_t n) void 最低でも n 個の要素を格納できるメモリ領域を確保する。
.empty() bool 要素数が 0 なら true、それ以外なら false を返す。
.clear() void 要素を全て削除し、要素数を 0 にする。
.resize(size_t n) void 要素数を n に変更する。
.push_back(const Type& value) void 末尾に value を追加する。
.pop_back() void 末尾の要素を削除する。
.insert(std::vector<Type>::iterator pos, const Type& value) std::vector<Type>::iterator pos の位置に value を挿入する。挿入後、挿入された要素を指すイテレータを返す。
.erase(std::vector<Type>::iterator pos) std::vector<Type>::iterator pos の位置の要素を削除する。削除後、削除された要素の次の要素を指すイテレータを返す。
.erase(std::vector<Type>::iterator first, std::vector<Type>::iterator last) std::vector<Type>::iterator [first, last) の範囲の要素を削除する。削除後、削除された要素の次の要素を指すイテレータを返す。
.begin() std::vector<Type>::iterator 配列の先頭位置を指すイテレータを返す。
.end() std::vector<Type>::iterator 配列の末尾の終端位置を指すイテレータを返す。
.rbegin() std::vector<Type>::reverse_iterator 配列の末尾位置を指す逆イテレータを返す。
.rend() std::vector<Type>::reverse_iterator 配列の先頭の前を指す逆イテレータを返す。
.operator[](size_t i) Type& i 番目の要素にアクセスする。
.front() Type& 先頭の要素にアクセスする。v[0] と同じ。
.back() Type& 末尾の要素にアクセスする。v[v.size() - 1] と同じ。

#include <print>
#include <vector>

int main()
{
	std::vector<int> v = { 1, 2, 3, 4, 5 };

	std::println("size: {}", v.size());

	std::println("v[2]: {}", v[2]);

	v.push_back(6);

	std::println("size: {}", v.size());

	v.erase(v.begin() + 2);

	for (const auto& i : v)
	{
		std::println("{}", i);
	}
}
出力
size: 5
v[2]: 3
size: 6
1
2
4
5
6

5. std::badic_string(ベーシック・ストリング)

5.1 概要

  • 標準ライブラリには、任意の文字型 Char に対して、便利な文字列処理を提供するためのクラステンプレート std::basic_string<Char> がある。
  • 機能はおおよそ std::vector<Char> と同じで、文字列を格納するための動的配列として使える。
文字型 クラステンプレート 別名
char std::basic_string<char> std::string
wchar_t std::basic_string<wchar_t> std::wstring(ダブリューストリング)
char8_t std::basic_string<char8_t> std::u8string(ユーエイトストリング)
char16_t std::basic_string<char16_t> std::u16string(ユーじゅうろくストリング)
char32_t std::basic_string<char32_t> std::u32string(ユーさんじゅうにストリング)
  • この中でも std::string が C++ で最もよく使われる文字列クラス。
  • std::string の内部実装を簡易的に表現した例:
struct string
{
	char* m_ptr; // 確保したメモリの先頭を指すポインタ
	size_t m_size; // 格納している文字列の長さ
	size_t m_allocated_capacity; // 動的確保した配列に格納できる文字列の最大の長さ
};
より発展的な実装

現実の文字列処理のプログラムでは、"hello""dog", "atcoder" のように、数文字程度の短い文字列をたくさん扱うことが多い。そのような場合、std::string が毎回配列の動的確保(内部での new による確保)を行っていると、プログラムの実行速度が低下してしまう。そこで、一般的な std::basic_string の実装者は、文字列の長さが短い場合にメモリの動的確保を行わなくて済むよう、std::string 内に小さな配列を用意し、そこに文字列を格納する工夫を行っている。これを SSO (Small String Optimization) と呼ぶ。

struct string
{
	char* m_ptr; // 確保したメモリの先頭を指すポインタ
	size_t m_size; // 格納している文字列の長さ
	size_t m_allocated_capacity; // 動的確保した配列に格納できる文字列の最大の長さ
	
	static constexpr size_t LocalCapacity = 15; // 動的確保不要で格納できる文字列の最大の長さ
	char m_local_buffer[LocalCapacity + 1]; // 文字列の長さが LocalCapacity 以下の場合、動的確保の代わりに使う配列
};

m_local_buffer は、文字列の長さが LocalCapacity 以下の場合に使うことのできる配列。

LocalCapacity の値は、実装者の方針によって異なるが、一般には 15 程度になっています。この値が大きいほど、より長い文字列をメモリの動的確保を行わずに格納できるが、std::basic_string クラス自体のサイズが大きくなり、空の文字列や 1 文字の文字列を格納したいだけのときも大きなメモリを消費してしまう。

文字列の格納に m_local_buffer を使っている場合、m_ptrm_local_buffer の先頭を指す。そのときの文字列の .capacity()LocalCapacity

struct string
{
	char* m_ptr; // 確保したメモリの先頭を指すポインタ
	size_t m_size; // 格納している文字列の長さ
	size_t m_allocated_capacity; // 動的確保した配列に格納できる文字列の最大の長さ
	
	static constexpr size_t LocalCapacity = 15; // 動的確保不要で格納できる文字列の最大の長さ
	char m_local_buffer[LocalCapacity + 1]; // 文字列の長さが LocalCapacity 以下の場合、動的確保の代わりに使う配列

	size_t capacity() const noexcept
	{
		if (_is_local())
		{
			return LocalCapacity;
		}
		else
		{
			return m_allocated_capacity;
		}
	}

	// 内部関数: 文字列が m_local_buffer に格納されているかどうかを返す
	bool _is_local() const noexcept
	{
		return (m_ptr == m_local_buffer);
	}
};

さらに消費メモリを節約するため、union を用いて m_local_bufferm_allocated_capacity を同じメモリ領域に配置することが多い。実際、m_local_buffer を使用しているときは m_allocated_capacity は使わないため、合理的である。前述の string の実装と比べると、sizeof(string) は 40 バイトから 32 バイトに減る。gcc の標準ライブラリではこの方式の実装が採用されている。

```cpp struct string { char* m_ptr; // 確保したメモリの先頭を指すポインタ size_t m_size; // 格納している文字列の長さ

static constexpr size_t LocalCapacity = 15; // 動的確保不要で格納できる文字列の最大の長さ

union
{
    char m_local_buffer[LocalCapacity + 1]; // 文字列の長さが LocalCapacity 以下の場合、動的確保の代わりに使う配列
    size_t m_allocated_capacity; // 動的確保した配列に格納できる文字列の最大の長さ
} m_storage;

};

  • "hello" という文字列を格納する std::string の中身の例(16 バイトのメモリが確保されていて、5 文字の文字列を格納している)。

  • 確保された一定のサイズの char 型の配列の先頭から、文字列の長さ("hello" の場合は 5)分を消費して、文字列の各要素を、連続して格納する。
  • 文字列を格納し終わった直後(ここでは o の直後)の要素には、必ず NULL 終端文字 '\0' が格納される。
  • 「文字列の長さ」というときには、NULL 終端文字 '\0' の分は含まない。
  • 配列の NULL 終端以降の要素の状態は不定。

  • 何も文字列を格納していない、空の状態の std::string の中身の例。

  • 格納する文字列の長さが 0 だとしても、最後の文字の直後(ここでは配列の先頭)に NULL 終端文字 '\0' を必ず格納するため、一定のサイズの配列は確保されている。

  • m_allocated_capacity と同じ長さの文字列を格納している std::string の中身。

  • この状態からさらに新しい要素を追加しようとすると、配列の再確保が行われる。より広い m_allocated_capacity の値で配列を再確保し、既存の文字列を新しい配列にコピーしたのち、新しい追加の文字列が格納される。古い配列が使っていたメモリは解放される。

5.2 C 言語の文字列との相互運用性

  • C++ の std::string クラスは、配列の最後に NULL 終端文字 '\0' を持ち、C 言語の文字列と簡単に相互運用できるように設計されている。
  • メンバ関数 .c_str() は、C 言語の文字列(char 型の配列に NULL 終端文字 '\0' が付いたもの。5.1 の m_ptr)を返す。このメンバ関数を使用することで、std::string を、NULL 終端された文字列を要求する C 言語の関数でも使用できる。

#include <string>
#include <cstdio>

int main()
{
	std::string s = "Hello";
	printf("%s\n", s.c_str());
}
出力
Hello

  • C 言語の文字列から std::string に変換するのは簡単。
std::string word = "hello";
const char* s = "hello";
std::string word = s;

5.3 std::string の構築

  • 文字列リテラルを使って std::string を構築する。

#include <print>
#include <string>

int main()
{
	std::string s = "abc";
	std::println("{}", s);
	std::println("size: {}", s.size());
}
出力
abc
size: 3

  • std::string s(n, ch); は、n 回繰り返す文字 ch からなる文字列を構築する。

#include <print>
#include <string>

int main()
{
	std::string s(5, 'a'); // 5 個の 'a'
	std::println("{}", s);
	std::println("size: {}", s.size());
}
出力
aaaaa
size: 5

  • 別の std::string オブジェクトをコピーして新しい std::string を構築する。

#include <print>
#include <string>

int main()
{
	std::string s = "abc";
	std::string t = s; // s の値をコピーする

	std::println("{}", t);
	std::println("size: {}", t.size());

	std::println("{}", s); // コピー元は変化していない
	std::println("size: {}", s.size()); // コピー元は変化していない
}
出力
abc
size: 3
abc
size: 3

  • std::string s = { 'a', 'b', 'c' }; のようにして、複数の文字から std::string を構築する。

#include <print>
#include <string>

int main()
{
	char c = 'c';
	std::string s = { 'a', 'b', c };

	std::println("{}", s);
	std::println("size: {}", s.size());
}
出力
abc
size: 3

  • デフォルトコンストラクタは、空の文字列で初期化する。

#include <print>
#include <string>

int main()
{
	std::string s; // 空の文字列を構築する

	std::println("{}", s);
	std::println("size: {}", s.size());
}
出力
size: 0

5.4 std::string の主要なメンバ関数

メンバ関数 戻り値 説明
.size() size_t 文字列の長さを返す。
.length() size_t .size() と同じ。
.capacity() size_t 現在確保されているメモリ領域ので格納可能な文字列の最大の長さを返す。
.reserve(size_t n) void 最低でも n 文字を格納できるメモリ領域を確保する。
.empty() bool 文字列が空なら true、それ以外なら false を返す。
.clear() void 文字列を空にする。
.resize(size_t n) void 文字列の長さを n に変更する。新しい領域は '\0' で埋める。
.push_back(char ch) void 末尾に ch を追加する。
.pop_back() void 末尾の要素を削除する。
.operator[](size_t i) char& i 番目の要素にアクセスする。
.front() char& 先頭の要素にアクセスする。s[0] と同じ。
.back() char& 末尾の要素にアクセスする。s[s.size() - 1] と同じ。
.operator+=(const std::string& str) std::string& 文字列の末尾に str を追加する。
.c_str() const char* C 言語の文字列(char 型の配列に NULL 終端文字 '\0' が付いたもの)を返す。
.substr(size_t pos, size_t len) std::string pos 番目の文字から len 文字を抜き出した文字列を返す。
.start_with(char ch) bool 文字列が ch で始まるかどうかを返す。
.start_with(const std::string& str) bool 文字列が str で始まるかどうかを返す。
.end_with(char ch) bool 文字列が ch で終わるかどうかを返す。
.end_with(const std::string& str) bool 文字列が str で終わるかどうかを返す。
.contains(char ch) bool 文字列が ch を含むかどうかを返す。
.contains(const std::string& str) bool 文字列が str を含むかどうかを返す。
.find(char ch) size_t 文字列の中で最初に ch が現れる位置を返す。見つからない場合は std::string::npos を返す。
.find(const std::string& str) size_t 文字列の中で最初に str が現れる位置を返す。見つからない場合は std::string::npos を返す。
.begin() std::string::iterator 文字列の先頭位置を指すイテレータを返す。
.end() std::string::iterator 文字列の末尾の終端位置を指すイテレータを返す。
.rbegin() std::string::reverse_iterator 文字列の末尾位置を指す逆イテレータを返す。
.rend() std::string::reverse_iterator 文字列の先頭の前を指す逆イテレータを返す。

#include <print>
#include <string>

int main()
{
	std::string s = "abcdefghijklmnopqrstuvwxyz";

	std::println("size: {}", s.size());
	std::println("capacity: {}", s.capacity());

	s.push_back('!');

	std::println("size: {}", s.size());
	std::println("capacity: {}", s.capacity());

	s.pop_back();

	std::println("size: {}", s.size());
	std::println("capacity: {}", s.capacity());

	s.clear();

	std::println("size: {}", s.size());
	std::println("capacity: {}", s.capacity());
}
出力例
size: 26
capacity: 31
size: 27
capacity: 31
size: 26
capacity: 31
size: 0
capacity: 31

6. std::basic_string_view(ベーシック・ストリング・ビュー)

6.1 概要

  • 文字列の所有権は持たず、文字配列の全部または一部の範囲を参照するクラス。
文字型 クラステンプレート 別名
char std::basic_string_view<char> std::string_view
wchar_t std::basic_string_view<wchar_t> std::wstring_view
char8_t std::basic_string_view<char8_t> std::u8string_view
char16_t std::basic_string_view<char16_t> std::u16string_view
char32_t std::basic_string_view<char32_t> std::u32string_view
  • 文字列の先頭のポインタと長さだけを持つ。
  • std::string_view の内部実装を簡易的に表現した例:
struct string_view
{
	const char* m_ptr; // 文字列の先頭を指すポインタ
	size_t m_size; // 文字列の長さ
};
  • 軽量なクラスなので、const 参照渡しではなく値渡しでよい。
  • const ポインタを持つため、std::basic_string_view 経由で文字列を変更しようとするとコンパイルエラーになる。

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

int main()
{
	std::string s = "Hello, World!";
	std::string_view sv = s;

	std::println("{}", sv);
	std::println("size: {}", sv.size());
}
出力
Hello, World!
size: 13

  • 文字列リテラルを、std::string に変換せずに使いたい場合に便利。
#include <print>
#include <string_view>
#include <string>

void F1(const std::string& s)
{

}

void F2(std::string_view sv)
{

}

int main()
{
	std::string s = "Hello, World!";

	F1(s); // 効率的(const 参照渡し)
	F2(s); // 効率的(ポインタと長さだけを渡す)

	F1("Hello, World!"); // 非効率: std::string を構築する
	F2("Hello, World!"); // 効率的:(ポインタと長さだけを渡す)
}

6.2 std::string_view の主要なメンバ関数

メンバ関数 戻り値 説明
.size() size_t 文字列の長さを返す。
.length() size_t .size() と同じ。
.data() const Char* 文字列の先頭のポインタを返す。
.empty() bool 文字列が空なら true、それ以外なら false を返す。
.remove_prefix(size_t n) void 文字列の先頭から n 文字を削除する。
.remove_suffix(size_t n) void 文字列の末尾から n 文字を削除する。
.operator[](size_t i) const Char& i 番目の要素にアクセスする。
.start_with(Char ch) bool 文字列が ch で始まるかどうかを返す。
.start_with(std::string_view sv) bool 文字列が sv で始まるかどうかを返す。
.end_with(Char ch) bool 文字列が ch で終わるかどうかを返す。
.end_with(std::string_view sv) bool 文字列が sv で終わるかどうかを返す。
.contains(Char ch) bool 文字列が ch を含むかどうかを返す。
.contains(std::string_view sv) bool 文字列が sv を含むかどうかを返す。
.find(Char ch) size_t 文字列の中で最初に ch が現れる位置を返す。見つからない場合は std::string_view::npos を返す。
.find(std::string_view sv) size_t 文字列の中で最初に sv が現れる位置を返す。見つからない場合は std::string_view::npos を返す。
.substr(size_t pos, size_t len) std::string_view pos 番目の文字から len 文字を抜き出した string_view を返す。
.begin() std::string_view::iterator 文字列の先頭位置を指すイテレータを返す。
.end() std::string_view::iterator 文字列の末尾の終端位置を指すイテレータを返す。
.rbegin() std::string_view::reverse_iterator 文字列の末尾位置を指す逆イテレータを返す。
.rend() std::string_view::reverse_iterator 文字列の先頭の前を指す逆イテレータを返す。

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

int main()
{
	std::string s = "Hello, World!";
	std::string_view sv = s;

	std::string_view sv2 = sv.substr(0, 5);
	std::println("{}", sv2);
}
出力
Hello

力試し

問題 1

  • 与えられた文字列をコピーして、含まれるアルファベットをすべて小文字にした文字列を返す関数 ToLower を実装せよ。
  • <cctype> ヘッダの std::tolower(ch) 関数を使用するとよい。
#include <print>
#include <string_view>
#include <string>
#include <cctype>

std::string ToLower(std::string_view s)
{
	std::string result(s); // string_view から string を構築する



}

int main()
{
	std::string s = "Hello, World!";
	std::println("{}", ToLower(s));
}
解答例

#include <print>
#include <string_view>
#include <string>
#include <cctype>

std::string ToLower(std::string_view s)
{
	std::string result(s); // string_view から string を構築する

	for (char& c : result)
	{
		c = std::tolower(c); // 小文字に変換する
	}

	return result;
}

int main()
{
	std::string s = "Hello, World!";
	std::println("{}", ToLower(s));
}
出力
hello, world!

問題 2

  • 与えられた英数文字列が回文であるかを判定する関数 IsPalindrome を実装せよ。
#include <print>
#include <string_view>
#include <string>

bool IsPalindrome(std::string_view s)
{

}

int main()
{
	std::println("{}", IsPalindrome("aaabbbaaa"));
}
解答例
#include <print>
#include <string_view>
#include <string>

bool IsPalindrome(std::string_view s)
{
	for (size_t i = 0; i < (s.size() / 2); i++)
	{
		if (s[i] != s[s.size() - i - 1])
		{
			return false;
		}

	}

	return true;
}

int main()
{
	std::println("{}", IsPalindrome("aaabbbaaa"));
}