見出し画像

プログラミング学習記録【19日目】/マクロ、ラムダ式、その他の機能

大学の試験勉強をしていて、更新を滞らせていた。リハビリを兼ねてプログラムを作ったのだが、思ったように内容が出て来ず、記事の内容をまとめ直すなどしていたら年が明けてしまった。試験も無事終わり、内容もまとめ終わったので、久々に再開させたいと思う。

3.06.その他の機能

PCでは文字を数字として扱っている。扱いは基本ASCII文字コードを用いる。
そのため、文字コードを利用しプログラムすることも可能。

関数内で宣言される変数をローカル変数と呼ぶ。
変数は関数外で宣言することもでき、このようにして宣言される変数をグローバル変数と呼ぶ。
グローバル変数は複数の関数をまたいで使うことができる。

変数を定数とするときは、変数の宣言時にconstを用いる。

const 型 変数名 = 値;

これを用いると、値を変えたくない場合に変数ではなく定数を宣言することができる。

if文は条件演算子(三項演算子)を用いると短く書き換えられる場合がある。

条件式 ? 真のときの式 : 偽のときの式

でif文を表すことができる。短いif文はこのように書くと早く書けるがあまり一般的ではない。条件が複雑に入り組んでいる場合は条件演算子を使わないほうが読みやすいこともある。 

マクロを用いるとプログラムを変数のように扱うことができる。
何度も出てくる記述が多いプログラムはマクロ化しておくと作業が楽になる。

#define マクロ名 置き換えるプログラム

マクロは引数を受け取ることもできる。宣言は次のように行う。

#define  マクロ名(引数1, 引数2, …)   置き換えるプログラム

マクロが複数行にまたがる場合、行末に\を置くと記述できる。
マクロを用いると短くプログラムを書くことが可能になるが、ミスが多くなりやすい。
競技プログラミングのような小規模なプログラムであれば問題になることは少ないが、開発では不用意にマクロを定義することは避けることが推奨される。

ラムダ式という記法を用いると、関数の内部で関数を定義することができる。
短い関数をその場で定義したいときに利用する。

auto 関数名 = [](引数の型1 引数名1, 引数の型2 引数名2, …){ 関数の処理 }; //通常の宣言
auto 関数名 = [&](引数の型1 引数名1, 引数の型2 引数名2, …){ 関数の処理 }; //通常の宣言 // [&]としておくと、ラムダ式の外で定義された変数を利用できる。
function<返り値の型(引数の型1, 引数の型2, ...)> 関数名 = [&](引数の型1 引数名1, 引数の型2 引数名2, …){ 関数の処理 }; // 再帰呼び出しのあるラムダ式

do-while文
通常のwhile文とは異なり、条件式の判定を行うより前に1度ループ内部の処理を行う。条件がはじめから偽であったとしても処理を1度だけ行うことができる。

goto文
goto文に到達した際に、ラベルを付けた行に移動する制御構文。多重ループからの脱出に用いられることが多い。

do-while, goto の両文は必要になるケースはほとんど無く、基本的には使わないほうが良い。

EX26.電卓を作ろう3

変数と配列を扱える電卓を作成する。
入力はプログラムと似た命令の形式で与えられ、1行目は入力の行数を表す。
命令は以下の4つ。

int<変数名>=<int式>;  // 整数の変数を宣言
print_int<int式>;  // 整数の値を出力
vec<変数名> = <vec式>;  // 配列の変数を宣言
print_vec <vec式>; // 配列の値を出力

やることが多いので順番を決めて1つずつ対処していく。
まず、入力形式から式の種類や数字、記号などを読み取るのは非常に難しいのでここから対処していく。幸い、プログラムと似た形式であれば ; や = などは一つの指標になりそうなので取っ掛かりはここらへんにしようと思う。
入力の流れでmain文から構築していこうと思う。

int main()
{
 int N;
 cin >> N;
 for (int i = 0; i < N; i++)
 {
   
 }

ここまではいつもどおり。で、forでループする中身だが、入力される命令で別の動作をさせる必要がある。最初の命令部分を読み取ってif文で場合分けすれば良いが、その中身までmain文に書き込んでしまうとmain文がめっちゃ長くなってしまいそうなので、関数としてそれぞれの動きを定義しておいて、if文では関数呼び出しを行おうと思う。なんにせよ命令は4つしかないので、まずは関数を呼び出せるようにif文を作り込んでいく。

string order;
   cin >> order;
   if (order == "int")
   {
   }
   if (order == "print_int")
   {
   }
   if (order == "vec")
   {
   }
   if (order == "print_vec")
   {
   }

一つ一つ動作を考えていくのだが、どう考えても共通している項目も切り離して考えていく必要がある。まずは変数名の取得。read_var_nameとした。

int read_var_name()
{
 string var, equal;
 cin >> var >> equal;
 return var;
}

equalまで読み込ませといて、ここで排除しとけば楽になりそうだったので、返り値は変数名しか返さない方針で。
ここまで来てやっとintという命令に対しての動きを考える。
変数名を読み込むだけならread_var_nameだけで済むのだが、入力名は初期値を必ず入れているっぽいので、変数に値を入れることを考える。更に面倒なことに、変数名の宣言で計算まで行ってくれちゃっているので、対処を講じる。
さて、ここで問題になるのが、「数字と記号の識別」になる。ここを調べるのにめちゃくちゃ時間かかった。
で、STLに、isdigitという関数が用意されているのをやっと発見した。

isdigit( 文字列 ) // 引数の文字列が数字がどうかを調べる。引数が数字なら0以外の値を返り値として返し、数字でなければ0を返す。

これだけだと動作がイメージしづらかったので、簡単なプログラムを書いて確認してみた。

#include <bits/stdc++.h>
using namespace std;
int main()
{
   int x = 6;
   if (isdigit(x))
   {
       cout << "yes" << endl;
   }
   else
   {
       cout << "no" << endl;
   }
}

で、結果は以下の通り。

> ./a.exe       
no

ってことは、ifのtrueは文字列が該当するらしい。intの命令で文字列が来るのは演算子が来る場合と文末の ; が来るときなので、それぞれ別の動きをしなくてはならない。
もちろん空白のたびに一回一回別の動きをする必要があるので、ループの終了を ; にして、数字が来た場合と文字列が来た場合で別の動きをするように頑張って考えてみる。string型として読み取った文字列を数字として読み取り、int型の値に変換する、stoiという関数がSTLにあったので、読み取った数値が数字なのか変数なのかを考えてもらう役を与えたいと思う。初めて使う関数が多かったので、簡単な試作で動作確認。

#include <bits/stdc++.h>
using namespace std;
int main()
{
   string x;
   cin >> x;
   if (isdigit(x.at(0)))
   {
       cout << stoi(x) << endl;
   }
   else
   {
       cout << x << endl;
   }
}
> ./a.exe
12
12
> ./a.exe
pat
pat

ウムウム。期待した通りの働きをしておるわ。
実際組み込むときには、coutではなくreturnで、入力された数字を返したり、入力された変数を呼び出して中身を返す、といった動作を行わせる。

mapで変数を管理したら複数の変数があったとしても変数名のみでアクセスできるので、mapに変数管理は任せる。

{
 string num;
 cin >> num;
 if (isdigit(num.at(0)))
 {
   return stoi(num);
 }
 else
 {
   return var_int.at(num);
 }
}

int型であっても、内部に計算式が入ることがある。計算式が入るにせよ、変数に数字を代入するだけにせよ、その命令が終わる際には必ず ; が入るはずなので、繰り返しの終了条件を ; の入力に設定し、演算子が入力された場合はそれぞれの計算を行うようコードを組んでみる。繰返し項目の最後に記号部分を入力するようにし、次のループの際の確認に用いる。
一度そのままコードを組んで実行したところ、最初は数字もしくは変数が入力され、演算子や記号が入力されることはないため、記号に関する変数に初期値を設定しておき、初期値だった場合の処理も付け加えておく。

int calc_int(map<string, int> &var_int)
{
 string mark = "null";
 int value = 0;
 while (mark != ";")
 {
   int num = read_int(var_int);
   if (mark == "null")
   {
     value = num;
   }
   else if (mark == "+")
   {
     value += num;
   }
   else if (mark == "-")
   {
     value -= num;
   }
   cin >> mark;
 }
 return value;
}

配列は、3次元構造で考えると2次元の積み重ねでしかなく、今回に限ると行列で言えば1行 * n列の入力しかないことがわかっているので、[ ] が配列の終始を判断できる1つの材料になりそうだと考えた。これと、先程のcalc_intを順番に行うことで、配列は片付きそうなので、その線でソースコードを組み立てていく。読み取り部分では、配列の場合 [ が邪魔なのだが、配列の頭にしか出てこないため1文字飛ばす、などの処理は汎用性を欠いてしまう。ならば、その場で判断を行い、処理を行ったほうが良いと判断した。配列の読み込みは ] が出るまで繰り返し行えば良いので、mapを用いて読み込みを行う。 よって、以下の2つの関数を作り、使用することとした。

vector<int> read_vec_num(map<string, int> &var_int)
{
 vector<int> value;
 string mark = "null";
 while (mark != "]")
 {
   int num = read_int(var_int);
   value.push_back(num);
   cin >> mark;
 }
 return value;
}
vector<int> read_vec(map<string, int> &var_int, map<string, vector<int>> &var_vec)
{
 string num;
 cin >> num;
 if (num == "[")
 {
   return read_vec_num(var_int);
 }
 else
 {
   return var_vec.at(num);
 }
}

計算する部分はintのときと変わらない。vectorに書き換えて調整を行えば良い。

vector<int> calc_vec(map<string, int> &var_int, map<string, vector<int>> &var_vec)
{
 string mark;
 vector<int> value;
 while (mark != ";")
 {
   vector<int> vec = read_vec(var_int, var_vec);
   if (mark == "null")
   {
     value = vec;
   }
   if (mark == "+")
   {
     for (int i = 0; i < value.size(); i++)
     {
       value.at(i) += vec.at(i);
     }
   }
   else if (mark == "-")
   {
     for (int i = 0; i < value.size(); i++)
     {
       value.at(i) -= vec.at(i);
     }
   }
   cin >> mark;
 }
 return value;
}
void print_vec(vector<int> vec)
{
 cout << "[ ";
 for (int i = 0; i < vec.size(); i++)
 {
   cout << vec.at(i) << " ";
 }
 cout << "]" << endl;
}

ソースコードの全体像は以下の通り。

#include <bits/stdc++.h>
using namespace std;
string read_name()
{
 string name, equal;
 cin >> name >> equal;
 return name;
}
int read_int(map<string, int> &var_int)
{
 string num;
 cin >> num;
 if (isdigit(num.at(0)))
 {
   return stoi(num);
 }
 else
 {
   return var_int.at(num);
 }
}
int calc_int(map<string, int> &var_int)
{
 string mark = "null";
 int value = 0;
 while (mark != ";")
 {
   int num = read_int(var_int);
   if (mark == "null")
   {
     value = num;
   }
   else if (mark == "+")
   {
     value += num;
   }
   else if (mark == "-")
   {
     value -= num;
   }
   cin >> mark;
 }
 return value;
}
vector<int> read_vec_num(map<string, int> &var_int)
{
 vector<int> value;
 string mark = "null";
 while (mark != "]")
 {
   int num = read_int(var_int);
   value.push_back(num);
   cin >> mark;
 }
 return value;
}
vector<int> read_vec(map<string, int> &var_int, map<string, vector<int>> &var_vec)
{
 string num;
 cin >> num;
 if (num == "[")
 {
   return read_vec_num(var_int);
 }
 else
 {
   return var_vec.at(num);
 }
}
vector<int> calc_vec(map<string, int> &var_int, map<string, vector<int>> &var_vec)
{
 string mark;
 vector<int> value;
 while (mark != ";")
 {
   vector<int> vec = read_vec(var_int, var_vec);
   if (mark == "null")
   {
     value = vec;
   }
   if (mark == "+")
   {
     for (int i = 0; i < value.size(); i++)
     {
       value.at(i) += vec.at(i);
     }
   }
   else if (mark == "-")
   {
     for (int i = 0; i < value.size(); i++)
     {
       value.at(i) -= vec.at(i);
     }
   }
   cin >> mark;
 }
 return value;
}
void print_vec(vector<int> vec)
{
 cout << "[ ";
 for (int i = 0; i < vec.size(); i++)
 {
   cout << vec.at(i) << " ";
 }
 cout << "]" << endl;
}
int main()
{
 int N;
 cin >> N;
 map<string, int> var_int;
 map<string, vector<int>> var_vec;
 for (int i = 0; i < N; i++)
 {
   string order;
   cin >> order;
   if (order == "int")
   {
     string name = read_name();
     var_int[name] = calc_int(var_int);
   }
   if (order == "vec")
   {
     string name = read_name();
     var_vec[name] = calc_vec(var_int, var_vec);
   }
   if (order == "print_int")
   {
     cout << calc_int(var_int) << endl;
   }
   if (order == "print_vec")
   {
     print_vec(calc_vec(var_int, var_vec));
   }
 }
}

画像1

久々の勉強なのに、8000文字近いnoteになってしまった。
問題からプログラム全体が全く見えてこなくて、一つ一つ確認しながらプログラムを作っているうちに、時間も分量も非常に大きいものになってしまったのだ。感覚を取り戻しながら、今回編集したC++のまとめを今一度確認しながら、今後もしっかり学習をつづけていきたい。

この記事が気に入ったらサポートをしてみませんか?