网站安全检测官网,酒店网站建设的构思,网络建设费用,整页图片广告网站源码0. 结构体的内存分配
当声明某种类型的结构变量时#xff0c;结构成员被分配连续#xff08;相邻#xff09;的内存位置。 struct student{char name[20];int roll;char gender;int marks[5];} stu1; 此处#xff0c;内存将分配给name[20]、roll、gender和marks[5]。st1这…0. 结构体的内存分配
当声明某种类型的结构变量时结构成员被分配连续相邻的内存位置。 struct student{char name[20];int roll;char gender;int marks[5];} stu1; 此处内存将分配给name[20]、roll、gender和marks[5]。st1这意味着or的大小struct student将是其成员大小的总和。不是吗让我们检查。 void main()
{printf(Sum of the size of members %I64d bytes\n, sizeof(stu1.name) sizeof(stu1.roll) sizeof(stu1.gender) sizeof(stu1.marks));printf(Using sizeof() operator %I64d bytes\n,sizeof(stu1));
}/* Output */
Sum of the size of members 45 bytes
Using sizeof() operator 48 bytes 使用sizeof()运算符给出的3字节数多于成员大小的总和。为什么这3个字节在内存中在哪里我们先回答第二个问题。我们可以打印成员的地址来查找这些3字节的地址。 void main()
{printf(Address of member name %d\n, stu1.name);printf(Address of member roll %d\n, stu1.roll);printf(Address of member gender %d\n, stu1.gender);printf(Address of member marks %d\n, stu1.marks);
}/* Output */
Address of member name 4225408
Address of member roll 4225428
Address of member gender 4225432
Address of member marks 4225436 我们观察到该数组marks[5]不是从 分配的而是4225433从 分配的4224536。但为什么
数据对齐
在研究数据对齐之前了解处理器如何从内存读取数据非常重要。
处理器在一个周期内读取一个字。对于32 位处理器该字为4 字节对于64 位处理器该字为 8 字节。周期数越少CPU 的性能越好。
实现这一目标的一种方法是对齐数据。对齐意味着任何大小的基本数据类型的变量t将始终默认情况下具有 的倍数的地址t。这本质上是数据对齐。这种情况每次都会发生。
某些数据类型的对齐地址 数据类型大小以字节为单位地址char11的倍数short22的倍数int,float44的倍数double, long, *指针88的倍数long double1616的倍数 结构填充
可能需要在结构的成员之间插入一些额外的字节以对齐数据。这些额外的字节称为填充。
在上面的示例中3字节充当填充。如果没有它们marks[0] 类型int地址为 4 的倍数的基地址将为4225433不是 4 的倍数。
您现在大概可以明白为什么不能直接比较结构了。 结构成员对齐
为了解释这一点我们将举另一个例子你会明白为什么。 struct example{int i1;double d1;char c1;} example1;void main()
{printf(size %I64d bytes\n,sizeof(example1));
} 输出会是什么让我们应用我们所知道的。
i1是4个字节。其后将填充 4 个字节因为 的地址应能被 8 整除。对于和d1其后将分别填充 8 和 1 个字节。因此输出应为 4 4 8 1 17 字节。d1c1 /* Output */
size 24 bytes 什么又错了如何通过数组struct example我们可以更好的理解。我们还将打印 的成员的地址example2[0]。 void main()
{struct example example2[2];printf(Address of example2[0].i1 %d\n, example2[0].i1);printf(Address of example2[0].d1 %d\n, example2[0].d1);printf(Address of example2[0].c1 %d\n, example2[0].c1);}/* Output */
Address of example2[0].i1 4225408
Address of example2[0].d1 4225416
Address of example2[0].c1 4225424 假设 的大小example2[0]是 17 字节。这意味着 的地址example2[1].i1将为4225425。这是不可能的因为 的地址int应该是 4 的倍数。从逻辑上讲 的可能地址example2[1].i1似乎是42254284 的倍数。
这也是错误的。你发现了吗now的地址example2[1].d1将是 (28 4 ( i1) 3 ( padding))4225436它不是 8 的倍数。
为了避免这种不对齐编译器为每个结构引入了对齐。这是通过在最后一个成员之后添加额外的字节来完成的称为结构成员对齐。 在本节开头讨论的示例中它不是必需的因此是另一个示例。
一个简单的记住方法是通过这个规则 -结构地址和结构长度必须是 的倍数t_max。这里t_max是结构中成员所占用的最大大小。
对于struct example8 字节是 的最大大小d1。因此结构末尾有 7 个字节的填充使其大小为 24 个字节。
这两点将帮助您找到任何结构的大小 - 任何数据类型都将其值存储在其大小倍数的地址中。 任何结构的大小都是成员所占用的最大字节数的倍数。
尽管我们可以降低 CPU 周期但仍会浪费大量内存。将填充量减少到可能的最小值的一种方法是按成员变量大小的递减顺序声明成员变量。
如果我们遵循这一点struct example结构的大小就会减少到 16 个字节。填充从 7 个字节减少到 3 个字节。 struct example{double d1; int i1;char c1;} example3;void main()
{printf(size %I64d bytes\n,sizeof(example3));
}/* Output */
size 16 bytes 结构填料
包装与填充相反。它防止编译器填充并删除未分配的内存。对于 Windows我们使用该#pragma pack指令它指定结构成员的打包对齐方式。 #pragma pack(1)struct example{double d1; int i1;char c1;} example4;void main()
{printf(size %I64d bytes\n,sizeof(example4));
}/* Output */
size 13 bytes 这可确保成员在 1 字节边界上对齐。换句话说任何数据类型的地址都必须是 1 字节或其大小以较小者为准的倍数。
1. 指针
指针作为成员
结构也可以将指针作为成员。 struct student{char *name;int *roll;char gender;int marks[5];};void main()
{ int alexRoll 44;struct student stu1 { Alex, alexRoll, M, { 76, 78, 56, 98, 92 }};
} 使用.点运算符我们可以再次访问成员。由于roll现在有 的地址alexRoll我们将不得不取消引用stu1.roll来获取值而不是stu1.(*roll)。 printf(Name: %s\n, stu1.name);printf(Roll: %d\n, *(stu1.roll));printf(Gender: %c\n, stu1.gender);for( int i 0; i 5; i)printf(Marks in %dth subject: %d\n, i, stu1.marks[i]);/* Output */
Name: Alex
Roll: 43
Gender: M
Marks in 0th subject: 76
Marks in 1th subject: 78
Marks in 2th subject: 56
Marks in 3th subject: 98
Marks in 4th subject: 92 结构体指针
与整数指针、数组指针和函数指针一样我们也有结构体指针或结构体指针。 struct student {char name[20];int roll;char gender;int marks[5];
};struct student stu1 {Alex, 43, M, {76, 98, 68, 87, 93}};struct student *ptrStu1 stu1; 在这里我们声明了一个ptrStu1类型为 的指针struct student。stu1我们已将的地址分配给ptrStu1。
ptrStu1存储 的基地址stu1它是结构体第一个成员的基地址。增加 1 将使地址增加sizeof(stu1)字节。 printf(Address of structure %d\n, ptrStu1);
printf(Adress of member name %d\n, stu1.name);
printf(Increment by 1 results in %d\n, ptrStu1 1);/* Output */
Address of structure 6421968
Adress of member name 6421968
Increment by 1 results in 6422016 我们可以通过两种方式访问stu1using的成员。ptrStu1使用 *间接运算符或使用-中缀或箭头运算符。
对于*我们将继续使用.点运算符而对于 -我们将不需要点运算符。 printf(Name w.o using ptrStu1 : %s\n, stu1.name);
printf(Name using ptrStu1 and * : %s\n, (*ptrStu1).name);
printf(Name using ptrStu1 and - : %s\n, ptrStu1-name);/* Output */
Name without using ptrStu1: Alex
Name using ptrStu1 and *: Alex
Name using ptrStu1 and -: Alex 同样我们也可以访问和修改其他成员。请注意使用时括号是必需的*因为点运算符 ( .) 的优先级高于*。
结构数组
我们可以创建一个类型数组struct student并使用指针来访问元素及其成员。 struct student stu[10];/* Pointer to the first element (structure) of the array */
struct student *ptrStu_type1 stu;/* Pointer to an array of 10 struct student */
struct student (*ptrStu_type2)[10] stu; 请注意ptrStu_type1是 一个指向stu[0]while的指针而ptrStu_type2是一个指向整个数组 10 的指针struct student。加 1 将ptrStu_type1指向stu[1]。
我们可以使用ptrStu_type1循环来遍历元素及其成员。 for( int i 0; i 10; i)
printf(%s, %d\n, ( ptrStu_type1 i)-name, ( ptrStu_type1 i)-roll); 2. 功能
作为成员发挥作用
函数不能是结构的成员。但是使用函数指针我们可以使用点运算符调用函数.。但是不建议这样做。 struct example{int i;void (*ptrMessage)(int i);};void message(int);void message(int i)
{printf(Hello, Im a member of a structure. This structure also has an integer with value %d, i);
}void main()
{struct example eg1 {6, message};eg1.ptrMessage(eg1.i);
} 我们在内部声明了两个成员一个integeri和一个函数指针。函数指针指向一个接受eger 并返回 的函数。ptrMessagestruct exampleintvoid
message就是这样一个函数。我们eg1用6和初始化message。然后我们使用和 pass.来调用该函数。ptrMessageeg1.i
结构作为函数参数
与变量一样我们可以将单个结构成员作为参数传递。 #include stdio.hstruct student {char name[20];int roll;char gender;int marks[5];
};void display(char a[], int b, char c, int marks[])
{printf(Name: %s\n, a);printf(Roll: %d\n, b);printf(Gender: %c\n, c);for(int i 0; i 5; i)printf(Marks in %dth subject: %d\n,i,marks[i]);
}
void main()
{struct student stu1 {Alex, 43, M, {76, 98, 68, 87, 93}};display(stu1.name, stu1.roll, stu1.gender, stu1.marks);
}/* Output */
Name: Alex
Roll: 43
Gender: M
Marks in 0th subject: 76
Marks in 1th subject: 98
Marks in 2th subject: 68
Marks in 3th subject: 87
Marks in 4th subject: 93 请注意该结构是在最顶部的struct student外部声明的。main()这是为了确保它在全球范围内可用并且display()可以使用。
如果该结构体在内部定义main()其范围将被限制为main().
当结构成员数量很大时传递结构成员的效率不高。然后结构变量可以传递给函数。 void display(struct student a)
{printf(Name: %s\n, a.name);printf(Roll: %d\n, a.roll);printf(Gender: %c\n, a.gender);for(int i 0; i 5; i)printf(Marks in %dth subject: %d\n,i,a.marks[i]);
}
void main()
{struct student stu1 {Alex, 43, M, {76, 98, 68, 87, 93}};display(stu1);
} 如果结构的大小很大那么传递它的副本不会非常有效。我们可以将结构指针传递给函数。在这种情况下结构的地址作为实际参数传递。 void display(struct student *p)
{printf(Name: %s\n, p-name);printf(Roll: %d\n, p-roll);printf(Gender: %c\n, p-gender);for(int i 0; i 5; i)printf(Marks in %dth subject: %d\n,i,p-marks[i]);
}
void main()
{struct student stu1 {Alex, 43, M, {76, 98, 68, 87, 93}};struct student *ptrStu1 stu1;display(ptrStu1);
} 将结构数组传递给函数类似于将任何类型的数组传递给函数。数组的名称即结构体数组的基地址被传递给函数。 void display(struct student *p)
{ for( int j 0; j 10; j){printf(Name: %s\n, (pj)-name);printf(Roll: %d\n, (pj)-roll);printf(Gender: %c\n, (pj)-gender);for(int i 0; i 5; i)printf(Marks in %dth subject: %d\n,i,(pj)-marks[i]);}
}void main()
{struct student stu1[10];display(stu1);
} 结构作为函数返回
我们可以返回一个结构变量就像任何其他变量一样。 #include stdio.hstruct student {char name[20];int roll;char gender;int marks[5];
};struct student increaseBy5(struct student p)
{for( int i 0; i 5; i)if(p.marks[i] 5 100){p.marks[i]5;}return p;
}void main()
{struct student stu1 {Alex, 43, M, {76, 98, 68, 87, 93}};stu1 increaseBy5(stu1);printf(Name: %s\n, stu1.name);printf(Roll: %d\n, stu1.roll);printf(Gender: %c\n, stu1.gender);for(int i 0; i 5; i)printf(Marks in %dth subject: %d\n,i,stu1.marks[i]);
}/* Output */
Name: Alex
Roll: 43
Gender: M
Marks in 0th subject: 81
Marks in 1th subject: 98
Marks in 2th subject: 73
Marks in 3th subject: 92
Marks in 4th subject: 98 该函数increaseBy5()对加分后分数小于或等于 100 的科目加 5 分。注意返回类型是 类型的结构体变量struct student。
返回结构成员时返回类型必须是该成员的类型。
结构体指针也可以由函数返回。 #include stdio.h
#include stdlib.hstruct rectangle {int length;int breadth;
};struct rectangle* function(int length, int breadth)
{struct rectangle *p (struct rectangle *)malloc(sizeof(struct rectangle));p-length length;p-breadth breadth;return p;
}void main()
{struct rectangle *rectangle1 function(5,4);printf(Length of rectangle %d units\n, rectangle1-length);printf(Breadth of rectangle %d units\n, rectangle1-breadth);printf(Area of rectangle %d square units\n, rectangle1-length * rectangle1-breadth);
}/* Output */
Length of rectangle 5 units
Breadth of rectangle 4 units
Area of rectangle 20 square units struct rectangle请注意我们已经使用动态分配了 size 的内存malloc()。由于它返回一个void指针我们必须将其类型转换为struct rectangle指针。
3. 自指结构
我们讨论过指针也可以是结构的成员。如果指针是结构体指针怎么办结构体指针可以与结构体类型相同也可以不同。
自引用结构是那些具有与其成员相同类型的结构指针的结构。 struct student {char name[20];int roll;char gender;int marks[5];struct student *next;
}; 这是一个自引用结构其中next是struct student类型结构指针。我们现在将创建两个结构变量stu1并stu2用值初始化它们。然后我们将把 的地址存储stu2在 的next成员中stu1。 void main()
{struct student stu1 {Alex, 43, M, {76, 98, 68, 87, 93}, NULL};struct student stu2 { Max, 33, M, {87, 84, 82, 96, 78}, NULL};stu1.next stu2;
} stu2我们现在可以访问使用stu1和的成员next。 void main()
{printf(Name: %s\n, stu1.next-name);printf(Roll: %d\n, stu1.next-roll);printf(Gender: %c\n, stu1.next-gender);for(int i 0; i 5; i)printf(Marks in %dth subject: %d\n,i,stu1.next-marks[i]);
}/* Output */
Name: Max
Roll: 33
Gender: M
Marks in 0th subject: 87
Marks in 1th subject: 84
Marks in 2th subject: 82
Marks in 3th subject: 96
Marks in 4th subject: 78 假设我们想要在 后添加一个不同的结构体变量stu1即在和之间插入另一个结构体变量stu1stu2。这很容易做到。 void main()
{struct student stu3 { Gasly, 23, M, {83, 64, 88, 79, 91}, NULL};st1.next stu3;stu3.next stu2;
} 现在stu1.next存储 的地址stu3。并stu3.next有地址stu2。我们现在可以使用访问所有三个结构stu1。 printf(Roll Of %s: %d\n, stu1.next-name, stu1.next-roll);printf(Gender Of %s: %c\n, stu1.next-next-name, stu1.next-next-gender);/* Output */
Roll Of Gasly: 23
Gender Of Max: M stu1请注意我们如何使用结构指针在,stu3和之间形成链接stu2。我们在这里讨论的内容构成了链表的起点。
自引用结构在创建数据结构例如链表、堆栈、队列、图等时非常有用。