【C言語】カスタム型:構造体、列挙体、共用体

目次

1. 構造

        1. 構造体の型の宣言

        2. 構造の自己参照

        3. 構造体変数の定義と初期化

        4. 構造メモリの調整

        5. 構造体パラメータの受け渡し

        6. ビットセグメントを実装した構造(ビットセグメントの充填と移植性)

2. 列挙

        1. 列挙型の定義

        2. 列挙の利点

        3. 列挙型の使用

3、組合

        1.共用体型の定義

        2. ユニオンの特徴

        3. ジョイントサイズの計算

4. まとめ


1. 構造

        1. 構造体の型の宣言

        1.1 構造の基礎知識

構造体はメンバー変数と呼ばれる値の集合であり、構造体の各メンバーは異なる型の変数にすることができます。

         1.2 構造体の宣言

struct tag
{
   member-list;
}variable-list;

        たとえば、学生について次のように説明します

struct Stu
{
    char name[20];//名字
    int age;//年龄
    char sex[5];//性别
    char id[20];//学号
}; //分号不能丢

        1.3 特別な声明

        構造体を宣言する場合、不完全に宣言することもできます

        例えば:

//匿名结构体类型
struct
{
    int a;
    char b;
    float c;
}x;

struct
{
    int a;
    char b;
    float c;
}a[20], *p;

        上記 2 つの構造体は、宣言時に構造体タグを省略しています。

        それで、ここに問題があるでしょうか?

//上記のコードに基づくと、次のコードは合法ですか? p = &x;        

        警告:コンパイラは、上記の 2 つの宣言を 2 つの完全に異なる型として扱います。したがって、それは違法です

        2. 構造の自己参照

        構造体自体の型のメンバーを構造体に含めても問題ありませんか?

//代码1
struct Node
{
    int data;
    struct Node next;
}; //可行否?

如果可以,那sizeof(struct Node)是多少?

        答えは、無限ループを実行するということですが、この書き方は間違っています。

        自己言及の正しい方法:

//代码2
struct Node
{
    int data;
    struct Node* next;
};

        ポインタを使用すると、ポインタの空間サイズが固定数であるため、無限ループを引き起こすことなく自己参照できます。

//代码3
typedef struct
{
    int data;
    Node* next;
}Node;
//这样写代码,可行否?

//解决方案:
typedef struct Node
{
    int data;
    Node* next;
}Node;

        3. 構造体変数の定義と初期化

        構造体型を使用すると、変数の定義方法が実際には非常に簡単になります。

struct Point
{
    int x;
    int y;
}p1;    //声明类型的同时定义变量p1

struct Point p2;    //定义结构体变量p2

//初始化:定义变量的同时赋初值

 struct Point p3 = {x, y};

struct Stu //类型声明
{
    char name[15]; //名字
    int age;  //年龄
};

struct Stu s = {"zhangsan", 20}; //初始化

struct Node
{
    int data;
    struct Point p;
    struct Node* next;
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化

struct Node n2 = {20, {5, 6}, NULL}; //结构体嵌套初始化

        4. 構造メモリの調整

        構造物の基本的な使い方をマスターしました

次に、構造のサイズの計算という        問題について詳しく説明します。

        これも特に人気のあるテスト ポイントです。構造メモリのアライメントです。

        以下にいくつかの実践例を示します

#include<stdio.h>
int main()
{
	struct S1
	{
		char c1;
		int i;
		char c2;
	};
	printf("%d\n", sizeof(struct S1));
}

         構造の入れ子の問題:

#include<stdio.h>
struct S3
{
	double d;
	char c;
	int i;
};
int main()
{
	struct S4
	{
		char c1;
		struct S3 s3;
		double d;
	};
	printf("%d\n", sizeof(struct S4));
}

 テストポイント

計算方法は?

まず、構造の配置規則をマスターする必要があります。

        1. 最初のメンバーは、構造体変数からのアドレス オフセット 0 にあります。

        2. その他のメンバ変数は、ある数値(アライメント番号)の整数倍のアドレスにアライメントされている必要があります。アライメント番号 = コンパイラのデフォルトのアライメント番号とメンバーのサイズの小さい方。VS のデフォルト値は 8 です。Linux にはデフォルトのアライメント番号はありません。アライメント番号はメンバー自体のサイズです。

        3. 構造体の合計サイズは、最大アライメント番号の整数倍です (各メンバー変数にはアライメント番号があります)。

        4. 構造体がネストされており、そのネストされた構造体がその最大アライメント数の整数倍にアライメントされている場合、構造全体のサイズは、すべての最大アライメント数 (ネストされた構造体のアライメント数を含む) の整数倍になります。

        なぜメモリアライメントが存在するのでしょうか?

        ほとんどの参考資料には次のように書かれています。

        1. プラットフォームの理由 (移植の理由):

         すべてのハードウェア プラットフォームが任意のアドレスのデータにアクセスできるわけではありません。一部のハードウェア プラットフォームは、特定のアドレスで特定の種類のデータしかフェッチできません。そうでない場合は、ハードウェア例外がスローされます。

        2. パフォーマンス上の理由:

        データ構造 (特にスタック) は、可能な限り自然な境界に揃える必要があります。その理由は、アライメントされていないメモリにアクセスするには、プロセッサが 2 回のメモリ アクセスを行う必要があるのに対し、アライメントされたメモリ アクセスには 1 回のアクセスだけで済むためです。

        一般的に:

        構造の記憶位置合わせは、空間を時間と交換します。

        構造を設計するときは、位置合わせを満たすだけでなく、スペースを節約することも必要です。

        占有スペースの少ないメンバーをできるだけ一緒に保ちます。

#include<stdio.h>
int main()
{
	//例如:
	struct S1
	{
		char c1;
		int i;
		char c2;
	}s1;
	struct S2
	{
		char c1;
		char c2;
		int i;
	}s2;
	printf("%d\n%d", sizeof(s1), sizeof(s2));
}

 タイプ S1 と S2 のメンバーはまったく同じですが、S1 と S2 が占めるスペースのサイズにいくつかの違いがあります。

        デフォルトのアライメント番号を変更する

#pragma前処理ディレクティブについては前に説明しましたが、ここでもこれを再度使用して、デフォルトのアライメント番号を変更します。

#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
    char c1;
    int i;
    char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1
struct S2
{
    char c1;
    int i;
    char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
    //输出的结果是什么?
    printf("%d\n", sizeof(struct S1));
    printf("%d\n", sizeof(struct S2));
    return 0;
}

         構造体のアライメントが不適切な場合は、デフォルトのアライメント番号を自分で変更できます。

        5. 構造体パラメータの受け渡し

       直接コードのデモンストレーション:

struct S
{
	int data[1000];
	int num;
};
struct S s = { {1,2,3,4}, 1000 };
//结构体传参
void print1(struct S s)
{
	printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
	printf("%d\n", ps->num);
}
int main()
{
	print1(s);  //传结构体
	print2(&s); //传地址
	return 0;
}

        上記の print1 関数と print2 関数のどちらが優れていますか?

        答えは、print2 関数が優先されるということです。

       理由:

        関数がパラメータを渡すとき、パラメータをスタックにプッシュする必要があるため、時間と空間のシステム オーバーヘッドが発生します。

        構造体オブジェクトが渡され、その構造体が大きすぎる場合、パラメータをスタックにプッシュするシステムのオーバーヘッドが比較的大きくなり、パフォーマンスの低下につながります。

        6. 構造実装ビットセグメント

        6.1 ビットセグメントとは何ですか?

        ビット フィールドの宣言と構造は似ていますが、次の 2 つの違いがあります。

1. ビットフィールドのメンバーは、int、unsigned int、または signed int である必要があります。

2. ビットフィールドのメンバー名の後ろにコロンと数字が続きます。

        例えば:

#include<stdio.h>
int main()
{
	struct A
	{
		int _a : 2;
		int _b : 5;
		int _c : 10;
		int _d : 30;
	};
	printf("%d", sizeof(struct A));
	return 0;
}

         6.2、ビットセグメントのメモリ割り当て

1. ビット フィールドのメンバーは、int unsigned int signed int または char (整数ファミリーに属する) 型にすることができます。

2. ビットフィールドのスペースは、必要に応じて 4 バイト (int) または 1 バイト (char) の形式で割り当てられます。

3. ビットセグメントは不確定要素が多く、クロスプラットフォームではないため、移植性を重視するプログラムではビットセグメントの使用を避けてください。

//一个例子
struct S
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};
struct S s = { 0 };
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
//空间是如何开辟的?

         6.3、ビットセグメントにおけるクロスプラットフォームの問題

1. int ビットフィールドが符号付き数値とみなされるか、符号なし数値とみなされるかは不明です。

2. ビットフィールドの最大ビット数は決定できません。(16 ビット マシンの最大数は 16、32 ビット マシンの最大数は 32 です。27 と記述すると、16 ビット マシンで問題が発生する可能性があります。

3. ビット セグメントのメンバーがメモリ内で左から右に割り当てられるか、右から左に割り当てられるかはまだ定義されていません。

4. 構造体に 2 つのビット フィールドが含まれており、2 番目のビット フィールドのメンバーが大きく、最初のビット フィールドの残りのビットを収容できない場合、残りのビットを破棄するか使用するかがわかりません。

要約:

        構造と比較して、ビット セグメントは同じ効果を達成でき、スペースを大幅に節約できますが、クロスプラットフォームの問題があります。

          

2. 列挙

        1. 列挙型の定義

        列挙とはその名の通り、一つ一つ列挙することです。

可能な値を1 つずつ        リストします

        たとえば、私たちの実生活では次のようになります。

月曜日から日曜日までの 7 日間は 1 週間に限られており、1 つずつリストできます。

性別には男性、女性、秘密、1 つずつリストすることもできます

月は12か月あり、1つずつリストすることもできます。

        ここで列挙を使用できます。

enum Day//星期
{
	Mon,
	Tues,
	Wed,
	Thur,
	Fri,
	Sat,
	Sun
};
enum Sex//性别
{
	MALE,
	FEMALE,
	SECRET
};
enum Color//颜色
{
	RED,
	GREEN,
	BLUE
};

        上記で定義された列挙型 Day、列挙型 Sex、および列挙型 Color はすべて列挙型です。

        {} 内の内容は列挙型の可能な値であり、列挙定数とも呼ばれます

        これらの可能な値はすべて有効であり、デフォルトでは 0 から始まり、順番に 1 ずつ増加します。もちろん、列挙型を宣言するときに初期値を割り当てることもできます。例えば:

enum Color//颜色
{
	RED = 1,
	GREEN = 2,
	BLUE = 4
};

        2. 列挙の利点

        なぜ列挙型を使用するのでしょうか?

        #define を使用して定数を定義できますが、なぜ列挙型を使用する必要があるのでしょうか? 列挙型の利点:

1. コードの可読性と保守性の向上

2. #define で定義された識別子と比較して、列挙型にはより厳密な型チェックが行われます。

3. デバッグが簡単

4. 使いやすく、複数の定数を一度に定義可能

        3. 列挙型の使用

enum Color//颜色
{
	RED = 1,
	GREEN = 2,
	BLUE = 4
};
enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
clr = 5;               //ok??

        clr=5 ではデータ型が異なるためエラーが発生します。

3、組合

        1.共用体型の定義

ユニオンも特殊カスタムタイプ

この型で定義される変数にも一連のメンバーが含まれており、これらのメンバーが同じ空間を共有するのが特徴です(共用体は共用体とも呼ばれます)。例えば:

//联合类型的声明
union Un
{
	char c;
	int i;
};
//联合变量的定义
union Un un;
//计算连个变量的大小
printf("%d\n", sizeof(un));

現在のコンピューターのストレージのビッグ エンディアンとスモール エンディアンを決定するなど、いくつかの使用例もあります。

#include<stdio.h>
//联合类型的声明
union Un
{
	char c;
	int i;
};
//联合变量的定义
int main()
{
	union Un un;
	un.i = 1;
	//计算连个变量的大小
	if (un.c == 1)
	{
		printf("小端存储");
	}
	else printf("大端存储");
	return 0;
}

        3. ジョイントサイズの計算

共用体のサイズは少なくとも最大のメンバーのサイズである

最大メンバー サイズが最大アライメント数の整数倍ではない場合、最大アライメント数の整数倍にアライメントする必要があります。

       例えば:

#include<stdio.h>
int main()
{
	union Un1
	{
		char c[5];
		int i;
	};
	union Un2
	{
		short c[7];
		int i;
	};
	//下面输出的结果是什么?
	printf("%d\n", sizeof(union Un1));
	printf("%d\n", sizeof(union Un2));
	return 0;
}

4. まとめ

       この章には多くの知識と包括的な内容が含まれており、深く探求するのに非常に適しており、復習教材として非常に良い選択です。

おすすめ

転載: blog.csdn.net/m0_71676870/article/details/131764033