Lecture 6 データ型

コンピュータでデータ(変数)を扱うには,メモリにその変数を格納する必要がある. 従って,変数は数学の理想的なものとは異なり,どれくらいのメモリ量をその変数に 割り当てるかによって,変数が扱える値の範囲が異なる. ここでは,Cで一般に用いられるデータ型と,メモリの割当量,そのデータが扱える範囲に, 様々な種類があることを知り,それらを適切に扱う方法について学ぶ.

ビット(bit)とバイト(byte)

ビットとバイトは,コンピュータの(iPhoneやUSBメモリ等でも使いますね) 記憶容量等を数える時に使う単位です.最近は随分記憶容量が大きくなりました. 例えば,

  • 1PB ... 1ペタ(250 = 約1000兆)バイト
  • 1TB ... 1テラ(240 = 約1兆)バイト
  • 1GB ... 1ギガ(230 = 約10億)バイト
  • 1MB ... 1メガ(220 = 約100万)バイト
  • 1KB ... 1キロ(210 = 約1000)バイト

は聞いたことがあるでしょう.みなさんのiPhoneは64GBモデル?などと使ったはずですね. ではそもそもバイトって何でしょう? バイトを理解するには,ビットを知っている必要があります. ビットは0か1の2つの状態を記憶できる,コンピュータの記憶容量の最小単位です. コンピュータに使われている,現在のメモリ(memory=記憶とか,記憶素子という意味で使われる)は, は,一つの場所に2値(0か1か)しか記憶することができません.これを 1ビットと表現します.

ビット(bits)

最小単位が2値なので,その大きさを数える時も,我々が馴染んでいる 10進数ではなく,2進数が用いられます.
10進数  2進数 16進数
0 0 0
1 1 1
2 10 2
3 11 3
4 100 4
5 101 5
6 110 6
7 111 7
8 1000 8
9 1001 9
10 1010 A
11 1011 B
12 1100 C
13 1101 D
14 1110 E
15 1111 F
2進数は,0と1だけで表されていることがわかりますね. 上の表をみると,4ビット(最小の記憶場所4つ分. つまり2進数で4桁)で表せる数の最大値は15であることが分かります. 0も含めると,記述できる場合の数は16通りあります.これは,

24 = 16

として計算して求めることが出来ます.

バイト(bytes)

この最小単位であるビットを8つ,つまり2進表記で8桁分合わせたものを「バイト(bytes)」と 呼びます.ほぼすべてのコンピュータでは,1 byte 単位でメモリへの読み書きを行います.
【Quiz】では1バイトで表せる最大の数は幾つでしょう?

16進数

コンピュータのメモリ(記憶素子)では2値(ONとOFF,つまり1と0)しか記憶できないので, 2進数が使われる,という話をしました.ただし,2進数を表記すると,0と1の羅列となり, 非常にわかりにくいですね.そこで,2進数ととても親和性が高く,かつ,人間が見ても 比較的わかりやすい表記法として,一般に16進数が用いられます.
C言語では,16進数の表記を,頭に「0x」を付けて,

0xFF

の様に表します.

変数

変数とは,コンピュータのメモリ上に用意された作業エリアのことを言います. 今まであまり深く考えずに
int a;
などと使ってきましたが,この様に記述すると,メモリ上に "a" という名前の整数を扱う16ビット(2バイト),または32ビット(4バイト)の作業エリアが作られる. intの大きさは後で述べるように,処理系依存である.以下のプログラムを実行して,現在の処理系でのintの大きさを調べて見なさい.
#include <stdio.h>
int main(void){
    printf("size of int    = %d\n", sizeof(int));

    printf("size of char   = %d\n", sizeof(char));
    printf("size of short  = %d\n", sizeof(short));
    printf("size of long   = %d\n", sizeof(long));
    printf("size of float  = %d\n", sizeof(float));
    printf("size of double = %d\n", sizeof(double));
    printf("size of long double = %d\n", sizeof(long double));
}
【ヒント】sizeof()関数は,変数のサイズをバイト単位で求める関数です.

変数の型と大きさ

C言語には,データ型(変数の型)があり,それぞれ,メモリ上のサイズと表せる値の範囲が異なる. 以下は,第3章の繰り返しになるが,もう一度記述しておく.

変数の型の種類(箱の種類と大きさ)

種類 名前 サイズ 範囲
整数型 int 処理系により異なる
bcc32(32bit)ではlongと同じ
unsigned int
short 2バイト -32768...32767
unsigned short 2バイト 0...65535
long 4バイト -2147483648...2147483647
unsigned long 4バイト 0...4294967295
 浮動小数点型(実数)  float 4バイト -3.4E38...3.4E38
double 8バイト -1.7E308...1.7E308

データ型修飾子

データ型を修飾する型修飾子というものがあり,例外もあるが,基本データ型に適用できる.
long intとdoubleに適用可.longだけだとlong intの意味となり,32bit長.long doubleはdoubleよりも大きな数値を扱えるが,データ長は処理系依存.
short intに適用可.shortだけだとshort int の意味.16ビット長となる.
signed 符号付き,すなわち正負を表せる変数となる.int型,char型に適用可だが,int, charとだけ宣言すると,singedつまり符号付きとなる.
unsigned 符号なし.すなわち正の整数のみ表すことができる.int型,char型に適用可.

変数のスコープ,変数の及ぶ範囲

変数には,その宣言された場所に応じて,適用できる範囲(スコープという)がある.通常,変数は,中括弧で区切られた中でのみ,メモリ上に配置される.プログラムがその時実行している場所が,変数を宣言された場所の外へ出ると,その変数のためのメモリの場所は開放されて,メモリ内容も破壊されてしまう.
int func(void) {
    int a = 1;
	
    while(1) {
        int b = a+1;	// ここでaは使える.
    }
	
    int c = a + 3;	// OK
    int d = b;		// エラー!! bはスコープ外
}

auto変数とstatic変数

上で述べたように,スコープ内でのみメモリ上に配置されて,スコープ外では消去されてしまう変数のことを,auto変数と呼ぶ.その一方で,スコープ外になっても,消去されずに変数の値を保持したい場合もある.このようなときは,変数の宣言時に修飾子 staticをつける.
int func(void){
    static int count = 0;// static変数は,コンパイル時に初期化される
    return count++;	// funcの外に出ても破壊されず,値を保持する.
}

int func2(void){
    int x = func();	// 初めてコールしたときには,0が返る.
    x = func();		// この返り値は1になる.
    x = func();		// この返り値は2になる.
}

グローバル変数

プログラム中どこでも参照可能な変数のことをグローバル変数という. グローバル変数は,プログラムが起動されている間,常にメモリ中に確保されている.
#include <stdio.h>
const float pi = 3.14;	// これがグローバル定数
float global_val = 0;	// これがグローバル変数

int func1(void) {
    float x = pi * ...
}

int func2(void) {
    float y = pi * ...	// 共通して同じ変数や定数が使える
    global_val = ...
    ...
}

変数名のバッティング

同じ名前のグローバル変数を宣言することはできず,コンパイル(またはリンク)エラーになる(最新のC++言語では,namespaceという手法を用いて,同じ名前のグローバル変数を使い分けることができるが,ここでは扱わない). しかし,auto変数の場合は,同じ名前の変数を幾つでも宣言することができる.この時,スコープの一番近い変数が一つだけ有効になり,その外側のスコープの変数にはアクセスできなくなる.
int x = 1;

int func(void){
    int y = x;	// これはグローバルのxで値は1
    int x = 2;	// エラーにならない

    x = 3;		// ここでアクセスしているxは直近のauto変数

    {
        int x = 5;
        y = x;	// ここでyは5である.
    }

    y = x;		// ここでyは3である.
}

int x = 2;		// コンパイル(リンク)エラー! 
			// 同名のグローバル変数がぶつかっている.
int func2(void){
    func();		
    int z = x;	// このxは1である.
}
【ヒント】スコープが重なる同名のauto変数は,非常に紛らわしく,見つかり難いバグの温床である. 従って,変数名をしっかり管理して,変数名のバッティングを避け,変数の量も必要なものだけになるよう,限定することが望ましい.
【ヒント】グローバル変数を用いると,汎用性が低下するので,どうしても必要な場合(=グローバル変数を使うことが最も適切である場合)以外は使わないこと.
【ヒント】変数のスコープはできるだけ小さいほうが良い.アクティブな変数は,その時必要なものだけに限定すればメモリの使用量が減らせるだけではなく,プログラムのミスを減らすことができる.

定数,数値定数,文字定数,文字列定数

定数は,プログラム中に書かれた,変数ではない値そのものである.例えば,
int x = 3;		// 3の部分が数値定数
float y = 5.94e3;	// 5.94 x 103の意味.
この定数には,以下の表に示すように,幾つかの種類がある.
数値定数 整数定数 8進数先頭に「0」をつけて表記(017など)
10進数通常の10進数と同じ表記
16進数先頭に「0x」をつけて表記(0x41など)
浮動小数点定数小数点以下が扱える数(16.25 や 1.0e-3 など)
文字定数1文字のこと。'A' や 'B' のように ' ' で囲んで表記.その時実際にプログラム中で扱われる値は,以下に表で示したアスキーコード*である.すなわち,char c = 'A'; というコードと,char c = 0x41;というコードは同じである.
文字列定数複数文字のこと。"ABC" や "computer"のように " "で囲んで表記.ダブルコーテーションで囲んだ時に示される値は,その文字列への参照(アドレス)である.一つ一つの文字はアスキーコードで示される.

*アスキーコード表

アスキーコードは英数字や記号に通し番号を振った文字テーブルの代表的なものである.一文字は1バイトで表される,実際には,どのコンピュータでも有効な共通部分は,前半の7ビット分だけ(0x00 - 0x7F)である.その内,最初の32個(0x00 - 0x1F)は,画面制御用コードで,目に見える形の文字ではない.0x20は何も印字されていないように見えるが,実は「ホワイトスペース文字(=キーボードのスペースバーを叩くと入力されるもの)」という「印字可能な」文字である.
以下の表は,横軸が上位4ビット,縦が下位4ビットとして表作成を行ったものである.例えば,大文字の'A'は0x41,'+'の文字コードは0x2Bである.

 

型変換,整数拡張,型の昇格

Cでは,式の中で複数の異なるデータ型の変数や定数を混在させることができる.例えば,
char c = 'A';
int i = 10;
float f = 3.14e0;

double d = c * i / f;	// この式はエラーにならずに演算できる!
この時,整数拡張と型変換というルールが適用され,式の中で最も大きなデータ型に合わせてから演算される.例えば,上の式では,char型とint型の乗算は,乗算を行う前に,char型の変数をint型に変換される.次にfloatで割られる前に,c*iの結果をfloat型に変換する.最終的にfloat型の計算結果をdouble型に代入する前にdoubleに変換し,その後に代入が行われる.
上記は最新のCおよびC++での場合である.古いCコンパイラでは,常に演算の左側のデータ型に合わせられていた.つまり,c*iはchar型になってしまい,多くの場合,演算結果がオーバーフロー(charでは表せない大きな値になること)して,誤った結果を招いていた.最新のコンパイラではこのようなことが起こりにくくなっている.

代入文での型変換

式の演算途中であれば,コンパイラが自動的に大きなデータ型に合わせてくれるが,代入の場合はそうは行かない.例えば,以下の場合,
int i = 258;
char c = i;	// 大きなものから小さなものへの代入
printf("c = %d\n",c);	// cを整数として印字
この演算結果がどうなるかわかるだろうか? 結果は
c = 2;
となる.
その理由は,代入はビットごとのコピーであり, 対応するビットの存在しない上位ビットは捨てられる(失われる)からである. すなわち,ビットイメージは以下のようになる.
[00000010] = [00000001][00000010]	// 258 = 256 + 2
char型8ビット int型16ビット(左上位,右下位ビット)
このように,上位ビットは捨てられ,下位ビットだけがそのままコピーされるので,値が2になるのである.
同様に,浮動小数から整数へのコピーも可能で,その場合は小数点以下は切り捨てられる.
float pi = 3.14;
int p = pi;		// floatからintへのコピー
この場合,p = 3 である.但し,このコードはコンパイラのエラー,または警告になる可能性がある. どうしても上記のコードを成立させたい場合は,以下の型キャストを行う.

型のキャスト

型をキャストすることで,どのような変数型の変数同士の代入操作も可能になる. 但し,代入結果についてはプログラマの責任である.
float pi = 3.14;
int p = (int)pi;	// 型キャスト
これでどんなコンパイラでもエラーにならずにコンパイルできるようになる.

ユーザが作成する独自の型

Cでは,ユーザが変数型(に近いもの)を定義するすることができる.例えば,以下は構造体を使った例. 本格的にユーザ定義のデータ型を作るには,C++の機能である「クラス」を使う必要があるが, 簡易的な方法でも,劇的にプログラムを読みやすくする効果があるので,是非とも利用すべきである.
struct Point3D {// 構造体を用いた新しいデータ構造の作成
    float x;
    float y;
    float z;
};


int func(void) {
    Point3D p;	// 新しいデータ型の変数の宣言
    		// たったこれだけで,float3個を宣言できる.
    Point3D q[10];// 配列にもできる.

    p.x = 3.14;// 変数へのアクセス
    p.y = 0.5;
    ...
    q[i].x = 10.3;//配列変数へのアクセス
    ...



Quiz 6(第6回の提出課題ではありません)

以下の問それぞれに対応するプログラムを作成しなさい.

    1. キーボードから数字を入力したら,10進数と16進数表示するプログラムを作成しなさい.
      //実行例 
      type a number:30 
      10進数:30 
      16進数:1e 
      

      解答例 Q6-1




    2. 上の本文中で表示したアスキーコード表と同じものを書いてみよ. この時,画面に印字されない制御コードを書いてしまうと表示が乱れるので, isprint()を用いて,表示可能なものだけ表示せよ. このプログラムでは関数の中でprintfを使用して良い.
      【ヒント】isprint()を使うためには,ヘッダ ctype.h を用います.

      解答例 Q6-2




    3. アスキーコード表の中から,入力した文字の文字コードを出力するプログラムを作成せよ.
      //実行結果 
         0  @  P  `  p  
      !  1  A  Q  a  q  
      "  2  B  R  b  r  
      #  3  C  S  c  s  
      $  4  D  T  d  t  
      %  5  E  U  e  u  
      &  6  F  V  f  v  
      '  7  G  W  g  w  
      (  8  H  X  h  x  
      )  9  I  Y  i  y  
      *  :  J  Z  j  z  
      +  ;  K  [  k  {  
      ,  <  L  \  l  |  
      -  =  M  ]  m  }  
      .  >  N  ^  n  ~  
      /  ?  O  _  o    
      上のアスキーコード表から文字を入力:# 
      " # "の文字コードは0x23
      

      解答例 Q6-3




    4. 【文字コードと数値の違い】文字としての数字('0', '1', '2'など)1文字を「数値」に変換する関数 int atoi(char) を作成(自作)せよ. また,変換したその数字を3乗した結果を表示するプログラムを作れ.

      int atoi(char &c) 
      { 
      	// write your code here
      } 
      
      int cubic(int &x) 
      { 
      	// write your code here
      } 
      
      int main(void) 
      { 
          char c = 0; 
              
          printf("Input a number: "); 
          scanf("%c", &c); 
              
          int n = atoi(c);    
          int x = cubic(n);        
      
          printf("%dの3乗は = %d\n", n, x); 
          return 0; 
      } 
      

      解答例 Q6-4




    5. 【Challenge! 新しいデータ型をつくる】構造体を用いて,3次元ベクトル型を作りなさい.また,2つの3次元ベクトルの和を求める関数を作りなさい.
      struct Vector3D {
      	// write your code down here.
      };
      
      int main(void)
      {
      	// write your code down here.
      }
      

      解答例 Q6-5






Assignment 6(第6回の宿題)

【課題提出における注意】
  • すべてのプログラムは,必ずコンパイル&実行し,正しく機能することをチェックしてから提出すること.
  • プログラムは正しくインデントさせること.


Assignment 6のページ



課題提出

作成したプログラムを一つのファイル(*.zip)にしてOh-o! Meijiシステムレポート第6回に提出する. 提出するすべてのファイルには,年組番号 氏名を記入すること.締め切りは月曜日よる0時までとする.

【注意】すべてのファイル名は半角のみで構成すること.
【注意】提出時のファイル名命名規則などを守っていない人がいる.これらのファイルは採点されないので注意すること.
【お願い】提出するファイルは,フォルダに入れないでそのまま圧縮してください.