C语言-基础入门-学习笔记(13):结构体
一、结构体
结构体在C语言中用于处理多个单一数据组成的数据集合。
1. 声明结构体类型
一个结构体可以将多个单一信息结合在一起作为一个整体来使用。使用结构体时,首先要声明需要的结构体类型,形式如下:
struct 结构体名{
数据类型1 结构体成员1;
数据类型2 结构体成员2;
数据类型3 结构体成员3;
···
};
每一个结构体可以包含很多成员,每一个成员都有一个数据类型,这些成员可以被声明为除该结构体类型本身外的任意有效数据类型。例如:
struct personal_infor{
char name[SIZE_N];
int age;
char gender;
char profession[SIZE_P];
char address[SIZE_A];
char telephone[SIZE_T];
};
注意,这个结构体的数据类型名称为struct personal_infor,而不是personal_infor。
2. 定义结构体变量
声明了结构体类型后,便可以使用该类型来定义结构体变量。定义方式有三种:
- 声明结构体类型,并定义结构体变量
struct personal_infor{ //声明结构体数据类型
char name[SIZE_N];
int age;
char gender;
···
};
struct personal_infor Raul,Philip; //定义结构体变量
struct personal_infor Joe; //定义结构体变量
这种形式的定义方式可以多次定义结构体变量。
在定义结构体变量时,一定要struct+结构体变量名的形式。
struct personal_infor{ //声明结构体数据类型
char name[SIZE_N];
int age;
char gender;
···
}Raul,Philip; //定义结构体变量
struct personal_infor Joe; //定义结构体变量
- 不声明结构体类型,直接定义结构体变量
struct { //声明结构体数据类型
char name[SIZE_N];
int age;
char gender;
···
}Raul,Philip,Joe; //定义三个结构体变量
由于这种形式并没有声明结构体数据类型,因此无法像第一种形式那样在其他语句中多次定义结构体变量。
3. 使用typedef得到简化后的结构体类型名,再定义结构体变量
typedef struct personal_infor{
char name[SIZE_N];
int age;
char gender;
···
}personal_infor;
也可以省略该结构体名,例如:
typedef struct{
char name[SIZE_N];
int age;
char gender;
···
}personal_infor;
如果要在结构体内定义指向本结构体类型空间的指针时,只能使用第一种类型。
3. 结构体的存储形式
结构体数据在存储时是以其中字节长度最大的成员的字节数为基本单位对齐的。
结构体变量所占的字节空间数,即结构体数据类型的字节长度,一般等于各个结构体成员的字节长度和。但如果结构体成员的字节长度长短不一时,由于内存对齐的影响,会使结构体额外占用一些空间,从而使其内存空间大于各个结构体成员的字节长度总和。
范例1
#include <stdio.h>
//使用typedef定义结构体类型
typedef struct my_data_type_A{
char a[3];
int b;
char c;
}data_type_A;
//使用缺少struct名的形式定义结构体类型
struct{
int a;
double b;
}v;
int main(void){
//使用完整形式定义结构体类型
struct data_type_B{
char a[3];
char c;
int b;
};
printf("sizeof(data_type_A) = %d\n",sizeof(data_type_A));
printf("sizeof(v) = %d\n",sizeof(v));
printf("sizeof(struct data_type_B) = %d\n",sizeof(struct data_type_B));
return 0;
}
所谓对齐是指将内存以一个固定的字节长度作为最小单位分块。
例如最后一个结构体,char a占3个字节,char b占一个字节,因此接挨着char a;int b为4个字节,空闲位置刚好是其对齐的最小单位,即4字节开始的位置,因此也接挨着char b,一共用了8字节。
二、结构体的应用
在访问结构体变量时,必须通过访问其成员来实现。
1. 访问结构体成员
成员操作符是一个二元操作符,前面的操作符为结构体变量,后面的操作符为结构体成员名。标准形式如下:
结构体名.结构体成员名;
Raul.name //访问raul的名字
Raul.age //访问raul的年龄
Raul.gender //访问raul的性别
范例2
#include <stdio.h>
#define SIZE_NAME 30
typedef struct my_per_infor{
char name[SIZE_NAME];
int age;
char gender;
}per_infor;
int main(void){
per_infor raul; //定义结构体变量
per_infor joe; //定义结构体变量
printf("Please input Raul's information:\n");
gets(raul.name);
scanf("%d %c",&raul.age,&raul.gender);
printf("raul.name\t = %s\n",raul.name);
printf("raul.age\t = %d\n",raul.age);
printf("raul.gender\t = %c\n",raul.gender);
joe.age = 31;
printf("joe.name\t = %s\n",joe.name); //未赋值成员
printf("joe.age\t\t = %d\n",joe.age);
printf("joe.gender\t = %c\n",joe.gender); //未赋值成员
return 0;
}
未对成员进行赋值时,其内存会是一个混乱的状态。
2. 初始化结构体变量
为防止其处于一个混乱的状态,所以需要对其进行初始化。结构体与数组初始化方法类似,可以使用一个数值序列依次为结构体成员赋值。例如:
per_infor raul = {"Raul Wang",25,'M'};
也可以只初始化结构体变量的部分成员,未被初始化的其余变量自动清零。
per_infor raul =
{
//.name = "Raul Wang", // 初始化时去掉了名字元素,输出时为0
.age = 25,
.gender = 'M'
};
如果要将结构体变量清零,只需将其第一个成员内存初始化为全零即可。
per_infor raul = {0};
范例3
#include <stdio.h>
typedef struct my_data_t{
int a;
double b;
char c;
char d[20];
}data_t;
int main(void){
data_t v = {1,2.0,'c',"OK"};
data_t x = {0};
printf("v.a\t = %d\n",v.a);
printf("v.b\t = %f\n",v.b);
printf("v.c\t = %c\n",v.c);
printf("v.d\t = %s\n",v.d);
printf("x.a\t = %d\n",x.a);
printf("x.b\t = %f\n",x.b);
printf("x.c\t = %c\n",x.c);
printf("x.d\t = %s\n",x.d);
return 0;
}
3. 结构体变量的赋值
data_t a = {2,2.0,'c',"DATA"};
data_t b = a;
这种简单的复制方式成为浅赋值,不适用于带指针的变量,容易使得两个结构体变量的成员指向同一地址。
深赋值是指如果结构体含有指针型成员,复制时不是简单地复制该成员的值,而是先为该指针成员申请新的内存空间,再进行复制。
int deep_copy(struct_name * src,struct_name * dest){
//非指针部分的复制。为简化,假定所有非指针成员类型都为int型
dest->m1 = src->m1;
dest->m2 = src->m2;
//针部分的复制。为简化,假定所有非指针成员类型都为(int *)型
dest->mp1 = (int *)malloc(sizeof(int));
if(NULL == dest->mp1){
处理错误···
}
dest->mp1 = sre->mp1;
dest->mp2 = (int *)malloc(sizeof(int));
if(NULL == dest->mp2){
处理错误···
}
dest->mp2 = sre->mp2;
}
由于涉及到申请额外的内存空间,因此要对内存进行释放
free_deep_copy(struct_name *p){
free(p->mp1);
p->mp1 = NULL;
free(p->mp2);
p->mp2 = NULL;
}
如果我们在C语言中定义了一个结构体,然后申明一个该结构体类型的指针,那么我们要用指针取出结构体中的数据,就要用到“->”。
范例4
#include <stdio.h>
#include <stdlib.h>
const int TRUE = 1;
const int FALSE = 0;
typedef struct my_data_t{
int a;
int *b;
}data_t;
//将src的内容深复制到dest
int data_t_copy(data_t *dest,data_t *src){
dest->a = src->a; //复制非指针部分
dest->b = (int *)malloc(sizeof(int));//复制指针部分
if(NULL == dest->b){
printf("Error:can't allocate memory by malloc!\n");
return FALSE;
}
*(dest->b) = *(src->b);
return TRUE;
}
//释放data_t中的指针成员获得的内存
void data_t_free(data_t *p){
free(p->b);
p->b = NULL;
}
int main(void){
int x = 3;
data_t s1 = {2,&x};
data_t s2,s3;
//浅复制
s2 = s1;
printf("*(s1.b) = %d\n",*(s1.b));
printf("*(s2.b) = %d\n",*(s2.b));
*(s2.b) = 10;
printf("*(s1.b) = %d\n",*(s1.b));
printf("*(s2.b) = %d\n",*(s2.b));
//深复制
if(FALSE == data_t_copy(&s3,&s1)){
printf("Error: can't copy.\n");
return -1;
}
printf("*(s1.b) = %d\n",*(s1.b));
printf("*(s3.b) = %d\n",*(s3.b));
*(s3.b) = 20;
printf("*(s1.b) = %d\n",*(s1.b));
printf("*(s3.b) = %d\n",*(s3.b));
data_t_free(&s3);
return 0;
}
我们可以发现,浅复制之后,s1.b的变量值也发生了改变,因为两者指向的同一内存地址;但是用深复制以后,由于地址的不同,s1.b的值不发生变化。
三、结构体数组
结构体数组就是以结构体类型变量为数组元素的数组。
1. 定义结构体数组
- 声明结构体类型,并定义结构体数组
struct person_infor{
char name[20];
int age;
char gender;
}worker[10]; //定义结构体数组worker
struct person_infor farmer[10]; //定义结构体数组farmer
- 不声明结构体类型,直接定义结构体数组
struct{
char name[20];
int age;
char gender;
}worker[10];
- typedef方式
typedef struct my_person_infor{
char name[20];
int age;
char gender;
}person_infor;
person_infor worker[10]; //定义结构体数组worker
person_infor farmer[10]; //定义结构体数组farmer
2. 初始化结构体数组
初始化结构体数组可以采用初始化值序列的形式。
struct person_infor{
char name[20];
int age;
char gender;
}student[4] = {
{"Raul",25,'M'},
{"Joe",23,'F'},
{"Philip",21,'M'},
{"William",22,'M'}
};
这里每个数组对应花括号里面的三个成员要与结构体对应。
同样,也可以只初始化部分数组元素。
也可以不指定数组容量,则其数组容量由初始化列表的值的个数来决定。
在数组中可以不加内部的那4个花括号,直接写成员的值,编译器会依次将各个值赋值给相应的结构体元素的相应成员。
范例5
用结构体数组处理人物信息,输出最年轻男性的人物信息
#include <stdio.h>
#define SIZE_NAME 30
#define SIZE_STU 4
typedef struct my_per_infor{
char name[SIZE_NAME];
int age;
char gender;
}per_infor;
int main(void){
per_infor stu[SIZE_STU] = {
{"raul",25,'M'},
{"joe",29,'F'},
{"philip",22,'M'},
{"alan",28,'M'}
};
int max_index = 0;
int i = 0;
//比较所有结构体元素的age成员,将max_index指向age最大的成员
for(i=0;i<SIZE_STU;i++){
if('M' == stu[i].gender && stu[i].age < stu[max_index].age)
max_index = i;
}
printf("Output the information of the youngest man: \n");
printf("name\t = %s\n",stu[max_index].name);
printf("age\t = %d\n",stu[max_index].age);
printf("gender\t = %c\n",stu[max_index].gender);
return 0;
}
四、结构体指针
1. 指向结构体变量的指针
基本形式如下:
typedef struct{
int a;
char b;
double c;
}my_struct;
my_struct *p;
使用( * p).a、( * p).b、( * p).c可以分别访问成员a、b和c 。
范例6
指针操作符与成员操作符的优先级比较
#include <stdio.h>
typedef struct my_mytype_t{
int a;
int *b;
}mytype_t;
int main(void){
int x = 2;
mytype_t v = {3,&x};
mytype_t *p = &v;
printf("*(v.b) = %d\n",*(v.b));
printf("(*p).a = %d\n",(*p).a);
printf("*v.b = %d\n",*v.b);
return 0;
}
上述例子说明了成员操作符的优先级要大于指针操作符。
2. 指向堆空间的结构体指针
可以定义一个结构体指针变量并将其指向从堆上分配的内存空间。
范例7
#include <stdio.h>
#include <stdlib.h>
#define SIZE_NAME 30
//声明结构体变量
typedef struct my_per_infor{
char name[SIZE_NAME];
int age;
char gender;
}per_infor_t;
int main(void){
per_infor_t *p = NULL; //定义并初始化结构体指针
p = (per_infor_t *)malloc(sizeof(per_infor_t));//从堆上获取内存
if(NULL == p){
printf("Error in malloc().\n");
return -1;
}
printf("Please input the information:\n");
gets(p->name); //为name成员赋值
scanf("%d %c",&p->age,&p->gender);
printf("name\t = %s\n",p->name);
printf("age\t = %d\n",p->age);
printf("gender\t = %c\n",p->gender);
free(p);
p = NULL;
return 0;
}
3. 指向结构体数组元素的指针
将一个结构体数组元素的地址赋给一个指针变量,即可得到一个指向结构体数组元素的指针变量。
per_infor_t stu[10] ={..};
per_infor_t *p = stu;
范例8
#include <stdio.h>
#define SIZE_NAME 30
#define SIZE_STU 4
typedef struct my_per_infor{
char name[SIZE_NAME];
int age;
char gender;
}per_infor_t;
int main(void){
per_infor_t stu[SIZE_STU] = {
{"raul",25,'M'},
{"joe",29,'F'},
{"philip",22,'M'},
{"alan",28,'M'}
};
per_infor_t * p = stu;
while(p < stu + SIZE_STU)
printf("%s\n",(p++)->name);
return 0;
}
练习1
使用结构体将复数作为一个整体,实现复数的加减运算
#include <stdio.h>
typedef struct my_complex_t{
int real; //实数部分
int imag; //复数部分
}complex_t;
//复数相加函数
complex_t add(const complex_t a,const complex_t b){
complex_t r;
r.real = a.real + b.real;
r.imag = a.imag + b.imag;
return r;
}
//复数相减函数
complex_t sub(const complex_t a,const complex_t b){
complex_t r;
r.real = a.real - b.real;
r.imag = a.imag - b.imag;
return r;
}
void output(const complex_t a){
if(a.imag >= 0)
printf("%d + i%d\n",a.real,a.imag);
else
printf("%d - i%d\n",a.real,-a.imag);
}
int main(void){
complex_t x = {2,7};
complex_t y = {12,-9};
printf("x = ");
output(x);
printf("y = ");
output(y);
printf("x + y = ");
output(add(x,y));
printf("x - y = ");
output(sub(x,y));
return 0;
}
练习2
要求使用结构体数组设计一个程序,实现对候选人的投票设计
#include <stdio.h>
#include <string.h>
#define STRING_LEN 256
#define NAME_LEN 30
#define SIZE 4
typedef struct my_name_count_t{
char name[NAME_LEN]; //姓名
int count; //分数
}name_count_t;
int main(void){
//模拟一个候选人名单
name_count_t sta[SIZE] = {
{"zhangsan",0},
{"lisi",0},
{"wangwu",0},
{"chengliu",0}
};
char name[NAME_LEN];
int n = 6; //投票人为6人
int i = 0;
while(n>0){
--n;
scanf("%s",name);
for(i=0;i<SIZE;i++){
if(0 == strncmp(sta[i].name,name,strlen(name)))
++sta[i].count;
}
}
printf("The result is shown as follows:\n");
for(i=0;i<SIZE;i++){
printf("%s:%d\n",sta[i].name,sta[i].count);
}
return 0;
}