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;
在定义了结构体变量后,系统会为之分配内存单元。stu1
和stu2
在内存中各占(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;
列表成员可以是一个结构,例如,根据下图给出结构体定义:
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);
}
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
是一个任意类型的指针变量,它指向被释放区域的首地址。被释放区应是由malloc
或calloc
函数所分配的区域。
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*
//注意,一个有分号,一个没有分号