9.1 结构体基础

一、结构体概念与意义

  1. 意义 为了表达复杂的事物,而普通类型的变量无法满足实际需要。比如表达学生信息,一个学生信息包含名字、性别年龄等多项信息,定义多维数组太复杂了。
  2. 概念 把一些基本类型数据组合在一起,形成的一个新的复合数据类型,这个叫做结构体

二、结构体使用

(一) 结构体定义

# include <stdio.h>
/*
结构体定义的总结:
结构体标记和变量至少有一种
如果有变量可以进行初始化
*/

int main (void)
{
    /*
    1.定义时为这个结构体起一个标记名
    首推这一种!!!后边的看起来不舒服!
    */
    struct Student 
    {
        char name[30];
        int age;
        double score;
    };//分号别忘了哦,小老弟!

    /*
    2,定义时为这个结构体起一个标记名
    并定义这种数据类型的变量
    */
    struct Student2 
    {
        char name[30];
        int age;
        double score;

    }A, *P; //A就是Student2类型,*P为 Student2 * 类型

    /*
    3,定义时为这个结构体起一个标记名
    并定义这种数据类型的变量
    并为变量进行初始化
    */  
    struct Student3 
    {
        char name[30];
        int age;
        double score;

    }A1={"张三",20}, *P1; //A就是Student2类型,*P为 Student2 * 类型
    
    /*
    4,只定义结构体类型的变量
    */ 
    struct 
    {
        char name[30];
        int age;
        double score;

    }A2, *P2; 

    return 0;
}

(二) 赋值与初始化

/*
初始化的几种方式
*/
# include <stdio.h>
struct Student 
{
    char *name; //写成char name[50]会报错,因为后边有一个 st.name="小花",char name [50],能用scanf赋值
    int age;
    float score;
};

void f(void)
{
    /*
    定义的同时初始化可以全部赋值,定义完再初始化,只能单个单个赋值
    */
    struct Student st1={"小花",17,99.9f};//完全初始化
    struct Student st2;//只定义不初始化,后期通过对成员的引用,逐个初始化
    st2.name="小白";
    st2.age = 14;
    st2.score = 98.7f;

    struct Student st3={"小兰",14};//不完全初始化,未初始化的将被赋 default: 0 或者default

    struct Student st4 =st2;//用一个结构体变量为另一个进行初始化

    struct Student st5={.score=99.2f};//C99允许,VC++6.0不支持。
    print(st1);
    print(st2);
    print(st3);
    print(st4);
    print(st5);
}

void print(struct Student st)
{
    printf("名字是:%s ",st.name);
    printf("年龄是:%d ",st.age);
    printf("成绩是:%f\n",st.score);
}
int main(void)
{
    f();   
    return 0 ;
}
/*输出
名字是:小花 年龄是:17 成绩是:99.900002      
名字是:小白 年龄是:14 成绩是:98.699997      
名字是:小兰 年龄是:14 成绩是:0.000000       
名字是:小白 年龄是:14 成绩是:98.699997      
名字是:(null) 年龄是:0 成绩是:99.199997   
*/
/*
结构体里面定义了一个指针类型的成员变量且通过scanf赋值的操作
*/
# include <stdio.h>
# include <stdlib.h>
struct Student 
{
    char *name;
    int age;
    float score;
};

void f(void)
{
    struct Student st2;
    st2.name =(char *)malloc(50); 
    printf ("请输入学生姓名、年龄、成绩\n");
    scanf("%s%d%f",st2.name,&st2.age,&st2.score);
    printf("学生的姓名是:%s 年龄是:%d 成绩是:%.2f\n",st2.name,st2.age,st2.score);
}

int main (void)
{
    f();
    return 0;
}

(三) 结构体成员访问

  1. 变量名.成员名(上边例子已经有了 st2.age=17)
  2. 结构体指针变量名->成员名 结构体指针变量名->成员名,在计算机内部会被转换成(*指针变量名).成员名的方式来执行。
struct Student
{
    int age;
    double score;
};
struct Student st={17;99};
struct Student *pst; //结构体变量
pst = &st;
st.age = 15; //第一种方式访问成员
pst->age = 10; //第二种方式访问成员
//pst->age会被系统转换成 (*pst).age  二者等价!

pst->age会被系统转换成 (*pst).age ,二者等价
pst->age 的含义:pst所指向的那个结构体变量中的age这个成员

(四) 结构体变量的运算

结构体变量之间不能相互 + - × ÷
但是可以相互赋值

三、结构体变量所占字节长度

利用 sizeof
字节对齐和机器填充

原则:

  1. 结构体变量的首地址能够被其最宽的基本类型成员的大小所整除
  2. 结构体每个成员相对结构体首地址的偏移量都是每个成员本身大小的整数倍,如有需要会在成员之间填充字节。
  3. 结构体变量所占空间的大小必定是最宽数据类型大小的整数倍。如有需要会在最后一个成员末尾填充若干字节使得所占空间大小是最宽数据类型大小的整数倍。
  4. 如果结构体成员是复合数据类型的,那么它对于结构体首地址的偏移量应该是其最宽子成员大小的整数倍。
int main(void)
{
    //字节对齐:各种数据类型是按照一定的规则在内存中排列的
    short a;
    int b;
    double c;
    printf("%d,%d,%d\n",(int)&a%2,(int)&b%4,(int)&c%8);//输出结果都是0
    //这说明基本数据类型的地址是能够被它们所占的字节的长度所整除的。
}
int main(void)
{
    struct stu1
    {
        char a;
        short b;
    };
    struct stu2
    {
        char a;
        int b;
    };
    struct stu1 A;
    struct stu2 B;
    printf("%d,%d\n",(int)&A%2,(int)&B%4);//输出都是0
    //一、这说明结构体变量的首地址能够被其最宽的基本类型成员的大小所整除
    printf("%d,%d\n",sizeof(A),sizeof(B));//输出4,8
    /*
    二、结构体每个成员相对结构体首地址的偏移量都是每个成员本身大小的整数倍,如有需要会在成员之间填充字节。
    */
    printf("%d,%d\n", ((int)&A.b-(int)&A)%2, ((int)&B.b-(int)&B)%4);//输出结果0,0
    return 0;
}

# include <stdio.h>
int main(void)
{
    struct stu3
    {
        char a;
        double f;
        int b;
    }C;
    printf("%d\n",sizeof(C));//输出24
    //三、结构体的总大小为结构体最宽基本类型成员的整数倍,如果不是,会在最后一个成员后边补若干个字节
}

# include <stdio.h>
int main(void)
{
    struct stu4
    {
        char i;
        struct stu2;
    }D;
    printf("%d\n",sizeof(D));//输出12
    //结构体嵌套
    //如果结构体成员是复合数据类型的,那么它对于结构体首地址的偏移量应该是其最宽成员大小的整数倍。
}
/*
D中最宽的基本类型是D.b.b长度为4个字节,所以:
1. D的首地址必须被4整除
2. D的长度必须被4整除
struct stu2:
1. D.b的地址能够被4整除
2. D.b的长度是8 
*/

四、结构体变量和结构体变量指针作为函数参数传递的问题

# include <stdio.h>
# include <string.h>

struct Student
{
    int age;
    char gender;
    char name[50];
};
void InputStudent(struct Student * );//声明时可以不写形参名
void OutputStudent(struct Student stu);
int main (void)
{
    struct Student st;
    InputStudent(&st);//对结构体变量输入;
    OutputStudent(st);//对结构体变量输出;
    return 0;
}
void OutputStudent(struct Student stu)//可以发送st的地址,也可以发送st的内容,推荐使用地址。代码见后边(讨论)
{
    printf("%d %c %s",stu.age,stu.gender,stu.name);
}
void InputStudent(struct Student * pstu) //pstu只占4个字节
{
    strcpy(pstu->name,"张三");
    pstu->gender='F';
    pstu->age=17;
}

讨论:对结构体变量进行输出,发送地址好还是直接发送内容好? 用指针 指针的优点:快速传递速度、耗用内存少、执行速度快

如果直接发送内容则需要把结构体变量的内容全部拷贝,传递的内容就多了,只发一个地址显然占用内存更小。

void OutputStudent(struct Student *stu)//可以发送st的地址,也可以发送st的内容,推荐使用地址。
{
    printf("%d %c %s",stu->age,stu->gender,stu->name);
}
int main(void)
{
    OutputStudent(&st);
}

本文章使用limfx的vsocde插件快速发布