注意: この記事は古い方針に基づいた内容であり、閲覧しないことを推奨します。学びたい人は こちら をどうぞ。
今回は複数の値の取り扱いと
それに必要な知識もやっていく
目標
- 2 通り以上の条件分岐
- 一時的な値の保存
- ループ
- 任意の長さの値の取り扱い
の習得
インデント (字下げ)
括弧の中身が複数行にまたがるとき、
その始まりと終わりを明確にするために行う
{
{
{
{
}
}
}}
上のものより
下のほうが見やすい (と思ってくれれば嬉しい)
{
{
{
;
}
}
{
;
}
}
これから波括弧などが増えてくるので書いておく
else
if
と一緒に使う構文
条件が false
のときだけ実行される
分岐したお互いの文はどちらかしか実行されない
if ( expr ) {
~ ; // when expr is true
} else {
~ ; // when expr is false
}
else if
そして else
の直後に if
を付けられる
これで 2 パターン以上に対応できる
if ( expr ) {
~ ; // when expr is true
} else if ( expr2 ) {
~ ; // when expr is false & expr2 is true
} else if ( expr3 ) {
~ ; // when expr2 is false & expr3 is true
} else {
~ ; // when expr3 is false
}
上の if
から優先的に分岐する
変数
値を保存する場所をつくる仕組み
関数が受け取る引数も変数のうちの一つ
関数内において、
以下のように変数を作ることを「変数の定義」という
文献によっては「変数の宣言」ともいわれる
// Variable definitions
int var = 12; // `var` stores 12
double rough_e = 2.7; // `rough_e` stores 2.7
bool completed = false; // `is_completed` stores false
変数にアクセス
変数の名前をそのまま値のように書くと、変数の値がコピーされる
int var = 12;
int val = var; // `val` stores 12 (copied from `var`)
return val; // return 12 (copied from `val`)
命名規則
変数には使えない名前がいくつかある
- 数字で始まる
-
_
と大文字英字で始まる -
__
が含まれている
ような名前は定義できない
// You can't define:
int 2_to_3 = -1;
// You must not define:
int _X = 0;
int f_G = 1;
int a__ = 2;
int ___v__ = 3;
int __Q = 4;
auto
auto
はプレースホルダー型といわれるものの一つで、
実際の型は =
の右側から推論される
auto count = 0; // typed int
auto rough_pi = 3.; // typed double
auto done = true; // typed bool
代入
変数の値を書き換えること
代入演算子 =
を使う
※変数の定義に使う =
は同じ記号なだけで代入演算子ではない
int ham = 1130;
ham = 2680; // Assignment is here
これを使うと、足し合わせることなどができる
auto summing = 0;
summing = summing + 5;
summing = summing + 8;
summing = summing + 12;
summing = summing + 100;
引数も上書きできる
int example(int what) {
what = 2;
return what;
}
複合代入
上の v = v なんとか 数
というパターンが多いのでそのための構文
このパターンを v なんとか= 数
と書ける
この +=
-=
などを複合代入演算子という
auto summing = 0;
summing += 5;
summing += 8;
summing += 12;
summing += 100;
ただ対応する演算子が無いこともあるのでそのときは普通に書く
インクリメント / デクリメント
変数の値を 1 だけ増やす / 減らす演算子
auto count = 0;
++count; // increment
// count == 1
--count; // decrement
// count == 0
これらはそれぞれ += 1
/ -= 1
かのように使われる
C++ 使いは、必ず絶対に変数の前 に書く (後に書く方もある)
技術的なアレコレがあるので理由は省く
while
ループするための構文
auto to_loop = true;
while (to_loop) { ~ }
// loop while `to_loop` is true
auto count = 3;
while (3 < count) {
--count; // counting down
}
{ ~ }
が繰り返され、これが終わる度に while (~)
の式が評価される
実行順序
繰り返すかどうか → ループ内容 → 繰り返すかどうか → ループ内容 → ……
の順番で実行される
そのため条件式が最初から false
だと中の処理は一度も実行されない
条件式が true
のときにだけ繰り返されることに注意
ちなみに条件式に true
と書くと (そのままでは) ループから出てこない
while (true) { ; }
ある制御文と組み合わせて
これを使うこともあるが
今はまだ説明しない
for
while
の上位互換なので少し構文が複雑
for (int i = 0; i < 11; ++i) { ~ }
// To explain ...
for ( // For
int i = 0; // i starts from 0
i < 11; // to 11
++i // increment by the loop
) {
~ ; // is repeated 11 times
}
for (
~ ; // before the loop
~ ; // loop while the expression is true
~ // at the end of the loop
) { ~ }
(ループ開始前; 条件式; ループ後処理)
のように式が記述できる
独特かつ伝統的な記法なので、何回も書いて覚えよう
繰り返しの条件が一箇所にまとまる、何回目のループか分かるのが長所
実行順序
ループ開始前 → 条件式 → ループ内容 → ループ後処理
→ 条件式 → ループ内容 → ループ後処理 → ……
と実行される
よく使うパターン
-
0
から始まるfor (auto i = 0; i < 繰り返し回数; ++i) { ~ }
-
1
から始まるfor (auto i = 1; i <= 繰り返し回数; ++i) { ~ }
はよく使う
どうしてこうなるか考えてみよう
繰り返し回数を 2 とすると、
auto i = 0;
i < 2
はtrue
ループ内容 1 回目
++i
でi
が1
に
i < 2
はtrue
ループ内容 2 回目
++i
でi
が2
に
i < 2
はfalse
ループ終了
1 で始まる方も同様
例題 1
// void means return nothing
void fizz_buzz(int times) {
// Write here
print("Buzz");
}
ここで関数を実装せよ
値 times
が入力される
この数まで Fizz Buzz (下記) を行え
数字を数えていき、以下を出力する
- 3 と 5 の倍数なら "Fizz Buzz" を
- 3 の倍数なら "Fizz" を
- 5 の倍数なら "Buzz" を
- それ以外は数字をそのまま
ここで出力する関数として print
関数を用意しておいた
print(10);
とか print("Fizz");
で出力してほしい
解説
変数 count
で 1 から 数え上げていき、
その値によって分岐する
else
を使わないとおかしくなる
void fizz_buzz(int times) {
for (auto count = 1; count <= times; ++count) {
if (count % 15 == 0) {
print("FizzBuzz");
} else if (count % 3 == 0) {
print("Fizz");
} else if (count % 5 == 0) {
print("Buzz");
} else {
print(count);
}
}
}
関数呼び出し
さっき使ったけどここでちょっと解説
自分や他人が作った関数は、
関数名(引数);
とすると呼び出せる
std::string
std::string
はテキストを扱うためのクラス
先程のようにテキストの値を作るには "
で囲う
これを「文字列」という
でもこの文字列 "abcdefg"
は std::string
ではない
auto
を使ってしまうと違う型になる
std::string empty;
std::string text = "abcdefg";
std::to_string
int
や double
から数字の std::string
にするには、std::to_string
関数を使う
auto text = std::to_string(2); // == "2"
find
std::string
型の値はオブジェクトという類のもので、
これらには関数が付いている
テキストにある文字が含まれているかどうかは、以下で判定できる
std::string text = "timeleeeess!";
auto l_pos = text.find("l"); // returns position if found the text
auto x_pos = text.find("x"); // returns std::string::npos if not found
// so
auto contains_exclamation =
text.find("!") != std::string::npos; // means `whether contains "!"`
他にも機能があるけど次でこれを使うから説明した
#include
あ、これを使うより前に #include <string>
と書いて
string
ファイル (C++ に付属) を展開しておく必要がある
#include <string>
こういう外部のプログラムファイル群をライブラリといい、
特に <string>
のような C++ 標準のものを標準ライブラリという
他の標準ライブラリ内でも #include <string>
されていることが多いので、
この #include
が要らないこともある
問 1
auto included_three(int n) {
// Write here
return false;
}
ここで関数を実装せよ
値 n
が入力される
これが 3 のつく数かどうか判定せよ
std::vector
同じ型を大量に格納するためによく使われる
これは <vector>
標準ライブラリが必要
std::vector<int> v; // contains ints
auto copied = v; // another v
今回はこれを自分で作ったり触ったりする必要はない
あらかじめ用意されたものを渡すので、
これをどう加工するかをプログラムする
size
格納している個数を尋ねる
auto elements_count = v.size();
at
n 番目に格納しているものを取得する
この番号は 0 から始まり、0
以上 size
未満
また、格納しているものは「n 番目の要素」のように言う (他の格納するものでも同じ)
存在しない番号を渡すと terminating ~ std::out_of_range: vector
と出てプログラムが停止する
auto first_element = v.at(0);
auto third_element = v.at(2);
v.at(1) = 0;
イテレータ (反復子)
標準ライブラリには何かを格納するクラス (コンテナクラス) がいくつかあり、
それらはイテレーターを使って操作できる
ランダムアクセスイテレータ
std::vector<int> v;
auto head = v.begin();
auto tail = v.end();
によって得られるイテレータは、
- アクセス演算子
*
を使って格納された値を読み/書きできる - インクリメント演算子
++
を使って次の要素に移動する - デクリメント演算子
--
を使って前の要素に移動する - 比較演算子
==
/!=
を使って同じ要素を指すかどうか取得できる
といった特性を持っている
void example(std::vector<int> v) {
auto head = v.begin();
auto first_element = *head; // Read
*head = 2; // Write
auto next = ++head; // Advance, then copy
head == --next; // is true
}
これを利用して、要素に対する操作ができる
例題 2
int sum(It head, It tail) {
int current = 0;
// Write here
return current;
}
ここで関数を実装せよ
イテレーター head
tail
が入力される
この範囲 (head
~ tail
) の総和を計算せよ
解説
int sum(It head, It tail) {
int current = 0;
for (auto it = head; it != tail; +it) {
current += *it;
}
return current;
}
こうして実装できる
実は <numeric>
標準ライブラリに
std::accumulate
という上位互換の関数がある
問 2
void seq_interpolation(It begin, It end) {
int back = 0;
// Write here
}
ここで関数を実装せよ
イテレータ head
tail
が入力される
途中に -1
のデータがある
そのデータをそれより前の -1
でないデータで置き換えよ
最初に -1
がある場合は 0
にセットせよ
問 3
void normalize(It head, It tail, double min, double max) {
// Write here
}
ここで関数を実装せよ
イテレータ head
tail
値 min
max
が入力される
head
~ tail
の要素を min
~ max
の範囲で以下の値にせよ
-
min
以下の値は0
-
max
以上の値は1
- それ以外の値は
(要素の値 - min) / (max - min)
これは、min
~ max
の範囲で 0
~ 1
の値に変換 (正規化) する処理になっている
多次元コンテナ
std::vector
は任意の (実際は少し条件がある) 型を格納できる
つまり、
std::vector<
std::vector<
~
>
>
としてもいい
std::vector<std::vector<int>> square;
std::vector<std::vector<std::vector<int>>> cubic;
こういったコンテナのコンテナは「二次元」のコンテナと呼ばれ、
こんな感じで格納される
[
[0, 0, 0, ...],
[1, 2, 1, ...],
[3, 6, 1, ...],
:
:
]
行 (上図での横) 方向に格納されたコンテナが
列 (上図での縦) 方向に格納されるため、
これらへのインデックスによるアクセスは逆順 (列→行) になる
auto row = 0;
auto column = 0;
square.at(row).at(column);
auto depth = 0;
cubic.at(depth).at(row).at(column);
インデックスによる要素ループ
インデックスを指定すれば要素にアクセスできるということは、
for
文で 0 からコンテナのサイズの範囲を数えているカウントを
インデックスに指定すると順々にアクセスできる
ちなみに、インデックスに使う値の型は size_t
(これも整数) が正しい
std::vector<int> v;
for (size_t i = 0; i < v.size(); ++i) {
v.at(i) = 1;
}
二次元だとこう
std::vector<std::vector<int>> m;
for (size_t y = 0; y < m.size(); ++y) {
for (size_t x = 0; x < m[y].size(); ++x) {
m.at(y).at(x);
}
}
次回あたりからの数学的な問題を解くために
インデックスを用いた計算を実装する必要が多い
例題 3
std::vector<std::vector<bool>> shift_field(
std::vector<std::vector<bool>> field,
int offset_x,
int offset_y
) {
auto processed = field;
// Write here
return processed;
}
ここで関数を実装せよ
コンテナ field
と整数 offset_x
offset_y
が入力される
field
を行方向に offset_x
、列方向に offset_y
ずらしたものを返せ
範囲外からずらされてきた要素は false
にせよ
解説
コピーして新しい配列を作り、
要素 (x, y)
における元の配列との関係を
漸化式のように
processed.at(y + offset_y).at(x + offset_x) = field.at(y).at(x);
と記述する
また、アクセスしようとしている x
のインデックスを
src_x
として (名前は何でもいい)、
0 <= src_x && src_x < field.at(y).size()
というように範囲外でないかを
y
においても同様に確認している
std::vector<std::vector<bool>> shift_field(
std::vector<std::vector<bool>> field,
int offset_x,
int offset_y
) {
auto processed = field; // copy
for (size_t y = 0; y < field.size(); ++y) {
for (size_t x = 0; x < field.at(y).size(); ++x) {
// index validation
auto src_x = x - offset_x;
auto src_y = y - offset_y;
if (0 <= src_x && src_x < field.at(y).size()
&& 0 <= src_y && src_y < field.size()) {
processed.at(y).at(x) = field.at(src_y).at(src_x);
} else {
processed.at(y).at(x) = false;
}
}
}
return processed;
}
問 4
bool collides_field(
std::vector<std::vector<bool>> field,
std::vector<std::vector<bool>> to_join
) {
// Write here
return false;
}
ここで関数を実装せよ
同じサイズの二次元コンテナ field
to_join
が入力される
field
と to_join
において、
同じインデックスの2つの要素が true
であれば、true
を返せ
それ以外は false
を返せ
まとめ
お疲れ様!
次回はより複雑なデータ構造を扱うことになって、
「一つくらいの入力」→「データ構造を作成するシステム」→「データ構造を出力」
や
「データ構造」→「それを探索するシステム」→「一つくらいの出力」
を実装するよ
次回もよしなに