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

简介: 一:结构体定义与初始化引用1:结构体是什么?它的特点?<1>相对于数组存储结构的区别?数组是一种存储结构,一种可以存放相同类型的存储结构。比如int类型的存储结构就只能存放Int类型的数据,但是你若是想要描述清除一个学生的身份信息,一个数组绝对是不行的,比如名字,学号等这些,必须要使用多个数组来说明学生的信息。我们还需要构造关联的索引形成一一的对应,如果大型项目这样做,那么就会无比臃肿了。

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


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//只含有结构体名
{
};
1
2
3
4
5
6
7
//定义结构体
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'};//结构体存在内存对齐

1

2

3

4

5

6

请问结构体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

来看小甲鱼的图。非常详细了,所以我就拿来用了。



所以这样一共八个字节。

相关文章
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
588 1
|
存储 算法 搜索推荐
【趣学C语言和数据结构100例】91-95
本文涵盖多个经典算法问题的C语言实现,包括堆排序、归并排序、从长整型变量中提取偶数位数、工人信息排序及无向图是否为树的判断。通过这些问题,读者可以深入了解排序算法、数据处理方法和图论基础知识,提升编程能力和算法理解。
184 4
|
存储 机器学习/深度学习 搜索推荐
【趣学C语言和数据结构100例】86-90
本文介绍并用C语言实现了五种经典排序算法:直接插入排序、折半插入排序、冒泡排序、快速排序和简单选择排序。每种算法都有其特点和适用场景,如直接插入排序适合小规模或基本有序的数据,快速排序则适用于大规模数据集,具有较高的效率。通过学习这些算法,读者可以加深对数据结构和算法设计的理解,提升解决实际问题的能力。
174 4
|
存储 算法 数据处理
【趣学C语言和数据结构100例】81-85
本文介绍了五个经典算法问题及其C语言实现,涵盖图论与树结构的基础知识。包括使用BFS求解单源最短路径、统计有向图中入度或出度为0的点数、统计无向无权图各顶点的度、折半查找及二叉排序树的查找。这些算法不仅理论意义重大,且在实际应用中极为广泛,有助于提升编程能力和数据结构理解。
145 4
|
算法 数据可视化 数据建模
【趣学C语言和数据结构100例】76-80
本文介绍了五种图论算法的C语言实现,涵盖二叉树的层次遍历及广度优先搜索(BFS)和深度优先搜索(DFS)的邻接表与邻接矩阵实现。层次遍历使用队列按层访问二叉树节点;BFS利用队列从源节点逐层遍历图节点,适用于最短路径等问题;DFS通过递归或栈深入图的分支,适合拓扑排序等场景。这些算法是数据结构和算法学习的基础,对提升编程能力和解决实际问题至关重要。
205 4
|
10月前
|
定位技术 C语言
c语言及数据结构实现简单贪吃蛇小游戏
c语言及数据结构实现简单贪吃蛇小游戏
|
11月前
|
搜索推荐 C语言
数据结构(C语言)之对归并排序的介绍与理解
归并排序是一种基于分治策略的排序算法,通过递归将数组不断分割为子数组,直到每个子数组仅剩一个元素,再逐步合并这些有序的子数组以得到最终的有序数组。递归版本中,每次分割区间为[left, mid]和[mid+1, right],确保每两个区间内数据有序后进行合并。非递归版本则通过逐步增加gap值(初始为1),先对单个元素排序,再逐步扩大到更大的区间进行合并,直至整个数组有序。归并排序的时间复杂度为O(n*logn),空间复杂度为O(n),且具有稳定性,适用于普通排序及大文件排序场景。
|
存储 网络协议 编译器
【C语言】深入解析C语言结构体:定义、声明与高级应用实践
通过根据需求合理选择结构体定义和声明的放置位置,并灵活结合动态内存分配、内存优化和数据结构设计,可以显著提高代码的可维护性和运行效率。在实际开发中,建议遵循以下原则: - **模块化设计**:尽可能封装实现细节,减少模块间的耦合。 - **内存管理**:明确动态分配与释放的责任,防止资源泄漏。 - **优化顺序**:合理排列结构体成员以减少内存占用。
977 14
|
存储 编译器 C语言
【C语言】结构体详解 -《探索C语言的 “小宇宙” 》
结构体通过`struct`关键字定义。定义结构体时,需要指定结构体的名称以及结构体内部的成员变量。
660 10
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
1202 13