关于c语言结构体变量成员引用的问题

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于c语言结构体变量成员引用的问题相关的知识,希望对你有一定的参考价值。

⑴以下sacnf函数调用语句对结构体变量成员的引用不正确的是(D)
struct pupil
char nam[20];int age;
int sex;
pup[5],*p;p=pup;
A)scanf("%s,pup[0],name“)
B)scanf("%d,&pup[0].age")
C)scanf("%d,&(p->sex)")
D)scanf("%d,p->age")

⑵若有以下定义的语句
struct student
int age
int name;;struct student stu[3]=1001,20,1002,19,1003,21;
main()
struct student *p;
p=stu,…
则以下不正确的应用是(D)
A)(p++)->num B)p++ C)(*p).num D)p=&stu.age

⑶设有如下定义:
struct sk
int n;
float x;data,*p;
若要使p只想data中的n域,正确的赋值语句是(C)
A)p=&data.n B)*p=data.n C)p=(struct sk*)&data.n D)p=(struct sk*)data.n

⑷若有以下说明和语句:
int c[4][5],(*p)[5];
p=c;
能够正确引用c数组元素的是(D)
A)p+1 B)*(p+3) C)*(p+1)+3 D)*(p[0]+2)

这四题都是关于结构体变量引用的,求大神讲解,最好能每个选项说明为什么对为什么错,越细致越好。谢谢

scanf的用法,格式后的参数都必须是地址,name是数组名,本身就是数据,age和sex都是整型数据,必须使用取地址符&。

p是struct student*类型,stu是stu[3]这个数组的第一个元素的地址,p指向第一个元素,因此ABC都是对的,D错在把(int*)变量值赋值给(struct student*)变量,有个强制转换则不会错,因为age是结构体第一个变量,其地址就是结构体变量的地址。

是指针间的赋值,必须保证左右操作数的指针类型是一致的。由于n是结构体sk的第一个变量,因此其地址与结构体变量地址相同,可以强制转换得到正确的地址。

c是一个二维数组,p是一个指向数组的指针,因此p的*运算都是得到一个数组,**运算才能取到数组中的值。只有D是**运算。

扩展资料:

C语言初学者比较喜欢的Turbo C 2.0提供了400多个运行时函数,每个函数都完成特定的功能,用户可随意调用。这些函数总体分成输入输出函数、数学函数、字符串和内存函数、与BIOS和DOS有关的函数、 字符屏幕和图形功能函数、过程控制函数、目录函数等。

Windows系统所提供的Windows SDK中包含了数千个跟Windows应用程序开发相关的函数。其他操作系统,如Linux,也同样提供了大量的函数让应用程序开发人员调用。

参考资料来源:百度百科-C语言

参考技术A

第一题:

    貌似是你打字的时候,弄错了,scanf函数,第一个参数是个字符串,里面包含读取信息的格式,第二个是个不定参数,也就是参数的个数不确定,里面依次排列的是第一个参数里面指出的要读取的信息,所以这四个选项都写错了,正确应该是:

A)scanf("%s", pup[0].name)
B)scanf("%d", &pup[0].age)
C)scanf("%d", &(p->sex))
D)scanf("%d", p->age)

如题所示,pup是一个pupil类型的数组,有5个元素,而p是一个指针,其指向pup数组的首地址,根据以上信息,我们来分析各个选项

A. 由于scanf函数读取的信息要传递到函数外面,所以存放信息的参数都是采取指针传递的方式,因此,后面那个参数必须是内存的地址;A中%s代表,要读取的是个字符串,而pup[0].name是一个字符数组,数组名即代表数组首地址,所以A正确

B. %d代表要读取整型,pup[0].age代表age这个结构体成员,而&是取地址符,所以也是正确的

C. 同样要读取整型数据,由于p是一个指针,所以引用它的成员要用"->"而不是".",然后加&也是取地址的意思,所以C也正确

D. 最后的选项,很明显没有加&,而直接传入结构体成员"age",所以这个错了


第二题:

    这道题貌似也打错了,结构体中两个int类型的成员,age和name,而选项中却都写成num,考虑到name不应该是int类型,姑且把name更正成num。

A. p是指向同类型数组stu的指针而p++是在原有地址上做位置偏移,结果等同于stu[0]变位stu[1]

而作为指针的成员访问符号,(p++)->num的意思是,先取p->num的值,再将地址偏移加1,所以A没有问题

B. A没问题了,B肯定也没有问题

C. *是取地址所指向的内存里的信息,*p就是取p指向的内容,而非指针的实体变量,访问其成员就用"."符号,所以(*p).num就是取得p所指向结构体的成员num的值,这个选项没问题

D. p是student类型的指针,不能被赋予int类型数据(age)的地址,只是其一,其二,stu是数组名本身代表的就是数组首地址,&stu.age的写法是错误的,应该是&stu[0].age或者&(*stu).age,所以此选项错了


第三题:

    p是sk类型的指针而n是int类型,所以在赋值前必须要做强制类型转换,否则编译不会通过。四个选项中,A B没有做转换,PASS;而C D选项都是可以编译通过的,但从逻辑上分析,C做的是把data中成员n的地址给了p,而n实际是结构体的第一个成员,也就是说,n的地址就相当于是结构体的地址,所以C所做的实际就是把data的地址赋值给了p,而D显然就错了,它把n的值当成了地址赋给p,而n实际上不是一个地址,这样导致p指向了一块不确定的位置,有可能是0,这样是很危险的

所以综上所述,C是对的


第四题:

    c是一个二维数组,p是一个指针数组(一个数组,其元素为指针),它们的一维元素个数相同,所以当p=c时,p中的每个元素即对应了c中的第二维度;然后我们分析选项:

题目说的是能引用c中元素的是哪个,考虑到四个选项,我们姑且认为这里的“元素”指的是c中最内层的元素个体,也就是某个int类型的值;

A. p+1代表的是p数组的第二个元素,即一个指向c第二维的数组指针,这个选项不对

B. p+3同理代表的是p的第四个元素,而*则将数组指针转化为数组,即c[3],类型为int[5]

C. 同上*(p+1)对应的是c[1]而*(p+1) + 3就是c[1]指向的第二维数组的地址偏移加3,其实是一个int类型的指针,int*

D. p[0]相当于*(p + 0)就是c[0],p[0] + 2就是地址偏移加2,再用*取值,最后的结果就是c[0][2],类型为int,所以D正确

参考技术B (1)、scanf的用法,格式后的参数都必须是地址,name是数组名,本身就是数据,age和sex都是整型数据,必须使用取地址符&;
(2)、p是struct student*类型,stu是stu[3]这个数组的第一个元素的地址,p指向第一个元素,因此ABC都是对的,D错在把(int*)变量值赋值给(struct student*)变量,如果有个强制转换则不会错,因为age是结构体第一个变量,其地址就是结构体变量的地址。
(3)、与(2)类似,是指针间的赋值,必须保证左右操作数的指针类型是一致的。由于n是结构体sk的第一个变量,因此其地址与结构体变量地址相同,可以强制转换得到正确的地址。
(4)、c是一个二维数组,p是一个指向数组的指针,因此p的*运算都是得到一个数组,**运算才能取到数组中的值。只有D是**运算。本回答被提问者和网友采纳

c语言自定义类型——结构体,位段(匿名结构体,结构体的自引用,结构体的内存对齐)


在这里插入图片描述

结构体的定义

结构体是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
数组是一组相同类型的元素的集合。结构体也是一些值的集合,结构体的每个成员可以是不同类型的。

为什么要使用结构体

现实生活中,我们会遇到很多复杂的对象,这些复杂的对象仅通过c语言中的整型数据类型或者浮点数数据类型是无法全面且准确的描述的,这时候我们就需要用到结构体来描述这些复杂的对象。

结构体类型的声明

完全声明

struct tag
{
	member_list;
}  variable_list;

**

**> 结构体关键字:struct

结构体的标签:tag
结构体的类型:struct tag
结构的成员列表:member_list
结构体变量列表:variable_list**

**

例如描述一件衣服,衣服的属性有衣服名称,衣服编号,衣服价格,衣服颜色等等。
struct cloth
{
	char name[20];//衣服名称
	char id[20];//衣服编号
	int price;//衣服价格
	char color [5];//衣服颜色
};//分号不能丢弃

上面的结构体声明方式属于完全声明,当然也存在不完全声明的情况,比如说省略结构体的标签,这种结构体称为匿名结构体,如下代码:

//匿名结构体类型
struct 
{
	int a;
	char b;
	float c;
}x;

struct 
{
	int a;
	char b;
	float c;
}*p;

int main()
{
	p =&x;
	return 0;
}

上面的匿名结构体,成员都是一样的,表面上是同一类型,第一次创建了一个变量x,第二次创建了一个结构体指针,主函数里面取变量x的地址放在指针变量p里面,看似可行,但实际上他们是两种不同的类型,是不能进行赋值的,虽然没有什么大的问题,但编译器会报错发出警告**(在定义的时候没有写结构体类型名称,编译器当做两种不同的类型,所以编译不过,就会有如下报错了)**
在这里插入图片描述

结构体的自引用

思考:在结构中包含一个类型为该结构本身的成员是否可以呢 ?

//代码1
struct Node
{
	int data;//占4个字节
	struct Node n;
};
//可行否?

如果可以,那sizeof(struct Node)是多少 ?
我们首先进行简要分析一下,发现是不行的,要求struct Node 的大小,成员data占4个字节,之后又求struct Node n成员的大小,要求struct Node n成员的大小,又要求struct Node的大小,**要求其大小又要求它自己的大小,我们发现在求其大小的时候都要求它自己的大小,而它自己的大小都是未知的,求其大小的过程就陷入一个死递归的过程,永远无法求出struct Node 的大小,**因此这种自引用的方式不行。还有语法也不支持的,经过我的实验编译报错如下:
在这里插入图片描述

正确的自引用方式:
正确的自引用方式是要通过结构体指针的方式实现,那为什么要通过结构体指针的方式呢?我的理解是数据在内存中的存储多种多样,有像数组那样元素一个挨着一个顺序存储的,也有一些数据在内存中杂乱存储,但是要想对它们进行顺序访问,一个一个像连线一样访问就要使用结构体指针,还有我们知道,指针的大小跟其所指向的类型无关,仅跟平台环境有关,32位平台指针大小为4个字节,64位平台,指针大小为8个字节。正因为指针大小的确定性,所以再自引用的时候结构体类型的整体大小也是可以确定的。

//代码2
struct Node
{
	int data;//数据域
	struct Node* next;//指针域
};

假设我们存储5个数据1 2 3 4 5,有顺序存放的顺序表,也有杂乱存放的链表,它们都是线性数据结构,我们通过结构体指针,通过链表方式就可以顺序访问内存空间不相邻的数据了,像一根线一样把访问的数据串联起来(这里涉及数据结构知识没有能力深谈)

在链表数据结构中,链表中每一个元素称为“结点”,每个结点都应包括两个部分:一个是需要用的实际数据data;另一个就是存储下一个结点地址的指针,即数据域和指针域。数据结构中的每一个数据结点对应于一个存储单元,这种储存单元称为储存结点,也可简称结点

在这里插入图片描述

那么对于匿名结构体又如何实现结构体的自引用呢?


```c

//代码1
struct 
{
	int data;
	struct Node* next;
}Node1;


int main()
{
	printf("%d", sizeof(Node1));//结果8
	return 0;
}

上面的代码省略了结构体标签,创建了匿名结构体,我在vs2017上面测试通过了,虽然通过了,但按理论来说是不对的,因为下面定义了一个struct Node *的指针,但stuct Node都没定义,不知道从哪里来,怎么就创建了一个结构体指针了呢?可能原因很多,但这种写法错的,是编译器检测不严格造成的。

//代码2
typedef struct
{
int data;
Node* next;
}Node;
//这样写代码,可行否?
//代码2
typedef struct Node
{
int data;
struct Node* next;
}Node;

那我们能不能通过typedef类型重定义实现匿名结构体的自引用呢?可以但要写成代码二的形式,我们通过typedef将结构体重新命名为Node,之后创建一个结构体指针,但不能省略调结构体标签Node,原因还是因为我们结构体重命名为node ,结构体里面在未重定义Node之前就定义了一个Node*的指针,之前并没有这个指针,它是从哪里冒出来的呢?中间的指针创建是非法的,typedef的定义也是非法的。

结构体变量的定义和初始化

结构体变量的定义和初始化其实很简单,和其它类型的变量的定义和初始化基本一样,基本形式都是:变量类型+变量名称,进行定义,(struct *
+变量名称,是结构体指针)等号右边进行初始化。唯一不同的是结构体可以在结构体声明的代码块的后面,分号前面,添加变量名称进行定义。结构体变量进行初始化的时候,初始化的内容是放在一个花括号里面。不同的成员之间用逗号隔开即可,如果遇到结构体嵌套的情况,括号里面再加括号,括号里面放置嵌套结构体的成员。可以结合下面的代码更好地进行理解

代码一:


struct Point
{
	int x;
	int y;
}p1;//声明结构体类型的同时定义变量p1
struct Point p2;//定义结构体变量p2
struct Point p3 = { 1,1 };//初始化:定义变量的同时给变量赋值

代码二:

struct Stu//类型声明
{
	char name[20];//姓名
	int age;//年龄
};
struct Stu s = { "Student_zhang",20 };//定义和初始化初始化

代码三:

struct Node
{
	int data;
	struct Point p;
	struct Node* next;
}n1 = { 10,{4,5},NULL };//结构体嵌套初始化
struct Node n2 = { 20,{1,2},NULL };//结构体嵌套初始化

结构体内存对齐(结构体的存放规则)

对于结构体的声明,定义和初始化。我们已经基本了解,接下来我们一起来探讨一下结构体在内存当中的存放规则。数据的存放规则决定了它在内存当中的大小。在定义变量的时候编译器给不同类型的数据分配内存空间,比如char大小为1个字节,int类型为4个字节,double类型是8个字节等等,那么,结构体的大小是多少?又该如何计算呢?

结构体内存对齐规则:
1.第一个成员放在结构体变量偏移量为O的地址处。 ⒉.其他成员变量要放在某个数字(对齐数)的整数倍的地址处。 ①对齐数 = 编译器默认的一个对齐数与该成员大小的较小值。 ②VS中默认对齐数的值为8, Linux中无对齐数的概念,以自身大小为对齐数
3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体放到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。嵌套结构体的对齐数不是结构体大小的整数倍,不是简单的对齐数相加,而是结构体自己的最大对齐数的整数倍。这里的对齐数,可以简单的理解为对齐规则下的存储位置,也是偏移量位置。

接下来,我们通过以下练习来加深对结构体内存对齐规则的理解
练习一:

#include<stdio.h>
	struct s1
{
	char c1;
	int i;
	char c2;
	
};
int main()
{
	printf("%d\\n", sizeof(struct s1));
	return 0;
}

运行结果为12,详细理解如图。
在这里插入图片描述

练习二:

#include<stdio.h>
struct s2
{
	char c1;
	char c2;
	int i;
};
int main()
{
	printf("%d\\n", sizeof(struct s2));
	return 0;
}

程序运行结果为8
在这里插入图片描述
练习三,结构体的嵌套

计算嵌套结构体的大小,也是要遵循结构体的对齐规则的,第一个成员都是默认从偏移量为零的位置进行对齐,如遇到结构体结构体从结构体的最大对齐数位置开始开辟空间,空间的大小为结构体的大小,之后再继续进行对齐,开辟相应的变量空间,和上面的没有嵌套的对齐方式一样开辟空间一样。

#include<stdio.h>
struct s3
{
	double d;
	char c;
	int i;
};
struct s4
{
	char c1;
	struct s3 s3;
	double d;
};
int main()
{
	printf("%d\\n", sizeof(struct s4));//程序运行结果4
	return 0;
}

在这里插入图片描述
思考:为什么存在内存对齐 ?

大部分的参考资料都是这样解释的::
1.平台原因(移植原因)∶不是所有的硬件平台都能访问任意地址上的任意数据的; 某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。(所以我们对齐到能够被硬件访问的位置,在对齐位置进行存储数据)
⒉性能原因∶数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;
而对齐的内存访问仅需要一次访问。 总体来说︰ 结构体的内存对齐是拿空间来换取时间的做法。

那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到?

让占用空间小的成员尽量集中在—起。
如对比之前写的代码

	struct s1
{
	char c1;
	int i;
	char c2;
};
struct s2
{
	char c1;
	char c2;
	int i;
};

之前讲解结构体的对齐规则的时候,举例了两个代码,结构体s1和s2类型的成员一模一样,但是s1和s2所占用的空间大小是有区别的,前者大小为12个字节,后者为8个字节,显然后者这种方式空间利用的效率更高,节省内存。

使用#pragma,可以用来改变我们的默认对齐数。
如通过#pragma修改我们上面所写代码的默认对齐数

#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct s1
{
	char c1;
	int i;
	char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认

#pragma pack(1)//设置默认对齐数为1
struct s2
{
	char c1;
	int i;
	char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
	printf("%d\\n", sizeof(struct s1));
	printf("%d\\n", sizeof(struct s2));
	return 0;
}


s1的大小为12,s2的大小为6,默认对齐数为1,就不存在规则了,结构体成员就是顺序开辟空间的,因为存储信息的最小单元是一个字节,默认对齐数为1,取两者的较小值对齐数就都为1,之后的对齐位置都是1的倍数。所以结构体在对齐方式不合适的时候,我们可以自己更改默认对齐数来满足需求。

在这里插入图片描述

offsetof,这是一个宏,用来计算结构体成员相对于结构体起始位置的偏移量
如下面代码通过这个宏,计算结构体中某变量相对于首地址的偏移。不太清楚的还是自己画图看看。

#include<stdio.h>
#include<stdlib.h>
struct s2
{
	char c1;
	int i;
	char c2;
};
int main()
{
	printf("%d\\n", offsetof(struct s2, c1));
	printf("%d\\n", offsetof(struct s2, i));
	printf("%d\\n", offsetof(struct s2, c2));
	return 0;
}

程序运行结果:0,4 8

结构体传参

#include <stdio.h>
struct S
{
	int data[1000];
	int num;
};
struct S s = { {1, 2, 3, 4}, 1000 };
//结构体传参
void print1(struct S s)
{
	printf("%d \\n", s.num);
}
// 结构体地址传参
void print2(struct S* ps)
{
	printf("%d \\n", ps->num);
}
int main()
{
	print1(s);//传结构体
	print2(&s);//传结构体地址,通过结构体指针接收
	return 0;
}

结构体传参有两种方式:
一种是传递结构体对象(传值),对应的就是print1函数的方式
另一种是传递结构体地址(传址),对应的就是print2函数的方式
那么上面的print1和print2函数哪个好些 ?print2更好一点

对于结构体的传参首选传递地址,原因如下:

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性 能的下降。

结构体实现位段

什么是位段 ? 位段的声明和结构是类似的,所以放在一起研究,但有两个不同︰
1.位段的成员必须是int、unsigned int,char
2.位段的成员名后边有一个冒号和一个数字。
3.int 表示定义整形的位段成员,冒号后面的数字表示成员所占的大小,单位为字节,首先开辟一个整形大小的空间(32字节)来存储位段成员,如果空间不足继系开辟一个整形空间大小来创建位段成员。
如下代码:

#include<stdio.h>
struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};
int main()
{
	printf("%d\\n", sizeof(struct A));
	return 0;
}

那么如何计算位段A的大小呢?
位段的计算分两种情况:
情况一:
位段类型相同:
一句话来说就是一次一次的开辟位段类型对应的空间大小,依次把空间分给位段成员,假设使用后的空间不够分配给后面的成员,需要重新开辟空间,位段的总大小就是所有开辟空间的总和。如上代码计算位段大小大致如下:

1.首先位段的类型是int,int类型的大小是4个字节
2.开辟4个字节的空间,其中_a占用2个bit位,_b占5个bit,_c占10bit,这时候已经使用17个bit的空间,4个字节=32bit,还剩下15bit空间,而_d大小为30个bit,剩余的空间不够其使用
3.再次开辟4个字节的空间,_d占用30个bit
4.所以位段A的大小为4+4=8个字节
情况二:
位段成员不同,其实位段也是结构体,这时计算位段的大小就要按结构体的对齐规则来计算了和上面的一样了

#include<stdio.h>

struct Test
{
	char a : 1;
	char b : 6;
	int c : 1;
}Test;

int main()
{
	printf("size = %d\\n", sizeof(struct Test));
	return 0;
}

程序运行结果8

位段的内存分配

1.位段的成员可以是int ,unsigned int或者是char(属于整形家族)类型
2.位段的空间上是按照需要以4个字节([int)或者1个字节(char)的方式来开辟的。
3.位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段

这些不确定因素体现在: ①空间是否要被浪费? ②空间是从左向右使用还是从右向左使用?
如如下代码:

#include<stdio.h>
		struct S
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
}s;
int main()
{
	struct S s = { 0 };
	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	return 0;
}

我利用以上代码在vs2013里面进行测试,数据的存储先使用低地址再使用高地址。假设位段成员在一个字节空间里面是从右向左进行存储的,存储完一个结构体成员后,当一个字节的空间不够存储下一个结构体成员的时候,之前一个字节里面的空间舍弃。调试后结果如图和我们的假设一样(其实这里的假设有点牵强,我们已知在vs里面的存储情况,我们可以通过类似的假设方法在linux的gcc编译器下进行一下探究)
在这里插入图片描述
调试的时候数据是以16进制显示,四个二进制转化为一个16进制,第一个数据转化大致如图

在这里插入图片描述

位段的跨平台问题

1.int位段被当成有符号数还是无符号数是不确定的。
2.位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32),写成27,在16位机器会出问题。
3.位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4.当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时 是舍弃剩余的位还是利用,这是不确定的。
5.跟结构体相比,位段可以达到同样的效果,但是可以很好的节省空间

总结

博文是我学习过程的一些总结和个人理解,如有错误还请指正,欢迎大家点赞评论收藏支持,也希望和一起学习的小伙伴一起交流学习。

以上是关于关于c语言结构体变量成员引用的问题的主要内容,如果未能解决你的问题,请参考以下文章

C语言-结构体

c语言中定义结构体都有哪些方法?

C语言结构体函数的返回值是结构体结构体变量中的信息

C语言结构体函数的返回值是结构体结构体变量中的信息

c语言中怎样通过索引获取结构体成员的值

c语言自定义类型——结构体,位段(匿名结构体,结构体的自引用,结构体的内存对齐)