C语言:结构体类型

意义

可以存储不同类型的信息,在一块连续的空间。

类型描述

	struct  结构体名
	{
	    数据类型	成员1; //结构体类型描述是不占用内存空间,因此不能在定义好之后直接赋值,初始化
	    数据类型	成员2;
	    //int 不占空间,int 的变量占空间
	    ... ...
	};

优势

相比于数组,结构体有以下的更强大的优势:

  1. 批量存储数据
  2. 存储不同类型的数据
  3. 支持嵌套

结构体声明与定义

声明

结构体的声明使用struct关键字,如果我们想要把我们的学籍信息组织一下的话,可以这样表示:

struct Info
{
    unsigned long identifier;//学号,用无符号长整数表示
    char name[20];//名字,用字符数组表示
    unsigned int year;//入学年份,用无符号整数表示
    unsigned int years;//学制,用无符号整数表示
}

这样,我们就相当于描绘好了一个框架,以后要用的话直接定义一个这种类型的变量就好了。

定义

我们刚刚申请了一个名叫Info的结构体类型,那么理论上我们可以像声明其他变量的操作一样,去声明我们的结构体操作,但是C语言中规定,声明结构体变量的时候,struct关键字是不可少的。

struct  结构体类型名  结构体变量名

不过,你可以在某个函数里面定义:

#include <stdio.h>

struct Info
{
    unsigned long identifier;//学号,用无符号长整数表示
    char name[20];//名字,用字符数组表示
    unsigned int year;//入学年份,用无符号整数表示
    unsigned int years;//学制,用无符号整数表示
};

int main(void)
{
    /**
     *在main函数中声明结构体变量
     *结构体变量名叫info
     *struct关键字不能丢
     */
    struct Info info;
    ...
}

也可以在声明的时候就把变量名定义下来(此时这个变量是全局变量):

#include <stdio.h>

struct Info
{
    unsigned long identifier;//学号,用无符号长整数表示
    char name[20];//名字,用字符数组表示
    unsigned int year;//入学年份,用无符号整数表示
    unsigned int years;//学制,用无符号整数表示
} info;
/**
 *此时直接定义了变量
 *该变量是全局变量
 *变量名叫info
 */
int main(void)
{
    ...
}

访问结构体成员

结构体成员的访问有点不同于以往的任何变量,它是采用点号运算符.来访问成员的。比如,info.name就是引用info结构体的name成员,是一个字符数组,而info.year则可以查到入学年份,是个无符号整型。

比如,下面开始录入学生的信息:

#include <stdio.h>

struct Info
{
    unsigned long identifier;//学号,用无符号长整数表示
    char name[20];//名字,用字符数组表示
    unsigned int year;//入学年份,用无符号整数表示
    unsigned int years;//学制,用无符号整数表示
};

int main(void)
{
    struct Info info;

    printf("请输入学生的学号:");
    scanf("%d", &info.identifier);
    printf("请输入学生的姓名:");
    scanf("%s", info.name);
    printf("请输入学生的入学年份:");
    scanf("%d", &info.year);
    printf("请输入学生的学制:");
    scanf("%d", &info.years);

    printf("\n数据录入完毕\n\n");

    printf("学号:%d\n姓名:%s\n入学年份:%d\n学制:%d\n毕业时间:%d\n", \
        info.identifier, info.name, info.year, info.years, info.year + info.years);
    return 0;
}

运行结果如下:

请输入学生的学号:20210329
请输入学生的姓名:Harris
请输入学生的入学年份:2021
请输入学生的学制:4

数据录入完毕

学号:20210329
姓名:Harris
入学年份:2021
学制:4
毕业时间:2025

初始化结构体

像数组一样,结构体也可以在定义的时候初始化,方法也几乎一样:

struct Info info = {
    20210329,
    "Harris",
    2021,
    4
};
// 在C99标准中,还支持给指定元素赋值(就像数组一样):
struct Info info = {
    .name = "Harris",
    .year = 2021
};
// 对于没有被初始化的成员,则「数值型」成员初始化为0,「字符型」成员初始化为‘\0’。

对齐

下面这个代码,大家来看看会发生什么:

#include <stdio.h>

int main(void)
{
    struct A
    {
        char a;
        int b;
        char c;
    } a = {'a', 10, 'o'};
    
    printf("size of a = %d\n", sizeof(a));
    
    return 0;
}

我们之前学过,char类型的变量占1字节,int类型的变量占4字节,那么这么一算,一个结构体A型的变量应该就是6字节了。别急,我们看运行结果:

size of a = 12

怎么变成12了呢?标准更新了?老师教错了?都不是。我们把代码改一下:

#include <stdio.h>

int main(void)
{
    struct A
    {
        char a;
        char c;
        int b;
    } a = {'a', 'o', 10};
    
    printf("size of a = %d\n", sizeof(a));
    
    return 0;
}

这次再看结果:

size of a = 8

实际上,这是编译器对我们程序的一种优化——内存对齐。在第一个例子中,第一个和第三个成员是char类型是1个字节,而中间的int却有4个字节,为了对齐,两个char也占用了4个字节,于是就是12个字节。

而在第二个例子里面,前两个都是char,最后一个是int,那么前两个可以一起占用4个字节(实际只用2个,第一个例子也同理,只是为了访问速度更快,而不是为了扩展),最后的int占用4字节,合起来就是8个字节。

结构体数组

如果要录入一批学生,这时候我们就可以沿用之前的思路,使用结构体数组。
我们知道,数组的定义,就是存放一堆相同类型的数据的容器。而结构体一旦被我们声明,那么你就可以把它看作一个类型,只不过是你自己定义的罢了。

定义结构体数组也很简单:

struct 结构体类型
{
    成员;
} 数组名[长度];

/****或者这样****/

struct 结构体类型
{
    成员;
};
struct 结构体类型 数组名[长度];

结构体指针

既然我们可以把结构体看作一个类型,那么也就必然有对应的指针变量。

struct Info* pinfo;

但是在指针这里,结构体和数组就不一样了。我们知道,数组名实际上就是指向这个数组第一个元素的地址,所以可以将数组名直接赋值给指针。而结构体的变量名并不是指向该结构体的地址,所以要使用取地址运算符&才能获取地址:

pinfo = &info;

通过结构体指针来访问结构体有以下两种方法:

  1. (*结构体指针).成员名
  2. 结构体指针->成员名
    第一个方法由于点号运算符比指针的取值运算符优先级更高,因此需要加一个小括号来确定优先级,让指针先解引用变成结构体变量,在使用点号的方法去访问。

相比之下,第二种方法就直观许多。

这两种方法在实现上是完全等价的,但是点号只能用于结构体变量,而箭头只能够用于指针。
第一种方法:

#include <stdio.h>
...
int main(void)
{
    struct Info *p;
    p = &info;
    
    printf("学号:\n", (*p).identifier);
    printf("姓名:\n", (*p).name);
    printf("入学时间:%d/%d/%d\n", (*p).date.year, (*p).date.month, (*p).date.day);
    printf("学制:\n", (*p).years);
    return 0;
}

第二种方法:

#include <stdio.h>
...
int main(void)
{
    struct Info *p;
    p = &info;
    
    printf("学号:\n", p -> identifier);
    printf("姓名:\n", p -> name);
    printf("入学时间:%d/%d/%d\n", p -> date.year, p -> date.month, p -> date.day);
    printf("学制:\n", p -> years);
    return 0;
}

传递结构体消息

传递结构体变量

我们先来看看下面的代码:

#include <stdio.h>

int main(void)
{
    struct Test
    {
        int x;
        int y;
    }t1, t2;

    t1.x = 3;
    t1.y = 4;
    t2 = t1;

    printf("t2.x = %d, t2.y = %d\n", t2.x, t2.y);
    return 0;
}

运行结果如下:

t2.x = 3, t2.y = 4

这么看来,结构体是可以直接赋值的。那么既然这样,作为函数的参数和返回值也自然是没问题的了。

结构体传参

先来试试作为参数:

例:

#include <stdio.h>

struct Info getInput(struct Info info); //声明使用函数 这使用是结构体形式
void printInfo(struct Info info);  //声明使用函数 如果使用在main上面的话 就不需要提前声明不然找不到 在main下则需要

struct Date       //嵌套结构体 日期
{
    unsigned int year; 
    unsigned int month;
    unsigned int day;
};

struct Info
{
    unsigned long identifier;
    char name[20];
    struct Date date;    //结构体中使用结构体 struct +结构体类型 +结构体变量名
    unsigned int years;
};

struct Info getInput(struct Info info)
{
    printf("请输入学号:");
    scanf("%d", &info.identifier);
    printf("请输入姓名:");
    scanf("%s", info.name);
    printf("请输入入学年份:");
    scanf("%d", &info.date.year);
    printf("请输入月份:");
    scanf("%d", &info.date.month);
    printf("请输入日期:");
    scanf("%d", &info.date.day);
    printf("请输入学制:");
    scanf("%d", &info.years);
    return info;  //这也就是为什么 会是结构体定义的函数体 因为返回值是结构体
}

void printInfo(struct Info info) //上面是获取结构体内容,这个是打印,传入是的结构体那么打印也需要使用结构体
{
    printf("学号:%d\n姓名:%s\n入学时间:%d/%d/%d\n学制:%d\n毕业时间:%d\n", \
        info.identifier, info.name, \
        info.date.year, info.date.month, info.date.day, \
        info.years, info.date.year + info.years);
}

int main(void)
{
    struct Info i1 = {};                     //定义结构体变量名称
    struct Info i2 = {};
    printf("请录入第一个同学的信息...\n");
    i1 = getInput(i1);                       //传入定义好的结构体变量名  返回也应该是结构体
    putchar('\n');
    printf("请录入第二个学生的信息...\n");
    i2 = getInput(i2);

    printf("\n录入完毕,现在开始打印...\n\n");
    printf("打印第一个学生的信息...\n");
    printInfo(i1);
    putchar('\n');
    printf("打印第二个学生的信息...\n");
    printInfo(i2);
    return 0;
}
-----------------------------结果-----------------------------
请录入第一个同学的信息...
请输入学号:20210329
请输入姓名:Harris
请输入入学年份:2021
请输入月份:9
请输入日期:7
请输入学制:4

请录入第二个学生的信息...
请输入学号:20210330
请输入姓名:Joy
请输入入学年份:2021
请输入月份:9
请输入日期:8
请输入学制:5

录入完毕,现在开始打印...

打印第一个学生的信息...
学号:20210329
姓名:Harris
入学时间:2021/9/7
学制:4
毕业时间:2025

打印第二个学生的信息...
学号:20210330
姓名:Joy
入学时间:2021/9/8
学制:5
毕业时间:2026

传递指向结构体变量的指针

早期的C语言是不允许直接将结构体作为参数直接传递进去的。主要是考虑到如果结构体的内存占用太大,那么整个程序的内存开销就会爆炸。不过现在的C语言已经放开了这方面的限制。
不过,作为一名合格的开发者,我们应该要去珍惜硬件资源。那么,传递指针就是一个很好的办法。
在上面的代码基础稍微改动一下即可:

例:

#include <stdio.h>

void getInput(struct Info *info);     //传输的是指针指向结构体的地址
void printInfo(struct Info *info);

struct Date
{
    unsigned int year;
    unsigned int month;
    unsigned int day;
};

struct Info
{
    unsigned long identifier;
    char name[20];
    struct Date date;
    unsigned int years;
};

void getInput(struct Info *info)
{
    printf("请输入学号:");
    scanf("%d", &info->identifier);
    printf("请输入姓名:");
    scanf("%s", info->name);
    printf("请输入入学年份:");
    scanf("%d", &info->date.year);
    printf("请输入月份:");
    scanf("%d", &info->date.month);
    printf("请输入日期:");
    scanf("%d", &info->date.day);
    printf("请输入学制:");
    scanf("%d", &info->years);
	//返回值为void,上面都是往指定的地址写参数
}

void printInfo(struct Info *info)
{
    printf("学号:%d\n姓名:%s\n入学时间:%d/%d/%d\n学制:%d\n毕业时间:%d\n", \
        info->identifier, info->name, \
        info->date.year, info->date.month, info->date.day, \
        info->years, info->date.year + info->years);
}

int main(void)
{
    struct Info i1 = {};
    struct Info i2 = {};
    printf("请录入第一个同学的信息...\n");
    getInput(&i1);                        //传输的是指针指向结构体的地址 传输的是地址 则形参是指针类型
    putchar('\n');
    printf("请录入第二个学生的信息...\n");
    getInput(&i2);

    printf("\n录入完毕,现在开始打印...\n\n");
    printf("打印第一个学生的信息...\n");
    printInfo(&i1);
    putchar('\n');
    printf("打印第二个学生的信息...\n");
    printInfo(&i2);

    return 0;
}
// 此时传递的就是一个指针,而不是一个庞大的结构体。

-----------------------------结果-----------------------------
和上述一样的情况 。

动态申请结构体

结构体也可以在堆里面动态申请:

#include <stdio.h>
...
int main(void)
{
    struct Info *i1;
    struct Info *i2;
    
    i1 = (struct Info *)malloc(sizeof(struct Info));
    i2 = (struct Info *)malloc(sizeof(struct Info));
    if (i1 == NULL || i2 == NULL)
    {
        printf("内存分配失败!\n");
        exit(1);
    }
    
    printf("请录入第一个同学的信息...\n");
    getInput(i1);
    putchar('\n');
    printf("请录入第二个学生的信息...\n");
    getInput(i2);

    printf("\n录入完毕,现在开始打印...\n\n");
    printf("打印第一个学生的信息...\n");
    printInfo(i1);
    putchar('\n');
    printf("打印第二个学生的信息...\n");
    printInfo(i2);
    
    free(i1);   //申请记得释放 
    free(i2);   
    
    return 0;
}

综建立一个图书馆数据库

#include <stdio.h>
#include <stdlib.h>

#define MAX_SIZE 100

void getInput(struct Book* book);//录入数据
void printBook(struct Book* book);//打印数据
void initLibrary(struct Book* lib[]);//初始化结构体
void printLibrary(struct Book* lib[]);//打印单本书数据
void releaseLibrary(struct Book* lib[]);//释放内存
//少一个查询内容 

struct Date
{
    int year;
    int month;
    int day;
};

struct Book
{
    char title[128];
    char author[48];
    float price;
    struct Date date;
    char publisher[48];
};

int main(void)
{
    struct Book* lib[MAX_SIZE];    //定义一个结构体数组指针
    struct Book* p = NULL;
    int ch, index = 0;    //index 用来标记

    initLibrary(lib);

    while (1)
    {
        printf("请问是否要录入图书信息(Y/N):");
        do
        {
            ch = getchar();                       //获取输入信息结果 
        } while (ch != 'Y' && ch != 'N');

        if (ch == 'Y')
        {
            if (index < MAX_SIZE)
            {
                p = (struct Book*)malloc(sizeof(struct Book));   //申请一段结构体动态内存在堆区申请 并返回申请的地址
                getInput(p);          //获取输入信息,传输要输入的地址,形参要使用指针
                lib[index] = p;       //将输入的信息保存对应的数组下标中
                index++;              //数组下标+1 但是数组是有界限的 
                putchar('\n');
            }
            else
            {
                printf("数据库已满!\n");
                break;
            }
        }
        else
        {
            break;
        }
    }

    printf("\n数据录入完毕,开始打印验证...\n\n");
    printLibrary(lib);                  //打印所有的输入 需要用到遍历
    releaseLibrary(lib);

    return 0;
}

void initLibrary(struct Book* lib[])
{
    for (int i = 0; i < MAX_SIZE; i++)
    {
        lib[i] = NULL;           //将数组每一个都null,null是对与指针来说空指针,而0是数字。
    }
}

void getInput(struct Book* book)
{
    printf("请输入书名:");
    scanf("%s", book->title);
    printf("请输入作者:");
    scanf("%s", book->author);
    printf("请输入售价:");
    scanf("%f", &book->price);
    printf("请输入出版日期:");
    scanf("%d-%d-%d", &book->date.year, &book->date.month, &book->date.day);
    printf("请输入出版社:");
    scanf("%s", book->publisher);
}

void printLibrary(struct Book* lib[])
{
    for (int i = 0; i < MAX_SIZE; i++)
    {
        if (lib[i] != NULL)               //做一次判断 避免出现问题
        {
            printBook(lib[i]);            //进行对应的遍历
            putchar('\n');
        }
    }
}

void printBook(struct Book* book)
{
    printf("书名:%s\n", book->title);
    printf("作者:%s\n", book->author);
    printf("售价:%.2f\n", book->price);
    printf("出版日期:%d-%d-%d\n", book->date.year, book->date.month, book->date.day);
    printf("出版社:%s\n", book->publisher);
}

void releaseLibrary(struct Book* lib[])
{
    for (int i = 0; i < MAX_SIZE; i++)
    {
        if (lib[i] != NULL)
        {
            free(lib[i]);                  //申请使用结束之后 释放
        }
    }
}

综合结构体

#include <stdio.h>
#include <stdlib.h>
#define namesize 24

struct student 
{
    int id;
    int i;
    char name[namesize];
    struct birthday
    {
        int year;
        int month;
        int day;
    }brith;
    int math;
    int chinese;
};

int main()
{
    int j;
    
    struct student stu = {12,34,"chu",{2000,12,12},145,123};
    printf("%d-%d-%s-%d-%d-%d-%d-%d\n",stu.id,stu.i,stu.name,stu.brith.year,stu.brith.month,stu.brith.day,stu.math,stu.chinese);
    struct student *p = &stu;
    printf("%d-%d-%s-%d-%d-%d-%d-%d\n",p->id,p->i,p->name,p->brith.year,p->brith.month,p->brith.day,p->math,p->chinese);
    struct student arr[2] ={{412,34,"chu",{2000,12,12},145,123},{552,34,"chu",{2000,12,12},145,123}};
    
     p = &arr[0];
    
    for(j=0;j<2;j++)
    {
        printf("%d-%d-%s-%d-%d-%d-%d-%d\n",p->id,p->i,p->name,p->brith.year,p->brith.month,p->brith.day,p->math,p->chinese);
        p++;//指针偏移,也可以写成for(j=0;j<2;j++,p++)
    }
    return 0;
    
}

在这里插入图片描述

占用内存空间大小运算

#include <stdio.h>
#include <stdlib.h>
#define namesize 24

struct student 
{
    int id;
    int i;
    char name[namesize];
 
   
 struct birthday
    {
        int year;
        int month;
        int day;
    }brith;
    int math;
    int chinese;
    
};
int main()
{
    struct student stu;
    struct student *p = &stu;
    
    printf("%d\n",sizeof(stu));
    printf("%d\n",sizeof(p));
    return 0;
    
}

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值