Lecture 11 構造体とクラス

 構造体(structure)は,複数のデータを含み,ユーザが定義することで新しいデータ型が作れる機能である. データ(あるいは情報)は,それぞれ何らかの関連があることが多く,この様なデータをまとめて 管理できると大変に都合が良い.例えば,友人の連絡先データベースを作る場合,「氏名」「電話」 「住所」などの情報をまとめて一人分のデータとできれば便利である.Cでは,このような 「複合データ型(aggregate data type)」を, を構造体を用いて作成でき,これを「複合データ構造体(complex data structure)」などと呼ぶ.
 また,C++言語では,この構造体を拡張した「クラス(class)」により, オブジェクト指向という洗練されたプログラミングの概念を導入し, より堅牢で大規模なプログラムを作成することを可能にしている. このように,構造体は,単にデータをまとめるというその機能以上に, より新しいソフトウェアの設計概念を理解する上で重要な機能である. 深く理解するように取り組んでほしい.

構造体

構造体は,メンバと呼ばれる,互いに関連のある2つ以上の変数で構成される複合データ型である. 配列がどの要素も同じ型を持つのに対して,構造体はそれぞれのメンバが異なる型を持つことができる. 構造体を定義する一般的な形式は次の通り,
    struct タグ名 {
        型 メンバ 1;
        型 メンバ 2;
        型 メンバ 3;
        ・・・
        型 メンバ N;
    } 変数名;

例えば,住所録の場合,
    struct Address {
        char name[40];
        short postal_code1;
        short postal_code2;
        char address[80];
        char phone;
    } card;

Cの場合の説明

Cでは,この例の場合,Address は「タグ名」で,struct Address とすると「型名」になる.その後にある,card は struct Address 型の変数である.
* struct Address は,int などと同じ型名であって変数ではない.

C++の場合の説明 ... 拡張子が.cppの場合はこちら

C++では,Cと比べて構造体の解釈が拡張され,Addressは「タグ名」ではなくて,「型名」として 使えるようになった.従って,変数(インスタンス)を作るときにも,
    struct Address card;
ではなくて,
    Address card;
でよく,typedef を使う必要もなくなった.



上の例は,型の宣言と,変数の宣言を同時に行なっている例であるが,通常はこの2つの宣言を別々に行うことが多い. 例えば,「型」の宣言:
    struct Address {  // 新しい「型」の宣言だけしている.
        char name[40];      // ヘッダファイルなどで宣言されることが多い
        short postal_code1;
        short postal_code2;
        char address[80];
        char phone_number[20];
    };
は,ヘッダファイル(.h)などで宣言されることが多い.

一方で,変数の宣言:
Cの場合>    struct Address card;	// cardという変数を宣言している.
C++の場合>  Address card;
は,ソースコード(.cpp)の中で行われる.
変数の宣言をして初めて,メモリ上に実体を格納する領域が作られる. そのメモリ配置は,
name 40バイト
postal_code1 2バイト
postal_code2 2バイト
address 80バイト
phone_number 20バイト


構造体のメンバにアクセスするには,変数名にピリオド(ドット演算子)をつけ,その後にメンバ名を書く.
    card.postal_code1 = 214;
    card.postal_code2 = 8571;
    strcpy(card.name,"理工太郎");
    strcpy(card.address,"川崎市多摩区東三田1-1-1");
    strcpy(card.phone_number,"044-934-xxxx");
その他の変数の要素へのアクセス方法は,一般の変数と同じである.
    scanf("%d",&card.postal_code1);
    for(int i=0; card.name[i], i++)
    	printf("%c",card.name[i]);

構造体の配列

 先の例では,一人分のアドレスを格納できる構造体 struct Address を宣言(定義)し, 一人分のデータを格納できる変数 card を一つ作った.だが実際には,アドレス帳には多数の連絡先を 記録したい.例えば,100人分の連絡先を記録するには,
    Address card[100];
を宣言すればよい.このそれぞれの要素にアクセスするには,
    card[i].postal_code1 = 101;
    card[i].postal_code2 = 8301;
    strcpy(card[i].name,"明治二郎");
    strcpy(card[i].address,"東京都千代田区神田駿河台1-1");
    strcpy(card[i].phone_number,"03-3296-xxxx");
などとすれば良い.

構造体へのポインタ

 構造体へのアクセスは,多くの場合,ポインタを用いて行われる.構造体へのポインタの宣言は, その他の変数のポインタの宣言と変わらない.
   Address card[100];
   Address *cardp = card;
このポインタにアクセスするには,アロー演算子を用いる.
    cardp->postal_code1 = 102;
    cardp++;    // ポインタをインクリメント(次のカードへ行く)

ビットフィールド

 構造体の特殊な利用例として,ビットフィールドがある.これはバイトやワードの内部の ビット1つまたは複数のビットにまたがる場所に名前をつけてアクセスすることができる. 機械やロボットの制御を行うときには大変良く用いられる.
   型 名前:サイズ;
例えば以下の例は,学生の年組番号と出欠を2バイトで表した例である.
    struct BitF {
        unsigned grade: 3;  // 1-4年生(最大8通り)
        unsigned class: 4;  // 5/6組 (最大16通り)
        unsigned number:8;  // 1-99番(最大256通り)
		unsigned shusseki:1;// 1:出席,0:欠席
    };
通常なら少なくとも4バイトは使っている(intを使えば16バイト)ところを, 非常にコンパクトに表すことができる. ビットフィールドは更に,機械のスイットのOn/OFFなどを制御するのにも非常に役立つ.

共用体

 共用体は,同じメモリ領域を複数の変数でシェア(共有)するものである.
    union タグ名 {
        型 メンバ1;
        型 メンバ2;
        型 メンバ3;
        ・・・
        型 メンバN;
    } 変数;
例えば以下のように用いる.
   union SharedMemory {
       long int li;
       short    s[2];
       float   f;
       char c[4];
   };
          
   union SharedMemory shm;
この変数 shm は,サイズが4バイトであり,その同じ領域を,longとしても floatとしても, また,shortを2つ,あるいはcharを4つとして利用することができる. 要素へのアクセス方法は、構造体と同じである。





クラス

C++では,構造体が拡張されて,「メンバ変数」だけでなく,「メンバ関数」を持つことができるようになった. この理由は,今までの言語が,「手続き」に重点が置かれていたのだが, それにより「データ」の管理が疎かになることで,プログラムコードの堅牢性が悪くなり, 巨大なプログラムになるほど,動かなくなるケースが増えたためである.

この問題を回避するため,「データ」に着目してデータを保護し,堅牢性を高める手法として 「データ」とそれを制御するための「インタフェース関数」をパッケージする(つまりこれがクラス) ことが考えられたのである.

構造体では,以下のように,データをパッケージすることができた.
struct 構造体の型名
{
    //メンバ変数
    型名 変数名;
} 構造体のインスタンス名;
ただし,このデータは外から丸見えのため,整理はできるが堅牢性は高くない. これに対し,C++のクラスでは,以下のようになる.

class クラスの型名
{
    // メンバ変数(ここでは非公開)
    型名 変数名;

  public: (これ以降は公開される)
    // メンバ関数
    戻り値型 関数名(引数1,...);
} クラスのインスタンス名;
実はC++では,構造体はCでの構造体と異なり,メンバ関数も持つことができる. クラスとの違いは,構造体はすべてのメンバが公開(public)であることである. クラスは,デフォルトではすべて非公開(private)である.

メンバ変数は通常,すべて非公開(private)とし,privateのメンバ変数に アクセスできるのは,メンバ関数だけに限定することで,データが不慮のトラブルから 守られるようにする(これをデータ隠蔽またはカプセル化という).

では,具体的な例で違いを見ていこう.
//  C 言語的な構造体を用いたプログラム
#include <stdio.h>
#include <string.h>

//  名前と年齢を管理する構造体
struct PersonalData
{
    char name[100];
    int age;
};

int main(void)
{
    PersonalData data; // 構造体 PersonalData型 の変数 data を宣言

    //  値を設定
    strcpy(data.name, "Taro"); // データには外からアクセスできる
    data.age = 20;

    //  データを表示
    printf("%s\n", data.name);
    printf("%d\n", data.age);
    return 0;
}
クラスを用いたコードは,例えば以下のようになる.
//  C++ クラスの使用
#include <stdio.h>
#include <string.h>

//  クラス
class PersonalData {
  private:       // 書かなくてもデフォルトでprivate
    // メンバ変数(保護したいデータ)
    char name[100];
    int age;

  public:
    // メンバ関数(インタフェース関数)
    void set(char new_name[], int n) {
        strcpy(name, new_name);
        age = n;
    }

    void disp(void) {
        printf("%s\n", name);
        printf("%d\n", age);
    }
};


int main(void) {
    PersonalData data;        // クラス PersonalData のオブジェクト(変数の実体) data を生成

    data.set("Taro", 20);     //  値の設定.メンバ関数 set() を呼び出す.
    data.disp();              //  データを表示.メンバ関数 disp() を呼び出す.

    return 0;
}
上記のプログラムをコンパイル,実行してみよ


Quiz 11


以下の問それぞれに対応するプログラムを作成しなさい.
    1. 以下に示す生徒一人分の情報を構造体として作成し,一人一人の内容を表示するプログラムを作成せよ


    2. 上記に加えて平均値と成績を求めるプログラムを作成せよ.

    3. 名前,出身地,年齢,性別を格納する構造体を作り, 関数に構造体引数として渡し,関数の中で内容を画面に出力するプログラムを作成せよ.
      // main関数の例
      int main(void)
      {
          AddressBook data[] = {
              "Aoyama Taro", "Tokyo", 20, 'M',
              "Kawasaki Jiro", "Kanagawa", 21, 'M',
              "Morioka Miki", "Iwate", 22, 'F',
              "Kawagoe Shiro", "Saitama", 23, 'M',
              "Narashino Goro", "Chiba", 24, 'M',
              NULL, NULL, 0, 0};
      
          print_data(data);
      }
      

    4. 上記のプログラムをクラスを用いて書き換えよ.データはすべてprivateとせよ.



    5. Assignment(課題)11

      課題の提出は Online Judge にて行います.

      • 提出期限があります.できるだけ早めにやりましょう.
      • Online Judge は普通の課題提出方法とは異なり,良い点が取れるまで何度でも挑戦できます.良い点が取れるまで頑張ってやろう.
      • わからないことは,早めに Teams で聞こう.問題は自分で解決すること.
      • それでもどうしてもわからないときは,毎週月曜日の2時限目にZoomによるオフィスアワーを用意しています. ZoomのリンクはOh-o! meijiで確認してください.
      • Online Judge のページはこちら