尚硅谷C语言笔记-结构体与共用体

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

1.1 为什么需要结构体?

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

1.2 结构体的理解

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

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

1.3 声明结构体

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

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

举例:学生

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

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

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

声明结构体变量格式1: 

C
struct 结构体类型名称 结构体变量名;

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

C
struct Student stu1;

调用结构体变量的成员:

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

举例:

C
#include
#include
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 结构的所有属性赋值。此时,初始化的属性个数最好与结构体中成员个数相同,且成员的先后顺序一一对应。格式:

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

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

//声明结构体变量
struct Car audi = {"audi A6L", 460000.99, 175};

注意:如果大括号里面的值的数量少于属性的数量,那么缺失的属性自动初始化为 0 。

声明结构体变量格式3: 

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

格式:

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

struct Car audi = {.speed=175, .name="audi A6L"};

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

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

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

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

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

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

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

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

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

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

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

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

C
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 结构指定一个别名,这样使用起来更简洁。

举例:

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

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

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

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

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

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

说明:

1、在创建一个结构体变量后,需要给成员赋值。在没有给成员赋值的情况下调用,打印的值是垃圾数据,可能导致程序异常终止。

2、不同结构体变量的成员是独立的,互不影响,一个结构体变量的成员更改,不影响另外一个。

1.6 小 结

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

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

C
int num1 ; // int 是数据类型, 而num1是一个具体的int变量

struct Car car1; // Car 是结构体数据类型,而car1是一个Car变量

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

2、进一步认识结构体

2.1 结构体嵌套

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

C
#include
#include

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.2 结构体占用空间

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

2.3 结构体变量的赋值操作

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

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

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

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

C
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 。

注意:C 语言没有提供比较两个自定义数据结构是否相等的方法,无法用比较运算符(比如 == 和 != )比较两个数据结构是否相等或不等。

3、结构体数组

3.1 对比结构体与数组

C
//定义一个结构体A
typedef struct{
    int a ;
    char b;
    float c;
} A;
//定义一个结构体变量
A a;

//定义一个数组类型的变量
int b[3];

语句int b[3];定义了一个数组,名字为b,由3个整型分量组成。而语句A a;可以类似认为定义了一个数组,名字为a,只不过组成a数组的3个分量是不同类型的。对于数组b,b[0]、b[1]、b[2]分别代表数组中第1、第2、第3个同为int类型的元素的值。而结构体a中,a. a、a. b、a. c分别对应于结构体变量a 中第1、第2、第3个元素的值,两者十分相似。

3.2 结构体数组的声明

结构体数组:数组元素是结构体变量而构成的数组。先定义结构体类型,然后用结构体类型定义数组变量。

方式1:先声明一个结构体类型,再用此类型定义结构体数组

C
结构体类型 数组名[数组长度];

struct Person{
        char name[20];
        int age;
};

struct Person pers[3]; //pers是结构体数组名

方式2:定义结构体类型的同时,定义数组变量。

C
struct 结构体名{
        成员列表;
} 数组名[数组长度];

struct Person{
        char name[20];
        int age;
} pers[3];

3.3 初始化数组元素

对应前面的声明方式1:

举例:

C
struct Student stus[3] = { {1001,"Tom",'M', 14},
                           {1002, "Jerry", 'M', 13},
                           {1003, "Lily",'F',12}};

尚硅谷C语言笔记-结构体与共用体_第1张图片

3.4 结构体数组元素的成员的调用

方式1:使用数组角标方式

C
结构体数组名[下标].成员名

stus[1].age = 23;

方式2:使用指向数组或数组元素的指针(下节讲)

C
指针->成员名

p->age=24;  //p为指向某个数组元素的指针

4、结构体指针

4.1 结构体指针格式

结构体指针:指向结构体变量的指针 (将结构体变量的起始地址存放在指针变量中)

具体应用场景:①可以指向单一的结构体变量  ②可以用作函数的参数 ③可以指向结构体数组

定义结构体指针变量格式:

C
struct 结构体名 *结构体指针变量名;

//int num;
//int *num;


struct Book {
    char title[50];
    char author[10];
    double price;
};

struct Book *b1;

// 等价于
struct Book {
    char title[50];
    char author[10];
    double price;
} *b1;

说明:变量 b1 是一个指针,指向的数据是 struct Book 类型的实例。

4.2 结构体传参

如果将 struct 变量传入函数,函数内部得到的是一个原始值的副本。

C
#include

struct Person {
    char *name;
    int age;
    char *address;
};

void addAge(struct Person per) {
    per.age = per.age + 1;
}

int main() {
    struct Person p1 = {"Tom", 20, "北京市海淀区"};
    addAge(p1);
    printf("age = %d\n", p1.age); // 输出 20
    return 0;
}

函数 addAge() 要求传入一个 struct 变量 per,但实际上传递的是 struct 变量p1的副本,改变副本影响不到函数外部的原始数据。

通常情况下,开发者希望传入函数的是同一份数据,函数内部修改数据以后,会反映在函数外部。而且,传入的是同一份数据,也有利于提高程序性能。这时就需要将 struct 变量的指针传入函数,通过指针来修改 struct 属性。如下

C
struct Person {
    char *name;
    int age;
    char *address;
};

void addAge(struct Person *per) {   //说明1
    (*per).age = (*per).age + 1;    //说明2
}

int main() {
    struct Person p1 = {"Tom", 20, "北京市海淀区"};
    addAge(&p1);                    //说明3
    printf("age = %d\n", p1.age);   // 说明4:输出 21
    return 0;
}

  • 说明1:per 是 struct 结构的指针,调用函数时传入的是指针。
  • 说明2:函数内部必须使用 (*per).age 的写法,从指针拿到 struct 结构本身。因为运算符优先级问题,不能写成*per.age,会将per.age看成是一个指针,然后取其值。
  • 说明3:结构体类型跟数组不一样,类型标识符本身并不是指针,所以传入时,指针必须写成 &p1。
  • 说明4: addAge() 内部对 struct 结构的操作,就会反映到函数外部。

3 -> 操作符

前面例子中,(*per).age 的写法很麻烦,C 语言就引入了一个新的箭头运算符( -> ),可以从结构体指针上直接获取属性,大大增强了代码的可读性。

C
void addAge(struct Person * per) {
    per->age = per->age + 1;  //使用结构体指针访问指向对象的成员
}

C
struct Student {
    char name[20];
    int age;
    char gender;
};

int main() {
    //打印结构体信息
    struct Student s = {"张三", 20, 'M'};

    //方式1:.为结构成员访问操作符
    printf("name = %s,age = %d,gender = %c\n", s.name, s.age, s.gender);

    struct Student *ps = &s;
    //方式2:.为结构成员访问操作符
    printf("name = %s,age = %d,gender = %c\n", (*ps).name, (*ps).age, (*ps).gender);

    //方式3:->操作符
    printf("name = %s,age = %d,gender = %c\n", ps->name, ps->age, ps->gender);

    return 0;
}

总结:如果指针变量p指向一个结构体变量stu,以下3种用法等价:

C
① stu.成员名    stu.num
② (*p).成员名   (*p).num
③ p->成员名     p->num

4.4 指向结构体数组的指针

C
struct Person {
    int id;
    char name[20];
};

int main() {

    struct Person per;
    struct Person arr[5];

    struct Person *p,*q;

    p = &per;  //指向单个结构体变量
    q = arr;   //指向结构体数组

    return 0;
}

5、结构体在数据结构中的应用

6、共用体类型(union)

6.1 共用体概述

有时需要一种数据结构,不同的场合表示不同的数据类型。比如,如果只用一种数据结构表示学生的“成绩”,这种结构就需要有时是整数(80、90),有时是字符('A'、'B'),又有时是浮点数(80.5、60.5)。

C 语言提供了共用体类型(Union 结构),用来自定义可以灵活变更的数据结构。它内部可以包含各种属性,但同一时间只能有一个属性,因为所有属性都保存在同一个内存地址,后面写入的属性会覆盖前面的属性。这样做的最大好处是节省内存空间

“共用体”与“结构体”的定义形式相似,但它们的含义是不同的。

  • 结构体变量所占内存长度是各成员占的内存长度之和;每个成员分别占有其自己的内存单元。
  • 共用体变量所占的内存长度等于最长的成员的长度;几个成员共用一个内存区。

6.2 声明共用体

格式:

C
union 共用体类型名称{
    数据类型 成员名1;
    数据类型 成员名2;
    …
    数据类型 成员名n;
};

union Data {
    short m;
    float x;
    char c;
};

上例中, union 命令定义了一个包含三个属性的数据类型 Data。虽然包含三个属性,但是同一时间只能取到一个属性。最后赋值的属性,就是可以取到值的那个属性

6.3 声明共用体变量

方式1:先定义共用体类型,再定义共用体变量

C
union Data {
    short m;
    float x;
    char c;
};

//声明共用体变量
union Data a, b;

方式2:定义共用体类型的同时定义共用体变量

C
union Data {
    short m;
    float x;
    char c;
} a, b;

方式3:直接定义共用体类型变量

C
union {
    short m;
    float x;
    char c;
} a, b;

6.4 调用共同体变量的成员

正确的方式

方式1:

C
union Data a;
a.c = 4;

方式2:声明共同体变量的同时,给任一成员赋值

C
union Data a = {.c = 4};

方式3:声明共同体变量的同时,给首成员赋值

C
union Data a = {8};

6.5 ->操作符

Union 结构也支持指针运算符 -> 。

C
union evaluation { //评价
    int score;
    float grade;
    char level;
};

int main() {
    union evaluation e;
    e.score = 85;

    union evaluation *p;
    p = &e;
    printf("%d\n", p->score); // 85

    return 0;
}

6.6 补充说明

  • 不能对共用体变量名赋值,也不能企图引用变量名来得到一个值。只能引用共用体变量中的成员。
  • C99允许同类型的共用体变量互相赋值。

7、typedef 的使用(熟悉)

7.1 为什么使用typedef

C语言允许为一个数据类型起一个新的别名,就像给人起“绰号”一样。

起别名的目的不是为了提高程序运行效率,而是为了编码方便。例如,有一个结构体的名字是 student,定义一个结构体变量stu1,代码如下:

C
struct student stu1;

struct 看起来就是多余的,但不写又会报错。如果为 struct student起了一个别名 Student,书写起来就简单了:

C
Student stu1;

这种写法更加简练,意义也非常明确,不管是在标准头文件中还是以后的编程实践中,都会大量使用这种别名。

7.2 使用格式

用typedef声明数组类型、指针类型,结构体类型、共用体类型等,使得编程更加方便。

1、为某个基本类型起别名

typedef 命令用来为某个类型起别名

C
typedef 类型名 别名;

2、为结构体、共用体起别名

为 struct、union等命令定义的复杂数据结构创建别名,从而便于引用。

3、为指针起别名

typedef 可以为指针起别名。

4、为数组类型起别名

typedef 也可以用来为数组类型起别名。

5、为函数起别名

typedef 为函数起别名的写法如下

7.3 小 结

(1) typedef的方法实际上是为特定的类型指定了一个同义字(synonyms)。

(2) 用typedef只是对已经存在的类型指定一个新的类型名,而没有创造新的类型。

(3) typedef与#define是不同的。#define是在预编译时处理的,它只能作简单的字符串替换,而typedef是在编译阶段处理的,且并非简单的字符串替换。

(4) 当不同源文件中用到同一类型数据(尤其是像数组、指针、结构体、共用体等类型数据)时,常用typedef 声明这些同一的数据类型。

技巧:可以把所有的typedef名称声明单独放在一个头文件中,然后在需要用到它们的文件中用#include指令把它们包含到文件中。这样编程者就不需要在各文件中自己定义typedef名称了。

(5) 使用typedef名称有利于程序的通用与移植。有时程序会依赖于硬件特性,用typedef类型就便于移植。

你可能感兴趣的:(尚硅谷C语言笔记,c语言,笔记)