云计算百科
云计算领域专业知识百科平台

C结构体基础(一)

C结构体基础(一)

  • 什么是结构体
  • 建立结构体类型
  • 定义结构体类型变量
  • 结构体变量的初始化
  • 结构体变量的引用
    • 1. 引用成员的基本方式
      • 补充:字符数组成员不能直接赋值
      • 方法一:逐个字符复制
      • 方法二:使用 strcpy 函数
    • 2. 成员本身也是结构体时的引用方式
    • 3. 结构体成员可以参与运算
    • 4. 同类型的结构体变量可以相互赋值
    • 5. 结构体变量和成员都可以取地址
    • 6. 两种错误用法

什么是结构体

数组只能存储相同类型的数据,但在实际使用中,我们常常希望有一种数据类型,可以把多个不同类型的数据放在一起进行管理。而结构体正好满足这个需求,它和类和对象中的类非常像:我们可以先设定一个结构类型,在其中自定义多个不同类型的数据变量。结构体变量创建之后,就可以使用结构体变量调用其中的数据变量进行赋值等操作。

建立结构体类型

格式:

struct 结构体名{
成员表列
};

说明:

  • 结构体名由用户自行定义
  • struct不能省略
  • 结构体可以设计多个
  • 成员可以同时属于不同的结构体
  • 下面以 Student 与 Teacher 两个结构体的定义为例子:

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

    struct Teacher{
    int id;
    char name[20];
    char course[30];
    float salary;
    };

    定义结构体类型变量

    这里再简单说一下:结构体类型和 int、float 一样,都是一种数据类型。不同的是,int、float 这类类型是由系统预先定义好的,而结构体类型则是由用户根据实际需求自行指定的。因此,定义一个结构体类型变量,和定义一个 int 类型变量在语法上非常相似。

    类比:

    int a;
    float b;
    struct Student student1, student2;

    在上面的声明中,struct Student 是结构体类型名,作用类似于 int a; 中的 int;student1 和 student2 则是结构体变量名,作用类似于 int a; 中的 a。

    当然,也可以在声明结构体类型的同时定义变量:

    struct Student{
    int id;
    char name[20];
    int age;
    float score;
    }student1, student2;

    但通常并不推荐这样写。因为这种写法把「结构体类型的定义」和「结构体变量的定义」糅合在了一起,代码变长后不容易区分哪些是类型、哪些是变量;同时,如果后续还想继续使用 Student 这个结构体类型,也不够灵活。更推荐的做法仍然是:先单独定义好结构体类型,再按需像 int a; 一样去定义变量。

    结构体变量的初始化

    结构体变量的初始化,通常是在定义结构体变量时进行的。

    例如,先定义好 Student 结构体类型,再在定义变量时赋值:

    struct Student {
    int id;
    char name[20];
    int age;
    float score;
    } student = {20260101, "张三", 18, 125.5};

    也可以在主函数中这样初始化:

    struct Student student = {.name = "张三", .score = 125.5};

    这里使用的是指定初始化,.name 表示给结构体变量 student 中的 name 成员赋值,.score 表示给 score 成员赋值。指定初始化时可以不按成员顺序书写,也可以只初始化部分成员。

    需要注意的是,如果只初始化了部分成员,那么未被初始化的成员会自动被初始化为零值:数值型成员为 0,字符型成员为 '\\0',指针型成员为 NULL。

    结构体变量的引用

    定义结构体变量之后,就可以使用结构体变量来引用其中的成员。

    1. 引用成员的基本方式

    引用结构体变量中某个成员的方式为:

    结构体变量名.成员名

    例如,给结构体变量 student 中的 id 成员赋值:

    student.id = 20260101;

    这里的 . 称为成员运算符,student.id 就表示 student 这个结构体变量中的 id 成员。这句话的作用就是把 20260101 赋给 student 的 id 成员。

    补充:字符数组成员不能直接赋值

    试运行下面的代码,你会发现编译器报出错误:

    [错误] assignment to expression with array type

    这是因为我们之前讲过,字符串在 C 语言中本质上就是一个字符数组,而数组名表示数组的首地址,是一个地址常量,不能放在赋值号左边。也就是说,数组之间不能直接相互赋值。

    #include <stdio.h>

    struct Student{
    char name[50];
    int id;
    };

    int main(){
    struct Student stu;
    stu.name = "张三";
    stu.id = 20260101;

    printf("姓名:%s,学号:%d", stu.name, stu.id);

    return 0;
    }

    上面代码中,stu.name 是一个字符数组,"张三" 是一个字符串,stu.name = "张三"; 相当于试图把一个字符串赋给一个数组名,因此会报错。为了解决这个问题,我们有两种办法。

    方法一:逐个字符复制

    既然数组不能整体赋值,但数组中的单个元素是可以赋值的,那么我们可以通过循环,把字符串中的字符一个一个复制到 stu.name 中。

    #include <stdio.h>

    struct Student{
    char name[50];
    int id;
    };

    int main(){
    struct Student stu;
    char Name[10] = "张三";

    for (int i = 0; i < 10; i++){
    stu.name[i] = Name[i];
    }
    stu.id = 20260101;

    printf("姓名:%s,学号:%d", stu.name, stu.id);

    return 0;
    }

    不过需要注意的是,这种方法也容易写出问题。比如:

    for (int i = 0; i < 2; i++){
    stu.name[i] = Name[i];
    }

    如果把循环条件中的 i < 10 改成了 i < 2,最后输出的姓名就只有“张”。这是因为前面说过,字符串中会有一个结束标志 '\\0':"张三" 在内存中实际存放的是 '张'、'三'、'\\0' 三个字符。上面的循环只复制了前两个字符,没有把 '\\0' 复制到 stu.name 中,printf 输出时找不到结束标志,就会继续向后读取,所以输出结果会出错。

    如果不知道字符数组的长度,可以用下面这个方法,借助字符串的结束标志 '\\0' 来控制循环:

    for (int i = 0; Name[i] != '\\0'; i++){
    stu.name[i] = Name[i];
    }
    stu.name[i] = '\\0';

    这样当遇到 '\\0' 时循环结束,就不必提前知道数组长度。不过要记得在循环结束后,再给目标字符串补上一个 '\\0',保证 stu.name 是一个完整的字符串。

    方法二:使用 strcpy 函数

    逐个字符复制虽然可以解决问题,但写起来比较麻烦,而且容易漏掉 '\\0'。C 语言标准库提供了专门用于字符串复制的函数 strcpy,使用时需要包含头文件 <string.h>。

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

    struct Student{
    char name[50];
    int id;
    };

    int main(){
    struct Student stu;

    strcpy(stu.name, "张三");
    stu.id = 20260101;

    printf("姓名:%s,学号:%d", stu.name, stu.id);

    return 0;
    }

    strcpy(stu.name, "张三"); 表示把字符串 "张三" 复制到字符数组 stu.name 中。strcpy 会自动把字符串末尾的 '\\0' 一起复制过去,因此比手动逐个字符复制更安全、更方便。使用 strcpy 时要注意:目标数组 stu.name 必须有足够的空间存放源字符串以及最后的 '\\0'。

    2. 成员本身也是结构体时的引用方式

    如果结构体的某个成员本身也是一个结构体,那么引用时就需要一层一层地使用 . 运算符。

    例如,学生结构体中包含一个表示成绩的结构体成员 scores:

    student.scores.PE = 85;
    student.scores.Math = 120;

    上面先通过 student.scores 找到 student 中的 scores 成员,再通过 .PE、.Math 继续引用 scores 内部的成员。

    3. 结构体成员可以参与运算

    结构体变量中的成员可以像普通变量一样,根据它自身的数据类型进行各种运算。

    例如,对 score 这样的 float 类型成员进行加法运算:

    student.score = student.score + 10.5;

    对 age 这样的 int 类型成员进行自增运算:

    student.age++;

    这里要顺便说明一下:成员运算符 . 的优先级很高,它和圆括号、下标等运算符同属于最高优先级,因此计算时会优先完成成员引用,再进行其他运算。

    4. 同类型的结构体变量可以相互赋值

    C 语言允许同类型的结构体变量之间直接整体赋值。

    例如:

    student1 = student2;

    这句话表示把结构体变量 student2 中所有成员的值,依次赋给结构体变量 student1 中对应的成员。

    5. 结构体变量和成员都可以取地址

    结构体变量本身可以取地址,它的成员也可以取地址。

    例如:

    &student;
    &student.id;

    &student 表示整个结构体变量的地址,&student.id 表示其中 id 成员的地址。

    6. 两种错误用法

    不能把整个结构体变量当作一个整体直接输出。下面的写法是错误的:

    printf("%s\\n", student);

    结构体变量中通常包含多个不同类型的成员,不能直接用 %s 这样的格式说明符输出整个结构体。

    也不能对结构体变量取地址后,再用与成员不对应的格式说明符整体输出:

    printf("%d,%s,%d,%f", &student);

    &student 是整个结构体变量的地址,并不是某个具体成员的值。要输出结构体中的内容,应当逐个成员进行输出,例如:

    printf("%d,%s,%d,%f", student.id, student.name, student.age, student.score);

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » C结构体基础(一)
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!