【Day6 C语言语法学习-5】

一、函数指针

函数指针:本质是一个指针,指向一个函数;
我们定义的函数名其实就是一个指针,保存当前函数代码区的首地址;
函数指针学习的目的就是想解决能不能定义一个指针,可以像函数名一样对当前函数进行调用。

函数指针定义的格式:
返回值类型(*变量名)(参数);

函数数值真的作用:将一个函数作为参数传递给另一个函数是需要定义成函数指针,也称之为回调函数

#include <stdio.h>
typedef unsigned char uchar;   //用uchar表示 unsigned char
typedef int (*T)(int,int);   //声明T为函数指针类型
void f1()
{
	printf("hello world!\n");
}
int f2(int x,int y)
{
	printf("this is f2\n");
	return 0;
}
int main(int argc, const char *argv[])
{
	void (*p1)();  //定义函数指针
	p1 = f1;//不能写成p1 = f1();   ---->调用f1函数,将返回值赋值给p1
	p1();
   //	p1 = f2;   //类型不兼容
   int (*p2)(int,int);
   p2 = f2;
   p2(1,2);
   T p3;  //等价于int(*p3)(int,int)
   p3 = f2;
   p3(1,2);
	return 0;
}

二、回调函数

#include <stdio.h>
int less(int x,int y)
{
	return (x < y) ? 1 : 0;
}
int greater(int x,int y)
{
	return (x > y) ? 1 : 0;
}
void Sort(int a[],int length,int (*p)(int,int))
{
	int i,j;
	for(i = 0 ; i < length - 1;i++)
	{
		for(j = 0 ; j < length - 1 - i;j++)
		{

			if(p(a[j],a[j+1]))
			{
#if 0
				int t = a[j];
				a[j] = a[j+1];
				a[j+1] = t;
#endif
			a[j] = a[j] + a[j + 1];
			a[j + 1] = a[j] - a[j + 1];
			a[j] = a[j] - a[j + 1];
			}
		}
	}
	
}
void Print(int a[],int length)
{
	int i;
	for(i = 0 ; i < length;i++)
	{
		printf("%d ",a[i]);
	}
	putchar(10);

}
int main(int argc, const char *argv[])
{
	int a[10] = {0};
	printf("请输入10个数字:\n");
	int i;
	int length = sizeof(a)/sizeof(a[0]);
	for(i = 0 ; i < length;i++)
	{
		scanf("%d",&a[i]);
	}
	Sort(a,length,less);
	Print(a,length);
	return 0;
}

练习:编写函数,定义一个无符号4字节的整数,然后获取每一个字节的内容,然后返回相加之和

要求:这个整数需要传参,返回每一个字节之和
458963212 —>‭0001 1011 0101 1011 0011 1001 0000 1100‬
0000 1100‬ —>12
0011 1001 —> 57
0101 1011 —>91
0001 1011 —> 27
12+57+91+27 = 187

#include <stdio.h>
int GetByteSum(unsigned int num)
{
	int sum = 0;
#if 0
	//将每8位拿出来
	int byte1,byte2,byte3,byte4;
	byte1 = num & 0xff;
	byte2 = (num >> 8) & 0xff;
	byte3 = (num >> 16) & 0xff;
	byte4 = (num >> 24) & 0xff;
	sum = byte1 + byte2 + byte3 + byte4;
#endif
	unsigned char *p = (unsigned char)&num;
	int i;
	for(i = 0 ; i < 4; i++)
	{
		sum += *p;
		p++;
	}
	p = NULL;
	return sum;

}
int main(int argc, const char *argv[])
{
	unsigned int num = 458963212;
	int n = GetByteSum(num);
	printf("n = %d\n",n);
	return 0;
}

面试题:define和typedef的区别?

1.#define是一个预处理指令,在预处理阶段进行字符串的简单替换,并不参与到编译阶段,typedef是类型的重定义,在编译的时候进行过类型的重定义;
2.当使用#define和typedef定义多个指针的时候,define定义的是第一个指针,后面全是变量,而typedef定义的全是指针;
3.define一般用于表示常量,还有函数的替换工作,typedef用于类型重定义。

#include <stdio.h>
#include <stdlib.h>
// 方法1:({})
// 方法2:do{}while(0)
int min;
#define MAX(a,b) ({int max;if(a > b)max=a;else max=b;max;})  //({})里面可以有多条语句,最后一句就是宏的返回值
#define MIN(a,b) do{if(a < b) min = a;else min = b;}while(0)

#define PRINT_MSG(msg) do{printf("%s\n",msg);return -1;}while(0)
#define S(n) #n   //#代表字符串化
int main(int argc, const char *argv[])
{
	int *p  = NULL;
	int hello = 100;
	printf("max = %d\n",MAX(100,200));
	MIN(100,200);
	printf("min = %d\n",min);
	p = (int *)malloc(4);
	if(p == NULL)
	{
		PRINT_MSG("malloc failed!\n");
	}
	printf("%s\n",S(10));  //字符串化
	return 0;
}

三、存储类型

auto:
register:定义变量在寄存器上,运行速率快,但是芯片的寄存器的个数有限制,不能无限使用;寄存器变量不能取地址;
extern: 使用的变量或者文件在其它文件中定义;
static:
1.限定作用域:(修饰的变量或者函数只能在本文件中使用)
2.延长生命周期
volatile:保证数据每次都从内存中取值,而不从缓存中取数据,防止编译器对代码进行优化
1.多线程访问同一个变量的时候
2.使用C语言操作硬件地址的时候

#include <stdio.h>

int a = 100;
void fun()
{
	static int i = 0; 
	printf("i = %d\n",i++);
	printf("a = %d\n",a);
}
int main(int argc, const char *argv[])
{
	int a = 200;
	printf("a = %d\n",a);
	
	{
		int a = 300;
		printf("a = %d\n",a);
	}
	printf("i = %d\n",i);
	fun();
	fun();
	fun();
	return 0;
}

四、内存管理

----------------------------
栈区:局部变量,正在调用的函数,形参都在栈
	栈区由于在释放时不会清0,所以在定义变量的时候不初始化,他们都是随机值,
    栈区空间由系统自动申请,自动释放
----------------------------
堆区:使用malloc分配的内存都在堆区,手动申请,手动释放
----------------------------
静态区|.bss:使用static修饰的未初始化的变量和全局的未初始化的变量在.bss段
      |----------------------------
      |.data:使用static修饰的已初始化的变量和全局的已初始化的变量在.data段
      |----------------------------
	  |.text:代码段,文本段
      |----------------------------
      |.ro: 只读数据段
      |const int a;
      |char *p = "helloworld";

#include <stdio.h>
int a; //.bss段
static int w = 4; //.data
int b = 1234;   //.data
const int g = 789; //.ro
void func(void)
{
	const int y = 1234; //.ro
	int *p = &y;  //栈区
}
int main(int argc, const char *argv[])
{
	char ch; //.stack
	char ch1 = 'a';  //.stack
	char *p = "aaaa"; //P .stack  "aaaa":.ro

	return 0;
}

思考题1

#include <stdio.h>
	
int main(int argc, const char *argv[])
{
	char *p = NULL;
	char *tmp = "helloworld";
	p = (tmp + 4);
	//p[-1] = ? p[-5] = ?
	printf("p[-1] = %c,p[-5] = %c\n",p[-1],p[-5]);
	return 0;
}

思考题2

#include <stdio.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
	char *data = "12345678";
	short *tmp = NULL;
	char p[6] = {0};
	tmp = (short *)&p[2];
	*tmp = atoi(&data[4]);
	printf("*tmp = %d\n",*tmp);
	//p[0],p[1],p[2],p[3],p[4],p[5]?
	int i = 0 ; 
	for(i = 0 ; i < 5;i++)
	{
		printf("p[%d] = %#x\n",i,p[i]);
	}
	return 0;
}

jsetc@linux:~/jsetc/208/day6$ ./8-思考题2 
*tmp = 5678
p[0] = 0
p[1] = 0
p[2] = 0x2e
p[3] = 0x16
p[4] = 0

五、结构体

结构体是一个构造类型,结构体的成员在内存上是连续的,
但是结构体的成员的类型可以是不相同的。
结构体的关键词用struct来标注。

格式:

struct 类型名{
    成员1;
    成员2;
    。。。
};

【注意事项】
(1)结构体使用struct来标识
(2)struct后面跟的是结构体的类型名,而非变量名
(3)结构体的成员是被{}所包括着
(4){}后面要加;
(5)结构体的成员可以不尽相同
(6)结构体成员之间用分号隔开
(7)定义结构体的时候不能赋值
(8)在结构体里面不能写函数,因为函数占用的字节不固定,但是可以写函数指针
(9)结构体访问成员的方式 “变量.成员”
(10) 结构体指针访问成员的方式“变量->成员”

5.1 结构体的使用

5.1.1 结构体的定义和赋值

#include <stdio.h>
struct person{
	char name[32];
	char sex;
	int age;
	int high;
	float weight;
};
int main(int argc, const char *argv[])
{
	struct person p1;
	strcpy(p1.name,"张三");
	p1.sex = 'm';
	p1.age = 18;
	p1.high = 189;
	p1.weight = 80;

	struct person *p2 = NULL;
	p2 = (struct person *)malloc(sizeof(*p2));
	if(p2 == NULL)
	{
		printf("malloc failure");
	}
	strcpy(p2->name,"张三");
	p2->sex = 'm';
	p2->age = 18;
	p2->high = 189;
	p2->weight = 80;

	return 0;
}

5.1.2 定义结构体同时赋值

#include <stdio.h>
struct person{
	char name[32];
	char sex;
	int age;
	int high;
	float weight;
};
int main(int argc, const char *argv[])
{
	struct person p1 = {"zhangsan",'m',20,186,80}; //给所有成员初始化
	struct person p2 = {             //选择成员初始化
		.name = "zhangsan",
		.weight = 80
	};


	return 0;
}

5.1.3 结构体数组

struct person p1[3]; //这个就是结构体数组,数组中有三个strcut person

5.2 结构体定义的方式

5.2.1 无名结构体

struct{
  char name[32];
  int age;
  float weight;  
}变量1,变量2;
struct 变量1;----->这个是错误的写法

typedef struct{
    char name[32];
    int age;
    float weight;
}student_t;  //类型名
student_t 变量1,变量2;

【注意】结构体可以整体赋值,数组在定义后不允许整体赋值

5.2.2 有名结构体

typedef struct student{
    char name[32];
    int age;
    float weight;
}student_t;  //类型名
student_t 变量1,变量2;

5.2.3 struct的特殊赋值方式

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct person{
	char name[32];
	char sex;
	int age;
	int high;
	float weight;
}person;
int main(int argc, const char *argv[])
{
	person p;
	p = (person){"张安",'m',20,189,80};
	printf("name = %s,sex = %c age = %d high = %d weight = %f\n",p.name,p.sex,p.age,p.high,p.weight);
//结构体数组赋值
//方法1
	person p1[2] = {
	{
		.name = "zhangsan",
		.age = 19
	},
	{
		.name = "zhangsi",
		.age = 20
	}
	};
//方法2:
person p2[3] = {
	[0] = {
		.name = "zhangsan",
		.age = 19
	},
	[2] = {
		.name = "zhangsi",
		.age = 20
	}
	};
	return 0;
}
//方法3:
person p3[3];
	p3[0].name

5.3 结构体占用字节大小

规则:
结构体占用字节大小的口诀是:
1.如果结构体内部的变量类型最大成员小于等于4字节,就按照最大的类型进行内存对齐
2.如果结构体内部变量类型大于四字节,就按照四字节对齐
3.结构体最终大小和结构体中成员最大类型对齐(32位和4字节对齐,64位和8字节对齐)
在这里插入图片描述
在这里插入图片描述

#include <stdio.h>
struct aa{
	char a;  //1个字节
};
struct bb{
	short a;  //2个字节
};
struct cc{
	int a;  //4个字节
};
struct dd{
	char a;  // 2个字节
	char b;
};
struct ee{
	int a;  // 8个字节
	char b;
};
struct ff{
	char a;  // 6个字节
	short b;
	char c;
};
struct gg{
	char a;  // 8个字节
	short b;
	int c;
};
struct hh{
	short a;  // 8个字节
	char b;
	int c;
};
struct ii{
	char a[3];  // 12个字节
	int b;
	char c;
	short d;
};
struct jj{
	double a;  // 24个字节
	char b;
	int c;
	short d;
	float e;
};
int main(int argc, const char *argv[])
{
	struct ff a;
	struct gg b;
	struct jj c;
	struct ii d;
	printf("aa = %ld\n",sizeof(struct aa));
	printf("bb = %ld\n",sizeof(struct bb));
	printf("cc = %ld\n",sizeof(struct cc));
	printf("dd = %ld\n",sizeof(struct dd));
	printf("ee = %ld\n",sizeof(struct ee));
	printf("ff = %ld\n",sizeof(struct ff));
	printf("gg = %ld\n",sizeof(struct gg));
	printf("hh = %ld\n",sizeof(struct hh));
	printf("ii = %ld\n",sizeof(struct ii));
	printf("jj = %ld\n",sizeof(struct jj));
	printf("ff.a = %p\nff.b = %p\nff.c = %p\n",&a.a,&a.b,&a.c);
	printf("gg.a = %p\ngg.b = %p\ngg.c = %p\n",&b.a,&b.b,&b.c);
	printf("jj.a = %p\njj.b = %p\njj.c = %p\njj.d = %p\njj.e = %p\n\n",&c.a,&c.b,&c.c,&c.d,&c.e);
	printf("ii.a = %p\nii.b = %p\nii.c = %p\nii.d = %p\n",d.a,&d.b,&d.c,&d.d);
	return 0;
}

5.3.1 结构体的位域

压缩结构体大小的一种方式

#include <stdio.h>
struct aa{
	char a:1;
	char b:5;
	char c:3;
};
int main(int argc, const char *argv[])
{
	printf("sizeof(aa) = %ld\n",sizeof(struct aa));
	return 0;
}

【注意】
1.在结构体内部进行位域操作的时候,是从内存最低位置开始站位,如果这个变量是有
符号的,那么它的最高位就是符号位,如果这个位域只有一位,它既是符号位又是数据位
2.结构体位域的成员不能够取地址

六、共用体

6.1 基本用法

union内部的成员公用同一块内存空间,共用体的大小就是内部最大成员的内存空间
不管union有多少个成员,union的内存空间都是这个最大成员的内存空间

格式:

union 类型名{
    char a;
    short b;
    int c;
};

6.2 大小端的判断

字节序:
大端序:高字节存放在低地址,低字节存放在高地址(网络序)
小端序:高字节存放在高地址,低字节存放在低地址
在这里插入图片描述

#include <stdio.h>
typedef union aa{
	unsigned char a;
	int c;
}aa_t;
int main(int argc, const char *argv[])
{
	aa_t a;
	a.c = 0x12345678;
	if(a.a == 0x12)
	{
		printf("这是大端机器\n");
	}
	else if(a.a == 0x78)
	{
		printf("这是小端机器\n");
	}
	return 0;
}

在这里插入图片描述

6.3结构体中定义联合体

作用:让结构体的通用性更强

#include <stdio.h>
struct aa
{
	char a;
	union{
		int b;
		char c;
	};
};
int main(int argc, const char *argv[])
{
	struct aa a;
	a.a = 10;
	a.b = 0xaabb;
	printf("size = %ld\n",sizeof(a));
	printf("c = %#x\n",a.c);
	return 0;
}

七、枚举类型

枚举就是有限数据的罗列,比如一周有七天,一年有12个月,
这些都是有限的罗列,适合用枚举
格式:

enum 类型名{
    成员1,
    成员2,   
    成员3,    
    成员4 = 10,
    成员5,
    .。。
    成员n,   
};

【注意】
(1)枚举类型的关键是enum
(2)枚举的成员没有变量类型
(3)成员间通过逗号来区分
(4)枚举的成员的值依次向下递增1
(5)枚举中的成员访问不是通过.或者->来访问,直接拿成员使用
(6)枚举类型的大小是4个字节
(7)枚举类型如果不赋初值,默认从0开始

练习:

实例一:

#include <stdio.h>
enum week{
	MON = 1,
	TUS,
	WED,
	THU,
	FRI,
	SAT,
	SUN
};
int main(int argc, const char *argv[])
{
	enum week day;
	printf("day size = %ld\n",sizeof(day));

	day = TUS;
	printf("day  = %d\n",day);
	day = 10;   //虽然编译器不会报错,但是不能这样赋值
				//给枚举类型变量赋值的时候,只能赋值枚举常量
	printf("day  = %d\n",day);
	return 0;
}

实例二

#include <stdio.h>
#define S(n) case n: return #n
typedef enum leds{
	RED,
	GREEN,
	BLUE,
}leds;
char *led_type_to_str(enum leds led)
{
	switch(led)
	{
		S(RED);
		S(GREEN);
		S(BLUE);
		default: 
			return "不存在的";
	}

}

leds led_open(leds led)
{
	printf("打开%s灯\n",led_type_to_str(led));
	return led;
}

int main(int argc, const char *argv[])
{
#if 0
	char *p = NULL;
	enum leds led;
	led = led_open(RED);
	p = led_type_to_str(led);
	printf("打开%s-ok!\n",p);
#endif
	printf("打开%s-ok!\n",led_type_to_str(led_open(RED)));
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

狗子爱猫粮855

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值