目次
Structure キーワードと typedef キーワードの組み合わせ:
ポインター演算子「*」とメンバー・アクセサー「.」による場合:
構造体のメンバーにより、メモリ内にスペースが確保され、メモリ アライメントの完全なプロセスが開始されます。
構造:
私たちがこれまで接してきたデータ型の多くは、int型やdouble型などの基本的なデータ型であり、その構造体は必要に応じて自分で設計できるカスタムデータ型です。データ型。
C言語には集合データ型(集合データ型)と呼ばれる2つのデータ型があり、構造体と配列です。前述したように、配列は同じ要素のコレクションであり、配列の各要素には添字を介してアクセスできます。
しかし、構造は異なります。構造もいくつかの値のコレクションですが、これらの値は構造のメンバーと呼ばれます。構造内のメンバーは異なるデータ型を持つ場合があります。たとえば、学生の構造を作成したいとします内部のメンバーには、生徒の名前、年齢、身長、性別、テストの得点が含まれます。配列とは異なり、構造体の各メンバーには独自の名前があり、構造体のメンバーにも名前によってアクセスします。
設計構造の形式は次のとおりです。
struct 类型名
{
成员(变量的声明);
};//分号是必须要有的
ここでは、構造を徐々に理解するために例を使用します。
#include<stdio.h>
struct student
{
const char *name;
int age;
int score;
};
int main()
{
struct student stu1 = {"stu1",9,99};//花括号里面的内容一一对应结构体里面的成员列表
return 0;
}
ここで、ファイルが純粋な C コード、つまり .c 形式のファイルの場合は、student の前に struct を追加する必要がありますが、cpp ファイルの場合は、student を直接使用できることに注意してください。
たとえば、ここでは名前、年齢、学年の 3 つのメンバーで構成される Student という構造体を定義し、メイン プログラムに stu1 という構造体変数を定義します。
構造体が占めるスペース (メモリの配置に関係なく):
次に、メモリ内で構造がどのように見えるかを見てみましょう。
型の設計はメモリを占有しません。たとえば、 int 型を使用する場合、変数の定義に int を使用しない場合、 int はメモリ領域を占有しませんが、 int a = 0 などの変数の定義に int を使用する場合は、 int はメモリ領域を占有しません。 ; int 型は 4 バイトのメモリ領域を占有し、構造体にマップすることもできますが、構造体型を使用して変数を定義する場合にのみ領域を占有します。
メモリのアライメントを考慮しないという前提で、構造体が占めるスペースを分析してみましょう。
ここでは 2 つの構造を示します。
1つ:
struct Student
{
char name[10];
int age;
int score;
}
メモリ内の構造は次のようになります。
二:
struct Student
{
const char* name;
int age;
int score;
};
最初の構造体のサイズは、文字配列の 10 バイトに 2 つの int 型を加えた合計 18 バイトです。
2 番目の構造体では、ポインタ格納構造体の名前文字列のアドレスとして name が出力されますが、32 ビット システムではポインタのサイズは 4 バイトなので、構造体の合計サイズは 12 バイトになります。
構造体メンバーへのアクセス:
では、構造体のメンバーにどのようにアクセスすればよいのでしょうか?
上記の構造を引き続き使用し、メイン プログラムで構造のメンバーに対応する stu1 という名前の人物を定義し、構造変数を通じてメンバーにアクセスします。
#include<stdio.h>
struct Student
{
const char* name;
int age;
int score;
};
int main()
{
struct Student stu1 = {"zs",12,100};
printf("%s ",stu1.name);//通过结构体变量来进行成员的访问
return 0;
}
操作結果:
同様に、構造体のすべてのメンバーにアクセスします。
printf("%s,%d,%d\n",stu1.name,stu1.age,stu1.score);
注: ここでの「.」はメンバー アクセサーと呼ばれます。
構造体と配列の組み合わせ:
上に書いたのはstu1という生徒が一人だけの場合ですが、Student構造体を定義した際に複数の生徒の情報を格納する必要がある場合、いちいち定義するのは面倒です。今回は学生情報を保存するには:
最初:
中かっこを使用して配列に情報を直接入力し、配列の添え字を通じて要素にアクセスし、メンバー アクセサーを使用して要素内のメンバーへの方向性アクセスを実行します。
#include<stdio.h>
struct Student
{
const char *name;
int age;
int score;
};
int main()
{
struct Student ar[] = {
{"zs",10,100},{"lisi",9,99},{"ww",8,88}};
int len = sizeof(ar) / sizeof(ar[0]);
for(int i = 0;i < len;i++){
printf("第%d个学生,姓名:%s,年龄:%d,成绩:%d\n",i,ar[i].name,ar[i].age,ar[i].score);
}
return 0;
}
操作の結果は次のようになります。
2 番目のタイプ:
構造体配列を定義する前に学生を設定した場合は、構造体の変数名を配列に直接格納することもできます。
struct Student stu1 = {"zs",10,100};
struct Student stu2 = {"lisi",9,99};
struct Student stu3 = {"ww",8,88};
struct Student ar[] = {stu1,stu2,stu3};
int len = sizeof(ar) / sizeof(ar[0]);
for(int i = 0;i < len;i++){
printf("第%d个学生,姓名:%s,年龄:%d,成绩:%d\n",i,ar[i].name,ar[i].age,ar[i].score);
}
操作の結果は次のようになります。
どちらのメソッドも構造体配列にアクセスするための要件を満たすことができ、2 番目のメソッドのコードは少し多くなりますが、比較的すっきりしています。
Structure キーワードと typedef キーワードの組み合わせ:
構造体を定義するときに、その構造体を使用するたびに構造体 Student を書き直すのは面倒であることに気づくのは難しくないため、型の名前変更を使用できます。
たとえば、ここでは struct Student の名前を Student に直接変更します。
struct Student
{
const char* name;
int age;
int score;
};
typedef struct Student student;
ステートメントを簡略化します。
typedef struct Student
{
const char* name;
int age;
int score;
}student;//将新类型名称添加到分号的前面
構造体の型の名前を変更した後、新しい型名を使用して変数を定義することができ、ステートメントを記述するときに構造体を省略することもでき、より簡潔になります。
構造体とポインタの組み合わせ:
ポインター演算子「*」とメンバー・アクセサー「.」による場合:
構造体のタイプを定義する構造体へのポインターを使用することで、構造体のメンバーへの方向性アクセスを実行できます。
student ar[] = {
{"zs",10,100},{"lisi",9,99},{"ww",8,88}};
student *ptr = ar;
printf("%s",(*ptr).name);
ここで、メンバーアクセス演算子「.」の優先順位はポインタ演算子「*」の優先順位よりも高いため、ポインタを使用して構造体にアクセスする場合は括弧を追加する必要があります。同様に、構造体配列にアクセスしたい場合は、 2 番目のメンバーの名前を指定するには、ポインタに 1 を加算する関数を使用して次のように書き込むこともできます。
(*(ptr + 1)).name);//第二个成员的名字
(*(ptr + 2)).name);//第三个成员的名字
操作結果:
ポインタ「->」を介して:
ここで、ポインタも逆参照されることに注意してください。
たとえば、ここではポインタを介して構造体配列の最初の要素の name メンバーにアクセスする必要があります。
student ar[] = {
{"zs",10,100},{"lisi",9,99},{"ww",8,88}};
student *ptr = ar;
printf("%s",ptr->name);
それでは、ポインターを使用して 2 番目と 3 番目の要素の name メンバーにそれぞれアクセスするには何が必要でしょうか?
ポインターに 1 を追加するという知識はまだ残っていますが、優先順位の問題のため、方向性アクセスを実行する場合には括弧を追加する必要があります。
printf("%s",(ptr+1)->name);//第二个元素中name成员的访问
printf("%s",(ptr+2)->name);//第三个元素中name成员的访问
3 つの結果をすべて出力し、実行結果を見てみましょう。
図に示すように、アクセスは成功しました。
次に、 typedef キーワードを使用して、構造体のポインター型の名前を変更します。
student ar[] = {
{"zs",10,100},{"lisi",9,99},{"ww",8,88}};
typedef struct Student *Pstu;
Pstu ptr = ar;
printf("%s\n%s\n%s\n",ptr->name,(ptr+1)->name,(ptr+2)->name);
図に示すように、コンパイルが成功したことが示されています。
ポインターの名前変更を、メインプログラムの直前に型名を再定義する構造内に記述して、コードを簡素化できます。
typedef struct Student
{
const char *name;
int age;
int score;
}student,*Pstu;
この方法はまだ可能です。
注: ポインター メソッドを使用して構造体のメンバーにアクセスする場合は、逆参照を使用するよりもポインターを使用することをお勧めします。
構造体のメモリ サイズ:
前述したように、メモリ アライメントに関係なく、構造体の合計サイズは構造体のすべてのメンバの型サイズの合計になりますが、メモリ アライメントを考慮すると結果は異なります。
まず、メモリ アライメントとは何かを見てみましょう。
struct A
{
char a;
int b;
short c;
};
struct B
{
short c;
char a;
int b;
};
A と B の 2 つの構造体では、内部のメンバーのデータ型は同じですが、メンバーの順序が異なります。A と B の 2 つの構造体のサイズを出力します。
実際、2 つの構造が占めるメモリは異なりますが、これはなぜでしょうか?
その答えは、メモリ アラインメントにより、変数を異なる順序で宣言する構造体が異なるサイズを持つことになるためです。コンパイラはコンピュータ内の各プログラムに適切なメモリ空間を割り当てますが、これはすべて CPU まで遡ることができます。
メモリがコンピュータの橋渡しの役割を果たしているのは誰もが知っています. CPU と外部メモリの間の接続を確立するのはメモリです. メモリはまた CPU 内の計算情報を一時的に保存します. ここでは, メモリの読み取りに焦点を当てます. CPU をメモリに接続する方法は次のとおりです。
CPU がメモリを読み取る方法はブロック単位です。ブロックのサイズは 2、4、8、16 バイトで、通常は 2 の整数倍です。したがって、CPU がメモリを読み取るときは 1 つずつ読み取ります。 、ブロック サイズは (メモリ粒度) メモリ読み取り粒度と呼ばれます。読み取りメモリはブロック単位で読み取られるため、メモリの無駄が発生するはずです。
メモリ内の構造体メンバーの分散には、次の 3 つのルールがあります。
構造体変数の最初のアドレスは、MIN (メモリ アライメントを指定する、構造体変数内の最大の基本データ型) のメンバーが占めるバイト数の整数倍でなければなりません。
構造体の最初のアドレスに対する構造体変数内の各メンバーのオフセットは MIN です (メンバーの基本データ型はバイト ツリーの整数倍を占め、メモリ アラインメントが指定されます)。
構造体の合計サイズは、構造体 MIN (変数内の最大の基本データ型またはメモリ アライメントが占めるバイト数) の整数倍です。
注: ここでの MIN は、2 つの間の最小値を指します。
構造体変数の最初のメンバーはアドレス 0 にあります。この構造体を例に挙げてみましょう。
struct A
{
int a;
char b;
short c;
long long d;
};
構造体 A では、図に示すように、最大の基本データ型は Long Long で 8 バイトを占め、残りは 4 バイト、1 バイト、および 2 バイトです。
このとき、デフォルトのデータ型アライメントは 8 バイトで、初期アドレスは 0 です。構造体は int a から始まり 4 バイトを占め、その後に char b が続きます。前述したように、CPU はメモリを 1 つずつ読み取ります。読み取り、したがって、char b は 1 バイトを占めます。メモリ アライメントのため、後ろに 1 バイトのスペースを追加し続ける必要があります。このスペースはデータを保存するものではなく、コンピュータがメモリ アライメント スペースを必要とするためにのみ生成されるため、この時点で以前のスペースに加えてint 型の空間は合計 6 バイトで 2 の整数倍なので、short 用のメモリ空間を直接割り当てます。これは現在 8 バイトであり、後で long long 型を追加し続けます。合計サイズは 16 です。前述したように、構造体の合計サイズは、構造体変数の最大の基本データ型の整数倍である必要があります。構造体変数の最大の型は、long long 型で、8 バイトです。合計サイズは次のとおりです。 16 バイトは 8 の整数倍であるため、この構造体の合計サイズは 16 バイトになるはずです。
実行結果を見てみましょう。
結果は正しいです。
次に、整数型のメンバーを構造体に追加すると、すべての型を合計してメモリ アライメントをカウントすることによって割り当てられるスペースは 20 になるはずですが、20 は構造体の最大の基本データ型の整数倍ではないため、次のようにする必要があります。後でさらに 4 バイトのスペースが割り当てられ、合計サイズは 24 バイトになります。プログラムを通じて次のことを確認します。
結果は正しいです。
前述したように、メモリの読み取りはブロック単位で行われ、メモリのアライメントには 2、4、8 などの方法があり、メモリのアライメントはカスタマイズ可能です。では、メモリのアライメントをどのように定式化するか?
#progma pack(1)//对齐方式开始
括弧内に記入し、メモリ位置合わせとして数バイトを指定する必要があります
たとえば、上記の構造 A を使用します。
struct A
{
int a;
char b;
short c;
long long d;
}
メモリ アライメント カスタム ステートメントを使用しない場合、プログラムは最終的な構造体の合計サイズを、最大の基本データ型 (24) の整数倍で決定します。
カスタム メモリ アライメント ステートメントを使用する場合は、ここで 4 と 1 に設定します。
このとき、メンバーとメモリのアラインメントが 4 の整数倍、つまり 20 である限り、
メモリ アライメントが 1 の場合、char 型の後ろにスペースを割り当てる必要はありません。メモリ型は元々 1 なので、構造体のすべてのメンバ型のサイズの合計は 19 になるからです。
カスタム メモリ アライメントの使用を終了したら、当然、それを終了する必要があります。end ステートメントは次のとおりです。
#pragma pack()//内存对齐方式结束
#pragma Pack() は、#define と同様、前処理ディレクティブです。
構造体のメンバーにより、メモリ内にスペースが確保され、メモリ アライメントの完全なプロセスが開始されます。
構造:
struct student
{
int a;
char b;
short c;
int e;
long long d;
};
図に示すように、以下は学生の構造内の各メンバーの記憶発達のプロセスです。
1: まず、int 型のメンバー a のサイズを 4 の空間を空けます。
2: int型のサイズはchar型のサイズ1の整数倍である4であるため、int型の直後にスペースが空きます。
3: short 型のサイズは 2 で、前の int 型と char 型を足したサイズは 5 であり、2 の整数倍ではないため、メモリ アラインメントのために再び 1 バイトのスペースを空けます。今回、合計サイズは 6 で、正確に 2 の整数倍です。メンバー c は、新しく開かれたバイトの後に開かれます。
4: このとき、前部分のサイズを合計すると8となり、int型のメンバーeのサイズのちょうど整数倍となるので、そのまま後ろに展開します。
5; 前のサイズを合計すると 12 になりますが、これは Long Long 型のメンバー e の整数倍ではないため、メモリを埋めるために 4 バイトのスペースを空けます。このときのサイズは 16 で、これはちょうど 16 です。 8 の整数倍なので、メンバー d は後で開発されます。
6: 構造体のメンバーの合計サイズは 24 です。プログラムを使用して検証します。
結果は正しいです
構造のネスト:
struct address
{
const char *city;
const char *street;
};
struct student
{
const char *name;
int age;
struct address;
};
同様に、メモリのアライメントを含むメモリ割り当てのプロセスを分析してみましょう。
1: アドレス構造体の 2 つのメンバー型は両方ともポインター型であり、ポインター型のサイズは 4 であるため、アドレス構造体の合計サイズは 8 です。
2:student 構造体では、メンバー char *name が最初にメモリ展開され、サイズは 4 バイト、age メンバーは int 型で、同じく 4 バイト、4 は 4 の整数倍なので、開発に直接接続されます。 、アドレス構造体ボディの合計サイズは 8 バイト、前のメンバーのサイズの合計は 8 バイトで、これは整数倍です。メモリは後ろで直接開かれ、実行中にメモリ アライメントはありません。したがって、学生構成の合計サイズは 16 人になります。
このプログラムを使用して以下を検証します。
図に示すように、結果は正しいです
構造と動的メモリの組み合わせ:
typedef struct student
{
const char *name;
int age;
};
int main()
{
student s = {"zs",10};
student* p = (student*)malloc(sizeof(student));
assert(p != NULL);
memset(p,0,sizeof(student));
printf("%s\n",p -> name);
free(p);
p = NULL;
return 0;
}
malloc 関数を使用して学生サイズのヒープ領域を適用し、この領域を 0 で初期化します。メンバー名に属する初期化された領域を文字列形式で出力します。結果は空になります。
操作結果:
変数:
プログラミングでは、変数を正しく使用するために、各変数間のつながりを区別し、変数間の違いを把握する必要があります。変数は次の 2 種類に分類されます。
ローカル変数:
関数内で定義された変数:
例:
このとき、aはメインプログラムのcppファイルtestarray配下に定義されているため、この時点ではaはローカル変数となります。
そして、ローカル変数の定義時に値が代入されていないことを前提として、システムはローカル変数にランダムな値を代入します。
ローカル変数のライフサイクル:
関数内で定義 → 定義時に生成 → 関数終了時に変数も終了
スタック -> スタック内のメモリの開発と解放
グローバル変数:
関数の外で定義された変数:
例:
このとき、変数 a は main 関数の外で定義され、testarray ファイル内のすべての関数を指向しており、このとき a はグローバル変数です。
グローバル変数の定義時に値が割り当てられていない場合、システムは自動的にデフォルト値 (つまり 0) をグローバル変数に割り当てます。
グローバル変数の宣言サイクル:
プログラムが開始するとグローバル変数が生成され、プログラムが終了するとグローバル変数も終了します。
他のファイルでグローバル変数を定義し、メイン プログラムでグローバル変数の値を変更すると、プログラムはエラーを報告しますが、C 言語のキーワード extern を使用して、別のファイルでグローバル変数を実行できます。
これから、extern キーワードを導入します。
図に示すように、my_struct ファイルでグローバル変数 a を定義し、次に testarray ファイルの変数 a に値を代入しようとすると、プログラムがエラーを報告しました。
ただし、メイン プログラムの前に次のステートメントを追加すると、プログラムは正常にコンパイルできます。
extern int a;
extern キーワードの役割は、外部ファイル内のグローバル変数をリンクすることです。
注: このときのグローバル変数が静的グローバル変数である場合、つまり、グローバル変数の定義の前に static キーワードが追加される場合、そのグローバル変数にはリンク可能な属性がなく、このファイルでのみ表示されます。 extern キーワードがリンクに引き続き使用されている場合、プログラムはエラーを報告します。
static int a;
構造を使った演習:
構造体メンバーへの直接アクセスとソートとの組み合わせ:
教育機関の本体を使用して学生情報を保存し、sort を使用して学生の成績を昇順に並べ替えるプログラムを作成します。成績が同じ場合は、名前の降順で並べ替えられます。
#include<stdio.h>
#include<assert.h>
#include<string.h>
typedef struct Student//重新定义结构体类型名
{
const char *name;
int age;
int score;
}student;
void Swap(student *p,student *q)//交换函数
{
assert(p != NULL && q != NULL);
student temp = *p;//因为我们需要交换的是结构体中的成员,所以临时值temp也应是我们重新定义的类型
*p = *q;
*q = temp;
}
void Bubble_sort(student *ar,int len)//冒泡排序(针对结构体中的成员,所以形参类型为结构体类型)
{
assert(ar != NULL && len >= 0);
while(len--){
int flag = 0;
for(int i = 0;i < len;i++){
if(ar[i].score > ar[i + 1].score){
flag = 1;
Swap(&ar[i],&ar[i + 1]);
}
else if(ar[i].score == ar[i + 1].score){//如果成绩相同的情况下
if(strcmp(ar[i].name,ar[i + 1].name) < 0){//使用strcmp函数,如果前一个字符串返回的整数值是小于后一个字符串的则直接进行交换
Swap(&ar[i],&ar[i + 1]);
}
}
}
if(!flag){
break;
}
}
}
int main()
{
student ar[] = {
{"zs",10,100},{"lisi",9,100},{"ww",8,100}};
int len = sizeof(ar) / sizeof(ar[0]);
Bubble_sort(ar,len);
for(int i = 0;i < len;i++){
printf("姓名:%s,成绩:%d\n",ar[i].name,ar[i].score);
}
return 0;
}
操作結果: