C语言 超详细 零基础入门 结构体 的 基本介绍

第07章_结构体与共用体


本章专题脉络

1、结构体(struct)类型的基本使用

1.1 为什么需要结构体?

C 语言内置的数据类型,除了几种原始的基本数据类型,只有数组属于复合类型,可以同时包含多个值,但是只能包含相同类型的数据,实际使用场景受限

举例1:

现有一个需求,编写学生档案管理系统,这里需要描述一个学生的信息。该学生的信息包括学号、姓名、性别、年龄、家庭住址等,这些数据共同说明一个学生的总体情况。

显然,这些数据类型各不相同,无法使用数组进行统一管理。

举例2:

隔壁老王养了两只猫咪。一只名字叫小黄,今年2岁,橘色;另一只叫小黑,今年3岁,黑色。请编写一个程序,当用户输入小猫的名字时,就显示该猫的名字,年龄,颜色。如果用户输入的小猫名错误,则显示老王没有这只猫。

传统的解决方法

尝试1:单独定义多个变量存储,实现需求。但是,多个变量,不便于数据的管理。

尝试2:使用数组,它是一组具有相同类型的数据的集合。但在编程中,往往还需要一组类型不同的数据,例如猫的名字使用字符串、年龄是int,颜色是字符串,因为数据类型不同,不能用一个数组来存放。

尝试3:C语言提供了结构体。使用结构体,内部可以定义多个不同类型的变量作为其成员。

考研中见的最多的就是指针和结构体结合起来构造结点(如链表的结点、二叉树的结点等)。

1.2 结构体的理解

C 语言提供了 struct关键字,允许自定义复合数据类型,将不同类型的值组合在一起,这种类型称为结构体(structure)类型。

C 语言没有其他语言的对象(object)和类(class)的概念,struct 结构很大程度上提供了对象和类的功能。

比如:

1.3 声明结构体

构建一个结构体类型的一般格式:

struct 结构体名{ 
    数据类型1 成员名1;   //分号结尾
    数据类型2 成员名2; 
    ……
    数据类型n 成员名n;
}; //注意最后有一个分号

举例:学生

struct Student{       // 定义结构体:学生
    int id;           //学号
    char name[20];    //姓名
    char gender;      //性别
    char address[50]; //家庭住址
};  

举例:猫

struct Cat{
    char name[20];   //名字
    int age;         //年龄
    char color[20];  //颜色
};

举例:人类

struct Person{
    char name[20];    //姓名
    char gender;      //性别
    int age;          //年龄
    double weight;    //体重
}; 

举例:通讯录

struct Contacts{            
    char name[50];          //姓名
    int year;               //年
    int month;              //月
    int day;                //日
    char email[100];        //电子邮箱
    char phone_number[15]; //手机号
};

举例:员工

struct Employee {  
    int id;               //员工编号 
    char name[20];		  //员工姓名
    char gender;		  //员工性别
    int age;			  //员工年龄
    char address[30];	  //员工住址
};

1.4 声明结构体变量并调用成员

定义了新的数据类型以后,就可以声明该类型的变量,这与声明其他类型变量的写法是一样的。

声明结构体变量格式1:

struct 结构体类型名称 结构体变量名;
注意,声明自定义类型的变量时,类型名前面,不要忘记加上 struct 关键字。

举例:

struct Student stu1;

调用结构体变量的成员:

结构体变量名.成员名 [= 常量或变量值]

举例:

#include <stdio.h>
#include <string.h>
int main() {

    struct Student stu1; //声明结构体变量
	
    //调用结构体成员
    stu1.id = 1001;
    //stu1.name = "Tom"; //报错,不能直接通过赋值运算符来给字符数组赋值
    strcpy(stu1.name, "Tony");
    stu1.gender = 'M';
    strcpy(stu1.address, "北京市海淀区五道口");

    printf("id = %d,name = %s,gender = %c,address = %s\n", 
           stu1.id, stu1.name, stu1.gender, stu1.address);

    return 0;
}
说明:
1)先声明了一个 struct Student类型的变量 stu1,这时编译器就会为 stu1 分配内存,接着就可以为 stu1 的不同属性赋值。可以看到,struct 结构的属性通过点( . )来表示,比如 id 属性要写成 stu1.id。
2)字符数组是一种特殊的数组,直接改掉字符数组名的地址会报错,因此不能直接通过赋值运算符来对它进行赋值。你可以使用字符串库函数 strcpy() 来进行字符串的复制操作。

声明结构体变量格式2:

除了逐一对属性赋值,也可以使用大括号,一次性对 struct 结构的所有属性赋值。此时,初始化的属性个数最好与结构体中成员个数相同,且成员的先后顺序一一对应。格式:

struct 结构体名 结构体变量={初始化数据};

举例:

//声明结构体
struct Car {
  char* name;
  double price;
  int speed;
};

//声明结构体变量
struct Car audi = {"audi A6L", 460000.99, 175};
注意:如果大括号里面的值的数量少于属性的数量,那么缺失的属性自动初始化为 0 。
struct Student {
    int id;
    char name[20];
    char gender;
    int score;  //学生成绩

};

int main() {
    struct Student stu = {1001, "songhk", 'M'};
    printf("Name: %s\n", stu.name);
    printf("Score: %d\n", stu.score);

    return 0;
}

声明结构体变量格式3:

方式2中大括号里面的值的顺序,必须与 struct 类型声明时属性的顺序一致。此时,可以为每个值指定属性名。

格式:

struct 结构体名 结构体变量={.成员1=xxx,.成员2=yyy,...};

举例:

struct Car audi = {.speed=175, .name="audi A6L"};
同样,初始化的属性少于声明时的属性,剩下的那些属性都会初始化为 0 。

声明变量以后,可以修改某个属性的值。

struct Car audi = {.speed=175, .name="audi A6L"};
audi.speed = 185;  //将 speed 属性的值改成 185

声明结构体变量格式4: 声明类型的同时定义变量

struct 的数据类型声明语句与变量的声明语句,可以合并为一个语句。格式:

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

举例:同时声明了数据类型 Circle 和该类型的变量 c1

struct Circle {
	int id;
    double radius;
} c1;

举例:

struct Employee {
    char name[20];
    int age;
    char gender;
    char phone[11];
} emp1, emp2;

声明结构体变量格式5: 不指定类型名而直接定义结构体类型变

如果类型标识符(比如Student、Circle、Employee等)只用在声明时这一个地方,后面不再用到,那就可以将类型名省略。 该结构体称为匿名结构体

格式:

struct {
    成员列表;
} 变量名列表;

举例:

struct {
    char name[20];
    int age;
    char gender;
    char phone[11];
} emp1, emp2;

struct 声明了一个匿名数据类型,然后又声明了这个类型的两个变量emp1、emp2 。与其他变量声明语句一样,可以在声明变量的同时,对变量赋值。

举例:

struct {
    char name[20];
    int age;
    char gender;
    char phone[11];
} emp1 = {"Lucy", 23, 'F', "13012341234"},
  emp2 = {"Tony", 25, 'M', "13367896789"};

上例在声明变量 emp1 和 emp2 的同时,为它们赋值。

声明结构体变量格式6:使用 typedef 命令

使用 typedef 可以为 struct 结构指定一个别名,这样使用起来更简洁。

举例:

//声明结构体
typedef struct cell_phone {
    int phone_no;              //电话号码
    double minutes_of_charge;  //每分钟费用
} Phone;

//声明结构体变量
Phone p = {13012341234, 5};

上例中, Phone 就是 struct cell_phone 的别名。声明结构体变量时,可以省略struct关键字。

这种情况下,C 语言允许省略 struct 命令后面的类型名。进一步改为:

//声明匿名结构体
typedef struct {
    int phone_no;
    double minutes_of_charge;
} Phone;

//声明结构体变量
Phone p = {13012341234, 5};

进一步,在考研中,还会出现如下的声明方式:

typedef struct {
    int phone_no;
    double minutes_of_charge;
} Phone,*pPhone;

这里多了个*pPhone,其实在定义一个结点指针p时,Phone *p; 等价于 pPhone p; ,前者的写法类似于int *a、char *b等更方便记忆,不必再加个pPhone p来增加记忆负担。所以在考研中我们不采用这种方法,统一删掉 *pPhone的写法。

说明:
1、在创建一个结构体变量后,需要给成员赋值。在没有给成员赋值的情况下调用,打印的值是垃圾数据,可能导致程序异常终止。
2、不同结构体变量的成员是独立的,互不影响,一个结构体变量的成员更改,不影响另外一个。

1.5 举例

练习:盒子案例

(1)编程创建一个Box结构体,在其中定义三个成员表示一个立方体的长、宽和高,长宽高可以通过控制台输入。

(2)定义一个函数获取立方体的体积(volume)。

(3)创建一个结构体,打印给定尺寸的立方体的体积。

#include <stdio.h>

// 1. 定义Box结构体
struct Box {
    double length;
    double width;
    double height;
};

// 2. 获取立方体体积的函数
double getVolume(struct Box box) {
    return box.length * box.width * box.height;
}

int main() {

    // 3. 创建结构体实例
    struct Box box;
    printf("输入长度:");
    scanf("%lf", &box.length);

    printf("输入宽度:");
    scanf("%lf", &box.width);

    printf("输入高度:");
    scanf("%lf", &box.height);

    // 调用函数获取体积并打印
    printf("体积为: %.2lf\n", getVolume(box));

    return 0;
}

1.6 小 结

区分三个概念:结构体、结构体变量、结构体变量的成员。

  • 结构体是自定义的数据类型,表示的是一种数据类型。
  • 结构体变量代表一个具体变量。类比:
int num1 ; // int 是数据类型, 而num1是一个具体的int变量

struct Car car1; // Car 是结构体数据类型,而car1是一个Car变量
  • Car 就像一个“汽车图纸”,生成出来的具体的一辆辆汽车,就类似于一个个的结构体变量。这些结构体变量都含有相同的成员,将结构体变量的成员比作“零件”,同一张图纸生产出来的零件的作用都是一样的。

2、进一步认识结构体

2.1 结构体嵌套

结构体的成员也是变量,那么成员可以是基本数据类型,也可以是数组指针结构体等类型 。如果结构体的成员是另一个结构体,这就构成了结构体嵌套。

举例1:

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

struct Name {
    char firstName[50];
    char lastName[50];
};

struct Student {
    int age;
    struct Name name;
    char gender;
} stu1;

int main(){
    strcpy(stu1.name.firstName, "美美");
    strcpy(stu1.name.lastName, "韩");
    //stu1.age = 18;
    //stu1.gender = 'F';

    //或者
    struct Name myname = {"美美","韩"};
    stu1.name = myname;
    //stu1.age = 18;
    //stu1.gender = 'F';
    return 0;
}

举例2:

struct Date {    //声明一个结构体类型 struct Date 
    int year;    //年
    int month;   //月
    int day;     //日
};

struct Employee { //声明一个结构体类型 struct Employee
    int id;
    char name[20];
    int age;
    struct Date birthday; //成员birthday属于struct Date类型
};

声明结构体变量并调用成员:

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

int main(){
    struct Employee emp1;

    emp1.id = 1001;
    strcpy(emp1.name,"Tony");
    emp1.age = 24;

    emp1.birthday.year = 2001;
    emp1.birthday.month = 3;
    emp1.birthday.day = 12;

    return 0;
}

说明:如果成员本身又属一个结构体类型,则要用若干个点( . ),一级一级地找到最低的一级的成员。比如,emp1.birthday.year

赋值的时候还有多种写法:

#include <stdio.h>

int main() {
    //方式1:
    struct Employee emp1 = {1001, "Tony", 24, {1999, 10, 11}};

    //方式2:
    struct Date birthday = {2001, 5, 6};
    struct Employee emp2 = {1002, "Tom", 22, birthday};

    //方式3:
    struct Employee emp3 = {
            .id = 1003,
            .age = 24,
            .name = "Jerry",
            .birthday = {2001, 3, 16}};

    //方式4:
    struct Employee emp4 = {
            .id = 1003,
            .age = 27,
            .name = "Jerry",
            .birthday.year = 1998,
            .birthday.month = 8,
            .birthday.day = 12};

    return 0;
}

举例3:自我嵌套

单链表结构的结点定义如下:

struct Node {
    int data;            //这里默认的是int型,如需其他类型可修改
    struct Node* next;   //指向Node型变量的指针
};
//等同于
typedef struct Node {
    int data;
    struct Node *next;
} LNode;

二叉树结构的结点定义如下:

typedef struct BTNode {
    int data;              //这里默认的是int型,如需其他类型可修改
    struct BTNode *lchild;        //指向左孩子结点指针,在后续的二叉树章节中讲解
    struct BTNode *rchild;        //指向右孩子结点指针,在后续的二叉树章节中讲解
} BTNode;

2.2 结构体占用空间

结构体占用的存储空间,不是各个属性存储空间的总和。为了计算效率,C 语言的内存占用空间一般来说,都必须是 int 类型存储空间的整数倍。如果 int 类型的存储是4字节,那么 struct 类型的存储空间就总是4的倍数。

struct A{
    char a;
    int b;
} s;

int main() {
    printf("%d\n", sizeof(s)); // 8
    return 0;
}

变量 s 的存储空间不是5个字节,而是占据8个字节。a 属性与 b 属性之间有3个字节的“空洞”。

2.3 结构体变量的赋值操作

同类型的结构体变量可以使用赋值运算符( = ),赋值给另一个变量,比如

student1 = student2; //假设student1和student2已定义为同类型的结构体变量

这时会生成一个全新的副本。系统会分配一块新的内存空间,大小与原来的变量相同,把每个属性都复制过去,即原样生 成了一份数据

也就是说,结构体变量的传递机制是值传递,而非地址传递。这一点跟数组的赋值不同,使用赋值运算符复制数组,不会复制数据,只是传递地址。

举例1:

struct Car {
    double price;
    char name[30];
} a = {.name = "Audi A6L", .price = 390000.99};

int main() {
    struct Car b = a;

    printf("%p\n", &a); //结构体a变量的地址 00007ff75a019020
    printf("%p\n", &b); //结构体b变量的地址 000000a6201ffcd0

    printf("%p\n", a.name);  //结构体a变量的成员name的地址  00007ff719199028
    printf("%p\n", b.name);  //结构体b变量的成员name的地址  000000c2565ffd88

    a.name[0] = 'B';
    printf("%s\n", a.name); // Budi A6L
    printf("%s\n", b.name); // Audi A6L

    return 0;
}

上例中,变量 b 是变量 a 的副本,两个变量的值是各自独立的,修改掉 b.name 不影响 a.name 。

举例2:将结构体内的字符数组改为字符指针

上个例子有个前提,就是 struct 结构的属性必须定义成字符数组,才可以复制数据。如果属性定义成字符指针,结果就不一样了。

struct Car {
    char *name;
    double price;
} a = {"Audi A6L", 390000.99};

int main() {
    struct Car b = a;

    printf("%p\n", &a); //结构体a变量的地址 00007ff75a019020
    printf("%p\n", &b); //结构体b变量的地址 000000a6201ffcd0

    printf("%p\n", a.name); //结构体a变量的成员name的地址  00007ff7d778a000
    printf("%p\n", b.name); //结构体b变量的成员name的地址  00007ff7d778a000

    return 0;
}

上例中, name 属性变成了一个字符指针,这时 a 赋值给 b ,此时的b变量仍然是新开辟的内存空间。但是,a 和 b的 name 成员保存的指针相同,也就是说两个属性共享同一个"Audi A6L"

在C语言中,相同的字符串常量通常只会保存一份,即这些字符串常量共享相同的内存。当你声明多个指针变量并让它们指向相同的字符串常量时,它们实际上都指向相同的内存地址。字符串常量的共享,有助于减小程序的内存占用。

注意:C 语言没有提供比较两个自定义数据结构是否相等的方法,无法用比较运算符(比如 == 和 != )比较两个数据结构是否相等或不等。
【武汉科技大学2019研】已知书籍结构体定义如下,则对结构体变量bk的正确赋值是( )。
struct BOOK { struct { int year, month, day; } publish; } bk;
A.bk.year=1998; bk.month=11; bk.day=11;
B.publish.year=1998; publish.month=11; publish.day=11;
C.year=1998; month=11; day=11;
D.bk.publish.year=1998; bk.publish.month=11; bk.publish.day=11;
【答案】D
【解析】变量bk是结构体BOOK的一个结构体变量,该变量含有一个成员变量publish,publish也是一个结构体变量,该结构变量含三个成员变量,分别是year、month、day,结构体变量中的成员变量不可直接访问,必须以结构体变量名.成员变量名形式访问,所以只能通过bk.publish.year形式访问到最内层的变量并为其赋值,答案选D。

猜你喜欢

转载自blog.csdn.net/m0_67184231/article/details/134873666