6/23(火)〜6/25(木) の学習ログです(主にC言語)
こんにちは、安藤です。
いつもありがとうございます。
私の学習ログです。
2020.6.23(火)
●複数のソースファイル*最小限の分割
・複数のファイルを使う理由
これまでプログラムを記述する時には、エディタのウィンドウに書き込んで来たが、その時、常に1つの画面の中に全てのプログラムを記述してきた。
つまり、1つのファイルの中に全てのプログラムを記述してきたことを意味する。
この方法は単純なので規模の小さなプログラムでは有効な方法。
しかし、規模が大きなプログラムでは、1つのファイルに全て書き込んでいると、どこにどのプログラムがあるのか分かりにくくなってしまう。
さらに、何人かで1つのプログラムを作ろうとする場合には、1つのファイルに2人以上の人が同時に書き込むことは基本的に不可能なので、2人以上でのプログラミングは事実上不可能になってしまう。
この問題を解決するには、複数のファイルに分割してプログラムを書く必要がある。
複数のファイルに分割することで、どこにどのプログラムがあるのか分かりやすくなり、複数人での開発も可能になる。
・ソースとヘッダーファイル
printf関数などを使う時は、#include <stdio.h>を先頭に記述する必要があった。
これこそがプログラムを複数のファイルに分割するということ。
#include擬似命令は、指定されたファイルの内容を取り込むという命令。
そして、stdio.hは、printf関数などの様々な関数の宣言を含んでいる。
ここで重要なことは、stdio.hの中には、printf関数の宣言だけが書かれているだけであり、実際のプログラムは書かれていないということ。
printf関数の実際のプログラムは、stdio.hとは別のファイルに記述されている。
stdio.hのような宣言だけが書かれたファイルをヘッダーファイルという。
ヘッダーファイルには、拡張子として、.hをつけることが慣習となっている。
これに対して、実際にプログラムを記述するファイルをソースファイルと呼ぶ。
拡張子は、.cとすることが慣習となっている。
ソースファイルとヘッダーファイルは、通常1対1で対応するように作成する。
・最小限のヘッダーファイル
まずは、sum関数を含むソースファイル、sum.cを作成してみる。
/*sum.c*/
int sum(int min, int max) {
int num; num = (min + max)* (max - min + 1)/2;
return num;
}
次にsum.c内に含まれる関数を他のソースファイルから使えるようにするために、sum.cから宣言部分を抜き出して、ヘッダーファイルsum.hを作成する。
/*sum.h*/
int sum(int min, int max);
これで、sum関数の分割は完了。
しかし、これではmain関数がないのでプログラムを実行できない。
そこで、main関数を含むソースファイル、main.cを作成する。
今回、sum関数のプロトタイプ宣言は、sum.hに記述しているので、sum.hを取り込まない限り、sum関数を使うことはできない。
sum.hを取り組むのは、当然、#include擬似命令を使用する。
ただし、今まではヘッダーファイル名を< > で囲んでいたが、自分で作成したヘッダーファイルを取り込むには、” ” で囲むことになっている。
#include擬似命令でヘッダーファイルを取り込むことを、インクルードという。
/* main.c */
#include <stdio.h>
#include “sum.h”
int main(void) {
int value;
value = sum(50,100);
printf(“%d\n”,value);
return 0;
}
これで、main.cも完成。
なお、main.cのヘッダーファイル、main.hは作成する必要なない。
main.c内に含まれる関数を他のソースファイルから使う必要はないから。
このままではコンパイルできない。
2つ以上のファイルを使う場合には、使うファイルを指定する必要がある。
<Borland C++ Compiler と Visual C++ Toolkit 2003 の設定>
メニュー -> 実行 -> コンパイル時パラメータを選択し、コンパイル時パラメータの欄に、-emain main.c sum.c と打ち込む。
-emainとは、実行ファイルを main.exe という名前で作成させる命令。
後ろの2つは、共にコンパイルするソースファイル名を指定している。
ヘッダーファイルはソースファイルでインクルードしているので不要。
<LSI C-86の設定>
メニュー -> 実行 -> コンパイル時パラメータを選択し、コンパイル時パラメータの欄に、-o main.exe main.c sum.c と打ち込む。
-omain.exeとは、実行ファイルをmain.exeという名前で作成する命令。
後ろの2つは、共にコンパイルするソースファイル名を指定している。
ヘッダーファイルは、ソースファイルでインクルードしているので不要。
*分割の定石
・変数の共有宣言には2種類ある。
変数や関数の宣言を行うと、コンパイラがその名前と形を記憶する。
これが宣言と呼ばれる機能。
そして、同時にコンパイラは実際に変数や関数を作成する。
これが定義と呼ばれる機能。これまでの変数では、この宣言と定義を常に同時に行なっていた。
宣言は、変数や関数の形をコンパイラに教えるだけなので、その形さえ同じであれば、何回宣言しても問題はない。
しかし定義は、関数や変数の実態を作成することになる。
同じ関数や変数が何回も作られると区別がつかなくなるため、エラーとなる。
※プロトタイプ宣言は、宣言だけを行い、定義は行わない。
・extern(エクスターン)宣言
宣言だけを行い、定義は行わない宣言方法。
extern宣言の使い方は、宣言の前にexternと記述するだけ。
このextern宣言を使うと、異なるソースファイルで変数を共有することができる。
変数の共有は便利だが、乱用に注意。
本来、複数のファイルに分割するのは、機能毎に独立させるため。
しかし、変数の共有をすると、同じ変数が使えるようになることで機能毎に独立させる意味合いが薄まってしまう。
従って、可能な限り、関数の引数や戻り値を利用して変数の共有は、どうしてもという場合のみとすること。
・ヘッダーファイルの重複防ぎ
ヘッダーファイルの重複インクルードそれ自体を防ぐ方法もある。
それには、#ifndef〜#endif擬似命令を使用する。
#ifndef~#endif擬似命令は、ある記号が定義されていなかった場合だけ、その間に挟まれたプログラムをコンパイルするという記号。
C言語は可能な限り無駄を減らせるように設計されており、プログラマーが意識しなければならないことが多いかわりに、意識さえすれば無駄を大きく減らせるようになっている。
◎今日のビジネス関連の読書
・ズボラPDCA 著:北原孝彦
・他人とうまくやっていく 著:アラン・ピーズ、バーバラ・ピーズ
・日本でいちばん大切にしたい会社5 著:坂本光司
2020.6.24(水)
◎C言語(web記事「苦しんで覚えるC言語」)
第2部:C言語応用編
●汎用計算
*様々な計算
・絶対値
絶対値を計算するにはabs関数を使用する。
#include <stdlib.h>の記述が必要。
絶対値=abs(数値);
・累乗
累乗を計算するには、pow関数を使う。
#include <math.h>の記述が必要。
累乗=pow(数値,指数);
・平方根(√)
平方根を計算するには、sqrt関数を使う。
#include <math.h>の記述が必要。
平方根=sqrt(数値);
・三角関数
#include <math.h>の記述が必要。
sin(関数名) サイン(三角関数値)
cos コサイン
tan タンジェント
asin アークサイン
acos アークコサイン
atan アークタンジェント
・アーク系三角関数
アーク系三角関数は、普通の三角関数の逆の計算をする。
普通の三角関数は、角度から辺の長さの割合を求めるが、
アーク系三角関数は、辺の長さから角度を求まる。
これらの関数の使い方はいずれも同じ。
なので、tan関数を例に進める。
ただし、この角度は普段使用する90度が直角となる角度ではなく、ラジアンと呼ばれる角度の単位を使用する。
次の式で、普通の角度からラジアンへ変換できる。
ラジアン=(度*3.14159/180)
ラジアン…
円弧と半径の長さが等しくなる位置を1ラジアンとする角度の単位。
コンピュータの世界では、ほとんどの場合ラジアンを使用する。
毎回この計算を行うのは面倒なので、次のようなマクロを作成する。
#define RADIAN(ARC) *1
この通りにすれば最小値〜最大値の範囲の乱数を計算できる。
毎回異なる乱数にする
擬似乱数は計算によるものなので、同じ数を元に作った場合は同じ乱数になってしまう。
この問題を解決するには、乱数の計算に使う元の数を変える必要がある。
そのための関数として、srand関数が用意されている。
srand(元の数);
ただし、srand関数を使って別の数値を入れたとしても、実行される時に元の数が同じであれば同じ乱数になるので解決にならない。
その解決方法は、現在時刻を入れる方法。
秒単位の現在時刻をsrand関数に入れれば毎回異なる元の数を乱数に使える。
現在時刻を得る関数は、time関数で<time.h>を#includeする必要がある。
srand関数とtime関数を使えば毎回異なる乱数を計算できる。
srand*2;
なお、unsigned int という型にキャストしているが、これは符号なしの整数値。
この処理はプログラムを開始する時に1回行えば十分。
◎今日のビジネス関連の読書
・ブロックチェーン 著:岡嶋裕史
・信用の新世紀 ブロックチェーン後の未来 著:斉藤賢爾
2020.6.25(木)
◎C言語(web記事「苦しんで覚えるC言語」)
第2部:C言語応用編
●キーボード入力
*1行の文字列として入力
・gets関数によるキーボード入力
C言語には、キーボードから1行の文字列を入力するgets関数が用意されている。
使うには、<stdio.h> を#includeすること。
gets(文字列);
gets関数を実行すると、scanf関数と同じように入力待ち状態になる。
ユーザーがキーボードから入力した文字列は指定した文字列内に格納される。
画面に1行の文字列を表示するputs関数というものもある。
使うには、<stdio.h> を#includeすること。
puts(文字配列);
指定した文字列が画面に表示される。
また、文字列の最後で必ず改行される。
printf関数より低機能だが、文字列の表示だけならputs関数の方が簡単。
・バッファオーバーラン対策
gets関数は要素数を越えた入力があるとバグが生じるので使用しないこと。
その代わりに、fgets関数を使用する。
使うには、<stdio.h> を#includeすること。
fgets(文字配列,配列の要素数,ファイルポインタ);
最後でファイルポインタを指定していることからも分かるが、この関数はファイルから文字列を読み込むための関数。
C言語では、全ての周辺機器はファイルとして扱うことができる。
キーボードには、stdinという名前のファイルポインタが割り当てらている。
このstdinを指定すれば、ファイルから読み込む関数がキーボード用に早変わりする。
周辺機器のファイル扱いは、UNIXというOSで取り入れられた機能で、現在のコンピュータはほとんど全て同様な仕組みを備えている。
配列の要素数を知るには、sizeof関数を使用するのが簡単で確実。
次のようにすれば、安全なgets関数の代わりが実現する。
fgets(文字配列,sizeof(文字配列),stdin);
※改行文字
gets関数は、‘\n’を格納してないけど、fgets関数は格納している。
入力の終わりを調べるには、‘\n’を検索するといい。
・文字列から数値などを取り出す
文字列を数値に読み替える一番良い方法は、atoi関数を使うこと。
実数の場合には、atof関数を使う。
文字列の中から単語を取り出すには、strtok関数を使用する。
使用するには、<string.h> を#includeすること。
◎今日のビジネス関連の読書
・アフター・ビットコイン 著:中島真志
・ブロックチェーン・AIで先行くエストニアで見つけたつまらなくない未来 著:小島健志
今回はこれで以上になります。
それでは、また次回。
*1:ARC) * 3.14159 / 180 )
三角関数でも計算を繰り返すと誤差が出るため注意が必要。
*乱数
・擬似乱数
乱数とは、その名の通りランダムな数のこと。
サイコロのようなもの。
ランダムな数が必要になるゲームには欠かせない。
また、複雑な現象や統計的な性質の解析などを行う場合には、乱数を使うことで手軽に実験を行うことができる。
擬似乱数では、あくまでも計算によってランダムに見える数を作っている。
しかし、実際にかなりバラバラな数値を得ることができるので、ほぼランダムな数であると考えてよい。
擬似乱数には様々な計算方法があるが、C言語で用意されるのは、ほとんどが線形合同法。
簡単に言うと、
X=適当な数*X(上位ケタの部分を切り捨てて増加を防ぐ)
をひたすら繰り返すことで毎回異なる値を得る計算。
この方法は単純でそれほどランダムにならず何回か組み合わせてく使うと同じパターンになってしまう。しかし、ゲームなどの用途であれば十分ランダムな値になる。
・乱数を作る
C言語には、擬似乱数を作るrand関数が用意されている。
使用するには、<stdlib.h>を#includeすること。
変数= rand();
・乱数の範囲を限定する
C言語では、rand関数で得られる最大値はRAND_MAXという定数の値で分かる。
従って、rand関数で得られた値をRAND_MAXを等分した値で割ればいいのだが、そのために計算をするのは面倒なので、公式がある。
範囲乱数公式
最小値+(int)(rand()*最大値ー最小値+1.0/(1.0+RAND_MAX
*2:unsigned int)time(NULL