函数指针、函数指针数组详解及典型应用_韦东山 函数指针数组-程序员宅基地

技术标签: c语言  嵌入式linux学习笔记  C语言学习  指针  嵌入式  

20200305 杨千嬅唱的《处处吻》真是太好听了,下个他他吻她他吻她吻他吻她… 已沉醉

一、何为函数指针

我们知道指针变量指向内存单元的地址,比如存放普通变量int a;的地址的就是一重指针,存放一重指针变量的地址的就是二重指针,指针变量存地址,以此来实现传址调用,
函数指针,顾名思义,就是指向函数的指针,那么何为指向函数呢?按照上面的逻辑,我们得有一个指针变量,这个指针变量里存放着该函数块在内存中的首地址。
要想理解这个,我们要从指针的层次上理解函数——函数的函数名实际上就是该函数的代码在内存中的首地址的别名,说白了还是个地址,这个有点类似于数组名和数组首元素地址的关系,随便找个反汇编代码就可印证上面所说的,下面的是汇编代码和反汇编调用main函数的例子
1.关于别名:586、587行
2.关于调用:我们可以看看汇编中是如何调用函数的,可以发现,PC指向了文字池中的内容30000848,这不正是mian函数的首地址吗,传址调用就好了
在这里插入图片描述

二、函数指针变量的类型及定义

类型表征着特征,而能够描述一个函数的特征的无非就是入口参数(形参)和返回值了

形式1:返回类型(*函数指针变量名)(参数表)

char*pFun)(int); //定义了一个名为pFun的函数指针变量,要求指向的函数的要有一个int类型的形参并要求该函数返回值为char类型
char glFun(int a)
{
    
	return a;
}
void main()
{
    
	pFun =glFun;
	
	/*函数指针的普通使用,以下两种方式个都可*/
	pFun (2);//与其指向的函数用法无异  
	(*pFun)(2);//此处*pf两端括号必不可少  
}

第1行:pFun是 char (*)(int) 类型的指针
第2行:定义了一个函数glFun().该函数正好是一个以int为参数返回char的函数。即char (int) 类型
第8行:指针变量pFun指向了glFun函数的首地址

形式2:使用typedef更直接

typedef char(*PTRFUN)(int)
PTRFUN pFun;
char glFun(int a)
{
    
	return a;
}
void main()
{
    
	pFun = glFun;
	(*pFun)(2);
}

typedef的功能是定义新的类型。第一句就是定义了一种PTRFUN的类型,并定义这种类型为指向某种函数的指针,这种函数以一个int为参数并返回char类型。

三、举例说明用途

简单的计算函数回调

#include <stdio.h>
#define SIZE 4
/*typedef的功能是定义新的类型。
下面这句就是定义了一种PFUNC的类型,并定义这种类型为指向某种函数的指针,这种函数以两个int为参数并返回int类型。*/
typedef int (*PFUNC)(int, int);
PFUNC fucp_arr[SIZE] = {
    NULL};
int add(int a, int b)
{
    
	return a + b;
}
int sub(int a, int b)
{
    
	return a - b;
}
int mul(int a, int b)
{
    
	return a * b;
}
int div(int a, int b)
{
    
	return b ? a / b : -1;
}

/*返回函数类型,实现函数回调*/
PFUNC calc_func(char op)
{
    
	switch (op)
	{
    
	case '+':
		return add;
	case '-':
		return sub;
	case '*':
		return mul;
	case '/':
		return div;
	default:
		return NULL;
	}
	return NULL;
}
	int main(void)
{
    

	/*无论是什么类型的指针,在32位系统下都占4字节*/
	printf("sizeof(PFUNC) = %d\n", sizeof(PFUNC));
	/*实现函数回调*/
	printf("%d + %d = %d\n", 13, 14, (calc_func('+'))(13, 14));
	printf("%d - %d = %d\n", 13, 14, (calc_func('-'))(13, 14));
	printf("%d * %d = %d\n", 13, 14, (calc_func('*'))(13, 14));
	printf("%d / %d = %d\n", 13, 14, (calc_func('/'))(13, 14));
	
	return 0;
}


编译运行后得到

root@youngfar-PC:/mnt/hgfs/Linux教程/mytest# gcc -o exe test.c -m32
root@youngfar-PC:/mnt/hgfs/Linux教程/mytest# ./exe
sizeof(PFUNC) = 4
13 + 14 = 27
13 - 14 = -1
13 * 14 = 182
13 / 14 = 0

四、利用函数指针数组进行函数的注册和调用

#include <stdio.h>
#define SIZE 4
/*typedef的功能是定义新的类型。
下面这句就是定义了一种PFUNC的类型,并定义这种类型为指向某种函数的指针,这种函数以两个int为参数并返回int类型。*/
typedef int (*PFUNC)(int, int);
PFUNC fucp_arr[SIZE] = {
    NULL};
int add(int a, int b)
{
    
	return a + b;
}
int sub(int a, int b)
{
    
	return a - b;
}
int mul(int a, int b)
{
    
	return a * b;
}
int div(int a, int b)
{
    
	return b ? a / b : -1;
}

/*返回函数类型,实现函数回调*/
PFUNC calc_func(char op)
{
    
	switch (op)
	{
    
	case '+':
		return add;
	case '-':
		return sub;
	case '*':
		return mul;
	case '/':
		return div;
	default:
		return NULL;
	}
	return NULL;
}

/* 
 不使用typedef,直接让函数返回一个函数指针,写起来比较麻烦,不直观,所以不推荐

 定义了一个函数,s_calc_func是函数名,
 s_calc_func这个函数需要char类型的形参,
 且最终返回一个函数指针,
 且要求这个函数指针指向的函数有两个int类型的形参和一个int类型的返回值
 */
int (*s_calc_func(char op))(int, int) /* 注意:这个函数的用途与上一个名为calc_func的函数的作业和调用方式完全相同*/
{
    
	return calc_func(op);
}

/*我们要从指针的层次上理解函数-函数的函数名实际上就是一个指针,函数名指向该函数的代码在内存中的首地址。*/
void Fp_Register(int position, PFUNC fp)
{
    
	fucp_arr[position] = fp;
}

int main(void)
{
    
/*无论是什么类型的指针,在32位系统下都占4字节*/
	printf("sizeof(PFUNC) = %d\n", sizeof(PFUNC));
	// /*实现函数回调*/
	// printf("%d + %d = %d\n", 13, 14, (calc_func('+'))(13, 14));
	// printf("%d - %d = %d\n", 13, 14, (calc_func('-'))(13, 14));
	// printf("%d * %d = %d\n", 13, 14, (calc_func('*'))(13, 14));
	// printf("%d / %d = %d\n", 13, 14, (calc_func('/'))(13, 14));

	/*函数注册*/
	Fp_Register(0, add);
	Fp_Register(1, sub);
	Fp_Register(2, mul);
	Fp_Register(3, div);

	/*函数调用*/
	printf("%d + %d = %d\n", 13, 14, fucp_arr[0](13, 14));
	printf("%d - %d = %d\n", 13, 14, fucp_arr[1](13, 14));
	printf("%d * %d = %d\n", 13, 14, fucp_arr[2](13, 14));
	printf("%d / %d = %d\n", 13, 14, fucp_arr[3](13, 14));

	return 0;
}

运行结果和上边的一样

函数指针数组应用实例

函数指针数组一般用于有相同操作流程但细节却不能使用一个函数统一描述的的函数的调用,比如在2440处理中断时的一些操作,下面贴出代码,举例说明,先来看2440中断的处理流程
在这里插入图片描述
The S3C2440A has two interrupt pending registers: source pending register (SRCPND) and interrupt pending register (INTPND). These pending registers indicate whether an interrupt request is pending or not. When the interrupt sources request interrupt the service, the corresponding bits of SRCPND register are set to 1, and at the same time, only one bit of the INTPND register is set to 1 automatically after arbitration procedure. If interrupts are masked, then the corresponding bits of the SRCPND register are set to 1. This does not cause the bit of INTPND register changed. When a pending bit of INTPND register is set, the interrupt service routine will start whenever the I-flag or F-flag is cleared to 0. The SRCPND and INTPND registers can be read and written, so the service routine must clear the pending condition by writing a 1 to the corresponding bit in the SRCPND register first and then clear the pending condition in the INTPND registers by using the same method.
S3C2440A有两个中断暂挂寄存器:源暂挂寄存器(SRCPND)和中断暂挂寄存器(INTPND)。这些挂起的寄存器指示中断请求是否挂起。当中断源请求中断服务时,SRCPND寄存器的相应位被设置为1,同时,经过仲裁程序,INTPND寄存器中只有1位被设置为1。如果中断被屏蔽,那么SRCPND寄存器的相应位被设置为1。这不会导致INTPND寄存器的位发生变化。当设置一个暂挂的INTPND寄存器位时,中断服务例程将在I-flag或F-flag被清除为0时启动。SRCPND和INTPND寄存器可以读写,因此服务例程必须先通过将1写入SRCPND寄存器中相应的位来清除挂起条件,然后使用相同的方法清除INTPND寄存器中的挂起条件。
在这里插入图片描述
在这里插入图片描述
下面是2440代码的按键中断处理函数

#include "s3c2440_soc.h"

/* SRCPND 用来显示哪个中断产生了, 需要清除对应位
 * bit0-eint0
 * bit2-eint2
 * bit5-eint8_23
 */

/* INTMSK 用来屏蔽中断, 1-masked
 * bit0-eint0
 * bit2-eint2
 * bit5-eint8_23
 */

/* INTPND 用来显示当前优先级最高的、正在发生的中断, 需要清除对应位
 * bit0-eint0
 * bit2-eint2
 * bit5-eint8_23
 */

/* INTOFFSET : 用来显示INTPND中哪一位被设置为1
 */

/* 初始化中断控制器 Initial State 为1, 0 = Service available*/
void interrupt_init(void)
{
    
	INTMSK &= ~((1 << 0) | (1 << 2) | (1 << 5)); /*按键中断源配置*/
	INTMSK &= ~(1 << 10);						 /* timer0 中断源配置*/
}

/* 初始化按键, 设为中断源 */
void key_eint_init(void)
{
    
	/* 配置GPIO为中断引脚 */
	GPFCON &= ~((3 << 0) | (3 << 4));
	GPFCON |= ((2 << 0) | (2 << 4)); /* S2,S3被配置为中断引脚 */

	GPGCON &= ~((3 << 6) | (3 << 22));
	GPGCON |= ((2 << 6) | (2 << 22)); /* S4,S5被配置为中断引脚 */

	/* 设置中断触发方式: 双边沿触发 */
	EXTINT0 |= (7 << 0) | (7 << 8); /* S2,S3 */
	EXTINT1 |= (7 << 12);			/* S4 */
	EXTINT2 |= (7 << 12);			/* S5 */

	/* 设置EINTMASK使能eint11,19 */
	EINTMASK &= ~((1 << 11) | (1 << 19));
}

/* 读EINTPEND分辨率哪个EINT产生(eint4~23)
 * 清除中断时, 写EINTPEND的相应位
 */

void key_eint_irq(int irq)
{
    
	unsigned int val = EINTPEND;
	unsigned int val1 = GPFDAT;
	unsigned int val2 = GPGDAT;

	if (irq == 0) /* eint0 : s2 控制 D12 */
	{
    
		if (val1 & (1 << 0)) /* s2 --> gpf6 */
		{
    
			/* 松开 */
			GPFDAT |= (1 << 6);
		}
		else
		{
    
			/* 按下 */
			GPFDAT &= ~(1 << 6);
		}
	}
	else if (irq == 2) /* eint2 : s3 控制 D11 */
	{
    
		if (val1 & (1 << 2)) /* s3 --> gpf5 */
		{
    
			/* 松开 */
			GPFDAT |= (1 << 5);
		}
		else
		{
    
			/* 按下 */
			GPFDAT &= ~(1 << 5);
		}
	}
	else if (irq == 5) /* eint8_23, eint11--s4 控制 D10, eint19---s5 控制所有LED */
	{
    
		if (val & (1 << 11)) /* eint11 */
		{
    
			if (val2 & (1 << 3)) /* s4 --> gpf4 */
			{
    
				/* 松开 */
				GPFDAT |= (1 << 4);
			}
			else
			{
    
				/* 按下 */
				GPFDAT &= ~(1 << 4);
			}
		}
		else if (val & (1 << 19)) /* eint19 */
		{
    
			if (val2 & (1 << 11))
			{
    
				/* 松开 */
				/* 熄灭所有LED */
				GPFDAT |= ((1 << 4) | (1 << 5) | (1 << 6));
			}
			else
			{
    
				/* 按下: 点亮所有LED */
				GPFDAT &= ~((1 << 4) | (1 << 5) | (1 << 6));
			}
		}
	}

	EINTPEND = val; //写1清除中断标志
}

void handle_irq_c(void)
{
    
	/* 分辨中断源 */
	int bit = INTOFFSET;

	/* 调用对应的处理函数 */
	if (bit == 0 || bit == 2 || bit == 5) /* eint0,2,eint8_23 */
	{
    
		key_eint_irq(bit); /* 处理中断, 清中断源EINTPEND */
	}
	else if (bit == 10)
	{
    
		timer_irq();
	}

	/* 清中断 : 从源头开始清 */
	SRCPND = (1 << bit);
	INTPND = (1 << bit);
}

每次编写中断相关函数需要

汇编中:
1.中断发生前:

01.设置中断向量表
02.调用c中的mian函数

2.中断发生时:

跳转到中断向量表,去执行相应的处理(硬件自动)
01.设置 sp_irq
02.保存现场 :
在irq异常处理函数中有可能会修改r0-r12, 所以先保存
lr-4是异常处理完后的返回地址, 也要保存
05. 汇编中调用c中断处理函数处理中断
06. 恢复现场

C语言中 :
3.中断发生前

01.main函数由汇编调用,初始化各个中断控制器,配置中断源

4.中断发生时:

由汇编调用中断处理函数,该函数应包含:
01.中断产生时分辨中断源
02.调用对应的处理函数
03.清中断标志

函数指针数组主要用来解决上述 4 中断处理函数的流程调用,未使用函数指针数组时,每配置中断,都需要改变handle_irq_c来完成 4 中的01、02、03,改动interrupt_init来配置对应的MASK位,而使用之后我们就不需要改动了,由上述 2 中的05调用 后可自动配置,也是一种优化,下面是使用函数指针数组改进后的代码:

#include "s3c2440_soc.h"

typedef void(*irq_func)(int) ;
irq_func irq_array[32];


/* SRCPND 用来显示哪个中断产生了, 需要清除对应位
 * bit0-eint0
 * bit2-eint2
 * bit5-eint8_23
 */

/* INTMSK 用来屏蔽中断, 1-masked
 * bit0-eint0
 * bit2-eint2
 * bit5-eint8_23
 */

/* INTPND 用来显示当前优先级最高的、正在发生的中断, 需要清除对应位
 * bit0-eint0
 * bit2-eint2
 * bit5-eint8_23
 */

/* INTOFFSET : 用来显示INTPND中哪一位被设置为1
 */

/* 初始化中断控制器 */
// void interrupt_init(void)
// {
    
// 	INTMSK &= ~((1<<0) | (1<<2) | (1<<5));
// 	INTMSK &= ~(1<<10);  /* enable timer0 int */
// }

/* 读EINTPEND分辨率哪个EINT产生(eint4~23)
 * 清除中断时, 写EINTPEND的相应位
 */


void key_eint_irq(int irq)
{
    
	unsigned int val = EINTPEND;
	unsigned int val1 = GPFDAT;
	unsigned int val2 = GPGDAT;
	
	if (irq == 0) /* eint0 : s2 控制 D12 */
	{
    
		if (val1 & (1<<0)) /* s2 --> gpf6 */
		{
    
			/* 松开 */
			GPFDAT |= (1<<6);
		}
		else
		{
    
			/* 按下 */
			GPFDAT &= ~(1<<6);
		}
		
	}
	else if (irq == 2) /* eint2 : s3 控制 D11 */
	{
    
		if (val1 & (1<<2)) /* s3 --> gpf5 */
		{
    
			/* 松开 */
			GPFDAT |= (1<<5);
		}
		else
		{
    
			/* 按下 */
			GPFDAT &= ~(1<<5);
		}
		
	}
	else if (irq == 5) /* eint8_23, eint11--s4 控制 D10, eint19---s5 控制所有LED */
	{
    
		if (val & (1<<11)) /* eint11 */
		{
    
			if (val2 & (1<<3)) /* s4 --> gpf4 */
			{
    
				/* 松开 */
				GPFDAT |= (1<<4);
			}
			else
			{
    
				/* 按下 */
				GPFDAT &= ~(1<<4);
			}
		}
		else if (val & (1<<19)) /* eint19 */
		{
    
			if (val2 & (1<<11))
			{
    
				/* 松开 */
				/* 熄灭所有LED */
				GPFDAT |= ((1<<4) | (1<<5) | (1<<6));
			}
			else
			{
    
				/* 按下: 点亮所有LED */
				GPFDAT &= ~((1<<4) | (1<<5) | (1<<6));
			}
		}
	}

	EINTPEND = val;
}


void handle_irq_c(void)
{
    
	/* 分辨中断源 */
	int bit = INTOFFSET;

	/* 调用对应的处理函数 */
	irq_array[bit](bit);
	
	/* 清中断 : 从源头开始清 */
	SRCPND = (1<<bit);
	INTPND = (1<<bit);	
}

void register_irq(int irq, irq_func fp)
{
    
	irq_array[irq] = fp;

	INTMSK &= ~(1<<irq);
}


/* 初始化按键, 设为中断源 */
void key_eint_init(void)
{
    
	/* 配置GPIO为中断引脚 */
	GPFCON &= ~((3<<0) | (3<<4));
	GPFCON |= ((2<<0) | (2<<4));   /* S2,S3被配置为中断引脚 */

	GPGCON &= ~((3<<6) | (3<<22));
	GPGCON |= ((2<<6) | (2<<22));   /* S4,S5被配置为中断引脚 */
	

	/* 设置中断触发方式: 双边沿触发 */
	EXTINT0 |= (7<<0) | (7<<8);     /* S2,S3 */
	EXTINT1 |= (7<<12);             /* S4 */
	EXTINT2 |= (7<<12);             /* S5 */

	/* 设置EINTMASK使能eint11,19 */
	EINTMASK &= ~((1<<11) | (1<<19));

	register_irq(0, key_eint_irq);
	register_irq(2, key_eint_irq);
	register_irq(5, key_eint_irq);
}


只需在初始化时注册一下相应的处理函数
register_irq(0, key_eint_irq);
register_irq(2, key_eint_irq);
register_irq(5, key_eint_irq);
待到中断到来时即可自动完成 分辨中断源 、调用对应的处理函数 、清中断这一系列操作,时代码更加便于维护

注:本文参考了
typedef函数指针的用法(C++)

C/C++ 函数指针使用总结

和韦东山老师的讲解,转载请注明出处,本人初学Linux,如有错误,欢迎评论区批评指出,共同进步

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_28816873/article/details/104684373

智能推荐

微信小程序踩坑记录 ------- 优化修改微信小程序原生单选(radio)复选(checkbox)框样式_微信小程序的复选框太丑,自己写一个-程序员宅基地

文章浏览阅读6.8k次。现在的微信小程序是火的不要要的,很多公司都开始做微信小程序的产品项目,很多前端工程师都或主动或被动的一头扎向小程序开发大军,当然我也不可避免的开始加入其中。不管是做什么样的项目,或多或少会涉及到表单,小程序提供了一系列的原生表单组件,其中很多组件是很不错的,我们直接拿来用就可以,但是单选和复选框的样式,实在是不在丑的不忍直视,当然和我们的设计小哥哥小姐姐做的设计图更是相差甚远,为了达到预期效果我..._微信小程序的复选框太丑,自己写一个

AAPT2简介-程序员宅基地

文章浏览阅读619次,点赞80次,收藏73次。AAPT2(Android 资源打包工具)是一种构建工具,Android Studio 和 Android Gradle 插件使用它来编译和打包应用的资源。 AAPT2 会解析资源、为资源编制索引,并将资源编译为针对 Android 平台进行过优化的二进制格式。_aapt2

4大主流小程序平台介绍及其优缺点对比-程序员宅基地

文章浏览阅读3.3k次,点赞19次,收藏18次。小程序是一种轻量级应用程序,能够在手机上直接运行,无需下载安装,适用于一些简单的功能场景,如点餐、预约、查看天气等。以下是目前主流的小程序平台及其优缺点对比_小程序平台

树莓派交叉编译环境搭建 for Mac_mac环境搭建gnu交叉编译工具链x-程序员宅基地

文章浏览阅读1.1w次。made by Rk本文由浙江大学《嵌入式系统》课程提供强力支持。感谢翁恺老师 @翁恺BA5AG/*************************************************************/树莓派的运算能力不是特别强(700MHz),而我们的CPU往往有几个GHz的运算速度,所以我们有必要搭建一个交叉编译环境。通过交叉编译,我们就_mac环境搭建gnu交叉编译工具链x

解决 “该扩展程序未列在 Chrome 网上应用店中,并可能是在您不知情的情况下添加的”_该扩展程序未列在 chrome 应用商店中,并可能是在您不知情的情况下添加的-程序员宅基地

文章浏览阅读4.8w次,点赞20次,收藏16次。1、首先把需要安装的第三方插件,后缀.crx 改成 .rar,然后解压,得到一个文件夹2、再打开chrome://extensions/谷歌扩展应用管理,点击右上角的开发者模式,就可以看到“加载正在开发的扩展程序”这一选项。3、选择刚才步骤1中解压好的文件夹,确定4、确认新增扩展程序,点击添加,成功添加应用程序。原问题贴吧链接:http://tieba.baidu.com/p/41171..._该扩展程序未列在 chrome 应用商店中,并可能是在您不知情的情况下添加的

Springboot2.x版本切换修改默认的数据库连接池_springboot hikaricp更换不了-程序员宅基地

文章浏览阅读1k次。1.HikariCP连接池如何被默认加入到Springboot项目中的?Springboot2.x版本默认的连接池HikariCP,这是一个高性能的连接池,一般不需要切换。我们先看看这个连接池如何被依赖的,我们开发springboot项目时候,直接加入下面依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter_springboot hikaricp更换不了

随便推点

django 常见过滤器-程序员宅基地

文章浏览阅读52次。一、形式:小写{{ name | lower }} 二、过滤器是可以嵌套的,字符串经过三个过滤器,第一个过滤器转换为小写,第二个过滤器输出首字母,第三个过滤器将首字母转换成大写标签{{ str|lower|first|upper }} 三、过滤器的参数显示前30个字{{ bio | truncatewords:"30..._django前台{{ str|upper|lower|upper }}

StanfordCoreNLP从零安装与我遇到的各种坑(使用python)超详细最全截图-程序员宅基地

文章浏览阅读8.7k次,点赞28次,收藏72次。安装安装步骤很简单,不过要注意自己的电脑环境:(1)下载安装JDK 1.8及以上版本。 这一点很重要!!!!!!!注意:如果本来有java的盆友记得先在控制面板卸载所有的java程序,然后再下载安装新的JDK,我就是没卸载,系统无法识别……具体地址:oracle官网 https://www.oracle.com/index.html进入网站后:点击左上角的横线菜单:在菜单中点击J..._stanfordcorenlp

一文搞懂电子电路-程序员宅基地

文章浏览阅读1.3k次。电子电路1.1 分类1.1.1 模拟电子电路1.1.1.1 介绍1.1.1.2 特点1.1.2 数字电子电路1.1.2.1 特点1.1.2.2 数字电路中的相关概念1.1.2.3 门电路1.1.2.3.1 TTL集成门电路1.1.2.3.1.1 特点1.1.2.3.1.2 电压范围1.1.2.3.1.3 介绍1.1.2.3.1.4 反相器1.1.2.3.1.5 开路门1.1.2.3.1.6 三态门1.1.2.3.1.7 线与1.1.2.3.2 CMOS集成门电路1.1.2.3.2.1 特点1.1.2.3._电子电路

输出二叉树中从每个叶子结点到根结点的路径(代码简练,思路易懂)_输出二叉树中从每个叶子结点到根结点的路径算法-程序员宅基地

文章浏览阅读3.6k次,点赞14次,收藏35次。如果我们在递归函数,传入一个栈,(不要传引用!)每次递归,若当前结点非空那么就将该节点入栈,那么每一层递归都会对应一个栈,==这个栈表示当前结点到根结点的路径==。_输出二叉树中从每个叶子结点到根结点的路径算法

算法趣题 第一天_算一算 梗 例题-程序员宅基地

文章浏览阅读184次。本篇简介本篇博客的题目来源于《程序员的算法趣题》。这是一本非常有意思的算法书,不像其他的算法书从链表,二叉树等数据结构原理入手,通过一道道的题目,从实际问题出发,来使读者了解算法的运用。不过唯一不足之处在于书中的算法源码多用Ruby与JavaScript来实现,对于刚上手学习编程的读者来说,这两种语言不会是首选的编程语言。因此,博主使用Java对书中的源码进行重构实现,对使用Java的初学者能够明白其中的算法。PS:博主的技术水平有限,可能代码会比较的臃肿冗余,在时间复杂度和空间复杂度上无法做到最优,_算一算 梗 例题

dotnet run 提示System.Net.Sockets.SocketException (10049): 在其上下文中,该请求的地址无效。...-程序员宅基地

文章浏览阅读8.9k次。更换端口号试一下。查看官方文档PS:使用帮助命令 -h,可以指定启动配置文件: dotnet run --launch-profile xxx例如下面的配置文件,假如我们要使用codes-test的配置,可以这样:dotnet run --launch-profile codes-test学会看官方文档可以少走很多弯路。{ "iisSettings"..._system.net.sockets.socketexception:“在其上下文中,该请求的地址无效。”