今回の目的
今回もC言語の学習を進めていきます。今回はCにおける型について学習をしていきます。
Cにおける型の種類
C言語の主要なデータ型は、基本型、派生型、ユーザー定義型の3つに大きく分類できます。
基本型
| 型名 | サイズ(一般的な環境) | 説明 |
|---|---|---|
char |
1バイト | 1つの文字を格納します。ASCII文字を扱う際に使用されます。 |
int |
4バイト | 整数を格納します。最も一般的な整数型です。 |
float |
4バイト | 単精度浮動小数点数を格納します。小数点を含む数値を扱います。 |
double |
8バイト | 倍精度浮動小数点数を格納します。floatより高い精度が求められる場合に使用されます。 |
void |
N/A | 値を持たないことを示します。関数の戻り値や引数がない場合、または汎用的なポインタとして使用されます。 |
まず最初に注意点があります。
上記のサイズ欄にて一般的な環境と記載してあるかと思いますが、基本形におけるバイト数は処理系、つまり実行環境により変わり一定のバイト数である保証がないためこのような記載をしています。なので使用する際も、予めバイト数に制限を持たせることはよろしくないようです。
/*バイト数の調べ方*/
printf("char..%d\n",(int)sizeof(char));
printf("int..%d\n",(int)sizeof(int));
printf("float..%d\n",(int)sizeof(float));
printf("double..%d\n",(int)sizeof(double));
結果(あくまで私の環境での結果です)
char..1
int..4
float..4
double..8
派生型
| 型名 | 説明 |
|---|---|
| 配列(Array) | 同じ型の複数の要素を連続したメモリ領域に格納します。int array[5];のように宣言します。 |
| ポインタ(Pointer) | 変数のメモリ上のアドレスを格納します。int *ptr;のように宣言し、他の変数を間接的に操作できます。 |
| 関数(Function) | 一連の処理をまとめたものです。データ型ではありませんが、関数のポインタとして扱われることがあります。 |
派生型
ポインタ型
ポインタ型は変数のメモリアドレスを格納する型で、間接的に他の変数にアクセスしたり、関数に変数のアドレスを渡す際に使用します。
//ポインタのテスト
int* p = NULL;//int型へのポインタ変数
int i;
i= 10;
//iのアドレスをpに代入
p = &i;
//pの値
printf("p = %p\n",p);
//iの値
printf("&i = %p\n",&i);
結果
p = 0x7fffffffd9f4
&i = 0x7fffffffd9f4
配列型
配列型は同じ型の複数の要素(メンバ)からなる型で、予め要素数を決めて宣言を行う。
(Cにも可変長配列(mallocとVLA)があるが、mallocについて勉強不足のためこちらも後日まとめて記事にします。一応VLAを使用した可変長配列の例だけ載せておきます。)
また、配列の要素に対してのポインタ経由でのアクセスも可能。
/*配列型*/
int array[5] = {10, 20, 30, 40, 50};
printf("*array = %d\n", *array); // 10
printf("array[3] = %d\n", array[3]); // 40
printf("*(array + 3) = %d\n", *(array + 3));//3つ目の要素に対してポインタ経由でアクセス
結果
*array = 10
array[3] = 40
*(array + 3) = 40
#include <stdio.h>
/*C99におけるVLA*/
void sub(int size1,int size2, int size3){
int var1;
int array1[size1];
int var2;
int array2[size2][size3];
int var3;
printf("array1..%p\n",(void*)array1);
printf("array2..%p\n",(void*)array2);
printf("&var1..%p\n",(void*)&var1);
printf("&var2..%p\n",(void*)&var2);
printf("&var3..%p\n",(void*)&var3);
}
int main(void) {
int size1,size2,size3;
printf("整数を3つ入力してください\n");
scanf("%d%d%d",&size1,&size2,&size3);
sub(size1,size2,size3);
return 0;
}
関数
データ型ではないが、こちらも配列と同様にポインタを宣言することが可能。
他の型との違いには以下のようなものがある。
- 非オブジェクト型のため関数型の変数が存在しない
- ポインタ型を除く他の型への派生ができない
また、関数の戻り値に配列型は使用できない
#include <stdio.h>
/*引数に1.0を足して表示させる関数の宣言*/
void func1(double d){
printf("func1 + 1.0 = %f\n", d + 1.0);
}
/*引数に2.0を足して表示させる関数*/
void func2(double d){
printf("func2 + 2.0 = %f\n",d + 2.0);
}
int main(void){
/*関数へのポインタ宣言*/
void(*func_p)(double);
func_p = func1;
/*関数func1を直接呼び出し*/
func1(1.0);
/*関数ポインタ経由で関数func1を直接呼び出し*/
func_p(1.0);
/*関数func2を直接呼び出し*/
func2(1.0);
/*関数ポインタ経由で関数func2を直接呼び出し*/
func_p(1.0);
return 0;
}
結果
func1 + 1.0 = 2.000000
func2 + 2.0 = 3.000000
ユーザー定義型
| 型名 | 説明 |
|---|---|
構造体(struct) |
異なる型のデータを1つのまとまりとして扱います。関連する複数のデータをグループ化する際に便利です。 |
共用体(union) |
異なる型のデータが同じメモリ領域を共有します。メモリを節約したい場合に用いられますが、一度にアクセスできるのは1つのメンバだけです。 |
列挙型(enum) |
整数定数に名前をつけ、可読性を高めます。enum { RED, GREEN, BLUE };のように使用します。 |
typedef |
既存のデータ型に別名(エイリアス)をつけます。複雑なデータ型をより分かりやすく表現するために使用されます。 |
こちらはまだ学習が追いついていないため、後日改めて記事にします。
まとめ
今回の学習を通じて派生型、特にポインタの重要性と特殊性を知った。
配列の要素や関数に対してポインタ経由でアクセスできるのは、これまで使ってきたjavaやPythonなどと大きく違うためとても勉強になった。