カスタムタイプの詳細説明

目次

構造

1.1 構造の基礎知識

1.2 構造体宣言

1.3 特記事項

1.4 構造の自己参照

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

1.6 構造体メモリのアラインメント

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

1.8 構造パラメータの受け渡し

2 つのセグメント

2.1 ビットセグメントとは

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

2.3 ビット セグメントに関するクロスプラットフォームの問題

3つの列挙

3.1 列挙型の定義

3.2 列挙の利点

フォーユニオン(コミュニティ)

4.1 共用体型の定義

4.2 ユニオンサイズの実装


感動的なセッション

人がつま先立ちで太陽に近づこうとすると、全世界が太陽の光を遮ることができません。


この章の要点

        C 言語には、char、short、int、long、long long、double、float などの組み込み型に加えて、構造体、列挙型、共用体などのカスタム型もあります。

構造体:構造体型の宣言 自己参照構造体 変数の定義と初期化 構造体メモリのアライメント パラメータを渡す構造体 ビット セグメントの構造体の実装 (ビット セグメントの埋め込みと移植性)

列挙型:列挙型の定義 列挙型の利点 列挙型の使用

ユニオン:ユニオン タイプの定義 ユニオンの特性 ジョイント サイズの計算


構造

1.1 構造の基礎知識

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

1.2 構造体宣言

#include <stdio.h>

struct stu//struct是结构体关键字,stu是结构体标签
{
	//这里面放成员列表
	char name[10];
	char sex[5];
	int age;
	int height;
}s2,s3,s4;//这里的s2,s3,s4也是结构体变量,是一个全局变量

struct stu s5;//这里的s5是结构体变量,是一个全局变量

int main()
{
	struct stu s1 = { "xianming", "nan", 18, 43 };//这里的s1就是结构体变量,是一个局部变量
	return 0;
}

1.3 特記事項

無名構造体型 

#include <stdio.h>

struct
{
	char c;
	int a;
	double d;
}e;//结构体变量必须在这里命名,而且只能使用一次

struct
{
	char c;
	int a;
	double d;
}* ps;

int main()
{
	ps = &e;//在这里是不能这样用的,会出现错误,即使结构体成员一模一样也不可以
    //编译器认为等号两边是不同的结构体类型,所以这种写法是错误的
	return 0;
}

  コンパイラは、上記の 2 つの構造体宣言を 2 つの完全に異なる型として扱います。だから違法です

構造タグを省略すると、コンパイラは 2 つの構造が異なるものと見なします。(ただし、構造体のラベルを省略した後の 2 つの構造体メンバーは同じです)

1.4 構造の自己参照

 自己参照の正しい方法は次のとおりです。

struct Node
{
	int m;
	struct Node* next;
};
#include <stdio.h>

typedef struct Node//重命名这个结构体为Node
{
	int data;
	struct Node* next;
}Node;
//这种方法尽量不要使用,不建议
int main()
{
	struct Node s2 = { 0 };//这种写法是可以的
	Node s1 = { 0 };//这种写法也是可以的
	return 0;
}

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

意味: 

struct stu    
{
	char name[20];
	int age;
	float score;
};
int main()
{
	struct stu s;
	return 0;
}
struct stu    
{
	char name[20];
	int age;
    float score;
}s1, s2;//这里的s1 s2 和s一样,也是结构体变量,(全局的)
struct stu s3;//定义一个初始化变量 全局变量
int main()
{
	struct stu s;//s就是结构体变量,struct stu 就是和int char float 一样的类型,但是却是局部的
	return 0;
}

初期化:

struct stu    
{
	char name[20];
	int age;
	float score;
};

struct stu s2 = { "xiaohong", 20, 97.5f };

int main()
{
	struct stu s = { "xiaoming", 20, 97.5f };//97.5 后面加f说明是float类型,不加的话,会默认为为double类型
	printf("%s %d %f", s.name, s.age, s.score);
	return 0;
}
结构体嵌套初始化
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = {10, {4,5}, NULL}; 

struct Node n2 = {10, {4,5}, NULL};

1.6 構造体メモリのアラインメント

この知識ポイントはより重要です

構造体のサイズの計算も、特に一般的なテスト ポイントです: 構造体のメモリ アラインメント

コード 1 は次を示します。

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

操作の結果: 12

#include <stdio.h>
#include <stddef.h>

struct S1
{
    char c1;
    int i;
    char c2;
};
//offsetof(a,b)计算的是返回的是size_t,a代表结构体类型名,b代表结构体成员名,头文件是<stddef.h>
//偏移量,第一个位置的偏移量是0.
int main()
{
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", offsetof(struct S1, c1));
	printf("%d\n", offsetof(struct S1, i));
	printf("%d\n", offsetof(struct S1, c2));
	return 0;
}

印刷結果:12 0 4 8 

 c1 は最初のメンバーなので 0 です。2 番目のメンバーのアラインメントは 4 で、4 から始まります。int は 4 バイトなので、4 つの位置を占有し、c2 のアラインメントは 1 で、8 は1 の倍数なので 8 です。このときのサイズは 9 です (まだ 0 のサイズがあるため)。最終的なサイズは最大アラインメント数の整数倍なので、12 バイトです。

配置規則

1. 最初のメンバは、構造体変数からのオフセットが 0 のアドレスにあります。2. その他のメンバ変数は、ある数の整数倍のアドレス
にアラインする必要があります(アラインメント番号)。整列= コンパイラのデフォルトの整列とメンバー サイズの小さい方の値。(例えばメンバはint4、8と比べて4を選択、というわけで)        VSのデフォルトのアライメント数は8で、Linuxにはデフォルトのアライメント数が無いので、アライメント数はメンバ自体のサイズ3 .構造体の合計サイズ最大アラインメントの整数倍(各メンバー変数にはアラインメントがあります) . 4. 構造体がネストされている場合、ネストされた構造体は最大アラインメントの整数倍にアラインされ、ネストされた構造体のサイズに応じて、対応するバイト数を占有します.構造体の全体のサイズは、すべての整数倍です.最大アラインメント (ネストされた構造のアラインメントを含む)。


注: 整列してスペースを節約できるようにするため。スペースの無駄遣いを防ぐため、狭いスペースを占有するメンバーを可能な限り集めることができます。

(各メンバーは、0 から始まるアライメント番号を確認する必要があり、合計サイズ (サイズに 0 を加えた値) は、最大アライメント番号の倍数です)

なぜメモリ アライメントが存在するのですか?
ほとんどの参考資料では次のように述べられています:
1. プラットフォームの理由 (実装の理由):
すべてのハードウェア プラットフォームが任意のアドレスのデータにアクセスできるわけではありません。そうし
ないと、ハードウェア例外がスローされます。
2. パフォーマンス上の理由: (32 ビット プラットフォーム、4 バイトおよび 4 バイト読み取り)
データ構造 (特にスタック) は、可能な限り自然な境界に揃える必要があります。
その理由は、アラインされていないメモリにアクセスするには、プロセッサが 2 回のメモリ アクセスを行う必要があるのに対し、アラインされたメモリ アクセスでは 1 回のアクセスしか必要としないためです

一般に、
構造体のメモリ アラインメントは、空間を時間と交換する方法です。

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

 #pragma この前処理ディレクティブは、デフォルトの配置を変更できます

#include <stdio.h>
#pragma pack(2)  //设置默认对齐数为2
struct S1
{
	char c1;
	int i;
	char c2;
};
#pragma pack()   //取消设置的默认对齐数,还原为默认

int main()
{
	printf("%d\n", sizeof(struct S1));
	return 0;
}

印刷結果: 8

変わらず、12.

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

1.8 構造パラメータの受け渡し

  構造体がパラメーターとして渡される場合、構造体のアドレスを渡す必要があります。

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

 関数がパラメーターを渡す場合、パラメーターをスタックにプッシュする必要があります。これにより、時間とスペースのシステム オーバーヘッドが発生します。

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

2 つのセグメント

ビット フィールドを実装する構造体の機能

2.1 ビットセグメントとは

(1) ビット フィールドのメンバは、int、unsigned int、または signed int でなければなりません。(char型でも可)
(2)ビットフィールドのメンバ名の後にコロンと数字がある

#include <stdio.h>
struct A
{
	int _a : 2;//_a需要2个比特位
	int _b : 5;//_b需要5个比特位
	int _c : 10;//_c需要10个比特位
	int _d : 30;//_d需要30个比特位
};  //这就是一个位段
int main()
{
    printf("%d\n", sizeof(struct A);
    return 0;
}

操作の結果: 8

  上のコードビット部分は、まずint型なので4バイト(32ビット)空けていて、1行目は2ビット、2行目は5ビット、3行目は10ビットを使用しています。となり、この時点で15ビット残っていますが、4行目はint型なので30ビット必要なので4バイト空けています。したがって、合計 8 バイトです。

1回目のオープンの残り15バイトを使って2回目にオープンした32ビットのうちの15ビットを使うか、2回目にオープンしたスペースの30バイトをそのまま使うかというと、これはC言語ではなくで定義されています。

include <stdio.h>

struct S
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};

int main()
{
	printf("%d\n", sizeof(struct S));
	return 0;
}

コード実行結果: 3

ここで推測できるのは、 VS2019では、前回開いたスペースが十分ではなく、破棄されて役に立たず、新しく開いたスペースが直接使用されたということです。 

2.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;
};

int main()
{
	struct S s = { 0 };
	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	return 0;
}

メモリ上のビットセグメントメンバの配置に関して、C言語には明確な規定はなく、コンパイラに依存します.VS2019は上図のようになります.

2.3 ビット セグメントに関するクロスプラットフォームの問題

1. intビットフィールドが符号付き数値と見なされるか符号なし数値と見なされる かは不明です。
2. ビット フィールドの最大ビット数を特定できません。( 16ビット マシンは 16 まで 32ビット マシンは32まで、 27と表記されます。16ビットマシンでは問題が発生します。(初期の 16 ビット プラットフォームでは、sizeof (int) のサイズは 16 ビットです。 、現在の 32 ビット プラットフォームおよび 64 ビット プラットフォームでは 32 ビットです。書き込まれた数が 16 を超える場合、32 ビットまたは 64 ビット プラットフォームに配置するのは問題があります)
3. ビット セグメントのメンバがメモリ内で左から右に割り当てられるか、右から左に割り当てられるかはまだ定義されていません。
4. 構造体に 2 つのビット セグメントが含まれ、2 番目のビット セグメントが大きすぎて最初のビット セグメントの残りのビットを収容できない場合、残りのビットを破棄するか利用するかは不明です。

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

ビット セグメント自体はクロスプラットフォームではありません。

3つの列挙

列挙は、名前が示すように、可能な値を列挙する列挙です。(enum は定数)
週は月曜から日曜までで、限定されており、1つずつリストできます。

3.1 列挙型の定義

#include <stdio.h>
enum Day
{
	//枚举的可能取值
	Mon,
	Tues,
	Wed,
	Thir,
	Fri,
	Sta,
	Sun
};
int main()
{
	enum Day d = Sun;
	printf("%d\n", Mon);
	printf("%d\n", Tues);
	printf("%d\n", Wed);
	return 0;
}

印刷された構造は次のとおりです。0 1 2。

上で 定義した列挙型 Day は列挙型であり、{} 内の内容は列挙型の可能な値であり、列挙型定数と呼ばれます。
これらの可能な値はすべて価値があり、デフォルトで 0 から始まり、順番に 1 を追加するか、定義時に初期値を割り当てます。

#include <stdio.h>

enum Day
{
	//枚举的可能取值
	Mon = 2,
	Tues = 3,
	Wed,
	Thir,
	Fri,
	Sta,
	Sun
};

int main()
{
	enum Day d = Sun;
	printf("%d\n", Mon);
	printf("%d\n", Tues);
	printf("%d\n", Wed);
	return 0;
}

印刷結果は次のとおりです: 2 3 4

#include <stdio.h>

enum Day
{
	//枚举的可能取值
	Mon = 2,
	Tues,
	Wed = 3,
	Thir,
	Fri,
	Sta,
	Sun
};

int main()
{
	enum Day d = Sun;
	printf("%d\n", Mon);
	printf("%d\n", Tues);
	printf("%d\n", Wed);
	return 0;
}

印刷結果: 2 3 3

列挙型は定数です。列挙型が定義された後は(割り当てられているかどうかに関係なく)、可能な値の値を変更することはできません。例: Mon= 3; (値を変更したい場合は、列挙を定義するときにのみ割り当てることができます)

#include <stdio.h>

enum Day
{
	//枚举的可能取值
	Mon,
	Tues,
	Wed,
	Thir,
	Fri,
	Sta,
	Sun
};

int main()
{
	enum Day s = Mon;// 定义的变量只能是,枚举里面的元素,不能是数字
//只能用枚举常量给枚举变量赋值
	printf("%d\n", Mon);
	printf("%d\n", Tues);
	printf("%d\n", Wed);
	printf("%d\n", s);//0
	printf("%d\n", sizeof(s));//因为是int类型,所以是4
	return 0;
}

3.2 列挙の利点

1. コードの可読性と保守性を高める
2. #define で定義された識別子と比較して、列挙にはより厳密な型チェックがあります。
3. ネーミング汚染防止(カプセル化)
4. デバッグが容易
5.使いやすく、一度に複数の定数を定義できます

フォーユニオン(コミュニティ)

4.1 共用体型の定義

Union は特殊なカスタム タイプです。このタイプによって定義される変数には、一連のメンバーが含まれており、これらのメンバーが同じ空間を共有するという特徴があります。

#include <stdio.h>

union Un
{
	char c;
	int i;
};

int main()
{
	union Un u;
	printf("%d\n", sizeof(u));
	printf("%p\n", &u);
	printf("%p\n", &(u.c));
	printf("%p\n", &(u.i));
	return 0;
}

印刷結果: 4 007D9BC 007D9BC 007D9BC

開始アドレスは同じで、1 つのアドレスを共有しているため、4 バイトです。(ただし、i と c はこのアドレスを一緒に使用できません)

共用体のメンバーは同じメモリ空間を共有し、共用体のサイズは少なくとも最大のメンバーのサイズです (共用体には少なくとも最大のメンバーを保存する機能が必要なためです。

演習: 現在のコンピューター ストレージのサイズとサイズを確認する

知識ポイント:下位アドレスに下位ビットを入れるのがリトルエンディアン、上位アドレスに下位ビットを入れるのがビッグエンディアン

通常の書き込み:

#include <stdio.h>

int cheak_sys()
{
	int a = 1;//00 00 00 01(16进制)
	return *((char*)&a);
}

int main()
{
	int ret = 0;
	ret = cheak_sys();
	if (ret == 1)
		printf("小端\n");
	else
		printf("大端\n");
	return 0;
}

ユニオン方式で書く:

#include <stdio.h>

int cheak_sys()
{
	union Un
	{
		char c;
		int i;
	}u;
	u.i = 1;
	return u.c;
}

int main()
{
	int ret = 0;
	ret = cheak_sys();
	if (ret == 1)
		printf("小端\n");
	else
		printf("大端\n");
	return 0;
}

印刷結果: リトルエンディアン

4.2 ユニオンサイズの実装

共用体のサイズは、少なくとも最大のメンバーのサイズです。
最大メンバー サイズが最大位置合わせの整数倍でない場合は、最大位置合わせの整数倍に位置合わせする必要があります。
(構造と同じ) ただし、開始アドレスが同じであることに注意してください
#include <stdio.h>
union Un1
{
	char c[5];
	int i;
};
union Un2
{
	short c[7];
	int i;
};
int main()
{
	printf("%d\n", sizeof(union Un1));//8
	printf("%d\n", sizeof(union Un2));//16
	return 0;
}

カスタムタイプはこれにて終了!! !

おすすめ

転載: blog.csdn.net/m0_57388581/article/details/125882063