《C语言程序设计》读书笔记(第11章——结构体与共用体)

11.1 结构体

11.1.1 概述

有时候需要将不同类型的数据组合成一个有机的整体,以便于引用,所以出现了结构体。类似于高级语言如Java中的类。例如,一个学生有学号/姓名/性别/年龄/地址等属性:

int num;
char name[20];
char sex;
int age;
char addr[30];

定义一个结构体的语法如下:

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

成员表列由若干个成员组成,每个成员都是该结构的一个组成部分。对每个成员也必须作类型说明,其形式为:

类型说明符 成员名;

成员名的命名应符合标识符的书写规定。例如:

struct student {
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char addr[30];
}

而结构体就是把它们放到一起引用,其实刚开始学C语言可能并不好学这种结构,感觉和Java类有些类似,那些都是该类下的属性,学过面向对象的Java后就能找到相似之处,加深理解。例如:

class Student {
    int num;
    String name;
    String sex;
    int age;
    float score;
    String addr;
}

11.1.2 定义结构体类型变量的方法

声明结构体变量有如下三种方式:

  • 第一种方法:先声明结构体类型,再定义变量名。
// 声明结构体类型
struct 结构体名 {
    成员表列
};
// 定义变量名
struct 结构体名 变量名,变量名,...

实例:

// 声明结构体类型
struct student {
     int num;
     char name[20];
     char sex;
     int age;
     float score;
     char addr[30];
 };
// 定义变量
struct student stu1,stu2;

在定义了结构体变量后,系统会为之分配内存单元。stu1stu2在内存中各占(4+20+1+4+4+30=67)字节。

  • 第二种方法:在声明结构体类型的同时并定义变量。
struct 结构体名 {
    成员表列
} 变量名表列;

实例:

// 声明结构体类型的同时并定义变量
struct student {
     int num;
     char name[20];
     char sex;
     int age;
     float score;
     char addr[30];
 } stu1,stu2;
  • 第三种方法:直接定义结构体类型变量,即不出现结构体名。
struct {
    成员表列
} 变量名表列;

实例:

// 不出现结构体名
struct {
     int num;
     char name[20];
     char sex;
     int age;
     float score;
     char addr[30];
 } stu1,stu2;

列表成员可以是一个结构,例如,根据下图给出结构体定义:

img

struct date
{
	int year;
	int month;
	int day;
};

struct student
{
	int num;
	char name[20];
	char sex;
	struct date birthday;
	float score;
} stu1;

如果想要访问结构体中的成员,基本语法如下:

结构体变量名.成员名;

如果成员本身又属于一个结构体类型,则要用若干个成员运算符,一级一级地找到最低的一级的成员。只能对最低级的成员进行赋值或存取以及运算。例如:

stu1.num; // 访问num
stu1.birthday.month; // 访问birthday中的month

对结构体变量的成员可以像普通变量一样进行各种运算。例如:

stu2.score=stu1.score;
sum=stu1.score+stu2.score;
stu1.age++;
++stu2.age;

可以引用结构体变量成员的地址,也可以引用结构体变量的地址。但不能用以下语句来整体读入结构体变量:

scanf("%d, %d, %c, %d, %f, %s", &student1);

结构体变量的地址主要用作函数参数,传递的是结构体变量的地址。

11.1.3 结构体变量的初始化

初始化的语法如下:

// 声明结构体类型
struct 结构体名{
    成员表列
};
// 定义结构体变量并进行初始化
struct 结构体名 变量名 = {...};

// 注意,下面的语法是错误的
struct 结构体名 变量名;
变量名 = {...};

可以直接对结构体变量进行初始化,例如:

#include <stdio.h>

struct student
{
	int num;
	char *name;
	char sex;
	float score;
} student1,student2={102, "张三", 'M', 99.5};

int main()
{
	student1=student2;
	
	printf("编号:%d\n姓名:%s\n性别:%c\n分数;%1.1f\n", student1.num, student1.name, student1.sex, student1.score);
	printf("\n");
	printf("编号:%d\n姓名:%s\n性别:%c\n分数;%1.1f\n", student2.num, student2.name, student2.sex, student2.score);
} 

img

11.1.4 结构体数组

11.1.4.1 概述

一个结构体变量可以存放一组数据(如一个学生的学号、姓名、成绩等信息)。

如果有50个学生的数据需要参加运算,就该使用数组来存储,这就是结构体数组。

结构体数组里面的每一个元素都是一个结构体类型的变量。

11.1.4.2 定义结构体数组

与定义结构体变量的方法相仿,只需要说明其为数组即可。语法如下:

// 声明结构体类型
struct 结构体名 {
    成员表列
};
// 定义结构体数组
struct 结构体名 数组名[数组长度];

例如:

struct student {
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char addr[30];
};
struct student stu1[10];// 表示一个结构体数组,里面有10个元素
11.1.4.3 结构体数组的初始化

结构体数组初始化的一般语法如下:

// 声明结构体类型
struct 结构体名 {
    成员表列
};
// 定义结构体数组
struct 结构体名 数组名[数组长度] = {{...}, {...}, {...}};

即先声明结构体类型,然后定义数组为该结构体类型,在定义数组时初始化。例如:

struct student {
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char addr[30];
};
struct student stu[2] = {
    {121, "张三", 'm', 18, 56.5, "北京市"},
    {122, "李四", 'f', 20, 68, "上海市"}
};

11.1.5 结构体指针变量的说明和使用

11.1.5.1 指向结构体类型数据的指针
11.1.5.1.1 结构体指针变量的声明

一个指针变量当用来指向一个结构变量时,称之为结构指针变量。结构指针变量中的值是所指向的结构变量的首地址。通过结构指针即可访问该结构变量,这与数组指针和函数指针的情况是相同的。结构指针变量说明的一般形式为:

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

例如:

// 声明结构体类型
struct student {
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char addr[30];
};
// 定义结构体指针变量
struct student *stu;
11.1.5.1.2 结构体指针变量的赋值

结构指针变量必须要先赋值后才能使用。

注意:赋值是把结构变量的首地址赋予该指针变量,不能把结构名赋予该指针变量。

// 声明结构体类型
struct student {
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char addr[30];
};
// 定义结构体变量
struct student stu1,stu2;
// 定义结构体指针变量
struct student* stu;
// 进行赋值
stu = &stu1;// 这是正确的,因为stu1是结构体变量
stu = &student;// 这是错误的,因为student是结构体名称

结构名和结构变量是两个不同的概念,结构名只表示一个结构形式,编译系统并不对其分配内存空间。

只有当某结构变量被说明为这种类型的结构时,才对该变量分配存储空间。

结构指针变量,能够更加方便的访问结构变量的各个成员。

11.1.5.1.3 结构体指针变量的访问

其访问的一般形式为:

(*结构体指针变量名).成员名;

// 或者

结构体指针变量->成员名;

例如:

(*stu).num;

// 或者

stu->num;

实例:

#include <stdio.h>

struct student {
    int num;
    char *name;
    char sex;
    float score;
} student1 = {101, "张三", 'M', 55.4};

int main() {
    // 结构指针变量的声明
    struct student* stu;
    // 结构指针变量的赋值
    stu = &student1;
    // 结构指针变量的访问
    printf("%d %s %c %1.1f\n", (*stu).num, (*stu).name, (*stu).sex, (*stu).score);// 101 张三 M 55.4
    printf("%d %s %c %1.1f\n", stu->num, stu->name, stu->sex, stu->score);// 101 张三 M 55.4
}
11.1.5.2 结构体指针变量作为函数参数

可以将一个结构体变量的值传递给一个函数,有如下三种方法:

  • 用结构体变量的成员作参数
  • 用结构体变量作实参
  • 用指向结构体变量(或数组)的指针作实参,将结构体变量(或数组)的地址传给形参

实例,用结构体变量作为函数形参:

#include <stdio.h>

// 声明结构体类型
struct student {
    int num;
    char *name;
    char sex;
    float score;
};

// 将结构体变量作为函数形参
void print(struct student stu) {
    printf("num=%d\tname=%s\tsex=%c\tscore=%f", stu.num, stu.name, stu.sex, stu.score);
}

int main() {
    // 定义结构体变量并为结构体变量赋值
    struct student stu = {121, "张三", 'm', 85.5};
    // 将结构体变量作为实参传入函数
    print(stu);// num=121 name=张三       sex=m   score=85.500000
}

实例,用结构体指针变量作为函数形参:

#include <stdio.h>

// 声明结构体类型
struct student {
    int num;
    char *name;
    char sex;
    float score;
};

// 将结构体指针变量作为函数形参
void print(struct student* stu) {
    // 既可以通过(*stu).name访问,也可以通过stu->name进行访问
    printf("num=%d\tname=%s\tsex=%c\tscore=%f", (*stu).num, (*stu).name, stu->sex, stu->score);
}

int main() {
    // 定义结构体变量并为结构体变量赋值
    struct student stu = {121, "张三", 'm', 85.5};
    // 将结构体变量作为实参传入函数
    print(&stu);// num=121 name=张三       sex=m   score=85.500000
}

11.1.6 动态存储分配

11.1.6.1 概述

由于数组的长度是必须先定义好的,在整个程序中固定不变。C语言中不允许动态数组类型。

例如注意的:int a[n];用变量表示长度,想对数组进行大小进行动态说明,是不行的。

在实际中,所需要的内存空间往往取决了用户实际输入的数据,而不清楚实际大小,无法预先得知。

所以C语言提供了一些内存管理函数,来动态分配内存空间,也可以把不需要的内存空间回收,有效管理内存资源。也是为后面的链表等数据结构打下基础。

常用的内存管理函数有如下三个:

  • 分配内存空间函数malloc和calloc,其中malloc用得比较多。
  • 释放内存空间函数free,也常用。

在使用上述三个函数前,需要引入

#include <stdlib.h>
11.1.6.2 malloc函数

函数原型为void *malloc(unsigned int size);,调用语法如下:

(类型说明符*)malloc(size);
  • 类型说明符”表示把该区域用于何种数据类型。
  • (类型说明符*)表示把返回值强制转换为该类型指针。
  • size”是一个无符号数。

该方法的功能是:在内存的动态存储区中分配一块长度为"size"字节的连续区域。函数的返回值为该区域的首地址。例如:

// 表示分配100个字节的内存空间,并强制转换为字符数组类型,函数的返回值为指向该字符数组的指针,把该指针赋予指针变量pc
char* pc = (char*)malloc(100);
11.1.6.3 calloc函数

函数原型为void *calloc(unsigned n, unsigned size);,调用语法如下:

(类型说明符*)calloc(n, size);
  • (类型说明符*)用于强制类型转换。
  • calloc函数与malloc 函数的区别仅在于一次可以分配n块区域。

该方法的功能是:在内存动态存储区中分配n块长度为“size”字节的连续区域。函数的返回值为该区域的首地址。例如:

// 其中的sizeof(struct stu)是求stu的结构长度。因此该语句的意思是:按stu的长度分配2块连续区域,强制转换为stu类型,并把其首地址赋予指针变量ps。
struct stu* ps = (struct stu*)calloc(2, sizeof(struct stu));
11.1.6.4 free函数

函数原型为void free(void *p);,调用语法如下:

free(void* ptr);

该方法的功能是:释放ptr所指向的一块内存空间,ptr是一个任意类型的指针变量,它指向被释放区域的首地址。被释放区应是由malloccalloc函数所分配的区域。

11.1.7 用typedef定义类型

11.1.7.1 基本使用
11.1.7.1.1 用于基本类型

typedef关键字用来声明新的类型名来替代已有的类型名。语法如下:

typedef 旧类型名 新类型名;

例如:

typedef int INTEGER;
// 其中int是已有的类型,INTEGER是新的类型名
// 声明了INTEGER为整型

实例:

#include <stdio.h>
#include <stdlib.h>

typedef int INTEGER;

int main() 
{
	INTEGER i=1;
	int j=2;
	
	printf("%d %d\n", i, j);
} 
11.1.7.1.2 用于结构体类型

基本语法如下:

typedef struct [结构体名] {
    成员表列
} 结构体变量名;// 其中结构体名可以省略

例如:

#include <stdio.h>

// 声明结构体类型
typedef struct date {
    int year;
    int month;
    int day;
} DATE;

int main() {
    // 以前的使用方式:
    // struct date DATE;
    // 现在的使用方式:
    DATE d;
    d.year = 2021;
    d.month = 12;
    d.day = 25;

    printf("%d-%d-%d\n", d.year, d.month, d.day);// 2021-12-25
}
11.1.7.1.3 用于数组类型

基本语法如下:

typedef 类型说明符 数组名[数组长度];

例如:

#include <stdio.h>
#include <stdlib.h>

typedef int NUM[100];

int main() {
    NUM num = {0};
    printf("%d\n\n", sizeof(num));
    return 0;
} 
11.1.7.1.4 用于字符指针类型

基本语法如下:

typedef char* 名字;

例如:

#include <stdio.h>

typedef char* String;

int main() {
    String str = "Hello World!";
    printf("%s", str);// Hello World!
}
11.1.7.1.5 用于指向函数的指针类型

基本语法如下:

typedef 返回值类型 (*函数名)();

例如:

#include <stdio.h>

// 声明指向函数的指针类型
typedef int (*fun)(int, int);

int max(int x, int y) {
    return x > y ? x : y;
}

int main() {
    // 将真正的函数名进行赋值
    fun f = max;
    // 调用函数,获取返回值结果
    int result = f(1, 2);
    printf("%d", result);
}
11.1.7.2 用typedef定义类型的步骤

步骤:

  • 先按定义变量的方法写出定义体(如:int i
  • 将变量名换成新类型名(如:将i换成COUNT
  • 在最前面加typedef(如:typedef int COUNT
  • 然后可以新类型名去定义常量(如:COUNT i,j;

例如:

#include <stdio.h>


// int i;
// int COUNT;
typedef int COUNT;

int main() {
    COUNT num = 123;
    printf("%d", num);// 123
}
11.1.7.3 关于typedef的一些说明

typedef可以声明各种类型名,但不能用来定义变量,如typedef i;,这是错误的。

typedef只是对已经存在的类型增加一个类型名,而没有创造新的类型。

当不同源文件用到同一类型数据时,常用typedef声明一些数据类型,把它们单独放在一个文件中,然后在需要用到它们的文件中用#include命令把它们包含进来。

使用typedef有利于程序的通用与移植。

typedef#define有相似之处,例如:typedef int COUNT; #define COUNT int的作用都是用COUNT代表int。但是,它们二者是不同的。

#define是在预编译时处理的,它只能作简单的字符串替换,而typedef是在编译时处理的。实际上它并不是作简单的字符串替换,而是采用如同定义变量的方法那样来声明一个类型。

关于typedef#define的区别:

typedef (int*) p1;

// 和

#define p2 int*

//注意,一个有分号,一个没有分号
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值