C语言结构体详解

什么是结构体

C 数组允许定义可存储相同类型数据项的变量,结构是C编程中另一种用户自定义的可用的数据类型,它允许您存储不同类型的数据项。

结构用于表示一条记录,假设您想要跟踪图书馆中书本的动态,您可能需要跟踪每本书的下列属性:

  • Title
  • Author
  • Subject
  • Book ID

定义结构类型

为了定义结构,您必须使用 struct 语句。struct 语句定义了一个包含多个成员的新的数据类型,struct 语句的格式如下:

struct tag { 
    member-list;
    member-list;
    member-list; 
    ...
};

tag 是结构体标签,是一个可选的标志,它是用来引用该结构体的快速标记。

member-list 是标准的变量定义,比如 int i; 或者 float f,或者其他有效的变量定义。

成员后面用分号;隔开,结构类型定义的末尾也有个分号;

但是注意,它并没有创建一个实际的数据对象,而是描述了一个组成这类对象的元素。

因此,我们有时候也将结构体声明叫做模板,因为它勾勒出数据该如何存储,并没有实例化数据对象。

结构体变量的声明/定义

1、类型定义和变量声明分开

//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//结构体的标签被命名为simple,没有声明变量
struct simple
{
    int a;
    char b;
    double c;
};
//用simple标签的结构体,另外声明了变量t1、t2、t3
struct simple t1, t2[20], *t3;

2、类型定义的同时声明变量

//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//同时又声明了结构体变量s1
//这个结构体并没有标明其标签
struct 
{
    int a;
    char b;
    double c;
} s1;

这种情况,变量是一次性的。

在上面的声明中,第一个和第二声明被编译器当作两个完全不同的类型,即使他们的成员列表是一样的,如果令 t3=&s1,则是非法的。

3、使用typedef创建类型别名

typedef struct
{
    int a;
    char b;
    double c; 
} Simple2;
//现在可以用Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;

结构体变量的初始化

变量的初始化就看哪里声明了变量,声明的同时初始化就行。

比如:

struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} book = {"C 语言", "RUNOOB", "编程语言", 123456};

//或者
struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};

struct Books book = {"C 语言", "RUNOOB", "编程语言", 123456};

声明的语法为:用大括号括起来,用逗号分隔,然后根据变量的顺序依次赋值,注意每个初始化项目必须要和要初始化的结构体成员类型相匹配。

也可以不按照顺序来,这时候就需要指定初始化:

struct Books book = {
    .author = "RUNOOB",
    .subject = "编程语言"
};

如果在定义结构体变量的时候没有初始化,那么后面就不能全部一起初始化了,意思就是:

struct Books book;

//这样是不允许的
book = {"C 语言", "RUNOOB", "编程语言", 123456};

后面如果想要再赋值,只能一个一个赋值,比如:

book.title = "C 语言";
book.author = "RUNOOB";
book.subject = "编程语言";
book.book_id = "123456";

另外,结构体可以和数组一样,全部初始化为0。

struct student stu = {0};

访问结构体成员

为了访问结构的成员,我们使用成员访问运算符(.)

结构体变量名.成员名;

上面赋值的原理就是如此。
book.title = "JAVA 语言";
book.author = "NOGET";
book.subject = "另一种编程语言";
book.book_id = "789";

如果其成员本身又是一种结构体类型,那么可以通过若干个成员运算符,一级一级的找到最低一级成员再对其进行操作;

结构体变量名.成员.子成员………最低一级子成员;

结构体包含结构体

结构体的成员可以包含其他结构体,也可以包含指向自己结构体类型的指针,而通常这种指针的应用是为了实现一些更高级的数据结构如链表和树等。

//此结构体的声明包含了其他的结构体
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;
};

从数组到结构体的进步之处:
结构体可以认为是从数组发展而来的。其实数组和结构体都算是数据结构的范畴了,数组就是最简单的数据结构、结构体比数组更复杂一些,链表、哈希表之类的比结构体又复杂一些;二叉树、图等又更复杂一些。

数组有2个明显的缺陷:第一个是定义时必须明确给出大小,且这个大小在以后不能再更改;第二个是数组要求所有的元素的类型必须一致。更复杂的数据结构中就致力于解决数组的这两个缺陷。
结构体是用来解决数组的第二个缺陷的,可以将结构体理解为一个其中元素类型可以不相同的数组。结构体完全可以取代数组,只是在数组可用的范围内数组比结构体更简单。

关于结构体包含自身,先看这道题:

一开始我有点蒙,结构体中包含其本身,这种嵌套怎么能确定大小呢?

后来才想明白,里面包含的是结构体指针,不管指针指向哪里,其本身只占1个指针的大小,通常为4个字节。

p、q、r是3个指向结构体的指针变量,通过动态内存分配分别为它们分配所对应的内存块,即3个结点,然后对其成员赋值,这样3个结点就构成了一单链表。其内存映像如下图所示,p->num的值为10,q->next->num其实就是r.num,其值为30,所以输出结果为40。故正确答案是D。

补充:结构体的自引用。 

结构体内部的成员是否可以是该结构体本身。即结构体是否可以包含结构体本身?

举例:

struct self_contain
{
	char author[50];
	float price;
struct self_contain  mu;
};

这种类型的自引用是非法的,因为成员mu是另一个结构体,类型是struct self_contain它的内部还要包含自己的成员mu。
这个mu还将包括自己的成员mu,这样重复下去,永无止境。
像是一个永远不会终止的递归程序。

那么,结构体可以怎样被自引用?

答案是:使用结构体指针

举例:

struct self_contain
{
	char author[50];
	float price;
	struct self_contain*  mu;
};

mu现在是一个结构体指针,指针的长度是确定的
编译器在结构体的长度确定之前就已经知道了指针的长度,所以使用结构体指针的自引用是合法的。

结构体内部包含一个指向该结构体本身的指针,事实上,该指针所指向的是同一种类型的不同的结构体,链表和树就是使用这种技巧实现的。

结构体指针自引用的错误示例

这个错误示例与结构体初始化的方式有关,如果声明时省略结构体名(结构标签),使用结构体指针自引用也是不合法的!

举例:

typedef struct 
{
	char author[50];
	float price;
	SELF_CONTAIN*  mu;
}SELF_CONTAIN;

使用typedef创建SELF_CONTAIN并且其结构成员可以自引用,但是上述示例在定义mu时,SELF_CONTAIN并没有创建,所以在结构体内部定义mu时,结构体类型并没有创建,所有不合法。

合法的方式:

typedef struct self_contain_tag
{
	char author[50];
	float price;
	self_contain_tag*  mu;
}SELF_CONTAIN;

指向结构的指针

您可以定义指向结构的指针,方式与定义指向其他类型变量的指针相似,如下所示:

struct Books *struct_pointer;

现在,您可以在上述定义的指针变量中存储结构变量的地址。如下所示:

struct_pointer = &book;

为了使用指向该结构的指针访问结构的成员,您必须使用 -> 运算符,如下所示:

struct_pointer->title;

.和->访问结构体元素的实质是一样的,只是C语言规定:

用结构体变量来访问元素用.

用结构体指针来访问元素用->

示例如下:

#include <stdio.h>

struct stu
{
	int a;
	int b;
};

int main()
{
	struct stu jack = {14, 19};
   
	struct stu *p = &jack;
	
    printf("Hello, World! %d\n", p.a);
   
    return 0;
}

打印提示如下:

根据提示修改程序:

#include <stdio.h>

struct stu
{
	int a;
	int b;
};

int main()
{
	struct stu jack = {14, 19};
   
	struct stu *p = &jack;
	
    printf("Hello, World! %d\n", p->a);    //用结构体指针访问
	printf("Hello, World! %d\n", jack.b);  //直接用结构体变量访问
   
    return 0;
}

结果如下:

Hello, World! 14
Hello, World! 19

结构体变量作为函数形参

结构体变量作为函数形参的时候,实际上和普通变量(类似于int之类的)传参时表现是一模一样的。所以说结构体变量其实也是普通变量而已。


因为结构体一般都很大,所以如果直接用结构体变量进行传参,那么函数调用效率就会很低。(因为在函数传参的时候需要将实参赋值给形参,所以当传参的变量越大调用效率就会越低)。怎么解决?思路只有一个那就是不要传变量了,改传变量的指针(地址)进去。


结构体因为自身太大,所以传参应该用指针来传(但是程序员可以自己决定,你非要传结构体变量过去C语言也是允许的,只是效率低了);回想一下数组,为什么C语言设计的时候数组传参默认是传的数组首元素首地址而不是整个数组?  

以下展示常规用法:

#include <stdio.h>
#include <string.h>
 
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 信息 */
   printBook( Book1 );
 
   /* 输出 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

结构体的对齐访问

举例说明什么是结构体对齐访问
上面讲过结构体中元素的访问本质上还是用指针方式,结合这个元素在整个结构体中的偏移量和这个元素的类型来进行访问的。
但是实际上结构体的元素的偏移量比我们之前讲的还要复杂,因为结构体要考虑元素的对齐访问,所以每个元素实际占的字节数和自己本身的类型所占的字节数不一定完全一样。(譬如char c实际占字节数可能是1,也可以是2,也可能是3,也可以能4····)不像数组,每个元素的大小都是一样的,其本身已经是对齐的,所以访问起来效率较高。
一般来说,我们用.的方式来访问结构体元素时,我们是不用考虑结构体的元素对齐的。因为编译器会帮我们处理这个细节。但是因为C语言本身是很底层的语言,而且做嵌入式开发经常需要从内存角度,以指针方式来处理结构体及其中的元素,因此还是需要掌握结构体对齐规则。

结构体为何要对齐访问
结构体中元素对齐访问主要原因是为了配合硬件,也就是说硬件本身有物理上的限制,如果对齐排布和访问会提高效率,否则会大大降低效率。
内存本身是一个物理器件(DDR内存芯片,SoC上的DDR控制器),本身有一定的局限性:如果内存每次访问时按照4字节对齐访问,那么效率是最高的;如果你不对齐访问效率要低很多。
还有很多别的因素和原因,导致我们需要对齐访问。譬如Cache的一些缓存特性,还有其他硬件(譬如MMU、LCD显示器)的一些内存依赖特性,所以会要求内存对齐访问。
对比对齐访问和不对齐访问:对齐访问牺牲了内存空间,换取了速度性能;而非对齐访问牺牲了访问速度性能,换取了内存空间的完全利用。

结构体对齐的规则和运算
编译器本身可以设置内存对齐的规则,有以下的规则需要记住:32位编译器,一般编译器默认对齐方式是4字节对齐。

示例:

int

int

char

char

int

以上类型变量如何对齐的?

int占4个字节;int占4个字节;char占1个字节;char占1个字节;之后的int因为无法对齐,所以实际上,会另外占用4个字节。上面的两个char会总共占据4个字节空间,而后面2个空间是被浪费掉的。图例如下:

这里需要注意的是,当4字节对齐时,我们很容易产生一个误解,以为是以下的分布:

原则是,当能凑成4个字节的就凑,凑不成的时候再下一个。如果是8个字节,就会占4字节+4字节,也是4字节对齐。对于上方的两个图,很显然,第一种方案是最优化的。

结构体对齐的分析要点和关键:
1、结构体对齐要考虑:结构体整体本身必须安置在4字节对齐处,结构体对齐后的大小必须是4的倍数(编译器设置为4字节对齐时,如果编译器设置为8字节对齐,则这里的4是8)
2、结构体中每个元素本身都必须对齐存放,而每个元素本身都有自己的对齐规则。
3、编译器考虑结构体存放时,以满足以上2点要求的最少内存需要的排布来算。

对齐指令

gcc支持但不推荐的对齐指令:#pragma pack()   #pragma pack(n) (n=1/2/4/8)

#pragma是用来指挥编译器,或者说设置编译器的对齐方式的。编译器的默认对齐方式是4,但是有时候我不希望对齐方式是4,而希望是别的(譬如希望1字节对齐,也可能希望是8,甚至可能希望128字节对齐)。

常用的设置编译器编译器对齐命令有2种:第一种是#pragma pack(),这种就是设置编译器1字节对齐(有些人喜欢讲:设置编译器不对齐访问,还有些讲:取消编译器对齐访问);第二种是#pragma pack(4),这个括号中的数字就表示我们希望多少字节对齐。


我们需要#prgama pack(n)开头,以#pragma pack()结尾,定义一个区间,这个区间内的对齐参数就是n。


#prgma pack的方式在很多C环境下都是支持的,但是gcc虽然也可以不过不建议使用。

gcc推荐的对齐指令__attribute__((packed))     __attribute__((aligned(n)))

注意,是两个下划线,前后各有两个下划线。

__attribute__((packed))使用时直接放在要进行内存对齐的类型定义的后面,然后它起作用的范围只有加了这个东西的这一个类型。packed的作用就是取消对齐访问。


__attribute__((aligned(n)))使用时直接放在要进行内存对齐的类型定义的后面,然后它起作用的范围只有加了这个东西的这一个类型。它的作用是让整个结构体变量整体进行n字节对齐(注意是结构体变量整体n字节对齐,而不是结构体内各元素也要n字节对齐)

比如:

struct stu{
    char sex;
    int length;
    char name[10];
}__attribute__ ((aligned (4)));

参考阅读blog:

http://www.cnblogs.com/dolphin0520/archive/2011/09/17/2179466.html
http://blog.csdn.net/sno_guo/article/details/8042332

内存对齐规定:结构体的总大小为结构体最宽基本类型成员大小的整数倍;

可见最宽的float是4,数组的形式只是多个数据放在一起而已。

联合体里面总共是5个字节,要为4的倍数所以为8个字节。

所以为8+8+4=20。

结构体中数组的初始化

如果在结构体中有数组,可以在初始化时一次性赋值,而且按照顺序依次赋值即可。

举例如下:

#include <stdio.h>

typedef struct {
	char *name;
	int score[3];
} STU;

int main()
{	
	STU s1 = {"cao", 80, 95, 99};
	s1.score[0] = 100;
    printf("%d-%d-%d\n", s1.score[0], s1.score[1], s1.score[2]);
   
    return 0;
}

// 100-95-99

没有赋值的则默认为0,但是不能赋不同类型的值。

#include <stdio.h>

typedef struct {
	char *name;
	int score[3];
} STU;

int main()
{	
	STU s1 = {"cao", 80, 95};
	s1.score[0] = 100;
    printf("%d-%d-%d\n", s1.score[0], s1.score[1], s1.score[2]);
   
    return 0;
}

//100-95-0

下面这样的就会报警告。

#include <stdio.h>

typedef struct {
	char *name;
	int score[3];
	char *subject;
} STU;

int main()
{	
	STU s1 = {"cao", 80, 95, "math"};
	s1.score[0] = 100;
    printf("%d-%d-%s\n", s1.score[0], s1.score[1], s1.score[2]);
   
    return 0;
}

/*
100-95-math
main.c: In function ‘main’:
main.c:11:27: warning: initialization of ‘int’ from ‘char *’ makes integer from pointer without a cast [-Wint-conversion]
   11 |  STU s1 = {"cao", 80, 95, "math"};
      |                           ^~~~~~
main.c:11:27: note: (near initialization for ‘s1.score[2]’)

*/

补充

对结构体指针解引用能得到什么?

通常,对普通变量指针解引用能得到变量的值,其实可以将*p就看做一个变量来使用;

同理,对结构体指针解引用可以看做是结构体变量。

但是要注意的是,普通类型的变量能直接得到值,但是结构体变量需要再进行点操作.才能得到具体的值。

经过验证,结构体变量名并不是一个地址。就是一个普通的变量。

这个变量默认值就是第一个元素的值。

测试如下:

#include <stdio.h>

int main()
{
    struct m
    {
   		 int a;
	     int b;
    };
	
	 struct m c = {10, 19};
	
    printf("Hello, World! %d\n", c);
	printf("Hello, World! %d\n", c.a);
	printf("Hello, World! %d\n", c.b);
   
    return 0;
}

其中,c和c.a的值是一样的。

运行结果如下:

如果要得到结构体的地址,就要struct m *p = &c,此时,p就是首元素的地址。

验证如下:

#include <stdio.h>

int main()
{
    struct m
    {
   		 int a;
	     int b;
    };
	
	struct m c = {10, 19};
	struct m *p = &c;
	
    printf("Hello, World! %d\n", *p);
   
    return 0;
}

运行结果如下:

不过,结构体中变量名表示什么并不重要,指针表示什么也不是很重要。因为变量可以通过.来访问变量,指针可以通过->来访问变量,都很直接。

柔性数组

先来了解一下“不完整类型(incomplete type)”,不完整类型是这样一种类型,它缺乏足够的信息(如长度)去描述一个完整的对象。C99标准支持不完整类型,其形式形如int a[],但也有一些编译器把int a[0] 作为非标准扩展来支持。

知道了不完整类型,就可以去了解柔性数组了。在日常的编程中,有时候需要在结构体中存放一个长度动态的字符串,一般的做法,是在结构体中定义一个指针成员,这个指针成员指向该字符串所在的动态内存空间,例如:

typedef struct   
{  
       int a;  
       double b;  
       char *p;  
} test;

这样子是很容易想到的做法,但是却有一点不方便的地方。比如为test对象分配空间之后,还需要再为p指针分配一次空间。这样子,如果第二次为指针分配空间时malloc失败了,就必须要回滚释放第一个分配的结构体,这样带来了编码麻烦。

于是,有一种做法是,只进行一次分配。可以把代码修改为:

typedef struct   
{  
       int a;  
       double b;  
       char p[0];   // 若有些编译器不支持,可以改成char p[];  
} test;

这样子进行分配的时候,只需要:

char a[] = "hello world";
test *stpTest = (test *)malloc(sizeof(test) + strlen( a ) + 1 );
strcpy(stpTest + 1, a );

释放结构体的时候,也不会出现忘记释放指针导致的内存泄露问题,因为只需要:

free(stpTest);

在进行Linux内核开发或者嵌入式开发时,经常会遇到结构体的最后出现char data[], char data[0], char data[1],这样的代码,这就是柔性数组的实现。柔性数组也并没有定义柔性数组,只是所支持的不完整类型产生了柔性数组这样神奇的结构。

使用柔性数组的好处
其实上面也说到了,总结而来就是:
(1)不需要初始化,数组名直接就是所在的偏移
(2)不占任何空间,指针需要占用int长度空间,空数组不占任何空间。(注意,char data[1];这种形式是占用一个单位的空间的)
(3)空间一次分配,防止内存泄漏
(4)分配连续的内存,减少内存碎片化。(因为指针所分配的空间不是连续的,而数组占用连续的空间)

使用柔性数组时需要注意的
(1)必须是结构体的最后一个成员
(2)柔性数组之上,需要有其他的成员(结构体中不能只有一个柔性数组)
(3)sizefo返回的结构体的大小不包括柔性数组的内存(如果是char data[1]就会有一个单位的空间)

使用char data[0]与char data[1]的区别(转载的)
结构体中最后一个成员为[0]长度数组的用法:这是个广泛使用的常见技巧,常用来构成缓冲区。比起指针,用空数组有这样的优势:(1)、不需要初始化,数组名直接就是所在的偏移;(2)、不占任何空间,指针需要占用int长度空间,空数组不占任何空间。“这个数组不占用任何内存”,意味着这样的结构节省空间;“该数组的内存地址就和它后面的元素地址相同”,意味着无需初始化,数组名就是后面元素的地址,直接就能当指针使用。

这样的写法最适合制作动态buffer,因为可以这样分配空间malloc(sizeof(structXXX) + buff_len); 直接就把buffer的结构体和缓冲区一块分配了。用起来也非常方便,因为现在空数组其实变成了buff_len长度的数组了。这样的好处是:(1)、一次分配解决问题,省了不少麻烦。为了防止内存泄露,如果是分两次分配(结构体和缓冲区),那么要是第二次malloc失败了,必须回滚释放第一个分配的结构体。这样带来了编码麻烦。其次,分配了第二个缓冲区以后,如果结构里面用的是指针,还要为这个指针赋值。同样,在free这个buffer的时候,用指针也要两次free。如果用空数组,所有问题一次解决。(2)、小内存的管理是非常困难的,如果用指针,这个buffer的struct部分就是小内存了,在系统内存在多了势必严重影响内存管理的性能。要是用空数组把struct和实际数据缓冲区一次分配大块问题,就没有这个问题。如此看来,用空数组既简化编码,又解决了小内存碎片问题提高了性能。

结构体中最后一个成员为[1]长度数组的用法:与长度为[0]数组的用法相同,改写为[1]是出于可移植性的考虑。有些编译器不支持[0]数组,可将其改成[]或[1]。

类数组形式访问结构体变量

数组里,知道一个元素的指针,那么指针直接加减1就可以得到左右的元素值,而不用考虑元素的字节数,这是数组内部实现的,那么结构体呢?

访问结构体变量,通常都是通过点号或者箭头来访问,但是,也可以通过指针来访问。

如果知道某一个元素的地址,那么,可以通过地址加减的方式来获取其他元素的地址。

这一般用在结构体元素类型都一致的情况下,此时,元素的排列其实就跟数组一模一样。

之前学习过,当类型相同的数据连续存放时,是可以通过首地址加下标的方式来访问的,这其实就是数组的原理。

一开始,我以为结构体用类数组方式访问时,指针需要根据元素的大小来计算,一个字节一个地址,其实不是的,而是跟数组一样,一个元素加减1即可。这一点,要十分注意。

但是有个前提条件,就是结构体里的元素类型要一致,其实就是数组的特征。

补充:结构体数组

结构体数组和普通数组类似。

奇怪的是,我先定义了好几个结构体变量并且初始化了,随后定义了一个结构体数组,将这些变量依次都放在数组里,报错

initialzer element is not a compile-time constant

这是因为数组在直接初始化时,里面的元素必须是一个确定的值,不能是变量。

因为数组arr[i]本身就已经是一个变量,我把另一个变量给进来,是错的。

但是可以通过(arr[i] = 一个变量)的形式,来让变量arr[i]获得值。

比如我想定义4个结构体变量,要么就分别定义4个变量,要么就定义一个长度为4的数组,不要先定义4个变量,再定义一个长度为4的数组,然后把这4个变量放进对应的数组里,以为每个数组下标对应一个变量。这种思路是不对的。

st arr[4] = {

{},

{},

{},

{}

};

之后,arr[0] arr[1] arr[2] arr[3]就是这几个对应的结构体变量。

使用数组形式的话,还可以直接使用arr指针,然后用指针来操作结构体。

另外,虽然不能存储结构体变量,但是可以放结构体变量指针,因为指针是一个确定的值。

先定义结构体变量,再定义数组来存储结构体变量指针是可以的。

但是,使用数组定义就可以直接用arr指针,所以上述方式也没什么必要。

和普通数组类比下,就能明白了。

int a = 0;

int b = 1;

int c = 2;

int d = 3;

int e[4] = {a, b, c, d};

这种操作是不合法的。

但是

int *f[4] = {&a, &b, &c, &d}

却是可行的。

我们操作结构体时,一般推荐使用结构体指针来操作,在STM32标准库里,初始化时就有很多这样的操作。

比如:

sizeof(结构体类型)和普通的变量类型一样,能得到对应结构体的大小。 

  • 17
    点赞
  • 87
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用\[1\]:C语言字节对齐问题详解中提到了C语言中的字节对齐问题。在结构体中,为了提高内存访问的效率,编译器会对结构体进行字节对齐。这意味着结构体的成员在内存中并不是紧凑排列的,而是按照一定的规则进行对齐。具体的对齐规则取决于编译器和编译选项。\[1\] 引用\[2\]:在C语言中,可以使用宏offsetof来获取结构体成员相对于结构体开头的字节偏移量。这个宏非常有用,可以帮助我们计算出每个结构体成员相对于结构体开头的偏移字节数。通过这个宏,我们可以更好地理解结构体的内存布局。\[2\] 引用\[3\]:在C语言中,指针和结构体的组合常常用于处理复杂的数据结构。指针可以指向结构体的成员,通过指针可以方便地对结构体进行操作。指针和结构体的组合可以实现更灵活的数据处理和内存管理。\[3\] 综上所述,C语言中的指针结构体组合可以用于处理复杂的数据结构,而字节对齐问题则是在结构体中为了提高内存访问效率而进行的优化。通过使用宏offsetof,我们可以更好地理解结构体的内存布局。 #### 引用[.reference_title] - *1* *3* [结构体指针,C语言结构体指针详解](https://blog.csdn.net/weixin_34069265/article/details/117110735)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [C语言结构体详解](https://blog.csdn.net/m0_70749276/article/details/127061692)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值