1、结构体简单介绍

结构体是一种用来描述一个复杂对象的自定义数据类型。

比如我们要描述一个学生,而一个学生需要名字、年龄、学号、身高、体重等多种属性。对于这个问题,仅用C语言的内置类型(char、short、int、long、float、double等)是不够的。为了解决这一问题,C语言加入了结构体,让程序员可以自己创造合适的类型。

1.1、结构的声明

struct tag
{
    member-list;
}variable-list;

其中,“ struct ”是结构体的 关键字 “ member-list ”是结构体的 成员列表 ,相当于结构体的属性“ variable ”是结构体的 变量列表 ,被用于定义结构体变量。

比如,我们来描述一个学生。

struct Student
{
    char name[20];//这是名字
    int age;//这是年龄
    char id[20];//这是学号
    float height;//这是身高
    int weight;//这是体重
};//分号加上

1.2、结构体变量的定义和初始化

1.2.1、变量的定义

我们还是以描述学生为例。

struct Student
{
    char name[20];
    int age;
    char id[20];
    float height;
    int weight;
}p1, p2;   //声明同时定义变量

struct Student p3;   //定义变量

int main()
{
    struct Student p4;   //定义只能在main函数内使用的变量

    return 0;
}

上述代码中,p1、p2是我们在声明类型的同时定义的变量, 用逗号隔开,为全局变量 ,可以在任何地方进行调用;p3是我们另外定义的结构体变量,也是 全局变量 ;而p4是在main函数内定义的,所以其只能在main函数内使用,以此类推,定义在其他函数或语句内的变量也是如此。

1.2.2、初始化

结构体变量的初始化有两种:一是 按顺序初始化 ,二是 指定顺序初始化

我们还是以描述学生为例。

struct Student
{
    char name[20];
    int age;
    char id[20];
    float height;
    int weight;
}p1, p2;

struct Student p3;

int main()
{
    struct Student p4;
    
    struct Student p5 = {"张三", 24, "202508101724", 175.5f, 56};
    struct Student p6 = {.age = 23, .name = "李四", .id = "20250810", .height = 174.3f, .weight = 55};

    return 0;
}

首先,初始化值写在 大括号 内。若是按顺序初始化,只需 将初始化值按顺序与格式要求列出,用逗号隔开即可 ,如p5;

若是指定顺序初始化,则按照 .成员名 的格式写出,并在其后输入初始化值 。不同成员变量的初始化用逗号隔开,如p6。

1.2.3、结构体嵌套初始化

假设我们有多个学生,每一个学生都需要描述,而他们又有共同的属性,比如在读学校、入学年份。这时,我们可以使用嵌套初始化。为了简化说明,我们去掉几个属性。

struct School
{
    char school_name[20];
    int enter_year;
};

struct Student
{
    struct School s;
    char name[20];
    int age;
    char id[20];
}p1 = {{"阳光中学", 2025}, "王五", 16, "20251752"};

struct Student p2 = {{"阳光中学", 2024}, "赵六", 17, "20251754"};

如上面代码所示, 首先要在结构体成员变量中加入所需其他结构体变量

然后, 加入的结构体变量初始化时另外加上一个大括号 。其他步骤同 1.2.2 的做法。

1.3、结构成员访问操作符

结构成员访问操作符有两种:点操作符( . ),和( -> )。分别用于结构体成员的直接访问,和结构体成员的直接访问。

1.3.1、结构体成员的直接访问

结构体成员的直接访问是通过点操作符访问的。点操作符接受两个操作数。点操作符的使用方式: 结构体变量.成员名 。继续以描述学生为例。

#include<stdio.h>

struct Student
{
    char name[20];
    int age;
    char id[20];
    float height;
    int weight;
};

int main()
{
    struct Student p5 = {"张三", 24, "202508101724", 175.5f, 56};
    printf(" name:%s\n age:%d\n id:%s\n height:%f\n weight:%d\n", p5.name, p5.age, p5.id, p5.height, p5.weight);

    return 0;
}

而对于嵌套过的结构体,我们则可以使用 较大范围结构体变量.较小范围结构体变量.成员名 这一方法。我们来举例。

#include<stdio.h>

struct School
{
    char school_name[20];
    int enter_year;
};

struct Student
{
    struct School s;
    char name[20];
    int age;
    char id[20];
}p1 = {{"阳光中学", 2025}, "王五", 16, "20251752"};

int main()
{
    printf(" school_name:%s\n enter_year:%d\n", p1.s.school_name, p1.s.enter_year);

    return 0;
}

1.3.2、结构体成员的间接访问

有时候我们得到的不是一个结构体变量,而是得到了一个指向结构体的指针。

由于我还未学习指针,暂时不做讲解。还请读者们谅解。

2、操作符的属性:优先级、结合性

这两个属性决定了表达式求值的计算顺序。

2.1、优先级

顾名思义,就是一个表达式中若包含多种操作符,优先级较高的操作符先执行。

例如

#include<stdio.h>

int main()
{
    printf("%d\n", 4 + 5 * 6);

    return 0;
}

上面例子中,4 + 5 * 6 表达式中包含加法运算符( + )和乘法运算符( * ),而 * 的优先级高于 + ,所以计算结果是34,而不是54。

下面是部分运算符的优先级顺序(从高到低)。再下面是参考表格。

• 圆括号( () )   //圆括号的优先级最高,可以修改其他操作符的优先级
• ⾃增运算符( ++ ),⾃减运算符( -- )
• 单⽬运算符( + 和 - )
• 乘法( * ),除法( / )
• 加法( + ),减法( - )
• 关系运算符( < 、 > 等)
• 赋值运算符( = )

表格参考至:https://zh.cppreference.com/w/c/language/operator_precedence

2.2、结合性

若两个运算符优先级相同,我们就看向结合性。至于表达式的执行顺序,取决于运算符是左结合,还是右结合。

3、表达式求值

表达式求值这一块,内容有两点:整型提升、算术转换。

3.1、整型提升

C语言中整型算术运算总是至少以缺省(默认)整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整型提升的规则:
1、有符号整数:按照变量的数据类型的符号位来提升的。
2、无符号整数:高位补0。

以 char 类型为例。char 类型占 1 字节,8 bit 。系统计算用的是补码。

#include<stdio.h>

int main()
{
    char a = 3;
    //2进制值:00000011
    // char 是有符号类型
    //所以整型提升:00000000000000000000000000000011

    char b = 127;
    //2进制值:01111111
    //整型提升:00000000000000000000000001111111
               
    char c = a + b;
    //相加:00000000000000000000000010000010
    // c 只能存 8 bit
    //为:10000010
    //补转原:11111110。为:-126

    printf("%d\n", c);

    return 0;
}

另:char c1 = -1;

 c1 只有 8 bit
 c1 为:11111111
整型提升:11111111111111111111111111111111

3.2、算术转换

如果某个操作符的各个操作数属于不同的类型,那么除非其中⼀个操作数的转换为另⼀个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
long double
double
float
unsigned long int            ( 向上转换 ) 
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中排名靠后,那么首先要转换为另外一个操作数的类型后执行运算。

4、问题表达式解析

4.1、表达式1

a*b + c*d + e*f

计算表达式1时,的确可以保证 * 比 + 先计算;但是,却不能保证某一个 * 比某一个 + 先计算。换句话说,计算机计算的方式可能就是随机的。

随着代码复杂性的增加,表达式1随机的计算方法会出现问题。

为了避免这些问题,我们可以采取以下两种方法:

1、 添加括号 。( ) 是最高级的操作符,可以帮助我们规定哪一个先算,哪一个后算。

2、 拆开来写 。计算机读取并执行代码,其顺序是从上到下的。

4.2、表达式2

c + --c

在这里,我们知道,自减操作肯定是比加操作先执行。而关键是加号左边的 c 。我们知道,对 c 进行自减操作后,得到的新值将存储在变量 c 中。那么,这个 c 到底是原来的 c ,还是自减操作以后的 c ?

为了避免不同的人对上面表达式产生不同理解这一现象的发生,也许我们++可以定义 不同的变量

4.3、表达式3

#include<stdio.h>

int main()
{
    int i = 0;
    i = i-- - --i * (i = -3) * i++ + ++i;
    printf("%d\n", i);

    return 0;
}

也许,你的心里会想:这啥玩意儿?!

所以,还是那一句话:一行一行写,或者用不同的变量。

4.4、表达式4

#include<stdio.h>

int fun()
{
    static int count = 1;
    return ++count;
}

int main()
{
    int answer;
    answer = fun() - fun() * fun();
    printf("%d\n", answer);//输出多少?

    return 0;
}

上述代码中,我们只能知道:先算乘法,再算加法。

但是,函数的调用顺序,我们无法得知。而 static 的作用是 创建不销毁 ,也就是说,调用顺序的·不同,导致不同位置函数的返回值不同,进而导致输出结果不同。

所以,我们一定要写出一个能有唯一计算路径的代码。

4.5、表达式5

#include<stdio.h>

int main()
{
    int i = 1;
    int ret = (++i) + (++i) + (++i);
    printf("%d\n", ret);
    printf("%d\n", i);

    return 0;
}

 i 的返回值是毋庸置疑的,经过 3 次自增操作,为 4 。

而 ret 的返回值,就有说法了。

这段代码中的第一个 + 在执行的时候,第三个++是否执行,这个是不确定的,因为依靠操作符的优先级和结合性是无法决定第一个 + 和第三个前置 ++ 的先后顺序。

4.6、总结

即使有了操作符的优先级和结合性,我们写出的表达式依然有可能不能通过操作符的属性确定唯⼀的计算路径,那这个表达式就是存在潜在风险的, 建议不要写出特别复杂的表达式
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐