C语言之自定义类型------结构体

简介: C语言之自定义类型------结构体

在前面我们学习过char,short,int,long,float,double等,这些都属于内置类型,C语言本身就含有的数据类型。

对于结构体,枚举,联合体等这种复杂的类型,我们称之为自定义类型。

结构体:

结构体的定义:结构是一些值的集合,这些值被称为成员变量,结构的每个成员可以是不同类型的变量。
结构体的定义:
struct tag //结构体标签
{
  member - list;//成员变量列表:可以是同种类型,也可以是不同种类型
}variable-list;//变量列表:注意后面的“;”,不要忘记了
结构体的声明:
#include<stdio.h>
struct Stu
{
  char name[20];
  int age;
  char phone[20];
  char sex;
}Student1, Student2;//创建全局结构体变量
struct Stu Student3;//创建全局结构体变量
int main()
{
  //创建局部结构体变量
  struct Stu Student4;
  struct Stu Student5;
  struct Stu Student6;
  return 0;
}
匿名结构体类型:

与上面所不同的是,匿名结构体是没有名字的,它的特点是只能使用一次,并且只能在创建的时候定义结构体变量,就是下述S的这种创建方式。

struct 
{
  char name[20];
  int age;
  char phone[20];
  char sex;
}S;

那么省略了结构体标签,结构体指针是否能够指向两个成员变量相同的结构体呢?

举例:

#include<stdio.h>
struct 
{
  int a;
  int b;
}sa;
struct 
{
  int a;
  int b;
}*pasa;
int main()
{
  pasa = &sa;
}

当你尝试运行该程序,你会发现,编译并没有通过,编译器指出,无法实现将指针变量pasa的指向改变。

原因是即使上述两个编译器的成员变量是完全相同的,但是编译器依然会把二者当成完全不同的两个类型,所以是无法通过编译的。


结构体的自引用:

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

举例:

在数据结构中,我们接触过链表,如下所示:


想用结构体表示链表,是否可以用以下这种表示方法?

struct Node
{
  int data;
  struct Node n;
};


data包含存储的数据,struct Node n表示下一个节点,这样有点道理,但不多,原因是:struct Node n既包含了下个节点还包含了上个节点的数据等,这样反复包含,我们根本无法确定n的大小,那么sizeof(struct Node)的值是无法确定的,在此后,如果我们想使用该结构体,根本没有办法确定应该给它创建多大的空间。


因此,我们可以得出一个结论:结构体类型可以包含除了自己以外的结构体变量。


对于表示上述链表的正确代码应为:

#include<stdio.h>
struct Node
{
  int data;//4个字节
  struct Node*next;//定义一个类型为struct Node的指针,字节大小为4/8
};
int main()
{
  sizeof(struct Node);//字节大小为8-12
}

如下图所示:

对类型名重命名:

举例:

typedef struct Node
{
  int data;
  struct Node*next;
}Node1;//给struct Node起别名为Node1
//起别名后:原始名和别名都可以直接使用
int main()
{
  struct Node s;//使用原始名创建结构体变量
  Node1 s1;//使用别名创建结构体变量
}

对匿名结构体进行重命名:

当我们省略原始名之后,此时的结构体就变成了匿名结构体,那么,我们能否使用别名定义指针?

如下所示:

typedef struct 
{
  int data;
  Node1* next;
}Node1;
int main()
{
  Node1 s1;
}

答案是不可以的,程序由上自下进行,当程序运行到Node1* next;,结构体别名并未被定义,所以是不能使用这种方法进行的,因此对结构体进行重命名之后不要省略原始名。

结构体变量的初始化:

举例:

#include<stdio.h>
struct Stu 
{
  int age;
  char name[20];
  char phone[20];
};
int main()
{
  struct Stu s={19,"张三","217361"};
  printf("%d %s %s", s.age, s.name, s.phone);
  return 0;
}
19 张三 217361

结构体嵌套初始化:

#include<stdio.h>
struct Stu1 
{
  char sex[10];//注意这里的sex:单个汉字,不能将类型直接定义为%c
  //原因:汉字由两个字符组成,%c根据ascll码值只能识别数字来打印,如果我们使用%c打印单独的汉字那么就会乱码
  char address[20];
};
struct Stu 
{
  int age;
  char name[20];
  char phone[20];
  struct Stu1 s1;//结构体Stu1作为结构体Stu的成员变量
};
int main()
{
  struct Stu s = { 19,"张三","217361",{"男","北京市海淀区"}};
  //结构体嵌套初始化时需要注意:成员变量为结构体时,初始化的数据需要用括号括起来
  printf("%d %s %s %s %s", s.age, s.name, s.phone,s.s1.sex,s.s1.address);
  return 0;
}

输出如下所示:

结构体内存对齐:

举例:

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

我们以Stu1为例进行分析:

结构体的对齐规则:

1:第一个成员在与结构体变量偏移量为零的地址处

分析如下:

2:其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

对齐数=编译器默认的一个对齐数与该成员大小的较小值。

vs中默认的值为8,而对于gcc编译器来说,是没有默认对齐数的,因此对齐数就是该成员大小

注:变量和变量之间的空间会被浪费


3:结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。

4:如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有的最大对齐数(含嵌套结构体的对齐数)的整数倍。

举例:

struct Stu3 
{
  double c1;
  char a;
  int c2;
};
struct Stu4 
{
  char c1;
  struct Stu3 s3;
  double d;
};

分析如下:

为什么存在内存对齐?

1:平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。


2:性能原因:数据结构(尤其是栈)应尽可能地在自然边界上对齐,原因在于:为了访问未对齐的内存,处理器需要做两次内存访问,而对齐的内存访问仅需要一次内存访问。


总结来说:结构体的内存对齐是拿空间换取时间的做法。


分析如下:

在设计结构体的时候,如果我们既要满足对齐还要满足节省空间,那么就需要让占用空间小的成员尽量集中在一起。

举例:

struct Stu3 
{
  char c;
  int a;
  char c2;
};
struct Stu4 
{
  char c1;
  char c2;
  int a;
};
//Stu4所占的空间少一些,因为浪费的空间少

修改默认对齐数:

方法:#pragma pack(4)//设置默认对齐数

#pragma pack()//取消设置的默认对齐数

修改之后,浪费的空间数就会减少。

举例:

#pragma pack(4)//设置默认对齐数为4
struct Stu3 
{
  char c;
  double b;
};

输出结果为

12

取消设置的默认对齐数

输出结果为

16

这样操作之后,空间就节省了4个字节,因此,结构在对齐方式不合适的时候,我们可以自己更改默认对齐数。

offsetof(structName,memberName):

用来计算成员变量的偏移量,它的头文件是<stddef.h>

举例:

#include<stdio.h>
#include<stddef.h>
struct Stu3 
{
  char c;
  int i;
  double b;
};
struct Stu4 
{
  char c1;
  char c2;
  int a;
};
int main()
{
  printf("%d ", offsetof(struct Stu3, c));
  printf("%d ", offsetof(struct Stu3, i));
  printf("%d ", offsetof(struct Stu3, b));
}

输出如下所示:

0 4 8

结构体传参:

举例:

#include<stdio.h>
struct Stu3 
{
  char c;
  int i;
  double b;
};
void Init(struct Stu3 tmp)//更改结构体Stu3的值
{
  tmp.c = 'b';
  tmp.i = 0;
  tmp.b = 13.14;
}
int main()
{
  struct Stu3 s3;
  s3.c = 'h';
  s3.i = 3;
  s3.b = 23.45;
  Init(s3);//结构体名传递
  printf("%c ", s3.c);
  printf("%d ", s3.i);
  printf("%f ", s3.b);
  return 0;
}

输出如下所示:

为什么值没有发生改变呢?

和我们之前在指针学习哪里是一样的,这里也是引用传递,编译器在函数Init中更改的只是Stu3的复制品,因此出Init函数之后值还是原来的值,并没有发生真正意义上的改变。

正确的传参方式应该是地址传递:

代码可修改为:

#include<stdio.h>
struct Stu3 
{
  char c;
  int i;
  double b;
};
void Init(struct Stu3* tmp)
{
  tmp->c = 'b';
  tmp->i = 0;
  tmp->b = 13.14;
}
void print(struct Stu3 tmp)
{
  printf("%c ", tmp.c);
  printf("%d ", tmp.i);
  printf("%f ", tmp.b);
}
void print1(struct Stu3* tmp)
{
  printf("%c ", tmp->c);
  printf("%d ", tmp->i);
  printf("%f ", tmp->b);
}
int main()
{
  struct Stu3 s3;
  s3.c = 'h';
  s3.i = 3;
  s3.b = 23.45;
  Init(&s3);//通过地址传递,进行结构体变量的修改
  print(s3);//通过值传递,打印变量
  print1(&s3);//通过地址传递,打印变量
  return 0;
}

此时的输出结果正是我们修改后的值:

但是在打印修改后的变量时,我们也选用了两种方法,值传递和地址传递,那么这两种方法,那种好一些呢?

答案是:地址传递

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

因此,结构体传参的时候,要传结构体的地址。

相关文章
|
22天前
|
存储 C语言
如何在 C 语言中实现结构体的深拷贝
在C语言中实现结构体的深拷贝,需要手动分配内存并逐个复制成员变量,确保新结构体与原结构体完全独立,避免浅拷贝导致的数据共享问题。具体方法包括使用 `malloc` 分配内存和 `memcpy` 或手动赋值。
30 10
|
22天前
|
安全 编译器 Linux
【c语言】轻松拿捏自定义类型
本文介绍了C语言中的三种自定义类型:结构体、联合体和枚举类型。结构体可以包含多个不同类型的成员,支持自引用和内存对齐。联合体的所有成员共享同一块内存,适用于判断机器的大小端。枚举类型用于列举固定值,增加代码的可读性和安全性。文中详细讲解了每种类型的声明、特点和使用方法,并提供了示例代码。
19 3
|
21天前
|
存储 大数据 编译器
C语言:结构体对齐规则
C语言中,结构体对齐规则是指编译器为了提高数据访问效率,会根据成员变量的类型对结构体中的成员进行内存对齐。通常遵循编译器默认的对齐方式或使用特定的对齐指令来优化结构体布局,以减少内存浪费并提升性能。
|
26天前
|
编译器 C语言
共用体和结构体在 C 语言中的优先级是怎样的
在C语言中,共用体(union)和结构体(struct)的优先级相同,它们都是用户自定义的数据类型,用于组合不同类型的数据。但是,共用体中的所有成员共享同一段内存,而结构体中的成员各自占用独立的内存空间。
|
26天前
|
存储 C语言
C语言:结构体与共用体的区别
C语言中,结构体(struct)和共用体(union)都用于组合不同类型的数据,但使用方式不同。结构体为每个成员分配独立的内存空间,而共用体的所有成员共享同一段内存,节省空间但需谨慎使用。
|
1月前
|
编译器 C语言 C++
C语言结构体
C语言结构体
25 5
|
1月前
|
编译器 Linux C语言
C语言 之 结构体超详细总结
C语言 之 结构体超详细总结
18 0
|
1月前
|
存储 编译器 Linux
深入C语言:探索结构体的奥秘
深入C语言:探索结构体的奥秘
|
1月前
|
存储 编译器 C语言
c语言回顾-结构体(2)(下)
c语言回顾-结构体(2)(下)
28 0
|
1月前
|
存储 编译器 程序员
c语言回顾-结构体(2)(上)
c语言回顾-结构体(2)(上)
28 0