Autoleaders控制组——结构体学习笔记

资料来源于网络,理解整合一下。

1.结构体基础知识

1.1结构体概念

结构体与数组类似,都是一种集合。只是数组(Array)是一组具有相同类型的数据的集合,而结构体可以用来存放一组不同类型的数据。定义形式为:

struct 结构体名{
    结构体所包含的变量或数组
};

结构体里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员(Member)。请看下面的一个例子:

struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在学习小组
    float score;  //成绩
};

stu 为结构体名,它包含了 5 个成员,分别是 name、num、age、group、score。结构体成员的定义方式与变量和数组的定义方式相同,只是不能初始化

1.2结构体变量

既然结构体是一种数据类型,那么就可以用它来定义变量。例如:
struct stu stu1, stu2;
定义了两个变量 stu1 和 stu2,它们都是 stu 类型,都由 5 个成员组成。注意关键字struct不能少。
也可以在定义结构体的同时定义结构体变量:

struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在学习小组
    float score;  //成绩
} stu1, stu2;//将变量放在结构体定义的最后即可

1.3成员的获取和赋值

结构体和数组类似,也是一组数据的集合,整体使用没有太大的意义。数组使用下标[ ]获取单个元素,结构体使用点号.获取单个成员。获取结构体成员的一般格式为:

结构体变量名.成员名;
通过这种方式可以获取成员的值,也可以给成员赋值:

#include <stdio.h>
int main(){
    struct{
        char *name;  //姓名
        int num;  //学号
        int age;  //年龄
        char group;  //所在小组
        float score;  //成绩
    } stu1;

    //给结构体成员赋值
    stu1.name = "Tom";
    stu1.num = 12;
    stu1.age = 18;
    stu1.group = 'A';
    stu1.score = 136.5;

    //读取结构体成员的值
    printf("%s的学号是%d,年龄是%d,在%c组,今年的成绩是%.1f!\n", stu1.name, stu1.num, stu1.age, stu1.group, stu1.score);

    return 0;
}

运行结果:
Tom的学号是12,年龄是18,在A组,今年的成绩是136.5!
除了可以对成员进行逐一赋值,也可以在定义时整体赋值,例如:

struct{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在小组
    float score;  //成绩
} stu1, stu2 = { "Tom", 12, 18, 'A', 136.5 };

不过整体赋值仅限于定义结构体变量的时候,在使用过程中只能对成员逐一赋值,这和数组的赋值非常类似。

2.结构体数组

所谓结构体数组,是指数组中的每个元素都是一个结构体。在实际应用中,C语言结构体数组常被用来表示一个拥有相同数据结构的群体,比如一个班的学生、一个车间的职工等。
在C语言中,定义结构体数组和定义结构体变量的方式类似,请看下面的例子:

struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在小组 
    float score;  //成绩
}class[5];//这个数组中有5个结构体

表示一个班级有5个学生。

结构体数组在定义的同时也可以初始化,例如:

struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在小组 
    float score;  //成绩
}class[5] = {
    {"Li ping", 5, 18, 'C', 145.0},
    {"Zhang ping", 4, 19, 'A', 130.5},
    {"He fang", 1, 18, 'A', 148.5},
    {"Cheng ling", 2, 17, 'F', 139.0},
    {"Wang ming", 3, 17, 'B', 144.5}
};

当对数组中全部元素赋值时,也可不给出数组长度,例如:

struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在小组 
    float score;  //成绩
}class[] = {
    {"Li ping", 5, 18, 'C', 145.0},
    {"Zhang ping", 4, 19, 'A', 130.5},
    {"He fang", 1, 18, 'A', 148.5},
    {"Cheng ling", 2, 17, 'F', 139.0},
    {"Wang ming", 3, 17, 'B', 144.5}
};

结构体数组的使用也很简单,例如,获取 Wang ming 的成绩:
class[4].score;

修改 Li ping 的学习小组:
class[0].group = ‘B’;
【示例】计算全班学生的总成绩、平均成绩和以及 140 分以下的人数。

#include <stdio.h>

struct{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在小组
    float score;  //成绩
}class[] = {
    {"Li ping", 5, 18, 'C', 145.0},
    {"Zhang ping", 4, 19, 'A', 130.5},
    {"He fang", 1, 18, 'A', 148.5},
    {"Cheng ling", 2, 17, 'F', 139.0},
    {"Wang ming", 3, 17, 'B', 144.5}
};

int main(){
    int i, num_140 = 0;
    float sum = 0;
    for(i=0; i<5; i++){
        sum += class[i].score;
        if(class[i].score < 140) num_140++;
    }
    printf("sum=%.2f\naverage=%.2f\nnum_140=%d\n", sum, sum/5, num_140);
    return 0;
}

运行结果:
sum=707.50
average=141.50
num_140=2

3.结构体指针

3.1基础知识

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

//结构体
struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在小组
    float score;  //成绩
} stu1 = { "Tom", 12, 18, 'A', 136.5 };
//结构体指针
struct stu *pstu = &stu1;

注意,结构体变量名和数组名不同,数组名在表达式中会被转换为数组指针,而结构体变量名不会,无论在任何表达式中它表示的都是整个集合本身,要想取得结构体变量的地址,必须在前面加&。

结构体结构体变量是两个不同的概念:结构体是一种数据类型,是一种创建变量的模板,编译器不会为它分配内存空间,就像 int、float、char 这些关键字本身不占用内存一样;结构体变量才包含实实在在的数据,才需要内存来存储。下面的写法是错误的,不可能去取一个结构体名的地址,也不能将它赋值给其他变量:

struct stu *pstu = &stu;
struct stu *pstu = stu;

3.2获取结构体成员

通过结构体指针可以获取结构体成员,一般形式为:

(*pointer).memberName

或者:

pointer->memberName

第二种写法中,->是一个新的运算符,习惯称它为“箭头”,有了它,可以通过结构体指针直接取得结构体成员;这也是->在C语言中的唯一用途。

【示例】结构体指针的使用。

#include <stdio.h>
int main(){
    struct{
        char *name;  //姓名
        int num;  //学号
        int age;  //年龄
        char group;  //所在小组
        float score;  //成绩
    } stu1 = { "Tom", 12, 18, 'A', 136.5 }, *pstu = &stu1;

    //读取结构体成员的值
    printf("%s的学号是%d,年龄是%d,在%c组,今年的成绩是%.1f!\n", (*pstu).name, (*pstu).num, (*pstu).age, (*pstu).group, (*pstu).score);//(*pstu)等价为结构体
    printf("%s的学号是%d,年龄是%d,在%c组,今年的成绩是%.1f!\n", pstu->name, pstu->num, pstu->age, pstu->group, pstu->score);

    return 0;
}

运行结果:
Tom的学号是12,年龄是18,在A组,今年的成绩是136.5!
Tom的学号是12,年龄是18,在A组,今年的成绩是136.5!

【示例】结构体数组指针的使用。

#include <stdio.h>

struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在小组
    float score;  //成绩
}stus[] = {
    {"Zhou ping", 5, 18, 'C', 145.0},
    {"Zhang ping", 4, 19, 'A', 130.5},
    {"Liu fang", 1, 18, 'A', 148.5},
    {"Cheng ling", 2, 17, 'F', 139.0},
    {"Wang ming", 3, 17, 'B', 144.5}
}, *ps;

int main(){
    //求数组长度
    int len = sizeof(stus) / sizeof(struct stu);//长度=结构体数组长度/结构体长度
    printf("Name\t\tNum\tAge\tGroup\tScore\t\n");
    for(ps=stus; ps<stus+len; ps++){
        printf("%s\t%d\t%d\t%c\t%.1f\n", ps->name, ps->num, ps->age, ps->group, ps->score);
    }
//ps=stus 指针指向数组开头
    return 0;
}

3.3结构体指针作为函数参数

【示例】计算全班学生的总成绩、平均成绩和以及 140 分以下的人数。

#include <stdio.h>
//定义结构体
struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在小组
    float score;  //成绩
}
//定义结构体数组
stus[] = {
    {"Li ping", 5, 18, 'C', 145.0},
    {"Zhang ping", 4, 19, 'A', 130.5},
    {"He fang", 1, 18, 'A', 148.5},
    {"Cheng ling", 2, 17, 'F', 139.0},
    {"Wang ming", 3, 17, 'B', 144.5}
};

void average(struct stu *ps, int len);

int main(){
    int len = sizeof(stus) / sizeof(struct stu);
    average(stus, len);//调用函数时顺便输出,所以main函数里没有输出
    //stus被自动转化成指针然后传到函数里?
    return 0;
}

void average(struct stu *ps, int len){
    int i, num_140 = 0;
    float average, sum = 0;
    for(i=0; i<len; i++){
        sum += (ps + i) -> score;
        if((ps + i)->score < 140) num_140++;
    }
    printf("sum=%.2f\naverage=%.2f\nnum_140=%d\n", sum, sum/5, num_140);
}

4.枚举类型

在实际编程中,有些数据的取值往往是有限的,只能是非常少量的整数,并且最好为每个值都取一个名字,以方便在后续代码中使用,比如一个星期只有七天,一年只有十二个月,一个班每周有六门课程等。

以每周七天为例,我们可以使用#define命令来给每天指定一个名字:

#include <stdio.h>

#define Mon 1
#define Tues 2
#define Wed 3
#define Thurs 4
#define Fri 5
#define Sat 6
#define Sun 7

int main(){
    int day;
    scanf("%d", &day);
    switch(day){
        case Mon: puts("Monday"); break;
        case Tues: puts("Tuesday"); break;
        case Wed: puts("Wednesday"); break;
        case Thurs: puts("Thursday"); break;
        case Fri: puts("Friday"); break;
        case Sat: puts("Saturday"); break;
        case Sun: puts("Sunday"); break;
        default: puts("Error!");
    }
    return 0;
}

运行结果:
5↙
Friday

#define命令虽然能解决问题,但也带来了不小的副作用,导致宏名过多,代码松散,看起来总有点不舒服。C语言提供了一种枚举(Enum)类型,能够列出所有可能的取值,并给它们取一个名字。

枚举类型的定义形式为:
enum typeName{ valueName1, valueName2, valueName3, … };
enum是一个新的关键字,专门用来定义枚举类型,这也是它在C语言中的唯一用途;typeName是枚举类型的名字;valueName1, valueName2, valueName3, …是每个值对应的名字的列表。注意最后的;不能少。
【示例】判断用户输入的是星期几。

#include <stdio.h>

int main(){
    enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun } day;
    scanf("%d", &day);
    switch(day){
        case Mon: puts("Monday"); break;
        case Tues: puts("Tuesday"); break;
        case Wed: puts("Wednesday"); break;
        case Thurs: puts("Thursday"); break;
        case Fri: puts("Friday"); break;
        case Sat: puts("Saturday"); break;
        case Sun: puts("Sunday"); break;
        default: puts("Error!");
    }
    return 0;
}

运行结果:
4↙
Thursday

需要注意的两点是:

  1. 枚举列表中的 Mon、Tues、Wed 这些标识符的作用范围是全局的(严格来说是 main() 函数内部),不能再定义与它们名字相同的变量。

  2. Mon、Tues、Wed 等都是常量,不能对它们赋值,只能将它们的值赋给其他的变量。

5.位域

有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。例如开关只有通电和断电两种状态,用 0 和 1 表示足以,也就是用一个二进位。正是基于这种考虑,C语言又提供了一种叫做位域的数据结构。

在结构体定义时,我们可以指定某个成员变量所占用的二进制位数(Bit),这就是位域。请看下面的例子:

struct bs{
    unsigned m;
    unsigned n: 4;
    unsigned char ch: 6;
};

:后面的数字用来限定成员变量占用的位数。成员 m 没有限制,根据数据类型即可推算出它占用 4 个字节的内存。成员 n、ch 被:后面的数字限制,不能再根据数据类型计算长度,它们分别占用 4、6 位的内存。

位域的具体存储规则如下:

  1. 当相邻成员的类型相同时,如果它们的位宽之和小于类型的 sizeof 大小,那么后面的成员紧邻前一个成员存储,直到不能容纳为止;如果它们的位宽之和大于类型的 sizeof 大小,那么后面的成员将从新的存储单元开始,其偏移量为类型大小的整数倍。

以下面的位域 bs 为例:

#include <stdio.h>

int main(){
    struct bs{
        unsigned m: 6;
        unsigned n: 12;
        unsigned p: 4;
    };
    printf("%d\n", sizeof(struct bs));

    return 0;
}
  1. 当相邻成员的类型不同时,不同的编译器有不同的实现方案,GCC 会压缩存储,而 VC/VS 不会。

请看下面的位域 bs:

#include <stdio.h>

int main(){
    struct bs{
        unsigned m: 12;
        unsigned char ch: 4;
        unsigned p: 4;
    };
    printf("%d\n", sizeof(struct bs));

    return 0;
}

在 GCC 下的运行结果为 4,三个成员挨着存储;在 VC/VS 下的运行结果为 12,三个成员按照各自的类型存储
3) 如果成员之间穿插着非位域成员,那么不会进行压缩。例如对于下面的 bs:

struct bs{
    unsigned m: 12;
    unsigned ch;
    unsigned p: 4;
};

在各个编译器下 sizeof 的结果都是 12。

位域成员可以没有名称,只给出数据类型和位宽,如下所示:

struct bs{
    int m: 12;
    int  : 20;  //该位域成员不能使用
    int n: 4;
};

无名位域一般用来作填充或者调整成员位置。因为没有名称,无名位域不能使用。

6.位运算

所谓位运算,就是对一个比特(Bit)位进行操作。
C语言提供了六种位运算符:

请添加图片描述

按位与运算(&)
一个比特(Bit)位只有 0 和 1 两个取值,只有参与&运算的两个位都为 1 时,结果才为 1,否则为 0。例如1&1为 1,0&0为 0,1&0也为 0,这和逻辑运算符&&非常类似。

C语言中不能直接使用二进制,&两边的操作数可以是十进制、八进制、十六进制,它们在内存中最终都是以二进制形式存储,&就是对这些内存中的二进制位进行运算。其他的位运算符也是相同的道理。

例如,9 & 5可以转换成如下的运算:

    0000 0000 -- 0000 0000 -- 0000 0000 -- 0000 10019 在内存中的存储)
& 0000 0000 -- 0000 0000 -- 0000 0000 -- 0000 01015 在内存中的存储)
-----------------------------------------------------------------------------------
    0000 0000 -- 0000 0000 -- 0000 0000 -- 0000 00011 在内存中的存储)

也就是说,按位与运算会对参与运算的两个数的所有二进制位进行&运算,9 & 5的结果为 1。

按位或运算(|)
参与|运算的两个二进制位有一个为 1 时,结果就为 1,两个都为 0 时结果才为 0。例如1|1为1,0|0为0,1|0为1,这和逻辑运算中的||非常类似。

例如,9 | 5可以转换成如下的运算:

    0000 0000 -- 0000 0000 -- 0000 0000 -- 0000 10019 在内存中的存储)
|   0000 0000 -- 0000 0000 -- 0000 0000 -- 0000 01015 在内存中的存储)
-----------------------------------------------------------------------------------
    0000 0000 -- 0000 0000 -- 0000 0000 -- 0000 110113 在内存中的存储)

9 | 5的结果为 13。
按位异或运算(^)
参与^运算两个二进制位不同时,结果为 1,相同时结果为 0。例如01为1,00为0,1^1为0。

例如,9 ^ 5可以转换成如下的运算:

   0000 0000 -- 0000 0000 -- 0000 0000 -- 0000 10019 在内存中的存储)
^  0000 0000 -- 0000 0000 -- 0000 0000 -- 0000 01015 在内存中的存储)
-----------------------------------------------------------------------------------
    0000 0000 -- 0000 0000 -- 0000 0000 -- 0000 110012 在内存中的存储)

9 ^ 5的结果为 12。
取反运算(~)
取反运算符为单目运算符,右结合性,作用是对参与运算的二进制位取反。例如1为0,~0为1,这和逻辑运算中的!非常类似。。

例如,~9可以转换为如下的运算:

~ 0000 0000 -- 0000 0000 -- 0000 0000 -- 0000 10019 在内存中的存储)
-----------------------------------------------------------------------------------
   1111 1111 -- 1111 1111 -- 1111 1111 -- 1111 0110-10 在内存中的存储)

所以~9的结果为 -10。
左移运算(<<)
左移运算符<<用来把操作数的各个二进制位全部左移若干位,高位丢弃,低位补0。

例如,9<<3可以转换为如下的运算:

<< 0000 0000 -- 0000 0000 -- 0000 0000 -- 0000 10019 在内存中的存储)
-----------------------------------------------------------------------------------
     0000 0000 -- 0000 0000 -- 0000 0000 -- 0100 100072 在内存中的存储)

所以9<<3的结果为 72。
右移运算(>>)
右移运算符>>用来把操作数的各个二进制位全部右移若干位,低位丢弃,高位补 0 或 1。如果数据的最高位是 0,那么就补 0;如果最高位是 1,那么就补 1。
例如,9>>3可以转换为如下的运算:

>> 0000 0000 -- 0000 0000 -- 0000 0000 -- 0000 10019 在内存中的存储)
-----------------------------------------------------------------------------------
     0000 0000 -- 0000 0000 -- 0000 0000 -- 0000 00011 在内存中的存储)

所以9>>3的结果为 1。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值