C++に入門する #AtCoder - Qiita

なぜC++を勉強するか?

  1. 実行速度が速いらしい: 競技プログラミングでは制限時間内に処理を完了する必要があるため、C++の高速な実行速度は大きな利点です
  2. AtCoderの回答例がC++で書かれている: 他の参加者の解答や解説がC++で書かれていることが多く、それらを別の言語に置き換えて読み解くのは手間がかかります。
  3. 参考書のサンプルコードもC++: 「競技プログラミングの鉄則」を購入して学習中だが、C++でサンプルコードが書かれています
  4. C言語に以前から興味があった: これまで学習する機会がなかったC/C++に触れるきっかけとして、AtCoderは良い動機付けになりました

C++ 基礎の基礎

変数と基本データ型

#include 
#include 

// 変数と基本データ型

// - char: C++の基本データ型(プリミティブ型)で、1文字だけを格納できます
// - std::string: C++の標準ライブラリに含まれるクラス(複合型)で、複数の文字からなる文字列を扱います


// - float: 約7桁の精度(単精度)  数値の後に f または F をつける(例: 19.99f)
// - double: 約15-16桁の精度(倍精度) サフィックスなし、または d または D をつける(例: 3.14 または 3.14d)

int main() {
  // 整数型
  int age = 25;

  // 浮動小数点型
  float price = 19.99f;
  double precise_value = 3.141592653589793;

  // 文字型 (1文字のみ)
  char grade = 'A';

  // 真偽値型
  bool is_active = true;

  // 文字列(C++のstring型)
  std::string name = "C++ Learner";

  std::cout  "Age: "  age  std::endl;
  std::cout  "Price: "  price  std::endl;
  std::cout  "Grade: "  grade  std::endl;
  std::cout  "Active: "  is_active  std::endl;
  std::cout  "Name: "  name  std::endl;

  return 0;
}
  • #include : 入出力のための標準ライブラリをプログラムに含める指示です。この行があることで std::cout などが使えるようになります。
  • #include : 文字列処理のためのライブラリをプログラムに含める指示です。この行があることで std::string 型が使えるようになります。
  • int main(): プログラムの開始点(エントリーポイント)です。

    • int: 関数の戻り値の型を示しています。main 関数は整数値を返すことを示しています。
    • main: 関数の名前です。C++では必ずこの名前のエントリーポイント関数が必要です。
    • (): 関数のパラメータリストです。空の括弧は、この関数が引数を取らないことを示しています。
    • return 0;: プログラムが正常終了したことを示す値(0)を返します。
  • 出力の解説
    • std::cout: 標準出力ストリームです(Console OUTput)。
    • : 出力演算子で、データをストリームに送ります。
    • std::endl: 改行を挿入し、バッファをフラッシュします(出力を確実に表示させます)。
  • メモリとデータ型のサイズ
    • char: 1バイト(8ビット)
    • bool: 通常1バイト
    • int: 通常4バイト(32ビット)
    • float: 4バイト(32ビット)
    • double: 8バイト(64ビット)
    • std::string: 可変サイズ(内部で動的にメモリを確保)

条件分岐

#include 

// ジャンプ:プログラムの実行位置を移動する
// - break:ループや switch 文から抜け出す
// - continue:ループの残りの処理をスキップして次の繰り返しへ
// - return:関数から値を返して終了する

// 条件分岐
int main() {
  int score = 85;

  // if-else文
  if (score >= 90) {
      std::cout  "Grade: A"  std::endl;
  } else if (score >= 80) {
      std::cout  "Grade: B"  std::endl;
  } else if (score >= 70) {
      std::cout  "Grade: C"  std::endl;
  } else {
      std::cout  "Grade: D"  std::endl;
  }

  // switch分
  // break の役割: case で条件に一致した部分を実行した後、switch 文全体から抜け出します。 もし break がないと、その下にある全ての case の処理も続けて実行されてしまいます(これを「フォールスルー」と呼びます)
  int day = 2;
  switch (day) {
    case 1:
      std::cout  "Monday"  std::endl;
      break;
    case 2:
      std::cout  "Tuesday"  std::endl;
      break;
    case 3:
      std::cout  "Wednesday"  std::endl;
      break;
    // 他の曜日も同様に...
    default:
      std::cout  "Invalid day"  std::endl;
  }

  return 0;
}

繰り返し処理 ループ

#include 

// ループ

int main() {
  // for ループ
  std::cout  "For Loop:"  std::endl;
  for (int i = 0; i  5; i++) {
    std::cout  i  " ";
  }
  std::cout  std::endl; // => 0 1 2 3 4

  // while ループ
  std::cout  "While Loop:"  std::endl;
  int j = 0;
  while (j  9) {
    std::cout  j  " ";
    j ++;
  }
  std::cout  std::endl; // => 0 1 2 3 4 5 6 7 8

  // do-while ループ
  std::cout  "Do-while Loop:"  std::endl;
  int k = 0;
  do {
    std:: cout  k  " ";
    k++;
  } while (k  6);
  std::cout  std::endl; // => 0 1 2 3 4 5

  return 0;
}

関数

#include 
#include 

// 関数

// 関数のプロトタイプ宣言
int add(int a, int b);
void greet(std::string name);
double calculateAverage(int arr[], int size);


int main() {
  // 関数の呼び出し
  int sum = add(5, 3);
  std::cout  "Sum: "  sum  std::endl;

  greet("C++ Beginner");

  int scores[] = {85, 90, 78, 92, 88};
  double avg = calculateAverage(scores, 5);
  std::cout  "Average score: "  avg  std::endl; // => Average score: 86.6

  return 0;
}

// 関数の定義
int add(int a, int b) {
  return a + b;
}

void greet(std::string name) {
  std::cout  "Hello, "  name  "!"  std::endl;
}

double calculateAverage(int arr[], int size) {
  int sum = 0;
  for (int i = 0; i  size; i++) {
    sum += arr[i];
  }

  // static_cast(sum) はC++の型変換(タイプキャスト
  // 整数型の sum を浮動小数点型の double に変換しています
  return static_castdouble>(sum) / size;
}

関数のプロトタイプ宣言をする理由

  • 前方参照を可能にする:C++は上から下へと順番にコードを解釈します。プロトタイプ宣言があれば、関数の本体が後に定義されていても、先に関数を呼び出すことができます。これにより、コードの構成を柔軟に行えます。

  • インターフェイスと実装の分離:プロトタイプ宣言はインターフェイス(何ができるか)を示し、関数定義は実装(どう動作するか)を示します。これは大規模なプログラムで特に重要です。

配列

#include 
using namespace std;

// 配列
int main() {
  // 配列の宣言と初期化
  int numbers[5] = {10, 20, 30, 40, 50};

  // 個別の要素にアクセス
  cout  "Third element: "  numbers[2]  endl;  // 0から始まるので2番目は30

  // 配列の全要素の出力
  for (int i = 0; i  5; i++) {
    cout  numbers[i]   endl;
  }

  // 多次元配列
  int matrix[2][3] = {
    {1, 2, 3},
    {4, 5, 6}
  };

   // 多次元配列の全要素を出力
  cout  "Matrix elements:"  endl;
  for (int i = 0; i  2; i++) {
    for (int j = 0; j  3; j++) {
      cout  matrix[i][j]  " ";
    }
    cout  endl;
  }

  return 0;
}

ポインタ

#include 
using namespace std;

// ポインタ

void incrementByValue(int num);
void incrementByReference(int& num);

int main() {
  // 変数の宣言
  int number = 42;

  // ポインタの宣言と初期化
  int* ptr = &number; // numberのアドレスをptrに格納

  // 値とアドレスの表示
  cout  "Value of number: "  number  endl;    // Value of number: 42
  cout  "Address of number: "  &number  endl; // Address of number: 0x16baceca8  // &number は number 変数のメモリアドレス
  cout  "Value of ptr: "  ptr  endl;          // Value of ptr0x16f0beca8  // ptr は number のメモリアドレスを格納しているポインタ
  cout  "Value ptr points to: "  *ptr  endl;  // (デリファレンス) Value ptr points to: 42  // デリファレンス(dereference)とは、ポインタが指しているメモリアドレスの値を取得する操作です。C++では「*」(アスタリスク)演算子を使って行います。

  // ポインタを使った値の変更
  *ptr = 100;
  cout  "New value of number: "  number  endl; // New value of number: 100

  // 動的メモリ割り当て
  int* dynaamic_array = new int[5]; // 5つのint型のメモリ確保

  cout  dynaamic_array  endl;   // 0x131804080

  // 値の設定
  for (int i = 0; i  5; i++) {
    dynaamic_array[i] = i * 50;
  }

  // 値の表示
  for (int i = 0; i  5; i++) {
    cout  "dynamic_array["  i  "] = "  dynaamic_array[i]  endl;
  }

  cout  *dynaamic_array  endl;         // 0 配列の先頭要素(0番目の要素)の値
  cout  dynaamic_array[1]  endl;       // 50 配列の先頭要素(1番目の要素)の値
  cout   *(dynaamic_array + 2)  endl;  // 100 そのアドレスにある値(2番目の要素の値)を取得します

  // 各要素のアドレスを表示
  for (int i = 0; i  5; i++) {
    cout  "&arr["  i  "] = "  &dynaamic_array[i]  endl;
  }
  // 配列の各要素にはそれぞれ固有のアドレスがあります。これらのアドレスは連続しており、要素のサイズだけずれています:
  // &arr[0] = 0x12fe05d80
  // &arr[1] = 0x12fe05d84  // int型は通常4バイトなのでアドレスが4増加
  // &arr[2] = 0x12fe05d88
  // &arr[3] = 0x12fe05d8c
  // &arr[4] = 0x12fe05d90


  // ----------------------------------------------------------------------------------

  int x = 10;
  int y = 20;

  cout  "Before increment - x: "  x  ", y: "  y  endl; // => Before increment - x: 10, y: 20

  incrementByValue(x);      // 値渡し
  incrementByReference(y);  // 参照渡し

  cout  "After increment - x: "  x  ", y: "  y  endl; // => After increment - x: 10, y: 21

  // 参照の宣言
  // 参照演算子 & について
  // int& という型宣言の中の & は、「参照型」を作成することを意味します。
  int& ref = x;  // xへの参照
  ref = 100;     // ref に対する操作は全て実際には x に対して行われます。 ref と x は同じメモリ位置を指します

  cout  "After reference modification - x: "  x  endl; // => After reference modification - x: 100

  return 0;
}

// 値渡しの関数
void incrementByValue(int num) {
  num++;

  // 値渡しでは:
  // 1. 引数のコピーが関数内で作成されます
  // 2. 関数内での変更は、呼び出し元の変数に影響を与えません
  // 3. 元の変数とは別のメモリ領域を使用します
  // 4. 大きなデータ構造の場合はコピーのコストが高くなります
  // 今回のコード例では、incrementByValue(x)を呼び出しても、xの値は変わらず10のままです。関数内ではnumというコピーが作られて、そのコピーの値だけが増加します。
}

// 参照を使用した関数
// C++における int& の & は「参照演算子」と呼ばれるものです。
void incrementByReference(int& num) {
  num++;

  // 参照渡しでは:
  // 1. 引数の**エイリアス(別名)**が作成されます
  // 2. 関数内での変更は、呼び出し元の変数に直接影響します
  // 3. 追加のメモリを使わず、元の変数と同じメモリ領域を参照します
  // 4. 大きなデータ構造を効率的に扱えます
  // 今回のコード例では、incrementByReference(y)を呼び出すと、yの値が20から21に変わります。関数内のnumはyの参照(別名)なので、numを変更すると実際にはyを変更していることになります。
}

Atcoder対策の基礎

ベクターの基礎と操作

基本

#include 
#include 
#include 
using namespace std;

// ベクターの基礎と操作
int main() {
  // ベクターの宣言と初期化
  vectorint> vec1;        // 空のベクター
  vectorint> vec2(5);     // 5つの要素(すべて0)を持つベクター
  vectorint> vec3(5, 10);  // 5つの要素(すべて10)を持つベクター
  vectorint> vec4 = {1, 2, 3, 4, 5}; // 初期値を指定したベクター

  // ベクターのサイズを取得
  cout  "vec1のサイズ: "  vec1.size()  endl;       // 0
  cout  "vec2のサイズ: "  vec2.size()  endl;       // 5
  cout  "vec3のサイズ: "  vec3.size()  endl;       // 5
  cout  "vec4のサイズ: "  vec4.size()  endl;       // 5

  // ベクターが空かどうか確認
  cout  "vec1は空か: "  (vec1.empty() ? "はい" : "いいえ")  endl;  // はい
  cout  "vec4は空か: "  (vec4.empty() ? "はい" : "いいえ")  endl;  // いいえ

  // ------------------------------------------------------------------

  vectorint> numbers = {10, 20, 30, 40, 50};
  cout  numbers[2]  endl; // 30

  // 最初と最後の要素にアクセス
  cout  numbers.front()  endl;  // 10
  cout  numbers.back()  endl;   // 50

  // 要素の変更
  numbers[1] = 25;
  numbers.at(3) = 45;

  // ベクターの内容を表示
  for(int num : numbers) {
    cout  num  " ";
  }
  cout  endl;  // 10 25 30 45 50

  return 0;
}

操作 (随時更新)

#include 
#include 
using namespace std;

// v: イテレート(反復処理)するコンテナ(この場合はベクター) | element: 各イテレーションで取得される要素を参照する変数名 | const auto&: 要素の型の宣言部分
// const: この修飾子は element が変更できないこと(読み取り専用)を示します | auto: コンパイラに型を自動的に推論させます | &: element が v の実際の要素への参照(reference)であることを示します | : 「〜の中の各要素」という意味を表します
void printVector(const vectorint>& v, const string& name) {
  cout  name  " = { ";
  for(const auto& element : v) {
    cout  element  " ";
  }
  cout  "}"  endl;
  // 例1 => 初期状態 = { 1 2 3 }
}

// ベクターの操作
int main() {
  vectorint> vec = {1, 2, 3};
  printVector(vec, "初期状態");

  // 末尾に要素を追加
  vec.push_back(4);
  vec.push_back(5);
  printVector(vec, "push_back後");

  // 要素の挿入
  // (vec.begin() + 2) - インデックス2番目の要素を指すイテレータを得られます
  vec.insert(vec.begin() + 2, 10); // インデックス2の位置(つまり3番目の要素の位置)に値10を挿入する
  printVector(vec, "insert後"); // insert後 = { 1 2 10 3 4 5 }

  // 複数要素の挿入
  vec.insert(vec.begin() + 4, 3, 99); // インデックス4の位置(つまり5番目の要素の位置)に値99を3つ挿入する
  printVector(vec, "複数insert後"); // 複数insert後 = { 1 2 10 3 99 99 99 4 5 }

  // 別のベクターからの挿入
  vectorint> another = {100, 200, 300};
  vec.insert(vec.begin() + 1, another.begin(), another.end());
  printVector(vec, "別ベクターからのinsert後"); // 別ベクターからのinsert後 = { 1 100 200 300 2 10 3 99 99 99 4 5 }

  // 末尾の要素の削除
  vec.pop_back();
  printVector(vec, "pop_back後"); // pop_back後 = { 1 100 200 300 2 10 3 99 99 99 4 }

   // 特定位置の要素を削除
  vec.erase(vec.begin() + 3);  // index 3の要素を削除 300が削除される
  printVector(vec, "erase後"); // erase後 = { 1 100 200 2 10 3 99 99 99 4 }

  // サイズの変更
  vec.resize(11);  // サイズを11に拡大(新しい要素は0で初期化)
  printVector(vec, "resize(10)後"); // { 1 100 200 2 10 3 99 99 99 4 0 }

  vec.resize(5);  // サイズを5に縮小(末尾の要素が削除される)
  printVector(vec, "resize(5)後"); // resize(5)後 = { 1 100 200 2 10 }

  vec.resize(8, 7);  // サイズを8に拡大(新しい要素は7で初期化)
  printVector(vec, "resize(8,7)後"); // resize(8,7)後 = { 1 100 200 2 10 7 7 7 }

  // ベクターのクリア
  vec.clear();  // すべての要素を削除
  printVector(vec, "clear後"); // clear後 = { }

  return 0;
}

セットとマップの基礎と操作

後日更新予定

標準入力

  • std::cin で標準入力に対して入力します。
  • cinは「Character INput」(文字入力)の略です。
  • cinの操作の直前に、標準出力(cout)のバッファが自動的にフラッシュ(出力)されます。
  • cinの後にcoutを使うと、新しい行から出力が始まります。これは入力時のEnterキーによる改行によるものです。
  • 以下のコードはセクションごとにコメントを外して動作確認できます。
#include 
#include 
#include 
#include 
using namespace std;

int main() {
  // 数値を一つ入力する
  // int number;
  // cout 
  // cin >> number; // ここで入力を待つ
  // cout 

  // ------------------------------------------------------------------

  // 複数の値を入力する
  // std::cinが空白文字(スペース、タブ、改行など)を区切り文字として扱う
  // int x, y;
  // cout 
  // cin >> x >> y; // 5 6 と入力
  // cout  合計: 11

  // ------------------------------------------------------------------

  // 文字列の入力
  // string name;
  // std::cout 
  // cin >> name; // okuyama と入力
  // cout  こんにちは、okuyamaさん!!

  // ------------------------------------------------------------------

  // 空白を含む文字列の入力
  // string full_name;
  // cout 
  // getline(cin, full_name); // sato taro と入力

  // cout  こんにちは、sato taroさん

  // ------------------------------------------------------------------

  // 注意点:数値と文字列を混在させる場合 (失敗例)
  // int age;
  // string name;

  // cout 
  // cin >> age;

  // // 問題:この行ではEnterキーが入力バッファに残っているため、
  // // getlineはそれを読み取って即座に終了してしまう
  // cout 
  // getline(cin, name); // この時点で入力バッファには'n'が残っているので、「n」だけを読み込んで、空の文字列を line に格納してしまいます。

  // cout  (失敗例) 名前を入力してください: あなたは20歳のさんです。

  // ------------------------------------------------------------------

  // 数値と文字列を混在させる場合 (成功例)
  // int age;
  // string name;

  // cout 
  // cin >> age;

  // // std::cin.ignore() - 入力ストリーム(cin)から文字を読み飛ばす関数
  // // std::numeric_limits<:streamsize>::max() - 読み飛ばす最大文字数を指定(今回も場合は改行文字を含むすべての文字を、バッファーから削除し、クリーンにする)
  // // 'n' - 改行文字が見つかるまで読み飛ばすという条件
  // cin.ignore(numeric_limits::max(), 'n');

  // cout 
  // getline(cin, name);

  // cout  (失敗例) 名前を入力してください: あなたは20歳のさんです。

  // ------------------------------------------------------------------

  // 入力した数字からスペース区切りの配列を作成
  string input;
  vectorint> numbers; // vectorは動的配列。 push_back(), pop_back(), insert(), erase()など、要素を操作するための便利なメソッドを多数提供。

  cout  "スペース区切りで数字を入力してください: ";
  getline(cin, input);

  // cout  1 2 3 4 5

  stringstream ss(input); // この行で作成される ss は、文字列をストリームとして扱うためのオブジェクト。ss はその文字列「1 2 3 4 5」を保持していますが、重要なのは単なる文字列ではなく、ストリームとして扱えるようになっていること

  int number;

  while (ss >> number) {
    // 要素を追加
    numbers.push_back(number);
  }

  cout  "あなたが入力した数字の配列: "  "{";
  for (size_t i = 0; i  numbers.size(); i++) {
    cout  numbers[i];
    if (i  numbers.size() - 1) {
      cout  ", ";
    }
  }

  cout  "}"  endl; // => あなたが入力した数字の配列: {1, 2, 3, 4, 5}

  return 0;
}

終わりに

随時更新して、競技プログラミングに入門するための準備が概ねできるようなブログにしていく予定です。


株式会社シンシアでは、実務未経験のエンジニアの方や学生エンジニアインターンを採用し一緒に働いています。
※ シンシアにおける働き方の様子はこちら

弊社には年間100人程度の実務未経験の方に応募いただき、技術面接を実施しております。
この記事が少しでも学びになったという方は、ぜひ wantedly のストーリーもご覧いただけるととても嬉しいです!



フラッグシティパートナーズ海外不動産投資セミナー 【DMM FX】入金

Source link