C语言中结构体的定义,变量的创建和初始化,相关的指针调用以及存在的内存对齐。

2024-01-03 13:23:46

怀心之所爱,勇敢前进!

前言:

结构体与指针是进入数据结构的必要道具,如果这两个没弄懂那后面探究数据结构是很容易变成炮灰的,今天我想就结构体的声明,结构体变量的初始化以及变量的成员的调用和 结构体指针的使用,以及内存对齐番讲述,ok那么话不多说,开始。


一.什么是结构体

结构体是C语言中的一种数据类型,它由一系列其他的数据类型(如int? ?char,double,数组和别的结构体)等组成,这些数据类型定义的变量名被称为是结构体的成员,其成员可以是同种类型也可以是不同种类型,

具体取决于程序员(感觉有点像数组plus)。

二.结构体的定义

首先要知道结构体的关键字是struct,其定义方式有两种。

一种是完全声明:

如图所示,我们定义了一个结构体类型“struct?apple”,其成员包括weight,money,address,分别代表了重量,加个,来源地址。“apple”被称为结构体的标签

struct apple
{
?? ?double?weight;
?? ?double money;
?? ?char address[20];
};

接着我们调用它创建了一个名为app的变量。

注意定义是在主函数之外的,真样可以保证其作用范围足够大

typedef struct apple
{
?? ?char a;
?? ?int b;
?? ?char c;
}apple;
#include<stdio.h>
int main()
{
?? ?printf("%d", sizeof(apple));
?? ?return;
}

当然我们也会觉得这样的类型名有点冗长,于是乎可以借用typedef关键字,给他改个名

typedef struct apple
{
?? ?double height;
?? ?double money;
?? ?char address[20];
}apple;

这样一来他的名字就变成了apple,使用起来会更加方便。

2.不完全声明。

不完全声明的结构体又称为匿名结构体,他是没有名字的,你可别说他还有个struct,要知道struct是关键字!!!!

?struct?
{
?? ?double height;
?? ?double money;
?? ?char address[20];
}apple;

它的使用就比较特殊了,你要在创建他的时候就使用它创建变量,如上图我就用它创建了一个叫apple的变量,他是个一次性的“道具”,只能在定义时用,不能在别的地方使用。而且如果你建立了两个匿名结构体,那你分别用他们创建一个变量,则这两个变量会被编译器看作两个不同的类型

例如:

#define _CRT_SECURE_NO_WARNINGS 1
?struct?
{
?? ?char a;
?? ?int b;
?? ?char c;
}apple;
struct?
{
?? ?char a;
?? ?int b;
?? ?char c;
}apples;
#include<stdio.h>
int main()
{
?? ?apple = apples;
?? ?return;
}

此时编译器会报错

当你希望创建的结构体类型只能用一次时,或者不想该结构体类型被别人使用,这时可以用匿名结构体。

三结构体变量的初始化

还是apple(问就是爱吃!!!)

建立完变量后就该初始化了,有两种方法。

1.创建时初始化

typedef struct apple
{
?? ?double?weight;
?? ?double money;
?? ?char address[20];
}apple;

#include<stdio.h>
int main()
{
?? ?apple apple = {2.2,30,"sun"};
?? ?return;
}

2.创建后初始化

typedef struct apple
{
?? ?double?weight;
?? ?double money;
?? ?char address[20];
}apple;

#include<stdio.h>

int main()
{
?? ?apple apple;
?? ?apple.weight = 2.2;
?? ?apple.money = 3.0;
?? ?apple.address[0] = 's';
?? ?apple.address[0] = 'u';
?? ?apple.address[0] = 'n';
?? ?apple.address[0] = '\0';
?? ?char arr[3];
?? ?return;
}

这两个的效果是一样的,重量2.2,价格3.0,地址是sun。

第一个就是把元素按照结构体成员的顺序放进大括号里。

第二种就有点麻烦了,要用到“.”这个东西,他叫成员访问运算符,顾名思义,就是用来访问成员的,使用方法就是“变量.成员名”,然后对其赋值即可。

但是第一个的方法能且只能在创建变量时使用,如果你创建后想修改它那只能用方法二。

四.结构体指针

先建一个结构体指针,方法和别的类型一样,都是“类型名*变量名”,别忘了初始化为NULL,防止野指针。

#include<stdio.h>
int main()
{
?? ?apple* apple = NULL;
?? ?return;
}

指针在这里的主要作用还是去访问成员,访问的方式有两种

第一种:使用“->”访问

#include<stdio.h>
int main()
{
?? ?apple* apple = NULL;
?? ?apple->weight = 2.2;
?? ?apple->money = 3.0;
?? ?apple->address[0] = 's';
?? ?apple->address[0] = 'u';
?? ?apple->address[0] = 'n';
?? ?apple->address[0] = '\0';
?? ?return;
}

第二种使用“.”访问

#include<stdio.h>
int main()
{
?? ?apple* apple = NULL;
?? ?(*apple).weight = 2.2;
?? ?(*apple).money = 3.0;
?? ?(*apple).address[0] = 's';
?? ?(*apple).address[0] = 'u';
?? ?(*apple).address[0] = 'n';
?? ?(*apple).address[0] = '\0';
?? ?return;
}

第二种还是很好理解的,毕竟结构体指针解引用就变成了结构体变量,结构体变量用的成员访问运算符,那指针解引用之后肯定也要用这个“.”啊。

五。结构体数组及指针对数组的访问

结构体数组元素的初始化也是分为两种

1.创建后初始化

注意一点结构体里有数组,而且还是创建后初始化只能像这样一个元素一个元素的赋值。

int main()
{
?? ?apple apples[10];
?? ?apples[0].weight = 2.2;
apples[0].money = 3.0;
apples[0].address[0] = 's';
apples[0].address[0] = 'u';
apples[0].address[0] = 'n';
apples[0].address[0] = '\0';
?? ?return;
}

2.创建时初始化

未被赋值到的自动赋值为0,即不完全初始化。

int main()
{
?? ?apple apples[10]= { { 2.2,3.0,"sun" } };
?? ?return;
}

很明显第二种更方便,但遗憾的是当你创建完后面想修改时必须用第一种方法,和数组蛮像的

3.指针在结构体数组中的使用

#include<stdio.h>
int main()
{
?? ?apple apples[10];
?? ?apple* applee = NULL;
?? ?applee = apples;
?? ?(*(applee+1)).weight = 2.2;
?? ?(*(applee + 1)).money = 3.0;
?? ?(*(applee + 1)).address[0] = 's';
?? ?(*(applee + 1)).address[0] = 'u';
?? ?(*(applee + 1)).address[0] = 'n';
?? ?(*(applee + 1)).address[0] = '\0';
?? ?return;
}

这次我们是对数组里的第二个元素赋值了,先通过*(applee+1)找到他,在用方法1进行赋值操作。

六内存对齐

ok接下来就是重难点了

这个结构体用了这么久了,就想问问他的大小是多少

typedef struct apple
{
?? ?double weight;
?? ?double money;
?? ?char address[20];
}apple;
#include<stdio.h>
int main()
{
?? ?printf("%d", sizeof(apple));
?? ?return;
}

是不是要说8+8+20=36,所以结果是36?

抱歉啦,结果是40.

为什么呢?这就涉及到内存对齐的原理了。

1.偏移量的概念

结构体中元素地址相对于结构体起始地址的偏移大小,单位字节。

我们把结构体的第一个成员对齐到和结构体变量其实的位置偏移量为的地址处。

其它成员变量对齐到偏移量为对齐数的最小整数倍的地址处。

2.对齐数的概念

即编译器默认的一个对齐数与该成员变量大小的较小值,

不同的编译器结果是不一样的。

例如vs默认是8

linux中gcc没有默认对齐数,对齐数就是成员大小

结构体中每个变量都有一个对齐数,最大的称为最大对齐数,结构体的大小是最大对齐数的整数倍

3嵌套结构体

.如果嵌套了结构体则嵌套的结构体的对齐数就是自己内部成员的最大对齐数,

了解这些,我们再回过头看着道题

结构体中double,double ,char address[20]

我使用的是64位下的vs,所以默认对齐数是8,double大小也是8,因此两个就是16,接着char数组一个元素大小是1,所以对齐数是1,所以再加20,一是36,?结构体的大小是最大对齐数的整数倍,此时最大对齐数是8,所以最终结果是40.

4.设置默认对齐数

#define _CRT_SECURE_NO_WARNINGS 1
#pragma pack(1)//设置默认对齐数为1
typedef struct apple
{
?? ?char a;
?? ?int b;
?? ?char c;
}apple;

#include<stdio.h>
int main()
{
?? ?printf("%d", sizeof(apple));
?? ?return;
}
#pragma pack()//取消设置的默认对齐数,还原为默认

此时的打印结果是6

但是要记住,设置对齐数必须在你定义一个或结构体之前,不然不会对他起作用

5.为什么要有内存对齐

大部分的参考资料都是这样说的
1平台原因(移植原因)
不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据
否则抛出硬件异常。
2,性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。假设一个处理器总是从内存中取8个字节,则地址必须是8的倍数。如我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用一个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中总体来说:结构体的内存对齐是拿空间来换取时间的做法。

6.如何减小空间浪费

那既然说是那空间换取时间,有没有什么办法可减小空间的浪费呢,请看以下代码

#define _CRT_SECURE_NO_WARNINGS 1
?struct a
{
?? ?char a;
?? ?int b;
?? ?char c;
};
?struct b
?{
?? ? char a;
?? ? char c;
?? ? int b;
?};
#include<stdio.h>
?int main()
?{
?? ? struct a dhdh;
?? ? struct b dh;
?? ? printf("%d\n",sizeof dh);
?? ? printf("%d", sizeof dhdh);
?}

打印结果是:

8? ? ? ?12

由此我们可以得出结论,如果想要尽量减小空间的浪费就应该将占用内存较小的变量类型先写在一起,这样一来,空间没浪费太多,时间也节约到了,两全其美

7.题库

趁热打铁,练练手吧

自动售货机内有多种商品(最多不超过50种)供人购买,可以定义如下结构体类型来表示商品信息:

struct product {
    int id;          // 商品编号
    char name[20];   // 商品名称
    double price;    // 商品单价
};

本题要求编写2个函数,分别用于显示售货机内的商品、计算所购买商品的总价格。

函数接口定义:

void menu(struct product p[],int n); void order(struct product p[],int n);

其中:

  • 函数menu()的功能是按[商品编号] 商品名称 商品单价形式显示菜单,分行输出各商品信息,包括商品编号、商品名称和商品单价。
  • 在函数order()中,先输入要购买的商品编号以及数量,接着按商品名称 * 数量 = 价格形式输出购买该商品的金额,允许购买多种商品,当输入0时表示所选商品结束,最后输出计算的总价格(保留1位小数)。

在主函数中:

  1. 首先读入该自动售货机的商品信息,先输入正整数n(n≤20),之后n行依次输入n个商品的编号、名称以及单价,例如输入样例中的前4行。

  2. 调用menu()函数,按[商品编号] 商品名称 商品单价形式输出主函数中数组p的各成员值,商品个数不同,菜单项数也会不同。

  3. 调用order()函数,分行输入所需购买商品的编号和数量,接着按商品名称 * 数量 = 价格形式输出购买该商品的金额,允许购买多种商品,当输入0时表示所选商品结束,最后按Total = 总价格形式输出计算的总价格(保留1位小数)。

裁判测试程序样例:

#include<stdio.h> struct product { int id; char name[20]; double price; }; void menu(struct product p[],int n); void order(struct product p[],int n); int main() { struct product p[20]; // 最多可有50个商品 int n; scanf("%d",&n); // 商品数量 for(int i=0; i<n; i++) { scanf("%d %s %lf",&p[i].id,p[i].name,&p[i].price); // 输入各商品的编号、名称和单价 } menu(p,n); // 显示菜单 order(p,n); // 点单,输入所需商品,并按要求输出各商品的名称、数量及价格,以及最终的总价格 return 0; } /* 请在这里填写答案 */

输入样例:

3
1001 Water 2.0
1002 OrangeJuice 3.5
1003 GreenTea 3.0
1001 2
1003 1
0

输出样例:

[1001] Water 2.0
[1002] OrangeJuice 3.5
[1003] GreenTea 3.0
Water * 2 = 4.0
GreenTea * 1 = 3.0
Total = 7.0

代码长度限制

16 KB

时间限制

400 ms

内存限制

64 MB

答案解析在这里:

PTA 6-2自动售货机,c语言答案,包含解析_自动售货机内有多种商品(最多不超过50种)供人购买,可以定义如下结构体类型来表示-CSDN博客

总结:

今天分享了了很多

关于结构体的知识点,包括结构体的定义,结构体变量的初始化,结构体指针的使用,内存对齐等等知识,写了好几个小时写完,创作不易,这份博客👍如果对你有帮助,给博主一个免费的点赞以示鼓励欢迎各位🔎点赞👍评论收藏??,谢谢!!!
如果有什么疑问或不同的见解,欢迎评论区留言欧👀

文章来源:https://blog.csdn.net/2301_79551553/article/details/135352671
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。