目录
结构体
在 C 语言中,char、int、float 等属于系统内置的基本数据类型,往往只能解决简单的问题。当遇到比较复杂的问题时,只使用基本数据类型是难以满足实际开发需求的。因此,C 语言允许用户根据实际项目需求,自定义一些数据类型,并且用它们来定义变量。
前面文章中介绍了 C 语言的多种数据类型,例如,整型、字符型、浮点型、数组、指针等。但是在实际开发中,只有这些数据类型还难以胜任复杂的程序设计。例如,在员工信息管理系统中,员工的信息就是一类复杂的数据。每条记录中都包括员工的姓名、年龄、工号、工资等信息。姓名为字符数组,年龄为整型,工号为整型,工资为整型。
对于这类数据,显然不能使用数组存储,因为数组各个元素的类型都是相同的。为了解决这个问题,C 语言提供了一种组合数据类型—结构体。
结构体是一种组合数据类型,由用户自己定义。结构体类型中的元素既可以是基本数据类型,也可以是结构体类型。
定义结构体类型的一般格式为:
struct 结构体名
{
成员列表
};
成员列表由多个成员组成,每个成员都必须作类型声明,成员声明的格式为:
数据类型 成员名;
下面来看一个具体的例子。
struct Employee
{
char name[8];
int age;
int id;
int salary;
};
这段代码中 struct 是关键字,Employee 是结构体名,struct Employee 表示一种结构体类型。
该结构体中有 4 个成员,分别为 name、age、id、salary,使用这种结构体类型就可以表示员工的基本信息。
定义结构体变量
C 语言中,定义结构体变量的方式有三种。
第一种,先定义结构体类型,再定义结构体变量
一般形式为:
struct 结构体名
{
成员列表
};
struct 结构体名 变量名;
例如:
struct Employee
{
char name[8];
int age;
int id;
int salary;
};
struct Employee emp;
这种方式和基本类型的变量定义方式相同,其中 struct Employee 是结构体类型名,emp 是结构体变量名。
第二种,在定义结构体类型的同时定义变量
一般形式为:
struct 结构体名
{
成员列表
}变量名;
例如:
struct Employee
{
char name[8];
int age;
int id;
int salary;
}emp;
这种方式将结构体类型定义与变量定义放在一起,可以直接看到结构体的内部结构,比较直观。
第三种,直接定义结构体变量,不需指定结构体名
一般形式为:
struct
{
成员列表
}变量名;
例如:
struct
{
char name[8];
int age;
int id;
int salary;
}emp;
这种方式由于没有指定结构体名,显然不能再使用该结构体类型去定义其他变量,在实际开发中很少用到。
结构体初始化和引用
1.结构体变量初始化
在 C 语言中,结构体变量初始化,本质上是对结构体变量中的成员进行初始化,方式是使用「{ }」在初始化列表中对结构体变量中各个成员进行初始化,例如:
struct Employee emp={"rupeng",20,1,10000}
或
struct Employee
{
char name[8];
int age;
int id;
int salary;
}emp={"rupeng",20,1,10000};
编译器会将「rupeng」、20、1、10000 按照顺序依次赋值给结构体变量 emp 中的成员 name、age、id、salary。
2.引用结构体变量
引用结构体变量的本质,是引用结构体变量中不同类型的成员,引用的一般形式为:
结构体变量名.成员名;
例如,emp.name 表示引用 emp 变量中的 name 成员,emp.id 表示引用 emp 变量中的 id 成员。
其中「.」是成员运算符,它的优先级在所有运算符中是最高的。
下面通过例子来了解结构体变量的初始化和引用。
例:结构体变量的初始化和引用。
01 #include<stdio.h>
02 struct Employee
03 {
04 char name[8];
05 int age;
06 int id;
07 int salary;
08 };
09 int main(void){
10 struct Employee emp={"rupeng",20,1,10000};
11 printf("%s\n",emp.name);
12 printf("%d\n",emp.age);
13 printf("%d\n",emp.id);
14 printf("%d\n",emp.salary);
15 getchar();
16 return 0;
17 }
运行结果如图
【程序分析】
(1)第 2~8 行,定义结构体类型 struct Employee,成员分别为 name、age、id、salary。
(2)第 10 行,定义 struct Employee 类型变量 emp,并使用初始化列表对成员进行初始化。
(3)第 11~14 行,分别输出 emp 中各个成员的值。
除了采用初始化列表,还可以使用赋值运算符,对成员进行初始化。
结构体类型大小
结构体类型大小,也即结构体类型所占字节数,就是结构体各个成员类型所占字节数的总和,例如:
struct Employee
{
char name[8]
int age;
int id;
int salary;
};
其中 name 是长度为 8 的字符数组,占 8 个字节,age 是 int 类型,占 4 个字节,id 是 int 类型,占 4 个字节,salary 是 int 类型,占 4 个字节。因此,struct Employee 类型所占字节数为 8+4+4+4=20 个字节。
下面通过例子来了解一下。
例:计算结构体类型大小。
01 #include<stdio.h>
02 struct Employee
03 {
04 char name[8];
05 int age;
06 int id;
07 int salary;
08 };
09 int main(void)
10 {
11 printf("%d\n",sizeof(struct Employee));
12 getchar();
13 return 0;
14 }
运行结果如图
程序分析:
1.第 2~8 行,定义结构体类型 struct Employee。
2.第 11 行,使用 sizeof 函数计算 struct Employee 类型所占字节数,并输出结果 20。
结构体指针
指向结构体变量的指针就是结构体指针,如果指针变量中保存一个结构体变量的地址,则这个指针变量指向该结构体变量,需要注意的是指针变量的类型必须和结构体变量的类型相同。
定义结构体指针变量的一般形式为:
struct 结构体名 *指针变量名
例如:
struct Employee emp;
struct Employee * p_emp=&emp;
其中 emp 为结构体变量,p_emp 为结构体指针,将 emp 取地址赋给指针变量 p_emp,表示 p_emp 指向 emp。
在 C 语言中,通过结构体指针 p 也可以引用结构体中的成员,主要有以下两种方式:
(1)(*p).成员名;
(2)p→ 成员名。
例如:
struct Employee * p_emp=&emp;
(*p_emp)表示指向的结构体变量 emp,(*p_emp).age 表示指向的结构体变量 emp 中的成员 age。注意,「.」运算符优先级是最高的,(*p_emp)两侧的括号不能省略。
为了简化操作,C 语言允许将「(*p).成员名」用「p-> 成员名」替换,(*p_emp).age 等价于 p_emp->age,「->」称为指向运算符。
typedef 类型另起名函数
在 C 语言中,除了使用 C 语言提供的标准类型名:char、int、double 以及自定义的结构体类型,还可以使用 typedef 关键字指定一个新的类型名来代替已有的类型名,相当于给已有类型另起名。类似于现实生活中,给一个人起外号一样。
typedef 的一般使用形式为:
typedef 原类型名 新类型名
例如:
typedef int integer
其中 integer 是 int 类型的别名
结构体复制
在 C 语言中,允许相同类型的结构体变量之间相互赋值。
例如:
t_Employee emp={"rupeng",20,1,10000};
t_Employee emp2=emp;
执行 emp2=emp,会将结构体变量 emp 中各个成员的值复制一份到变量 emp2 各个成员中。和基本类型变量的赋值规则相同,emp2 是 emp 的一个复制体。这种赋值方式,被称为「结构体复制」。
内存管理
内存管理
在 C 语言中,当一个程序被加载到内存中运行时,系统会为该程序分配一块独立的内存空间,并且这块内存空间又可以再被细分为很多区域,如栈区、堆区、静态区、全局区等。本章将介绍内存空间中常用的区域:栈区、堆区。
栈区、堆区
在 C 语言程序中,栈区、堆区是最常使用的内存区域。
栈区:保存局部变量。存储在栈区的变量,在函数执行结束后,会被系统自动释放。
堆区:由 malloc、calloc、realloc 等函数分配内存。其生命周期由 free 函数控制,在没有被释放之前一直存在,直到程序运行结束。
栈内存
定义在函数内部的局部变量,都保存在栈区。栈区的特点是:函数执行结束后,系统会「自动回收」局部变量所对应的内存空间。所谓的「自动回收」其实是操作系统将这块栈内存又分配给其他函数中的局部变量使用。
可以将栈区比作餐厅,局部变量比作客人,局部变量对应的栈内存比作餐具。客人吃饭时使用的餐具,在客人离开后由餐厅负责回收、清洗,然后再给其他客人使用。同理,局部变量与栈内存的关系也是如此,当定义局部变量时,系统会在栈区为其分配一块内存空间,当函数执行结束后系统负责回收这块内存,再分配给其他局部变量使用。
由于局部变量在函数执行结束后,会被系统「自动回收」并分配给其他函数中的局部变量使用。因此,在 C 语言程序中,不能将局部变量地址作为函数返回值,否则会出现一些错误结果。
使用 malloc 系列函数分配的内存都属于堆区,使用完后需调用 free 函数将其释放,否则可能会造成内存泄漏。
举个例子,堆区相当于自己家,malloc 分配的堆内存相当于盘子。在家里吃饭时使用的盘子,吃完后必须手动进行清洗,否则盘子将不能再使用。同理,堆内存也是如此,使用完毕后,需要调用 free 函数手动释放,否则这块堆内存将无法再次被使用。
malloc 函数
【函数原型】
void *malloc(int size);
【头文件】
#include <stdlib.h>
【形参列表】
size:分配多少个字节。
【函数功能】
申请指定大小的堆内存。
【返回值】
如果分配成功则返回指向被分配内存的指针,否则返回空指针 NULL。
注意:void*表示「不确定指向类型」的指针,使用前必须进行强制类型转换,将 void*转化为「确定指向类型」的指针。
free 函数
【函数原型】
void free(void* ptr);
【头文件】
#include <stdlib.h>
【形参列表】
ptr:指向要被释放的堆内存。
【函数功能】
释放 ptr 指向的内存空间。
栈内存由系统自动分配、释放。常见形式如函数形参、局部变量等。栈内存比较小,在 VS2012 中,栈内存默认最大为 1Mb,如果局部变量占用的栈内存过大,会发生栈溢出。