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

第6章 配列とポインタ

  1. 6.1 配列
    1. (プログラム例 6.0 ) 範囲 for 文
    2. (プログラム例 6.1 ) 平均値と平均値以下の人の出力
    3. (プログラム例 6.2 ) 平方根の計算とファイルへの出力
    4. (プログラム例 6.3 ) 大文字から小文字への変換
  2. 6.2 配列とポインタ
    1. (プログラム例 6.4 ) 1 次元配列,ポインタ,及び,初期化
  3. 6.3 2 次元以上の配列とポインタ
    1. (プログラム例 6.5 ) 2 次元配列,ポインタ,及び,初期化
    2. (プログラム例 6.5.1 ) 3 次元配列とポインタ
  4. 6.4 メモリの動的確保
    1. 6.4.1 malloc と free
      1. (プログラム例 6.6 ) メモリの動的確保( 1 次元配列)
      2. (プログラム例 6.7 ) メモリの動的確保( 2 次元配列)
    2. 6.4.2 new と delete(C++)
      1. (プログラム例 6.8 ) メモリの動的確保( 1 次元配列,new)
      2. (プログラム例 6.9 ) メモリの動的確保( 2 次元配列,new)
      3. (プログラム例 6.10 ) new 演算子と代入・初期化
  5. 演習問題6

6.1 配列

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

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

  ある変数がスカラー(単純変数)でなく,配列変数であることを定義するためには,例えば,以下のように記述します.
double x[10];
	// 初期設定する場合(いずれの方法でも可)
double x[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
double x[10] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};   // 初期化子リスト(C++11)
double x[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
double x[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};   // 初期化子リスト(C++11)		
  このように定義することにより,変数 x は 10 個の要素を持った配列変数( 10 次元のベクトル)とみなされ,この場合は,10 個の倍精度実数を記憶するために 80( 8×10 )バイトの連続した領域が確保されます.なお,基本的に,要素の数は定数でなければなりません.ただし,const 宣言された変数 n を使用して,
const int n = 10;   // n の値は変更できない
double x[n];		
のように,宣言することは可能です.例えば,下の例のように,入力するデータの数に応じて配列を宣言したいことがしばしば起こります.C/C++ のバージョンによっては可能ですが,以下のような処理を行いたい場合は,後に述べるように,特別な関数( malloc 等)または new 演算子を使用した方が良いと思います.
int n;
printf("データの数を入力してください ");
scanf("%d", &n);
double x[n];   // 最近の C では可能		
  配列の各要素を参照するためには,ベクトルの場合と同様,添え字を利用します.ただし,[] を使用して x[i] のように記述すると共に,添え字 i は,ベクトルとは異なり,上のように定義した場合,0 から 9 まで変化します.従って,
x[5] = 3.0;
y    = x[3];		
と書くと,配列 x の 6 番目の要素( 5 番目でないことに注意)に値 3.0 が代入され,4 番目の要素に記憶されている値が変数 y に代入されます.このように,配列の参照や代入は,添え字を利用して各要素ごとに行えますので,たとえば,配列 x の 2 から 15 番目の要素を配列 y の先頭部分にコピー(代入)したい場合は,以下に示すように,繰り返し文を使用して簡単に実行できます.14 個の単純変数を他の 14 個の単純変数に代入する場合と比較してみて下さい.
for (i1 = 1; i1 <= 14; i1++)   // i1 の値に注意
	y[i1-1] = x[i1];		
  文字列も char 型の配列として取り扱われますが,数値を要素とする配列とは多少異なる点がありますので,説明を追加しておきます.3.2.2 節で述べましたように,
char c = 'x';		
と宣言することにより,変数 c には 1 文字だけ記憶できます.この例では,その変数を「x」という文字で初期化しています(「'」が使われていることに注意).複数の文字,つまり,文字列を記憶するためには,複数の char 型変数を必要とします.従って,配列が重要な役割を果たします.

  例えば,9 個の文字( 10 個ではない)からなる文字列を,配列を使用して記憶するためには,
char z[10];		
という宣言が必要になります.なぜなら,配列を利用して文字列を扱う場合,文字列の最後には文字列の終わりであることを示す「 \0 」( NULL 文字)という文字を入れておく必要があるからです.そのため,(文字列の長さ+1)以上の大きさの配列を定義しておく必要があります.NULL 文字を使用しないで記憶することも可能ですが,printf や文字列を操作する関数(7.0.2 節参照)などを使いにくくなります.

  最後に,char 型変数に対する入出力について述べておきます.以下の例では,変数 z に対して,「 char z[10]; 」の宣言がされているものとします.なお,NULL 文字を使用せずに文字列を扱いたい場合は,%s を使用した入出力方法を利用できませんので,1 文字ずつ入力することになります.
scanf("%c", &z[2]);     // z の 3 番目の要素に 1 文字入力する
scanf("%s", z);         // 9 文字以下の文字列を入力する
                        // (文字列の最後に \0 が付加される)
printf("%c\n", z[2]);   // z の 3 番目の要素を出力する
printf("%s\n", z);      // 文字列を出力する		

---------------------(C++11)範囲 for 文-------------------------

  簡単な配列に対する説明を行いましたので,繰り返し文の中で説明できなかった範囲 for 文(C++11)について触れておきます.範囲 for 文は,配列やコンテナ(複数のデータを保存するための入れ物)を簡潔に扱うための for 文の別表現です.その一般的仕様は以下に示すとおりです.
for (変数宣言 : 範囲) {
    文(複数の文も可)
}		
変数宣言において宣言した変数に,範囲内の要素が先頭から終端まで順番に代入されます.範囲に示された変数の型が配列の場合,配列のサイズが分かるものでなければエラーとなります.また,配列以外(クラスなど)の場合,begin() と end() で範囲の先頭と終端が表せるものでなければエラーとなります.例えば,次のプログラムのように,配列を関数の引数として場合,main 関数内では問題ありませんが,関数 sub 内では許されません(コンパイルエラー).
void sub(int x[]) {
	for (auto a : x)
		printf("%d ", a);
	printf("\n");
}

int main() {
	int x[] = {4, 5, 6};
	for (auto a : x)
		printf("%d ", a);
	printf("\n");
	return 0;
}
		

(プログラム例 6.0 ) 範囲 for 文

  この例においては,配列とコンテナの一種である vector クラスmap クラスを使用しています.vector は,可変個のデータを保存できる動的な配列です.map は,キーと値のペアを一つのデータとして持つ連想配列を扱います.以下に示す例においては,キーとして文字列( string クラスのオブジェクト),値として int を使用しています.16,17 行目の & は,参照を表しており,変数 x が配列の各要素の別名であることを表しています.そのため,入力データによって各要素の値を変更することが可能です.また,41,54 行目においては,初期化子リスト(C++11)を使用して,vector 及び map の初期設定を行っています.このように,下に挙げた例の中では,まだ学習していない項目を多く使用していますので,それらを学習した後,再度見直してください.

  17 ~ 21 行目,30 ~ 32 行目,43 ~ 45 行目,56 ~ 58 行目が範囲 for 文の例になっています.17,30,43,及び,56 行目においては,変数の型宣言に対して auto を使用していますが,それらの行を auto を使用しないで記述した場合が,16,29,42,及び,55 行目になっています.24 ~ 27 行目,35 ~ 37 行目,48 ~ 50 行目,61 ~ 64 行目は,範囲 for 文を使用しないで書いた例になっています.この例からも明らかなように,範囲 for 文は,記述しやすく,かつ,理解しやすい文だと思いますので,可能であれば,積極的に利用すべきではないでしょうか.なお,このコンテンツ内の以下の例においても,範囲 for 文を使用して記述できる部分も多いかと思います.

01	/****************************/
02	/* 範囲 for 文              */
03	/*      coded by Y.Suganuma */
04	/****************************/
05	#include <iostream>
06	#include <vector>
07	#include <map>
08	
09	using namespace std;
10	
11	int main()
12	{
13				// 配列
14		cout << "*** array ***\n";
15		int k = 0, a[5];
16	//	for (int& x : a) {
17		for (auto& x : a) {
18			cout << (k+1) << " 番目のデータ? ";
19			cin >> x;
20			k++;
21		}
22					// 従来の for 文
23	/*
24		for (int i1 = 0; i1 < 5; i1++) {
25			cout << (i1+1) << " 番目のデータ? ";
26			cin >> a[i1];
27		}
28	*/
29	//	for (int x : a)
30		for (auto x : a)
31			cout << "  " << x;
32		cout << endl;
33					// 従来の for 文
34	/*
35		for (int i1 = 0; i1 < 5; i1++)
36			cout << "  " << a[i1];
37		cout << endl;
38	*/
39				// vector*
40		cout << "*** vector ***\n";
41		vector<int> v {10, 20, 30, 40, 50};
42	//	for (int x : v)
43		for (auto x : v)
44			cout << "  " << x;
45		cout << endl;
46					// 従来の for 文
47	/*
48		for (unsigned int i1 = 0; i1 < v.size(); i1++)
49			cout << "  " << v[i1];
50		cout << endl;
51	*/
52				// map
53		cout << "*** map ***\n";
54		map<string, int> mp {{"tanaka", 90}, {"suzuki", 100}, {"sato", 70}};
55	//	for (pair<string, int> x : mp)
56		for (auto x : mp)
57			cout << "  (" << x.first << ", " << x.second << ")";
58		cout << endl;
59					// 従来の for 文
60	/*
61		map<string, int>::iterator it;
62		for (it = mp.begin(); it != mp.end(); it++)
63			cout << "  (" << (*it).first << ", " << (*it).second << ")";
64		cout << endl;
65	*/
66	
67		return 0;
68	}
		
(出力)
1 番目のデータ? 1
2 番目のデータ? 2
3 番目のデータ? 3
4 番目のデータ? 4
5 番目のデータ? 5
  1  2  3  4  5
*** vector ***
  10  20  30  40  50
*** map ***
  (sato, 70)  (suzuki, 100)  (tanaka, 90)
		

----------------------(C++11)範囲 for 文終わり--------------------

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

  配列に対する最初の例として,プログラム例 5.7 と似た問題を取り上げます.n 人いるクラスで試験を行ったとします.各人の点数を入力し,平均値を計算した後,平均値以下の人の番号と点数を出力するプログラムです.

(プログラム)
/********************************/
/* 平均値の計算と平均値以下の人 */
/*      coded by Y.Suganuma     */
/********************************/
#include <stdio.h>

int main()
{
/*
         データの数の読み込み
*/
	int n;
	printf("人数は? ");
	scanf("%d", &n);

	if (n <= 0 || n > 50)
		printf("人数が不適当です\n");
/*
         データの読み込み
*/
	else {
		int i1;
		double mean = 0.0;
		double x[50];
		for (i1 = 0; i1 < n; i1++) {
			printf("%d 番目の人の点は? ", i1+1);
			scanf("%lf", &(x[i1]));
			mean += x[i1];
		}
/*
         平均値の計算と出力
*/
		mean /= n;
		printf("   平均値は=%f\n", mean);
/*
         平均値以下の人を調べ,出力
*/
		for (i1 = 0; i1 < n; i1++) {
			if (x[i1] <= mean)
				printf("      %d番 %f点\n", i1+1, x[i1]);
		}
	}

	return 0;
}
			
16,17 行目

  このプログラムは,24 行目で宣言しているように,50 個のデータしか記憶できません.従って,入力された人数が 50 人より多いとき,エラーメッセージを出し何も行わないようにするため,これらの行があります.同時に,0 以下の値も避けています.もし,プログラム内に
	printf("%f\n", x[51]);
	x[55] = 3.6;			
のような記述をしても,コンパイルやリンク時に何のエラーメッセージも出力されず,実行可能となってしまいます.しかし,このような文を実行すると,出力の場合は予期せぬ値が出力されるだけで済みますが,代入の場合は,配列変数 x に用意された記憶領域の外にデータを書き込むことになり,場合によってはプログラム自身を破壊してしまいます.従って,最初に用意された領域外のデータが参照されることを避けるために 16 行目の if 文が必要になります.

27 行目

 (i1 + 1)番目の人の点数が,配列変数 x の(i1 + 1)番目の要素に読み込まれます.

38 ~ 41 行目

  平均点以下の人を調べるための for ループです.全員に対しチェックする必要がありますので,再度,for 文を使う必要があります.

(プログラム例6.2) 平方根の計算とファイルへの出力 ( A ~ D の部分を可能な限り一つの変数,定数,演算子等で,埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.)

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

  なお,このプログラムでは,出力するファイル名(文字列)をプログラムで固定せず,キーボードから入力しています.

(プログラム)
/****************************/
/* 平方根の計算             */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>
#include <math.h>

int main()
{
/*
     ファイル名の入力
*/
	char f_name[50];
	printf("出力ファイル名は? ");
	scanf("%s", f_name);
/*
     平方根の計算
*/
	double data = 0.0;
	double x[100], y[100];
	int i1;

	for (i1 = 0; i1 < 100; i1++) {
		data  += 1.0;
		x[i1]  = data;
		y[i1]  = sqrt(data);
	}
/*
     出力
*/
	FILE *out = fopen(f_name, "w");

	for (i1 = 0; i1 < 100; i1++)
		fprintf(out, "%f %f\n", x[i1], y[i1]);

	return 0;
}
			
6 行目

  26 行目において,平方根を計算する関数 sqrt を使用しているため,このヘッダファイルが必要になります.同様に,三角関数,対数等,数学関係の関数を使用するときはこのヘッダファイルが必要になります.C++ の場合は,<cmath> を使用しても構いません.ただし,<cmath> をインクルードした時は std 名前空間内の関数を使用しますが(「累乗,冪乗,絶対値」参照),<math.h>をインクルードした時は C の標準関数(グローバル関数)を使用します.

13 行目

  ファイル名は,一般に,文字列です.ファイル名を入力するために,配列変数 f_name の宣言をしています.この配列変数には,49 文字以下の文字列を記憶できます.なぜなら,文字列の場合,文字列の最後に必ずヌル文字「 \0 」が付加されるため,このヌル文字も文字数として数える必要があるからです.

15 行目

  ファイル名を入力しています.「 %s 」を使用すること,及び,変数の前に & が付かないことに注意して下さい.

19,20 行目

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

25,26 行目

  変数 data の値を x[i1] に保存し,かつ,その値に対する平方根を計算し,配列変数 y[i1] に保存しています.

33,34 行目

  if 文と同じように,for 文で繰り返す文が 1 文だけのときは,「 { 」と「 } 」を省略できます.

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

  文字( char 型)は,1 バイトの整数とみなすこともできます.実際,整数と同様の取り扱いが可能です.以下に示すプログラムでは,この性質を利用して,大文字を小文字に変換しています.アスキーコードでは,大文字は 65 ~ 90,小文字は 97 ~ 122 までです.したがって,文字を整数として扱い,32 を加えてやれば,大文字から小文字への変換が可能です.

(プログラム)
/****************************/
/* 大文字から小文字への変換 */
/*  	coded by Y.Suganuma */
/****************************/
#include <stdio.h>

int main()
{
/*
	 データ数の入力
*/
	char c[50];
	printf("大文字の文字列を入力してください(49字以内) ");
	scanf("%s", c);
/*
	 小文字へ変換
*/
	int k = 0;
	while (c[k] != '\0') {
		c[k] += 32;
		k++;
	}
/*
	 出力
*/
	printf("%s\n", c);

	return 0;
}
			

6.2 配列とポインタ

  4.1 節で述べたポインタと配列とは非常に深い関係があります.例えば,
int x[4] = {100, 200, 300, 400};   // 各要素の初期設定も行っている
int *y;   // int y[4]; と宣言した場合は,y = x; は実行できない
char *c;		
のような宣言がなされていたとして,以下の説明を行います.

  このとき,配列変数 x に対しては,図 6.1 の左に示すようなイメージを浮かべると理解しやすいと思います.つまり,x がポインタ変数であり,この変数の値が,連続した領域にある 4 つの変数 x[0],x[1],x[2],及び,x[3] の先頭アドレスを指しているというイメージです.従って,ポインタ変数 x の値を同じポインタ変数である y に代入,つまり,
y = x; または y = &(x[0]);		
という文を実行してやれば,図 6.1 の右に示すように,変数 x と変数 y はほとんど同じものになります.例えば,y[2] は x[2] と同じ箇所を参照することになります.ただし,厳密には,全く同じではありません.例えば,「y++」などの演算は可能ですが,「x++」は不可能です.x と y を全く同じように扱いたい場合は,x に対する定義部分を.後に述べる new 演算子を使用して,以下のように修正してやる必要があります.このようにすれば,x と y を全く同じように扱えます.
int *x = new int [4];
for (int i1 = 0; i1 < 4; i1++)
	x[i1] = 100 * (i1 + 1);
// C++11 以降であれば,上 3 行の代わりに下の 1 行で可能
//int *x = new int [4] {100, 200, 300, 400};   // 初期化子リスト,C++11
int *y;
char *c;		
  また,先頭アドレスではなく,例えば,y = &x[2] とすることにより,y を x の後半 2 つのデータに対応する 2 個の要素を持った配列として使用することも可能です.つまり,y[0] と x[2],及び,y[1] と x[3] が同じものになります(右図参照).

  上で述べたように,変数 y はポインタですので,間接演算子を使用して配列の各要素を参照することも可能です.たとえば, *y という方法で,y[0] ( x[0] でもある.以下,同様)を参照できます.それでは,ポインタを使用して y[1] 以下を参照するにはどのようにすれば良いでしょうか.まず,
y++;
*y = ・・・;		
のように,y の値を 1 だけ増加させれば,ポインタ変数 y は y[1] のアドレスを指すようになります.4.1 節でも述べたように,y は int 型のポインタ変数ですので,単純に 1 だけは増加しません.図 6.1 のような場合であれば,int 型変数の大きさ( 4 バイト)だけ増加し,24 という値になり,y[1] の記憶場所を指すことになります.y[2] 以降を参照する場合も,同様に,ポインタ変数を 1 ずつ増加させていけば可能です.

  しかし,この方法では,ポインタ変数 y の値自身が変化してしまい,後に同じ配列を参照したいとき困ることになります.もちろん,他のポインタ変数,例えば z にポインタの値を代入した後に上の操作を行えば問題ありませんが,通常,y[0],y[1],y[2],・・・ を,
*y     = ・・・;
*(y+1) = ・・・;
a      = *(y+2);
 ・・・		
のように参照する方法の方がよく使用されます.

  ポインタ変数の値を実際 1 だけ増加させたいような場合は,char 型のポインタ変数を使用すれば可能です.例えば,
c = (char *)x;
c++;		
とすれば,変数 c の値は 1 だけ増加し,図 6.1 の場合,21 になります.

  以下,配列に対する初期化についてまとめておきます.配列表現の場合とポインタ表現の場合とでは,微妙な違いがありますので注意して下さい.
01	int x1[4] = {1, 2};
02	int x2[] = {1, 2};
03	int *x3 = {1, 2};     // 誤り
04	char c1[15] = {"test data"};   // char c1[15] = "test data"; でも可
05	char c2[] = {"test data"};   // char c2[] = "test data"; でも可
06  int x4[4] = {0};    // すべての要素が 0 で初期設定される
07  int x5[4] = {};    // 上と同様
08  int x6[4] {1, 2};   // C++11 以降では可能(初期化子リスト)
09	int x7[] {1, 2};   // C++11 以降では可能(初期化子リスト)
		
  01 行目の場合は,配列変数 x1 に対し,4 つの値が入る領域がとられ,その最初の 2 つが 1 と 2 に初期設定されます(残りは 0 ).02 行目の宣言に対しては,同様に配列変数 x2 の 2 つのデータに対する初期設定が行われますが,2 つのデータが入る領域しか確保されません.このように,初期設定する場合は,「[]」内の数字を書かなくても,構いません.しかし,03 行目の表現は許されません(ポインタ変数 x3 そのものを初期化するものとみなされる).

  文字型の場合も,基本的には,上で述べた int 型に対する場合と同じです(ただし,{} は無くても良い).04 行目では,15 字入る領域が確保され,最初の 10 文字に "test data\0" が保存されます.05 行目では,同じように 10 文字が記憶されますが,領域として 10 文字分しか確保されません.

  すべての要素を 0 で初期設定したい場合だけは,06 行目のような表現が許されます.初期設定したい値が 0 でないときは,01 行目のように,最初の要素だけがその値に初期設定されます.また,C++11 の場合は,08,09 行目のように,初期化子リストを使用した代入演算子を記述しない表現も可能です.

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

(プログラム)
/****************************/
/* 配列とポインタ           */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

int main()
{
/*
     初期設定された値と確保された領域のサイズ
*/
	int i1;
	int x2[6] = {1, 2, 3, 4, 5, 6};  /* 6つのデータ領域 */
	for (i1 = 0; i1 < 6; i1++)
		printf("%d ", x2[i1]);
	printf("(%dバイト)\n", sizeof(x2));
	int x3[]  = {1, 2, 3, 4};        /* 4つのデータ領域 */
	for (i1 = 0; i1 < 4; i1++)
		printf("%d ", x3[i1]);
	printf("(%dバイト)\n", sizeof(x3));
	char c1[15] = {"test data"};     /* 15個のデータ領域 */
	char c2[] = {"test data"};       /* 10個のデータ領域 */
	printf("%s (%dバイト)\n", c1, sizeof(c1));
	printf("%s (%dバイト)\n", c2, sizeof(c2));
/*
     要素の参照と変更
*/
	int *x1 = x2;   // x1 = &x2[0]の意味
	x1[1]   = -1;
	*(x1+2) = -2;
	x2[3]   = -3;
	*(x2+4) = -4;
	for (i1 = 0; i1 < 6; i1++)
		printf("%d ", x1[i1]);
	printf("\n");
	for (i1 = 0; i1 < 6; i1++)
		printf("%d ", x2[i1]);
	printf("\n");

	char *c3 = c2;
	c3[6]    = '\0';
	printf("%s (%dバイト)\n", c2, sizeof(c2));
	printf("%s (%dバイト)\n", c3, sizeof(c3));

	return 0;
}
			
16,20 行目

  int 型配列のために確保された領域の大きさを出力しています.int 型は 4 バイトであるため,それぞれ,24 および 16 が出力されます.

23,24 行目

  宣言および初期設定の方法により sizeof の出力が異なることに注意してください.

28 行目

  配列 x2 の先頭アドレスを,ポインタ変数 x1 に代入しています.このようにすることによって,29 から 32 行目に見るように,x1 と x2 を全く同様に使用できます.また,先頭アドレスではなく,例えば「 x1 = &x2[2] 」とすることにより,x1 を x2 の後半 4 つのデータに対応する 4 個の要素を持った配列として使用することも可能です.

40 ~ 43 行目

  40 行目の代入によって,c2 と c3 は,同じ文字列を指すことになります.c3[6]( c2[6] )に NULL 文字( '\0' )を代入することによって,文字列の長さは 6 になります(配列の大きさは変わらない).

  プログラム例 6.4 を実行すると,以下のような結果が得られます.
	1 2 3 4 5 6 (24バイト)
	1 2 3 4 (16バイト)
	test data (15バイト)
	test data (10バイト)
	1 -1 -2 -3 -4 6
	1 -1 -2 -3 -4 6
	test d (10バイト)
	test d (4バイト)			

6.3 2 次元以上の配列とポインタ

  2 次元以上の配列に対しても,1 次元の場合と同様に,ポインタを介して参照等が可能です.例えば,2 次元配列の場合,以下のような宣言がなされていたとします.このとき,配列とポインタの関係は,2 行目のような初期設定を行った場合,概念的に,図 6.2 の左側のような関係になります.なお,C/C++ において,初期設定を行う場合,x[3][2] を x[][2] のように記述することもできます.同様に,3 次元以上の配列の場合も,一番左側の大きさだけは省略可能です.
int a[3][2];   // 初期設定を行わない場合
int x[3][2] = {{100, 200}, {300, 400}, {500, 600}};   // 以下のいずれの方法でも可
//int x[][2] = {{100, 200}, {300, 400}, {500, 600}};
//int x[3][2] {{100, 200}, {300, 400}, {500, 600}};   // C++11
//int x[][2] {{100, 200}, {300, 400}, {500, 600}};   // C++11		

  まず,実際にデータが記憶される場所として,x[0][0] から x[2][1] の連続した 6 個の領域がとられます.この例では,それらが,20 番地からの領域であるとしています.次節に示すようなメモリを動的に確保する場合は,配列の配列として処理されるため,必ずしも連続的な領域になる保証はありません.添え字が最も右側から変化していることに注意して下さい(この点は,3 次元以上の配列の場合も同様です).次に,x[0] から x[2] のポインタを要素とする配列があり,その各要素は,配列の各行の値が保存されている場所の先頭を指しています.変数 x は,このポインタ配列の先頭を指していることになります.そこで,
int **y = x;		
という記述により,x と y が同じ場所を指す( y が x[0] を指し,x[0][0] と y[0][0] 等が同じものになる)ようになりそうですが,残念ながらこの記述は許されません.しかし,以下に示すような記述を行えば,変数 x は,ポインタ変数 y (ポインタ配列に対するポインタ)と同等のものになり,例えば,x[1][1],y[1][1],及び,w[1][1] などは,すべて同じ場所を指すことになります(右図参照).
int *w[3];   // w は,int に対するポインタの配列
int **y = &(w[0]);   // int **y = w; でも良い
w[0]    = x[0];
w[1]    = x[1];
w[2]    = x[2];		
ただし,後に述べる new 演算子を使用して,配列 x を
int **x = new int *[3];
for (int i1 = 0; i1 < 3; i1++)
	x[i1] = new int[2];
	// 以下のようにすれば,上と同じ値で初期設置も可能
int **x = new int *[3];
x[0] = new int[2] {100, 200};
x[1] = new int[2] {300, 400};
x[2] = new int[2] {500, 600};		
のように宣言すれば,
int **y = x;		
という記述によって,x 及び y が全く同じ配列を指すことになります.

  配列に対し連続的な領域が確保されていますので( new 演算子を使用した場合は,その限りでは無い),図 6.2 の右に示すように,2 次元の配列を 1 次元の配列として処理することも可能です.例えば,
int *z = (int *)x;   // int *z = &x[0][0]; でも良い		
のような処理をすれば,z[3] と x[1][1] が同じ場所を指すことになります.一般に,x[i][j] と z[(i×k) + j] は,同じ場所を指すことになります.ただし,k は,配列変数 x の宣言における列の数です(上の例では 2 ).さらに,
int *z = x[1];
//int *z = &x[1][0]; でも OK		
のような処理をすれば,配列 x の 2 行目以降を要素数が 4 である 1 次元配列として扱うことができます.

  以下,多次元配列に対する初期化についてまとめておきます.2 次元配列の場合,列の数を必ず記述しなければならない点に注意してください.誤りと書いたものを除き,各宣言とも正しく初期設定されると共に,各行の右側に書かれたような領域が確保されます.
int x1[][] = {{1, 2}, {3, 4}, {5, 6}};   // 誤り
int x1[3][2] = {{1, 2}, {3, 4}, {5, 6}};   // 3行2列
int x1[3][2] {{1, 2}, {3, 4}, {5, 6}};   // 3行2列,C++11
int x2[][2] = {{1, 2}, {3, 4}};   // 2行2列
int x2[][2] {{1, 2}, {3, 4}};   // 2行2列,C++11
int x3[3][] = {{1, 2}, {3, 4}};   // 誤り
char c1[5][10] = {"zero", "one", "two", "three"};   // 5行10列
char c2[][10] = {"zero", "one", "two", "three"};   // 4行10列
char c4[5][] = {"zero", "one", "two", "three"};   // 誤り
		
(プログラム例 6.5 ) 2 次元配列,ポインタ,及び,初期化 ( A ~ G の部分を可能な限り一つの変数,定数,演算子等で,埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.)

(プログラム)
/****************************/
/* 2次元配列とポインタ      */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

int main()
{
	int x1[3][2] = {{1, 2}, {3, 4}, {5, 6}};          /* 3行2列 */
	int x2[][2] = {{1, 2}, {3, 4}};                   /* 2行2列 */
	char c1[5][10] = {"zero", "one", "two", "three"}; /* 5行10列 */
	char c2[][10] = {"zero", "one", "two", "three"};  /* 4行10列 */
	char *c3[] = {"zero", "one", "two", "three"};     /* 4つのポインタ */
	int *px, *px1;
	char *pc;
/*
	 初期設定された値と確保された領域のサイズ
*/
	printf("%d (%dバイト)\n", x1[1][1], sizeof(x1));
	printf("%d (%dバイト)\n", x2[1][1], sizeof(x2));
	printf("%s (%dバイト)\n", c1[3], sizeof(c1));
	printf("%s (%dバイト)\n", c2[3], sizeof(c2));
	printf("%s (%dバイト)\n", c3[3], sizeof(c3));
/*
	 要素の参照
*/
	px = (int *)x1;
	px1 = x1[2];
	pc = &(c1[2][0]);
	printf("%d %d %d %d\n", x1[2][1], px[5], *(px+5), px1[1]);
	printf("%c%c%c %s\n", c1[2][0], pc[1], *(pc+2), pc);

	return 0;
}
			
  このプログラムを実行すると,以下のような結果が得られます.その理由を十分理解してください.
4 (24バイト)
4 (16バイト)
three (50バイト)
three (40バイト)
6 6 6 6
two two		
  3 次元以上の配列の対しても,ポインタ配列が増えていく以外,2 次元配列と同様です.なお,初期設定等の際,要素数を省略できるのは,一番左側の要素数だけです.例として,
int x[4][3][2];   // 初期設定等で省略できるのは 4 という値だけ		
と宣言した場合,24 個の int 型のデータが入る連続した領域が確保され,その概念図は図 6.3 のようになります.

この場合,変数 x の最も右側の添え字から順に変化していく点に注意して下さい.また,ポインタの概念からすると,変数 x は,
int ***y;		
と宣言されたポインタ変数 y と同等のものになります.さらに,
int *z = (int *)x;   // int *z = &x[0][0][0];		
という記述により,3 次元配列を 1 次元配列として参照することも可能です.上で定義した 3 次元配列の場合,x[i][j][k] と z[((i*3)+j)*2+k] が同じ場所を指すことになります.

  2 次元配列の場合と同様,後に述べる new 演算子を使用して,配列 x を
int ***x = new int **[4];
for (int i1 = 0; i1 < 4; i1++) {
	x[i1] = new int *[3];
	for (int i2 = 0; i2 < 3; i2++)
		x[i1][i2] = new int[2];
}		
のように宣言すれば(連続領域とは限らない),
int ***y = x;   // x と同じ 3 次元配列
int **z  = x[1];   // x[1][][] に対応する 2 次元配列
int *w   = x[1][0];   // x[1][0][] に対応する 1 次元配列		
という記述だけで,配列全体,または,その一部である 2 次元配列や 1 次元配列を参照可能です.

(プログラム例 6.5.1 ) 3 次元配列とポインタ

/****************************/
/* 3次元配列とポインタ      */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

int main()
{
	int i1, x[4][3][2], *x1[4][3], **x2[4];
	for (i1 = 0; i1 < 4; i1++) {
		int i2;
		for (i2 = 0; i2 < 3; i2++)
			x1[i1][i2] = x[i1][i2];
	}
	for (i1 = 0; i1 < 4; i1++)
		x2[i1] = x1[i1];
	int **x3 = x2[2];   // x[2][][] を 2 次元配列として処理
	int ***y = &x2[0];   // int ***y = x2;,x と同じ 3 次元配列
	x[2][1][0] = 100;
	printf("%d %d %d\n", x[2][1][0], y[2][1][0], x3[1][0]);   // 全て100
	int *z           = (int *)x;   // int *z = &x[0][0][0];
	z[((2*3)+1)*2+0] = 1000;   // 1 次元配列として処理
	printf("%d %d %d %d\n", x[2][1][0], y[2][1][0], x3[1][0], z[((2*3)+1)*2+0]);   // 全て1000

	return 0;
}

/* 以下,new 演算子を使用した場合

#include <stdio.h>

int main()
{
	int ***x = new int **[4];
	for (int i1 = 0; i1 < 4; i1++) {
		x[i1] = new int *[3];
		for (int i2 = 0; i2 < 3; i2++)
			x[i1][i2] = new int [2];
	}
	int **x3   = x[2];   // x[2][][] を 2 次元配列として処理
	int ***y   = x;   // x と同じ 3 次元配列
	x[2][1][0] = 100;
	printf("%d %d %d\n", x[2][1][0], y[2][1][0], x3[1][0]);   // 全て100
	int *z = x[2][1];   // x[2][1][] を 1 次元配列として処理
	z[0]   = 1000;
	printf("%d %d %d %d\n", x[2][1][0], y[2][1][0], x3[1][0], z[0]);   // 全て1000

	return 0;
}
*/
		

6.4 メモリの動的確保

  以下のプログラムに示すように,複数のデータを記憶するために配列を使用しても,以下に述べる標準関数 malloc や new 演算子を使用しても,各要素の参照方法はほとんど同じです.ただし,配列のように連続した領域が確保されるとは限りません.また,確保可能な領域の大きさは異なってくる可能性があります(コンパイラやコンピュータによって異なる可能性がある).例えば,以下に示すプログラムの断片において,配列を使用する場合は正しく動作せず,malloc や new 演算子を使用する場合は正しく動作します.これは,malloc や new 演算子を使用した場合,その領域が,プログラム実行時に任意に確保や解放を繰り返すことができるメモリ領域(ヒープ領域,ヒープメモリ,heap memory )に確保されるからです.

//	int x[5000000];   // 実行できない
//	int *x = (int *)malloc(5000000 * sizeof(int));   // 実行可
	int *x = new int [5000000];   // 実行可
	x[4999999] = 100;
	printf("%d\n", x[4999999]);		

6.4.1 malloc と free

  クラスの人数が 50 人であるとします.各人の国語,数学,及び英語の試験の点を処理するため,以下のような配列変数を定義したとします.
int a[50][3];		
クラスの人数や対象とする科目数が減少するような場合は特に問題はありませんが,増加する場合は,同じプログラムで処理できなくなります.

  このような場合に対処する 1 つの方法は,printf 等のようにシステムが所有している関数 callocmalloc を利用することです.これらの関数を利用することにより,各行毎に異なった型,サイズの配列を定義したり,入力データ等によって配列のサイズを動的に変化させたりすることが可能になります.また,確保したメモリがプログラム実行の進展に従い不足したような場合は,realloc 関数によってメモリ領域を変更することもできます.

  なお,これらの関数を使用して確保されたメモリは,プログラムが終了するまで存在します.確保されたメモリを開放したい場合は,free 関数を使用する必要があります.ただし,領域を開放してもその領域をプログラムが有効に利用してくれるとは限りません.なぜなら,C/C++ においては,ガーベッジコレクションを自動的には行ってくれないからです.したがって,頻繁に領域の確保と解放を繰り返すと,未使用の領域がふくれあがり,頻繁にスワッピングが起こったり,または,メモリが不足するような状態が発生します( realloc の場合も同様).できるだけ,プログラムの開始時に領域をまとめて確保し,プログラム終了時に解放されるようなプログラムを書いた方が安全だと思います.

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

  次のプログラムは,入力されたデータ数に応じた配列を確保し,入力されたデータをその配列に保存し,それらの和を求めています.

(プログラム)
/**********************************/
/* メモリの動的確保(1次元配列) */
/*      coded by Y.Suganuma       */
/**********************************/
#include <stdio.h>
#include <stdlib.h>

int main()
{
/*
		  データの数
*/
	int n;
	printf("データの数は? ");
	scanf("%d", &n);
/*
		  領域の確保
*/
	int *x = (int *)malloc(n * sizeof(int));
/*
		  データの入力と和の計算
*/
	int i1, sum = 0;
	for (i1 = 0; i1 < n; i1++) {
		printf("   %d 番目のデータを入力してください ", i1+1);
		scanf("%d", &x[i1]);
		sum += x[i1];
	}

	printf("和 = %d\n", sum);
/*
		  領域の開放
*/
	free(x);

	return 0;
}
			

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

  このプログラムでは,n 行 m 列の 2 次元配列を利用し,その中に n クラスにおける各学生の点数を入力しています.各クラスの学生数は m 人とし,クラス毎に異なっても構いません.また,各クラスごとの平均も計算し,配列に入れています.

(プログラム)
/**********************************/
/* メモリの動的確保(2次元配列) */
/*      coded by Y.Suganuma       */
/**********************************/
#include <stdio.h>
#include <stdlib.h>

int main()
{
/*
		  データの数
*/
	int m, n;
	printf("クラスの人数は? ");
	scanf("%d", &n);
	printf("科目の数は? ");
	scanf("%d", &m);
/*
		  領域の確保
*/
	int **ten = (int **)malloc(n * sizeof(int *));
	int *mean = (int *)calloc(m, sizeof(int));
/*
		  データの入力と和の計算
*/
	int i1;
	for (i1 = 0; i1 < n; i1++) {
		ten[i1] = (int *)calloc(m, sizeof(int));
		printf("%d 番目の学生\n", i1+1);
		int i2;
		for (i2 = 0; i2 < m; i2++) {
			printf("   %d 番目の科目の点数は? ", i2+1);
			scanf("%d", &ten[i1][i2]);
			mean[i2] += ten[i1][i2];
		}
	}
/*
		  平均の出力
*/
	printf("各科目の平均点は以下の通りです\n");
	for (i1 = 0; i1 < m; i1++)
		printf(" %d", mean[i1]/n);
	printf("\n");
/*
		  領域の開放
*/
	free(ten);
	free(mean);

	return 0;
}
			

6.4.2 new と delete(C++)

  C++ では,new 演算子及び delete 演算子を使用して,malloc や free 関数と同じように,メモリの動的確保及び解放を行うことが可能です.

  new 演算子は,データ型を与えると,例えば,
int *pi = new int;   // 2 で初期設定したい場合は,new int (2);		
のように,その型のデータを記憶するのに必要な大きさのメモリを確保し,そのメモリ領域のアドレスを返します.指定した大きさのメモリを確保できないときは,0 を返します.コメントに示すように,初期設定も可能です.

  delete 演算子は,new 演算子によって確保されたメモリを解放します.意図的に解放しない限り,確保されたメモリはプログラム終了時まで存在しますので,必要な場合は,例えば,
delete pi;		
のように,delete 演算子によって解放して下さい.また,new によって配列を確保した場合は,次のような delete の使用方法をして下さい.ガーベッジコレクションが行われない点は malloc などと同じですので,前節で述べましたように,確保と解放には十分注意してください.
double *dp = new double [10];  // 10個のdouble型データを記憶
                                 // 10の部分は変数でも構わない
  ・・・・・
delete [] dp;
int *x = new int [5] {1, 2, 3, 4, 5};   // 初期設定も可能C++11
int *x = new int [5] = {1, 2, 3, 4, 5};   // error
int *x = new int [] {1, 2, 3, 4, 5};   // error		
  2 次元以上の配列も同様にして確保できます.例えば,2 行 3 列(行や列の数は変数でも構わない)の配列を確保するためには,以下のように記述します.この結果,概念的には,右図に示すような領域が確保されます.
double** pd = new double* [2];
for (i1 = 0; i1 < 2; i1++)
	pd[i1] = new double [3];
		// 初期設定を行う場合C++11
double** pd = new double* [2];
pd[0] = new double [3] {1, 2, 3};
pd[1] = new double [3] {4, 5, 6};		
また,解放するときは,以下のようになります.解放する順番に注意してください.
for (i1 = 0; i1 < 2; i1++)
	delete [] pd[i1];
delete [] pd;		
  なお,realloc 関数に対応するような演算子はありません.new と delete 算子の詳細な使用方法については,プログラム例を参考にして下さい.

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

  プログラム例 6.6 を new と delete を使用して書いた例です.

(プログラム)
/**************************************/
/* メモリの動的確保(1次元配列,new) */
/*      coded by Y.Suganuma           */
/**************************************/
#include <stdio.h>

int main()
{
/*
		  データの数
*/
	int n;
	printf("データの数は? ");
	scanf("%d", &n);
/*
		  領域の確保
*/
	int *x = new int [n];
/*
		  データの入力と和の計算
*/
	int sum = 0;
	for (int i1 = 0; i1 < n; i1++) {
		printf("   %d 番目のデータを入力してください ", i1+1);
		scanf("%d", &x[i1]);
		sum += x[i1];
	}

	printf("和 = %d\n", sum);
/*
		  領域の開放
*/
	delete [] x;

	return 0;
}
			

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

  プログラム例 6.7 を new と delete を使用して書いた例です.

(プログラム)
/**************************************/
/* メモリの動的確保(2次元配列,new) */
/*      coded by Y.Suganuma           */
/**************************************/
#include <stdio.h>

int main()
{
/*
		  データの数
*/
	int m, n;
	printf("クラスの人数は? ");
	scanf("%d", &n);
	printf("科目の数は? ");
	scanf("%d", &m);
/*
		  領域の確保
*/
	int **ten = new int * [n];
	int *mean = new int [m];

	for (int i1 = 0; i1 < m; i1++)
		mean[i1] = 0;
/*
		  データの入力と和の計算
*/
	for (int i1 = 0; i1 < n; i1++) {
		ten[i1] = new int [m];   // 学生毎に科目数を変えることも可
		printf("%d 番目の学生\n", i1+1);
		for (int i2 = 0; i2 < m; i2++) {
			printf("   %d 番目の科目の点数は? ", i2+1);
			scanf("%d", &ten[i1][i2]);
			mean[i2] += ten[i1][i2];
		}
	}
/*
		  平均の出力
*/
	printf("各科目の平均点は以下の通りです\n");
	for (int i1 = 0; i1 < m; i1++)
		printf(" %d", mean[i1]/n);
	printf("\n");
/*
		  領域の開放
*/
	for (int i1 = 0; i1 < n; i1++)
		delete [] ten[i1];
	delete [] ten;
	delete [] mean;

	return 0;
}
			

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

  以下に示す例においては,new 演算子と代入・初期化との関係を,様々な型の変数に対して行っています.クラスに関する説明がまだですので,その部分に関しては,クラスの説明が終了した後,再度見直してみてください.

001	/****************************/
002	/* new 演算子と代入・初期化 */
003	/*      coded by Y.Suganuma */
004	/****************************/
005	#include <stdio.h>
006	#include <vector>
007	
008	using namespace std;
009	
010	/*****************/
011	/* クラスComplex */
012	/*****************/
013	class Complex
014	{
015		public:
016			int x, y[1], *z;
017			Complex ()
018			{
019				x    = 0;
020				y[0] = 0;
021				z    = new int [1];
022				z[0] = 0;
023			}
024	};
025	
026	/********/
027	/* main */
028	/********/
029	int main()
030	{
031						// 単純変数
032		printf("***単純変数***\n");
033		int a1 = 0;
034		int a2 = a1;
035		a2     = 1234;
036		printf("   a1 %d\n", a1);
037		printf("   a2 %d\n", a2);
038		printf("***単純変数(new)***\n");
039		int *p1 = new int(0);
040		int *p4 = p1;
041		*p4     = 1234;   // p1 が示す値も 1234 になる
042		printf("   p1 %d\n", *p1);
043		printf("   p4 %d\n", *p4);
044						// 複数データ(配列と同等)
045		printf("***配列***\n");
046		int b1[4] = {1, 2, 3, 4};
047		int b2[4];
048		for (int i1 = 0; i1 < 4; i1++)
049			b2[i1] = b1[i1];
050		b2[2] = 200;
051		printf("   b1");
052		for (int i1 = 0; i1 < 4; i1++)
053			printf(" %d", b1[i1]);
054		printf("\n   b2");
055		for (int i1 = 0; i1 < 4; i1++)
056			printf(" %d", b2[i1]);
057		printf("\n***配列(new)***\n");
058		int *p2 = new int [4];
059		for (int i1 = 0; i1 < 4; i1++)
060			p2[i1] = i1 + 1;
061		int *p5 = p2;   // p5 = &p2[0] と同じ
062		p5[2]   = 200;   // *(p5+2) = 200; と同じ
063		printf("   p2");
064		for (int i1 = 0; i1 < 4; i1++)
065			printf(" %d", p2[i1]);
066		printf("\n   p5");
067		for (int i1 = 0; i1 < 4; i1++)
068			printf(" %d", p5[i1]);
069						// クラスのオブジェクト
070		printf("\n***クラスのオブジェクト***\n");
071		Complex c1;
072		Complex c2 = c1;
073		c2.x    = 10;
074		c2.y[0] = 20;
075		c2.z[0] = 30;
076		printf("   c1 x %d y[0] %d z[0] %d\n", c1.x, c1.y[0], c1.z[0]);
077		printf("   c2 x %d y[0] %d z[0] %d\n", c2.x, c2.y[0], c2.z[0]);
078		printf("***クラスのオブジェクト(new)***\n");
079		Complex *p3 = new Complex();
080		Complex *p6 = p3;
081		p6->x    = 10;
082		p6->y[0] = 20;
083		p6->z[0] = 30;
084		printf("   p3 x %d y[0] %d z[0] %d\n", p3->x, p3->y[0], p3->z[0]);   // (*p3).x,・・・ でも可
085		printf("   p6 x %d y[0] %d z[0] %d\n", p6->x, p6->y[0], p6->z[0]);   // (*p6).x,・・・ でも可
086						// STL の vector
087		printf("***STL の vector***\n");
088		vector <int> d1(4, 0);
089		vector <int> d2 = d1;
090		d2[1] = 10;
091		printf("   d1");
092		for (int i1 = 0; i1 < 4; i1++)
093			printf(" %d", d1[i1]);
094		printf("\n   d2");
095		for (int i1 = 0; i1 < 4; i1++)
096			printf(" %d", d2[i1]);
097		printf("\n***STL の vector(new)***\n");
098		vector <int> *p7 = new vector <int> (4, 0);
099		vector <int> *p8 = p7;
100		(*p7)[1] = 10;
101		printf("   p7");
102		for (int i1 = 0; i1 < 4; i1++)
103			printf(" %d", (*p7)[i1]);
104		printf("\n   p8");
105		for (int i1 = 0; i1 < 4; i1++)
106			printf(" %d", (*p8)[i1]);
107		printf("\n");
108						// 領域の開放
109		delete p1;
110		delete [] p2;
111		delete p3;
112		delete p7;
113	
114		return 0;
115	}
		
013 行目~ 024 行目

  クラスの定義です.ここでは,int 型変数 x,int 型の 1 次元配列 y,new 演算子を使用した int 型の 1 次元配列 z から構成される新しい Complex 型の変数が定義されたという程度で理解しておいて下さい.

032 行目~ 037 行目

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

  032 行目~ 037 行目とは異なり,p1,p4 はポインタとして定義してあります.039 行目において,int 型のデータが入る領域が確保され( 0 で初期設定),そのアドレス(右図においては 20 番地)が変数 p1 に代入されます.「 * 」は,変数 p1 が,int 型のデータが入る領域を指すアドレスが記憶される変数(ポインタ)であることを表しています.040 行目において,p1 の値がコピーされて p4 に代入されますが,p1 はアドレスですので p1 に記憶されているアドレスが p4 に代入されます.その結果,p1 と p4 は,全く同じ領域を指すことになります(右図参照).

  041 行目は,変数(ポインタ) p4 が指すアドレスに 1234 を代入するという意味になります.p1 の値と p4 の値は等しいので,この記述と「 *p1 = 1234; 」は同じ結果になり,いずれの場合も,確保した領域に値 1234 が記憶されます(下に示す出力結果参照).
	***単純変数(new)***
	   p1 1234
	   p4 1234			
045 行目~ 056 行目

  046 行目において,4 個の int 型データからなる配列 b1 を定義し,その初期設定を行っています.047 行目~ 049 行目では,その配列を別の配列 b2 にコピーしています.「 b2 = b1 」という処理が不可能ですので,b1 の各要素を対応する b2 の各要素に代入しています.050 行目において,b2 の 3 番目の要素の値を変更していますが,単純変数の場合と同様,b1 と b2 は異なる変数ですので,下に示す出力結果から明らかなように,b1 の各要素は,その影響を受けません.
	***配列***
	   b1 1 2 3 4
	   b2 1 2 200 4			
057 行目~ 068 行目

  058 行目~ 060 行目において,4 個分の int 型データが入る領域が確保し,その先頭アドレス(下の図では 20 番地)を変数 p2 に代入し,初期設定を行っています(下の左図).061 行目において,p2 の値のコピーが p5 に代入されますので,p2 と p5 は同じ場所を指すことになります(下の右図).そのため,062 行目のように,p5[2] の値を変更すると p2[2] の値も変化します(図の下に示す出力結果参照).

	***配列(new)***
	   p2 1 2 200 4
	   p5 1 2 200 4			
070 行目~ 077 行目

  071 行目では,Complex クラスのインスタンス(オブジェクト) c1 を生成し,クラス内の変数 x,y[0],z[0] を,すべて,0 で初期化しています( 017 行目~ 023 行目).072 行目において,Complex クラスのインスタンス(オブジェクト) c2 を生成し,c1 を代入( c1 で初期化)しています.基本的に,c1 の値のコピーが c2 に代入されるだけですので,c2 の値を変更( 073 行目~ 075 行目)しても,変数 c1 の値は変化しないはずです.しかし,下に示す出力結果を見てください.c1 における z[0] の値も変化しています.これは,代入時のコピーによって,変数 z に記憶されているアドレスだけがコピーされ,そのアドレスが指す場所は考慮されないため,c1 と c2 の z は,同じデータを指すことになるからです.
***クラスのオブジェクト***
   c1 x 0 y[0] 0 z[0] 30
   c2 x 10 y[0] 20 z[0] 30			

078 行目~ 085 行目

  Complex クラスのインスタンス(オブジェクト)を記憶できる領域が確保され,そのアドレスが変数 p3 に代入されます( 079 行目).080 行目によって,p3 と p6 が同じ場所を指すことになるため,片方の変数と介して値を変更すれば( 081 行目~ 083 行目),他の変数の値も変化します(下に示す出力結果参照).
***クラスのオブジェクト(new)***
   p3 x 10 y[0] 20 z[0] 30
   p6 x 10 y[0] 20 z[0] 30			
087 行目~ 107 行目

  STL の vector クラスを扱った例です.基本的に,Complex クラスのオブジェクトを扱った場合と同じような結果が得られます.
***STL の vector***
   d1 0 0 0 0
   d2 0 10 0 0
***STL の vector(new)***
   p7 1 10 1 1
   p8 1 10 1 1			
109 行目~ 112 行目

  確保した領域を必要としなくなった場合は,delete 演算子によって,確保されたメモリを解放してやる必要があります.ただし,C/C++ においては,ガーベッジコレクションが行われないため,頻繁に大きなサイズの new や delete を繰り返すと,残されたゴミのためメモリが圧迫されることになります.

演習問題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 人(入力,クラス毎に異なる)の生徒がいるものとする.全員に対しある試験を実施したとき,各人の名前,点数を動的確保した配列に保存した後,学年平均点以下の人の名前とその点数を出力するプログラムを書け.

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