【C高级专题】C语言复杂表达式与指针高级应用

目录

一、指针数组与数组指针

1.指针数组与数组指针
1、字面意思来理解指针数组与数组指针
(1)指针数组的实质是一个数组,这个数组中存储的内容全部是指针变量。
(2)数组指针的实质是一个指针,这个指针指向的是一个数组。
2、分析指针数组与数组指针的表达式
(1)int *p[5]; int (*p)[5]; int *(p[5]);
(2)一般规律:int p;(p是一个指针); int p[5];(p是一个数组)
总结:我们在定义一个符号时,关键在于:首先要搞清楚你定义的符号是谁(第一步:找核心);其次再来看谁跟核心最近、谁跟核心结合(第二步:找结合);以后继续向外扩展(第三步:继续向外结合直到整个符号完)。
(3)如果核心和
结合,表示核心是指针;如果核心和[]结合,表示核心是数组;如果核心和()结合,表示核心是函数。
(4)用一般规律来分析3个符号:

第一个:int *p[5]; 
// 核心是p,p是一个数组,数组有5个元素大,数组中的元素都是指针,指针指向的元素类型是int类型的;整个符号是一个指针数组。
第二个,int (*p)[5];
// 核心是p,p是一个指针,指针指向一个数组,数组有5个元素,数组中存的元素是int类型; 总结一下整个符号的意义就是数组指针。
第三个,int *(p[5]); 
// 解析方法和结论和第一个相同,()在这里是可有可无的。

注意:符号的优先级到底有什么用?其实是决定当2个符号一起作用的时候决定哪个符号先运算,哪个符号后运算。
遇到优先级问题怎么办?第一,查优先级表;第二,自己记住(全部记住都成神了,人只要记住[] . ->这几个优先级比较好即可)。

3、总结1:优先级和结合性是分析符号意义的关键
(1)在分析C语言问题时不要胡乱去猜测规律,不要总觉得c语言无从捉摸,从已知的规律出发按照既定的规则去做即可。
4、总结2:学会逐层剥离的分析方
(1)找到核心后从内到外逐层的进行结合,结合之后可以把已经结合的部分当成一个整体,再去和整体外面的继续进行结合
5、总结3:基础理论和原则是关键,没有无缘无故的规则

二、 函数指针与typedef

1、函数指针的实质(还是指针变量)
(1)函数指针的实质还是指针,还是指针变量。本身占4字节(在32位系统中,所有的指针都是4字节)
(2)函数指针、数组指针、普通指针之间并没有本质区别,区别在于指针指向的东西是个什么玩意。
(3)函数的实质是一段代码,这一段代码在内存中是连续分布的(一个函数的大括号括起来的所有语句将来编译出来生成的可执行程序是连续的),所以对于函数来说很关键的就是函数中的第一句代码的地址,这个地址就是所谓的函数地址在C语言中用函数名(一段代码的首地址)这个符号来表示
(4)结合函数的实质,函数指针其实就是一个普通变量(实质就是一个地址),这个普通变量的类型是函数指针变量类型,它的值就是某个函数的地址(也就是它的函数名这个符号在编译器中对应的值)

2、函数指针的书写和分析方法
(1)C语言本身是强类型语言(每一个变量都有自己的变量类型),编译器可以帮我们做严格的类型检查
(2)所有的指针变量类型其实本质都是一样的,但是为什么在C语言中要去区分它们,写法不一样呢(譬如int类型指针就写作int *p; 数组指针就写作int (*p)[5],函数指针就得写得更复杂)
(3)假设我们有个函数是:void func(void); 对应的函数指针:void (p)(void); 类型是:void ()(void);
(4)函数名和数组名最大的区别就是:函数名做右值时加不加&效果和意义都是一样的;但是数组名做右值时加不加&意义就不一样
(5)写一个复杂的函数指针的实例:譬如函数是strcpy函数(char *strcpy(char *dest, const char *src);),对应的函数指针是:char *(*pFunc)(char *dest, const char *src);

函数指针解析
char *(*pFunc)(char *dest, const char *src);
// 核心是pFunc,pFunc是一个指针,指针指向一个函数,整个符号就是一个函数指针。
// 函数指针指向的是一个接受两个参数(char *dest, const char *src),返回值为char *类型的函数
函数指针示例
void func1(void)
{
	printf("test func1 in the screen.\n");
}

int mian(void)
{
	void (*pFunc)(void);		// 定义一个变量
	//pFunc = func1;			// 左边是一个函数指针变量,右边是一个函数名
	pFunc = &func1;				// &func1和func1做右值时是一模一样的,没任何区别
	pFunc();					// 用函数指针来调用该函数
	
	return 0;
}

3、typedef关键字的用法
(1)typedef是C语言中一个关键字,作用是重命名类型或制造用户自定义类型
(2)C语言中的类型一共有2种:一种是编译器定义的原生类型(基础数据类型,如int、double之类的);第二种是用户自定义类型,不是语言自带的是程序员自己定义的(譬如数组类型、结构体类型、函数类型·····)。
(3)我们今天讲的数组指针、指针数组、函数指针等都属于用户自定义类型。
(4)有时候自定义类型太长了,用起来不方便,所以用typedef给它重命名一个短点的名字。
(5)注意:typedef是给类型重命名,也就是说typedef加工出来的都是类型(类型是不占用内存空间的),而不是变量

// 这句重命名了一种类型,这个新类型名字叫pType,类型是:char* (*)(char *, const char *);
typedef char* (*pType)(char *, const char *);

int main(void)
{
	char* (*p1)(char *, const char *);
	
	pType p3;		// 等效于 char* (*p3)(char *, const char *);
	
	return 0;
}

4、总结:函数指针的分析方法也是源于优先级与逐层剥离的基本理论

三、函数指针调用执行函数实战

1、用函数指针调用执行函数

#include <stdio.h>

int add(int a, int b);
int sub(int a, int b);
int multiply(int a, int b);
int divide(int a, int b);

// 定义了一个类型pFunc,这个函数指针类型指向一种特定参数列表和返回值的函数
typedef int (*pFunc)(int, int);


int main(void)
{
	pFunc p1 = NULL;		// 定义一个函数指针变量并初始化
	char c = 0;
	int a = 0, b = 0, result = 0;
	
	printf("请输入要操作的2个整数:\n");
	scanf("%d %d", &a, &b);
	
	printf("请输入操作类型:+ | - | * | /\n");
	// 接受上一个scanf()的回\n 也可以用getchar();
	do 			
	{
		scanf("%c", &c);
	}while (c == '\n');
	// 在switch();接受的操作类型不同,p1指向不同函数,类似给函数指针赋值
	switch (c)	
	{
	case '+':
		p1 = add; break;
	case '-':
		p1 = sub; break;
	case '*':
		p1 = multiply; break;
	case '/':
		p1 = divide; break;
	default:
		p1 = NULL;	break;
	}
	
	// 用函数指针来调用该函数,传入参数a,b 并把返回值赋值个result
	result = p1(a, b);	
	printf("%d %c %d = %d.\n", a, c, b, result);
	
	return 0;
}

int add(int a, int b)
{
	return a + b;
}

int sub(int a, int b)
{
	return a - b;
}

int multiply(int a, int b)
{
	return a * b;
}

int divide(int a, int b)
{
	return a / b;
}

四、结构体内嵌函数指针实现分层实战

主题:结构体内嵌函数指针实现分层

(1)程序为什么要分层?因为复杂程序东西太多一个人搞不定,需要更多人协同工作,于是乎就要分工。要分工先分层,分层之后各个层次由不同的人完成,然后再彼此调用组合共同工作。
(2)本程序要完成一个计算器,我们设计了2个层次:上层是framework.c,实现应用程序框架下层是cal.c,实现计算器。实际工作时cal.c是直接完成工作的,但是cal.c中的关键部分是调用的framework.c中的函数来完成的。
(3)先写framework.c,由一个人来完成。这个人在framework.c中需要完成计算器的业务逻辑,并且把相应的接口写在对应的头文件中发出来,将来别的层次的人用这个头文件来协同工作。
(4)另一个人来完成cal.c,实现具体的计算器;这个人需要framework层的工作人员提供头文件来工作(但是不需要framework.c)
(5)总结:
第一:本节和上节实际完成的是同一个习题,但是采用了不同的程序架构。
第二:对于简单问题来说,上节的不分层反而容易理解,反而简单;本节的分层代码不好理解,看起来有点把简单问题复杂化的意思。原因在于我们这个问题本身确实是简单问题,而简单问题就应该用简单方法处理。我们为什么明知错误还要这样做?目的是向大家演示这种分层的写代码的思路和方法。
第三:分层写代码的思路是:有多个层次结合来完成任务,每个层次专注各自不同的领域和任务;不同层次之间用头文件来交互
第四:分层之后下层为上层提供服务,下层写的代码是为了在上层中被调用。
第五:上层注重业务逻辑,与我们最终的目标相直接关联,而没有具体干活的函数。
第六:**下层注重实际干活的函数,注重为上层填充变量,并且将变量传递给上层中的函数(其实就是调用下层提供的接口函数)**来完成任务。
第七:上层代码中其实核心是一个结构体变量(譬如本例中的struct cal_t),写上层代码的逻辑其实很简单:第一步先定义结构体变量;第二步填充结构体变量;第三步调用下层写好的接口函数,把结构体变量传给它既可。

下列该函数只截取一部分作为分层分析

typedef int (*pFunc)(int, int);

// 结构体是用来做计算器的,计算器工作时需要计算原材料
struct cal_t
{
	int a;
	int b;
	pFunc p;		// 函数指针
};

int calculator(const struct cal_t *p)
{
	return p->p(p->a, p->b);	// 访问结构体中的元素->
}

int divide(int a, int b)
{
	return a / b;
}


int main(void)
{
	int ret = 0;
	struct cal_t myCal;		// 构建面向对象实例
	
	myCal.a = 12;			// 填充面向对象实例
	myCal.b = 4;
	myCal.p = divide;
	
	ret = calculator(&myCal);		// 使用面向对象实例
	printf("ret = %d.\n", ret);
	
	return 0;
}

注:本资料大部分由朱老师物联网大讲堂课程笔记整理而来

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Tyx-☆、、、

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

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

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

打赏作者

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

抵扣说明:

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

余额充值