3
1

vector<string>型変数を、変数名[][]という2次元配列として扱える件について

Last updated at Posted at 2023-12-22

初めに

C++におけるvector<string>型変数の柔軟な扱い方について学習した記事です。
結論から言うと、vector<string>型変数:hogeはhoge[i][j]というように2次元配列として扱えます。
hoge[i]に収められている文字列を、hoge[i][j]で1文字ずつchar型として取り出せます。
ご指摘あればコメントでお願いいたします。

2023/12/23追記

コメントで今回扱ったような、「配列の配列」をジャグ配列と呼ぶことをご教示いただきました。
明解な概念を教えてくださり感謝いたします!

char Array[3][5] ← 「2次元配列」というとこういうやつで,
string Strs[3] みたいなのは,各stringを「charの配列」と考えるならば「配列の配列」なのであり,そういうのを「ジャグ配列 (jagged array)」と呼ぶ様子.
(後者の側は,個々の配列:ここではstring のサイズが異なり得る)

以下はC#でのジャグ配列についての具体例。↓

int[][] jarray = new int[2][];
jarray[0] = new int[] { 1, 2 };
jarray[1] = new int[] { 3, 4, 5 };

追記ここまで

きっかけ

C++で競技プログラミングサイトAtCoderの過去問
「ABC323 B - Round-Robin Tournament」
のユーザ解説を見ていて引っかかる点があった。

18行目のこの部分。

c[i][j]

この変数cは、9行目に宣言されている通りvector<string>型のvector。
さらっと流しそうになったのだが、2次元配列として扱っていることに躓く。

なぜ躓いたか

そもそも1次元ベクトルとして宣言しているので、c[i]としてアクセスするのは理解できる。
しかしどう見てもc[i][j]というのは2次元配列であり、しかもエラーにならず想定通りの挙動をしている。
想定というのはこの場合、i行目のj番目の文字列、-oxのうちのどれかにアクセスできているということ。
なぜこのような挙動ができるのか?素直に理解できず躓いてしまった。
ネットで調査してみても、明解なものが得られず。
(あくまで今回の疑問についてです。このような有用な知識を残してくださった方々に多謝)
「C++ vector string」などで検索。

C++のvectorまとめ
https://qiita.com/ysuzuki19/items/df872d91c9c89cc31aee

C++ で文字列の配列を作成する
https://www.delftstack.com/ja/howto/cpp/string-array-in-cpp/

競プロ等におけるC++の標準入力
https://qiita.com/zenrshon/items/cd19361842ae887afe8a

【C++】2次元配列の入力&出力方法
https://note.com/imsakasa/n/n0fcf0fd88744

std::vecotrを2次元配列として扱う
https://kaworu.jpn.org/cpp/std::vecotr%E3%82%92%EF%BC%92%E6%AC%A1%E5%85%83%E9%85%8D%E5%88%97%E3%81%A8%E3%81%97%E3%81%A6%E6%89%B1%E3%81%86

ChatGPTに頼る

結局、ChatGPT(3.5)に質問することに。ただ誤答もありえるため、裏付けをとるように心がける。
質問してみたところ、以下回答が得られた。

C++のstd::vectorやstd::stringなどの標準ライブラリコンテナは、operator[]をオーバーロードして、要素にアクセスするためのインターフェースを提供しています。
例えば、vector型の変数vがある場合、v[i]でi番目の要素にアクセスできます。これは、内部的にoperator[]がオーバーロードされており、対応する要素への参照(またはポインタ)を返すようになっています。
同様に、string型もoperator[]をオーバーロードしており、文字列中の各文字へアクセスできます。string型は実際にはchar型の配列として実装されているため、「abc」という文字列では0番目が'a'、1番目が'b'というようなアクセスが可能です。
この仕組みを利用することで、「c[i]」と書くことでi番目の文字列(c内部ではstring型)へアクセスし、「c[i][j]」と書くことでi番目の文字列中でj番目の文字(char型)へアクセスすることができます。
つまり、「c[i][j]」は「c[i]」から返されるstring型オブジェクトに対してさらに「[j]」を適用したものです。最初に「c[i]」が呼ばれて該当するstringオブジェクトへアクセスし、「[j]」が呼ばれてその中から該当するchar型要素へアクセスします。
したがって、二次元的な配列やベクター操作を必要とする場合でも簡単かつ直感的な記述方法です。

それらしい回答が得られた。個人的に重要だった部分を抜き出すと、

  • [](インデックス演算子)をオーバーロードし、vectorでも利用できるようにしている
  • vectorと宣言した場合、c[i]でそれぞれのstring型変数にアクセス
  • c[i][j]で、指定したstring型の文字列を1文字ずつchar型として扱い、文字ごとにアクセス可能

ということだった。
そのため、c[i][j]とすることで文字列を2次元配列のようにアクセスすることが可能、ということになる。

裏付けも探してみたが、こちらの記事が参考になった。

i番目文字の参照(charに変換)、代入
[] 演算子(operator) を使って、通常の文字列と同じように、i 番目の文字の参照(char に変換)・代入が可能。
operator と聞くと身構える人がいるかもしれないが、「str[10]」の様に、普通の文字列の要素にアクセスする時と同じ記述だ。 恐れることは何も無い。
下記は、i 番目の文字の参照(char に変換)と代入例。普通の参照(char に変換)・代入とまったく同じでしょ。

test.cpp
   std::string str("31415926535");

    for (int i = 0; i < 10; ++i)

        std::cout << str[i] << " ";      //  str の i 番目の要素を表示

    const int SZ = 10;          // 文字数

    std::string str(SZ, 'a');     // 10 文字の 'a' から成る str を生成

    for(int i = 0; i < SZ; ++i)

        str[i] = 'b' + i;             // 文字を 'b', 'c' ... に設定

    std::cout << str << "\n";

http://s170199.ppp.asahi-net.or.jp/tech/cpp/string.html

vectorとして扱った記事ではないが、string型変数に配列としてアクセス→char型変数として取り出せることについて記述されていた。

結論

vectorはインデックス演算子[]がオーバーロードされているため、vector[]とすれば各行の要素にアクセスできる。
また、string型変数に[]を付与すれば、char型として1文字ずつアクセスできる。
これらを合わせてvector<string>型として宣言された変数は、変数名[][]というように2次元配列として扱うことができる。

振り返り

できれば公式ドキュメントを参照するのが一番良いのだろうが、そこまで理解できる読解力を持っていなかった。。
強いエンジニアになりたいと願った一件だった。
また、内容が充実している初心者・初級者向けC++の教本を読んだ方がいいかもしれないと痛感した。

3
1
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1