C言語では、私たちが一般的に使用するいくつかの基本的なデータ型に加えて、カスタム型と呼ばれる型もあります。例:学生について説明します。この学生には、名前、性別、年齢、身長などがあります。基本的なデータ型だけでは完全に説明することはできません。今回は、カスタムタイプを使用して説明します。カスタムタイプは、構造体、列挙型、および共用体です。
1.構造
構造体は値のコレクションであり、これらの値はメンバー変数と呼ばれ、各メンバー変数は異なるタイプを持つことができます。
1.ステートメント
ここでのstructtag// tagは、変数名ではなくタグを表します
{{
member-list;//メンバー変数
}variable-list ; //変数リスト。変数を定義するために使用されます。ここでセミコロンに注意してください
たとえば、人を説明するには、次のようにします。
struct person
{
char name[20];
char sex[5];
int age;
char nation[20];
};
ここでの変数リストはオプションです。変数リストで定義されている変数はグローバル変数です。
2、定義して初期化します
定義するには、変数リストまたはメイン関数で直接定義できます。
struct person
{
char name[20];
char sex[5];
int age;
char nation[20];
}person1;//结构体变量person1
struct person person2;//结构体变量person2
int main(void)
{
struct person person3;//结构体变量person3
struct person person4[2];//结构体数组变量person4,有两个元素,都是结构体类型
return 0;
}
初期化も非常に簡単です。
#include<stdio.h>
struct person
{
char name[20];
int age;
}person1 = {"zhangsan", 18};//结构体变量person1
struct person person2 = {"lisi", 18};//结构体变量person2
int main(void)
{
struct person person3 = {"wangwu", 18};//结构体变量person3
struct person person4[2] =
{"abc", 18, {"def", 18}};//加不加{}都可以
return 0;
}
場合によっては、構造もネストされます。
struct person
{
char name[20];
int age;
}person1 = {"zhangsan", 18};//结构体变量person1
struct people
{
struct person person;
char nation[20];
};
初期化:
struct people people = {{"zhangsan"、18}、"XXX"};//ネストされた構造体の初期化
構造を宣言する際にも、完全に宣言することはできず、現時点ではラベルを省略しています。
struct
{
int a;
char b;
float c;
}a;
タグを省略できない場合があります。つまり、typedefキーワードが使用されている場合です。typedefキーワードは、データ型の新しい名前を定義できます。省略した場合、エラーが報告されます。
typedef struct S
{
int data[1000];
int num;
}s;
s s1 = { {1,2,3,4}, 1000 };
3、構造体メンバーへのアクセス
構造体のメンバーへのアクセスには、ドット(。)演算子を使用してアクセスする必要があります。次に、これらの初期化された変数を出力しましょう。
#include<stdio.h>
struct person
{
char name[20];
int age;
}person1 = {"zhangsan", 18};//结构体变量person1
struct people
{
struct person person;
char nation[20];
};
struct person person2 = {"lisi", 18};//结构体变量person2
int main(void)
{
struct person person3 = { "wangwu", 18 };//结构体变量person3
struct person person4[2] =
{"abc", 18, {"def", 18}};//加不加{}都可以
struct people people = { {"zhangsan",18}, "XXX" };//嵌套结构体的初始化
printf("%s\n", person1.name);
printf("%d\n", person1.age);
printf("--------------\n");
printf("%s\n", person2.name);
printf("%d\n", person2.age);
printf("--------------\n");
printf("%s\n", person3.name);
printf("%d\n", person3.age);
printf("--------------\n");
printf("%s\n", person3.name);
printf("%d\n", person3.age);
printf("--------------\n");
printf("%s\n", person4[1].name);
printf("%d\n", person4[1].age);
printf("--------------\n");
printf("%s\n", people.person.name);
printf("%s\n", people.nation);
return 0;
}
それでは、構造体ポインタアクセスが指す変数のメンバーを見てみましょう。次のコードがあります。
struct Stu
{
char name[20];
int age;
};
void print(struct Stu* ps)
{
printf("name = %s age = %d\n", (*ps).name, (*ps).age);
//使用结构体指针访问指向对象的成员,为了简化,使用->操作符来替代(*).操作。
printf("name = %s age = %d\n", ps->name, ps->age);
}
int main()
{
struct Stu s = { "zhangsan", 20 };
print(&s);//结构体地址传参
return 0;
}
#include<stdio.h>
struct person
{
char name[20];
int age;
};
struct people
{
struct person* person;
char nation[20];
};
int main(void)
{
struct person person = { "zhangsan", 18 };
struct people people = { &person ,"XXX"};
printf("%s\n", people.person->name);
printf("%d\n", people.person->age);
printf("%s\n", people.nation);
return 0;
}
4.構造パラメータ
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;
}
パラメータを渡すときは、パラメータをスタックにプッシュする必要があります。構造体オブジェクトを渡す場合、構造体が大きすぎるため、パラメーターがスタックにプッシュされるときのシステムオーバーヘッドが比較的大きくなり、パフォーマンスが低下します。また、構造体のサイズを計算するときに、渡された構造体の値である場合、構造体のサイズが無限に大きくなります。したがって、構造体はパラメータを渡すときにアドレスを渡す必要があります。
5、構造体のメモリアライメント
それでは、構造体のメモリサイズについて説明しましょう。
5.1、次のコードを見て、そのサイズ(*)を計算します。
struct S1
{
char c1;
int i;
char c2;
};
int main(void)
{
printf("%d\n", sizeof(struct S1));
return 0;
}
この構造のサイズは、思ったように6バイトではなく、12バイトであることがわかりました。
構造メモリは次のように計算されます。
図1に示されるように、最初のメンバーは、構造変数からのオフセット0のアドレスにある。
2.他のメンバー変数は、アライメントツリーの整数倍であるアドレスにアライメントする必要があります。Alignment=コンパイラのデフォルトの配置とメンバーのサイズの小さい方。VSのデフォルト値は8で、一部のデフォルト値は4です。
3.構造体の合計サイズは、最大アライメント番号の整数倍です(各メンバー変数にはアライメント番号があります)。
4.構造体がネストされている場合、ネストされた構造体はそれ自体の最大整列数の整数倍に整列され、構造体の全体のサイズはすべての最大整列数(ネストされた構造体の整列数を含む)の整数になります。 。
簡単に言うと、メモリ内では、最初の変数は開始アドレス0に配置され、他のアドレスが配置される場所はアライメント番号によって異なります。アラインメントは、デフォルトのアラインメントとメンバーサイズの最小値です。配置番号に従って配置された後、この構造のサイズは最大配置番号の整数倍になります。
次のネストされた構造のサイズを計算します。
struct S1
{
char c1;//1
int i;//4
};//大小为8
struct S2
{
char c1;//1
struct S1 s1;//8
int d;//4
};
int main(void)
{
printf("%d\n", sizeof(struct S2));
return 0;
}
計算時に、アライメント番号の最大値の倍数で、メンバー変数のサイズを直接合計することはできません。たとえば、最初の計算(*)では、合計は6であり、アライメント番号(4)の整数倍を取ると、結果は8になります。これは、明らかに計算結果と一致しません。
最後のものを見てみましょう:
struct S
{
short s1;//2
char c1;//1
int s2;//4
};
int main(void)
{
printf("%d\n", sizeof(struct S));
return 0;
}
上手にやれば絵を描く必要はなく、直接計算するだけです。ショートとチャーが一緒に出会うこともありますが、現時点では直接4サイズとみなすことができます。
5.2、メモリアライメントが存在する理由:
1.プラットフォームの理由(移植の理由):すべてのハードウェアプラットフォームが任意のアドレスのデータにアクセスできるわけではありません。一部のハードウェアプラットフォームは、特定のアドレスの特定のタイプのデータのみをフェッチできます。そうしないと、ハードウェア例外がスローされます。
2.パフォーマンス上の理由:データ構造(特にスタック)は、可能な限り自然な境界に揃える必要があります。その理由は、アラインされていないメモリにアクセスするには、プロセッサが2回のメモリアクセスを行う必要があるためです。アラインされたメモリアクセスには1回のアクセスしか必要ありません。
一般的に:構造のメモリアライメントは、時間と空間を交換する方法です。この方法は非常に一般的です。
構造を設計する際には、位置合わせだけでなく、スペースを節約する必要があるため、小さなスペースを占めるメンバーをできるだけ集中させます。たとえば、(*)で順序を変更すると、2つのcharタイプを組み合わせると、この構造のサイズが変更されます。
5.3、デフォルトのアライメント番号を変更します
#pragma pack(1)//设置默认对齐数为1
struct S2
{
char c3;
int i2;
char c4;
};
int main()
{
printf("%d\n", sizeof(struct S2));//6
return 0;
}
デフォルトに戻します。
#pragma pack()//デフォルトのアライメント番号の設定を解除し、デフォルトに復元します
第二に、セグメント
1.定義とステートメント
構造体はビットフィールドを実装できます。ビットフィールドは構造体と同様に宣言されます。ビットフィールドは、構造体(または共用体)のメンバー変数が占めるスペースをビット単位で定義します。ビットセグメント構造を使用することで、スペースを節約できるだけでなく、操作も容易になります。たとえば、ビットセグメントA:
struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};
ビットフィールドのメンバー名の後には、コロンと数字が続きます。後続の数値はビット単位です。ビットフィールドのメンバーは整数ファミリである必要があります。
2、ビットセグメントのメモリ割り当て
ビットフィールドのスペースは、必要に応じて4バイト(int)または1バイト(char)で開かれます。前のビットセグメントA、_aはint型であり、4バイトで開かれます。では、A(32ビット)のサイズはどれくらいですか?
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
int main()
{
//输出的结果是什么?
printf("%d\n", sizeof(struct A));
return 0;
}
これはどのように計算されますか?
ここで、_aは4バイトのサイズを開きますが、_aは2ビットしか必要としません。32ビットの下には、さらに30ビットがあります。_bには5ビット、残り25ビット、_cには10ビットが必要なので、残り15ビット、_dには30ビット、残りの15ビットでは不十分です。_bを格納するために4バイトのスペースを開くだけです。こちらの図は左から配置したものと想定しています。
この質問を見てみましょう(VSの右から始めます):
struct S
{
char a : 2;
char b : 3;
char c : 4;
char d : 5;
};
int main(void)
{
struct S s = { 0 };
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;//在内存中是什么样的
return 0;
}
3.ビットセグメントのクロスプラットフォームの問題
1.intビットフィールドが符号付きまたは符号なしの数値として扱われるかどうかは不明です。
2.ビットセグメントの最大ビット数を特定できません。(16ビットマシンの最大数は16、32ビットマシンの最大数は32、書き込み27であるため、16ビットマシンで問題が発生します。
3.ビットフィールドのメンバーがメモリ内で左から右に割り当てられるか、右から左に割り当てられるかは定義されていません。
4.構造体に2つのビットセグメントが含まれていて、2番目のビットセグメントのメンバーが大きすぎて最初のビットセグメントの残りのビットを収容できない場合、残りのビットを破棄するか使用するかは不明です。
構造と比較すると、ビットセグメントは同じ効果を達成できますが、スペースを非常に節約できますが、クロスプラットフォームの問題があります。
3.列挙
列挙することは列挙することです。可能な値をリストします。
1.定義:
enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
int main(void)
{
printf("%d\n", Mon);
printf("%d\n", Tues);
printf("%d\n", Wed);
printf("%d\n", Thur);
printf("%d\n", Fri);
printf("%d\n", Sat);
printf("%d\n", Sun);
return 0;
}
上で定義された列挙型は列挙型です。{}の内容は、列挙型の可能な値であり、列挙定数とも呼ばれます。これらの可能な値にはすべて値があります。デフォルトは0から始まり、一度に1ずつ増加します。もちろん、定義時に初期値を割り当てることもできます。
enum Day//星期
{
Mon,
Tues,
Wed,
Thur = 6,
Fri,
Sat,
Sun
};
2.利点
1.コードの可読性と保守性を向上させます
2. #defineで定義された識別子と比較して、列挙には型チェックがあり、より厳密です。
3.ネーミング汚染の防止(カプセル化)
4.デバッグが簡単
5.使いやすく、一度に複数の定数を定義できます
3.使用する
switchステートメントで、ブランチが多すぎてケースの後のラベルが数字である場合、現時点では、数字が何を表しているかを知るために、それを見つける必要があります。ただし、列挙型を使用すると、数値を定数名に置き換えて、一目でわかるようにすることができます。これにより、コードの可読性が大幅に向上します。
enum Game
{
EXIT,
GAME_BEGIN,
GAME_DISCONTINUE
};
int main(void)
{
switch (1)
{
case GAME_BEGIN:
printf("你开始了游戏!\n");
case GAME_DISCONTINUE:
printf("你中止游戏!\n");
case EXIT:
printf("你退出了游戏!\n");
}
return 0;
}
4.コンソーシアム(連邦)
1.定義:
ユニオンは、一連のメンバーを含む変数を定義する特別なカスタムタイプでもあります。これらのメンバーは共通の空間を共有しているため、共通の組織とも呼ばれます。結合の定義は構造の定義と似ています。
//联合类型的声明
union Un
{
char c;
int i;
};
//联合变量的定义
union Un un;
2.機能:
ユニオンはスペースを共有するため、ユニオン変数のサイズ、少なくとも最大のメンバーのサイズは、最大のメンバーを格納できます。
#include<stdio.h>
union Un
{
int i;
char c;
};
union Un un;
int main(void)
{
// 下面输出的结果是一样的吗?
printf("%p\n", &(un.i));
printf("%p\n", &(un.c));
//下面输出的结果是什么?
un.i = 0x44;
un.c = 0x55;
printf("%x\n", un.i);
return 0;
}
このことから、コンソーシアムの特徴である空間の共有がわかります。このため、un.iを印刷すると、結果は0x55になります。ここで0x44が上書きされます。ユニオンの特性に応じて、現在のコンピュータのストレージサイズを判断するために使用できます。
union Un
{
int i;
char ch;
};
int main(void)
{
union Un un;
un.i = 1;
printf("%d\n", un.ch);//1,是小端
return 0;
}
3.ユニオンのサイズの計算
組合の規模は、少なくとも最大の組合員の規模です。最大メンバーサイズが最大アライメント数の整数倍でない場合は、最大アライメント数の整数倍にアライメントする必要があります。
union Un1
{
char c[5];//共5个元素,每个占1个字节,总的大小为5
int i;//4
};
int main(void)
{
printf("%d\n", sizeof(union Un1));//8,是最大对齐数4的倍数。注意不是5,这里的5是数组总的大小
return 0;
}
union Un2
{
short c[7];//2*7
int i;//4
};
int main(void)
{
printf("%d\n", sizeof(union Un2));//16
return 0;
}