C言語/中級者向けの話題

出典: フリー教科書『ウィキブックス(Wikibooks)』
ナビゲーションに移動 検索に移動

配列の文法についての制限[編集]

宣言時にしか、配列に一括で代入できない[編集]

C言語では、ある配列で、その全要素に一括して数値を代入する行為は、宣言のときにしか、出来ない。

宣言以外のときに代入したい場合には、面倒だが、各要素に一個ずつ中身を代入していくしかない。


構造体のビットフィールド[編集]

ビットフィールドとは、 1ビット以上のビットからなる、 構造体のメンバの一種である。 ビットフィールドを用いると、 1ビット以上のビットに名前をつけ、 その名前でアクセスできる。

ビットフィールドは、 構造体の型枠を宣言する時、 メンバとして次のように記述する。

データ型 メンバ名:サイズ;

データ型は、(signed) intまたはunsigned intを指定する。 (signed) intを指定すると、 上位ビットが符号として扱われる。 メンバ名とサイズとを「:(コロン)」で区切る。 サイズはそのメンバを何ビットで表すかを指定する。

//例 ビットフィールド
#include <stdio.h>

int main(void)
{
	struct sbits
	{
		unsigned char bit8:1;
		unsigned char bit7:1;
		unsigned char bit6:1;
		unsigned char bit5:1;
		unsigned char bit4:1;
		unsigned char bit3:1;
		unsigned char bit2:1;
		unsigned char bit1:1;
	}bits;

	bits.bit1=1;
	bits.bit2=0;
	bits.bit3=1;	
	bits.bit4=0;	
	bits.bit5=1;	
	bits.bit6=0;	
	bits.bit7=1;	
	bits.bit8=0;		
	
	printf("bitsのメンバの値は、%d %d %d %d %d %d %d %dです。\n",
		bits.bit1,bits.bit2,bits.bit3,bits.bit4,bits.bit5,bits.bit6,bits.bit7,bits.bit8);
	printf("bitsのサイズは、%dです。\n", sizeof(bits));
}

上の例では、 0か1かの値を持つ1ビットの、 8つのビットフィールドをもつ、 bits構造体を宣言し、 各ビットフィールドに値を代入し、 その値を表示している。 なお、unsigned char型をビットフィールドに使えるかは、コンパイラによる。



関数[編集]

関数の実行環境[編集]

フリースタンディング環境[編集]

オペレーティングシステムのいかなる支援もなしにCプログラムを実行する環境を、 フリースタンディング環境という。 フリースタンディング環境では、 プログラムの開始時に呼び出される関数の名前および型は処理系定義である。 フリースタンディング環境では、複素数型を使ってはいけない。 フリースタンディング環境でプログラムが利用できるライブラリ機能は、 <float.h>, <iso646.h>, <limits.h>, <stdarg.h>, <stdbool.h>, <stddef.h>, <stdint.h> であり、[1] これ以外は処理系定義である。 [2]

ホスト環境[編集]

オペレーティングシステムの支援ありでCプログラムを実行する環境を、 ホスト環境という。[3] ホスト環境では、プログラム開始処理において呼び出される関数の名前はmainである(エントリポイント)。 main関数とはプログラムの開始処理において、 いろいろな初期化処理の後、最初に呼び出される関数である。 たいていのプログラムはこのmain関数から始まると考えてよい。 main関数には大まかに分けてプログラム仮引数(コマンドライン引数)を持たない関数と持つ関数との2種類の記述方法がある。

  • プログラム仮引数とは

プログラムを実行する際、プログラムに対して渡される情報を、プログラム仮引数と呼ぶ。 例えばコマンドプロンプトにおいて、

C:\example.exe ABC DEF GHI

のようにプログラムを実行した場合、 example.exeプログラムのmain関数に対して、 「example.exe」、「ABC」、「DEF」、「GHI」という4個の文字列が渡される。

  • プログラム仮引数を持たない関数

プログラム仮引数を使用しない場合、次のように仮引数をもたない関数を記述する。

int main(void)
{
	/*いくつかの文*/
}
  • プログラム仮引数を持つ関数

プログラム仮引数を使用する場合、次のように2つの仮引数をもつ関数を記述する。

int main(int argc, char *argv[])
{
	/*いくつかの文*/
}

argcはプログラム仮引数の個数である。 argvはプログラム仮引数を表す文字列の配列へのポインタである。 argv[0]が指す文字列はプログラム名である。 argcにはこのプログラム名も個数として数えられる。

ただし、argcが0の場合、argv[0]は空ポインタである。 またargc>0であっても、ホスト環境からプログラム名が得られない場合、 argv[0]は空ポインタである。 [4] argv[1]からargv[argc-1]までが指す文字列は、プログラム仮引数である。 argv[argc]は空ポインタである。 [5]

//例 プログラム仮引数
#include <stdio.h>

int main(int argc, char *argv[])
{
	for (int i = 0; i < argc; ++i)
	{
		printf("argv[%d]の値 : %s\n", i, argv[i]);
	}
}

ホスト環境では、プログラムは、全てのライブラリ機能を使用してよい。 [6]

ホスト環境では、main関数の返却値が、このプログラムを実行するオペレーティングシステムに対して渡され、0はプログラムの成功終了状態を表している。 main関数を終了する}に到達した場合、main関数は値0を返す。 [7]

関数の応用[編集]

inline[編集]

関数指定子inlineは、関数の宣言だけで使用できる。 関数指定子inlineは、その関数の呼び出しを可能な限り速くすることを示唆する。 この示唆が効果をもつ程度は、処理系定義とする。 [8]

inline関数は、その関数を呼び出した部分に展開して直接埋め込む。 関数呼び出しにかかる処理を短縮することができるが、 コードを複数の部分に展開するためファイルサイズが大きくなる。

//例 inline関数の使用例
#include <stdio.h>

inline int function(int a, int b)
{
        return a+b;
}

int main(void){
        int r;
        r=function(1,2);
}

関数へのポインタ[編集]

関数へのポインタとは、 ある関数のメモリアドレスを格納し、 その関数に間接的にアクセスする方法である。 関数へのポインタは、 次のような場合に使われる。 ひとつは、関数を呼び出す際に、 関数へのポインタを引数として渡し、 呼び出した関数の内部で、 関数へのポインタが指す関数を実行する場合。 もうひとつは、関数へのポインタの配列をつくり、 if文や、switch文で関数を呼び出すのをやめ、 コードを単純にする場合。

関数へのポインタの宣言の記述は次のようになっている。

返却値のデータ型 (*関数へのポインタ名)(引数のリスト):

この宣言では、 代入する関数と同じ返却値のデータ型と引数のリストを指定する必要がある。 また、演算子の優先順位のため、 「*関数へのポインタ名」を囲う「()」を省略はできない。

関数のメモリアドレスを関数へのポインタに格納する記述は次のようになっている。

関数へのポインタ名=関数名;

関数名の後ろに、「()」はいらないことに注意。

関数へのポインタを使って、 間接的に関数を呼び出すには、 次のように記述する。

(*関数へのポインタ名)(引数のリスト)
//例 関数へのポインタの引数
#include <stdio.h>

void func1()
{
	printf("func1()が呼び出されました。\n");
}

void func2(void (*func)())
{
	printf("func2()が呼び出されました。\n");
	(*func)();
}


int main(void)
{
	func2(func1);
}
//例 関数へのポインタの配列
#include <stdio.h>

int add(int a, int b)
{
	return a+b;
}

int sub(int a, int b)
{
	return a-b;
}

int mul(int a, int b)
{
	return a*b;
}

int div(int a, int b)
{
	if(b==0){
		printf("0で割ることはできません。\n");
		return 0;
	}else{
		return a/b;
	}
}

int main(void)
{
	int i,j;
	int arithmetic;
	int (*func[])(int a, int b)={add, sub, mul, div};
	printf("2つの整数をスペースで区切って入力してください。:");
	scanf("%d %d", &i, &j);
	printf("計算方法を入力してください(0=加法、1=減法、2=乗法、3=除法)。:");
	scanf("%d", &arithmetic);
	if(0<=arithmetic&&arithmetic<=3)
		printf("答えは%d。\n", (*func[arithmetic])(i,j));
}

再帰[編集]

再帰とは、ある関数がその関数自身を呼び出すことである。 再帰に向いた計算に再帰を使うと、ソースコードを簡潔に書ける場合がある。

ある関数がその関数自身を呼び出すたびに、 引数と制御が戻るべきアドレス(リターンアドレス)が、 スタックと呼ばれる領域に格納される(プッシュ)。 また、ローカル変数もスタックと呼ばれる領域に格納されている。 関数の処理が終わるたびに、 スタックに最後に格納したデータを取り出して(ポップ)、 関数を呼び出したときの状態を復元し、 処理を続ける。[9]

再帰を行うには、その関数がリエントラントである必要がある。 リエントラントであるためには、使用する変数を共有しないように気を付ける必要がある。[10]

//例 再帰を使った階乗の計算
#include <stdio.h>

int factorial(int n);

int main(void)
{
	int i;
	printf("整数を入力してください:");
	scanf("%d", &i);
	printf("%dの階乗は%dです。", i, factorial(i));
}

int factorial(int n)
{
	if(n==0)return 1;
	return factorial(n-1)*n;
}

この例では再帰を使って、入力された整数の階乗を計算している。 3を入力した場合、処理の流れは以下のようになる。

factorial(3)が呼ばれ、
factorial(3)が実行され、factorial(2)が呼ばれ、
factorial(2)が実行され、factorial(1)が呼ばれ、
factorial(1)が実行され、factorial(0)が呼ばれ、
factorial(0)が実行され、1が返され、
factorial(1)に戻り、1*1が返され、
factorial(2)に戻り、1*1*2が返され、
factorial(3)に戻り、1*1*2*3が返される。

脚注[編集]

  1. ^ 『JISX3010:2003』p.5「4 規格合致性」
  2. ^ 『JISX3010:2003』p.8「5.1.2.1 フリースタンディング環境」
  3. ^ 『JISX3010:2003』p.8「5.1.2.2 ホスト環境」
  4. ^ 『[迷信] argv[0] はプログラム名 | 株式会社きじねこ』http://www.kijineko.co.jp/tech/superstitions/argv0-is-program-name.html
  5. ^ 『JISX3010:2003』p.8「5.1.2.2.1 プログラム開始処理」
  6. ^ 『JISX3010:2003』p.9「5.1.2.2.2 プログラムの実行」
  7. ^ 『JISX3010:2003』p.9「5.1.2.2.3 プログラム終了処理」
  8. ^ 『JISX3010:2003』p.83「6.7.4 関数指定子」
  9. ^ 矢沢久雄、原田英生『日経 BP パソコンベストムック C言語とC++がわかる本』日経ソフトウェア、2013年5月15日発行、112項
  10. ^ 『再入可能とは 「リエントラント」 (reentrant) さいにゅうかのう: - IT用語辞典バイナリ』http://www.sophia-it.com/content/%E5%86%8D%E5%85%A5%E5%8F%AF%E8%83%BD