(1)構造体、およびそのメンバーにアクセスするためのドット演算子(.)とアロー演算子(->)
(2)typedef宣言:既存の型に対して同じように使用できる別名を与える。
(3)ファイル入出力:fopen, fcloseによるファイルのオープン・クローズ
(4)テキストファイル入出力(fprintf, fscanf)
(5)バイナリファイル入出力(fread, fwrite)
以下の実習課題1から4(問題は説明時に少し変更するので注意すること)のソースコードを以下のように1つのファイルにコピーして提出せよ。
==== 提出ファイル ===============
// 課題1
(課題1のソースコード)
// 課題2
(課題2のソースコード)
// 課題3
(課題3のソースコード)
// 課題4
(もとのプログラムからの変更部分のみコピー)
=============================
以下のプログラムの関数asteriskにコードを書き加えて、プログラムを実行した結果が以下のようになるようにせよ。
<実行結果>
口座番号:################
<プログラム>
#include <stdio.h>
void asterisk(char *account)
{
// ここに書き加える。
}
int main(void)
{
char account[] = "1234567890123456";
asterisk(account);
printf("口座番号:%s\n", account);
return (0);
}
以下のプログラムが角度AOB(度)を表示するように関数sproduct内部にコードを書き加えよ。角度はベクトルOAとベクトルOBの内積をそれぞれのベクトルの長さで割ってmath.hで定義されたacos( )関数を用いて角度(rad)を計算したものをdegree(度)に変換する。ただしベクトルの長さが0のときは考慮しなくても可とする。
#include <stdio.h>
struct vector {
double x; /* X成分 */
double y; /* Y成分 */
double z; /* Z成分 */
};
double sproduct( struct vector a, struct vector b)
{
/* ここにコードを書き加える */
}
int main(void)
{
struct vector a, b;
printf("点Aの座標:\n");
printf("x:"); scanf("%lf", &a.x);
printf("y:"); scanf("%lf", &a.y);
printf("z:"); scanf("%lf", &a.z);
printf("点Bの座標:\n");
printf("x:"); scanf("%lf", &b.x);
printf("y:"); scanf("%lf", &b.y);
printf("z:"); scanf("%lf", &b.z);
printf("OABの角度は%.2f度です。\n", sproduct(a, b));
return (0);
}
以下のプログラムはx=0からπまでのsin(x)の値をxの間隔π/50でファイルに出力するものである。出力されたファイルから値を読み取って、以下のようにその値を出力するプログラムを作成せよ。
<作成するプログラムの実行結果>
0.00 0.06 0.13 0.19 0.25 0.31 0.37 0.43 0.48 0.54
0.59 0.64 0.68 0.73 0.77 0.81 0.84 0.88 0.90 0.93
0.95 0.97 0.98 0.99 1.00 1.00 1.00 0.99 0.98 0.97
0.95 0.93 0.90 0.88 0.84 0.81 0.77 0.73 0.68 0.64
0.59 0.54 0.48 0.43 0.37 0.31 0.25 0.19 0.13 0.06
0.00
<ファイルへsin(x)を出力するプログラム>
// sin.txtファイルへ出力
#include <stdio.h>
#include <math.h>
#define N 50
#define PI 3.141592
int main(void)
{
FILE *fp;
int i;
double x = 0.0;
fp = fopen("sin.txt", "w");
for (i=0; i<=N; i++)
fprintf(fp, "%.2f\n", sin(i*PI/N));
fclose(fp);
printf("ファイル作成終了。\n");
return 0;
} // 出力プログラム終了
演習問題9Cの問題で与えてあるプログラムを実行したときの出力画像の色を白黒から赤に変更せよ。
(1)構造体(structure):ひとまとまりのデータを集めたデータ構造
-
構造体とは、関連性のあるデータをひとつの型としてまとめて扱う場合に便利なものです。
struct student {
char name[20]; /* 名前 */
int height; /* 身長 */
double weight; /* 体重 */
};
(3)構造体変数の作成
struct student shibata;
struct student watanabe;
(4)プログラム中で1回しか変数を宣言しないときはタグ名を省略し以下のようにできる。
struct {
char name[20];
int height;
double weight;
} shibata;
(5)構造体で用いる演算子
以下のような構造体を仮定する。
struct point {
double x;
double y;
double z;
};
struct point start;ドット演算子(.演算子)
start.x = 1.0;
start.y = 1.0;
start.z = 1.0;
printf(“(%f, %f, %f)”, start.x, start.y, start.z);
アロー演算子(->演算子)
pointer = &start;
printf(“(%f, %f, %f)”,(*pointer).x, pointer->y, pointer->z);
同じ型の構造体変数は代入可能
struct point {
double x;
double y;
double z;
};
struct point start, end;ポインターをメンバーとして含む場合はコピーされた参照先が同じとなるので、参照先を書き換えるとコピー元のデータも変化するので注意が必要。
(6)構造体の初期化
struct point end = {1.0, 2.0, 3.0};
(7)typedef宣言
typedef宣言:既存の型に対して型名として用いることのできる同義語を与える。構造体に関してはtypedefで別名を与えることが良く行われる。
typedef unsigned int UINT;
UINT num; /* unsigned int num;と等しい */
typedef struct {double x, y, z;} point;
point start;
/* struct {double x, y, z;} start;と等しい */
「1」構造体のメンバ(.演算子の例、テキストp.312 List12-2から)
/*
学生を表す構造体で表した佐中君
*/
#include <stdio.h>
#include <string.h>
#define NAME_LEN 64
struct student {
char name[NAME_LEN]; /* 名前 */
int height; /* 身長 */
double weight; /* 体重 */
long schols; /* 奨学金 */
};
int main(void)
{
struct student sanaka;
/* struct student sanaka = {"Sanaka", 175, 60.5, 70000}; */
strcpy(sanaka.name, "Sanaka"); /* 名前 */
sanaka.height = 175; /* 身長 */
sanaka.weight = 60.5; /* 体重 */
sanaka.schols = 70000; /* 奨学金 */
printf("氏 名 = %s\n", sanaka.name);
printf("身 長 = %d\n", sanaka.height);
printf("体 重 = %f\n", sanaka.weight);
printf("奨学金 = %ld\n", sanaka.schols);
return (0);
}
「2」構造体のメンバ(->演算子の例、テキストp.314 List12-4から)
/*
超能力をもったひろ子さん
*/
#include <stdio.h>
#define NAME_LEN 64
struct student {
char name[NAME_LEN]; /* 名前 */
int height; /* 身長 */
double weight; /* 体重 */
long schols; /* 奨学金 */
};
/*--- 身長を伸ばし体重を減らしてくれるひろ子さん ---*/
void hiroko(struct student *std)
{
if (std->height < 180) std->height = 180;
if (std->weight > 80) std->weight = 80;
}
int main(void)
{
struct student sanaka = {"Sanaka", 175, 60.5, 70000};
hiroko(&sanaka);
printf("氏 名 = %s\n", sanaka.name);
printf("身 長 = %d\n", sanaka.height);
printf("体 重 = %f\n", sanaka.weight);
printf("奨学金 = %ld\n", sanaka.schols);
return (0);
}
「3」C言語内で定義された構造体には、処理系の違いを吸収するという役割や、複雑な構造をユーザーから隠してブラックボックスとして扱えるようにするといった役割があります。ここでは、日付と時間を表す構造体tmを使ったプログラムを作ってみましょう。
現在の暦時刻を取得するライブラリ関数として、time.hで宣言されているtimeという関数があります。
#include <time.h>
time_t time( time_t *timer);
ここで用いられているtime_t型は、ここで用いている処理系ではtypedef unsigned long time_t;と定義されています。また、この関数はある基準時刻からの経過秒数を返しますが、この値から直接現在の日時を計算することは困難です。そのため、この値から現在の日時を求める関数として、localtimeという関数が用意されています。
暦時刻を年・月・日・時・分・秒へ変換する関数
#include <time.h>
struct tm *localtime(const time_t *timer);
この関数は、関数timeで返されたtime_t型の値を引数として、現在の日時を表すtm構造体へのポインタを返します。
struct tm {
int tm_sec; //秒
int tm_min; //分
int tm_hour; //時
int tm_mday; //日
int tm_mon; // 1を足した数字が月
int tm_year; // 1900を足した数が西暦
int tm_wday; // 0: 日曜、1: 月曜、...6: 土曜
int tm_yday; //1月1日からの通算日
int tm_isdst; //季節時間フラグ
}
関数localtimeからの返り値はポインタなので、それぞれのメンバーの値を知るにはアロー演算子( -> )を用います。
/*
今日の日付を表示する(テキストp.340, List13C-1参照)
*/
#include <time.h>
#include <stdio.h>
void put_date(void)
{
time_t current;
struct tm *local;
char wday_name[][3] = { "日", "月", "火", "水", "木", "金", "土" };
time(¤t); /* 現在の時刻を取得 */
local = localtime(¤t); /* 地方時の構造体に変換 */
printf("%4d年%02d月%02d日(%s)", local->tm_year + 1900 /* 年 */
, local->tm_mon + 1 /* 月 */
, local->tm_mday /* 日 */
, wday_name[local->tm_wday] ); /* 曜日 */
}
int main(void)
{
printf("今日は");
put_date();
printf("です。\n");
return (0);
}
「4」テキストファイルの入出力
ファイルには、テキストファイルとバイナリファイルがあります。この違いは、たとえば数字の1を保存する場合に対応するASCIIコード(16進数で表すと0x31)として保存するか、int型のビットパターン(int型が32ビットなら、00000000 00000000 00000000 00000001)として保存するかの違いです。ここではまず、テキストファイルについて学びます。C言語でファイルを取り扱うときには、stdio.hで宣言されているFILE構造体を用います。ちょっとstdio.hファイルを覗いてその構造体の内部を見てみましょう。
typedef struct
{
unsigned char *curp; /* Current active pointer */
unsigned char *buffer; /* Data transfer buffer */
int level; /* fill/empty level of buffer */
int bsize; /* Buffer size */
unsigned short istemp; /* Temporary file indicator */
unsigned short flags; /* File status flags */
wchar_t hold; /* Ungetc char if no buffer */
char fd; /* File descriptor */
unsigned char token; /* Used for validity checking */
} FILE; /* This is the FILE object */
このそれぞれのメンバーがどういう意味を持っているかは、ここでは理解する必要がありません。必要なことは、ファイルを作成したりする関数はFILE構造体へのポインタを返します。ファイルを操作する関数はこのFILE構造体へのポインタを引数として受け取ることにより、どのファイルに対して操作するのかを判断します。C言語ではファイルへのアクセスという複雑な処理にともなう情報をFILE構造体の中に閉じ込めることによってユーザーがその内部をブラックボックスとして扱うことを可能にしています。
以下、簡単なテキストファイルへの出力プログラムと入力プログラムを見てみましょう。テキストファイルは、Windowsに付属しているメモ帳などのテキストエディタで読めるファイルです。これら2つのプログラムは、前者で出力したファイルを後者で読み込むようになっています。4つの新しい関数が出てきます。
FILE *fopen( const char *(オープンするファイル名), const char *(オープンするモード) ); //オープンするモードは出力なら "w"、入力なら "r"となる。
int fclose(FILE *fp); // fopenの返り値であるfpで示されるファイルを閉じる。
残りの2つの関数、fprintf と fscanf は、最初にFILE構造体へのポインタを引数として取る以外はこれまで用いてきた関数 printf 及び scanf と同じです。
/* ファイルへ出力 */
#include <stdio.h>
int main(void)
{
FILE *fp;
int i;
double x = 0.0;
fp = fopen("test1.txt", "w");
fprintf(fp, "ファイルテスト\n");
for (i=0; i<=10; i++)
fprintf(fp, "%.2f\n", x++);
fclose(fp);
printf("ファイル作成終了。\n");
return 0;
} /* 出力プログラム終了 */
/* ファイルから入力 */
#include <stdio.h>
int main(void)
{
FILE *fp;
char string[50];
int i;
double x[11];
fp = fopen("test1.txt", "r");
fscanf(fp, "%s", string);
printf("%s\n", string);
for (i=0; i<=10; i++)
fscanf(fp, "%lf", &x[i]);
for (i=0; i<=10; i++)
printf("%.2f\n", x[i]);
fclose(fp);
return 0;
}
/* 入力プログラム終了 */
「5」バイナリファイルの入出力
テキストファイルは全てのデータが文字として保存されているファイルですが、バイナリファイルは整数値や実数値のビットパターンをそのまま保存したファイルです。バイナリファイルでは、テキストファイルと比較して同じデータをかなりコンパクトに保存することができますが、テキストファイルのようにその内容をテキストエディターで読むことは出来ません。
以下の4つの関数を用いれば、基本的な書き込み及び読み込みを行うことができます。fopenはテキストファイルの場合とモードが違うことに注意してください。
FILE *fopen( const char *(オープンするファイル名), const char *(オープンするモード) ); //オープンするモードは出力なら "wb"、入力なら "rb"となる。
int fclose(FILE *fp); // fopenの返り値であるfpで示されるファイルを閉じる。
fwrite(書き込むデータの格納先のアドレス(ポインタ)、要素1個の大きさ、要素の個数、fopenで返されたファイル構造体へのポインタ) 返り値:書き込んだ要素数
fread(読み込むデータの格納先のアドレス(ポインタ)、要素1個の大きさ、要素の個数、fopenで返されたファイル構造体へのポインタ) 返り値:読み込んだ要素数
以下に1個の整数値と要素数10のdouble型配列のバイナリファイルへの書き込み、及び読み出しプログラムを示します。バイナリファイルを読み出す場合には、事前にどのような型の情報が保存されているかを知っている必要があります。
/* バイナリファイルの入出力 */
/* ファイルへ出力 */
#include <stdio.h>
int main(void)
{
FILE *fp;
int a = 100, i;
double array[10];
for (i=0; i<10; i++){
array[i] = (double)i;
}
fp = fopen("test1.dat", "wb");
fwrite(&a, sizeof(int), 1, fp);
fwrite(array, sizeof(double), 10, fp);
fclose(fp);
printf("バイナリファイル作成終了。\n");
return 0;
} /* 出力プログラム終了 */
/* ファイルから入力 */
#include <stdio.h>
int main(void)
{
FILE *fp;
int a, i;
double array[10];
fp = fopen("test1.dat", "rb");
fread(&a, sizeof(int), 1, fp);
fread(array, sizeof(double), 10, fp);
fclose(fp);
printf("ファイル読み込み終了。\n");
printf("a = %d\n", a);
for (i=0; i<10; i++){
printf("array[%d] = %f\n", i, array[i]);
}
return 0;
} /* 入力プログラム終了 */
double型の3つのメンバx, y, zを持つvectorというタグ名を持つ構造体を定義する。このvector構造体を2つ引数として取り、その2つのベクトル積(外積)を返り値として持つ関数vproductとスカラー積(内積)を返り値として持つ関数sproductを以下のように作成する。
struct vector {
double x; /* X成分 */
double y; /* Y成分 */
double z; /* Z成分 */
};
struct vector vproduct( struct vector v1, struct vector v2) { /* */}
double sproduct( struct vector v1, struct vector v2) { /* */}
原点O、及びキーボードから読み込んだ3点A, B, Cの座標(ax, ay, az), (bx, by, bz), (cx, cy, cz)を頂点とする平行6面体の体積を計算するプログラムをこの関数を用いて作成せよ。
体積は、読み込んだ3点の座標をそれぞれ3つのvector構造体a, b, cに設定して、sproduct( vproduct(a, b), c)の絶対値として計算できる。 座標の読み込みは、例えばA点なら
printf("点Aの座標:\n");
printf("x:"); scanf("%lf", &a.x);
printf("y:"); scanf("%lf", &a.y);
printf("z:"); scanf("%lf", &a.z);
とすればよい。また、絶対値の計算は数学関数の fabs関数を用いて良い。以下の出力例に示すとおりに入力と出力が表示されるプログラムを作成すること。出力例において、イタリック体太字の数字はキーボードから入力された値を示す。===== 出力例 =====
3点A, B, Cの座標を入力してください。 点Aの座標: x:1 y:0 z:0 点Bの座標: x:0 y:1 z:0 点Cの座標: x:0 y:0 z:1 平行6面体の体積は、1.00です。 ====================
以下に示すプログラムは、符号なし文字型(unsigned char , 0~255までの値を表現できる)の512×512の配列data[i][j]をグレースケールでWindowsビットマップ形式の画像としてディスクに保存して表示するプログラムである。このプログラムの関数interference(…)はここでは縦方向に濃度が変化する画像を計算しているが、この関数本体の処理を書き換えて以下のような振動する2点から広がった波の強度分布を画像として保存して表示するプログラムを作成せよ。
2次元配列の添え字(i, j)を2次元座標(X,Y)と考え、位置A(I1, J1)とB(I2, J2)に同位相で振動する物体があり、その波が周囲に波長λで伝わっているとする。このとき、配列上のある点P(I, J)における振幅は、r1及びr2をAP及びBPの長さとして、(つまりr1 =sqrt( (I-I1)*(I-I1)+(J-J1)*(J-J1)) として)
![]()
と書ける。この波の振幅は2cos( )なので、強度分布は次の式に比例する。
![]()
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#define N 512 /* = Image width and height */
#define FILENAME "result.bmp" /* 保存するデータファイル名 */
#define PROGRAM "mspaint" /* 使用するビットマップファイルビュアー */
#define I1 200 /* Source A (X) */
#define J1 200 /* Source A (Y) */
#define I2 300 /* Source B (X) */
#define J2 300 /* Source B (Y) */
#define WL 25.0 /* Wavelength (pixel) */
typedef struct {
short bfType; /* 'BM' */
int bfSize;
short bfRes1;
short bfRes2;
int bfOffBits;
int biSize;
int biWidth;
int biHeight;
short biPlanes;
short biBitCount;
int biCompression;
int biSizeImage;
int biXPelsPerMeter;
int biYPelsPerMeter;
int biClrUsed;
int biClrImportant;
unsigned char color[256][4];
unsigned char data[N][N];
} BITMAP;
void init_BMP(BITMAP *bmp);
void save_BMP(char *filename, BITMAP *bmp);
void interference(unsigned char data[][N], int i1, int j1, int i2, int j2, float w);
int main(void)
{
static BITMAP image;
char temp[50];
init_BMP(&image);
interference(image.data, I1, J1, I2, J2, WL);
save_BMP(FILENAME, &image);
strcpy(temp, PROGRAM);
strcat(temp, " ");
strcat(temp, FILENAME);
system(temp);
return 0;
}
void interference(unsigned char data[][N], int i1, int j1, int i2, int j2, float w)
{ /* Source A(i1, j1), Source B(i2, j2), w=wavelength */
int i, j;
for (j=0; j<N; j++){
for (i=0; i<N; i++){
data[j][i] = (unsigned char)(j % 256);
}
}
}
//////////////////////////////////
void init_BMP(BITMAP *bmp)
{
int i, j;
bmp->bfType=0x4d42; /* bfType='BM' */
bmp->bfSize=14+40+sizeof(bmp->color)+sizeof(bmp->data); /* bfSize */
bmp->bfRes1=0;
bmp->bfRes2=0;
bmp->bfOffBits=14+40+sizeof(bmp->color);
bmp->biSize=40; /* biSize */
bmp->biWidth=N; /* Image Width */
bmp->biHeight=N; /* Image Height */
bmp->biPlanes=1; /* biPlanes */
bmp->biBitCount=8; /* Number of bits/pixel */
bmp->biCompression=0; /* No compression */
bmp->biSizeImage=sizeof(bmp->data); /* biSizeImage */
bmp->biXPelsPerMeter=0; /* biXPelsPerMeter */
bmp->biYPelsPerMeter=0; /* biYPelsPerMeter */
bmp->biClrUsed=256; /* biClrUsed */
bmp->biClrImportant=256; /* biClrImportant */
/* Initialize Gray Scale Color Map */
for(i=0; i<256; i++){
bmp->color[i][0]=bmp->color[i][1]=bmp->color[i][2]=(unsigned char)i;
bmp->color[i][3]=0;
}
}
void save_BMP(char *filename, BITMAP *bmp) /* Write BMP File to Disk */
{
FILE *fp;
fp=fopen(filename, "wb"); /* 'wb': Write and Binary Mode */
fwrite(&(bmp->bfType), sizeof(bmp->bfType), 1, fp);
fwrite(&(bmp->bfSize), sizeof(bmp->bfSize), 1, fp);
fwrite(&(bmp->bfRes1), sizeof(bmp->bfRes1), 1, fp);
fwrite(&(bmp->bfRes2), sizeof(bmp->bfRes2), 1, fp);
fwrite(&(bmp->bfOffBits), sizeof(bmp->bfOffBits), 1, fp);
fwrite(&(bmp->biSize), sizeof(bmp->biSize), 1, fp);
fwrite(&(bmp->biWidth), sizeof(bmp->biWidth), 1, fp);
fwrite(&(bmp->biHeight), sizeof(bmp->biHeight), 1, fp);
fwrite(&(bmp->biPlanes), sizeof(bmp->biPlanes), 1, fp);
fwrite(&(bmp->biBitCount), sizeof(bmp->biBitCount), 1, fp);
fwrite(&(bmp->biCompression), sizeof(bmp->biCompression), 1, fp);
fwrite(&(bmp->biSizeImage), sizeof(bmp->biSizeImage), 1, fp);
fwrite(&(bmp->biXPelsPerMeter), sizeof(bmp->biXPelsPerMeter), 1, fp);
fwrite(&(bmp->biYPelsPerMeter), sizeof(bmp->biYPelsPerMeter), 1, fp);
fwrite(&(bmp->biClrUsed), sizeof(bmp->biClrUsed), 1, fp);
fwrite(&(bmp->biClrImportant), sizeof(bmp->biClrImportant), 1, fp);
fwrite(&(bmp->color), sizeof(bmp->color), 1, fp);
fwrite(&(bmp->data[0][0]), sizeof(bmp->data), 1, fp);
fclose(fp);
printf("ファイル名%sで保存しました。\n", filename);
}
========
C言語はコンパイラ言語であり、プログラム全体をコンピューターが直接理解できる機械語に翻訳(コンパイル)してから実行する。それに対してインタープリター言語はプログラムを1行ずつ解釈・実行する言語で、現在ではPythonが幅広く使われるようになっている。ここでは、フィボナッチ数列を求める計算を題材に、C言語とPythonのプログラム、およびPythonからC言語で作成された関数を呼び出した場合の実行速度を比較してみよう。
フィボナッチ数列は以下の式で定義される数列である。(前の2項の和です。下の式のマイナスはプラスの間違いです。)
この第40項目を計算するC言語のプログラムを以下に示す。
#include<stdio.h>
#include<time.h>
#define N 40
int fib(int n)
{
if (n < 2) return (n);
else return (fib(n-1) + fib(n-2));
}
int main(void)
{
long cpu_time;
printf("Fibonacci数列%d項目:%d\n", N, fib(N));
cpu_time = clock();
printf("%f秒掛かりました。\n", (double)cpu_time/CLOCKS_PER_SEC);
return (0);
}
このプログラムでは、time.hで定義されたclock()関数を用いてプログラムの実行時間を計測している。clock()関数は、プログラム実行開始時刻からそのプログラムが費やしたCPUのクロック数をlong型整数値で返す。その値をCLOCKS_PER_SECで割って秒に変換している。CLOCKS_PER_SECはtime.hで宣言された1秒間のクロック数である。3D206コンピュータールームのパソコンでこのプログラムを実行すると、計算に0.468秒かかった。
次にPythonで同じ計算を実行してみる。3D206のWindows 11環境にはPythonの環境としてAnaconda3の環境がインストールされている。ここでは、その中のSpiderの環境を用いてPythonプログラムをテストする。以下のPythonコードを用いる。
# Pythonでフィボナッチ数列を計算するプログラム
import time
def fib(n):
# print(n, end=' ')
if n <= 1:
return n
return(fib(n-1) + fib(n-2))
N = 40 #計算する項
print('計算開始')
py_start = time.time()
print(f'Fibonacci数列{N}項目:{fib(N)}')
py_time = time.time() - py_start
print(f'{py_time:3f}秒かかりました。')
3D206のWindows11環境では、メニューからSpiderを起動して上記プログラムをエディター部分に入力して実行ボタンをクリック(またはF5)すれば実行できる。3D206のパソコンで実行した結果、計算時間は13.7秒であった。C言語で作成した場合と比較して、およそ40倍近い時間がかかっている。
次に、PythonからC言語で作成された関数を呼び出すプログラムを紹介する。これにはまずC言語で作成された関数のライブラリを作成する必要がある。1回目の授業でVisual Studio CodeによるC言語開発環境を作成して頂いたが、1回目の授業で説明した方法は、32ビットの実行ファイルを作る方法であった。3D206のパソコンにインストールされたPythonは64ビットなので、呼び出されるライブラリの側も64ビットにする必要がある。それには、1回目の授業で作成したVisual Studio Code起動用のバッチファイルVSCode_MSVC.batの中の1行:
call "C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\VsDevCmd.bat"
を次の1行で置き換えて、64ビット環境用のVisual Studio Code起動用バッチファイルとしてVSCode_MSVC64.batとして保存する。
call "C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Auxiliary\Build\vcvars64.bat"
Windows環境では、Microsoft C++ (MSVC) C および C++ コンパイラとリンカーを制御するツールとしてcl.exeを利用していた。コンパイラは、オブジェクト (.obj) ファイルを生成し、リンカーは、実行可能 (.exe) ファイルまたはダイナミック リンク ライブラリ (DLL: Dynamic-Link Library) を生成する。/LD オプションを指定すると、メイン出力ファイルとして DLL をビルドする。関数を別のプログラムに公開するためには、ソースコードで関数名の前に__declspec(dllexport)キーワードを付ける。ライブラリのソースファイル名をfibDLL.cとして以下のように作成する。
__declspec(dllexport) int fib(int n)
{
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}
カレントディレクトリをこのファイルの存在するホルダーに移動し、コマンドラインから/LDオプションを付けてcl.exeを以下のように実行する。
cl /LD .\fibDLL.c
このようにすると、フィボナッチ数列を計算する関数int fib(int n)がダイナミックリンクライブラリfibDLL.dllとして作成される。この関数を呼び出すPythonプログラム(ファイル名をfib_c.pyとする)は以下のように書くことができる。
import ctypes
import time
libc = ctypes.cdll.LoadLibrary("./fibDLL.dll") #同じホルダーにDLLがあるとしている
N = 40 #計算する項
print('計算開始')
py_start = time.time()
print(f'Fibonacci数列{N}項目:{libc.fib(N)}')
py_time = time.time() - py_start
print(f'{py_time:3f}秒かかりました。')
このプログラムをDLLと同じホルダーに置いてSpider環境で実行すると、3D206のパソコンでは計算時間が0.39秒であった。測定方法が異なるので正確な時間の比較は出来ないが、時間のかかる計算部分を高速で動作するC言語で作成することで、インタープリタ言語でもコンパイラ言語と同等な速度で計算出来ていることが分かる。