C言語編 第37章 ポインタF(高度な使用法)

このエントリーをはてなブックマークに追加

この章の概要

この章の概要です。

const修飾子

前章で登場した自作関数の中に、次のようなものがありました。

/*
	メッセージを一覧表示 
	引数:
		messages:	表示するメッセージへのポインタを保持するポインタ配列。
		max:		messages に含まれる要素数。
*/
void printMessage(char** messages, size_t max)
{
	size_t i;
	
	for( i = 0; i < max; ++i ){
		puts( messages[i] );
	}
}

この printMessage関数は、渡された文字列を出力するだけの関数です。 関数へ渡した文字列(のポインタ)の中身は、関数内で書き換えることがありません。

関数の引数にポインタを渡すときはいつでもそうですが、関数内部から、ポインタ経由で呼び出し元の変数を書き換える可能性があります。 もちろん、そういう関数だと分かって使っている分には問題ありませんが、そうとは知らずに使ってしまったらどうでしょう? 例えば、

int num = 10;
func( &num );
printf( "%d\n", num );

このようなコードがあるとして、printf関数が出力する値は一体幾つになるでしょう。 これだと、func関数の中身を覗いてみないと分かりません。 もしかすると、func関数が内部で、渡されたポインタを経由して、変数num の値を書き換えているかも知れません。

そこで、書き換えを行わないことを明示するために、const修飾子を利用します。 具体的には、func関数の宣言を、

void func(const int* pNum);

このように書きます。 こうすると、func関数内から仮引数pNum を経由して、 その先にある変数を書き換えるという行為が、コンパイラによって禁止されます(つまり、コンパイルエラーになります)。 const修飾子には、「書き換え不可とする」という意味がある訳です。
以下は、実際に動作を確認できる完全なコードです。

#include <stdio.h>

void func(const int* pNum);

int main(void)
{
	int num = 10;
	func( &num );
	printf( "%d\n", num );

	return 0;
}

void func(const int* pNum)
{
	*pNum = 100;  /* ここでコンパイルエラーになる */
}


また、const修飾子付きのポインタ変数を、const修飾子の付いていないポインタ変数へ代入すると、大抵のコンパイラは警告を出します

#include <stdio.h>

int main(void)
{
	const char* str;
	char* str2;

	str = "abcde";    /* OK */
	str2 = str;       /* const が外されるような代入。多くの環境では警告 */
	str2 = "abcde";   /* OK */

	return 0;
}

コメントにあるように、「str2 = str;」の部分は警告を発するでしょう。

C++ ではコンパイルエラーになります。


冒頭の例に戻りますが、printMessage関数でも const修飾子を使えますし、使うべきです。

/*
	メッセージを一覧表示 
	引数:
		messages:	表示するメッセージへのポインタを保持するポインタ配列。
		max:		messages に含まれる要素数。
*/
void printMessage(const char** messages, size_t max)
{
	size_t i;
	
	for( i = 0; i < max; ++i ){
		puts( messages[i] );
	}
}

これで、printMessage関数に渡したポインタを経由して、指し示す先の変数が書き換えられないことが明示的になります。

この例と反対に、ポインタを渡して、そこに結果を格納してもらうような使い方(第33章)をする関数の場合には、 const修飾子を付けられないということになります。


先にポインタの例を説明しましたが、ポインタ以外の変数でも同様に const修飾子を利用できます。

const int num = 100;

この後、変数num に何らかの値を代入しようとすると、コンパイルエラーになります。

C言語の const は書き換え不能な変数という扱いであり、定数とは異なります。 一方、C++ の const は定数を意味しますから、C++プログラマは、両言語で意味が微妙に異なることに注意して下さい。 例えば、C言語の const な変数を、配列を宣言する際に要素数の指定に使うことができません。


実は、ポインタに対する const には 2通りあります。

const int* p1 = &num;
int* const p2 = &num;

p1 の方は「このポインタ変数を経由して、その先にある値を書き換えられない」という意味になります。
p2 の方は「このポインタ変数が保持しているアドレスを書き換えられない」という意味になります。

const修飾子を記述する位置が非常に分かりづらいことにも触れておかねばなりません。 先ほどの変数p1 は次のようにも書けます。

int const* p1 = &num;

そのため、「int などの型の名前の手前に書けば、指し示す先の値が書き換えられない」と覚えていると、他人のコードを読むときに失敗します。 そうではなく、「const が * よりも前方にあれば、指し示す先の値が書き換えられない。後方にあれば、ポインタ変数自体が書き換えられない」と覚えておく必要があります。

なお、この 2つの const の使い方を組み合わせて、次のように書くこともできます。

const int* const p3 = &num;

p3 は「このポインタ変数が保持しているアドレスも、その先にある値も、どちらも書き換えられない」という意味になります。

const は、バグの少ない安全なプログラムを書くために大きな助けになる優れた機能です。 const が使える場面では積極的に使うべきです。 実際、標準ライブラリ関数の中で、ポインタの受け渡しを行う関数は、積極的に const が使われています。

落とし穴として、次のようなコードがコンパイルできてしまう問題があるので注意して下さい。

const int num = 100;  /* 書き換えられないはず */
int* p = &num;
*p = 200;             /* でもこれはコンパイルエラーにならない */

コンパイルエラーにはならないものの、これはどんな結果を巻き起こすか不定となっています。 これは、2行目のところで、int*型変数を宣言する際、const を付けて、

const int* p = &num;

このように書き直すべきでしょう。

C++ ではこのように const を付けないと、コンパイル自体が通りません。

volatile修飾子

const修飾子と同じカテゴリに属するものなので、volatile修飾子も取り上げておきます。

volatile修飾子は、変数の値が、コンパイラが知りえない方法で書き換えられる可能性があるため、 最適化の対象としないように要請するものです。

最適化というのは、コンパイラがソースコードを最終成果物(Windows環境でのC言語プログラムであれば、.exeファイル等)を作り出す過程の中で、 実行効率を高めるために行っているもので、簡単に言えば「無駄を無くす作業」です。
例えば、ループの中でローカル変数に値を格納するが、その値はいつも同じ値であれば、ループを1回周るたびに代入し直す意味はありませんから、 これを定数で置き換えてしまうといった作業を指します。 もちろん、最適化の前後で意味が変わってしまうことはありません。

現実には、コンパイラのバグのため、誤った最適化が行われてしまうトラブルもあるにはあります。


ハードウェアに取り付けられた物理的なボタンによる入力等では、その入力を、特定のメモリアドレスにデータを書き込むような方法で実現することがあります。 これをメモリマップドI/O などと呼びますが、この場合がまさに、「コンパイラが知りえない方法で書き換えられる」という状況です。 使用者がいつどんなタイミングで入力を行い、メモリ上の値が書き変わるか分かりません。

int main(void)
{
	unsigned int* lcd = (unsigned int*)0x6000;  /* アドレスは適当 */
	
	
	*lcd = 1;
	
	/* 少し間を空ける */
	
	*lcd = 0;
	
	/* 少し間を空ける */
	
	*lcd = 1;

	return 0;
}

この例で、ポインタ変数lcd は、機械に取り付けられたランプを制御するためのメモリアドレスを保持しているとします。 ここに 1 を書き込むとランプが点灯し、0 になると消灯するとしましょう。 要するに、上のプログラムは、点灯と消灯とを繰り返すようにしたい訳です。

しかし、そういう事情を知らずにこのプログラムを見ると、結局最終的には lcd が指し示す先の値は 1 で終わるので、最適化によって、

int main(void)
{
	unsigned int* lcd = (unsigned int*)0x6000;  /* アドレスは適当 */
	
	
	*lcd = 1;

	return 0;
}

このようなコードにまで縮小できるように見えるでしょう。 しかしこんな最適化をされてしまったら、ランプは点灯しっ放しになってしまい、意図に反しています。 そのため、変数lcd の宣言時に volatile修飾子を付ける必要があるのです。

volatile unsigned int* lcd = (unsigned int*)0x6000;  /* アドレスは適当 */

volatile修飾子は、C言語の機能の中ではかなり特殊なものであり、まったく使う機会の巡ってこない人も多いと思われます。 一方で、機械制御のような分野では必須なものでもあります。

関数ポインタ

ポインタは、メモリアドレスを持つものであれば何でも指し示すことができます。 実は、関数そのものもメモリ上に存在していますから、関数を指し示すことも可能です。 これを、関数ポインタと呼びます。

関数ポインタ自体も変数に過ぎないので、まず宣言しなければなりません。

戻り値の型 (*関数ポインタの変数名) (引数のリスト);

ここで例えば、次のような関数が宣言されていたとします。

int func(const char* str);

この関数を指し示す関数ポインタは、次のように宣言できます。

int (*func_ptr)(const char*) = func;

最後の「= func」は初期値として、func関数のアドレスを渡しているということです。 もちろん、宣言だけしておいて、後から代入しても構いません。

int (*func_ptr)(const char*);
func_ptr = func;

1行目の宣言だけを見ると、ちょっと何が書いてあるんだかよく分からないような感じがしますが、 2行目の代入文を見ると、func_ptr が(関数ポインタの)変数名であることがはっきり読み取れると思います。
関数ポインタの最大の難関は、むしろこの読みづらい構文にあると言えるかも知れません。 そこで、typedef(第26章参照)を使用してすっきりさせるという方法がよく取られています。

typedef int (*func_ptr_t)(const char*);  /* 型名を定義 */
func_ptr_t func_ptr;                     /* 定義した型名を使って、変数宣言 */
func_ptr = func;                         /* 変数へ、関数のアドレスを代入 */

typedef のところでは相変わらず複雑な記述ですが、これはプログラムの先頭や、ヘッダファイルなどに1回記述すれば済みます。 関数ポインタを宣言する際には、typedef で定義された型名を使って、すっきりと記述できます。
関数ポインタの型を typedef で定義する場合、型名が謎めいた位置に記述されますが、これはこういうルールなので覚えてしまって下さい。

さて、ここまで来たらあとは、関数ポインタを経由して関数を呼び出すだけです。 これは非常に簡単です。

int ret = func_ptr( "abcde" );
int ret = (*func_ptr)( "abcde" );

この2行はどちらも同じ意味です。 前者の方法だと、関数ポインタ経由なのか、普通に関数を呼んでいるのか見分けはつきません。 後者の方法だと、関数ポインタ経由であることが明確になります。 しかしながら、明確にしなければならない理由は、ほとんど無いので、好みの問題と考えていいでしょう。

関数ポインタを使うと、引数と戻り値の型と個数が一致していれば、他の関数を指し示すように代入し直すこともできます。 これは状況に応じて、処理内容を変えるために利用できます。
そういう用途であれば、その時々で if文なり switch文なりで分岐させればいいだけのように聞こえますが、それは "ほぼ正しい" です。 それで良いのならそれで良いでしょう。 この考え方で書いたプログラムは次のようになります。

#include <stdio.h>

void mainProcForEasy(void);
void mainProcForNormal(void);
void mainProcForHard(void);

int main(void)
{
	char str[40];
	int level;


	puts( "難易度を選んで下さい。" );
	puts( "0〜2 で大きいほど難しくなります。" );
	fgets( str, sizeof(str), stdin );
	sscanf( str, "%d", &level );

	/* 難易度に応じたメインの処理を実行する */
	switch( level ){
		case 0:
			mainProcForEasy();
			break;
		case 1:
			mainProcForNormal();
			break;
		case 2:
			mainProcForHard();
			break;
		default:
			puts( "入力が正しくありません。" );
			return 0;
	}

	return 0;
}

void mainProcForEasy(void)
{
	puts( "簡単なモードで実行。" );
}

void mainProcForNormal(void)
{
	puts( "標準的な難易度のモードで実行。" );
}

void mainProcForHard(void)
{
	puts( "難しいモードで実行。" );
}

実行結果:

難易度を選んで下さい。
0〜2 で大きいほど難しくなります。
1
標準的な難易度のモードで実行。

何のプログラムか不明ですが、ともかく、標準入力から入力された難易度に応じて、処理を分けるものだと考えて下さい。 多分、mainProcXXXX という名前の関数の中では、問題が表示されてそれに答えるような処理があり、 難易度によって、出題される問題の難しさが変わるのでしょう。

この実装方法の問題の1つは、switch文(または if文の連続)で分岐を行う必要があるため、 後から、難易度の種類が増えると、忘れずに case句なり else句なりを増やさないといけないことです。 今回のように、分岐処理を書く場所が1カ所だけならまだいいのですが、 ソースファイルが複数あって、色々なところで同様の意味合いの分岐処理が書いてあると大変なのです。

次に同じプログラムを、関数ポインタを駆使して書いてみます。

#include <stdio.h>

#define SIZE_OF_ARRAY(array)	(sizeof(array)/sizeof(array[0]))

void mainProcForEasy(void);
void mainProcForNormal(void);
void mainProcForHard(void);

int main(void)
{
	typedef void (*mainProc_t)(void);  /* メイン処理の関数ポインタ型 */

	static const mainProc_t mainProcArray[] = {  /* メイン処理の関数テーブル */
		mainProcForEasy,
		mainProcForNormal,
		mainProcForHard,
	};

	char str[40];
	int level;


	puts( "難易度を選んで下さい。" );
	puts( "0〜2 で大きいほど難しくなります。" );
	fgets( str, sizeof(str), stdin );
	sscanf( str, "%d", &level );

	if( level < 0 || SIZE_OF_ARRAY(mainProcArray) <= level ){
		puts( "入力が正しくありません。" );
		return 0;
	}

	/* 難易度に応じたメインの処理を実行する */
	mainProcArray[level]();

	return 0;
}

void mainProcForEasy(void)
{
	puts( "簡単なモードで実行。" );
}

void mainProcForNormal(void)
{
	puts( "標準的な難易度のモードで実行。" );
}

void mainProcForHard(void)
{
	puts( "難しいモードで実行。" );
}

実行結果:

難易度を選んで下さい。
0〜2 で大きいほど難しくなります。
1
標準的な難易度のモードで実行。

こちらは、呼び出すべき関数を配列で管理してあり、難易度と添字を一致させてあります。 そのため、分岐構造は必要なくなります(エラーチェックのためには必要ですが)。
後から難易度が増えたら、配列の中身を変えて、新しい関数を作るだけで済みます。 つまり、変更すべき箇所が激減し、しかも見つけやすい場所にあります。

コールバック(qsort関数、bsearch関数)

標準関数の中には、関数ポインタを利用した関数も存在します。 ここではその中から、qsort関数(⇒リファレンス)と、 bsearch関数(⇒リファレンス)を取り上げます。

qsort関数は、ソート(整列)という操作を行う関数です。 ソートとは、複数のデータを、大きい値から小さい値の順序(降順)になるように並び変える、 あるいは反対に、小さい値から大きい値の順序(昇順)になるように並び変える操作のことです。

bsearch関数は、サーチ(探索)という操作を行う関数です。 サーチとは、複数のデータの中から、特定の値を見つけ出す操作のことです。

ソートもサーチも、様々な実装手段が存在していますが、qsort関数や bsearch関数は比較的高速に動作する実装が使用されています。 実装手段の詳細については割愛しますが、興味があれば、アルゴリズム関係の資料をあたってください。
ただ、bsearch関数に関しては、その実装手段の制約のため、対象となるデータ列が事前にソートされた状態でないと正しく動作しません。 もし対象のデータが既にソート済みであることが保証できるなら問題ありませんが、そうでないのなら、qsort関数と組み合わせて使うことになるでしょう。

まずは qsort関数の方ですが、この関数は次のように宣言されています。

void qsort(void* base, size_t count, size_t size, int (*compar)(const void* elem1, const void* elem2));

第1引数base は、ソート対象の配列のアドレスです。 第2引数count は、その配列に含まれる要素数を、 第3引数size は、要素1つ1つのサイズを指定します。 第4引数compar は、ソートの条件を指定するための関数のアドレス、すなわち関数ポインタです。

まずは実際に使ってみましょう。

#include <stdio.h>
#include <stdlib.h>

#define SIZE_OF_ARRAY(array)	(sizeof(array)/sizeof(array[0]))

void printTable(const int* array, size_t size);
int compareInt(const void* a, const void* b);

int main(void)
{
	int table[] = { 66, 85, 70, 92, 61, 89 };

	puts( "ソート前" );
	printTable( table, SIZE_OF_ARRAY(table) );

	qsort( table, SIZE_OF_ARRAY(table), sizeof(int), compareInt );

	puts( "ソート後" );
	printTable( table, SIZE_OF_ARRAY(table) );

	return 0;
}

/*
	int型配列の要素を出力。
*/
void printTable(const int* array, size_t size)
{
	size_t i;

	for( i = 0; i < size; ++i ){
		printf( "%d ", array[i] );
	}
	printf( "\n" );
}

/*
	int型による順序比較。

	引数:
		a:	比較する要素。
		b:	比較する要素。
	戻り値:
		a の方が小さいとき負数、
		b の方が小さいとき 0 より大きい値、
		a と b が同じときは 0、
		 が返される。
*/
int compareInt(const void* a, const void* b)
{
	int aNum = *(int*)a;
	int bNum = *(int*)b;

	if( aNum < bNum ){
		return -1;
	}
	else if( aNum > bNum ){
		return 1;
	}
	return 0;
}

実行結果:

ソート前
66 85 70 92 61 89
ソート後
61 66 70 85 89 92

compareInt関数が、qsort関数の第4引数に渡されるポインタが指し示す関数になります。 この関数は、引数で渡された 2つの値の大小関係を調べ、その結果を戻り値で返します。 コメントに書かれているように、 この比較関数は、第1引数の方が大きいなら正の値、第2引数の方が大きいなら負の値、両者が同じなら0を返すようにします。 この仕様はこれまでにも登場した strcmp関数(⇒リファレンス)と同様です。

この比較関数ですが、

int compareInt(const void* a, const void* b)
{
	return ( *(int*)a - *(int*)b );
}

このように1行で書くこともできますが、値が非常に小さい負数であったり、大きい正数であったりする場合、オーバーフローを起こす可能性があることに注意が必要になります。 また、単純にキャストが複雑で分かりづらいという意味もあって、 サンプルプログラムのように丁寧に書きました。

比較関数を指し示すポインタを qsort関数に渡すことによって、qsort関数はその内部から関数ポインタを経由して compareInt関数を呼び出すことができます。 qsort関数はソート作業を行うために、何度も 2つの値を比較する作業を必要としますが (この辺りはソートアルゴリズムというものを知っていないとイメージしづらいかも知れません)、 そのたびに、比較関数を呼び出す訳です。
このように、必要なタイミングで外部にある関数を呼び出すようなプログラミング上のテクニックを、コールバックと呼び、 コールバックされる関数を、コールバック関数と呼びます。

比較作業を qsort関数が内部で勝手に行わず、外部で関数を用意させる理由は、大きく2つあります。
1つには、対象となるデータの型が int型であるとは限らない点です。 対象が文字列かも知れないし、浮動小数点数かも知れないし、構造体かも知れません。 どのような型であっても扱えるように、比較関数の引数は void*型になっています。
もう1つの理由は、順序関係の定義がいつも同じとは限らないということです。 単純な話、昇順に並べたいのか、降順に並べたいのか、というだけでも比較関数を変えなければなりません。 先ほどのサンプルプログラムだと、昇順に並びますが、これを降順にしたければ比較関数を、

/*
	int型による順序比較。

	引数:
		a:	比較する要素。
		b:	比較する要素。
	戻り値:
		b の方が小さいとき負数、
		a の方が小さいとき 0 より大きい値、
		a と b が同じときは 0、
		 が返される。
*/
int compareInt(const void* a, const void* b)
{
	int aNum = *(int*)a;
	int bNum = *(int*)b;

	if( aNum < bNum ){
		return 1;
	}
	else if( aNum > bNum ){
		return -1;
	}
	return 0;
}

このように変更する必要があります。


次に、サーチを行う bsearch関数を使ってみましょう。 qsort関数の動作が理解できていれば、こちらも同じことです。

#include <stdio.h>
#include <stdlib.h>

#define SIZE_OF_ARRAY(array)	(sizeof(array)/sizeof(array[0]))

int compareInt(const void* a, const void* b);

int main(void)
{
	int table[] = { 66, 85, 70, 92, 61, 89 };
	char str[40];
	int target;
	int* search_result;


	/* bsearch関数を使う前には、対象データがソート済みでなければならないので、
	   事前に qsort関数でソートしておく 
	*/
	qsort( table, SIZE_OF_ARRAY(table), sizeof(int), compareInt );


	puts( "サーチする値を入力して下さい。" );
	fgets( str, sizeof(str), stdin );
	sscanf( str, "%d", &target );

	/* サーチ */
	search_result = bsearch( &target, table, SIZE_OF_ARRAY(table), sizeof(int), compareInt );
	if( search_result == NULL ){
		printf( "%d は存在しません。\n", target );
	}
	else{
		printf( "%d を発見しました。\n", target );
	}

	return 0;
}

/*
	int型による順序比較。

	引数:
		a:	比較する要素。
		b:	比較する要素。
	戻り値:
		a の方が小さいとき負数、
		b の方が小さいとき 0 より大きい値、
		a と b が同じときは 0、
		 が返される。
*/
int compareInt(const void* a, const void* b)
{
	int aNum = *(int*)a;
	int bNum = *(int*)b;

	if( aNum < bNum ){
		return -1;
	}
	else if( aNum > bNum ){
		return 1;
	}
	return 0;
}

実行結果:

サーチする値を入力して下さい。
70
70 を発見しました。

bsearch関数は次のように宣言されています。

void* bsearch(const void* key, void* base, size_t count, size_t size, int (*compar)(const void* key, const void* value));

第1引数は、探し出したい値を指すポインタです。 素直に値を渡せないのは嫌らしいところですが、これも、対象の型を特定できないので void*型で扱わざるを得ないことが理由です。
第2引数以降は、qsort関数と同様のものが並んでいます。 順番に、対象の配列のアドレス、配列の要素数、要素1個分のサイズ、比較関数のポインタです。
比較関数の第1引数には、bsearch関数の第1引数で渡された探索対象が、第2引数には比較対象へのポインタが渡されます。

サーチ作業を行った結果、その値が発見できた場合には、その要素へのポインタが返されます。 発見できなければ NULL が返されます。

前に書いたように、bsearch関数を使う際には、対象の配列がソート済みでなければならないことに注意して下さい。 これはソート済みであれば良いだけなので、必ずしも qsort関数とセットにする必要はなく、 自力でソートしてもいいですし、既にソートされていることが保証できるのなら何もする必要はありません。


文字列のソートやサーチを行う場合は、結構難解になってきます。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SIZE_OF_ARRAY(array)	(sizeof(array)/sizeof(array[0]))

int compareStringFor_qsort(const void* a, const void* b);
int compareStringFor_bsearch(const void* a, const void* b);

int main(void)
{
	char* table[] = {
		"Japan",
		"America",
		"Italy",
		"Thailand",
		"Mexico",
		"Russia"
	};
	char str[40];
	char target[40];
	char** search_result;


	/* bsearch関数を使う前には、対象データがソート済みでなければならないので、
	   事前に qsort関数でソートしておく 
	*/
	qsort( table, SIZE_OF_ARRAY(table), sizeof(char*), compareStringFor_qsort );


	puts( "サーチする文字列を入力して下さい。" );
	fgets( str, sizeof(str), stdin );
	sscanf( str, "%s", target );

	/* サーチ */
	search_result = bsearch( target, table, SIZE_OF_ARRAY(table), sizeof(char*), compareStringFor_bsearch );
	if( search_result == NULL ){
		printf( "%s は存在しません。\n", target );
	}
	else{
		printf( "%s を発見しました。\n", target );
	}

	return 0;
}

/*
	char*型による順序比較 (qsort用)

	引数:
		a:	比較する要素。
		b:	比較する要素。
	戻り値:
		a の方が小さいとき負数、
		b の方が小さいとき 0 より大きい値、
		a と b が同じときは 0、
		 が返される。
*/
int compareStringFor_qsort(const void* a, const void* b)
{
	char* s1 = *(char**)a;
	char* s2 = *(char**)b;

	return strcmp( s1, s2 );
}

/*
	char*型による順序比較 (bsearch用)

	引数:
		a:	探索キー。
		b:	比較する要素。
	戻り値:
		a の方が小さいとき負数、
		b の方が小さいとき 0 より大きい値、
		a と b が同じときは 0、
		 が返される。
*/
int compareStringFor_bsearch(const void* a, const void* b)
{
	char* s1 = (char*)a;
	char* s2 = *(char**)b;

	return strcmp( s1, s2 );
}

実行結果:

サーチする文字列を入力して下さい。
Italy
Italy を発見しました。

まず qsort関数の比較関数である compareStringFor_qsort関数を見て下さい。 引数は const void*型の状態で渡されますが、 これの正体は要素1つを指すポインタですから、今回の場合、char*型を指すポインタ、つまり char**型です。 このように、* の個数だけを当てにしていてはいけません。 void*型なのに、正体は char**型なのです。
char**型へのキャストののち、間接参照によって char*型にしてしまえば、あとは strcmp関数に引き渡してしまえます。

次に bsearch関数ですが、今度は比較関数が compareStringFor_bsearch関数になります。 qsort関数のときと同じ比較関数が使えない理由は、サーチしようとしている値、つまり bsearch関数の第1引数の型は char*型だからです。 先ほどの説明のように、要素1つを指すポインタは char**型ですが、サーチしたい型は char*型なので、ここに食い違いが生じています
そのため、第1引数の方は char*型にキャストし、第2引数の方は char**型にキャストしなければなりません。

bsearch関数の戻り値については、対象配列の要素へのポインタとして返されるので、今度の正体は char**型です。 受け取る変数の型はこれに合わせて、char**型にしなければなりません。


練習問題

問題@ 一般的に関数の宣言において、

void func(const MyStruct* s);

このような const修飾子の使い方をすることはありますが、

void func(MyStruct* const s);

このように書くことはあまりありません。 *演算子の後方に書くタイプの const修飾子が、関数の仮引数に使われることが少ない理由はなぜでしょう。

問題A 関数ポインタのところで登場した、難易度に応じて処理を分岐させるサンプルプログラムに、結果を出力するような関数を追加して下さい。 この関数は、引数で得点を受け取り、難易度ごとに異なる方法で合格・不合格を決定するものとします。 例えば、簡単なモードなら 60点で合格ですが、難しいと 80点必要という感じです。 得点は、mainProcXXXX関数が戻り値で返すようにするなど、元のプログラムを一部修正して構いません。
なお、関数ポインタを使う方法と、使わない方法の両方で、プログラムを作成して下さい。

問題B qsort関数や bsearch関数に渡す関数ポインタにおいて、指し示す先の関数の引数が void*型ではなく、const void*型である理由はなぜでしょうか。


解答ページはこちら

参考リンク

更新履歴

'2017/5/13 配列の要素数を求めるマクロを、他のページと同じ形の SIZE_OF_ARRAYマクロに統一。

'2013/2/19 誤字修正。

'2011/4/27 「const修飾子」にサンプルや補足を追加し、詳細さを増した。

'2010/2/6 新規作成。



前の章へ

次の章へ

C言語編のトップページへ

Programming Place Plus のトップページへ