一、结构体
1.结构体的声明
结构体类型是一种构造类型,它是由若干成员组成。而每一个成员既可以是基本数据类型也可以是构造类型(结构体里放结构体)
//下面我声明了一个叫Book的结构体类型,即我把含有括号内三个基本类型的构造类型称之为Book类型
//同时Book类型具有三个成员,分别是Book的价格,Book的名称,Book的编号
struct Book //struct 是结构体的关键字,而book是结构体的标志
{
float price;
char name[1000];
int number;
};//末尾的分号不能掉
结构体的声明与函数的声明一样,需放在main函数之前。
2.结构体变量的定义
通过上述方法,我们声明一个叫Book的类型,那我们如何像其它基本类型一样定义变量呢?
其实方法大同小异,我们同样可以根据基本类型的定义模式来定义结构体变量。
int main(void)
{
struct Book book1;类似于基本类型的类型 + 变量名
return 0;
}
//可能有人会觉得这个struct让人觉得变扭
//不要忘了我们也可以自定义类型名称
typedef struct Book
{
float price;
char name[1000];
int number;
}Book;//我们将上述结构体类型自定义称之为Book类型
int main(void)
{
Book book1;//此时我们就可以直接使用Book,而不使用struct关键字了
return 0;
}
我们还有第二种定义方式
struct Book
{
float price;
char name[1000];
int number;
}book1, book2;//可以在声明末尾,分号之前进行定义变量
struct
{
float price;
char name[1000];
int number;
}book1, book2;//也可以直接定义,但此举对于后面继续操作结构体不太方便,一般不建议
3.结构体变量的初始化
所谓结构体变量的初始化,就是给结构体变量中的成员进行一个最开是的赋值操作。由上述可知对于结构体变量的定义我们有两大类定义方式。故此,对于结构体变量的初始化也基本上有两种初始化方式。
#include<stdio.h>
typedef struct Book
{
float price;
char name[1000];
int number;
}Book;
int main(void)
{
Book book1 = {30.2,"C",0213};//按照成员顺序依次输入。
return 0;
}
//若初始化数据少于成员数量,缺少数据的成员将会自动初始化为0;
//即我若只输入30.2,"C"那么number将会自动初始化为0;
同理我们可得第二种初始化方式
struct Book
{
float price;
char name[1000];
int number;
}book1 = {66.3,"B",3256};
4.结构体变量成员的访问
此时我们就要用到一个操作符“.”
我们可以通过 结构体变量.结构体成员这个模式来访问一个结构体成员,我们称“.”为成员运算符
#include<stdio.h>
struct Book
{
float price;
char name[1000];
int number;
}book1 = {66.3,"B",3256};
int main(void)
{
printf("%d", book1.number);
return 0;
}
输出结果为
如果对指针了解的话,我们还有第二种访问方式
#include<stdio.h>
struct Book
{
float price;
char name[1000];
int number;
}book1 = {66.3,"B",3256};
int main(void)
{
struct Book* ch = &book1;
printf("%d", ch->number);//运用指针来访问成员
printf("%d", (*ch).number);//解引用指正来访问
return 0;
}
输出的结果于上面是一样的
int main(void)
{
struct Book* book = &book1;
scanf("%f %s %d", book->price, book->name, book->number);
return 0;
}
至此,我们可以运用“.”操作符来对结构体变量进行输入
int main(void)
{
scanf("%f %s %d", &book1.price, &book1.name, &book1.number);
return 0;
}
int main(void)
{
struct Book* book = &book1;
scanf("%f %s %d", &book->price, &book->name, &book->number);
return 0;
}
这两种都是可行的。
综上,我们可以知道对于结构体变量的输入输出,都是以结构体变量成员为单位输入输出。
但是,若我想把一个结构体变量的值拷贝到另一个结构体变量上,那我可以
struct Book
{
float price;
char name[1000];
int number;
}book1 = {30.2,"B", 1258}, book2;
int main(void)
{
book2 = book1;
return 0;
}
前提是两个结构体变量的类型是相同的。
5.结构体类型的数组
结构体类型的数组本质上还是数组,只不过将数组中的元素类型从基本数据类型替换成了结构体类型。
1.结构体类型数组的定义方式
#include<stdio.h>
struct Book
{
float price;
char name[1000];
int number;
};
int main(void)
{
struct Book books[100];//定义了一个名字为books的结构体数组名,其中可以容纳100个结构体变量
return 0;
}
模式还是类似,数组元素类型 + 数组名 + 数组元素个数,相同的数组名指的是首元素地址,即数组中第一个结构体变量的地址。
对于结构体类型数组来说,数组名+下标,例:books[0],指的是数组中第一个结构体变量,若想访问其成员还得继续使用操作符“.”。
2.结构体数组的初始化
我们回忆起数组的初始化方式为
int main(void)
{
int arr[5] = {1,2,3,4,5};//数组元素类型+数组名+数组元素个数={元素1,元素2.....}
return 0;
}
在上面我们也讲述了结构体变量的初始化方式,现在我们将两者结合起来就得到了结构体数组的初始化方式。
#include<stdio.h>
struct Book
{
float price;
char name[1000];
int number;
};
int main(void)
{//定义了含有三个元素的结构体类型数组
struct Book books[3] =
{
{3.1,"A",1},
{3.1,"B",2},
{3.1,"C",3}
};//个人喜欢这样每个结构体变量换一次行,这样更为清晰,根据个人喜好编码就好
for (int i = 0; i < 3; i++)//打印数组元素,本质是打印结构体变量成员
{
printf("%.1f %s %d\n", books[i].price, books[i].name, books[i].number);
}
return 0;
}
根据数组的输入方式,大家可以自己结合上述内容写写结构体类型数组的输入代码。
6.结构体指针变量
其实在本章的前面出现过结构体指针变量,顾名思义该变量就是一个指向结构体类型变量的指针
1.结构体指针变量的定义
我们也可根据指针的定义方式来类比结构体指针变量的定义 ,即
#include<stdio.h>
struct Book
{
float price;
char name[1000];
int number;
}book1;
int main(void)
{
struct Book* books = &book1;指向元素类型 *指针名 = &指向元素
return 0;
}
2.通过指针来访问结构体成员
前面已经出现过“->”操作符,本处就不再举例 。
注意“->”跟结构体指针变量搭配,名为指向运算符,“.”跟结构体变量搭配。
不要混淆,不可乱用。
3.结构体指针变量指向一个结构体数组
结构体指针与普通指针一样,也可以指向一个数组(什么类型指针指向什么类型数组),
此时该指针所指向的是该数组的首元素地址。因为本质上数组名在不添加“&”符号或者不在sizeof()括号内,就是☞数组首元素地址
使用结构体指针变量打印结构体数组
#include<stdio.h>
struct Book
{
float price;
char name[1000];
int number;
};
int main(void)
{
struct Book books[3] =
{
{3.1,"A",1},
{3.1,"B",2},
{3.1,"C",3}
};
struct Book* book = books;//指针指向结构体数组
for (int i = 0; i < 3; i++)
{
printf("%.1f %s %d\n", (book + i)->price, (book + i)->name, (book + i)->number);
}
return 0;
}
指向运算符“->”比较常见,下面有几个常见的表示方式及其含义(p表示指针,member表示成员,i表示某一整数)
- p->member;得到p所指向的结构体变量中成员member的值
- p->member++; 得到p所指向的结构体变量中成员member的值,然后member的值+1
- ++p->member;使p所指向的结构体变量中的成员member的值+1,在引用这个新值
- (++p)->member;使p的值+1,再得到p所指向的结构体变量中的成员member的值
- (p++)->member;得到p所指向的结构体变量中成员member的值,然后p的值+1
- (p+i)->member;使p的值+i后,再得到p所指向结构体变量中成员member的值
注意,指针的值改变,所指向的值不一定是原来的值。
7.结构体与函数
1.结构体变量作为函数参数
结构体变量作为函数参数时,所采取的方式是值传递的方式,即将实参结构体变量的各个成员的值对应传递给形参结构体变量。
例
#include<stdio.h>
struct Book//结构体类型的声明
{
float price;
char name[1000];
int number;
};
void print(struct Book books)//函数参数为结构体变量
{//打印结构体变量
printf("%.1f %s %d\n", books.price, books.name, books.number);
}
int main(void)
{
struct Book books = { 3.1,"A",1 };//结构体变量的定义和初始化
print(books);//调用函数
return 0;
}
2.结构体指针变量作为函数参数
上述用结构体变量作为函数参数的方式,显然在传递参数时,需要将每一个成员的值进行传递,耗费较多的空间和时间。而采用结构体指针变量进行传参(即址传递),则可以避免此类问题。
例
#include<stdio.h>
struct Book
{
float price;
char name[1000];
int number;
};
void print(struct Book* book1)//改用结构体指针变量作为参数
{
printf("%.1f %s %d\n", book1->price, book1->name, book1->number);
}
int main(void)
{
struct Book books = { 3.1,"A",1 };
struct Book* book1 = &books;//定义和初始化结构体指针变量
print(book1);//调用函数
return 0;
}
8.结构体变量的存储方式
结构体变量的大小计算与基本数据类型的计算是不一样的
首先我们来了解一些结构体大小的计算规则
- 第一个成员在结构体变量偏移量为0地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8
- 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
其中出现了一个词,对齐数。意思是某个成员地址要与某一个数对齐,这个数就称为对齐数。
举个例子
现在我们有下面一个结构体
struct O
{
int a;
char ch;
double m;
}O;
首先根据规则,第一个成员位于结构体变量偏移量为0地址处,即距离地址的偏移量0,不就是起始地址吗?故我们第一个成员a起始放在0地址处
假设这个方框的初始方框为0地址处,按照int类型为四个字节。
同时我们也可以根据规则二,默认对齐数8与int类型大小4进行比较,较小者为4.
故此时对齐数为4.
再者也是规则二,一个成员要对齐到对齐数的整数倍,故a在内存中的存放位置是如上图所示绿色部分。
再对ch分析可知此时对齐数为1,而a所占地址后的偏移量为4,是对齐数的整数倍,故ch如下图红色部分
对m分析可知此时对齐数为8,而ch地址后的偏移量为5,不是对齐数的整数倍。此时我们就需要舍弃内存,向下继续寻找对齐数整数倍处。显而易见,偏移量为8处是对齐数的整数倍,我们就从偏移量为8处存放m。m的存放如蓝色部分
我们已将所有成员放置内存中,最后我们要参考第三个规则,此时结构体所占用16个字节
每个成员的对齐数分别是4、1、8,其中最大的是8。结构体总大小正好是8的倍数,故规则三满足。结构体总大小就是16个字节
运行代码即可验证。
若最后结构体总大小不是最大对齐数的整数倍,我们要像存放m时一样一直往下寻找,直到找到符合规则。白色部分都是为了满足规则而浪费掉,虽然没有储存东西,但也算结构体的大小的一部分
有的人可能会觉得,为什么要这样储存结构体,这样不是浪费了很多空间吗。
我们有下面两个原因
- 设备
不同设备、平台读取内存的方式存在区别,不是想读什么就可以获取到什么的,某些硬件平台是按一定方式读取的
- 效率
为了获取未对齐的内容,处理器可能会访问两次内存,但是我将其对齐的话,处理器就只需要访问一次。
一般编译器的默认对齐数是8;
我们也可通过下面方式手动修改默认对齐数
#pragma pack()//填入数字
故,在规定了这些规则的情况下,我们如何能更好的节约内存呢,我们只需要将内存大小相近的成员放在一起,将会在一定程度上改善我们结构体的内存大小。
若有错误,望告知,会即使纠正