目录:
一:定义
1.组成格式
(1).tag
(2).member-list
(3).variable-list
2: 这 3 部分至少要出现 2 个
二.包含关系
三:初始化
四:访问结构成员
举例:
五:指向结构的指针
举例:
六:位域
1.定义
2.补充
3.位域的定义和位域变量的说明
举例:
4.位域的使用
举例:
5.位域声明
实例
八:结构体数组
1.定义
举例:
2.结构体数组的初始化
举例:
举例:
九:结构体大小的计算
十:结构体内存分配原则
1.原则一:
2.原则二:
举例:
为了定义结构,您必须使用 struct 语句
struct 语句定义了一个包含多个成员的新的数据类型
结构体中成员变量分配的空间是按照成员变量中占用空间最大的来作为分配单位
同样成员变量的存储空间也是不能跨分配单位的
如果当前的空间不足,则会存储到下一个分配单位中
结构体变量的首地址能够被其最宽基本类型成员的大小所整除
// 二进制表示为 1000 有四位,超出 Age.age = 8; printf("Age.age : %d\n", Age.age);
超出范围并不是直接丢弃,而是保留对应的 3 位的值。
比如 8 是 00001000,按照位域,对应 3 位的值是 000,所以打印结果是 0;
但是 9 是 00001001,按照位域,对应 3 位的值是 001,所以打印结果是 1;
同理 10 是 00001010,按照位域,对应 3 位的值是 010,所以打印结果是 2;
Age.age = 9; // 二进制表示为 1001 有四位,超出 printf( "Age.age : %d\n", Age.age ); Age.age = 10; // 二进制表示为 1010 有四位,超出 printf( "Age.age : %d\n", Age.age );
// 以下是 4,7,8,9,10 的打印结果 Sizeof( Age ) : 4 Age.age : 4 Age.age : 7 Age.age : 0 Age.age : 1 Age.age : 2
结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍
如有需要编译器会在成员之间加上填充字节(internal adding)
即结构体成员的末地址减去结构体首地址(第一个结构体成员的首地址)得到的偏移量都要是对应成员大小的整数倍
结构体的总大小为结构体最宽基本类型成员大小的整数倍
如有需要编译器会在成员末尾加上填充字节
struct tag {
member-list
member-list
member-list
...
} variable-list ;
是结构体标签
是标准的变量定义
比如 int i; 或者 float f,或者其他有效的变量定义
是结构变量
定义在结构的末尾,最后一个分号之前,您可以指定一个或多个结构变量
//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//同时又声明了结构体变量s1
//这个结构体并没有标明其标签
struct
{
int a;
char b;
double c;
} s1;
//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//结构体的标签被命名为SIMPLE,没有声明变量
struct SIMPLE
{
int a;
char b;
double c;
};
//用SIMPLE标签的结构体,另外声明了变量t1、t2、t3
struct SIMPLE t1, t2[20], *t3;
//也可以用typedef创建新类型
typedef struct
{
int a;
char b;
double c;
} Simple2;
//现在可以用Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;
结构体的成员可以包含其他结构体,也可以包含指向自己结构体类型的指针
而通常这种指针的应用是为了实现一些更高级的数据结构如链表和树等
//此结构体的声明包含了其他的结构体
struct COMPLEX
{
char string[100];
struct SIMPLE a;
};
//此结构体的声明包含了指向自己类型的指针
struct NODE
{
char string[100];
struct NODE *next_node;
};
如果两个结构体互相包含
则需要对其中一个结构体进行不完整声明
struct B; //对结构体B进行不完整声明
//结构体A中包含指向结构体B的指针
struct A
{
struct B *partner;
//other members;
};
//结构体B中包含指向结构体A的指针,在A声明完后,B也随之进行声明
struct B
{
struct A *partner;
//other members;
};
#include
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book = {"C 语言", "RUNOOB", "编程语言", 123456};
int main()
{
printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", book.title, book.author, book.subject, book.book_id);
}
运行结果:
title : C 语言
author: RUNOOB
subject: 编程语言
book_id: 123456
使用成员访问运算符(.)
成员访问运算符是结构变量名称和我们要访问的结构成员之间的一个句号
#include
#include
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main( )
{
struct Books Book1; /* 声明 Book1,类型为 Books */
struct Books Book2; /* 声明 Book2,类型为 Books */
/* Book1 详述 */
strcpy( Book1.title, "C Programming");
strcpy( Book1.author, "Nuha Ali");
strcpy( Book1.subject, "C Programming Tutorial");
Book1.book_id = 6495407;
/* Book2 详述 */
strcpy( Book2.title, "Telecom Billing");
strcpy( Book2.author, "Zara Ali");
strcpy( Book2.subject, "Telecom Billing Tutorial");
Book2.book_id = 6495700;
/* 输出 Book1 信息 */
printf( "Book 1 title : %s\n", Book1.title);
printf( "Book 1 author : %s\n", Book1.author);
printf( "Book 1 subject : %s\n", Book1.subject);
printf( "Book 1 book_id : %d\n", Book1.book_id);
/* 输出 Book2 信息 */
printf( "Book 2 title : %s\n", Book2.title);
printf( "Book 2 author : %s\n", Book2.author);
printf( "Book 2 subject : %s\n", Book2.subject);
printf( "Book 2 book_id : %d\n", Book2.book_id);
return 0;
}
运行结果:
Book 1 title : C Programming
Book 1 author : Nuha Ali
Book 1 subject : C Programming Tutorial
Book 1 book_id : 6495407
Book 2 title : Telecom Billing
Book 2 author : Zara Ali
Book 2 subject : Telecom Billing Tutorial
Book 2 book_id : 6495700
struct Books *struct_pointer;
指针变量中存储结构变量的地址
为了查找结构变量的地址
请把 & 运算符放在结构名称的前面
struct_pointer = &Book1;
为了使用指向该结构的指针访问结构的成员
使用 -> 运算符
struct_pointer->title;
#include
#include
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
/* 函数声明 */
void printBook( struct Books *book );
int main( )
{
struct Books Book1; /* 声明 Book1,类型为 Books */
struct Books Book2; /* 声明 Book2,类型为 Books */
/* Book1 详述 */
strcpy( Book1.title, "C Programming");
strcpy( Book1.author, "Nuha Ali");
strcpy( Book1.subject, "C Programming Tutorial");
Book1.book_id = 6495407;
/* Book2 详述 */
strcpy( Book2.title, "Telecom Billing");
strcpy( Book2.author, "Zara Ali");
strcpy( Book2.subject, "Telecom Billing Tutorial");
Book2.book_id = 6495700;
/* 通过传 Book1 的地址来输出 Book1 信息 */
printBook( &Book1 );
/* 通过传 Book2 的地址来输出 Book2 信息 */
printBook( &Book2 );
return 0;
}
void printBook( struct Books *book )
{
printf( "Book title : %s\n", book->title);
printf( "Book author : %s\n", book->author);
printf( "Book subject : %s\n", book->subject);
printf( "Book book_id : %d\n", book->book_id);
}
运行结果:
Book title : C Programming
Book author : Nuha Ali
Book subject : C Programming Tutorial
Book book_id : 6495407
Book title : Telecom Billing
Book author : Zara Ali
Book subject : Telecom Billing Tutorial
Book book_id : 6495700
有些信息在存储时,并不需要占用一个完整的字节
而只需占几个或一个二进制位
例如在存放一个开关量时,只有 0 和 1 两种状态,用 1 位二进位即可
为了节省存储空间,并使处理简便
C 语言又提供了一种数据结构,称为"位域"或"位段
是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数
每个域有一个域名
位域在本质上就是一种结构类型,不过其成员是按二进位分配的
带有预定义宽度的变量被称为位域
一个位域存储在同一个字节中,如一个字节所剩空间不够存放另一位域时,则会从下一单元起存放该位域
也可以有意使某位域从下一单元开始
struct bs{ unsigned a:4; unsigned :4; /* 空域 */ unsigned b:4; /* 从下一单元开始存放 */ unsigned c:4 }
在这个位域定义中,a 占第一字节的 4 位,后 4 位填 0 表示不使用,b 从第二字节开始,占用 4 位,c 占用 4 位
由于位域不允许跨两个字节
因此位域的长度不能大于一个字节的长度
也就是说不能超过8位二进位
如果最大长度大于计算机的整数字长,一些编译器可能会允许域的内存重叠
另外一些编译器可能会把大于一个域的部分存储在下一个字中
位域可以是无名位域
这时它只用来作填充或调整位置
无名的位域是不能使用的
struct k{ int a:1; int :2; /* 该 2 位不能使用 */ int b:3; int c:2; };
struct 位域结构名
{
位域列表
};
其中位域列表的形式为:
类型说明符 位域名: 位域长度
struct bs{
int a:8;
int b:2;
int c:6;
}data;
说明:
data 为 bs 变量,共占两个字节
其中位域 a 占 8 位,位域 b 占 2 位,位域 c 占 6 位
位域变量名.位域名
位域变量名->位域名
main(){
struct bs{
unsigned a:1;
unsigned b:3;
unsigned c:4;
} bit,*pbit;
bit.a=1; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
bit.b=7; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
bit.c=15; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
printf("%d,%d,%d\n",bit.a,bit.b,bit.c); /* 以整型量格式输出三个域的内容 */
pbit=&bit; /* 把位域变量 bit 的地址送给指针变量 pbit */
pbit->a=0; /* 用指针方式给位域 a 重新赋值,赋为 0 */
pbit->b&=3; /* 使用了复合的位运算符 "&=",相当于:pbit->b=pbit->b&3,位域 b 中原有值为 7,与 3 作按位与运算的结果为 3(111&011=011,十进制值为 3) */
pbit->c|=1; /* 使用了复合位运算符"|=",相当于:pbit->c=pbit->c|1,其结果为 15 */
printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c); /* 用指针方式输出了这三个域的值 */
}
上例程序中定义了位域结构 bs
三个位域为 a、b、c
说明了 bs 类型的变量 bit 和指向 bs 类型的指针变量 pbit。这表示位域也是可以使用指针的
struct
{
type [member_name] : width ;
};
元素 | 描述 |
---|---|
type | 只能为 int(整型) unsigned int(无符号整型) signed int(有符号整型) 三种类型,决定了如何解释位域的值 |
member_name | 位域的名称 |
width | 位域中位的数量 宽度必须小于或等于指定类型的位宽度 |
位域可以存储多于 1 位的数
#include
#include
struct
{
unsigned int age : 3;
} Age;
int main( )
{
Age.age = 4;
printf( "Sizeof( Age ) : %d\n", sizeof(Age) );
printf( "Age.age : %d\n", Age.age );
Age.age = 7;
printf( "Age.age : %d\n", Age.age );
Age.age = 8; // 二进制表示为 1000 有四位,超出
printf( "Age.age : %d\n", Age.age );
return 0;
}
运行结果:
Sizeof( Age ) : 4
Age.age : 4
Age.age : 7
Age.age : 0
一个结构体变量中可以存放一组数据(如一个学生的学号,姓名,成绩等数据)
每个数组元素都有一个结构体类型的数据
它们分别包括各个成员(分量)项
struct student
{
int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
};
struct student stu[3];
说明:
定义了一个数组 stu
其元素为 struct student 类型数据
数组有 3 个元素
也可以直接定义一个结构体数组
struct student
{
int mum;
char name[20];
char sex;
int age;
float score;
char addr[30];
}stu[3] = {{10101,"Li Lin", 'M', 18, 87.5, "103 Beijing Road"},
{10101,"Li Lin", 'M', 18, 87.5, "103 Beijing Road"},
{10101,"Li Lin", 'M', 18, 87.5, "103 Beijing Road"}};
定义数组 stu 时,元素个数可以不指定,即写成以下形式:
stu[] = {{...},{...},{...}};
数组的初始化也可以用以下形式:
struct student
{
int num;
...
};
struct student stu[] = {{...},{...},{...}};
说明:
即先声明结构体类型,然后定义数组为该结构体类型,在定义数组时初始化
https://www.runoob.com/w3cnote/struct-size.html
结构体中元素按照定义顺序存放到内存中,但并不是紧密排列
从结构体存储的首地址开始
每一个元素存入内存中时,它都会认为内存是以自己的宽度来划分空间的
因此元素存放的位置一定会在自己大小的整数倍上开始
在原则一的基础上
检查计算出的存储单元是否为所有元素中最宽的元素长度的整数倍
若是,则结束
否则,将其补齐为它的整数倍
#include
typedef struct t1{
char x;
int y;
double z;
}T1;
typedef struct t2{
char x;
double z;
int y;
}T2;
int main(int argc, char* argv[])
{
printf("sizeof(T1) = %lu\n", sizeof(T1));
printf("sizeof(T2) = %lu\n", sizeof(T2));
return 0;
}
输出:
sizeof(T1) = 16
sizeof(T2) = 24
解析:
sizeof(T1.x) = sizeof(T2.x) = 1;
sizeof(T1.y) = sizeof(T2.y) = 4;
sizeof(T1.z) = sizeof(T2.z) = 8;
T1: 若从第 0 个字节开始分配内存,则 T1.x 存入第 0 字节,T1.y 占 4 个字节,由于第一的 4 字节已有数据,所以 T1.y 存入第 4-7 个字节,T1.z 占 8 个字节,由于第一个 8 字节已有数据,所以 T1.z 存入 8-15 个字节。共占有 16 个字节。
T2: 若从第 0 个字节开始分配内存,则 T1.x 存入第 0 字节,T1.z 占 8 个字节,由于第一的 8 字节已有数据,所以 T1.z 存入第 8-15 个字节,T1.y 占 4 个字节,由于前四个 4 字节已有数据,所以 T1.z 存入 16-19 个字节。共占有 20 个字节。此时所占字节不是最宽元素(double 长度为 8)的整数倍,因此将其补齐到 8 的整数倍,最终结果为 24。