13回目の授業


11回目の課題について



第12章

構造体なんて,何の役に立つのか? → 構造体を使って複素数を表そう



 C言語では,大きく分けて三つの種類の変数を取り扱うことができます。それは,

 文字型(char),
 整数型(short, int, long),
 浮動小数点型(float, double)

です。これらの変数で表される数は,当然どれも実数となりますね。
(文字型も,コンピュータの内部では,JISコードによって数値で表されます。)

 ところで,科学技術分野の計算では,複素数を扱うことがよくあります。

複素数とは,実数部分と虚数部分の二つの成分からなる数ですから,上記のようなC言語で取り扱うことのできる変数だけでは力不足となります。

 では,C言語で複素数を使った計算をするにはどうしたらよいでしょうか。

 皆さんは,複素数平面というものを習ったことがあると思います。そこでは実数軸と虚数軸で張られた2次元平面上の1点で,ある一つの複素数を表す,ということをしていました。

すなわち,複素数とは一種のベクトルであるとも言えます。2次元平面上の1点の座標は,ベクトルの成分で表すことができるからです。

 以上のことを考えると,C言語において複素数を取り扱うための一つの方法としては,配列を使うという手がひとつ考えられます。具体的には,
    double a[2], b[2] ;
    .
    .
    .
のように,要素数が2であるdouble型の配列を用意して,それらの配列(上の例では a, b )の,0番目の要素( a[0], b[0] )を実数部分,1番目の要素( a[1], b[1] )を虚数部分と(自分の心の中で)約束して,複素数の計算をするコードを書いていく,という方法です。

 例えば,二つの複素数の掛け算をする場合には,以下のような関数を作ると良いでしょう。
(自分で勝手に配列を複素数として使おうと決めたのですから,その複素数どうしの演算をする方法も自分で作らなくてはなりません。)
    /* 複素数a, bを受け取って,a*bの値をansに納める */
    mul(double ans[], double a[], doulbe b[])
    {
        ans[0] = a[0]*b[0] - a[1]*b[1] ; /* a*bの実数部分 */
        ans[1] = a[0]*b[1] + a[1]*b[0] ; /* a*bの虚数部分 */
    }
 しかしながら,そのようにプログラミングをして,その場をしのげたとしても,他の人がそのコードを見た場合や,何ヵ月かたってから,自分の書いたそのコードを見たとき,a[]b[] の配列が(複素数ではなくて)単なる配列を表していると勘違いしたり,あるいは上の関数 mul() についても,(複素数どうしの掛け算ではなくて)単に配列どうし(ベクトルどうし)の掛け算(外積)を行う関数であると勘違いをしてしまうかもしれません。

もっと悪いことには,自分の書いたこれらのコードを見て,「なんだこれ! 間違ってるじゃんか!」と勘違いしてしまい,せっかく自分で昔に書いたコードを自分で書き換えてしまう,といったことが考えられます。

そのようなことをしていると,プログラム開発が非効率になるばかりでなく,エンジニアとしての自分の財産がいっこうに増えていきません。自分のせっかく書いたプログラムは,きちんと役割を整理して,有効に再利用していきたいものです。

 また,複素数の配列(ベクトル)や,2次元配列(行列)の計算をするときのことを考えると,上のやり方では複素数自体が既に配列となっているので,複素数のベクトルは2次元配列(配列の配列)に,複素数の行列は3次元配列(配列の配列の配列!!!)として宣言しなければなりません。

これではプログラムが非常に複雑となって読みにくくなり,バグの原因も増加することになります。運良くバグのないプログラムができたとしても,そのプログラムの実行効率は悪いものになってしまいます。
(一般的にどのようなプログラミング言語でも,配列の処理は,実行効率が非常に悪いことが知られています。)

 ソースプログラムを見直したときに,複素数は,複素数であるということが一見して分かるように,また,複素数の行列を扱うときに配列の配列の配列なんぞを使わなくてもよいように,いまのうちから準備しておきましょう。

実際,複素数の行列の計算は,音響分野に限らず広くエンジニアリングの分野において必ずと言ってよいほど出てきます。

そのための非常に有効な方法が,構造体を使った複素数の表現方法なのです。

だから,構造体を理解しなければならないのです。構造体を知っているエンジニアと知らないエンジニアでは,仕事の質とスピードに差が出てくることでしょう。


構造体による新しい型の定義

 複素数は,実数部分と虚数部分をもつ数ですから,実数部分と虚数部分のそれぞれについては,double型の変数として表すことができるでしょう。

そして,その実数部分と虚数部分をひとかたまりとして扱えるようにするためには,以下のようにして,新たな変数の型としてcomplex型を定義することが有効です。
    struct complex {
        double real; /* real part */
        double imag; /* imaginary part */
    } ;
新たな変数の型の名前(上の例では complex )は,自由に自分でつけることができます。この新たな変数の型の名前のことを,

 “構造体のタグ名”

といいます。また,構造体の中身である変数,real, imag のことを,

 “構造体のメンバ”

といいます。

 このように新たな変数の型として complex 型を定義すれば,その complex 型の変数を使いたいときには,以下のようにして型宣言文を書けばOKです。
    struct complex tx ; /* complex型の変数としてtxを型宣言 */
この complex 型は,新しい変数の型ですから,以下のようにして配列としても使用することができます。
    struct complex tx[10] ; /* complex型の配列(要素数10)としてtxを型宣言 */
    struct complex ux[10][20] ; /* complex型の2次元配列(10行20列)としてtxを型宣言 */
あるいは,complex 型を指すポインタ型の変数も使えるようになります。
    struct complex *p_tx ; /* complex型を指すポインタ型の変数(complex*型の変数)としてp_txを型宣言 */
便利でしょう!


構造体のメンバの取り出し方

 上のようにして新たに定義した complex 型の,実数部分を取り出したいときには,以下のようにすればOKです。
    tx.real ; /* complex型の変数txの実数部分(real)の参照 */
同様に,虚数部分を取り出したいときには,
    tx.imag ; /* complex型の変数txの虚数部分(imag)の参照 */
とします。


変数の型に別名をつける



 C言語では,変数の型に,別名(つまり,あだな)をつけてしまうことができます。

例えば,short型という変数の型がありますが,もし何らかの理由で,どうしてもshortという名前がいやでいやで仕方ない場合には,以下のようにしてshortにあだなをつけることができます。
    typedef short seisu ; /* shortにあだなとしてseisuをつけた */
これ以降,
    short sx ;
と型宣言する代わりに,
    seisu sx ;
と型宣言することができます。すなわち,short という言葉を使わずにコードを書くことができるというわけです。

 上のように,short という名前にわざわざ seisu とあだなをつける人は滅多にいないと思いますが,この“typedef宣言”は,実は,構造体を使用するときに便利に使うことができるのです。

前述したように,構造体の仕組みを使って新たに定義した complex 型の変数を使いたいときには,
    struct complex tx ;
のようにして型宣言文を書かなければなりませんね。

だけど,新たに complex 型という名前の変数の型を定義したわけですから,この complex 型についても通常の変数の型宣言文のように,
    complex tx ;
と型宣言できるようになったら,非常にすっきりしますね(struct といちいち書くの,めんどくさい,ですよね)。

そのようにするためには,さきほどの complex の定義文である,
    struct complex {
        double real; /* real part */
        double imag; /* imaginary part */
    } ;
の全体に対して,
complex とあだなをつければよさそうです。

これを“typedef宣言”を用いて行うには,以下のようにします。
    typedef struct{
        double real; /* real part */
        double imag; /* imaginary part */
    } complex;
これ以降,complex 型の変数を使いたいときには,
    struct complex tx ;
と型宣言する代わりに,
    complex tx ;
と型宣言することができる,というわけです。

便利でしょう!!!


構造体で複素数を扱うコードの例



 それではこれまで述べたことのまとめとして,以下に,構造体で複素数を扱うコードの例を示しておきます。自分なりに分かりやすく改造して,(コメントをいれておくなどして)ぜひ自分の財産として,とっておいて下さい。

/*************************************
	complex number calculation
*************************************/

#include <stdio.h>


/* complex(複素数)型の定義 */
typedef struct{ double real; /* real part */ double imag; /* imaginary part */ } complex; /* これ以降,complex 変数名; と型宣言すれば,その変数名のcomplex型を使用できる */
/* complex sum (a+b) */
complex sum(complex a, complex b) { complex ans; ans.real = a.real + b.real; ans.imag = a.imag + b.imag; return(ans); }
/* complex subtraction (a-b) */
complex sub(complex a, complex b) { complex ans; ans.real = a.real - b.real; ans.imag = a.imag - b.imag; return(ans); }
/* complex multiplication (a*b) */
complex mul(complex a, complex b) { complex ans; ans.real = a.real*b.real - a.imag*b.imag; ans.imag = a.real*b.imag + a.imag*b.real; return(ans); }
/* complex division (a/b) */
complex div(complex a, complex b) { double tmp; complex ans; tmp = b.real*b.real + b.imag*b.imag; ans.real = (a.real*b.real + a.imag*b.imag)/tmp; ans.imag = (a.imag*b.real - a.real*b.imag)/tmp; return(ans); }
/* show complex number */
void show(const char * text, complex a) { if(a.imag < 0) printf("%s %f - %fi\n", text, a.real, -1.*a.imag); else printf("%s %f + %fi\n", text, a.real, a.imag); } int main(void) { complex a = {1, 1}; /* 構造体による変数の,型宣言文,および同時に初期化 */ complex b; complex ans ; b.real = 1.; /* 構造体への代入 */ b.imag = -1.; show("a = ", a); show("b = ", b); ans = sum(a, b); show("a + b = ", ans); ans = sub(a, b); show("a - b = ", ans); ans = mul(a, b); show("a * b = ", ans); ans = div(a, b); show("a / b = ", ans); return(0); }


今日の実習問題と宿題



(重要)  宿題の締切は次の授業が始まる前までとします。きちんと動作をチェックしてから,提出して下さい。


戻る