題名はポインタ型になっていますが、安心して下さい。 ポインタ型自体についての説明はもう終わりました。 先ず、説明を伸ばし伸ばしにしてきた定数型についてみる事にします。 その次に、配列について述べます。


定数

プログラムには、変数という物があれば、定数という物もあります。 と言っても、C 言語の中では定数は「定数型の変数」の事です。 定数型というのは、普通の型に「const」という型修飾子を指定した物のことです。 例で見てみることにします。

const int N=1000; // ☆

for(int i=0;i<N;i++){ // (1) OK
	...
}

N=100; // (2) ERROR
N++;   // (2) ERROR

int n=N; // (3) OK

定数型は、プログラムを実行している間中、値が変化しない様な変数の型に使用します。 これには、変更しないはずの値を間違えて変更してしまうと言うプログラムミスを防ぐ効果があります。 また、「値が変化しない」という知識を用いてコンパイラがコードを最適化する事の助けにもなります。

次の例として const なポインタを見ることにします。

const int* p=NULL;

p=new int; // (4) OK
*p=100; // (5) ERROR

上の、(4) は可能な操作です。 p は定数ポインタなのに何故? と思う方がいるでしょう。 実は、p は int 型への定数ポインタではないのです。 「const int 型への」ポインタなのです。 証拠に、*p が const int 型の間接参照なので、 (5) はコンパイルエラーになります。

定数ポインタ (constant pointer) を宣言するには、以下の様にしなければなりません。

int* const p=...;

p=new int; // (6) ERROR
*p=1234; // (7) OK

上の場合は、p は int への定数ポインタ (* const) なので、(6) の様な代入は出来なくなります。 逆に *p は単なる int の間接参照なので、値を代入することが可能になります。

更に更に、定数への定数ポインタ const int* const も可能です。

const の表記

さてさて、実は「int const *」等という表記も可能です。 実はこれは「const int*」と同じ意味で、「int *const」ではありません。 何でか考える前に、const 修飾子の文法的な振る舞いについて覚えてしまえばこんなのは簡単です。

<const の結合則>
  1. 出来るだけ左側にある型にくっつきます。
  2. 左側になにもない時に限って、右側の型にくっつきます。

この規則を知っていれば、次の様な型も恐れるに足りないでしょう。

int const v1;
const double& r1;
int * const * * const ppp;

/* 頭の中で
int|* const|*|* const ppp;
と区切ることが出来れば大丈夫
*/

答えというか日本語にすると、v1 は "定数な int" です。 r1 は "定数な double への参照" です。 ppp は "int への定ポインタへのポインタへの定ポインタ" です (日本語にすると訳が分からなくなります)。 実際に ppp の様な型は殆ど出てきませんが、int* const* const 位なら出てくるかも知れません。 そう言う時は、全体を見ては行けません。最後の "* const" の部分だけを見る様にしましょう。 前の方に付いているごにょごにょは、"* const" のポイント先の型を説明しているに過ぎないのですから。

使用例

(良い例ではないですが…。クラスインスタンスの参照渡しまで進んだら、有効な例を見せることが出来ましょう)

#include <iostream>

const double& test(){
	static double svar=1000;
	svar/=2;
	return svar;
}

int main(){
	const double& r=test();
	
	std::cout<<r<<std::endl;
	
	test();test();
	
	std::cout<<r<<std::endl;
	
	test();
	
	std::cout<<r<<std::endl;
	
	return 0;
}

例えば、上の様にすれば、 main() の中から test() の中の静的ローカル変数に制限付きで触れる様になります。 つまり、値の変更は許さずに、値を覗き見ることは許すという事が可能になります。

キャスト

const の取り外しも型変換に当たります。

暗黙的変換

暗黙的に変換可能な物は以下の通りです。

値には const も非 const もありませんので、自由に行き来が可能です。 但し、値の場合にはメモリ上で値のコピーが生成します。 また、ポインタや参照型に const を追加するのも、「代入の操作を制限するだけ」なので問題ありません。 ポインタや参照の場合には、ポインタ・参照自体のコピーは発生しますが、 その指し示している先のデータのコピーは発生しません。

const int f();

int main(){
	int a       =f();// 暗黙変換
	const int b =a;  // 暗黙変換
	
	int p=new int;
	const int* q=p;  // 暗黙変換

	delete p;
	return 0;
}

明示的変換

変換しようと思ったら、明示的に変換しなければならないのは以下の変換です。

const 型へのポインタや参照というのは、「変更しないでね」という意味を込めたポインタ/参照です。 それを、変更できるようにしようと思ったら、特別な事情か配慮がなければなりませんので、 明示的変換を要求されているのです。

const int* g();

int main(){
	int* p=(int*)g(); // 明示変換
	
	return 0;
}

また、「特に const 等の型修飾子の取り外しを行います」と言うことを明示して変換を行う場合には const_cast<変換後の型>(変換前の値) を使用します。 これを使えば、「const を取り外すだけのつもりだったのに、 誤って const float* を double* に変換してしまった」などという事態を避けることが出来ます。

※ 但し const_cast は const 型への参照やポインタの場合にしか使用出来ません。 つまり、const 型の値自体に対するキャストは const_cast ではないのです。 (値に対して const の取り外しをするという事は値自体のコピーを意味しますので、 const を付け外しする以上の意味がある為です。)

const int* g();

int main(){
	int* p=const_cast<int*>(g()); // 明示変換
	
	return 0;
}

配列

配列は、ポインタと深い関係があります。 配列型自体はポインタとは関係がないのですが、配列に対する操作は全てポインタとして行われるのです。

取り敢えず、型がどうしたとか難しいことは考えずに、配列の宣言と基本的な使い方に付いてみることにしましょう。

配列の宣言・初期化

配列は、同じ型のデータを複数並べた物のことです。その宣言は以下の様になります。

int x[100];                // (1a) 配列の宣言

double a=3.14;
double y[4]
	={1.0, a, 2.0, a-1};   // (1b) 初期化子
int z[]={3,4,5};           // (1c) 要素数の省略
int w[5]={1.2, 3.0};       // (1d) 初期化子の要素の省略

配列の使用

int x[100];

x[3]=12345; // (2a) 代入
x[99]=11;   // (2a) 代入
int v=x[1]; // (2a) 取得

x[-1]=3;    // (2b) 危険
x[100]=5;   // (2b) 危険

// (2c)
for(int i=0;i<100;i++){
	x[i]=i*i;
}

int y[100];
y=x;        // (2d) エラー

多次元配列

多次元の配列を宣言して使用することも可能です。

int y[3][4]; // (3a)
int y[][3]={ // (3b)
	{1,2,3},
	{1},
	{0,5},
	{}
};

y[2][3]=123;   // (3c)
int v=y[0][0]; // (3c)

配列型

どうも、話が難しくなりすぎた (説明力不足) 様なので、この項は補足に引っ越しました。 実際の所、配列へのポインタ/参照を扱わない限り、この内容は知っている必要は余りありません。

/* 具体的には、"配列型"・"配列とポインタの関係" について述べています。補足を読むならポインタに結構慣れていた方が良いと思います。 */

補足 配列型

補足で述べている内容を、物凄く短く纏めると、

int x[5];

x は配列型 (int[5] 型) なんだけれども、式中で出て来るとポインタ (int* 型) に変換される、という事です。 添え字に依る操作も、実はポインタとしての動作なのです。

配列渡し? ←実は不可能

関数の引数として、配列を取る事を考えたい事があります。 その場合には、下の何れかで指定します。

void func1(int* x);    // ☆1 
void func2(int x[10]); // ☆2 
void func3(int x[]);   // ☆3 

int main(){
	int x[10]={0,1,4,2,9};
	
	func1(x);
	func2(x);
	func3(x);
	
	return 0;
}
☆1 func1(int*)

配列は式中ではポインタに変わってしまうという事は、配列型の項で述べました。 関数に渡す時にもこれは適用されるので、int* で受け取る事が可能です。

さて、配列の内容へのポインタとして関数に渡されるので、これは値渡しではありません。 ポインタ渡しです。則ち、呼び出し元の配列の内容を変更する事が出来ます

#include <iostream>

void func(int* arr){
	arr[0]=100;
}

int main(){
	int a[]={1,2,3};
	std::cout<<a[0]<<','<<a[1]<<','<<a[2]<<std::endl;
	
	func(a);
	std::cout<<a[0]<<','<<a[1]<<','<<a[2]<<std::endl;
	
	return 0;
}
1,2,3
100,2,3

この事から、他の人が作った関数に配列を渡す時には、渡す配列が変更されてしまうのかどうかを気にしなくてはなりません。 変更されないと思っていたのに、勝手に変更されてしまったら溜まりませんので。 そこで、「配列を受け取るけれど値の変更はしない関数」を作る場合には、関数の仮引数に const を付けましょう。 そうすれば、関数に配列を渡す方としても配列が変更されないという事が分かって安心です。

void func(const int* arr){
	arr[0]=100; // ERROR: const なので、値の変更は出来ません。
}

補足 配列の値渡し

☆2 func2(int[5])

仮引数を配列型として宣言する事も可能です。 でも、これは雰囲気だけで、実際の所、ポインタとして宣言したのと全く同じです。 紛らわしいですが、配列型の値渡しではありません。

配列型は式中でポインタに変わるという事をさんざん述べていますが、 仮引数を配列型として宣言したからと言ってやはり配列がポインタに化ける事を防ぐ事は出来ません。 配列型の仮引数では、ポインタ型に化けた配列は受け取れません。 故に、「本当に配列型の仮引数」 (値渡し) は宣言出来ても使えないので、宣言出来ません。

その代わりに、配列型の様に仮引数を宣言すると、自動的にポインタ渡しで解釈される様になっています。 柔軟な機能? と言えば柔軟な機能なのかも知れませんが、これは、配列/ポインタに混乱を生ずる数ある要因の一つではないかと思います…。

void func(int x[5]){
	x[0]=3; // (1)
	
	x=new int; // (2)
	delete x;  // (2)
}

void func(int* x);   // (3) ERROR
void func(int x[7]); // (4) ERROR

int main(){
	int a[3]={1,2,3};
	func(a); // (5) OK
}
☆3 func(int x[]);

上で述べた様に、

void g1(int x[3]);
void g2(int x[5]);

と、色々な要素数を指定しても、意味が無くなってしまいます。 その為、初めから要素数を省略して指定する事が出来ます。

void g(int x[]);

勿論、要素数を指定した時と同様に、ポインタ渡しになります。

色々な主義主張があるのかも知れませんが、ポインタの形 (☆1) で記しておくのが混乱もないし普通だと思います。

要素数

さて、配列をポインタとして渡す以上は、要素数の情報を一緒に渡す事は出来ません。 要素数が初めから分かっている場合には問題がありませんが、 要素数が関数呼び出しの度に異なるかも知れない場合には、要素数の情報も一緒に渡す様にしましょう。

void f(int* arr,int n);

補足 配列の参照渡し (要素数の省略)

センチネル

要素数の情報を渡す方法として、他にセンチネル (番兵) という物があります。 センチネルとは、配列 (など) の一番最後に置いておく余分な要素の事で、「末端である事」を示す特別な値を持ちます。 勿論、普通の要素にも使われる値を番兵にしてしまうと、 普通の要素と番兵の区別が付きませんので、 番兵には普通の要素には決して使われない値を使用します。 実際のプログラムでは、-1 や NULL がよく番兵に志願しているのを見かけます。

// 良くある使い方

const int SENTINEL=-1; // 配列の意味ある要素が 0 以上の場合

int f(int* arr){
	for(int i=0;arr[i]!=SENTINEL;i++)
		arr[i]=...;
			
	...
	
}

int g(int* arr){
	while(*arr!=SENTINEL)*arr++=...;
}

int main(){
	int a[]={1,3,2,8,10,2,33,SENTINEL}; // 末端に番兵を配備
	
	f(a);
	g(a);
	
	return 0;
}

配列渡しの補足

補足 配列の値渡し

補足 配列の参照渡し

補足 多次元配列を渡す

文字列

既に、Hello World! の辺りから出てきている文字列ですが、 茲まで来て漸くその正体をひもとく事が出来る様になりました。 とは言っても、配列まで学んだ今、文字列はとても簡単です。

C 言語では、文字列はただの文字型の配列です。 具体的には、整数型 char の配列、または、文字型 wchar_t の配列で表現します。 但し、null 文字 '\0' をセンチネルとした「null 終端文字列」と言われる物を使用します。

文字列の表現の仕方は言語やシステムによって異なります。 C/C++ 言語では null 終端文字列をよく使います。 pascal や BASIC (一部?) では、一番初めに長さのデータがあってその後文字配列が続いたりします。 この場合は、長さのデータが直接存在するので、番兵 (終端文字) は必要ありません。

リテラル

文字列のリテラルは "" で囲んだ物です。

"Hello World!"
// これを初期化子で表現すれば
{'H','e','l','l','o',' ','W','o','r','l','d','!','\0'}
/* (最後に終端文字 '\0' がある事に注意) */

"ABC\0" ⇔ {'A','B','C','\0','\0'}
/* \0 を明示的に入れると \0 は二つになります。
 * でも、これを他の関数に渡すと
 * 3 文字の文字列と勘違いされます。
 */

wchar_t の文字列の場合は L" と " で囲みます。 L と " はくっつけて書かなければなりません。

L"今日は" ⇔ {L'今', L'日', L'は', L'\0'}

初期化

const char* pstr0="Aiueo";      // (1)
char pstr1[]     ="Kakikukeko"; // (2)
char pstr2[10]   ="Keisan";     // (3)

起稿 2008-09-30
© 2008-2009, K. Murase myoga.murase@gmail.com
inserted by FC2 system