【C言語学習ノート---カスタムタイプ】

C言語のアドバンストカスタムタイプ

はじめに:
この記事では、C 言語の文字列関数とメモリ関数に関する高度な知識を通じて、C 言語のカスタム型について学び続けます。

/知識ポイントのまとめ/
C 言語のカスタム型
1. 構造
2. 列挙
3. 共用体

1. 構造

構造体: 値の集合です。これらの値はメンバー変数と呼ばれます。構造体の各メンバーは、異なる型の変数にすることができます。
配列: 同じ型の要素の集合です。

1.1. 構造体の宣言

形式:

struct tag
{
    
    
	member list; //成员列表
}variable list; //结构体变量
#include <stdio.h>

struct Student
{
    
    
	char name[20];
	int age;
	char sex[5];
	float score;
}s1,s2,s3;//三个结构体变量 --- 全局变量

struct Book
{
    
    
	char name[20];
	char author[12];
	float price;
};
struct Book
{
    
    
	char name[20];
	char author[12];
	float price;
}b1;
int main()
{
    
    
	struct Student s4, s5, s6;//三个结构体变量 ---局部变量
	return 0;
}

構造体の特別な宣言: 構造体匿名

#include <stdio.h>
struct Student
{
    
    
	char name[20];
	int age;
	char sex[5];
	float score;
}s1,s2,s3;//三个结构体变量 --- 全局变量

//一种特殊声明,匿名声明,只会执行一次。一次性
struct 
{
    
    
	char name[20];
	char author[12];
	float price;
}b1;
struct 
{
    
    
	char name[20];
	char author[12];
	float price;
}*p;
int main()
{
    
    
	struct Student s4, s5, s6;//三个结构体变量 ---局部变量
	
	//不建议使用匿名写法
	p = &b1;
	return 0;
}

1.2. 構造の自己参照

構造体の自己参照: 構造体の中に、自身の型の構造体へのポインタが含まれていることを意味します
誤った記述例:

struct Node
{
    
    
	int data;//数据
	struct Node n;//下一个节点
};

正しい引用方法

//正确自引用写法1:
struct Node
{
    
    
	int data;//数据
	struct Node* n;//下一个节点
};

//正确写法2:
typedef struct Node
{
    
    
	int data;//数据
	struct Node* n;//下一个节点
}Node;

要約:
構造体に同じ型の構造体を含めることは不可能ですが、同じ型のポインター構造体を含めることは可能です。

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

#include <stdio.h>
struct Point
{
    
    
	int x;
	int y;
}p1 = {
    
     1,2 };//初始化方式一
struct Point p3 = {
    
     4,5 };//初始化方式二
struct Stu
{
    
    
	char name[15];
	int age;
};
struct Node
{
    
    
	int data;
	struct Point p;
	struct NOde* next;
};
int main()
{
    
    
	int a = 3;
	int b = 4;
	struct Point p2 = {
    
     a,b };//初始化方式三
	
	struct Stu s = {
    
     "lisi",20 };//顺序初始化
	struct Stu s1 = {
    
     .age = 18,.name = "zhangsan" };//指定顺序或指定成员初始化
	printf("%s %d\n", s.name, s.age);
	printf("%s %d\n", s1.name, s1.age);

	struct Node n = {
    
     100,{
    
    3,4},NULL };
	printf("%d x=%d y=%d\n", n.data, n.p.x, n.p.y);
	return 0;
}

1.4. 構造メモリの調整

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

offsetof - マクロ - 直接使用可能
プロトタイプ: size_t offsetof( structName, memberName );
ヘッダー ファイル: <stddef.h>
機能: 開始位置と比較した構造体メンバーのオフセットを計算します

#include <stdio.h>
#include <stddef.h>
struct S1
{
    
    
	char c1;
	int i;
	char c2;
};
struct S2
{
    
    
	char c1;
	char c2;
	int i;
};
int main()
{
    
    
	//offsetof -- 计算起始位置的偏移量
	printf("%d\n", offsetof(struct S1, c1));//0
	printf("%d\n", offsetof(struct S1, c2));//8
	printf("%d\n", offsetof(struct S1, i));//4

	printf("%d\n", offsetof(struct S2, c1));//0
	printf("%d\n", offsetof(struct S2, c2));//1
	printf("%d\n", offsetof(struct S2, i));//4
	return 0;
}

概要:
構造体で定義された内容はまったく同じですが、バイト サイズが異なることがわかります
原理: 構造体のメモリ アライメント

1.5. 構造メモリのアライメントを調べる

1. どのように位置を合わせますか?
2. なぜ調整する必要があるのですか?

標準規定:
構造調整の規則:

1. 構造体のメンバは、構造体変数からオフセット 0 のアドレスに格納され始めます
2. 構造体の他のメンバ変数は、ある数値の整数倍のアドレスにアライメントされている必要があります (アライメント番号) ) に格納され、
アライメント番号 = コンパイラーに保管されます。デフォルトのアライメント番号とメンバーのサイズの小さい方です。

このうち、VS コンパイラのデフォルトは 8 ですが、Linux ではデフォルトはアライメント番号なしで、アライメント番号はメンバ自体のサイズです (gcc コンパイラでは)。

3. 構造体の合計サイズは、最大アライメント番号の整数倍です (各メンバー変数にはアライメント番号があります)。
4. 構造体がネストされている場合、ネストされた構造体は、それ自体の最大アライメント番号の整数倍にアライメントされます。 、構造全体のサイズは、最大アライメント数 (ネストされた構造のアライメント数を含む) の整数倍です。

1. どのように位置を合わせますか?
ルールによれば、アライメント オフセットは変数の型に関連付けられています。まず変数の型に基づいてオフセットが取得され、次に現在の VS コンパイラのデフォルトのアライメント番号 8 と比較され、最も小さいものが選択されます。変数のアライメント番号 (ストレージ) スペースによって開かれるスペースの量。
最後に、構造体の合計サイズは、構造体の全メンバーの最大アライメント数の整数倍でなければなりませんが、整数倍でない場合は、整数倍になるまでスペースが空き続けます。

#include <stdio.h>
struct S1
{
    
    
	char c1;
	char c2;
	int i;
};
struct S2
{
    
    
	char c1;
	int i;
	char c2;
};
struct S3
{
    
    
	double d;
	char c;
	int i;
};

struct S4
{
    
    
	char c1;
	struct S3 s3;
	double d;
};
int main()
{
    
    
	printf("%d\n", sizeof(struct S1));//8
	printf("%d\n", sizeof(struct S2));//12
	printf("%d\n", sizeof(struct S3));//16
	printf("%d\n", sizeof(struct S4));//32
	return 0;
}

2. なぜ調整する必要があるのですか?
(1) プラットフォームの理由:
すべてのハードウェア プラットフォームが任意のアドレスのデータにアクセスできるわけではありません。
一部のハードウェア プラットフォームは、特定のアドレスで特定の種類のデータしかフェッチできません。そうでない場合は、ハードウェア例外がスローされます
(2) パフォーマンスの理由:
データ構造 (特にその理由は、アライメントされていないメモリ
アクセスするには、プロセッサが 2 回のメモリ アクセスを行う必要があるのに対し、アライメントされたメモリ アクセスでは 1 回のアクセスだけで済むためです。

3. 一般的に言えば、構造の記憶の調整は、空間を時間に交換することを目的としています。
では、構造を設計するときに、位置合わせを満足させるだけでなくスペースも節約したい場合、どうすればよいでしょうか?
(1) スペースを取らないメンバ変数をできるだけまとめて配置します。

たとえば:

struct S1
{
    
    
	char c1;
	char c2;
	int i;
};
struct S2
{
    
    
	char c1;
	int i;
	char c2;
};

(2)、 #pragma を引用符、前処理命令、デフォルトのアライメント番号を変更します

#include <stdio.h>
struct S1
{
    
    
	char c1;
	char c2;
	int i;
};
#pragma pack(1)//设置默认对齐数为1
struct S2
{
    
    
	char c1;
	int i;
	char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为编译器默认值
int main()
{
    
    
	printf("%d\n", sizeof(struct S1));//8
	printf("%d\n", sizeof(struct S2));//6
	return 0;
}

概要:
したがって、構造体のアライメントが不適切な場合は、#pragma 前処理ディレクティブを使用してデフォルトのアライメント番号 (通常は偶数) を変更することもできます。

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

構造体パラメータの受け渡しの分類:値による構造体呼び出しとアドレスによる構造体呼び出し

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

概要:
1. 関数がパラメータを渡すとき、パラメータをスタックにプッシュする必要があるため、時間と空間のシステム オーバーヘッドが発生します 2.
構造体オブジェクトが渡され、その構造体が大きすぎる場合、プッシュのシステム オーバーヘッドが発生しますスタック上のパラメータが比較的大きくなるため、パフォーマンスの低下につながります。

2. 列挙

名前が示すように、データを 1 つずつリストし
、取り得る値を 1 つずつリストします。

たとえば、生活の中で 1 つずつリストできるデータ:
1. 週は 7 日あり、月曜から日曜日まで 1 つずつリストできる
2. 男性と女性の性別があり、それをリストできる3.
は 12 か月あり、1 つずつリストできます。

2.1. 列挙型の定義、使用、および初期化

#include <stdio.h>
//宏定义常量
#define MALE  1
#define FEMALE  2
#define SECRET  3
enum Sex
{
    
    
	//枚举的可能取值
	MALE,//注意,枚举常量以逗号隔开
	FEMALE,
	SECRET
};
enum Color
{
    
    
	//枚举的可能取值
	RED,
	GREEN,
	BLUE
};
enum Week
{
    
    
	Mon = 1,//赋初始值
	Tues = 2,
	wed = 3,
	Thur = 4,
	Fri = 5,
	sat = 6,
	Sun = 7
};
int main()
{
    
    
	//MALE = 5; ---- Error,因为枚举常量属于常量,不可以给常量赋值,常量是不可更改的
	printf("%d\n", MALE);//0
	printf("%d\n", FEMALE);//1
	printf("%d\n", SECRET);//2
	//枚举成员常量不赋予初值时,默认从0开始递增的

	//枚举类型的使用:
	enum Sex sex = SECRET;//相当于将一个常量赋值给变量sex
	//也类似于宏定义常量的使用
	printf("%d\n", sizeof(sex));//4,打印查看枚举常量的大小,整型4个字节
	
	return 0;
}

要約:
#define を使用して定数を定義できるのに、なぜ列挙型を使用するのでしょうか?
列挙型の利点:
1. コードの可読性と保守性が向上します。
2. #define で定義された識別子と比較して、列挙型には型チェックがあり、より厳密です。 3.
デバッグが簡単です
。 4. 使いやすく、一度に複数の定数を定義できます。

列挙型の使用の違い

#include <stdio.h>
enum Sex
{
    
    
	MALE,
	FEMALE,
	SECRET
};
int main()
{
    
    
	//枚举类型的使用:
	enum Sex sex = SECRET;
	//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异
	sex = 5;//会有类型的差异,比如在cpp条件下就会报错(enum类型 与 int类型不匹配,类型差异)
	return 0;
}

3. ユニオン(コミュニティ)

定義: Union も特殊なカスタム タイプであり、このタイプで定義される変数にも一連のメンバーが含まれており、これらのメンバーが同じ空間を共有することが特徴です (したがって、Union はコミュニティとも呼ばれます)。
共用体のキーワード:
共用体メンバーは構造体のメンバーと型は似ていますが、
複数の種類の共用体の特徴があります。
共用体のメンバーはメモリー空間を共有するため、共用体変数のサイズは少なくとも最大のメンバーのサイズになります。 (共用体は少なくとも最大のメンバーを保存できなければならないため)

#include <stdio.h>
union Un
{
    
    
	char c;//联合体成员跟结构体成员一样用分号隔开
	int i;
};
int main()
{
    
    
	union Un un;
	printf("%d\n", sizeof(un));//4

	//共用一块地址
	printf("%p\n", &un);//0073F6EC
	printf("%p\n", &(un.c));//0073F6EC
	printf("%p\n", &(un.i));//0073F6EC
	//所以当联合体成员被修改,可能导致其他的成员也跟着相应变化
	//所以在使用联合体成员时,不建议同时操作数据。
	return 0;
}

3.1. ビッグエンディアンとリトルエンディアンの判定の見直し - 共用体の適用

リトルエンディアンストレージ: 下位アドレスのデータを下位アドレスに、上位アドレスのデータを上位アドレスに格納 ビッグ
エンディアンストレージ: 下位アドレスのデータを上位アドレスに、上位アドレスのデータを上位アドレスに格納
書き込み方法1下位アドレスに格納

#include <stdio.h>
int check_sys()
{
    
    
	int a = 1;
	//if (*(char*)&a == 1)//&a类型是int* 
	//	return 1;
	//else
	//	return 0;
	return *(char*)&a;
}
int main()
{
    
    
	int ret = check_sys();
	if (ret == 1)
		printf("小端\n");
	else
		printf("大端\n");
	return 0;
}

書き方2:コンソーシアム申請

#include <stdio.h>
int check_sys()
{
    
    
	union Un//当Un省略就是匿名联合体,此时可以使用,因为这里联合体只需要执行一次与匿名结构体、匿名联合体的性质吻合,只执行一次
	{
    
    
		char c;
		int i;
	}u;
	u.i = 1;
	//因为i和c是联合体变量,所以共用一块内存空间
	//并且当给i赋值给1,以十六进制表示为:0x00 00 00 01
	//所以我们返回c的值,如果c等于0,则高地址数据存放在低地址处,
	//否则c为1,低地址处数据存放在低地址处
	return u.c;
}
int main()
{
    
    
	int ret = check_sys();
	if (ret == 1)
		printf("小端\n");
	else
		printf("大端\n");
	return 0;
}

3.2. コンソーシアムの規模の計算

1. 共用体のサイズは少なくとも最大メンバのサイズである
2. 最大メンバのサイズが最大アライメント数の整数倍でない場合は、最大アライメント数の整数倍にアライメントする必要がある。

#include <stdio.h>
union Un
{
    
    
	char c[5];//5
	int i;//4
};
int main()
{
    
    
	printf("%zd\n", sizeof(union Un));//8
	return 0;
}
#include <stdio.h>
union Un
{
    
    
	short c[7];//14
	int i;//4
};
int main()
{
    
    
	printf("%zd\n", sizeof(union Un));//16,共用一块地址且是4的倍数,对齐后16
	return 0;
}

概要:
コンソーシアムの一般的な使用シナリオは、一部のメンバーが同時にそれらを使用しないことです。
ユニオンを使用するとスペースを節約することもできます。

4. 結論

上記の内容を理解していただければ、C 言語の使用がプログラムの読みやすさと効率化にさらに役立ちます。
半エーカーの角砂糖が開かれ、天窓と雲の影が一緒に残ります。
運河がどこでこれほど透明になることができるのでしょうか? それは、生きた水の水源があるからです。-朱熹(本を読んだ感想)

おすすめ

転載: blog.csdn.net/m0_69455439/article/details/133238210