6.変数の取り扱い

Copyright(C) 8Feb2003 coskx

このページでは変数の取り扱いについて解説している。
ここで修得してほしい内容は以下の通りである。
1.代入による変数の型変換に付いて理解する。
2.混合演算における,型についての注意点,自動型変換のルール,明示的な型変換について理解する。
3.メンバ変数とローカル変数の振る舞いの違いを理解する。

このページでの内容は以下を含んでいる。
6.1 代入による変数の型変換
6.2 混合演算と明示的な変数の型変換
6.3 メンバ変数とローカル変数

6.1 代入による変数の型変換

変数の型を変換する1つの方法は代入である。次のプログラムではint型からdouble型に変換している。int型からdouble型に代入して型変換を行なう時,値のとりうる範囲はint型よりdouble型の方が広いので問題なく行なえる。 変数について参照

List 6.1.1 代入による型変換

class XXXX {
    public static void main(String[] args) {
        XXXX mainprg = new XXXX();
    }
 
    XXXX() {
        int a=3;
        double p;
        p=a;
        System.out.printf("a=%d p=%.2f\n",a,p);
    }
}

/******* 実行結果 ********
a=3 p=3.00
**************************/

次の例はdouble型からint型への代入による型変換を行なっているも のである。値のとりうる範囲の広さが逆の関係になるので,うまく変換できないことがあり,この点についてコンパイラがエラーを発生するので型変換(キャス ト)を行う。(同様なことがbyte型からint型への変換とその逆向き変換の場合にも生ずる。)またint型に変換すると小数点以下が失われるが,実行 結果よりわかるように,これは四捨五入ではなく切り捨てになる。
型キャストは変数の前にかっこを付け,カッコの中に型名を書く。

List 6.1.2 代入による型変換

class XXXX {
    public static void main(String[] args) {
        XXXX mainprg = new XXXX();
    }
 
    XXXX() {
        double p;
        int a;
        p=3.14159;
        a=(int)p;
        System.out.printf("p=%.3f a=%d\n",p,a);
        p=-p;
        a=(int)p;
        System.out.printf("p=%.3f a=%d\n",p,a);
        p=2.718;
        a=(int)p;
        System.out.printf("p=%.3f a=%d\n",p,a);
        p=-p;
        a=(int)p;
        System.out.printf("p=%.3f a=%d\n",p,a);
    }
}

/******* 実行結果 ********
p=3.142 a=3
p=-3.142 a=-3
p=2.718 a=2
p=-2.718 a=-2
**************************/

 

6.2 混合演算と明示的な変数の型変換

型の異なる変数同士の演算においては次の規則による。

混合演算

C言語での解釈

int型とdouble型の混合演算

int型をdouble型に変換してから演算

int a=3,c;
double p=1.5,q;
の時
q=a+p;  →qは4.5になる。
c=a+p; →演算では4.5になるが,
     代入時に小数点以下が失われてcは4になる。
int型とchar型の混合演算 char型をint型に変換してから演算 int a=3,c;
char b=5;
の時
c=a+b; →cは8になる。

一般に異なる型の変数間の混合演算では,劣勢な方の型は優勢な方の型に変換されてから,演算が行なわれると考えてよい。(実際は少し違うが,結果としてこのように考えてよい)型の優勢,劣勢は次のようになっている。

劣勢 byte型(1byte) < short型(2byte) < int型(4byte) < float型(4byte) < double型(8byte) 優勢
(劣勢 int < long 優勢)

数式内で型変換を確実にプログラマが行ないたい場合は,型キャストを行なう。型キャストは変数の前にかっこを付け,カッコの中に型名を書く。

例 int a=7,b=3; double p; の時

Java言語での記述

Java言語での解釈

p = a / b; 「a/b」が整数演算の割り算なので小数点以下が失われて,「a/b」は整数の2になる。
これをdouble型変数pに代入してもpには2.000が代入されるだけである。
p = (double)a / (double)b; (double)aのところはdouble型7.00と解釈され,
(double)bのところはdouble型3.00と解釈される。
「(double)a / (double)b」は2.333333になる。
これをdouble型変数pに代入するのでpには2.33333が代入される。
p = a / (double)b; (double)bのところはdouble型3.00と解釈される。
そのためint型とdouble型の混合演算と解釈され,aもdouble型に変換され,
「p = a / (double)b;」は2.333333になる。
これをdouble型変数pに代入するのでpには2.33333が代入される。
p = (double)a / b; 上の例と同様にpには2.33333が代入される。
p = ( a + b + 6 ) / (double)b (a+b+6)の部分が先に演算される。(a+b+6)は整数演算が行なわれ,16になる。次の段階で16/3.0が行なわれる直前に型変換が行なわれ,16.0/3.0が演算されることになる。
pには5.333333が代入される。

定数の場合は小数点の有無で整数型と実数型に区別される。
例えば「5」は整数(int型)とみなされ,「5.0」(または「5.」)は実数型(double型)とみなされる。

例 double x; の時

Java言語
での記述

Java言語での解釈

x=3/4; 整数同士の整数演算なので「3/4」は0になり,double型変数xには0.00が代入される。
x=3.0/4.0;
x=3./4.;
実数同士の実数演算なので「3.0/4.0」は0.75になり,double型変数xには0.75が代入される。
x=3/4.0;
x=3/4.;
x=3.0/4;
x=3./4;
実数同士の実数演算に自動的に変換されるので「3.0/4.0」となり,double型変数xには0.75が代入される。
x=(1+2)/4.;
x=(1+2)/4.0;
先に1+2が行なわれ,3が得られる。次に3/4.0が演算される直前に実数同士の実数演算に自動的に変換されるので「3.0/4.0」となり,double型変数xには0.75が代入される。

x=(1+2.0)/4.0;
x=(1.0+2)/4.0;

先に()中が実数同士の足し算に変換され,和は3.0になる。次に「3.0/4.0」が計算され,double型変数xには0.75が代入される。

練習6.1

次のプログラムの実行結果を予想しなさい。
List 6.2.1

class XXXX {
    public static void main(String[] args) {
        XXXX mainprg = new XXXX();
    }
 
    XXXX() {
        int i;
        double x=-1.5;
        int k;
        for (i=0;i<4;i++) {
            k=(int)(x+i);
            System.out.printf("i=%d k=%d\n",i,k);
        }
    }
}

練習6.2

次のプログラム(三角形の面積を求めようとしている)の実行結果を予想しなさい。
List 6.2.2

class XXXX {
    public static void main(String[] args) {
        XXXX mainprg = new XXXX();
    }
 
    XXXX() {
        double base=20.0; /*底辺[cm]*/
        double height=10.0; /*高さ[cm]*/
        double area; /*面積[cm2]*/
        System.out.printf("base=%.1f height=%.1f\n",base,height);
        area=1/2*base*height;
        System.out.printf("(1) area=%.1f\n",area);
        area=(1/2)*base*height;
        System.out.printf("(2) area=%.1f\n",area);
        area=(1./2.)*base*height;
        System.out.printf("(3) area=%.1f\n",area);
        area=(1./2)*base*height;
        System.out.printf("(4) area=%.1f\n",area);
        area=base*height*1/2;
        System.out.printf("(5) area=%.1f\n",area);
        area=base*height*1.0/2.0;
        System.out.printf("(6) area=%.1f\n",area);
        area=base*height*(1/2);
        System.out.printf("(7) area=%.1f\n",area);
    }
}

6.3 メンバ変数とローカル変数

これまでの変数はXXXX()(実はXXXX()もメソッドの1つである)やメソッドの 中で用いられていた。XXXX()内部の変数は他の関数内部からは見ることが出来ないし,(引数で渡された場合のみ値を知ることが出来る)他の関数内部の 変数はXXXX()内部から見ることが出来なかった。引数で渡さない変数で,XXXX()やすべての関数から見える変数を作ることが出来る。これがメンバ変数である。(まだ説明していないが正確にはクラスXXXXのメンバ変数である。)

次 のプログラムはメンバ変数テスト用である。メンバ変数はint型の watchme と seeme の2つである。このプログラムの動作を追跡すると,メンバ変数はXXXX(),functionA(),functionB()のすべてから見えていて, その値の変更も各関数から出来ていることがわかる。メンバ変数は,そのメンバ変数を使用する関数より前で,関数の外部に定義する。

なお,これまで使用してきた関数内の変数はローカル変数と呼ばれる。

ローカル変数が宣言される時に値(初期値)が代入されているように記述されている時は,その関数が実行されるたびに初期値の代入(これは「変数の初期化」と呼ばれる)が行なわれるが,メンバ変数では宣言される時に値(初期値)が代入されているように記述されている時は,プログラム起動時に(あるいはクラスのインスタンス(クラスのところで説明する)が生成されたときに)一度だけ初期値の代入(変数の初期化)が行なわれる。

List 6.3.1 メンバ変数テスト

class XXXX {
    private int watchme=0; /*ここに宣言されるのがメンバ変数*/
    private int seeme;     /*ここに宣言されるのがメンバ変数*/

    public static void main(String[] args) {
        XXXX mainprg = new XXXX();
    }
 
    XXXX() {
        seeme=0;
        System.out.printf("main      watchme=%d seeme=%d\n",watchme,seeme);
        watchme++; seeme++;
        functionA();
        functionB();
        functionA();
        System.out.printf("main      watchme=%d seeme=%d\n",watchme,seeme);
    }

    private void functionA() { /*引数無し,返す値無しの関数functionA*/
        System.out.printf("functionA watchme=%d seeme=%d\n",watchme,seeme);
        watchme++; seeme++;
    }

    private void functionB() { /*引数無し,返す値無しの関数functionB*/
        System.out.printf("functionB watchme=%d seeme=%d\n",watchme,seeme);
        watchme++; seeme++;
        functionA();
    }
}
/******* 実行結果 ********
main      watchme=0 seeme=0
functionA watchme=1 seeme=1
functionB watchme=2 seeme=2
functionA watchme=3 seeme=3
functionA watchme=4 seeme=4
main      watchme=5 seeme=5
**************************/

もしメンバ変数と同じ名前のローカル変数が関数内部で宣言されていると,その関数内からはメンバ変数が見えなくなる。
これを変数の「スコープ規制」という。次のプログラム例では,メンバ変数 watchme と seeme が使われているが,functonAでローカル変数watchmeがあるので,functionA中からはメンバ変数のwatchmeが見えない。プログラムを追跡すること。

List 6.3.2 メンバ変数と同じ名前のローカル変数がある場合

class XXXX {
    private int watchme=0; /*ここに宣言されるのがメンバ変数*/
    private int seeme;     /*ここに宣言されるのがメンバ変数*/

    public static void main(String[] args) {
        XXXX mainprg = new XXXX();
    }
 
    XXXX() {
        seeme=0;
        System.out.printf("main      watchme=%d seeme=%d\n",watchme,seeme);
        watchme++; seeme++;
        functionA();
        functionB();
        functionA();
        System.out.printf("main      watchme=%d seeme=%d\n",watchme,seeme);
    }

    private void functionA() { /*引数無し,返す値無しの関数functionA*/
        int watchme=1000;  /*ローカル変数のwatchme*/
        System.out.printf("functionA watchme=%d seeme=%d\n",watchme,seeme);
        watchme++; seeme++;
    }

    private void functionB(){  /*引数無し,返す値無しの関数functionB*/
        System.out.printf("functionB watchme=%d seeme=%d\n",watchme,seeme);
        watchme++; seeme++;
        functionA();
    }
}

/******* 実行結果 ********
main      watchme=0 seeme=0
functionA watchme=1000 seeme=1
functionB watchme=1 seeme=2
functionA watchme=1000 seeme=3
functionA watchme=1000 seeme=4
main      watchme=2 seeme=5
**************************/

時間の経過

 →   →    →    →    →    →

処理

プログラム
起動時

XXXX

functionA

functionB

functionA

functionA

XXXX

メンバ変数
watchme

0

0を表示
0→1

1だが
見えない

1を表示
1→2

2だが
見えない

2だが
見えない

2を表示

ローカル変数
watchme

存在しない

存在しない

1000を表示
1000→1001

存在しない

1000を表示
1000→1001

1000を表示
1000→1001

存在しない

メンバ変数
seeme

値は不定

0を表示
0→1

1を表示
1→2

2を表示
2→3

3を表示
3→4

4を表示
4→5

5を表示