深入理解C语言结构体(数据结构基础)

引言:首先我们怎样认识结构体?结构体在c语言基础以及数据结构中的地位是什么?可以说,结构体作为了一种中间比较重要的桥梁基础,是从基础的C语法过渡到C数据结构必不可少的一种重要数据结构。以及配合指针,成就C语言的灵魂所在。

一:结构体定义与初始化引用

1:结构体是什么?它的特点?

<1>相对于数组存储结构的区别?

数组是一种存储结构,一种可以存放相同类型的存储结构。比如int类型的存储结构就只能存放Int类型的数据,但是你若是想要描述清除一个学生的身份信息,一个数组绝对是不行的,比如名字,学号等这些,必须要使用多个数组来说明学生的信息。我们还需要构造关联的索引形成一一的对应,如果大型项目这样做,那么就会无比臃肿了。
结构体是这样一种数据结构,一种可以存放多种数据类型的数据结构。也就是既可以有int类型,也可以有char,float等,以此类推。是一种非常方便的数据结构,对于描述一个事物的特征具有非常方便的意义。

<2>结构体定义(常规)

我们定义结构体的方式有多种
1:第一种比较规范化的定义如下

//定义结构体,包含完整的结构体名和结构体变量
struct name//结构体名/标签
{
    /* data */
    int a;//结构体成员
    float b;
    char c;
    double d;
   
}var;//结构体变量

2:无结构体名

//定义结构体
struct 
{
    /* data */
    int a;
    float b;
    char c;
    double d;
   
}var;//结构体变量

3:无结构变量

//定义结构体
struct name
{
    /* data */
    int a;
    float b;
    char c;
    double d;
   
};

当然你就算定义一个空的结构体也是可以的,我这边只得空就是没有结构体成员,没有成员就是空的啦!
那么结构体在定义的时候可以简化到什么程度呢?
可以简化到只有结构体变量或者结构体名。但是如果连结构体变量或者结构体名都没有的化就会报错,没有办法识别。也当然没有什么意义。

//定义结构体
struct name//只含有结构体名
{
   
   
};

//定义结构体
struct 
{
   
   
}var;//只含有结构体变量

4:有typedef 关键字情况下对结构体初始化

<3>结构体初始化(常规)

我们下面对结构体进行初始化,初始化的结构体才真正具有了一定的意义。
我们来举例几种初始化结构体的方法
1:在定义变量的时候初始化

//定义结构体
struct name
{
    /* data */
    int a;
    float b;
    char c;
    double d;
   
}var = {1,2.0,'c',3.00};//对结构体变量进行了初始化

顺便说明一下结构体初始化对结构体变量的意义的说明
结构体名和结构体变量是不一样的,结构体名可以作为结构体的一种表标识,但是它不具有a,b,c,d这四个成员的属性,而我们的一个结构变量就具有了a,b,c,d四个成员属性。所以我们初始化需要对var赋值,而不是name。
2:在main函数中通过 struct name + var 定义变量并进行初始化

#include<stdio.h>
#include<windows.h>
//定义结构体
struct name
{
    /* data */
    int a;
    float b;
    char c;
    double d;
   
}var = {1,2.0,'c',3.00};//对结构体变量进行了初始化

int main()
{  
    struct name var1 = {1,2.0,'c',4.0};//在main函数中声明结构体变量并进行初始化
    system("pause");
}

注意:如果在main函数中,我们不能直接用var={};来进行赋值。

<3>typedef关键字对结构体的改变

先说明一下typedef关键字的作用。
我们常规的定义结构体的方式便是struct 这样的形式,但是我们也会常常见到typedef struct的形式。
那么typedef关键字的作用是什么呢?


明白了说就是给紧挨着后i面的类型起一个别名。举例如下:

// An highlighted block
//定义结构体
typedef struct name
{
    /* data */
    int a;
    float b;
    char c;
    double d;
   
}var_dif_name;

这里定义的struct name是我们的结构体,然后下面是var_dif_name,此时的var_dif_name相当于struct name,现在的var_dif_name并不是结构体变量,而是struct name这个整体类型的别称。所以你此时这样定义了不能再对var_dif_name进行赋值。如下是错误的
在这里插入图片描述
我们可以更加简化一些,这样来做

//定义结构体
typedef struct 
{
    /* data */
    int a;
    float b;
    char c;
    double d;
   
}var_dif_name ;

int main()
{  
    var_dif_name var = {1,2,'c',8};
    system("pause");
}

可以看到,若如上所言,那么这个意思就是var_dif_name就相当于struct了吗?如果这样去理解,顺着逻辑理解下去的化,那么var_dif_name就相当于struct var了,如果这样的话,这样做不是错误的话吗?其实这里的名字是隐含掉了,tag成为一种匿名的方式,我们这里省略了struc后面的名字tag。所以我们这样定义也是可以的。编译器会认为合法的,


举例了结构体使用typedef,那么我们为什么要使用它,使用它的意义何在?你想啊,如果没有typedef,那么如果我们再主函数里面声明多个结构体变量就需要struct name +var,当有了typedef,我们就可以用一个替代的var_dif_name + var来代替struct name +var。

2:引用结构体变量的成员属性

<1>在主函数中使用结构体变量

那么我们如何在主函数中使用结构体变量呢?
直接printf()可以吗?当然不可以。你想啊,我们输出一个结构体变量,它可能具有不同类型的成员属性,那么你怎么可以用一种形式输出呢?当然不可以。所以我们需要这样做。


#include<stdio.h>
#include<windows.h>
//定义结构体
typedef struct 
{
    /* data */
    int a;
    float b;
    char c;
    double d;
   
}var_dif_name ;

int main()
{  
    var_dif_name var = {1,2,'c',8};
    printf("%d-%f,%c,%d",var.a,var.b,var.c,var.d);
    system("pause");
}

引用结构体的成员属性,需要我们通过结构体变量来进行引用。结构体变量.结构体成员。

<2>通过指针进行引用

如何通过指针对结构体进行操作也是一件比较巧妙地事情。指针与结构体地结合是过渡数据结构的重要一步。
这边需要考虑的是,结构体变量内含有多个属性。int,char,类型这些,我们可以定义一个指针指向一个结构体变量吗?当然不可以,为了使类型匹配,所以需要也定义一个结构体指针来进行操作。

#include<stdio.h>
#include<windows.h>
//定义结构体
typedef struct 
{
    /* data */
    int a;
    float b;
    char c;
    double d;
   
}var_dif_name ;

int main()
{  
    var_dif_name var = {1,2,'c',8.00};
    var_dif_name * var_v = NULL;
    var_v= &var;
    printf("%d",var_v->a);
    //printf("%d",(*var_v).a);
   
    system("pause");
}

释疑:通过定义的结构体指针来引用结构体成员属性,先让我们的指针指向变量地址,然后一种是通过->符号取成员属性的值,一种是进行一层进行解引用,然后取到结构体变量层次,然后进行.来进行取属性,也就是说这两种方法是等效的。但是要注意.的运算级别高于号,所以为了符合逻辑上的需要我们需要加上()。

二:结构体嵌套

1:结构体嵌套使用

结构体嵌套的话,也是一样的道理,如果你的结构体嵌套了另一个结构体,如果你要取到成员属性的值,那么你需要进行两层解引用。来举例。

#include<stdio.h>
#include<windows.h>
struct Date
{
    int year;
    int month;
    int day;

};
struct Book
{
    char title[128];
    char author[40];
    float price;
    struct Date date;//结构体的嵌套
    char  publisher[40];


} book = {
            "<<带你学c带你飞>>",
            "小甲鱼",
            42,
            {2017,11,11},
            "清华大学出版社"

         };
int main()
{

    printf("title::%s\n",book.title);
    printf("author:%s\n",book.author);
    printf("food: %.2f\n",book.price);
    printf("date:%d-%d-%d\n",book.date.year,book.date.month,book.date.day);
    printf("publisher:%s\n",book.publisher);
    system("pause");


}

三:结构体变量做参数传递

1:在函数中传入结构体变量

结构体作为参数传递的话,我们只要在函数内部将形式参数的类型定义为结构体类型。具体代码示例如下。

#include<stdio.h>
#include<windows.h>
struct Date
{
    int year;
    int month;
    int day;
};
struct Book
{
    char title[128];
    char author[40];
    float price;
    struct Date date;
    char publisher[40];
};
struct Book getInput(struct Book book)
{
    printf("Please enter the title::\n");
    scanf("%s",book.title);
    printf("Please enter the author:\n");
    scanf("%s",book.author);
    printf("please enter the price\n");
    scanf("%f",&book.price);
    printf("please enter the date\n");
    scanf("%d-%d-%d",&book.date.year,&book.date.month,&book.date.day);
    printf("please enter the publisher:\n");
    scanf("%s",book.publisher);

}
void printBook(struct Book book)
{
    printf("title:%s\n",book.title);
    printf("author:%s\n",book.author);
    printf("price:%.2f\n",book.price);
    printf("date:%d-%d-%d\n",book.date.year,book.date.month,book.date.day);
    printf("publisher:",book.publisher);
}
int main(void)
{
    struct Book b1,b2;
    printf("please enter the message of the first book:\n");
    b1 = getInput(b1);
    putchar('\n');
    printf("please enter the message of the second book:\n");
    b2 = getInput(b2);
    printf("\n\nover!");
    printf("first book:\n");
    printBook(b1);
    putchar('\n');
    printf("second book:\n");
    printBook(b2);

    system("pause");
}

说明,如果你的结构体变量传入到函数不是传入的地址,那么如果想要成功的更新结构体包括赋值这些,那么你就需要返回一个接受的变量,否则结构体无法更新。

2:在函数中传入结构体变量的地址

当我们给函数中传入结构体变量的地址的时候,那么在主函数就无需再用变量接收传递改变,当把地址传入去的时候,那么就成为一种实质上的改变。具体看示例代码

#include<stdio.h>
#include<windows.h>
struct Date
{
    int year;
    int month;
    int day;
};
struct Book
{
    char title[128];
    char author[40];
    float price;
    struct Date date;
    char publisher[40];
};
void  *getInput(struct Book *book)
{
    printf("Please enter the title::\n");
    scanf("%s",book->title);
    printf("Please enter the author:\n");
    scanf("%s",book->author);
    printf("please enter the price\n");
    scanf("%f",&book->price);
    printf("please enter the date\n");
    scanf("%d-%d-%d",&book->date.year,&book->date.month,&book->date.day);
    printf("please enter the publisher:\n");
    scanf("%s",book->publisher);

}
void *printBook(struct Book *book)
{
    printf("title:%s\n",book->title);
    printf("author:%s\n",book->author);
    printf("price:%.2f\n",book->price);
    printf("date:%d-%d-%d\n",book->date.year,book->date.month,book->date.day);
    printf("publisher:",book->publisher);
}
int main(void)
{
    struct Book b1,b2;
    printf("please enter the message of the first book:\n");
    getInput(&b1);
    putchar('\n');
    printf("please enter the message of the second book:\n");
    getInput(&b2);
    printf("\n\nover!");
    printf("first book:\n");
    printBook(&b1);
    putchar('\n');
    printf("second book:\n");
    printBook(&b2);

    system("pause");
}

当我们传入地址的时候,那么形式参数就需要为指针类型,指针才可以存放地址。

四:计算结构体变量占用的字节?(结构体成员的内存对齐?)

  struct A
    {
        char a;
        int b;
        char c;
    } a={'x',5,'z'};//结构体存在内存对齐

请问结构体A占用多少字节?是1+4+1=6吗?


非也,答案是12个字节,你看啊!虽然char一个字节,但是int类型占用4个字节,两个char类型会和int类型对齐,int类型是四个字节,那么两个char也会给4个,这样就一共十二个字节。

那么再来看下面的占用多少?编译器不是傻子。


struct B
    {
        char a;
        char c;
        int b;
    }  c={'x','y',6};

什么?你不会认为三个字节吧?hh
来看小甲鱼的图。非常详细了,所以我就拿来用了。
在这里插入图片描述
所以这样一共八个字节。

posted @ 2022-01-10 12:00  兰舟千帆  阅读(238)  评论(0编辑  收藏  举报