C语言结构体

253 阅读9分钟

1.初识结构体

1.为什么要用结构体

整型数,浮点型数,字符串是分散的数据表示

有时候我们需要用很多类型的数据来表示一个整体,比如学生信息

类比数组:数组是元素类型一样的数据集合

如果是元素类型不同的数据集合,就要用到结构体了

结构体的核心就是用不同变量的总体来描述一个事件

2.定义一个结构体

引入struct关键字,告知系统这是一个结构体

image.png

以上算是一个模板,一般不给赋具体的值,每一项在实际应用中并不是都要调用

3.成员列表

成员列表也称为域表,每个成员都是结构体中的一个域,一般称成员变量

4.初始化一个结构体并引用

5.注意事项

1.在声明的同时,定义变量,尽量少用

2.结构体没什么特殊的,只是把变量藏在结构体里面,而内部的变量和以前的东西是相同的道理,只是“触达的表示”不同

2.结构体数组

3.结构体指针

1.概念引入

回忆:

指针就是地址
指针变量就是存放地址的变量
结构体也是变量
变量访问有两种方式 : 1.变量名 2.地址
之前案例,是用变量名访问

通过结构体变量地址来访问该结构体
需要一个变量来保持这个地址:
这和之前说的指针,其实是一样的
只是指针类型是结构体

int a;
int *p;
p = &a;

struct Test t;
struct Test *p;
p = &t

点运算符来访问结构体中的成员变量(域)

#include <stdio.h>
#include <string.h>

struct Student{
	int num;
	char name[32];
	char sex;
	int age;
	double score;
	char addr[32];
};

int main(){
	int a;
	struct Student stu1;
	stu1.num=1;
	stu1.age=16;
	stu1.score=98.5;
	strcpy(stu1.name,"Bill");
	strcpy(stu1.addr,"广东");
	printf("学号:%d,年龄:%d,分数:%f,姓名:%s,地址:%s\n",
        stu1.num,stu1.age,stu1.score,stu1.name,stu1.addr);
}
//学号:1,年龄:16,分数:98.500000,姓名:Bill,地址:广东

在上述示例中,还可将赋值过程进行优化,注意一一对应

struct Student stu2={2,"张三","男",17,98,"福建"};

printf("学号:%d,年龄:%d,分数:%f,姓名:%s,性别:%c,地址:%s\n",
stu2.num,stu2.age,stu2.score,stu2.name,stu2.sex,stu2.addr);

学号:2,年龄:17,分数:98.000000,姓名:张三,性别:g,地址:福建

2.通过结构体指针访问结构体

struct Test{
	int idata;
	char cdata;
};
int main(){
	int a = 10;
	int *p = &a;
	char c ='c';
	char *pc = &c;
	struct Test t1 = {10,'c'}; //结构体整体赋值法
	struct Test *ps = &tl;
	printf("t1的idata=%d\n",tl.idata);//变量名访问,用点运算符
	printf("t1的idata=%d\n",ps->idata);//指针访问,用"->"运算符
	
	ps->cdata ='R';//指针访问,用"->"运算符
        
	printf("t1的cdata=%c\n",t1.cdata);//变量名访问,用点运算符
	printf("t1的cdata=%c\n",ps->cdata);//指针访问,用"->"运算符
	return 0;
}

3.结构体数组,指针,函数应用

案例:选民投票系统

4.共用体/联合体

1.概念

1.有时候同一个内存空间存放类型不同,不同类型的变量共享一块空间

2.像结构体但是有区别

 结构体元素有各自单独空间
 共用体元素共享空间,空间大小由最大类型确定
struct TestT{
	int idata;
	char cdata;
	double ddata;
};
union TestU{
	int idata;
	char cdata;
	double ddata;
};
int main(){
	struct TestT t1;
	union TestU u1;
	printf("结构体的大小是:%d\n",sizeof(t1));
	printf("联合体的大小是:%d\n",sizeof(u1));
}
结构体的大小是:16
联合体的大小是:8,占用最大空间的域为double8字节
struct TestT{
	int idata;
	char cdata;
	int ddata;
};
union TestU{
	int idata;
	char cdata;
	int ddata;
};
int main(){
	struct TestT t1;
	union TestU u1;
	printf("结构体的大小是:%d\n",sizeof(t1));
	printf("联合体的大小是:%d\n",sizeof(u1));	
        
        printf("idata:%p\n",&t1.idata);
	printf("cdata:%p\n",&t1.cdata);
	printf("ddata:%p\n",&t1.ddata);
	printf("idata:%p\n",&u1.idata);
	printf("cdata:%p\n",&u1.cdata);
	printf("ddata:%p\n",&u1.ddata);
}

结构体的大小是:12
联合体的大小是:4,占用最大空间的域是int,大小为4字节

idata:000000000061FE14//结构体中成员变量的地址不同
cdata:000000000061FE18
ddata:000000000061FE1C

idata:000000000061FE10//共用体内成员变量的地址都一样
cdata:000000000061FE10
ddata:000000000061FE10

对比可以发现,联合体占用空间的大小取决于成员变量(域)占用的最大空间, 结构体占用空间的大小是成员变量占用空间的总和

结构体元素有各自单独空间,共用体元素共享空间

t1.idata=10;
t1.cdata='a';
t1.ddata=20;
printf("idata:%p,idata:%d\n",&t1.idata,t1.idata);
printf("cdata:%p,cdata:%d\n",&t1.cdata,t1.cdata);
printf("ddata:%p,ddata:%d\n",&t1.ddata,t1.ddata);

u1.idata=10;
u1.cdata='a';
u1.ddata=20;
printf("idata:%p,idata:%d\n",&u1.idata,u1.idata);
printf("cdata:%p,cdata:%d\n",&u1.cdata,u1.cdata);
printf("ddata:%p,ddata:%d\n",&u1.ddata,u1.ddata);	
        
idata:000000000061FE14,idata:10
cdata:000000000061FE18,cdata:97
ddata:000000000061FE1C,ddata:20
idata:000000000061FE10,idata:20
cdata:000000000061FE10,cdata:20
ddata:000000000061FE10,ddata:20

对比发现,结构体元素互不影响, 共用体赋值会导致覆盖

3.注意数据覆盖

2.应用

案例:有若干个人员的数据,其中有学生和教师。学生的数据中包括:姓名、号码、性别、职业、班级。教师的数据包括:姓名、号码、性别、职业、职务。要求用同一个表格来处理。

image.png

5.枚举类型

1.什么是枚举类型

如果一个变量只有几种可能的值,比如星期几 Sun Mon Tus Wed Thu Fri Sat

可以把所有的取值情况枚举完,故称枚举变量

2.定义枚举类型

列表中的名字可以自己定义,无需像变量一样去申请
C语言把当做常量处理,也称枚举变量

3.枚举变量

可以忽略枚举类型名,直接定义枚举变量

4.举例

第一个定义的方法

#include <stdio.h>
enum WeekDay {Sun,Mon=8,Tus,Wed,Thu,Fri,Sat};
int main(){
        enum WeekDay w;//先定义一个枚举变量
        enum WeekDay q;
//	w=kkk;//非法赋值,只限列表中的集中情况
        w=Sun;
        q=Mon;
        printf("w=%d\n",w);//w=0
        printf("q=%d\n",q);//q=8;
        return 0;
}

可以指定列表中枚举变量的值

值默认从0开始,枚举元素不能被赋值,虽然瞅着像变量名

不可非法赋值,赋值只限列表中的集中情况

枚举变量在不被赋值的情况下,输出的是其下标,注意下标从0开始

第二种定义的方法

enum WeekDay {Sun,Mon=8,Tus,Wed,Thu,Fri,Sat}w1,w2;
int main(){
        w1=Sun;//忽略枚举类型名
        w2=Mon;
        printf("w1=%d\n",w1);//w=0
        printf("w2=%d\n",w2);
        return 0;
}
//w1=0;w2=8

第二种定义的方式和结构体、共用体的第二种定义是同样的道理

6.typedef关键字

1.给已有的变量类型起名字

2.一般配合结构体用,方便,不需要每次都用struct开头

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

起别名的目的不是为了提高程序运行效率,而是为了编码方便。

例如有一个结构体的名字是 stu,要想定义一个结构体变量就得这样写:

struct stu stu1;

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

STU stu1;

这种写法更加简练,意义也非常明确。

1.使用关键字 typedef可以为类型起一个新的别名。

typedef 的用法一般为:

typedef  oldName  newName;

oldName 是类型原来的名字,newName 是类型新的名字。 例如:

typedef int INTEGER;
INTEGER a, b;
a = 1;
b = 2;

INTEGER a, b;等效于int a, b;

2.typedef 还可以给数组、指针、结构体等类型定义别名。

一个给数组类型定义别名的例子:

typedef char ARRAY20[20];

表示 ARRAY20 是类型char [20]的别名。 它是一个长度为 20 的数组类型。接着可以用 ARRAY20 定义数组:

ARRAY20 a1, a2, s1, s2;

它等价于:

char a1[20], a2[20], s1[20], s2[20];

3.为结构体类型定义别名:

typedef struct stu{    char name[20];    int age;    char sex;} STU;

STU 是 struct stu 的别名,可以用 STU 定义结构体变量:

STU body1,body2;

它等价于:

struct stu body1, body2;    

4.为指针类型定义别名:

typedef int (*PTR_TO_ARR)[4];

表示 PTR_TO_ARR 是类型int * [4]的别名,它是一个二维数组指针类型。接着可以使用 PTR_TO_ARR 定义二维数组指针:

PTR_TO_ARR p1, p2;

5.为函数指针类型定义别名:

typedef int (*PTR_TO_FUNC)(int, int);  
PTR_TO_FUNC pfunc;

举例:

#include <stdio.h>
typedef char (*PTR_TO_ARR)[30];
typedef int (*PTR_TO_FUNC)(int, int);

int max(int a, int b){
    return a>b ? a : b;
}
char str[3][30] = {
    "http://www.baidu.com",
    "百度",
    "Baidu"
};
int main(){
    PTR_TO_ARR parr = str;
    PTR_TO_FUNC pfunc = max;
    int i;
    printf("max: %d\n", (*pfunc)(10, 20));
    for(i=0; i<3; i++){
        printf("str[%d]: %s\n", i, *(parr+i));
    }
    return 0;
}

max: 20
str[0]: http://www.baidu.com
str[1]: 百度
str[2]: Baidu

案例

typedef int arr[10];//arr代替int [10]

struct Test{//结构体的第一种定义
	int data;
	int data2;
};
typedef struct{//结构体的第二种定义
	int data;
	int data1;	
}Demo;
typedef struct Test T;//结构体的第三种定义(命名)

void printInfo(T t){
	printf("%d\n",t.data);
}

int main(){
	arr a ;//arr代替int [10]
	a[0]=10;
	printf("%d\n",a[0]);	
	
	struct Test t1;
	t1.data=100;
	printf("%d\n",t1.data);

	Demo d;
	d.data1=1000;
	printf("%d\n",d.data1);
	
	T t2;
	t2.data=999;
	printInfo(t2);
}

image.png

typedef和#define的区别

typedef 在表现上有时候类似于 #define,但它和宏替换之间存在一个关键性的区别。

把 typedef 看成一种彻底的“封装”类型,声明之后不能再往里面增加别的东西。

1.可以使用其他类型说明符对宏类型名进行扩展, 但对 typedef 所定义的类型名却不能这样做。如下所示:

    #define INTERGE int  
    unsigned INTERGE n;  //没问题  

    typedef int INTERGE;  
    unsigned INTERGE n;  //错误,不能在 INTERGE 前面添加 unsigned

2.在连续定义几个变量的时候,typedef 能够保证定义的所有变量均为同一类型, 而 #define 则无法保证。例如:

    #define PTR_INT int *  
    PTR_INT p1, p2;

经过宏替换以后,第二行变为:

    int *p1, p2;

这使得p1、p2 成为不同的类型:p1 是指向 int 类型的指针,p2 是 int 类型

相反,在下面的代码中:

    typedef int * PTR_INT  
    PTR_INT p1, p2;

p1、p2 类型相同,它们都是指向 int 类型的指针。