C++超入門⑥ 配列・vector・stringを使おう
配列、std::vector、std::stringを使い、複数の数字や文字をまとめて扱う方法を学びます。
人数が増えるたびにscore1、score2と変数を増やすのは、だんだん面倒になりますよね。複数の値を並べて保存すると、同じ処理をまとめて使えるようになります。
今回は、固定個数の配列、個数を変えられるvector、文字列を扱うstringを比べます。特に「最後の番号」と「日本語の文字数」は、思い込みやすいので丁寧に見ていきましょう。
短い変数操作の例はmain内へ書く部分です。表示には<iostream>、vectorには<vector>、stringには<string>を読み込みます。全体を試せるコードも、各章に載せています。
1. 配列は、同じ型の値を決まった個数だけ並べる
int scores[3] = {80, 95, 70};
scoresはintを3個持つ配列です。1個ずつを要素、位置の番号を添字(そえじ)と呼びます。
| 添字 | 0 | 1 | 2 |
|---|---|---|---|
| 値 | 80 | 95 | 70 |
scores[0]が最初、scores[2]が最後です。3個の配列の最後が3ではない点に注意してください。
scores[1] = 90;
std::cout << scores[1] << '\n';
この操作で2番目だけが90へ変わります。配列全体の大きさは3のままです。
存在しない要素を読むとどうなる?
scores[3]やscores[-1]の読み書きは、未定義動作になります。自動で安全なエラーが出るとは限りません。
未定義動作とは、C++の規則として結果が保証されない処理です。たまたま数字が出ても、そのコードを正しい例として使ってはいけません。
2. for文で配列を順番に読む
#include <iostream>
int main() {
int scores[3] = {80, 95, 70};
for (int i = 0; i < 3; i++) {
std::cout << scores[i] << '\n';
}
return 0;
}
iが0、1、2と変わるので、80、95、70を順に表示します。条件はi < 3です。
i <= 3にしてしまうと、最後に存在しない要素を読みます。「最初は0、最後は個数-1」と、セットで覚えましょう。
今回の組み込み配列とは別に、C++には固定サイズを扱いやすくしたstd::arrayもあります。まずは組み込み配列の読み方を理解し、個数が変わるデータに進みます。
3. vectorなら、後から追加できる
#include <iostream>
#include <vector>
int main() {
std::vector<int> scores = {80, 95};
scores.push_back(70);
std::cout << "個数: " << scores.size() << '\n';
for (int score : scores) {
std::cout << score << '\n';
}
return 0;
}
個数: 3
80
95
70
std::vector<int>は、int型の値を順番に持つ入れ物です。<int>は「何の型を入れるか」の指定。これを使うために#include <vector>を書きます。
push_back(70)は、末尾へ70を追加する操作です。最初の2要素はそのまま残り、3番目が増えます。size()は今ある要素数、empty()は空かどうかを返します。
範囲forは値を1つずつ受け取る
for (int score : scores)は、配列やvectorの値を1つずつscoreへ取り出す範囲for文です。添字が不要なら、数え間違いを避けやすくなります。
ただし、このscoreは値のコピーです。score += 5;と書いても元のvectorは変わりません。変更したい場合は、次回の参照を使うfor (int& score : scores)を学んでから使います。
4. vectorを使えば、すべて自動で安全?
便利ですが、使い方の条件はあります。
scores[0]のような[]は、C++17では範囲外を安全な例外として報告してくれる操作ではありません。範囲確認が欲しいときはscores.at(0)を使えます。
at()で範囲外へアクセスするとstd::out_of_rangeという例外が発生します。例外は異常を伝える仕組みですが、処理しなければプログラムは終了します。「自動で適切な値に直す」機能ではありません。
| 目的 | 道具 |
|---|---|
| 個数が変わらない並びを基本から知る | 組み込み配列 |
| 追加しながら保存する | vector |
| vectorの個数を調べる | size |
| 空か調べる | empty |
| 範囲チェック付きで1要素へアクセス | at |
追加すると、以前の場所が変わる場合がある
vectorは要素を増やすと、新しいメモリ領域へ移動する場合があります。その場合、以前の要素を指すポインタや参照は使えなくなります。
今はポインタを保存せず、必要なときにvectorから読み直せば十分です。この注意点はvectorの変更操作の仕様にも定義されています。
5. stringは文字列を扱う道具
#include <iostream>
#include <string>
int main() {
std::string word = "code";
std::cout << word.length() << '\n';
std::cout << word[0] << '\n';
word += " seed";
std::cout << word << '\n';
return 0;
}
4
c
code seed
length()とsize()は同じ長さを返します。word[0]は最初のchar単位、+=は後ろへ文字列を足す操作です。
文字を選ぶときは0 <= 位置 < word.size()を守ります。空のstringに「最初の文字」があると思ってはいけません。std::stringには終端に関する特別な規則もありますが、内容を読むときはこの範囲で考えましょう。
日本語の「文字数」とは一致しない
std::stringのsizeは、見た目の文字数ではなくchar単位の数です。UTF-8で"種"を保存した場合、通常は3になります。
word[0]で日本語1文字を丸ごと取り出せるとは限りません。日本語や絵文字の分割・文字数制限には、文字コードを理解した別の処理が必要です。英数字の学習例で成立したことを、そのまま日本語へ広げないようにしましょう。
6. getlineで空白を含む名前を読む
#include <iostream>
#include <string>
int main() {
std::string name;
std::cout << "名前: ";
if (!std::getline(std::cin, name)) {
return 1;
}
std::cout << "こんにちは、" << name << "さん。\n";
return 0;
}
Aoi Soraと入力すれば、空白を含めて1行として受け取ります。行末の改行自体は、今回のgetlineの結果へ含まれません。stringの入出力の仕様で確認できます。
直前にstd::cin >> numberを使うと、残った改行をgetlineが読み、空行になる場合があります。入力を混ぜる場合にはその整理が必要です。最終回では入力をgetline中心へそろえます。
7. 練習:合計と平均を求めよう
80、65、90、75をvectorへ入れ、合計と平均を表示してください。空のvectorなら平均を計算しないようにします。
模範解答
#include <iostream>
#include <vector>
int main() {
std::vector<int> scores = {80, 65, 90, 75};
if (scores.empty()) {
std::cout << "点数がありません。\n";
return 0;
}
int total = 0;
for (int score : scores) {
total += score;
}
const double average =
static_cast<double>(total) / scores.size();
std::cout << "合計: " << total << '\n';
std::cout << "平均: " << average << '\n';
return 0;
}
解説
合計は80 → 145 → 235 → 310と増え、310 ÷ 4で平均77.5です。
static_cast<double>は、整数を小数を扱う型へ一時的に変換する書き方です。割り算より前に変換しているので、端数を失いません。
scores = {}の空の例では、先にemptyがtrueとなり、0で割る前に終了します。今回は少人数の0〜100点を想定しており、任意の大量データをintで無制限に合計できるわけではありません。
次の一歩
「何を入れるか」「個数が変わるか」「どこまで読んでよいか」を意識すると、データのまとめ方を選びやすくなります。