見出し画像

コンピューターサイエンス概論#7(8/10週目)

怪我があったり、確定申告したり(住宅ローン控除1年目とかの)、冠婚葬祭やらで1ヵ月スキップに…。

前回は関数の使い方、ループと再帰の違いや、その際のメモリの使われ方に触れました。変数も関数もメモリの使われ方が見えてくるとだいぶプログラミングでも靄が晴れて視界が良くなると思います。

ちなみに、この記事は海外の某大学のカリキュラムに沿って10週間の授業の内容を紹介するような感じで、これ全てでコードが書けるようになるものを想定していません。一時期、プログラミングスクールのステマが大爆発してた頃に、大学に行ってもプログラミングはできるようにならないという噴飯物の主張に対して、大学1年の最初の授業でもこの程度はやるんだよ?という気持ちで書き始めました。

この連載を完全に理解できてればコンピューターサイエンスの大学の1年生並みの知識があると思ってよいと思います。個人的には独学やスクールで基礎構文を一通り使いこなせるようになった人が理論的基礎を固めるために読んで頂ければと思います。コードの裏側でどんなことが起こっているかがわかることは中級者以上では必須だと思うので、基礎中の基礎として理解しましょう。

いよいよ残り2週分ですが、最後の週(Final=期末試験は除く)は流すような内容なのでまとめてしまいます。次は違うテーマの記事も書けたら。今回は文字列や多次元配列などの少し深めの配列構造とコマンドライン引数についてみていきます。

文字列

文字列は簡単なようで複雑でバグを出しやすいものです。
C++ではよく使われる物としてCスタイルの文字列と文字列クラスがあります。

Cスタイル文字列

C++におけるCスタイル文字列とは

  • \0で終わる

  • <cstring>をincludeして使う

char cstr[] = "test";
char pre[10];
std::cin.getline(pre, 10);

みたいな感じで使えます。最後のは標準入力から改行文字が見つかるまでか9文字(10文字目は\0)までをpre変数に格納する、と言う関数です。また、C言語由来の関数を呼ぶとCスタイル文字列が返ってきたりします。stringクラスのc_str()を呼んで取り出したりもします。これはサイズ固定の文字の配列というのが実態ですね。

多次元配列

多次元配列というのは簡単に言えば配列の配列です。3次元になると配列の配列の配列になります。例えば下記は2次元配列(2D array)です。

int array[4][3];

後ろから見るとわかりやすくて一番内側の要素は3つの要素を持つ配列で、それが4つある2次元配列で全部の要素は12個になります。

2次元配列はExcelみたいなものですね。言語によりますがC++では同じ型の要素が入ります。メモリ的には要素のサイズ*1次元目のサイズ*2次元目のサイズ*…と値が並んでいる形になります。そうやってメモリを想像するとarray[2][3]の開始位置がどう決定されるか想像がつくと思います。

3次元になると、Excelのシートが複数あるイメージです。結局のところその[x]より右側の形の配列がx個あるという感じですね。4次元以降は混乱するので、物質で想像するのは難しいと思います。

初期化

定義時はこんな感じですね。

int array[3][2] = {{1, 2}, {3, 4}, {5, 6}};

もしくはそれぞれデータを入れる

array[0][0] = 1;
array[0][1] = 2;
...

ループする

for (int i = 0; i < 3; ++i) {
  for (int j = 1; j <= 3; ++j) {
    array[i][j-1] = i * 3 + j;
  }
}

静的vs動的多次元配列

これは今回知ったというかちゃんと考えた話ですが。静的多次元配列( int xx[n][m] )と動的多次元配列(int** xxx)ではメモリの使われ方が違います。というか、そりゃそうか。ほとんどvector使ってたので…。

静的多次元配列は1行目の1項目から1行目の最後の項まで行ったら2行目の最初の項とスタック領域に連続して配置されます。

それに対して、動的多次元配列は都度newが行われるため、各行が行内のみ連続して、例えば1行目と2行目は繋がっていません。また、ポインタ変数自体はスタック領域にあって、newされた行はヒープにあります。1次元目の配列はそれぞれの行の先頭のアドレスを指すポインタの配列で、その先にそれぞれの行のデータが連続して配置されています。

ジャグ配列

これも初めて知ったのですが、動的配列で要素数が異なる配列をジャグ配列と言うそうです。

ポインタのポインタだと先の配列の数が変わっても問題ないので、C++でもジャグ配列作れますね。Pythonとかだと中の形すらばらばらに入るので、どうなってるのか一度調べたい。データ全部64bitなのかな、と一瞬思ったりしたけど、classも入る訳で…。まぁ、だから配列じゃなくてリストなのか。そもそも連続してないのかな。

その他

配列とポインタ組み合わせて int *[5]とか型の選択肢がありますね。コンパイル時にサイズが決まらない場合は動的にする感じですが、その際にはサイズがわからないので引数に追加する必要があったり、結構使い勝手が悪いので、基本的にstlのvectorを使うことが多いと思います。

動作原理やメリットを知る上でもC型の配列を知って損はないと思います。

コマンドライン引数

C++のコマンドライン引数は簡単です。
main関数の引数に渡されます。

int main(int argc, char *args[]) {
  std::cout << args[0] << std::endl;
  if (argc > 2) {
    std::cout << args[1] << std::endl;
  }
}

注意するのは1つ目の引数はコマンド自体だということですかね。つまり、ホワイトスペースでスプリットして渡されるような話ですね。

引数が使えると何が便利かと言うと、同じコードで色んな事ができるわけです。フラグとして機能を増やしたり絞ったり、出力する結果の数(例えばn人の人でゲームをやる時にランダムに順番を決める、とか)を変えたりもできます。練習でお釣りのプログラムを書いて、購入額と投入額を渡したり。

まぁ、対話できるなら標準入力でも良いのですが、バッチとしてファイルから時刻で実行したりする場合、コマンドライン引数を使えると便利だったりします。

おまけ

コマンドライン引数というと、フラグ+値と言うのが結構あります。
例えば

$ docker run --rm -it -p 3000:3000 -w ${PWD} -v ${PWD}:${PWD} \
  docker.xxx.com/test endpoint.sh

だとか。こういったコマンドはどういう風に解析しているか考えてみましょう。


この記事が気に入ったらサポートをしてみませんか?