情報学部 菅沼ホーム 全体目次 演習解答例 付録 索引

第6章 配列

  1. 6.1 new 演算子と配列
    1. 6.1.1 new 演算子
    2. 6.1.2 1 次元配列
      1. (プログラム例 6.1 ) 平均値と平均値以下の人の出力
      2. (プログラム例 6.2 ) 平方根の計算とファイルへの出力
      3. (プログラム例 6.3 ) 大文字から小文字への変換
    3. 6.1.3 多次元配列
      1. (プログラム例 6.4 ) 多次元配列
  2. 6.2 配列とポインタ
    1. (プログラム例 6.5 ) 1 次元配列,初期設定,及び,ポインタ
    2. (プログラム例 6.6 ) 多次元配列,初期設定,及び,ポインタ
    3. (プログラム例 6.7 ) new 演算子と代入・初期化
  3. 演習問題6

  この章では,配列について説明します.Java にはポインタという言葉(概念)はありませんが,int や double などの基本形以外のデータはすべてポインタで表現されていると考えた方が妥当です.したがって,Java を勉強する場合にも,ポインタという考え方は重要だと思います.そこで,ポインタやアドレスといった言葉についても説明していきます.

6.1 new 演算子と配列

  前節までに述べたプログラム,例えば,英語と数学の平均値を求めるプログラム(プログラム例 5.6 )においては,各科目の点数を常に変数 x と y に読み込んでいるため(つまり,同じ記憶場所に記憶していているため),変数 x と y には直前に入力されたデータだけが記憶されており,それ以前に入力されたデータはすべて失われます.しかし,プログラムによっては,すべての入力されたデータを以後の処理に利用するため記憶しておきたい場合があります.例えば,平均点を計算した後,平均点以下の人を調べるためには,各人の点数が記憶されていなければ,同じデータを再度入力し直さなければなりません.

  各人の点数を異なる変数,例えば,x1,y1,x2,y2,・・・,xn,yn に入力(記憶)することによって問題は解決できるかもしれません.しかし,この方法は,人数が少ないときは可能ですが,人数が多くなると膨大な数の変数を定義しなければならず事実上不可能になります.さらに,人数が前もってわかっていないような場合は,基本的に不可能となります.そこで,登場するのが,配列array )変数という考え方です.数学的な例えをすれば,今まで述べた変数はスカラー変数であり,配列変数はベクトルに対応します(行列等に対応する配列変数も存在しますが,それについては,後ほど述べます).配列変数を定義し,その各要素(ベクトルの各要素)に各人の点数を保存しておけば上の問題を解決できることになります.

6.1.1 new 演算子

  配列変数を使用した具体的な例について説明する前に,C++ における new 演算子アドレス演算子,及び,間接演算子について説明しておきます.new 演算子は,以下に示すように,データ型(とその数)を与えると,それらのデータを記憶するために必要な領域を確保し,確保した領域のアドレスを得るために使用されます.
int *x = new int;   // int* x = new int; でも可
int *y = new int[5];   // int* y = new int[5]; でも可,5個の要素からなる配列とほぼ同等		
  変数は,主記憶領域のある特定の場所に付けられた名前です.その場所(アドレス)を求めるために使用されるのがアドレス演算子です.アドレス演算子は「 & 」で表し,例えば,変数 value のアドレスを求めるためには,
&value		
と書きます.このアドレスをデータとして保存しておくためには,ポインタpointer)という特別な領域が必要です.この領域に付ける名前,つまり,ポインタ変数を定義するためには,例えば,int 型変数に対するポインタは,
int *point;   // int* point; でも可		
と宣言します.変数 value が int 型であったとすると,
point = &value;		
という文により,変数 value のアドレス 20 がポインタ変数 point に代入されます.図 6.0 は,この関係を表しています.

  アドレスを表現するのに必要な領域の大きさは,対象の変数の型が異なっても同じです.しかし,ポインタ変数の値を 1 だけ増やすことは,現在指しているデータの次にあるデータのアドレスを指すことに対応します.例えば,現在指しているデータが int 型であれば,次のデータは 4 バイト先に存在します( int 型が 4 バイトの場合).つまり,ポインタ変数の値を 1 だけ増やすことを,データのサイズ分だけ先のアドレスを指すような処理に対応させてやる必要があります.従って,どのような型の変数を指すポインタ変数かによって,宣言方法を変えてやる必要があります.例えば,char 型や double 型に対しては,以下のように宣言します.
char *pc;   // char *pc; でも可
double *pd;   // double *pd; でも可		
  ポインタ変数について,その変数に記憶されている値(図 6.0 では,20 )ではなく,その値が指す場所に記憶されている値(図 6.0 では,1234 )を参照したい場合があります.このようなとき使用されるのが間接演算子です.間接演算子は「 * 」で表し,例えば,ポインタ変数 point が示すアドレスに入っている内容を変数 data に代入するためには,
data = *point;		
と書きます.図 6.0 の例では,変数 data に 1234 が記憶されます.また,逆に,ポインタ変数が指すアドレスに値を代入することもできます.
*point = 100;		
と記述することにより,変数 value に 100 が代入されます.下に示すプログラムの断片では,「 x = y 」( 変数 x に,変数 y の値を代入)の操作をポインタを介して行っています.
double x, y = 10, *yp = &y;
x = *yp;		

6.1.2 1 次元配列 

  Java において,ある変数がスカラー(単純変数)でなく,配列変数であることを定義するためには,例えば,以下のように記述します.
int x[];   // int [] x; でも可,C++ では,int *x; に相当
double y[];   // double [] y; でも可,C++ では,double *y; に相当		
一般的に記述すれば,
配列の型 配列名[];		
となります.このように定義することにより,変数 x や y は配列変数とみなされます.ただし,このままでは,変数を定義しただけであり,具体的なデータを記憶する領域は確保されません.たとえば,変数 x を int 型データを 5 個記憶する配列としたい場合は,さらに,new 演算子を使用して,
x = new int [5];   // C++ では,x = new int [5]; に相当		
の記述が必要です(「 5 」の部分は変数でも可).また,型の宣言と領域の確保を,
int x[] = new int [5];   // int [] x = new int [5]; でも可,C++ では,int *x = new int [5]; に相当		
のように,同時に記述しても構いません.

  配列を初期化初期設定)したいような場合は,初期化子を使用します.たとえば,
int x[] = {1, 2, 3, 4, 5};   // int [] x = {1, 2, 3, 4, 5}; でも可		
のように記述すると,5 個の int 型データを記憶する場所が確保されるとともに,その各要素は,中括弧(初期化子)内の値で初期化されます.

  配列の各要素を参照するためには,添え字を使用し,x[i] のように記述します.ただし,添え字 i は,上のように定義した場合は,0 から 4 まで変化します.従って,
x[2] = 10;
y    = x[3];		
と書くと,配列 x の 3 番目の要素( 2 番目でないことに注意)に値 10 が代入され,4 番目の要素に入っている値が変数 y に代入されます.添え字の値が負になったり,または,4 より大きくなることは絶対に許されませんので注意してください

  このように,配列の参照や代入は,添え字を利用して各要素ごとに行えますので,たとえば,配列 x の 2 から 100 番目の要素を配列 y の先頭部分にコピー(代入)したい場合は,以下に示すように,繰り返し文を使用して簡単に実行できます(もちろん,各配列とも,必要な数のデータを保存できるだけの領域が確保されている必要があります).99 個の単純変数を他の 99 個の単純変数に代入する場合と比較してみて下さい.
for (i1 = 0; i1 < 99; i1++)   // i1 の値に注意
	y[i1] = x[i1+1];		
(プログラム例 6.1 ) 平均値と平均値以下の人の出力  ( A ~ D の部分を可能な限り一つの変数,定数,演算子等で,埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.)

  最初の例として,プログラム例 5.6 と似た問題を取り上げます.n 人いるクラスで試験を行ったとします.各人の学籍番号,英語の点,数学の点を入力し,平均値を計算した後,いずれかの科目が平均値以下の人の学籍番号と人数を出力するプログラムです.

(プログラム)
/********************************/
/* 平均点の計算と平均点以下の人 */
/*      coded by Y.Suganuma     */
/********************************/
import java.io.*;
import java.util.*;
public class Test {
	public static void main(String args[])
	{
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		double mean1, mean2, sum1 = 0.0, sum2 = 0.0;
		int n, m = 0, i1, no[], x[], y[];
		String line;
		StringTokenizer str;
		try {
					// 人数の読み込みと配列の定義
			System.out.print("人数は? ");
			n  = Integer.parseInt(in.readLine());
			no = new int [n];
			x  = new int [n];
			y  = new int [n];
					// データの読み込みと点数の和の計算
			for (i1 = 0; i1 < n; i1++) {
				System.out.print("学籍番号,英語の点,数学の点は? ");
				line    = in.readLine();
				str     = new StringTokenizer(line, " ");
				no[i1]  = Integer.parseInt(str.nextToken());
				x[i1]   = Integer.parseInt(str.nextToken());
				y[i1]   = Integer.parseInt(str.nextToken());
				sum1   += x[i1];
				sum2   += y[i1];
			}
					// 平均点の計算と平均点以下の人
			if (n <= 0)
				System.out.println("データが存在しません!");
			else {
				mean1 = sum1 / n;
				mean2 = sum2 / n;
				System.out.println("英語平均 " + mean1 + " 数学平均 " + mean2);
				System.out.println("いずれかの科目が平均点以下の人の学籍番号");
				for (i1 = 0; i1 < n; i1++) {
					if (x[i1] <= mean1 || y[i1] <= mean2) {
						System.out.println("    " + no[i1]);
						m++;
					}
				}
				System.out.println("該当学生数 " + m + " 人");
			}
		}
		catch (IOException ignored) {}
	}
}
			

12 行目

  3 つの変数 no,x,y が配列であることを宣言しています.

19~21 行目

  18 行目において人数 n を読み込んだ後,必要な領域( n 個の int 型データを記憶するのに必要な領域)を確保しています.もちろん,領域の大きさは,n より大きければ構わないわけですが,特別な理由がない限り,余分な領域を確保する必要はないと思います.

  では,前もって人数(データの数)が分からないような場合はどのようにしたらよいのでしょうか.一つの方法は,考えられる最大のデータ数を記憶できるだけの領域を確保しておく方法です.あと一つの方法は,Java に用意されている特別なクラスを使用する方法ですが,その方法に関してはプログラム例10.4 で説明します.

25~29 行目

  この例では,1 行に,学籍番号,英語の点,及び,数学の点が,半角スペースで区切って入力されることを仮定していますので,それらを分離し,各配列の (i1 + 1) 番目の要素に保存しています.

37,38 行目

  各科目の平均値を計算しています.

41~46 行目

  上で計算された平均値と,配列に記憶されている各人の点数から,英語または数学が平均点以下である人の学籍番号を出力するとともに,その人数を計算しています.

(プログラム例6.2) 平方根の計算とファイルへの出力 

  次のプログラムは,1 から 100 までの値に対する平方根を 1 おきに計算し,それらを一旦配列変数 x 及び y に入れた後(配列変数 x には 1 から 100,y にはその平方根),ファイルに出力するプログラムです.なお,このプログラムでは,出力するファイル名をプログラムで固定せず,キーボードから入力しています.

01	/****************************/
02	/* 平方根の計算             */
03	/*      coded by Y.Suganuma */
04	/****************************/
05	import java.io.*;
06	
07	public class Test {
08		public static void main(String args[]) throws IOException
09		{
10			BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
11			double data = 0.0;
12			double x[] = new double [100];
13			double y[] = new double [100];
14		/*
15		     平方根の計算
16		*/
17			for (int i1 = 0; i1 < 100; i1++) {
18				data  += 1.0;
19				x[i1]  = data;
20				y[i1]  = Math.sqrt(data);
21			}
22		/*
23		     ファイル名の入力
24		*/
25			System.out.print("出力ファイル名は? ");
26			String f_name = in.readLine();
27		/*
28		     出力
29		*/
30			PrintStream out = new PrintStream(new FileOutputStream(f_name));
31			for (int i1 = 0; i1 < 100; i1++)
32				out.println(x[i1] + " " + y[i1]);
33			out.close();
34		}
35	}
		
11 行目~ 13 行目

  平方根を計算する値は 1 から 100 までですので,変数 data や x に対する型は int で構わないように見えますが,メソッド sqrt に渡してやるデータ( 20 行目のカッコ内)は,必ず double である必要があります.このように,各メソッドに渡してやるデータは,そのメソッドによって型が決まっています.必ず,定められた型を使用して下さい.

20 行目

  与えられたデータの平方根を計算しています.Math クラスには,三角関数,平方根など,数学的な計算を必要する関数( Java では,メソッドと呼びます)が多く定義されています.メソッド sqrt の呼び方に注意してください.

26 行目

  ファイル名を入力しています.文字列を扱うのには,この例のように String クラスを使用します.

(プログラム例 6.3 ) 大文字から小文字への変換 

  文字( char 型)は,2 バイトの整数とみなすこともできます.実際,整数と同様の取り扱いが可能です.以下に示すプログラムでは,この性質を利用して,大文字を小文字に変換しています.アスキーコードでは,大文字は 65 ~ 90,小文字は 97 ~ 122 までです.したがって,文字を整数として扱い,32 を加えてやれば,大文字から小文字への変換が可能です.しかし,この操作を行うためには,文字列を扱うString クラスのオブジェクトを char 型の配列に変換し( 19 行目),各文字を小文字に変換した後,再び,String クラスのオブジェクトに変換して( 22 行目)やる必要があります.実際上は,String クラスのメソッド toLowerCase を使用した方が簡単です( 19 行目~ 22 行目の替わりに 23 行目の利用).
01	/****************************/
02	/* 大文字から小文字への変換 */
03	/*      coded by Y.Suganuma */
04	/****************************/
05	import java.io.*;
06	
07	public class Test {
08		public static void main(String args[]) throws IOException
09		{
10			BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
11		/*
12			 データの入力
13		*/
14			System.out.print("大文字の文字列を入力してください ");
15			String str = in.readLine();
16		/*
17			 小文字へ変換と出力
18		*/
19			char c[] = str.toCharArray();
20			for (int i1 = 0; i1 < c.length; i1++)
21				c[i1] += 32;
22			str = new String(c);
23	//		str = str.toLowerCase();
24			System.out.println(str);
25		}
26	}
		

6.1.3 多次元配列 

  2 次元以上の配列を利用することも出来ます.例えば,2 行 3 列(行や列の数は変数でも構わない)の配列を確保するためには,以下のように記述します.この結果,概念的には,右図に示すような領域が確保されます( C/C++ のように,連続した領域とは限らない).3 次元以上の配列も,同様の手続きを利用することによって可能です.
double pd[][] = new double [2][3];
double pd[][] = new double [2][];   // この行以下の 3 行のような形でも可能
for (int i1 = 0; i1 < 2; i1++)
	pd[i1] = new double [3];   // 行毎に列数を変えることも可能
double pd[][] = {{1, 2, 3}, {4, 5, 6}};   // 初期設定を行う場合
double x1[] = {1, 2, 3};   // これ以下の方法で初期設定も可能
double x2[] = {4, 5, 6};
double pd[][] = {x1, x2};		
  基本的に,pd はポインタですので,
double x[] = pd[1];		
とすることによって,2 次元配列の 2 行目を 1 次元配列として扱うことができます.ただし,同じ場所を指していますので,例えば,x[2] ( pd[1][2] )の値を変更すれば,pd[1][2] ( x[2] )の値も変化する点に注意してください.ただし,連続領域ではありませんので,C/C++ のように,全体を 1 次元配列として扱うようなことはできません.

(プログラム例 6.4 ) 多次元配列  ( A ~ D の部分を可能な限り一つの変数,定数,演算子等で,埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.)

  このプログラムは,プログラム例 6.1 を n 行 3 列の配列を使用して書き直したものです.

(プログラム)
/********************************/
/* 平均点の計算と平均点以下の人 */
/*      coded by Y.Suganuma     */
/********************************/
import java.io.*;
import java.util.*;
public class Test {
	public static void main(String args[])
	{
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		double mean1, mean2, sum1 = 0.0, sum2 = 0.0;
		int n, m = 0, x[][];
		String line;
		StringTokenizer str;
		try {
					// 人数の読み込みと配列の定義
			System.out.print("人数は? ");
			n = Integer.parseInt(in.readLine());
			x = new int [n][3];
					// データの読み込みと点数の和の計算
			for (int i1 = 0; i1 < n; i1++) {
				System.out.print("学籍番号,英語の点,数学の点は? ");
				line      = in.readLine();
				str       = new StringTokenizer(line, " ");
				x[i1][0]  = Integer.parseInt(str.nextToken());
				x[i1][1]  = Integer.parseInt(str.nextToken());
				x[i1][2]  = Integer.parseInt(str.nextToken());
				sum1     += x[i1][1];
				sum2     += x[i1][2];
			}
					// 平均点の計算と平均点以下の人
			if (n <= 0)
				System.out.println("データが存在しません!");
			else {
				mean1 = sum1 / n;
				mean2 = sum2 / n;
				System.out.println("英語平均 " + mean1 + " 数学平均 " + mean2);
				System.out.println("いずれかの科目が平均点以下の人の学籍番号");
				for (int i1 = 0; i1 < n; i1++) {
					if (x[i1][1] <= mean1 || x[i1][2] <= mean2) {
						System.out.println("    " + x[i1][0]);
						m++;
					}
				}
				System.out.println("該当学生数 " + m + " 人");
			}
		}
		catch (IOException ignored) {}
	}
}
			

12 行目

  変数 x が 2 次元配列であることを宣言しています.

19 行目

  18 行目において人数 n を読み込んだ後,必要な領域( n 行 3 列)を確保しています.

23~27 行目

  この例では,1 行に,学籍番号,英語の点,及び,数学の点が,半角スペースで区切って入力されることを仮定していますので,それらを分離し,配列の (i1 + 1) 行目の対応する要素に保存しています.

35,36 行目

  各科目の平均値を計算しています.

39~44 行目

  上で計算された平均値と,配列に記憶されている各人の点数から,英語または数学が平均点以下である人の学籍番号を出力するとともに,その人数を計算しています.

6.2 配列とポインタ 

  Java にはポインタという言葉はありませんが,ポインタという概念が存在しないわけではありません.むしろ,int や double などの基本データ型以外はポインタで参照されていると考えた方が理解しやすいと思います.一つ例を挙げておきます.

/****************************/
/* 配列とポインタ           */
/*      coded by Y.Suganuma */
/****************************/
import java.io.*;

public class Test {

	/****************/
	/* main program */
	/****************/
	public static void main(String args[])
	{
		int a[] = new int [3];
		int b[] = new int [2];
					// 値を設定
		a[0] = 1;
		a[1] = 2;
		a[2] = 3;
		b[0] = 10;
		b[1] = 20;
					// a,bの出力
		System.out.println("a " + a[0] + " " + a[1] + " " + a[2]);
//		System.out.println("b " + b[0] + " " + b[1] + " " + b[2]);   エラー
		System.out.println("b " + b[0] + " " + b[1]);
					// aをbに代入(配列aのアドレスをbに代入)
		b = a;
					// bの出力
		System.out.println("b(aをbに代入後) " + b[0] + " " + b[1] + " " + b[2]);
					// a[0]の値の変更
		a[0] = 5;
					// a,bの出力
		System.out.println("a(a[0]の値を変更後) " + a[0] + " " + a[1] + " " + a[2]);
		System.out.println("b(a[0]の値を変更後) " + b[0] + " " + b[1] + " " + b[2]);
	}
}
		

  このプログラムを実行すると,以下に示すような結果が得られます.
a 1 2 3
b 10 20
b(aをbに代入後) 1 2 3
a(a[0]の値を変更後) 5 2 3
b(a[0]の値を変更後) 5 2 3		
  この結果は,変数 a や b が,int 型配列へのポインタであると考えれば,a を b に代入するといった操作や,a[0] の値を変えると b[0] の値も変化するといった現象を容易に理解できます(右図参照).

  後の章で現れるクラスについても同様です.あるクラス型の変数を定義する場合,C++ においては,そのクラス型オブジェクト自身と,そこへのポインタが区別されます.従って,あるクラスのオブジェクト cl がポインタか否かにより,クラス内の変数等を参照する際に,
cl.val ( cl がポインタでない場合)
cl->val ( cl がポインタである場合)		
のような表現上の違いが出てきます.Java の場合は,表面上,ポインタとしての定義はできません.しかし,すべてがポインタとして定義されており,C++ であれば,「->」を使用すべきところを「.」で置き換えていると考えた方が,C++ の立場からは理解しやすいと思います.

  なお,Java においては,配列を確保すると,配列の各要素の内容は以下に示すような値で自動的に初期化されます.

配列型 初期値
byte 0
short 0
int 0
long 0L
float 0.0f
double 0.0d
char '\u0000'
boolean false
参照型(オブジェクト型) NULL

(プログラム例6.5) 1 次元配列,初期設定,及び,ポインタ 

01	/****************************************/
02	/* 1 次元配列,初期設定,及び,ポインタ */
03	/*      coded by Y.Suganuma             */
04	/****************************************/
05	import java.io.*;
06
07	public class Test {
08		public static void main(String args[])
09		{
10		/*
11		     初期設定された値と確保された領域のサイズ
12		*/
13			int x2[] = {1, 2, 3, 4, 5, 6};    // 6つのデータ領域
14			for (int i1 = 0; i1 < x2.length; i1++)
15				System.out.print(x2[i1] + " ");
16			System.out.print("(" + 4*x2.length + "バイト)\n");
17	
18			String c1 = "test data";   // 文字列
19			String c2[] = {"test1 data", "test2 data"};   // 2つの文字列
20			System.out.println(c1 + "(" + 2*c1.length() + "バイト)");
21			for (int i1 = 0; i1 < c2.length; i1++)
22				System.out.println(c2[i1] + "(" + 2*c2[i1].length() + "バイト)");
23		/*
24		     要素の参照と変更
25		*/
26			int x1[] = x2;   // int x1[] は,配列であることの宣言
27			x1[1]    = -1;
28			x2[3]    = -3;
29			for (int i1 = 0; i1 < x2.length; i1++)
30				System.out.print(x2[i1] + " ");
31			System.out.print("\n");
32	
33			c1 = "test0 data";
34			System.out.println(c1 + "(" + 2*c1.length() + "バイト)");
35			c2[0] = "test100 data";
36			for (int i1 = 0; i1 < c2.length; i1++)
37				System.out.println(c2[i1] + "(" + 2*c2[i1].length() + "バイト)");
38		}
39	}
		
13 行目

  new 演算子を使用しなくても,このように,初期設定によって配列のサイズを宣言可能です.

14 行目

  Java においては,配列は一種のオブジェクトです.length は配列の大きさが入っている変数です.

18,19 行目

  Java には String クラスが存在するため,文字列を String クラスで扱う場合が多いと思います.String クラスを利用すると,18 行目のように,文字列を配列と意識しないで使用することが可能です.19 行目のようなデータを扱いたい場合,C/C++ では 2 次元配列になります.

20 行目

  length() は文字列の長さを返すメソッド(関数)です.

26 ~ 28 行目

  C/C++ のプログラム例の 32 から 36 行目と比較すると非常に似ているのが分かると思います.Java にはアドレス演算子や間接演算子が存在しないため,C/C++ における「 x1 = &x[2] 」のように配列の中間のアドレスを取り出すことや C/C++ のプログラム例の 34 および 36 行目に対応する操作は不可能ですが,26 行目が,C/C++ のプログラム例の 32 行目に相当しているといっても,少なくともこの場合は問題ないように思えます.このように,Java においてもポインタの考え方自身は重要であると思います.33 および 35 行目の代入が可能であることも,c1 および c2[0] がポインタであると思えば納得できます(C/C++ のプログラム例の 47,48 行目).
(出力)
1 2 3 4 5 6 (24バイト)
test data(18バイト)
test1 data(20バイト)
test2 data(20バイト)
1 -1 3 -3 5 6 
test0 data(20バイト)
test100 data(24バイト)
test2 data(20バイト)		

(プログラム例 6.6 ) 多次元配列,初期設定,及び,ポインタ 

  残念ながら,C/C++ のプログラム例における 26 行目のように,2 次元配列を 1 次元配列として処理するような方法を使用することは不可能です.ただし,この例の 22 行目のように,2 次元配列の特定の行を 1 次元配列として扱うことは可能です.この考え方は,3 次元以上の配列においても同様です.このような点からも,
int px1[];   // int [] px1; でも可
int px2[][];   // int [][] px2; でも可		
の記述が,C/C++ における
int *px1;
int **px2;		
の記述に非常に似ていることに気づかれると思います.
01	/****************************************/
02	/* 多次元配列,初期設定,及び,ポインタ */
03	/*      coded by Y.Suganuma             */
04	/****************************************/
05	import java.io.*;
06	
07	public class Test {
08		public static void main(String args[])
09		{
10		/*
11			 初期設定された値と確保された領域のサイズ
12		*/
13			int x1[][]  = {{1, 2}, {3, 4}, {5, 6}};          // 3行2列(初期化)
14			System.out.println(x1[1][1] + "(" + 4*x1.length + "バイト)");
15	
16			String c1[] = {"zero", "one", "two", "three"};   // Stringの一次元配列
17			for (int i1 = 0; i1 < c1.length; i1++)
18				System.out.println(c1[i1] + "(" + 2*c1[i1].length() + "バイト)");
19		/*
20			 要素の参照と変更
21		*/
22			int px1[]   = x1[2];   // 配列 x1 の 3 行目の先頭アドレス
23			int px2[][] = x1;   // 配列 x1 のアドレス
24			System.out.println(x1[2][1] + " " + px1[1] + " " + px2[2][1]);
25		}
26	}
		
(出力)
4(12バイト)
zero(8バイト)
one(6バイト)
two(6バイト)
three(10バイト)
6 6 6		
  以下に示す例においては,new 演算子と代入・初期化との関係を,様々な型の変数に対して行っています.クラスに関する説明がまだですので,その部分に関しては,クラスの説明が終了した後,再度見直してみてください.

(プログラム例 6.7 ) new 演算子と代入・初期化 

  Java においても,new 演算子の意味する所は C++ とほとんど同じです.しかし,アドレス演算子や間接演算子が存在せず,ポインタという概念が表面に現れていないため,多少異なった表現になります.なお,Java には,確保した領域を開放するための delete 演算子は存在しません.変数を必要としなくなった時点で自動的に開放されます.また,ガーベッジコレクションも実行してくれます.

01	/****************************/
02	/* new 演算子と代入・初期化 */
03	/*      coded by Y.Suganuma */
04	/****************************/
05	import java.io.*;
06	import java.util.*;
07	
08	public class Test {
09		public static void main(String args[])
10		{
11						// 単純変数
12			System.out.println("***単純変数***");
13			int a1 = 0;
14			int a2 = a1;
15			a1     = 1234;
16			System.out.printf("   a1 %d\n", a1);
17			System.out.printf("   a2 %d\n", a2);
18						// 配列
19			System.out.println("***配列***");
20			int p2[] = {1, 2, 3, 4};
21			int p5[] = p2;
22			int pc[] = p2.clone();
23			Complex x1   = new Complex();
24			Complex x2   = new Complex();
25			Complex u5[] = {x1, x2}, u6[];
26			u6           = u5.clone();
27			p2[2]   = 200;
28			u6[0]   = new Complex();
29			u6[0].x = 6;
30			u6[1].x = 7;
31			System.out.printf("   p2");
32			for (int i1 = 0; i1 < 4; i1++)
33				System.out.printf(" %d", p2[i1]);
34			System.out.printf("\n   p5");
35			for (int i1 = 0; i1 < 4; i1++)
36				System.out.printf(" %d", p5[i1]);
37			System.out.printf("\n   pc");
38			for (int i1 = 0; i1 < 4; i1++)
39				System.out.printf(" %d", pc[i1]);
40			System.out.printf("\n   u5");
41			for (int i1 = 0; i1 < 2; i1++)
42				System.out.printf(" (%d %d)", u5[i1].x, u5[i1].y);
43			System.out.printf("\n   u6");
44			for (int i1 = 0; i1 < 2; i1++)
45				System.out.printf(" (%d %d)", u6[i1].x, u6[i1].y);
46						// クラスのオブジェクト
47			System.out.println("\n***クラスのオブジェクト***");
48			Complex p3 = new Complex();
49			Complex p6 = p3;
50			p3.x       = 20;
51			System.out.printf("   p3 %d %d\n", p3.x, p3.y);
52			System.out.printf("   p6 %d %d\n", p6.x, p6.y);
53						// String クラス
54			System.out.println("***String クラス***");
55			String s1 = new String("abc");
56			String s2 = s1;
57			s1        = s1.replace('a', 'X');
58			System.out.printf("   s1 %s\n", s1);
59			System.out.printf("   s2 %s\n", s2);
60						// ArrayList クラス
61			System.out.println("***ArrayList クラス***");
62			ArrayList <String> a = new ArrayList <String> ();
63			a.add("abc");
64			ArrayList <String> b = a;
65			ArrayList <String> c = new ArrayList <String> (a);
66			ArrayList <String> d = listCast(a.clone());
67	//		ArrayList <String> d = (ArrayList <String>)a.clone();
68			System.out.printf("   a (size) %d (contens) %s\n", a.size(), a.get(0));
69			System.out.printf("   b (size) %d (contens) %s\n", b.size(), b.get(0));
70			System.out.printf("   c (size) %d (contens) %s\n", c.size(), c.get(0));
71			System.out.printf("   d (size) %d (contens) %s\n", d.size(), d.get(0));
72			a.add("xyz");
73			System.out.printf("   a (size) %d (contens) %s %s\n", a.size(), a.get(0), a.get(1));
74			System.out.printf("   b (size) %d (contens) %s %s\n", b.size(), b.get(0), b.get(1));
75			System.out.printf("   c (size) %d (contens) %s\n", c.size(), c.get(0));
76			System.out.printf("   d (size) %d (contens) %s\n", d.size(), d.get(0));
77		}
78	
79		@SuppressWarnings("unchecked") 
80		public static <Type> Type listCast(Object src) { 
81			Type target = (Type)src; 
82			return target; 
83		} 
84	}
85	
86	/*****************/
87	/* クラスComplex */
88	/*****************/
89	class Complex
90	{
91		int x, y;
92		Complex()
93		{
94			x = 0;
95			y = 0;
96		}
97	}
		
12 行目~ 17 行目

  int 型の変数 a1,a2 を定義し,a1 は 0 で初期設定し,それを a2 に代入(初期設定)しています.15 行目において,a1 に 1234 を代入していますので,a1 の値は 1234,a2 の値は 0 になっているはずです.a1 と a2 は異なる変数であり,代入は,代入する値をコピーして実行されるわけですから,当然の結果と言えます.12 行目~ 17 行目内の出力文によって,以下に示すように,予想通りの結果が得られます.
***単純変数***
   a1 1234
   a2 0			
19 ~ 45 行目

  単純な代入の場合,間接演算子の存在を除けば,C++ における new 演算子を使用した場合とほとんど同じです(アドレスのコピーが代入される).20 行目において,4 個分の int 型データが入る領域が確保され(初期設定も行っている),その先頭アドレス(下図では 20 番地)が変数 p2 に代入されます(下の左図).21 行目において,p2 の値が p5 に代入されますので,p2 と p5 は同じ場所を指すことになります(下の右図).C++ と表現方法は異なりますが,C++ と同様,データ自身がコピーされるわけではないことに注意して下さい.この点は,下に示す出力結果からも明らかです.

  22 行目においては,メソッド clone を使用して,p2 のクローンを pc に代入しています.pc においては,p2 の各要素に記憶されている値が pc の各要素にコピーされ,p2 と pc は別の配列になります.そのため,p2 の要素の値を変更しても pc には影響を及ぼしません.

  しかし,Complex クラス( 89 ~ 97 行目)のオブジェクトを要素とした配列 u5,u6 に対する結果を見てください.u6 は,u5 のクローンですから,p2 と pc の関係と同じになるはずです.確かに,28,29 行において u6[0].x の値を変更しても u5 には何の影響も与えていません.しかし,30 行目において u6[1].x の値を変更すると,u5[1].x の値も変化してしまいます.これは,クローンの生成が浅いコピーに基づいているが故に起きる現象です.u5 のクローンを生成する際に,u5 の各要素に記憶されている値,つまり,Complex クラスのオブジェクトに対するアドレスを u6 にコピーします.浅いコピーのため,そのアドレスが指すデータまではコピーされません.その結果,クローンが生成された時点においては,u5 と u6 の各要素は同じクラス Complex のオブジェクトを指していることになり,u6[1].x の値を変更すると,u5[1].x の値も変化することになります.ただし,29 行における u6[0].x の変更は,28 行目において要素に記憶されているアドレス自身を変更しており(新しい Complex クラスのオブジェクトに対するアドレス),u5 の値には影響を及ぼしません.同様に,メソッド clone は浅いコピーを生成するため,例え整数を要素とする配列であっても,2 次元以上の配列に対しては有効に働きません.
***配列***
   p2 1 2 200 4
   p5 1 2 200 4
   pc 1 2 3 4
   u5 (0 0) (7 0)
   u6 (6 0) (7 0)			
47 行目~ 52 行目

  Complex クラスのインスタンス(オブジェクト)を記憶できる領域が確保され,そのアドレスが変数 p3 に代入されます( 48 行目).この例においても,C++ において new 演算子を使用する場合と同様,p3 と p6 が同じ場所を指しているため,どちらの変数を使用しても同じ結果になります.代入によって異なるオブジェクトを生成するためには,61 行目~ 76 行目に示す ArrayList クラスのように,クローンを生成するメソッドを作成する,Complex クラスのオブジェクトを引数とするコンストラクタを作成する,などの処理が必要となります.
***クラスのオブジェクト***
   p3 20 0
   p6 20 0			
54 行目~ 59 行目

  String クラスに対する例です.Complex クラスと同様の結果になるはずですが,String クラスの場合,文字列自体を変更するメソッドが存在せず,57 行目のように,変更した文字列を他の変数に代入することになりますので,以下のような結果になります.
***String クラス***
   s1 Xbc
   s2 abc			
61 行目~ 76 行目

  ArrayList クラスに対する例です.単純な代入の場合は,Complex クラスと同様,64 行目の代入文によって,a と b が同じ場所を指すことになるため,どちらの変数を使用しても同じ結果になります.また,クラスのオブジェクトを引数として生成した場合( 65 行目)やメソッド clone を使用した場合( 66 行目)は,全く異なるオブジェクトとして処理されます.ただし,この例における ArrayList クラス は,String クラスのオブジェクトを要素とし,かつ,d は a の浅いコピーである点に注意してください(配列に対する説明を参照).

  なお,クローンを生成する場合,67 行目のような形でキャスティングを行うと,警告メッセージが出力されてしまいます.そこで,コレクション型のオブジェクトに対する自動型変換を行うメソッド listCast を定義し( 80 行目~ 83 行目),このメソッド内では警告を出さないようにしています( 79 行目).
***ArrayList クラス***
   a (size) 1 (contens) abc
   b (size) 1 (contens) abc
   c (size) 1 (contens) abc
   d (size) 1 (contens) abc
   a (size) 2 (contens) abc xyz
   b (size) 2 (contens) abc xyz
   c (size) 1 (contens) abc
   d (size) 1 (contens) abc			
86 行目~ 97 行目

  クラスの定義です.ここでは,2 つの int 型変数 x,y から構成される新しい Complex 型の変数が定義されたという程度で理解しておいて下さい.

演習問題6

[問1] n (入力)次元のベクトル a の各要素を配列変数の各要素に入力した後,その大きさ(|a|)を出力するプログラムを書け.

[問2] n (入力)次元のベクトル ab の各要素を配列変数の各要素に入力した後,ベクトル ab の和(c = a + b)を出力するプログラムを書け.

[問3] n 次元のベクトル ab の内積を計算し出力するプログラムを書け.なお,n,および,各ベクトルの要素の値はキーボードから入力するものとする(ベクトルに対しては,配列変数を利用するものとする).また,ベクトル ab の内積 x の定義は以下の通りである.
x = a11 + a22 +・・・+ ann

[問4]配列 a に n (入力)個のデータ,また,配列 b に n 個のデータを入力した後,もし,a[i] が b[i] より大きければ,a[i] と b[i] を入れ換える操作を行うプログラムを書け.このとき,この操作を行った回数,及び,入れ換えを行った a[i],b[i] の値を出力するものとする.

[問5] n (入力)人の英語,及び,数学の点を入力し,各科目の平均点を計算した後(平均点も出力),どちらかの科目が平均点以下の人の数を出力するプログラムを書け.

[問6]平面上の n 個の点の座標(xi, yi)を読み込み,原点(0, 0)からの平均距離を計算した後,平均距離,原点から平均距離以上離れたすべての点の座標,及び,その数を出力するプログラムを書け.

[問7]今,ある物質を構成する n (未知数)個の成分の各質量がファイルに保存されていたとする.これらの成分を入力した後,各成分の全体に対する%を別のファイルへ出力するプログラムを書け.

[問8] n (入力)人のテストの点を入力し,0~10 点,11~20 点,・・・,91~100 点の範囲に入る人数を計算した後,下に示すようなグラフを出力するプログラムを書け(「*」の数は人数と同じとする).
 RANGE    NUM
  0- 10    2   **
 11- 20    5   *****
 ・・・・
 91-100    3   ***		
[問9]各月の売り上げ( 0~9,整数)を入力し,以下のようなグラフを出力するプログラムを書け.
        *
        *                        *
    *   *         ・・・・・       *  *
    *   *   *                 *  *
  --------------  ・・・・・  ---------
   1   2   3     ・・・・・      11 12		
[問10]ある店舗の毎日の売上の記録 xi(i = 1~30 )がある(入力データとして読み込む).ある与えられた日(入力データ)を基準にした前後 7 日間(例えば,5 日が与えられたときは,2 日から 8 日)の売上高の平均を計算し,出力するプログラムを書け.ただし,前後の日にちが 0 以下になったり 31 以上になった場合は,1 または 30 までの平均を計算する(例えば,2 日が与えられたときは,1 日から 5 日までの平均を計算)ものとする.また,0 以下または 31 以上の日にちが与えられたときは,再入力をうながすものとする( goto 文は使用しない).

[問11] n (入力)個の正の整数値を入力し,末尾の桁が 0,1,・・・,9 のものの個数とその割合(%)を出力するプログラムを書け.

[問12]ある学年に n (入力)クラスあり,各クラスには m 人(入力,クラス毎に異なる)の生徒がいるものとする.全員に対しある試験を実施したとき( 2 次元の配列に読み込む),全体における最高点,全体の平均点,及び,その平均点以上の各クラスにおける人数を出力するプログラムを書け.

[問13]平面上の n (入力)個の点の座標を入力した後,すべての点の間の距離を計算し,その距離が最大になる点の組( 2 つの点の座標),及び,その距離を出力するプログラムを書け.

[問14] n (入力)人の試験の点を入力し,上位 m(入力)人の平均点を計算するプログラムを書け.ただし,m << n ( m は n に比較して十分小さい)とし,すべての( n 人の)データを配列に保存し,並べ替えるようなことはしないものとする.

[問15]今,ある物質を構成する成分の数 n と各成分の質量がファイルに保存されていたとする.これらの成分を動的確保した配列に入力した後,各成分の全体に対する%を出力するプログラムを書け.

[問16]ある学年に n (入力)クラスあり,各クラスには m 人(入力,クラス毎に異なる)の生徒がいるものとする.全員に対しある試験を実施したとき,各人の名前,点数を動的確保した配列に保存した後,学年平均点以下の人の名前とその点数を出力するプログラムを書け.

情報学部 菅沼ホーム 全体目次 演習解答例 付録 索引