C语言中结构体(struct)的详细分解与使用(中)

简介: C语言中结构体(struct)的详细分解与使用(中)

C语言中结构体(struct)的详细分解与使用(上)https://developer.aliyun.com/article/1389316


第五:对于结构体变量的初始化


先回忆一下关于基本数据类型和数组类型的初始化:

int a = 0;
int array[4] = {1,2,3,4};//每个元素用逗号隔开


回忆一下数组初始化问题:

f12cb7a6df74cf12dca1ee7a31d35a13.jpg

再回到结构体变量的初始化吧

关于结构体变量的初始化与初始化数组类似;

也是使用花括号括起来,用逗号分隔的初始化好项目列表。注意,每个初始化项目必须要和要初始化的结构体成员类型相匹配。

struct book s1=
{//对结构体初始化 
  "yuwen", //title为字符串 
  "guojiajiaoyun", //author为字符数组 
  22.5     //value为flaot型 
};
//要对应起来,用逗号分隔开来,与数组初始化一样;


加入一点小知识,关于结构体初始化和存储类时期的问题:如果要初始化一个具有静态存储时期的结构体,初始化项目列表中的值必须是常量表达式;

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

/这样是可以的,在定义变量的时候就初始化了;
struct book s1=
{//对结构体初始化 
 "guojiajiaoyun",//author为字符数组 
 "yuwen",//title为字符串 
 22.5
};
/这种就不行了,在定义变量之后,若再要对变量的成员赋值,那么只能单个赋值了;
struct book s1;
s1={ 
 "guojiajiaoyun",//author为字符数组 
 "yuwen",//title为字符串 
 22.5
};//这样就是不行的,只能在定义的时候初始化才能全部赋值,之后就不能再全体赋值了,只能单个赋值;
只能:
s1.title = "yuwen";........//单个赋值;


对于结构体的指定初始化:

026c1cc7ee8a07ef2923f89d47fad9f0.jpg

访问结构体成员

结构体就像一个超级数组,在这个超级数组内,一个元素可以是char类型,下个元素就可以是flaot类型,再下个还可以是int数组型,这些都是存在的。

在数组里面我们通过下标可以访问一个数组的各个元素,那么如何访问结构体中的各个成员呢?

用结构成员运算符点(.)就可以了;

结构体变量名.成员名;

注意,点其结合性是自左至右的,它在所有的运算符中优先级是最高的;

例如,s1.title指的就是s1的title部分;s1.author指的就是s1的author部分;s1.value指的就是s1的value部分。

然后就可以像字符数组那样使用s1.title,像使用float数据类型一样使用s1.value;

注意,s1 虽然是个结构体,但是 s1.value 却是 float 型的。

因此 s1.value 就相当于 float 类型的变量名一样,按照 float 类型来使用;

例如:

printf(“%s\n%s\n%f”,s1.title,s1.author,s1.value); //访问结构体变量元素

注意 scanf(“%d”,&s1.value); 这语句存在两个运算符,&和结构成员运算符点。

按照道理我们应该将(s1.value括起来,因为他们是整体,表示s1的value部分)但是我们不括起来也是一样的,因为点的优先级要高于&。

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

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

struct date
{
  int year;
  int month;
  int day;
};
struct student
{
  char name[10];
  struct date birthday;
}student1;
//若想引用student的出生年月日,可表示为;student.brithday.year;
brithday是student的成员;year是brithday的成员;


第六:整体与分开


可以将一个结构体变量作为一个整体赋值给另一相同类型的结构体变量,可以到达整体赋值的效果;这个成员变量的值都将全部整体赋值给另外一个变量;

不能将一个结构体变量作为一个整体进行输入和输出;在输入输出结构体数据时,必须分别指明结构体变量的各成员;

6e3830ed884983bc50b2dd85f154e1b3.jpg

小结:除去“相同类型的结构体变量可以相互整体赋值”外,其他情况下,不能整体引用,只能对各个成员分别引用;


第七:结构体长度


数据类型的字节数:

16位编译器

char :1个字节

char*(即指针变量): 2个字节

short int : 2个字节

int:  2个字节

unsigned int : 2个字节

float: 4个字节

double:   8个字节

long:   4个字节

long long: 8个字节

unsigned long:  4个字节


32位编译器


char :1个字节

char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器)

short int : 2个字节

int: 4个字节

unsigned int : 4个字节

float:  4个字节

double:   8个字节

long:   4个字节long long:  8个字节

unsigned long: 4个字节

那么,下面这个结构体类型占几个字节呢?

typedef struct
{
  char addr;
  char name;
  int  id;
}PERSON;


通过printf("PERSON长度=%d字节\n",sizeof(PERSON));可以看到结果:

43f5fda74832780c295cde9d8a6e73e7.png

结构体字节对齐

通过下面的方式,可以清楚知道为什么是8字节。


1、定义20个char元素的数组

char ss[20]={0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29};


2、定义结构体类型的指针ps指向ss数组

PERSON *ps=(PERSON *)ss;


3、打印输出各个成员

printf("0x%02x,0x%02x,0x%02x\n",ps->addr,ps->name,ps->id);printf("PERSON长度=%d字节\n",sizeof(PERSON));


76f166e1100622225c779eb7fe0937cf.png

可以看到addr和name都只占一个字节,但是未满4字节,跳过2字节后才是id的值,这就是4字节对齐。结构体成员有int型,会自动按照4字节对齐。

9b8c2dd63e82e81894cebf6e99c57ea9.png

把结构体成员顺序调换位置

typedef struct
{
  char addr;
  int  id;
  char name;
}PERSON;


输出:

5d9a9212efb09b6360046193439b50d7.png

eada66cdd9a9cba8eeb1691df9530135.png

按照下面的顺序排列:

typedef struct
{
int  id;
char addr;
char name;
}PERSON;


输出:

13a74eb6c275745304317d681b7db690.png

c6a0d6fbf515231a5edcbf360df83d59.png

可见,结构体成员顺序优化,可节省空间。

如果全部成员都是 char 型,会按照 1 字节对齐,即

typedef struct
{
char addr;
char name;  
char  id;
}PERSON;


输出结果:

7c17784ce2cb775f106f0e65ec6de1f6.png

86e7b41030678357ab20bc6db1748776.png


结构体嵌套

结构体嵌套结构体方式:

typedef struct
{
  char addr;
  char name;  
  int  id;
}PERSON;
typedef struct
{
  char age;
  PERSON  ps1;
}STUDENT;


先定义结构体类型PERSON,再定义结构体STUDENT,PERSON作为它的一个成员。

按照前面的方法,打印各成员的值。


1、定义STUDENT 指针变量指向数组 ss

STUDENT *stu=(STUDENT *)ss;


2、打印输出各成员和长度

printf("0x%02x,0x%02x,0x%02x,0x%02x\n",stu->ps1.addr,stu->ps1.name,stu->ps1.id,stu->age);
printf("STUDENT长度=%d字节\n",sizeof(STUDENT));

7aac29e45c40c7e9411e1c5ed93f1004.png

a020fe754c6ce237b949b20d9bceff91.png

调换STUDENT成员顺序,

typedef struct
{
  PERSON  ps1;
  char age;
}STUDENT;


输出结果:

2c606efbc999235ab3b144b901b3bd9d.png

57af3e2b6b70f251e581796b72fb77d3.png


结构体嵌套其实没有太意外的东西,只要遵循一定规律即可:

//对于“一锤子买卖”,只对最终的结构体变量感兴趣,其中A、B也可删,不过最好带着  
struct A{   
  struct B{  
             int c;  
          }b;  
}a;  
//使用如下方式访问:
a.b.c = 10;


特别的,可以一边定义结构体B,一边就使用上:

struct A{
    struct B{
       int c;  
        }b;
struct B sb;
}a;  


使用方法与测试:

a.b.c = 11;  
printf("%d\n",a.b.c);  
a.sb.c = 22;  
printf("%d\n",a.sb.c);


结果无误。

但是如果嵌套的结构体B是在A内部才声明的,并且没定义一个对应的对象实体b,这个结构体B的大小还是不算进结构体A中。


占用内存空间

struct结构体,在结构体定义的时候不能申请内存空间,不过如果是结构体变量,声明的时候就可以分配——两者关系就像C++的类与对象,对象才分配内存(不过严格讲,作为代码段,结构体定义部分“.text”真的就不占空间了么?当然,这是另外一个范畴的话题)。

结构体的大小通常(只是通常)是结构体所含变量大小的总和,下面打印输出上述结构体的size:

printf("size of struct man:%d\n",sizeof(struct man));  
printf("size:%d\n",sizeof(Huqinwei));


结果毫无悬念,都是28:分别是char数组20,int变量4,浮点变量4。

下边说说不通常的情况

对于结构体中比较小的成员,可能会被强行对齐,造成空间的空置,这和读取内存的机制有关。

为了效率,通常32位机按4字节对齐,小于的都当4字节,有连续小于4字节的,可以不着急对齐,等到凑够了整,加上下一个元素超出一个对齐位置,才开始调整,比如3+2或者1+4,后者都需要另起(下边的结构体大小是8bytes),相关例子就多了,不赘述。

struct s
{
  char a;  
  short b;  
  int c;  
};


相应的,64 位机按 8 字节对齐。不过对齐不是绝对的,用#pragma pack()可以修改对齐,如果改成1,结构体大小就是实实在在的成员变量大小的总和了。

和C++的类不一样,结构体不可以给结构体内部变量初始化,。

如下,为错误示范:

#include<stdio.h>  
//直接带变量名
struct stuff{
//      char job[20] = "Programmer";  
//      char job[];  
//      int age = 27;  
//      float height = 185;  
};

PS:结构体的声明也要注意位置的,作用域不一样。

C++的结构体变量的声明定义和C有略微不同,说白了就是更“面向对象”风格化,要求更低。


为什么有些函数的参数是结构体指针型

如果函数的参数比较多,很容易产生“重复C语言代码”,例如:

int get_video(char **name, long *address, int *size, time_t *time, int *alg)
{
    ...
}
int handle_video(char *name, long address, int size, time_t time, int alg)
{
    ...
}
int send_video(char *name, long address, int size, time_t time, int alg)
{
    ...
}


上述C语言代码定义了三个函数:get_video() 用于获取一段视频信息,包括:视频的名称,地址,大小,时间,编码算法。

然后 handle_video() 函数根据视频的这些参数处理视频,之后 send_video() 负责将处理后的视频发送出去。下面是一次调用:

char *name = NULL;
long address;
int size, alg;
time_t time;
get_video(&name, &address, &size, &time, &alg);
handle_video(name, address, size, time, alg);
send_video(name, address, size, time, alg);


从上面这段C语言代码来看,为了完成视频的一次“获取”——“处理”——“发送”操作,C语言程序不得不定义多个变量,并且这些变量需要重复写至少三遍。

虽说C语言程序的代码风格因人而异,但是“重复的代码”永远是应尽力避免的,不管怎么说,每次使用这几个函数,都需要定义很多临时变量,总是非常麻烦的。所以,这种情况下,完全可以使用C语言的结构体语法:

struct video_info
{
  char *name;
  long address;
  int size;
  int alg;
  time_t time;
};


定义好 video_info 结构体后,上述三个C语言函数的参数可以如下写,请看:

int get_video(struct video_info *vinfo)
{
    ...
}
int handle_video(struct video_info *vinfo)
{
    ...
}
int send_video(struct video_info *vinfo)
{
    ...
}


修改后的C语言代码明显精简多了,在函数内部,视频的各个信息可以通过结构体指针 vinfo 访问,例如:

printf("video name: %s\n", vinfo->name);
long addr = vinfo->address;
int size = vinfo->size;


事实上,使用结构体 video_info 封装视频信息的各个参数后,调用这几个修改后的函数也是非常简洁的:

struct video_info vinfo = {0};
get_video(&vinfo);
handle_video(&vinfo);
send_video(&vinfo);


从上述C语言代码可以看出,使用修改后的函数只需定义一个临时变量,整个代码变得非常精简。

读者应该注意到了,修改之前的 handle_video() 和 send_video() 函数原型如下:

int handle_video(char *name, long address, int size, time_t time, int alg);
int send_video(char *name, long address, int size, time_t time, int alg);


根据这段C语言代码,我们知道 handle_video() 和 send_video() 函数只需要读取参数信息,并不再修改参数,那为什么使用结构体 video_info 封装数据,修改后的 handle_video() 和 send_video() 函数参数是 struct video_info  *指针型呢?

int handle_video(struct video_info *vinfo);
int send_video(struct video_info *vinfo);


既然 handle_video() 和 send_video() 函数只需要读取参数信息,那我们就无需再使用指针型了呀?的确如此,这两个函数的参数直接使用 struct video_info 型也是可以的:

int handle_video(struct video_info vinfo)
{
    ...
}
int send_video(struct video_info vinfo)
{
    ...
}


似乎这种写法和使用  struct video_info  *指针型 参数的区别,无非就是函数内部访问数据的方式改变了而已。但是,如果读者能够想到我们之前讨论过的 C语言函数的“栈帧”概念,应该能够发现,使用指针型参数的 handle_video() 和 send_video() 函数效率更好,开销更小。


C语言中结构体(struct)的详细分解与使用(下)https://developer.aliyun.com/article/1389347

目录
相关文章
|
6天前
|
存储 C语言
如何在 C 语言中实现结构体的深拷贝
在C语言中实现结构体的深拷贝,需要手动分配内存并逐个复制成员变量,确保新结构体与原结构体完全独立,避免浅拷贝导致的数据共享问题。具体方法包括使用 `malloc` 分配内存和 `memcpy` 或手动赋值。
22 10
|
5天前
|
存储 大数据 编译器
C语言:结构体对齐规则
C语言中,结构体对齐规则是指编译器为了提高数据访问效率,会根据成员变量的类型对结构体中的成员进行内存对齐。通常遵循编译器默认的对齐方式或使用特定的对齐指令来优化结构体布局,以减少内存浪费并提升性能。
|
10天前
|
编译器 C语言
共用体和结构体在 C 语言中的优先级是怎样的
在C语言中,共用体(union)和结构体(struct)的优先级相同,它们都是用户自定义的数据类型,用于组合不同类型的数据。但是,共用体中的所有成员共享同一段内存,而结构体中的成员各自占用独立的内存空间。
|
10天前
|
存储 C语言
C语言:结构体与共用体的区别
C语言中,结构体(struct)和共用体(union)都用于组合不同类型的数据,但使用方式不同。结构体为每个成员分配独立的内存空间,而共用体的所有成员共享同一段内存,节省空间但需谨慎使用。
|
14天前
|
编译器 C语言 C++
C语言结构体
C语言结构体
17 5
|
15天前
|
编译器 Linux C语言
C语言 之 结构体超详细总结
C语言 之 结构体超详细总结
12 0
|
20天前
|
存储 编译器 Linux
深入C语言:探索结构体的奥秘
深入C语言:探索结构体的奥秘
|
19天前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
30 3
|
10天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
30 10
|
3天前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。