分享

C语言之结构体基础

 山峰云绕 2023-03-25 发布于贵州

https://www.toutiao.com/article/7213990045064184372/?log_from=475a0a623534c_1679754477361

什么是结构体

https://www.toutiao.com/article/7213990045064184372/?log_from=c3b7a9a1150be_1681705946225


在C语言中,结构体是不同数据类型的元素的集合。该结构用于创建用户定义的数据类型。该结构也被称为“ C语言自定义类型”。换句话说,结构体是不同类型数据的集合。这种数据类型的名字是由用户自主定义的。通常结构体用于将不同数据类型的元素组合成一个组。结构体中定义的元素称为结构成员。在前面我们学习过基础的数据类型int float char 等,都只能用来表示基础的数据类型,那么要怎么来表示复杂的数据类型呢?比如下信息:

定义5个数组,然后通过数组下标一致性原则去描述上述表格数据是否可行? 当然没得问题,如下代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() 
{
  int ids[10] = { 0 };
  char names[10][10] = { 0 };
  char sexs[10][3] = { 0 };
  int ages[10] = { 0 };
  int scores[10] = { 0 };

  ids[0] = 100;
  strcpy(names[0], "欧阳疯");
  strcpy(sexs[0], "男");
  ages[0] = 18;
  scores[0] = 666;

  return 0;
}

看起来还不错,实际上很繁琐,在排序需要交换两者数据的时候极其繁琐,既然学生信息有很多,那么能不能定义一个学生类型呢?如果能,直接通过学生访问该学生的所有信息就很方便了!

C语言结构体创建

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

  • 结构体名自己起,struct 结构体名组成新的数据类型,C语言中struct不可缺少

  • 多个成员之间用分号分隔,C语言中不允许无数据成员的结构体定义

  • 末尾的分号不可缺少

那么对于上面的学生的信息,就可以用如下结构体表示学生结构体类型:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Student 
{
    int id;    //学号
    char name[10];  //姓名
    char sex;    //性别
    int age;    //年龄
    int score;    //总分
};
int main() 
{

  return 0;
}

结构体中所有数据成员组成一个整体,形成一个新的数据类型,而不同变量则是零散内存,毫无关联,如下图:

C语言结构体访问

结构体中的数据必须要通过结构体变量访问,访问方式有以下两种:

  • 普通结构体变量: 变量.成员

  • 结构体指针:指针->成员 或者 (*指针).成员

结构体变量的创建

结构体类型已经声明,如何使用结构体类型定义结构体变量呢?有以下方法(typedef别名创建后续再讲):

  • 先声明结构体类型再定义结构体变量

  • 在声明结构体类型的同时定义变量

如下测试代码:

#include <stdio.h>
#include <stdlib.h>
struct MM
{
  char name[20];
  int age;
  double score;
}mm;       
//在声明结构体类型的同时定义变量mm
int main() 
{
  //先声明结构体类型再定义结构体变量
  //struct MM: 类型
  //girl: 变量名
  struct MM girl;
  return 0;
}

结构体变量的初始化

在定义结构体变量的同时通过{}的方式为每一个成员变量进行赋初值,赋初值主要有以下几种方式:

  • 全部初始化

  • 部分初始化:未初始化部分自动初始化为0

  • 全部初始化为0

  • 初始化指定的成员(可以初始化任意成员,不必遵循定义顺序)

  • 用另一个结构体变量初始化

如下测试代码:

#include <stdio.h>
#include <stdlib.h>
struct MM
{
  char name[20];
  int age;
  double score;
};       
int main() 
{
  
  //全部初始化,顺序必须一致
  struct MM girl = {"girl",18,99.1};  
  //部分初始化:未初始化部分自动初始化为0
  struct MM mm = { "gril" };
  //全部初始化为0
  struct MM zero = { 0 };
  //初始化指定的成员(可以初始化任意成员,不必遵循定义顺序)
  struct MM beauty = { .age = 18,.name = "beauty" };
  //用另一个结构体变量初始化
  struct MM woman = girl;
  return 0;
}

结构体数组

一个结构体变量可以存放一个学生的一组信息,可是如果有 10 个学生呢?难道要定义 10 个结构体变量吗?难道上面的程序要复制和粘贴 10 次吗?很明显不可能,这时就要使用数组。结构体中也有数组,称为结构体数组。它与前面讲的数值型数组几乎是一模一样的,只不过需要注意的是,结构体数组的每一个元素都是一个结构体类型的变量,都包含结构体中所有的成员项。

struct Student stus[10]; 这就定义了一个结构体数组,共有 10 个元素,每个元素都是一个结构体变量,都包含所有的结构体成员。

示例程序| 从键盘输入 5 个学生的基本信息,如学号、姓名、年龄、性别,将年龄最大的学生的基本信息输出到屏幕

#include <stdio.h>
struct Student
{
    int id;
    char name[10];
    int age;
    char sex;
};
int main()
{
    struct Student stus[10];
    printf("input stu>:\n");
    for (int i = 0; i < 5; i++)
    {    
        scanf("%d %s %d %c", &stus[i].id, stus[i].name, &stus[i].age, &stus[i].sex);
    }
    struct Student maxStu = stus[0];
    for (int i = 0; i < 5; i++)
    {
        if (maxStu.age < stus[i].age)
        {
            maxStu = stus[i];
        }
    }
    printf("-------------Max---------------\n");
    printf("%d %s %d %c\n", maxStu.id, maxStu.name, maxStu.age, maxStu.sex);
    return 0;
}

程序测试结果如下:

当然对于这种表格数据操作有很多,例如排序,查找,文件保存等。详细参见结构体数组写管理系统。

结构体指针

当一个指针变量指向结构体时,我们就称它为结构体指针。C语言结构体指针的定义形式一般为:struct 结构体名 *变量名;

如下测试代码:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
struct MM
{
  char name[20];
  int age;
  double score;
};       
int main() 
{
  struct MM girl = {"girl",18,99.1};  
  struct MM* pMM = NULL;
  //结构体指针指向结构体变量
  pMM = &girl;
  //指针用->访问
  printf("%s\t%d\t%.1lf\n", pMM->name, pMM->age, pMM->score);
  //*pMM等效girl
  printf("%s\t%d\t%.1lf\n", (*pMM).name, (*pMM).age, (*pMM).score);
  //结构体指针动态内存申请
  struct MM* pArray = (struct MM*)malloc(sizeof(struct MM)*3);
  assert(pArray);
  for (int i = 0; i < 3; i++) 
  {
    pArray[i] =(struct MM){ "张三",18,99.9 };
    printf("%s\t%d\t%.1lf\n", pArray[i].name, pArray[i].age, pArray[i].score);
  }
  return 0;
}

程序测试结果如下:

基本上普通指针能做的,结构体指针一样的。只是在访问数据的时候需要剥洋葱(通过->访问每个数据)。当然也可以当做函数参数和返回值。

C语言位段

C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为位段。利用位段能够用较少的位数存储数据。基本语法如下:

struct 结构体名
{
      类型 位段名1 : 位段大小;
      类型 位段名2 : 位段大小;
      类型 位段名3 : 位段大小;
      类型 位段名4 : 位段大小;
      ...
};

C语言标准规定,只有有限的几种数据类型可以用于位段。

  • 所有整数类型

  • char类型

  • bool类型

如下测试代码:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
struct BitField
{
    unsigned char a : 1;
    unsigned char b : 4;
    unsigned char c : 3;
};
int main()
{
    //初始化
    struct BitField bit = { 1,2,3 };
    //输出
    printf("first:%d %d %d\n", bit.a, bit.b, bit.c);
    //赋值
    //10    1位有效去掉最高位
    bit.a = 2;  
    //10100 4位有效去掉最高位
    bit.b = 20;  
    //1000  3位有效去掉最高位
    bit.c = 8;   
    //再次输出
    printf("last:%d %d %d\n", bit.a, bit.b, bit.c);
}

程序测试结果如下:

位段注意项:

  • 位段的内存分配:位段占的二进制位数不能超过该基本类型所能表示的最大位数,即位段不能跨字节存储,比如char是占1个字节,那么最多只能是8位;

  • 位域的存储:C语言标准并没有规定位域的具体存储方式,不同的编译器有不同的实现,但它们都尽量压缩存储空间。

  • 禁止对位段取地址:地址是字节(Byte)的编号,而不是位(Bit)的编号。

  • 无名位段:位域成员可以没有名称,只给出数据类型和位宽,无名位域一般用来作填充或者调整成员位置。因为没有名称,无名位域不能使用。

C语言结构体嵌套

在一个结构体内包含另一个结构体作为其成员,当然一般嵌套可以理解为一类数据的封装。访问的话逐步剥洋葱即可,定义方式有两种写法。

  • 以结构体变量当做数据成员方式嵌套

  • 直接把结构体定义在另一个结构体内

示例程序以结构体变量当做数据成员方式嵌套 | 给学生增加一个出生日期,包含年月日

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
struct Date
{
    short year;
    short month;
    short day;
};
struct Student
{
    int id;
    char name[10];
    struct Date birth;  //出生日期
};
int main()
{
    //数据完整的情况,{}可有可无
    struct Student baby = { 1001,"baby",{2008,3,17} };
    printf("%d\t%s\t%d-%d-%d\n", baby.id, baby.name, 
        baby.birth.year, baby.birth.month, baby.birth.day);
    return 0;
}

示例程序直接把结构体定义在另一个结构体内| 给学生增加一个出生日期,包含年月日

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
struct Student
{
    int id;
    char name[10];
    struct Date
    {
        short year;
        short month;
        short day;
    }birth;//出生日期
};
int main()
{
    //数据完整的情况,{}可有可无
    struct Student baby = { 1001,"baby",{2008,3,17} };
    printf("%d\t%s\t%d-%d-%d\n", baby.id, baby.name, 
        baby.birth.year, baby.birth.month, baby.birth.day);
    return 0;
}

C语言结构体内存对齐

什么是内存对齐

从理论上讲,对于任何变量的访问都可以从任何地址开始访问,但是事实上不是如此,实际上访问特定类型的变量只能在特定的地址访问,这就需要各个变量在空间上按一定的规则排列,而不是简单地顺序排列,这就是内存对齐。通俗点讲就是厕所建坑位需要合理排布对齐,不然可能会存在只有半个坑的情况。

为什么要内存对齐

  • 某些平台只能在特定的地址处访问特定类型的数据;

  • 提高存取数据的速度。比如有的平台每次都是从偶地址处读取数据,对于一个int型的变量,若从偶地址单元处存放,则只需一个读取周期即可读取该变量;但是若从奇地址单元处存放,则需要2个读取周期读取该变量。

当然我们会不会算内存对齐其实并不重要,因为对于一个结构体占用内存一个sizeof即可搞定,重要的是大家要知道如何设计代码可以让内存更小,毕竟在特殊开发场景,内存占用是非常值得关注的。例如,网络传输,嵌入式等。

内存对齐规则

C语言标准并没有规定内存对齐的细节,而是交给具体的编译器去实现,但是对齐的基本原则是一样的。

  • 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;

  • 结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节;

  • 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。

如下测试代码:

#include <stdio.h>
#include <stdlib.h>
//会不会算出结果不重要,重要是学会怎么写可以少内存即可
//按照从小到大即可 字符和整形写一块
struct Data1
{
  double score;    //8
  char name[3];       //补一位和int组成8位
  int age;
};
struct Data2
{
  char name[3];     //补 5位
  double score;     //8位
  int age;          //补4位
};

struct Data3
{
  char name[9];   //8+1  +3
  int num;        //4
  double score;   //8
  int age;        //4+3 5
  char tel[3];
};
struct Data4
{
  int age;
  char name[3];
  int num;
};
struct Data5
{
  char name[5];
  char num;
};
struct Data6
{
  char name[5];
  int* p;         //32位按照4个字节对齐,64位按照8位对齐
};
struct Data7
{
  struct Data4 data;   //12
  //char name[3];     //3+1
  double score;        //8
  int age;             //8
};
union Data8
{
  char name[20];
  double score;
};
int main()
{
  struct Data1* p = (struct Data1*)malloc(sizeof(struct Data1));
  printf("%zd\n", sizeof(struct Data1));   //16
  printf("%zd\n", sizeof(struct Data2));   //24
  //8 1w  8w 
  printf("%zd\n", sizeof(struct Data3));   //32
  printf("%zd\n", sizeof(struct Data4));   //12
  printf("%zd\n", sizeof(struct Data5));   //6
  printf("%zd\n", sizeof(struct Data6));   //16
  //C语言不允许空的结构体
  printf("%zd\n", sizeof(struct Data7));   //32
  printf("%zd\n", sizeof(union Data8));   //24
  return 0;

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多