C_复合数据类型

复合数据类型——联合union、枚举enum、结构体、Typedef、结构体对齐、结构体数组、结构体指针结构体嵌套、结构体传参、指针函数和回调函数

1、联合union

定义:

  • 联合是一个能在同一个存储空间里(但不同时)存储不同数据的数据类型

代码演示:

  • 例1、了解union
#include <stdio.h>

int main()
{
        union u_data
        {
                unsigned int a;
                unsigned char b;
        } data;

        printf("sizeof(data) = %ld\n", sizeof(data));

        data.a = 0x12345678;

        printf("data.b = 0x%x\n", data.b);

        data.b = 0x00;

        printf("data.a = 0x%x\n", data.a);
}

执行结果:

sizeof(data) = 4
data.b = 0x78
data.a = 0x12345600

代码解析:

  • data_sizeof = 4:定义一个变量,需要分配一段内存空间,联合是一个能在同一个存储空间里(但不同时)存储不同数据的数据类型,所有的成员都在一段存储空间里面,所以这段存储空间的大小由联合里面最大的那个成员决定,此处最大是int类型,所以data_sizeof = 4。

  • data.b = 0x78[^1]

  • data.a = 0x12345600[^2]
    在这里插入图片描述

  • 例2、判断当前系统是大端还是小端字节序

方法1

#include <stdio.h>

int main()
{
        union u_data
        {
                unsigned int a;
                unsigned char b;
        } data;

        data.a = 0x12345678;
        printf("data.b = %x\n", data.b);
        if (data.b == 0x78) {
                printf("LSB\n");
        }else{
                printf("MSB\n");
        }

}

执行结果:

data.b = 78
LSB

方法2

#include <stdio.h>

int main()
{
        unsigned int a = 0x12345678;
        unsigned char *b = NULL;

        b = (char *) &a;

        printf("*b = %x\n", *b);

        if (*b == 0x78) {
                printf("LSB\n");
        }else{
                printf("MSB\n");
        }

}

执行结果:

*b = 78
LSB

2、枚举enum

定义:

  • 可以使用枚举类型声明表示整数常量的符号名称。

代码演示:

#include <stdio.h>

int main()
{
        enum
        {
                water,
                apple,
                pig,
                fish = 99,
                dog,
                cat,
        };

        printf("water = %d\n", water);
        printf("apple = %d\n", apple);
        printf("pig = %d\n", pig);
        printf("fish = %d\n", fish);
        printf("dog = %d\n", dog);
        printf("cat = %d\n", cat);
        
}

执行结果:

water = 0
apple = 1
pig = 2
fish = 99
dog = 100
cat = 101

3、结构体

定义:

  • 结构题可以由基本数据类型构造出一种新的复合类型来。

代码演示:

#include <stdio.h>

int main()
{
        struct st_student
        {
                char name[32];
                char gender[32];
                int age;
                float score;
        } ;
        
		/******************************
		  两种赋值方式
		  第一种按顺序存储
		  第二种不需按顺序存储
		*******************************/
        struct st_student student1 = {"lyt", "male", 20, 90.0};
        struct st_student student2 = {.gender = "female", .name = "ly", .score = 100.0, .age = 18};
        
        printf(" name: %s\n male: %s\n age: %d\n score: %f\n",student1.name, student1.gender, student1.age, student1.score);
        printf(" name: %s\n male: %s\n age: %d\n score: %f\n",student2.name, student2.gender, student2.age, student2.score);

}

执行结果:

 name: lyt
 male: male
 age: 20
 score: 90.000000
 name: ly
 male: female
 age: 18
 score: 100.000000

注意:

  • 定义结构体时没有从内存中分配内存,在定义变量时才分配内存。
  • 结构体是按顺序存放的。
    在这里插入图片描述

4、Typedef

定义:

  • Typedef用来定义一种新的数据。

代码演示:

#include <stdio.h>

int main()
{
        typedef struct _st_student
        {
                char name[32];
                char gender[32];
                int age;
                float score;
        } st_student;

        st_student student1 = {"lyt", "male", 20, 90.0};
        st_student student2 = {.gender = "female", .name = "ly", .score = 100.0, .age = 18};

        printf(" name: %s\n male: %s\n age: %d\n score: %f\n",student1.name, student1.gender, student1.age, student1.score);
        printf(" name: %s\n male: %s\n age: %d\n score: %f\n",student2.name, student2.gender, student2.age, student2.score);

}

执行结果:

 name: lyt
 male: male
 age: 20
 score: 90.000000
 name: ly
 male: female
 age: 18
 score: 100.000000

更多用法:

typedef unsigned char u8;			u8 var1,var2;  //8:char一个字节8位  //var1,var2都是unsigned char类型
typedef unsigned short uint 16;		uint_16 var1,var2; //16:short两个字节16位 //var1,var2都是unsigned shortr类型

typedef char * pchar;				pchar var1,var2; //var1,var2都是char *类型
typedef int * pint;					pint var1,var2; //var1,var2都是int *类型

#define PCHAR char *				PCHAR var1,var2; //var1是PCHAR char *类型,var2是char类型

typedef struct _st_student 
{
	char name[32]; 
	int gender; 
	int age, 
	float score, 
} st_student;
st_student student; struct st student student;
  • 在单片机开发时,尽量不要用int或short,因为在8位的单片机,int可能是16位,在32位的单片机,int是32位。
  • 单片机开发一定会定义uint16,不管是多少位的单片机,都会规定是16位。

5、结构体对齐

定义:

  • 自身对齐值:自身对齐值就是结构体变量里每个成员的自身大小
  • 指定对齐值:指定对齐值是由宏#pragma pack(N)指定的值,里面的N必须是2的幂次方,如1,2,4,8,16等。如果没有通过#pragma pack宏那么在32位的Linux主机上默认指定对齐值为464位的Linux主机上默认指定对齐值为8ARM CPU默认指定对齐值为8
  • 有效对齐值:结构体成员自身对齐时有效对齐值为自身对齐值指定对齐值中较小的一个。结构体圆整时,为所有成员中最大的自身对齐值指定对齐值较小的一个。

代码演示:

  • 求结构体的长度
#include <stdio.h>

int main()
{
        typedef struct _struct_sf
        {
                char a;
                short b;
                int c;
        } struct_sf;

        printf("struct_sizeof = %ld\n",sizeof(struct_sf));

        typedef struct _struct1_sf
        {
                char d;
                int e;
                short f;
        } struct1_sf;

}

执行结果:

struct_sizeof = 8
struct1_sizeof = 12

结果解析:

  • struct1_sizeof = 12
    在这里插入图片描述

注意:

  • 所有的数据都存在内存中运行,内存中的数据要给cpu,通过数据线,如果cpu是32位的,就有32根数据线连在内存(0~31),内存给cpu发数据,一下子发送32位,即4个字节。如果要保持高效性,内存给的地址是4的整数倍,即内存按4的整数倍存放数据cpu就可以一下子全部读4个字节。
    在这里插入图片描述

6、结构体数组

定义:

  • 保存和访问数组

代码演示:

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

int main()
{
        typedef struct _st_student
        {
                char id[20];
                char name[32];
                char gender[32];
                int age;
                float score;
        } st_student;
		
        st_student studenta[2] = {{"0001", "lyt", "male", 20, 90},{"0002", "ly", "female", 20, 90}};
		//定义一个名为studenta的数组,一起赋值。
		
        st_student studentb[3];  //定义一个名为studentb的数组,分开赋值。

        studentb[0].age = 20;
        studentb[0].score = 60;
        strcpy(studentb[0].name, "yjl");
        strcpy(studentb[0].gender, "female");
        strcpy(studentb[0].id, "0001");

        studentb[1].age = 20;
        studentb[1].score = 60;
        strcpy(studentb[1].name, "yjl");
        strcpy(studentb[1].gender, "female");
        strcpy(studentb[1].id, "0002");

        studentb[2].age = 20;
        studentb[2].score = 60;
        strcpy(studentb[2].name, "yjl");
        strcpy(studentb[2].gender, "female");
        strcpy(studentb[2].id, "0003");

        for(int i=0; i<2; i++)
        {
        printf("a\n id: %s\n name: %s\n male: %s\n age: %d\n score: %f\n", studenta[i].id, studenta[i].name, studenta[i].gender, studenta[i].age, studenta[i].score);
        }

        for(int i=0; i<3; i++)
        {
        printf("b\n id: %s\n name: %s\n male: %s\n age: %d\n score: %f\n", studentb[i].id, studentb[i].name, studentb[i].gender, studentb[i].age, studentb[i].score);
        }

}

执行结果:

a
 id: 0001
 name: lyt
 male: male
 age: 20
 score: 90.000000
a
 id: 0002
 name: ly
 male: female
 age: 20
 score: 90.000000
b
 id: 0001
 name: yjl
 male: female
 age: 20
 score: 60.000000
b
 id: 0002
 name: yjl
 male: female
 age: 20
 score: 60.000000
b
 id: 0003
 name: yjl
 male: female
 age: 20
 score: 60.000000

代码解析:

  • studenta[2]:一起赋值,只能赋两个
  • studentb[3]:分开赋值,能赋三个

注意:

  • 字符串数组赋值:
/******************************
正确的赋值:
******************************/
char str[31] = "hello"; //直接赋值

char *str;
str = "hello";  //指针str指向hello的首地址

char str[31];
strcpy(str, "hello");  //用strcpy库函数赋值,要#include <string.h>

/*****************************
错误的赋值:
******************************/
char str[31];
str = "hello"; //把hello的首地址给了str数组的首地址,把str当作指针用了
  • 数组名是这个数组的首地址。

7、结构体指针

代码演示:

int main()
{
        typedef struct _st_student
        {
                char name[32];
                char id[20];
                char gender[20];
                int age;
                float score;
        } st_student;

        st_student student[3];
        st_student *ptr = NULL;
        
        //给结构体成员赋值
		//方式一
        ptr = &student[0];
        strncpy(ptr->name, "lyt", 32);
        strncpy(ptr->gender, "female", 20);
        strncpy(ptr->id, "0001", 20);
        ptr->age = 20;
        ptr->score = 90;
		
		//方式2
        ptr = &student[1];
        strncpy((*ptr).name, "lyt", 32);
        strncpy((*ptr).gender, "female", 20);
        strncpy((*ptr).id, "0002", 20);
        (*ptr).age = 20;
        (*ptr).score = 90;
		
		//方式3
        ptr = &student[2];
        strncpy(student[2].name, "lyt", 32);
        strncpy(student[2].gender, "female", 20);
        strncpy(student[2].id, "0003", 20);
        student[2].age = 20;
        student[2].score = 90;

        for(int i=0; i<3; i++)
        {
        printf(" \n id: %s\n name: %s\n male: %s\n age: %d\n score: %f\n", student[i].id, student[i].name, student[i].gender, student[i].age, student[i].score);
        }
}

执行结果:

 id: 0001
 name: lyt
 male: female
 age: 20
 score: 90.000000

 id: 0002
 name: lyt
 male: female
 age: 20
 score: 90.000000

 id: 0003
 name: lyt
 male: female
 age: 20
 score: 90.000000

总结:

  • 最常用的是"->"。

8、结构体嵌套

代码演示:

typedef struct _st_score 
{
	float math;
	int english;
	int chinese;
} st_score;

typedef struct _st_student 
{ 
 	char name[32]; 
 	int gender; 
	int age;
	st_score score;
	st_score *pscore;

} st_student; 

st_student student; 
st_student *ptr = &student; 

//通过student访问math的四种方式
student.score.math=90.0; 
student.pscore->math =90.0;
ptr->score.math =90.0; 
ptr->pscore->math =90.0;

代码解析:

  • 自身是变量,用 .,自身是指针,用->

9、结构体传参

代码演示:

1void increase_age(st_student *p)
{
	p->age++;
}
st_student student; 
increase_age(&student);

2)

st_student increase_age(st_student student)
{
	student.age++;
	return student;  //返回一整个student
}
st_student student;
student = increase_age(student);

注意:

  • 现在的C允许把一个结构赋值给另一个结构,但不能对数组这么做。
例如:
	错误赋值:
	
		数组:
		char a[10];
		char b[10];
		a = b;  //这只是把b的首地址赋给a的首地址(数组名是数组的首地址)
	
	正确赋值:
	
	 	数组:
		char a[10];
		char b[10];
		memcpy(a,b,sizeof(b));
		
      	结构体:
		st_student student1; 
		st_student student2; 
		方式1:
		student2 = student1;
		方式2:
		memcpy(&student2, &student1, sizeof(st_student));

  • 字符串结尾的\0\0 = 0 = 0x00;strcpy碰到0以为是最后一位就会停止。

10、函数指针和回调函数

函数指针

定义:

  • 指针是用来存放地址的,而函数名就是函数的入口地址。只要有地址,就一定可以用指针指向它。
  • 函数只关心参数和返回值。
例如:
	void fanc (int a, char *s);
	{
		
	}
	
	void (*p) (int a, char *s) ;  //可以指向函数fanc (int a, char *s),因为返回值和参数严格一致
	p = func; //指针p指向func
	p(); //调用函数
	
	void (*p) (int a, char *s);  //定义一个函数指针p,要指向返回值和参数严格一致的函数
	void (*p) (int, char *); //形参可以去掉

注意:

  • 函数指针是指针,指针指向函数类型。
  • 指针函数是函数,是个返回值是函数的函数。
  • void *p (int a, char *s); //这是一个函数声明,声明了一个函数p,返回值是void * 类型的函数。

回调函数

代码演示:

#include <stdio.h>

typedef struct _st_score
{
         float math;
         float english;
         float chinese;
} st_score;

//******回调函数callback1******
float calc_total_score(struct _st_score score)
{
        return score.chinese + score.math + score.english;
}

//******回调函数callback2******
float calc_ch_math_score(struct _st_score score)
{
        return score.chinese + score.math;
}

//******回调函数callback3******
float calc_ch_eng_score(struct _st_score score)
{
        return score.chinese + score.english;
}

//******中间函数******
float printf_student_score(struct _st_score score, float(*p)(struct _st_score score))
{
        printf("score: %f\n", p(score));
}  
//*******************
//float(*p)(struct _st_score score):函数指针,可以指向calc_total_score、calc_ch_math_score、
//								   calc_ch_eng_score三个函数,需要调用哪个,就指向哪个。	
//p(score):调用函数,score是调用的那个函数的形参。													 
//*******************

int main()
{
        st_score score1 = {100, 100, 100};
        st_score score2 = {100, 80, 60};
        
        printf_student_score(score1, calc_total_score);
        printf_student_score(score1, calc_ch_math_score);
        printf_student_score(score1, calc_ch_eng_score);

        printf_student_score(score2, calc_total_score);
        printf_student_score(score2, calc_ch_math_score);
        printf_student_score(score2, calc_ch_eng_score);

}

执行结果:

score: 300.000000
score: 200.000000
score: 200.000000
score: 240.000000
score: 160.000000
score: 140.000000

注意:

  • 注册函数:当发生相应的事件,就调用相应的函数。都是通过函数指针实行的。把函数先放到指针那里注册好,当某个事情发生的时候就调用那个函数。
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值