なぜC++を勉強するか?
- 実行速度が速いらしい: 競技プログラミングでは制限時間内に処理を完了する必要があるため、C++の高速な実行速度は大きな利点です
- AtCoderの回答例がC++で書かれている: 他の参加者の解答や解説がC++で書かれていることが多く、それらを別の言語に置き換えて読み解くのは手間がかかります。
- 参考書のサンプルコードもC++: 「競技プログラミングの鉄則」を購入して学習中だが、C++でサンプルコードが書かれています
- C言語に以前から興味があった: これまで学習する機会がなかったC/C++に触れるきっかけとして、AtCoderは良い動機付けになりました
C++ 基礎の基礎
変数と基本データ型
#include <iostream>
#include <string>
// 変数と基本データ型
// - 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 <iostream>
: 入出力のための標準ライブラリをプログラムに含める指示です。この行があることでstd::cout
などが使えるようになります。 -
#include <string>
: 文字列処理のためのライブラリをプログラムに含める指示です。この行があることで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 <iostream>
// ジャンプ:プログラムの実行位置を移動する
// - 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 <iostream>
// ループ
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 <iostream>
#include <string>
// 関数
// 関数のプロトタイプ宣言
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<double>(sum) はC++の型変換(タイプキャスト
// 整数型の sum を浮動小数点型の double に変換しています
return static_cast<double>(sum) / size;
}
関数のプロトタイプ宣言をする理由
-
前方参照を可能にする:C++は上から下へと順番にコードを解釈します。プロトタイプ宣言があれば、関数の本体が後に定義されていても、先に関数を呼び出すことができます。これにより、コードの構成を柔軟に行えます。
-
インターフェイスと実装の分離:プロトタイプ宣言はインターフェイス(何ができるか)を示し、関数定義は実装(どう動作するか)を示します。これは大規模なプログラムで特に重要です。
配列
#include <iostream>
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 <iostream>
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 <iostream>
#include <string>
#include <vector>
using namespace std;
// ベクターの基礎と操作
int main() {
// ベクターの宣言と初期化
vector<int> vec1; // 空のベクター
vector<int> vec2(5); // 5つの要素(すべて0)を持つベクター
vector<int> vec3(5, 10); // 5つの要素(すべて10)を持つベクター
vector<int> 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; // いいえ
// ------------------------------------------------------------------
vector<int> 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 <iostream>
#include <vector>
using namespace std;
// v: イテレート(反復処理)するコンテナ(この場合はベクター) | element: 各イテレーションで取得される要素を参照する変数名 | const auto&: 要素の型の宣言部分
// const: この修飾子は element が変更できないこと(読み取り専用)を示します | auto: コンパイラに型を自動的に推論させます | &: element が v の実際の要素への参照(reference)であることを示します | : 「〜の中の各要素」という意味を表します
void printVector(const vector<int>& v, const string& name) {
cout << name << " = { ";
for(const auto& element : v) {
cout << element << " ";
}
cout << "}" << endl;
// 例1 => 初期状態 = { 1 2 3 }
}
// ベクターの操作
int main() {
vector<int> 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 }
// 別のベクターからの挿入
vector<int> 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 <iostream>
#include <string>
#include <vector>
#include <sstream>
using namespace std;
int main() {
// 数値を一つ入力する
// int number;
// cout << "数字を入力してください: ";
// cin >> number; // ここで入力を待つ
// cout << "あなたが入力した数字は " << number << " です。" << endl;
// ------------------------------------------------------------------
// 複数の値を入力する
// std::cinが空白文字(スペース、タブ、改行など)を区切り文字として扱う
// int x, y;
// cout << "2つの数字をスペースで区切って入力してください: ";
// cin >> x >> y; // 5 6 と入力
// cout << "合計: " << x + y << endl; // => 合計: 11
// ------------------------------------------------------------------
// 文字列の入力
// string name;
// std::cout << "あなたの名前を入力してください: ";
// cin >> name; // okuyama と入力
// cout << "こんにちは、" << name << "さん!!" << endl; // => こんにちは、okuyamaさん!!
// ------------------------------------------------------------------
// 空白を含む文字列の入力
// string full_name;
// cout << "フルネームを入力してください: ";
// getline(cin, full_name); // sato taro と入力
// cout << "こんにちは、" << full_name << "さん" << endl; // => こんにちは、sato taroさん
// ------------------------------------------------------------------
// 注意点:数値と文字列を混在させる場合 (失敗例)
// int age;
// string name;
// cout << "年齢を入力してください: ";
// cin >> age;
// // 問題:この行ではEnterキーが入力バッファに残っているため、
// // getlineはそれを読み取って即座に終了してしまう
// cout << "名前を入力してください: ";
// getline(cin, name); // この時点で入力バッファには'\n'が残っているので、「\n」だけを読み込んで、空の文字列を line に格納してしまいます。
// cout << "あなたは" << age << "歳の" << name << "さんです。" << endl; // => (失敗例) 名前を入力してください: あなたは20歳のさんです。
// ------------------------------------------------------------------
// 数値と文字列を混在させる場合 (成功例)
// int age;
// string name;
// cout << "年齢を入力してください: ";
// cin >> age;
// // std::cin.ignore() - 入力ストリーム(cin)から文字を読み飛ばす関数
// // std::numeric_limits<std::streamsize>::max() - 読み飛ばす最大文字数を指定(今回も場合は改行文字を含むすべての文字を、バッファーから削除し、クリーンにする)
// // '\n' - 改行文字が見つかるまで読み飛ばすという条件
// cin.ignore(numeric_limits<streamsize>::max(), '\n');
// cout << "名前を入力してください: ";
// getline(cin, name);
// cout << "あなたは" << age << "歳の" << name << "さんです。" << endl; // => (失敗例) 名前を入力してください: あなたは20歳のさんです。
// ------------------------------------------------------------------
// 入力した数字からスペース区切りの配列を作成
string input;
vector<int> numbers; // vectorは動的配列。 push_back(), pop_back(), insert(), erase()など、要素を操作するための便利なメソッドを多数提供。
cout << "スペース区切りで数字を入力してください: ";
getline(cin, input);
// cout << input << endl; // => 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 のストーリーもご覧いただけるととても嬉しいです!