1、概要

C言語では、ユーザーが自分でデータ構造を指定できます。これは異なる型のデータを組み合わせて1つのまとまりにし、参照しやすくするものです。このまとまりの中でデータは互いに関連しています。このようなデータ構造を構造体と呼び、他の高級言語のレコードに相当します。

構造体型を宣言する一般的な形式は次のとおりです:

struct 结构体名
{成员列表};

構造体名は、構造体型の目印として使われ、構造体タグとも呼ばれます。波括弧の中は、この構造体の各メンバーであり、それらによって構造体が構成されます。各メンバーに対して型宣言を行う必要があります。例:

型名 メンバー名;

メンバーリストはフィールドリストとも呼ばれ、最初のメンバーは構造体のフィールドとも呼ばれます。メンバー名の命名規則は変数名と同じです。

struct student
{
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char addr[30];
};

2、構造体型変数を定義する方法

前述では構造体型を指定しただけであり、それはモデルに相当しますが、そこには具体的なデータはなく、システムも実際のメモリユニットを割り当てません。プログラムで構造体型のデータを使用するには、構造体型の変数を定義し、その中に具体的なデータを格納する必要があります。構造体型変数を定義するには、次の3つの方法があります。

(1)構造体型を宣言してから変数名を定義する

上で構造体型 struct student を定義しました。これを使って変数を定義できます。例:

struct student{  //结构体类型名
    ...
    ...
    ...
}student1, student2 //结构体变量名

student1, student2 を struct student 型の変数として定義しました。

構造体変数を定義すると、システムはそれにメモリユニットを割り当てます。例えば、student1 と student2 はメモリ内でそれぞれ59バイトを占有します。

注意すべき点は、変数を標準型(基本データ型)として定義する場合と構造体型として定義する場合の違いです。後者では、変数を構造体型として指定するだけでなく、特定の構造体型(例えば struct student 型)を指定する必要があります。なぜなら、具体的な構造体型は多数定義できるからです。一方、変数を整数型として定義する場合は、単に int 型と指定するだけで済みます。

(2)型の宣言と同時に変数を定義する

例:

struct student
{
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char addr[30];
}student1, student2;

その働きは最初の方法と同じで、struct student 型の2つの変数 student1, student2 を定義します。この形式の定義の一般的な形式は次のとおりです。

struct 结构体名
{
    成员表列
}变量名表列;

(3)構造体型変数を直接定義する

その一般的な形式は次のとおりです:

struct
{
    成员表列
}变量名表列;

つまり、構造体名が現れません。

構造体型について、いくつか説明すべき点があります:

  • a. 型と変数は異なる概念であり、混同してはいけません。変数に対してのみ代入、アクセス、演算ができ、型に対して代入、アクセス、演算はできません。コンパイル時には、型には空間が割り当てられず、変数にのみ空間が割り当てられます。
  • b. 構造体のメンバー(すなわちフィールド)は単独で使用でき、その働きと地位は普通の変数に相当します。
  • c. メンバーは構造体変数にすることもできます。例:

    struct date // 声明一个结构体类型
    {
        int month;
        int day;
        int year;
    }
    
    struct student
    {
        int num;
        char name[20];
        char sex;
        int age;
        struct date birthday;
        char addr[30];
    }student1, student2;

    まず struct date 型を宣言します。これは「日付」を表し、month, day, year の3つのメンバーを含みます。次に struct student 型を宣言する際に、メンバー birthday を struct date 型として指定します。

  • d. メンバー名はプログラム内の変数名と同じにすることができますが、両者は同じオブジェクトを表すわけではありません。

3、構造体変数の参照

(1)構造体変数を1つのまとまりとして入出力することはできません。

構造体変数の各メンバーに対して個別に入出力を行うことしかできません。構造体変数のメンバーを参照する方法は次のとおりです:

構造体変数名.メンバー名

例えば、student1.num は student1 変数の中の num メンバー、つまり student1 の num 項目を表します。変数のメンバーに代入できます。例: student1.num = 10010;

.「.」はメンバー(成分)演算子であり、すべての演算子の中で最も優先順位が高いため、student1.num を1つのまとまりとして扱うことができます。上の代入文は、整数 10010 を student1 変数内のメンバー num に代入する働きをします。

(2)メンバー自体が構造体型である場合、複数のメンバー演算子を使用して、段階的に最下位のメンバーまでたどる必要があります。代入、アクセス、演算は最下位のメンバーに対してのみ行えます。

例:構造体変数 student1 の各メンバーには次のようにアクセスできます:

student1.num
student1.birthday.month

注意:student1.birthday を使って student1 変数のメンバー birthday にアクセスすることはできません。birthday 自体が構造体変数だからです。

(3)構造体変数のメンバーは、普通の変数と同じようにさまざまな演算を行うことができます(その型に応じて実行可能な演算が決まります)。

student2.score = student1.score;
sum = student1.score + student2.score;
student1.age ++;
++ student1.age;

なぜなら.「.」演算子の優先順位が最も高いため、student1.age ++ は student1.age に対してインクリメント演算を行うことになります。age に対して先にインクリメント演算を行うわけではありません。

(4)構造体変数のメンバーのアドレスを参照できます。また、構造体変数のアドレスも参照できます。例:

scanf("%d", &student1.num);// 输入 student1.num 的值
printf("%o", &student1);// 输出 student1 的首地址

しかし、次のような文で構造体変数をまとめて読み込むことはできません:

scanf("%d,%s,%c,%d,%f,%s", &student1);

構造体変数のアドレスは主に関数の引数として、構造体のアドレスを渡すために使われます。


4、構造体変数の初期化

他の型の変数と同様に、構造体変数には定義時に初期値を指定できます。

例

#include <stdio.h> struct student { long int num; char name[20]; char sex; char addr[30]; }a = {89031, "Li Lin", 'M', "123 Beijing Road"}; void main() { printf("NO. : %ld\nname: %s\nsex: %c\naddress: %s\n", a.num, a.name, a.sex, a.addr); }

5、構造体配列

1つの構造体変数には一連のデータ(例えば、学生の学籍番号、氏名、成績などのデータ)を格納できます。10人分の学生データを演算に用いる必要がある場合は、明らかに配列を使うべきです。これが構造体配列です。構造体配列は、以前紹介した数値型配列とは異なり、各配列要素が構造体型のデータであり、それぞれ各メンバー(成分)項目を含みます。

5.1 構造体配列の定義

構造体変数を定義する方法と似ており、それが配列であることを指定するだけで済みます。

struct student
{
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char addr[30];
};
struct student stu[3];

以上で、要素が struct student 型データである配列 stu を定義しました。配列には3つの要素があります。構造体配列を直接定義することもできます。例:

struct student
{
    int num;
    ....

}stu[3];

或

struct

{
    int num;
     ...
}stu[3];

5.2 構造体配列の初期化

他の型の配列と同様に、構造体配列も初期化できます。例:

struct student
{
    int mum;
    char name[20];
    char sex;
    int age;
    float score;
    char addr[30];
}stu[3] = {{10101,"Li Lin", 'M', 18, 87.5, "103 Beijing Road"},
            {10101,"Li Lin", 'M', 18, 87.5, "103 Beijing Road"},
            {10101,"Li Lin", 'M', 18, 87.5, "103 Beijing Road"}};

配列 stu を定義するとき、要素数は指定しなくてもよく、次のような形式で記述できます:

stu[] = {{...},{...},{...}};

コンパイル時、システムは与えられた初期値を持つ構造体定数の数に基づいて配列要素の数を決定します。

もちろん、配列の初期化は次の形式でも行えます:

struct student
{
    int num;
    ...
};
struct student stu[] = {{...},{...},{...}};

つまり、最初に構造体型を宣言し、その構造体型の配列を定義し、配列の定義時に初期化します。

以上からわかるように、構造体配列の初期化の一般的な形式は、配列の定義の後ろに次を付け加えることです:

5.3 構造体配列の応用例

次の例は、構造体配列の定義と参照を説明しています。

例

#include <stdio.h> #include <string.h> #include <stdlib.h> struct person { char name[20]; int count; }leader[3] = {{"Li", 0},  {"Zhang", 0},  {"Fun", 0}}; void main() { int i, j; char leader_name[20]; for(i = 1; i<= 10;i++) { scanf("%s", leader_name); for(j=0;j<3;j++) if(strcmp(leader_name, leader[j].name) == 0) leader[j].count ++; } printf("\n"); for(i=0;i<3;i++) printf("%5s: %d\n", leader[i].name, leader[i].count); system("pause"); }

実行結果は次のとおりです:

LI
Li
Fun
Zhang
Zhang
Fun
Li
Fun
Zhang
Li

   Li: 3
Zhang: 3
  Fun: 3

6、構造体型データを指すポインタ

構造体変数のポインタとは、その変数が占めるメモリセグメントの開始アドレスです。ポインタ変数を設定して構造体変数を指すことができます。このとき、そのポインタ変数の値は構造体変数の開始アドレスです。ポインタ変数は構造体配列の要素を指すためにも使えます。

6.1 構造体変数を指すポインタ

構造体変数を指すポインタの応用:

例

#include <string.h> #include <stdio.h> #include <stdlib.b> struct student { long num; char name[20]; char sex; float score; }; void main() { struct student stu_1; struct student *p; p = &stu_1; stu_1.num = 89101; strcpy(stu_1.name, "Li Lin"); stu_1.sex = 'M'; stu_1.score = 89.5; printf("NO. :%ld\nname: %s\nsex:%c\nscore:%f\n", stu_1.num, stu_1.name, stu_1.sex, stu_1.score); printf("NO. :%ld\nname: %s\nsex:%c\nscore:%f\n", (*p).num, (*p).name, (*p).sex, (*p).score); system("pause"); }

.num や stu

.name を関数の実引数として使用し、実引数の値を仮引数に渡します。使用方法は通常の変数を実引数として使うのと同じで、値渡し方式に属します。実引数と仮引数の型を一致させることに注意してください。

NO. :89101
name: Li Lin
sex:M
score:89.500000
NO. :89101
name: Li Lin
sex:M
score:89.500000

2つの printf の出力結果が同じであることがわかります。

C言語では、使いやすく直感的にするために、(*p).num の代わりに p->num を用いることができます。これは *p が指す構造体変数の中の num メンバーを表します。同様に、(*p).name は p->name と等価です。

つまり、次の3つの形式は等価です。

  • a. 構造体変数.メンバー名
  • b. (*p).メンバー名
  • c. p-> メンバー名

上記の最後の printf 関数の出力項目は次のように書き換えることができます。

printf("NO. :%ld\nname: %s\nsex:%c\nscore:%f\n",p->num, p->name, p->sex, p->score);

ここで->をアロー演算子(指し示し演算子)と呼びます。

次のいくつかの演算子を分析します。

  • p -> n は p が指す構造体変数の中のメンバー n の値を取得します。
  • p -> n ++ は p が指す構造体変数の中のメンバー n の値を取得し、その値を使った後に 1 増やします。
  • ++p -> n は p が指す構造体変数の中のメンバー n の値を取得して 1 増やします(先に加算)。

6.2 構造体配列を指すポインタ

以前、配列や配列要素を指すポインタとポインタ変数を使用できることを紹介しました。同様に、構造体配列とその要素に対してもポインタ変数で指すことができます。

構造体配列を指すポインタの応用。

例

#include <stdio.h> #inlcude <stdlib.h> struct student { int num; char name[20]; char sex; int age; }; struct student stu[3] = {{10101, "Li Lin", 'M', 18},  {10102, "Zhang Fun", 'M', 19},  {10103, "Wang Min", 'F', 20}}; int main() { struct student *p; printf("No. name sex age\n"); for(p=stu; p<stu+3;p++) printf("%5d %-20s %2c %4d\n", p->num, p->name, p->sex, p->age); system("pause"); }

実行結果は次のとおりです。

No.    name        sex        age
10101 Li Lin                M       18
10102 Zhang Fun        M       19
10103 Wang Min          F        20

次の2点に注意してください。

(

1)p の初期値が stu の場合、つまり最初の要素を指している場合、p + 1 は次の要素の先頭アドレスを指します。例えば:

(++p) -> num はまず p を 1 増やし、それから p が指す要素の中の num メンバーの値(すなわち10102)を取得します。

(p++) -> num はまず p->num の値(すなわち10101)を取得し、それから p を 1 増やして stu

上記の2つの違いに注意してください。

(2)プログラムではポインタ p を struct student 型データを指す変数として定義しています。p は struct student 型のデータのみを指すことができ(p の値は stu 配列のある要素の先頭アドレスです)、stu 配列要素の中の特定のメンバーを指すことはできません(つまり、p のアドレスはメンバーのアドレスであってはなりません)。例えば、次は正しくありません:

p = &stu[1].name

コンパイル時にエラーになります。p はアドレスを格納するものだから、どんなアドレスでも代入できると考えてはいけません。アドレスの型が異なる場合は、キャスト(型変換)を使用できます。例えば:

p = (struct student *)&stu[1].name;

このとき、p には stu

6.3 構造体変数と構造体を指すポインタを関数の引数にする

構造体変数の値を別の関数に渡すには、3つの方法があります。

  • (1)構造体変数のメンバーをパラメータとして使用します。例えば、stu
  • (2)構造体変数をパラメータとして使用します。古いバージョンのCシステムでは構造体変数を実引数として使用することはできませんでしたが、ANSI C ではこの制限がなくなりました。しかし、構造体変数を実引数として使用する場合、値渡し方式が採用され、構造体変数が占めるメモリユニットがすべて順番に仮引数に渡されます。仮引数も同じ型の構造体変数でなければなりません。関数呼び出し中、仮引数もメモリユニットを占有します。この渡し方は空間と時間のオーバーヘッドが大きくなります。構造体の規模が大きい場合、オーバーヘッドはかなりのものになります。さらに、値渡し方式を採用しているため、される呼び出し関数の実行中に仮引数(これも構造体変数です)の値が変更されても、その値を呼び出し元の関数に返すことができず、使用上の不便さを引き起こすことがよくあります。そのため、一般的にはこの方法はあまり使われません。
  • (3)構造体変数(または配列)を指すポインタを実引数として使用し、構造体変数(または配列)のアドレスを仮引数に渡します。

構造体変数を関数のパラメータとして使用します。

例

#include <stdio.h> #define FORMAT "%d\n%s\n%f\n%f\n%f\n" struct student { int num; char name[20]; float score[3]; }; void print(struct student stu) { printf(FORMAT, stu.num, stu.score[0], stu.score[1], stu.score[2]); printf("\n"); } void main() { struct student stu; stu.num = 12345; strcpy(stu.name, "Li Li"); stu.score[0] = 67.5; stu.score[1] = 89; stu.score[2] = 78.6; printf(stu); }

上記を、構造体変数を指すポインタを実引数として使用するように変更します。

例

#include <stdio.h> #define FORMAT "%d\n%s\n%f\n%f\n%f\n" struct student { int num; char name[20]; float score[3]; }stu = {12345, "Li Li", 67.5, 89, 78.6}; void print(struct student *p) { printf(FORMAT, p->num, p->name, p->score[0], p->score[1], p->score[2]); printf("\n"); } void main() { print(&stu); }

7、ポインタで連結リストを扱う

7.1 連結リストの概要

リンクリストは、一般的で重要なデータ構造です。これは動的にストレージを割り当てる構造です。

リンクリストには先頭ポインタ変数があり、そこにはアドレスが格納され、そのアドレスはある要素を指します。リンクリスト内の各要素はノードと呼ばれ、各ノードは2つの部分を含みます。1つはユーザーが必要とする実際のデータ、もう1つは次のノードのアドレスです。先頭ポインタ head が最初の要素を指し、最初の要素が2番目の要素を指し、……最後の要素まで続きます。最後の要素は他の要素を指さず、これは末尾と呼ばれ、そのアドレス部分には NULL(空アドレスを表す)が入れられ、リンクリストはここで終了します。

リンクリスト内の各要素はメモリ上で連続して格納されている必要はないことがわかります。特定の要素を見つけるには、まず前の要素を見つけ、それが提供する次の要素のアドレスに基づいて次の要素を見つける必要があります。先頭ポインタ head が提供されなければ、リンクリスト全体にアクセスできません。

このようなリンクリストのデータ構造は、ポインタ変数を利用して初めて実現できます。つまり、1つのノードにはポインタ変数を含め、それを使って次のノードのアドレスを格納する必要があります。

前述の構造体変数は、リンクリストのノードとして使用するのに最も適しています。構造体変数は複数のメンバーを含み、これらのメンバーは数値型、文字型、配列型、またはポインタ型にすることができます。このポインタ型メンバーを使用して次のノードのアドレスを格納します。例えば、次のような構造体型を設計できます:

struct student
{
    int num;
    float score;
    struct student *next;
};

ここで、メンバー num と score はノード内の有用なデータ(ユーザーが必要とするデータ)を格納するために使用され、next はポインタ型メンバーで、struct student 型データを指します(これは next が属する構造体型です)。ポインタ型のメンバーは、他の型の構造体データを指すこともできますし、自分が属する構造体型のデータを指すこともできます。今、next は struct student 型のメンバーであり、再び struct student 型のデータを指します。この方法でリンクリストを構築できます。

注意してください:単に struct student 型を定義しただけでは、実際にはストレージ空間は割り当てられません。変数を定義して初めてメモリユニットが割り当てられます。

7.2 単純な連結リスト

次に、例を通して単純なリンクリストを構築して出力する方法を説明します。

例

#include <stdio.h> #include <stdlib.h> #define NULL 0 struct student { long num; float score; struct student *next; }; void main() { struct student a, b, c, *head, *p; a.num = 99101; a.score = 89.5; b.num = 99103; b.score = 90; c.num = 99107; c.score = 85;//ノードの num メンバーと score メンバーに値を代入 head = &a;//ノード a の先頭アドレスを先頭ポインタ head に代入 a.next = &b;//ノード b の先頭アドレスをノード a の next メンバーに代入 b.next = &c; c.next = NULL;// c ノードの next メンバーには他のノードのアドレスを格納しない p = head;// p ポインタを a ノードに指させる do { printf("%ld %5.1f\n", p->num, p->score);// p が指すノードのデータを出力 p = p->next;// p に次のノードを指させる }while(p != NULL);// c ノードを出力し終えた後の p の値は NULL system("pause"); }

実行結果:

99101  89.5
99103  90.0
99107  85.0

7.3 動的連結リストを扱うために必要な関数

(1)malloc 関数

void *malloc(unsigned int size);

メモリの動的記憶領域に長さ size の連続空間を割り当てます。この関数の値(すなわち戻り値)は、割り当てられた空間の先頭アドレスを指すポインタ(基本型は void)です。この関数が正常に実行できなかった場合(例えばメモリ空間が不足している場合)、空ポインタ NULL を返します。

(2)calloc 関数

void *calloc(unsigned n, unsigned size);

その作用は、メモリの動的領域に長さ size の連続空間を n 個割り当てることです。関数は割り当てられた空間の先頭アドレスを指すポインタを返します。割り当てが成功しない場合は NULL を返します。 calloc 関数を使用すると、1次元配列のために動的記憶領域を確保できます。n は配列の要素数、各要素の長さは size です。

(3)free 関数

void free(void *p);

その作用は、p が指すメモリ領域を解放し、この部分のメモリ領域を他の変数が使用できるようにすることです。p は最後に calloc または malloc 関数を呼び出したときに返された値です。free 関数に戻り値はありません。 注意してください:以前のCバージョンが提供する malloc 関数と calloc 関数は、文字型データを指すポインタを返しました。ANSI C が提供する malloc 関数と calloc 関数は void * 型と規定されています。

7.4 動的連結リストの作成

動的リンクリストの構築とは、プログラムの実行中にゼロからリンクリストを作り上げることです。つまり、ノードを1つずつ確保し、各ノードのデータを入力し、前後が連結された関係を構築することです。

例

#include <stdio.h> #include <stdlib.h> #define NULL 0 #define LEN sizeof(struct student) struct student { long num; float score; struct student *next; }; struct student *create() { struct student *p1, *p2, *head; int num; float score; int n = 0; head = NULL; p1 = p2 = (struct student *)malloc(LEN); printf("please input num and score.\n"); scanf("%d,%f", &p1->num, &p1->score); while(p1->num != 0) { n ++; if(n == 1) head = p1; else p2->next = p1; p2 = p1; p1 = (struct student *)malloc(sizeof(struct student)); printf("please input num and score.\n"); scanf("%d,%f", &p1->num, &p1->score); } p2->next = NULL; return head; } void printlist(struct student *head) { struct student *p; p = head; if(head != NULL) { do { printf("num=%d score=%f\n", p->num, p->score); p = p->next; }while(p != NULL); } } void main() { struct student *head; head = create(); printlist(head); system("pause"); }

以下はリンクリストに対する各種操作です

リンクリストの出力:

void printlist(struct student *head)
{
    struct student *p;
    p = head;

    if(head != NULL)
    {
        do 
        {
            printf("num=%d score=%5.2f\n", p->num, p->score);
            p = p->next;
        } while (p != NULL);
    }
    /* while(p -> next != NULL)
    {
        printf("num=%d score=%f\n", p->num, p->score);
        p = p->next;
    }*/
}
ノードの削除:
struct student *delNode(struct student *head, int num)
{
    printf("delNode.\n");
    struct student *p1, *p2;
    if(head == NULL)
    {
        printf("The List is NULL.\n");
    }
    else
    {
        p1 = head;
        while(p1->next != NULL && p1->num != num)
        {
            p2 = p1;
            p1 = p1->next;
        }
        if(p1->num == num)
        {
            if(p1 == head)
                head = p1->next;
            else
                p2->next = p1->next;
        }
        else
            printf("Can not find list num.\n");
    }
    return head;
}

ノードの更新:

struct student *update(struct student *head, int index, int num, float score)
{
    printf("update.\n");
    struct student *p;
    if(head == NULL)
    {
        printf("The List is NULL.\n");
    }
    else
    {
        p = head;
        while(p->next != NULL && p->num != index)
        {
            p = p->next;
        }
        if(p->num == index)
        {
            p->num = num;
            p->score = score;
        }
        else
            printf("Can not find list index.\n");
    }
    return head;
}

ノードの追加:

struct student *add(struct student *head, int index, int num, float score)
{
    printf("add.\n");
    struct student *p1, *p2, *p3;
    if(head == NULL)
    {
        printf("The List is NULL.\n");
    }
    else
    {
        p1 = p2 = head;
        while(p1->next != NULL && p1->num != index)
        {
            p1 = p1->next;
            p2 = p1;
        }
        if(p1->num == index)
        {
            p3 = (struct student *)malloc(LEN);
            p3->num = num;
            p3->score = score;

            if(p2->next == NULL)
            {
                p2->next = p3;
                p3->next = NULL;
            }
            else
            {
                p3->next = p2->next;
                p2->next = p3;   
            }
        }
        else
            printf("Can not find list index.\n");
    }
    return head;
}

原文リンク:http://www.cnblogs.com/qkhhxkj/archive/2011/06/28/2091818.html

参考書籍:譚浩強先生著『Cプログラム設計』における構造体タイプに関する章。