今日やること
- 足し算引き算を計算する
- 標準入出力(IOストリーム)の使い方
main関数を作る
まずはmain関数を作りましょう。今回は先にお手本コードをお見せします。main.cppというファイルに以下の内容を書いてください。
int main() {
std::string input;
std::cout << "式を入力: ";
std::cin >> input;
std::cout << "実行結果: " << eval(input) << std::endl;
return 0;
}
1行ずつ解説していきますね。
std::string input;
この行はinputという変数を宣言しています。変数の宣言は型名 変数名;
の形で書きます。この行だとstd::string
が型で、inputが変数名です。std::string
は文字列を表すC++のクラスです。
std::cout << "式を入力: ";
この行はコマンドラインに「式を入力: 」という文字列を表示(標準出力)しています。
std::cout
は標準出力ストリームと呼び、標準出力に出す文字列を貯めて、ストリームに改行文字が入ってきたら1行出力する機能を持ちます。<<
は本来なら左シフト演算子と呼ばれるものなのですが、ストリームに対して使うと「右辺の文字列をストリームに入れる」という意味になります。std::cout
に文字列を押し込むイメージです。
他の言語と同様に、C++では文字列を"
で囲みます。
std::cin >> input;
この行は標準入力から文字列を取得し、inputに格納しています。std::cout
と同様にシフト演算子>>
を用いて取り出すことができます。
std::cout << "実行結果: " << eval(input) << std::endl;]
この行は「実行結果: (入力された式の結果)」を標準出力しています。std::cout
は出力したいものが複数ある時でも<<
を用いてつなげて書くことができます。
std::cout << "a=" << 1; // a=1と出力される
真ん中のeval(input)
はこれから実装するeval関数を呼び出しています。eval関数は入力された文字列を評価し、結果をint型の数値で返す関数です。
std::endl
は改行をストリームに送って、ストリームの中身を出力するためのものです。これは関数などではなく、ストリームに入れる値です。
return 0;
この行はmain関数の処理をこの行で終了させるコードです。return
自体はC言語と同様に関数から戻り値を返して、処理を終了するキーワードです。特に、main関数から0を返すと、プログラム自体が正常終了します。本当は色々深掘りできるのですが、この記事ではこれくらいにしておきます。詳しいことは「終了コード」でググってください。
初めてのコンパイル
ではmain.cppをコンパイルしてみましょう!コンパイルとは、コード全体を機械語(CPUが解釈する命令)に変換して、実行できるようにすることです。
コマンドラインにg++ main.cpp
と入力してみましょう。すると画像のようなエラーが出るはずです。
エラーメッセージは基本的に「ファイル名:行数:列数: error: エラーメッセージ」の書式で書かれています。今回だと5つエラーが出ていますが、エラーメッセージは全て「use of undeclared identifier 'std'」ですね。
直訳すると「'std'という宣言されていない識別子の使用」という意味になります。これは、std
がファイル内で宣言されていないので、コンパイラが宣言を見つけられずに困っている、ということです。
エラーの解消方法
これを解決するためには、std::cout
やstd::cin
が宣言されているファイルをincludeする必要があります。main.cppの先頭行に#include <iostream>
という行を追加しましょう。#include <iostream>
は、iostreamという標準入出力関係のライブラリの宣言をmain.cppに挿入します。#include
は、ファイルの中身をコードに挿入するC++の機能(ディレクティブ)で、特にライブラリの関数や自作の別ファイルの関数などを使用するときに使います。詳しいことは別の記事にいつかします。
リコンパイル
修正できたら再度g++ main.cpp
を実行してコンパイルしましょう。するとまたエラーが出るはずです。
今度はeval
が見つからずにエラーが出たようです。evalは後で実装する関数でまだ定義してません。なので、プリミティブ宣言をmain関数の前に追加しておきましょう。以下の行をmain関数の前に入れてください。
int eval(std::string input);
プリミティブ宣言は、関数の戻り値の型、名前、引数を先に宣言する方法です。コンパイラは、関数呼び出しをコンパイルする際に関数名から宣言を探します。なので、関数呼び出しより前に宣言しておく必要があるのです。
では再度コンパイルしてみましょう!
すると、またまたエラーが出ます...
エラー文の最後に「error: linker command failed with...」とか書いてますね。これはリンクエラーと呼ばれ、コードの構文が正しいかどうか解析した後に、コードを機械語に変換して実行ファイルを生成するタイミングで起こります。
今回の場合、エラーの1行目に「Undefined symbols」(未定義のシンボル)と書いてあります。これはエラーの種類で、今回だとシンボル(関数名などの識別子のこと)の定義が見つからないということです。定義とは、変数なら変数の値、関数なら関数の処理が記載されたコードのことです。このエラーの場合、下のエラー文に未定義の関数と呼び出し箇所が書いてあります。
この例だと未定義の関数は「eval(std::__1::basic...)」で、「_main in main-6ccf6f.o」が呼び出し箇所になります。
以上を要約すると「evalという関数の定義がmainの中で見つからない」というエラーです。eval関数は後ほど実装する関数なので定義がなくて当然です。よって、このエラーは一旦無視しても大丈夫ですね!
eval関数の実装
int eval(std::string input);
ここからはeval関数を実装していきます。先に実装方針を示すのでぜひ自分で手を動かして進めてみてください。
実装方針
eval関数は、inputに渡される式を計算して、計算結果を返す関数です。
まず、inputに渡される文字列に着目します。inputに入ってきそうな文字列は1+1
1-1
1+2-3
などですね。
この文字列を一般的に表すと次のようになります。
input := 数字 +か- 数字 +か- ... 数字
この規則をもとにループでinputを処理して、計算を実行してください。
もしinputに数字や+-以外の文字が含まれていた場合、eval関数は0を返すものとします。
以上をもとに挑戦してみてください!
解答例
解答例
#include <iostream>
#include <string>
int eval(std::string input) {
int result = 0;
int flag = 1;
int i = 0;
while (i < input.size()) {
int num = 0;
if (input[i] == '+') {
flag = 1;
i++;
} else if (input[i] == '-') {
flag = -1;
i++;
} else if (isdigit(input[i])) {
while (isdigit(input[i])) {
num = num * 10 + (input[i] - '0');
i++;
}
result += num * flag;
} else {
return 0; // 不正な文字が入っている時は即座に0を返すものとした
}
}
return result;
}
int main() {
std::string input;
std::cout << "式を入力: ";
std::cin >> input;
std::cout << "実行結果: " << eval(input) << std::endl;
return 0;
}