C指针之函数应用案例(内核分层架构思想)

函数指针应用案例

  • 案例1

#include 

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");
	
	do 
	{
		scanf("%c", &c);
	}while (c == '\n');
	// 加一句调试
	//printf("a = %d, b = %d, c = %d.\n", a, b, c);
	
	switch (c)
	{
	case '+':
		p1 = add; break;
	case '-':
		p1 = sub; break;
	case '*':
		p1 = multiply; break;
	case '/':
		p1 = divide; break;
	default:
		p1 = NULL;	break;
	}
	
	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;
}
  • 用函数指针指向不同的函数来实现同一个调用执行不同的结果。
  • 如果学过C++或者Java或者C#等面向对象的语言,就会知道面向对象三大特征中有一个多态。多态就是同一个执行实际结果不一样,跟我们这里看到的现象其实是一样的。

在代码调试过程,可以得到很多信息:

  • 第一:当程序出现段错误时,第一步先定位段错误。定位的方法就是在可疑处加打印信息,从而锁定导致段错误的语句,然后集中分析这句为什么会段错误。
  • 第二:linux中命令行默认是行缓冲的,意思就是说当我们程序printf输出的时候,linux不会一个字一个字的输出我们的内容,而是将其缓冲起来放在缓冲区等一行准备完了再一次性把一行全部输出出来(为了效率)。
    • linux判断一行有没有完的依据就是换行符'\n'(windows中换行符是\r\n, linux中是\n,iOS中是\r)。也就是说你printf再多,只要没有遇到\n(或者程序终止,或者缓冲区满)都不会输出而会不断缓冲,这时候你是看不到内容输出的。因此,在每个printf打印语句(尤其是用来做调试的printf语句)后面一定要加\n,否则可能导致误判。
  • 第三:关于在linux命令行下用scanf写交互性代码的问题,想说以下几点:
    • 命令行下的交互程序纯粹是用来学习编程用的,几乎没有实践意义,别浪费时间了。
    • scanf是和系统的标准输入打交道,printf和标准输出打交道。要完全搞清楚这些东西得把标准输入标准输出搞清楚。
    •  我们用户在输入内容时结尾都会以\n结尾,但是程序中scanf的时候都不会去接收最后的\n,导致这个回车符还存留在标准输入中。下次再scanf时就会先被拿出来,这就导致你真正想拿的那个数反而没机会拿,导致错误。

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

  • 程序为什么要分层?
    • 因为复杂程序东西太多一个人搞不定,需要更多人协同工作,于是乎就要分工要分工先分层,分层之后各个层次由不同的人完成,然后再彼此调用组合共同工作。
  • 本程序要完成一个计算器,我们设计了2个层次:
    • 上层是framework.c,实现应用程序框架
    • 下层是cal.c,实现计算器。实际工作时cal.c是直接完成工作的,但是cal.c中的关键部分是调用的framework.c中的函数来完成的。
  • 先写framework.c,由一个人来完成。这个人在framework.c中需要完成计算器的业务逻辑,并且把相应的接口写在对应的头文件中发出来,将来别的层次的人用这个头文件来协同工作。
  • 另一个人来完成cal.c,实现具体的计算器;这个人需要framework层的工作人员提供头文件来工作(但是不需要framework.c)

案例2:

  • framework.h

#ifndef __FRAMEWORK_H__
#define __FRAMEWORK_H__

// 函数原型声明
int calculator(const struct cal_t *p);

#endif
  • framework.c

#include "framework.h"

// framework.c中应该写实际业务关联的代码


// 计算器函数
int calculator(const struct cal_t *p)
{
	return p->p(p->a, p->b);
}
  • cal.h

#ifndef __CAL_H__
#define __CAL_H__
#include "framework.h"

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


// 结构体是用来做计算器的,计算器工作时需要计算原材料
struct cal_t
{
	int a;
	int b;
	pFunc p;
};
#endif
  • cal.c

#include "cal.h"
#include 


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;
}


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采用了不同的程序架构。
  • 第二:对于简单问题来说,案例1的不分层反而容易理解,反而简单;案例2的分层代码不好理解,看起来有点把简单问题复杂化的意思。原因在于我们这个问题本身确实是简单问题,而简单问题就应该用简单方法处理。我们为什么明知错误还要这样做?目的是演示这种分层的写代码的思路和方法。
  • 第三:分层写代码的思路是:
    • 有多个层次结合来完成任务,每个层次专注各自不同的领域和任务;
    • 不同层次之间用头文件来交互
  • 第四:分层之后上层为下层提供服务,上层写的代码是为了在下层中被调用。
  • 第五:上层注重业务逻辑,与我们最终的目标相直接关联,而没有具体干活的函数
  • 第六:下层注重实际干活的函数,注重为上层填充变量,并且将变量传递给上层中的函数(其实就是调用上层提供的接口函数)来完成任务。
  • 第七:下层代码中其实核心是一个结构体变量(譬如本例中的struct cal_t),写下层代码的逻辑其实很简单:
    • 第一步先定义结构体变量;
    • 第二步填充结构体变量;
    • 第三步调用上层写好的接口函数,把结构体变量传给它既可。

你可能感兴趣的:(重学C语言)