【第2回】Brainfuckを実装しながら学ぶC++【実装してみよう!前編】
IT技術
連載「Brainfuckを実装しながら学ぶC++」シリーズの第2回目です。
さて前回は、導入ということで、Brainfuck の仕組みを解説しました。
今回からは、実際に C/C++ を使って、実装していきましょう。
…と言いたいところですが、今回はまず C/C++ の基本的な話を中心に、解説していきたいと思います!
ですので、必要のない方は読み飛ばしていただいて OK です。
環境構築
これは、必要な人だけ読んでください。
今回必須な環境は、C/C++ コンパイラ「gcc」と、ビルドで使う「CMake」です。
「過去にインストールしたかもな…」と思った方は、以下のコマンドで確認してみてください。
1$ gcc --version
2$ cmake --version
gcc/g++
gcc は、C/C++ コンパイラの一つです。
macOS であれば、Homebrew で簡単にインストールできます。
1$ brew install gcc
ちなみに macOS では、デフォルトで「Clang」というコンパイラが入っているのですが、おそらくデフォルトのままでも大丈夫かと思います。
もし、筆者の環境と同じものにしたい場合は、Homebrew で新たに gcc を入れてください。
Windows では、MinGW というインストーラがよく利用されます。
CMake
CMake は、C/C++ ソースファイルを「解析・コンパイル」して、実行ファイルを生成するための Makefile を生成してくれるツールです。
macOS では、こちらも Homebrew でインストールできます。
1$ brew install cmake
または、Windows 含め、以下からインストーラを入手することもできます。
【 CMake 公式サイト】
https://cmake.org/download/
C/C++プロジェクトを作る
プロジェクトディレクトリをつくるほどのものではありませんが、CMake を利用したいので作成します。
ちなみに、Jetbrains 社の C/C++ IDE である「CLion」では、同じようなプロジェクトが自動で生成されます。
もし、ライセンスを持っている方は、ぜひ試してみてくださいね!
さて本連載では、プロジェクト名を「brainfuck」としておきます。
まずは、以下のようなディレクトリ構成を作成しましょう。
1brainfuck/
2├── biuld/ # Cmakeによるビルド用ディレクトリ (ここに完成品が出来上がる)
3├── CMakeLists.txt # CMakeスクリプト
4└── main.cpp # 実際にインタプリタを実装していくファイル
それぞれのファイルに、以下のようなコードを書いてみてください。
※ CLion で自動生成されるものと同じです。
CMakeLists.txt
1cmake_minimum_required(VERSION 3.16)
2project(brainfuck) # プロジェクト名
3
4set(CMAKE_CXX_STANDARD 11) # C++11 でのコンパイル
5
6add_executable(brainfuck main.cpp) # brainfuckという名前でビルド
正直なところ、もっと古いバージョンでも実装可能ですが、今回は C++11 にしておきます。
main.cpp
1#include <iostream>
2
3int main() {
4 std::cout << "Hello, World!" << std::endl;
5 return 0;
6}
こちらはよくある、最初の一歩「Hello, World!」です。
深く C++ を理解したいエンジニアのために、この基本的なコードを細かく解説していきます。
まず、#include <iostream> は、入出力に関する標準ライブラリを読み込んでいる記述です。
メイン関数 int main(){ } では、Java におけるメインクラスのように、このスコープ内のコードが順に実行されていきます。
std:: は、標準ライブラリの名前空間 (namespace) です。
名前空間は、関数名やクラス名などに重複がないことがハッキリしていれば、以下のように省略ができます。
1#include <iostream>
2using namespace std; // stdは省略する、という宣言
3
4int main() {
5 cout << "Hello, World!" << endl;
6 return 0;
7}
ちなみに、上のコードの各意味は、次のとおりです。
- cout :出力ストリーム
- endl :改行
- << :C++ では演算子のひとつ
- cout :文字列や数値型を出力にスタックする
ビルドして実行してみる
CMake を利用したビルドと実行は、とても簡単。
1$ cd build # ビルド用ディレクトリに移動
2$ cmake .. # CMakeLists.txtを探し解析する
3$ make # Makefileを実行 => brainfuckという名の実行ファイルが出来上がる
4$ ./brainfuck
5Hello, World!
上手くいきましたね!
C/C++の基本要素
型と関数
C/C++ では、常に明示的に型を記述する必要があります。
これは関数も同様で、返すものの型を明記します。
もし、何も返すものがないのであれば、以下のように void を使用します。
1#include <iostream>
2#include <string> // 文字列型
3using namespace std;
4
5void say_hello(string str){ // 本当はstd::string
6 cout << "Hello, " + str + "!" << endl;
7}
8
9int main() {
10
11 say_hello("Brainfuck"); // > Hello, Brainfuck!
12
13 return 0;
14}
他にも、よく使われる型として、以下のような型があります。
int | 整数型 (32bit = 4byte) |
float | 単精度浮動小数点型 (32bit = 4byte) |
double | 倍精度浮動小数点型 (64bit = 8byte) |
char | 文字型 (8bit = 1byte) (string の構成要素であり整数も一応扱える) |
ポインタ
C/C++ の重要な要素、「ポインタ」の扱いにも、軽く触れておきます。
通常、変数は他の言語と同じく、値を指すものとして保持されます。
以下のようなコードは、違和感なく見ることができますよね?
1int a = 123;
2cout << a << endl; // > 123
ところが、値そのものではなく変数のアドレスを扱いたい場合には、「*」と「&」を使う必要があるのです。
例えば、以下のような具合です。
1int a = 123; // 通常の変数宣言
2cout << a << endl; // > 123
3
4int* a_ptr = &a; // aのメモリアドレスをポインタとして保持
5cout << a_ptr << endl; // 例: > 0x7ffee6111b48
6cout << (&a == a_ptr) << endl; // > 1 (true)
7cout << *a_ptr << endl; // > 123
型名に * を付ければ、その変数はメモリアドレスを持つ変数になります。
ちなみに、int* a_ptr と int *a_ptr は同じ意味です。
また逆に、変数のメモリアドレスを参照する場合は、&a と記述します。
このメモリアドレスを扱う、ということを理解するには、値渡しと参照渡しを比較してみるのが一番です。
1int a = 123; // 通常の変数宣言
2int b = a; // 値渡し
3int *c = &a; // 参照渡し
4
5cout << a << endl; // > 123
6cout << b << endl; // > 123
7cout << *c << endl; // > 123
8
9b = 321; // b の値を変えてみる
10
11cout << a << endl; // > 123
12cout << b << endl; // > 321 <= bだけ変わった
13cout << *c << endl; // > 123
14
15*c = 456; // cの指す値を変えてみる
16
17cout << a << endl; // > 456 <= aのアドレスとcは一緒なので,aの値も代わる
18cout << b << endl; // > 321
19cout << *c << endl; // > 456 <= cの指す値はもちろん変わる
このように、単に値が同じであることと、メモリアドレスを共有するということの違いが分かるかと思います。
Python では、基本は全て参照渡しなので、最初は困惑するかもしれません。
関数の引数でも同じです。
例えば以下のように、関数の引数に & を明示的に記せば、変数名は違っても同じアドレスを指すものとして扱われるようになります。
1#include <iostream>
2using namespace std;
3
4/* & をつけていないので「引数の値だけ使用する」ことを示す */
5int func1(int num1, int num2){
6 num1 += num2;
7 return num1;
8}
9
10/** & をつけることで明示的に「引数の値を変更する」ことを示す */
11int func2(int& num1, int& num2){
12 num1 += num2;
13 return num1;
14}
15
16int main() {
17
18 int a = 3, b = 5;
19
20 cout << func1(a, b) << endl; // > 8
21 cout << a << endl; // > 3 <= a は変更されない
22
23 cout << func2(a, b) << endl; // > 8
24 cout << a << endl; // > 8 <= a も変更される
25
26 return 0;
27}
ポインタと配列
ここからは、さらに重要な話になります!
C/C++ における配列についてです。
通常 C/C++では、以下のように配列を定義できます。
1int arr[] = {1, 2, 3};
2cout << arr[1] << endl; // > 2
ここは、特に問題ないでしょう。
しかし、以下のコードでは、どのように出力されるでしょうか?
1int arr[] = {1, 2, 3};
2cout << arr << endl; // > ?
試してみると分かりますが、0x7ffeeebbeb3c のように、何かのメモリアドレスが出力されます。
これは、配列の先頭要素のアドレスです。
つまり、配列は一種のポインタであることが分かります。
さらに、ポインタですので、以下のように扱うことも可能です。
1int arr[] = {1, 2, 3};
2cout << *arr << endl; // > 1
3cout << *(arr+1) << endl; // > 2
4cout << *(arr+2) << endl; // > 3
C/C++ のポインタは、かなり奥が深いですが、今回での基本的な話は以上になります。
その他に大切な要素は、インタプリタを実装しながら、確認していきましょう!
第3回目へつづく!
さて今回は、Brainfuck インタプリタを始める前に、大事な環境構築と C/C++ の基本の話をしました。
次回からは、いよいよ本格的な実装をしていきます!
その中で、C++ の大事な要素もバンバン出てきますので、これを機に C++ に入門していきましょう!
第3回はこちら!
第1回目はこちら!
こちらの記事もオススメ!
2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...
2020.07.27IT・コンピューターの歴史特集IT・コンピューターの歴史をまとめていきたいと思います!弊社ブログにある記事のみで構成しているため、まだ「未完成状態」...
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ
「好きを仕事にするエンジニア集団」の(株)ライトコードです! ライトコードは、福岡、東京、大阪、名古屋の4拠点で事業展開するIT企業です。 現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。 いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。 システム開発依頼・お見積もり大歓迎! また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」を積極採用中です! インターンや新卒採用も行っております。 以下よりご応募をお待ちしております! https://rightcode.co.jp/recruit