naruの日記

いい記事を書きます。あと、日記ではないです。

C++ に屈服した男

はじめに(別に読まなくて良い)

私がAtCoderを始めたのには2つの理由がある。

ひとつは友達に勧められたからである。芯がない男なので勧められたものはやる。当たり前だ。

もうひとつは、Pythonに慣れたかったからである。

元々私は機械学習に興味がありKaggle*1等で勉強をしていたのだが、とにかくプログラミングのスキルが無く、自分でなかなかプログラムを実装できずにいた。

機械学習界隈で最もメジャーな言語はPythonであるので、機械学習の知識を詰め込む以前にまずPython自体の練習をしなければと思っていたところ、競プロに出会ったという感じだ。


今説明した通り、私は競プロがやりたいというよりは、Pythonの練習がしたいという思いでAtCoderを続けてきた。

そんな男が今、C++に屈服する。

C++に手を出す理由(これも読まなくても良い)

C++は競プロの界隈で(多分)最もよく使われている言語である。

実際にAtCoderに提出されるコードの使用言語を見たときに、C++で書かれたコードが最も多いそうだ。

詳しくは知らないが、おそらくその速さとライブラリの豊富さが評価されているのだろう。

C++のこういった噂は競プロを始めた時から聞いていたが、「俺はPython一筋だから卍」とC++を頑なに拒絶していた。

そんな私がC++を受け入れた理由は2つある。

ひとつは、「どうせいつか触るし、C++の練習をやって損はない。PythonC++も使えば良いじゃん。」と友達に言われたからだ。芯がない男なのでなるほどと思ってしまった。

もうひとつは、ネット上の解説記事の多くがC++を採用しているからだ。そういった記事はC++の知識が無く理解できないため、わざわざPythonで説明している別の記事を探して勉強してきたが、そろそろそれも煩わしくなってきた。


上の2点に加え、一応C言語は触ったことがあるのでC++の習得には苦労しないだろうという軽率な判断もあり、この度C++に手を出すことにした。

この記事では、C++について覚えておきたい事柄をメモ書き程度に載せていく。

なお、勉強にはAtCoderの以下の教材を用いた。ネットっちゅうのはなんでもありますなあ。

atcoder.jp

C++をはじめよう(読んでも良いかも)

プログラムの基本形

C++で書かれるコードは基本的に以下のような形式をしている。

#include <bits/stdc++.h>
using namespace std;

int main() {
  cout << "Hello, world!" << endl;
}

まず1行目の#include <bits/stdc++.h>だが、これはC++の機能を「全て」読み込むための命令らしい。

次に2行目のusing namespace std;について。これはプログラムを短く書くための機能らしい。
流石にこれじゃ納得できないので、あとで勉強したときにより詳しい内容を追記しときます。

1,2行目のコードは共に業務におけるプログラミングでは推奨されないことがあるそうだが、競技プログラミングで利用する場合は全く問題ないとのことなので、気にせず使っていく。

5行目はHello, world!と出力するためのコードである。C++ではcoutで文字列を出力する。
endlは改行を意味していて、行全体としては、"Hello, world!"endlというデータを、<<coutに送っていく様子をイメージすれば良い。

なおC言語同様、大体の行でセミコロン;は必要になってくるので忘れないよう注意。

変数の扱い

以下のコードを見ながら、いくつか覚えておきたいポイントを説明する。

#include <bits/stdc++.h>
using namespace std;

int main() {

  int a = 10, b = 5, c;
  string s = "Hello";

  cout << a << endl; // 10
  cout << b << endl; // 5
  cout << s << endl; // Hello

  c = a; // aの値がコピーされ、bに10が代入される
  a = 3; // aの値は3に書き換わるが、bは10のまま

  cout << c << endl; // 10

  cout << c / a << endl; // 3
}

C言語同様、変数を使う場合は初めに型を宣言する必要がある。型にはよく使うものでint,doublle,stringなどが存在する。

宣言するときには、初期化しても初期化しなくても良い。初期化とは値を代入することである。

コードの後半では変数のコピーを行っている。変数はメモのようなもので、変数1=変数2とかいた場合、変数の値そのものがコピーされる。
その後にどちらかの変数の値が変更されても、もう片方の変数は影響を受けないことに注意したい。

また細かい点だが、C++では、int同士で割り算した場合、結果は小数点以下を切り捨てした値になることをコードの最終行で確かめている。

入力受け取り

競プロをはじめるにはまず入力よりはじめよ(?)。入力受け取りの方法について。

#include <bits/stdc++.h>
using namespace std;

int main() {
  int a,b;
  string text;
  double d;

  cin >> a >> b; // 入力:2 3
  cin >> text; // 入力 : hello
  cin >> d; // 入力 : 1.5

  cout << a+b << endl; // 5
  cout << text << ", " << d << endl; // hello, 1.5
}

入力を受け取る際には、入力の型を宣言してからcin>>で受け取るのが基本である。配列で受け取りたい場合のやり方は、また後述。

その際、cin >> a >> bのようにつなげて値を受け取ることもできる。入力が複数ある場合は、スペースか改行で区切られていれば分解して受け取ることができる。

if文とスコープ

if文の書き方と変数のスコープについて説明する。

#include <bits/stdc++.h>
using namespace std;

int main() {
  int x = 5;

  if (x == 5) {
    int y = 10; // yのスコープはこの行からif文のブロックの終わりまで
    cout << x + y << endl; // 15
  }
  else if (x == 10) {
    cout << x << endl; 
  }
  else {
    string y = "hello"; //ブロックが違うので変数yを宣言できる
    cout << x << ", " << y << endl;
  }
    

  if (x == 5) {
    cout << x << endl; // 5

    string x = "hello"; // int x = 5;のスコープと重なっている
    cout << x << endl; // hello
  }

  cout << x << endl; // 5
}

上のコードにあるようにif文はif (条件式) { ~ }という形で書かれる。この{ }で囲われた部分をブロックと言う。

C++にはelifが無く、ちゃんとelse ifと書かなければいけない点に注意。

また、変数が使える範囲のことをスコープといい、変数のスコープは「変数が宣言されてからそのブロックが終わるまで」となっている。よってif文のブロックの中で宣言された変数は別のブロック内で再び宣言することができる。

さらに、スコープが重なっている場合は最も内側のブロックで宣言された変数が選ばれる。2個目のif文ではそのことを確かめている。

repマクロ

C++では自分でマクロを定義することでプログラムが書きやすくなることがある。

#include <bits/stdc++.h>
using namespace std;
#define rep(i, n) for (int i = 0; i < (int)(n); i++)

int main() {
  rep(i, 3) {
    cout << "Hello" << endl;
  }
}

まず、for文はfor (初期化;条件式;更新方法) {処理}の形で書かれ、条件式が真である間、処理が実行され続ける。いちいちfor文を書くのがめんどくさいと言う意見が多いらしく、このrepマクロが使われることがある。

C++では#defineと自分でマクロを定義することができる。ここではrep(i,n)を「n回だけ処理を繰り返す」という機能を持つマクロとして定義している。iはカウンタ変数と呼ばれ、ここではi=0,1,...,n-1と更新される。

書いてから思ったけど、まとめるほどじゃなかったですねこれ。

文字列と文字の扱い

文字の扱いについて。

#include <bits/stdc++.h>
using namespace std;

int main() {
  string str = "Hello";
  cout << str.size() << endl; // 5

  cout << str.at(0) << endl; // H
  cout << str.at(4) << endl; // o

  char c = str.at(1); // char型の値が得られる
 
  cout << c << endl; // l

  str.at(0) = 'M'; // char型の'M'
  cout << str << endl; // Mello

  // +=演算子による連結
  str += ", AtCoder!";
  cout << hello << endl; // Mello, AtCoder!
}

文字列はstring型で、文字はchar型で受け取る。

string型にはsize()at()といった関数(詳しくいうとメンバ関数)が存在し、それぞれ文字列のサイズ、文字列のi文字目を返す。

注意することが2点あり、1つ目は文字列は" "で囲み、文字は' 'で囲むべきだということ。文字列と文字では役割が違い(例えば文字列には上で述べた関数が存在し、文字は文字列の一部の書き換えや比較ができる。)、" "' 'かでstring型かchar型か扱い方が変わるので、間違えてはならない。

2つ目は、size()==演算子等を利用する場合、一度変数に格納するか、"文字列"s.size()のように" "の末尾にsをつける必要があるという点である。単に"文字列".size()と書いた場合はコンパイルエラーになるので注意。

配列

C++のことは何も知らないとはいえvectorというのは何か聞いたことがあるぞ。

#include <bits/stdc++.h>
using namespace std;

int main() {
//入力を受け取らない場合
  vector<int> vec; // int型の配列変数vecを宣言

  vec = { 25, 100, 64 }; // 25, 100, 64 という整数(int)の列を代入

  cout << vec.at(0) << endl; // 1つめである25を出力

  cout << vec.size() << endl; // 配列の長さである3を出力

  vec.push_back(1000); // 末尾に1000を追加

 // vecの全要素を出力
  for (int i = 0; i < vec.size(); i++) {
    cout << vec.at(i) << endl;
  }

  vec.pop_back(); // 末尾の要素を削除
 
  // vecの全要素を出力
  for (int i = 0; i < vec.size(); i++) {
    cout << vec.at(i) << endl;
  }

//入力を受け取る場合
  // 100要素の配列で初期化
  vec = vector<int> (100);
 
  // 100個の入力を受け取る
  for (int i = 0; i < 100; i++) {
    cin >> vec.at(i);
  }
}

色々な要素を詰め込んでコードが長くなってしまった。

まず、上のコードでは入力がない場合とある場合の2通りを示している。ない場合は、vector<int> vecと宣言し、ある場合は、vector<int> vec(100)とでも宣言すれば良い。

配列の長さを指定して宣言した場合、vecは全要素が0の状態に初期化される。この値は変更できて、例えばvector<int> vec(100,2)とすると配列は{ 2 , 2 , ,,, , 2 }で初期化される。
配列の長さがわからないときにどのように読み込めばいいかは、また調べて追記します。

配列には文字列にも適用できたsize()at()といった関数に加え、push_back()pop_back()といった要素を追加、削除する関数や、reverse(vec.begin(), vec.end())sort(vec.begin(), vec.end())といった配列をソートする関数など、たくさんの関数が用意されている。

また、関数は自分で定義できることも覚えておきたい。これはまとめようかと思ったが、C言語をやっている私にはあまりにも既知すぎたのでここには書かないでおく(1つだけ、自分で定義した関数には、例え返り値がない関数でも必ずreturn;をつけることだけは気をつけよう)。

おわりに(一番読まなくていい)

とりあえず、C++における基本の基本は上でまとめた通りである。

上の内容は最初に挙げた教材の第1章のみの内容であるので、これからさらにC++の基礎に関する記事を書くかもしれないし書かないかもしれない。

C++に手を出すとは言ってみたものの、実際のところ飽きて今日限り使わない可能性だってある。そもそも競プロに飽きてやらなくなる可能性も、ブログに飽きちゃう可能性も、明日以降スマブラしかしなくなっちゃう可能性もある。

家への帰り道でめちゃくちゃ金持ちのお婆さんを助けて養子にしてもらえるかもしれないし、ポケモンのダイヤモンドパールがリメイクされることもあるかもしれない。まあそれはないか。



あ、以上で〜す。

*1:機械学習・データサイエンスのコミュニティサイト。 機械学習を学ぶコースや、様々なコンペティションを開催している。 www.kaggle.com