C语言深度解析--函数

news2024/11/19 3:25:29

函数

函数的定义:

函数,又称为子程序,是一个大型程序中的某部分代码,由一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代码,具备相对独立性。
一般会有输入参数并有返回值,提供对过程的封装和细节的影藏。这些代码通常被集成为软件库。

函数的分类:

库函数:

早期的C语言是没有库函数的,在实际生活中往往会存在很多问题:代码冗余;开发效率低;不标准;
把常用的一些功能实现成函数,集成为库,由C语言直接提供。从而C语言标准,就可以规定库函数的标准;
C语言常用的库函数:IO函数,字符操作函数,内存操作函数,时间/日期函数,数学函数,其他库函数等;
注:使用库函数,必须包含#include对应的头文件

案例一:
strcpy函数:字符串拷贝函数
原型:extern char* strcpy(char* dest, char* src);
功能:把从src地址开始且含有NULL结束符的字符串赋值到以dest开始的地址空间,返回dest(地址中存储的为复制后的新值);
要求:src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src的字符串;

int main()
{
	char arr1[] = "abcdef";//a b c d e f \0,注意:\0也会拷贝过来
	char arr2[20] = {0};

	//能把arr1中的abcdef拷贝到arr2中
	strcpy(arr2,arr1);

	printf("%s\n",arr2);

	return 0;
}

运行结果:
案例二:
memset函数:内存填充函数
sizeof()函数的返回值类型是size_t,而size_t是unsigned int型;
void* memset(void* s, int ch, size_t n); (int ch可以是char或int);
将s所指向的某一块内存中的每个字节的内容全部设置为ch指定的ASCII值,块的大小由第三个参数指定,这个函数通常为新申请的内存做初始化工作, 其返回值为指向S的指针;

int main()
{
	char arr[] = "hello csdn";

	//设置内存的时候是以字节为单位的
	//每个字节的内容都是一样的value
	memset(arr,'X',6);//XXXXXXcsdn
	//memset(arr+1, 'X', 5);//hXXXXXcsdn,arr+1:表示从第二个字节开始

	printf("%s\n",arr);

	return 0;
}

运行结果:

自定义函数

自定义函数和库函数一样,有函数名,返回值类型和函数参数
函数格式:

//函数组成:
ret_type fun_name(para1, * )
{
statement;//语句项
}
ret_type 返回类型
fun_name 函数名
para1    函数参数

函数的参数:

实参:真实传给函数的参数;实参可以是常量,变量,表达式,函数等。无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参;
形参:形式参数是指函数名后括号中的变量,因为形参只有在函数被调用的过程中才实例化(分配内存);形参在函数调用完之后就会自动销毁,因此形式参数只在函数中有效;

案例分析:
写一个函数交换两个整型变量的内容
代码一:

void swap(int m, int n)
{
	int temp = m;
	m = n;
	n = temp;
	//printf("swap m = %d,n = %d\n", m, n);
}

int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d",&a,&b);
	printf("交换前:a=%d,b=%d\n",a,b);

	swap(a,b);
	printf("交换后:a=%d,b=%d\n",a,b);
	return 0;
}

运行结果:
从运行结果可知,在调用swap函数之后,a和b的值在交换前后并未发生变化,说明该函数并不能完成两个变量的内容交换。
通过调试来一探究竟:
a.在从主函数进入被调函数swap之前,通过键盘输入a和b的值,并查看其对应的地址分别为:
b.在进入被调函数swap之初,通过观察发现,a和b的值分别赋给了m和n,此时m和n的分别为3和6:
c.将swap内部的函数执行完毕之后,可以发现m和n的值已经发生了互换,但是并没有改变a和b的值,此时a和b的值依旧是3和6:
d.通过查看变量各自的地址空间可以发现,此时a和b对应的地址空间与m和n对应的地址空间是完全不相同的:
e.虽然被调函数swap中m和n的值发生了互换,但是并没有影响到主函数中a和b的值;究其原因可以发现,a和b所占用的内存空间与m和n所占用的内存空间是完全不同的;因此,对m和n的内存空间上的值进行修改修改并不能影响到对应的主函数中的a和b的值。

代码二:

void swap(int* x, int* y)
{
	int tmp = 0;
	tmp = *x; 
	*x = *y;
	*y = tmp;
}

int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d",&a,&b);
	printf("交换前:a=%d,b=%d\n",a,b);

	swap(&a,&b);//按F11进入swap内部进行调试
	printf("交换后:a=%d,b=%d\n",a,b);
	return 0;
}

运行结果:
分析:
此时,a和b的值在交换前后发生了变化,通过调试可以发现:
a.在从主函数进入被调函数swap之前,a和b对应的值及其地址分别为:

b.在进入被调函数swap之初,此时变量x和y的值分别等于对应的a和b所占内存的地址;同时,在变量x和y中存放的值分别为3和6:
c.待swap函数执行完毕返回主函数时,我们可以发现,x和y中存放的值均已发送变化,同时a和b的值也相应的发生了交换:
总结:
在传值时:
当实参传给形参的时候,形参是实参的一份零时拷贝,对形参的修改不会影响实参;
函数参数在传递的时候,都是传原数据的副本,也就是说,swap内部使用的a和b只是最初始a和b的一个副本而已;
所以无论在swap函数内部对a和b做任何改变,都不会影响初始的a和b的值
在传地址时:
实参和形参使用的是同一内存的空间
C语言里,参数传递都是值传递。也就是说,我们所认为的传指针也是传值,只不过它的值是指针;
如果想要改变入参内容,则需要传该入参的地址(指针和引用都是类似的作用),通过解引用修改其指向的内容

函数的调用:

传值调用:函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参
传址调用:传址调用是把函数外部创建变量的内存地址传递给函数参数的一种函数调用的方式;这种传参方式可以让函数和函数外边的变量建起真正的联系,也就是函数内部可以直接操作函数外部的变量

练习
案例一:
写一个函数:判断一个数是不是素数

#include<math.h>
int is_prime(int n)
{
	//2到n-1试除
	//2到sqrt(n)试除
	//1不是素数
	int j = 0;
	for (j = 2; j <= sqrt(n); j++)
	{
		if (n % j == 0)
		{
			return 0;//不是素数
		}
	}
	return 1;//是素数
}

int main()
{
	//打印100-200之间的素数
	int i = 0;
	for (i = 100; i <= 200; i++)
	{
		//判断i是否为素数
		if (is_prime(i) == 1)
		{
			printf("%d ",i);
		}
	}
	return 0;
}

运行结果:
案例二:
写一个函数判断一年是不是闰年

int is_leap_year(int y)
{
	return (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0));
}

int main()
{
	//1000-1050年的闰年
	int y = 0;
	for (y = 1000; y <= 1050; y++)
	{
		//判断y是不是闰年
		if (is_leap_year(y) == 1)
		{
			printf("%d ",y);
		}
	}
	printf("\n");
	return 0;
}

运行结果:
案例三:
写一个函数,实现一个整型有序数组的二分查找,找到了就返回下标,找不到就返回-1

int binary_search(int arr[], int k, int sz)
{
	int left = 0;
	int right = sz - 1;
	while (left <= right)
	{
		int mid = left + (right - left) / 2;
		if (arr[mid] < k)
		{
			left = mid + 1;
		}
		else if (arr[mid] > k)
		{
			right = mid - 1;
		}
		else
		{
			return mid;
		}
	}
	return -1;//找不到
}

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int k = 0;
	scanf("%d",&k);//输入要查找的元素
	int sz = sizeof(arr) / sizeof(arr[0]);
	int ret=binary_search(arr,k,sz);

	if (-1 == ret)
	{
		printf("找不到\n");
	}
	else
	{
		printf("找到了,下标是:%d\n",ret);
	}
	return 0;
}

运行结果:
错误案例:

int binary_search(int arr[], int k)//int arr[]本质是个int* arr
{
	//数组在传参的时候,传递的不是整个数组,传递的是数组首元素的地址
	int sz = sizeof(arr) / sizeof(arr[0]);//1,4/4=1,arr是个指针
	int left = 0;
	int right = sz - 1;
	while (left <= right)
	{
		int mid = left + (right - left) / 2;
		if (arr[mid] < k)
		{
			left = mid + 1;
		}
		else if (arr[mid] > k)
		{
			right = mid - 1;
		}
		else
		{
			return mid;
		}
	}
	return -1;//找不到
}

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int k = 0;
	scanf("%d", &k);//输入要查找的元素
	//int sz = sizeof(arr) / sizeof(arr[0]);
	int ret = binary_search(arr, k);

	if (-1 == ret)
	{
		printf("找不到\n");
	}
	else
	{
		printf("找到了,下标是:%d\n", ret);
	}
	return 0;
}

运行结果:
总结:当数组作为参数传参时,这里实际是同名的指针,而不是整个数组,拷贝进去的只是数组元素的首地址,传进去的不是值而是变量的地址,如果传递整个数组的话,对内存来说将会浪费很大的空间,这里不管数组多大都只传递数组的首地址,所以数组传参是传址。

案例四:
写一个函数,每调用一次这个函数,就会将num的值增加1

void test(int *p)
{
	*p = *p + 1;//(*p)++
}

int main()
{
	int num = 0;
	test(&num);
	printf("%d\n",num);//1

	test(&num);
	printf("%d\n", num);//2

	return 0;
}

运行结果:

函数的嵌套调用:

函数可以嵌套调用,但是不能嵌套定义

void new_line()
{
	printf("hehe\n");
}

void three_line()
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		new_line();
	}
}

int main()
{
	three_line();
	return 0;
}

函数的链式访问:

把一个函数的返回值作为另外一个函数的参数

int main()
{
	//int len = strlen("abcdef");
	
	//printf("len=%d\n",len); //strlen计算字符串的长度时,遇到‘\0’停止计数,不会统计‘\0’;而sizeof统计‘\0’(在计算字符数组时)
	printf("%d\n",strlen("abcdef"));

	//printf函数的返回值是打印的字符的个数
	printf("%d",printf("%d",printf("%d",43)));//4321:第一次打印43 第二次打印2(因为包含4和3两个字符) 第三次打印1(因为只显示了一个字符2),
	
	printf("\n");
	return 0;
}

运行结果:
总结:printf()函数也是有返回值的,一般是返回它所输出的数据的字符数目,如果printf()函数执行失败,就会返回一个负数;
大多情况下的程序是不需要处理printf()函数的返回值的,一般是在检测printf()函数是否执行成功的时候会来处理查看这个返回值

函数的声明:

告诉编译器有一个函数叫什么,参数是什么,返回类型是什么,但是具体是不是存在,函数声明决定不了;
函数的声明一般出现在函数的使用之前,要满足先声明后使用
函数的声明一般要放在头文件中的

int main()
{
	int a = 10;
	int b = 20;
	int sum = Add(a, b);

	printf("%d\n", sum);

	return 0;
}

int Add(int x, int y)
{
	return x + y;
}

当我们运行的时候,可以发现编译器发出了一个警告:warning C4013: “Add”未定义;假设外部返回 int,说明这是一种不规范的写法。函数的声明一般出现在函数的使用之前,要满足先声明后使用

改进:

//形参的名字可以省略
int Add(int x, int y);//或者int Add(int,int);

int main()
{
	int a = 10;
	int b = 20;
	int sum = Add(a, b);

	printf("%d\n", sum);

	return 0;
}

int Add(int x, int y)
{
	return x + y;
}

函数的定义:

函数的定义是指函数的具体实现,交代函数的功能实现;
一般情况下,函数的声明和定义是分开的

函数的声明一般是放在头文件add.h里的:
函数的定义一般是放在源文件add.c里的:
在主函数test.c中,要想使用该函数的话,需要包含对应的头文件:#include"add.h"
(一般情况,个人写的头文件往往要用“ ”,比如:#include"add.h";调用库函数时,头文件用<>,比如:#include<stdio.h>)

函数递归:

什么是递归?程序调用自身的编程技巧称为递归;只需少量的程序就可描述出解决过程所需要的多次重复计算,大大减少了程序的代码量;
递归的主要思考方式在于:把大事化小

int main()
{
	printf("hehe\n"); // warning C4717: “main”: 如递归所有控件路径,函数将导致运行时堆栈溢出
	
	main();
	return 0;
}

递归的必要条件
存在限制条件,当满足这个限制条件的时候,递归便不再继续;
每次递归调用之后越来越接近这个限制条件

案例一:
接收一个整型值(无符号),按照顺序打印它的每一位

void print(int n)
{
	if (n > 9)
		print(n/10);

	printf("%d ",n%10);
}

int main()
{
	unsigned int num = 0;
	scanf("%d",&num);//1234

	print(num);//print函数可以把num的每一位按顺序打印出来

	printf("\n");
	return 0;
}

运行结果:
分析:a.从主函数开始,当从键盘输入1234后,开始第一次的print函数调用;第一次调用print函数时,此时的n=1234。由于n>9成立,所以进入if判断并进行第二次的print函数调用;
b.第二次print函数调用时的参数是:n/10,也就是n=1234/10=123,此时的n>9依旧成立;那么此刻则要进入if判断并进行第三次的print函数调用;
c.第三次print函数调用时的参数是:n/10,也就是n=123/10=12,此时的n>9依旧成立;那么此刻则进入if判断并进行第四次的print函数调用;
d.第四次print函数调用时的参数是:n/10,也就是n=12/10=1,此时的n<9,则执行printf("%d “,n%10);打印输出1,那么第四次的print函数彻底执行完毕,则返回到第三次print函数调用中的print(n/10)语句;
e.第三次的print函数中的n=12,在if判断语句中的print(n/10)执行完毕后,则执行printf(”%d “,n%10);打印输出2,那么第三次的print函数彻底执行完毕,则返回到第二次print函数调用中的print(n/10)语句;
f.第二次的print函数中的n=123,在if判断语句中的print(n/10)执行完毕后,则执行printf(”%d “,n%10);打印输出3,那么第二次的print函数彻底执行完毕,则返回到第一次print函数调用中的print(n/10)语句;
g.第一次的print函数中的n=1234,在if判断语句中的print(n/10)执行完毕后,则执行printf(”%d ",n%10);打印输出4,那么第一次的print函数彻底执行完毕,则返回到主函数中的print(num),此时的函数递归调用彻底结束。

案例二:
编写函数不允许创建临时变量,求字符串的长度

#include<string.h>
//带临时变量
int my_strlen(char* str)
{
	int count = 0;//统计字符的个数
	while(*str != '\0')//*是解引用的意思
	{
		count++;
		str++;//str是指针,存放地址
	}
	return count;
}

//不带临时变量
int my_strlen(char* str)
{
	if (*str != '\0')
	{
		return 1 + my_strlen(str+1);
	}
	else
	{
		return 0;
	}
}

int main()
{
	char arr[] = "abcdef";//a b c d e f \0
	//char* str = arr;
	//int len = strlen(arr);
	int len = my_strlen(arr);//不包含\0,读取到\0则停止

	printf("%d\n",len);
	return 0;
}

运行结果:
注意:当递归到一定限度时会出现Stack overflow——栈溢出,所以递归调用时,要加上一定的限制条件

**递归的缺点:**递归调用,占用空间大;递归太深,易发生栈溢出;可能存在重复计算

递归与迭代:

迭代:利用变量的原值推算出变量的一个新值,如果递归是自己调用自己的话,迭代就是A不停的调用B。
迭代与普通循环的区别:迭代时,循环代码中参与运算的变量同时是保存结果的变量,当前保存的结果作为下一次循环计算的初始值。
递归与普通循环的区别:循环是有去无回,而递归则是有去有回(因为存在终止条件)。

在循环的次数较大的时候,迭代的效率明显高于递归。递归中一定有迭代,但是迭代中不一定有递归。能用迭代的不用递归,递归调用函数,浪费空间,并且递归太深容易造成堆栈的溢出。

案例一:
求n的阶乘(不考虑溢出)

//递归版本
int fac(int n)
{
	if (n <= 1)
		return 1;
	else
		return n * fac(n-1);
}

int main()
{
	int n = 0;
	scanf("%d",&n);
	int ret = fac(n);

	printf("%d\n",ret);
	return 0;
}

运行结果:
案例二:
求第n个斐波那契数
斐波那契数列:1 1 2 3 5 8 13 21 34 55…(前两个数相加等于第三个数)
fib(n):n<=2,1;n>2,fib(n-1)+fib(n-2)

//递归版本
int fib(int n)
{
	if (n <= 2)
		return 1;
	else
		return fib(n - 1) + fib(n-2);
}

int main()
{
	int n = 0;
	scanf_s("%d",&n);
	int ret = fib(n);
	printf("%d\n",ret);

	return 0;
}

运行结果:
通过以上两个案例,我们发现:
在使用fac函数求10000的阶乘(不考虑结果的正确性),程序会崩溃;
在使用fib这个函数的时候求第50个斐波那契数字的时候会特别消耗时间
为什么呢?
我们发现fib函数在调用的过程中很多计算其实在一直重复。
如果我们把代码修改一下:

int count = 0;//全局变量
int fib(int n)
{
	if (n == 3)
		count++;
	if (n <= 2)
		return 1;
	else
		return fib(n - 1) + fib(n - 2);
}

int main()
{
	int n = 0;
	scanf("%d", &n);

	int sum = fib(n);
	printf("%d\n", sum);

	printf("count=%d\n", count);

	return 0;
}

运行结果:
通过运行结果可知,在计算第30个斐波那契数的时候,第3个斐波那契数以及被计算了317811次,这说明fib函数在调用的过程中很多计算其实在一直重复。

那如何解决上述的问题?
将递归改写成非递归;
使用static对象替代nonstatic 局部对象。在递归函数设计中,可以使用static 对象替代nonstatic 局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放nonstatic 对象的开销,而且static 对象还可以保存递归调用的中间状状态并且可为各个调用层所访问。

那如何对案例一二进行修改以提升程序的运行效率呢?在这里我们主要考虑用迭代的方法代替递归。

案例一改进版:

int fac(int n)
{
	int i = 0;
	int ret = 1;
	for (i = 1; i <= n; i++)
	{
		ret = ret * i;
	}
	return ret;
}

int main()
{
	int n = 0;
	scanf("%d",&n);
	int ret = fac(n);

	printf("%d\n",ret);
	return 0;
}

案例二改进版:

int fib(int n)
{
	int a = 1;
	int b = 1;
	int c = 1;
	
	while (n > 2)
	{
		c = a + b;
		a = b;
		b = c;
		n--;
	}
	return c;
}

int main()
{
	int n = 0;
	scanf_s("%d",&n);
	int ret = fib(n);
	printf("%d\n",ret);

	return 0;
}

提示:
1.许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰;
2.但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些;
3.当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时的开销。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/541636.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关于江苏专转本的十大真相,值得一看

【真相1】专转本考试题主要是大学相关科目的骨干老师出的。他们较长时间从事相应课程教学&#xff0c;专业领域较宽&#xff0c;学术造诣较高。具有副高及以上职称&#xff0c;年龄—般在55周岁以下。VX:hhkb5200【真相2】专转本考试题"紧扣《考试大纲》&#xff0c;大家要…

C++11 新特性

文章目录 &#x1f36a;统一列表初始化&#x1f36a;左值引用&#xff0c;右值引用&#x1f95b;概念和作用&#x1f95b;使用场景 &#x1f36a;完美转发&#x1f36a;可变参数模板 C11是C的一次大更新&#xff0c;出现了很多实用的语法和特性&#xff0c;所以我们很有必要学习…

[网络安全]DVWA之XSS(Stored)攻击姿势及解题详析合集

[网络安全]DVWA之XSS&#xff08;Stored&#xff09;攻击姿势及解题详析合集 XSS(Stored)-low level源代码姿势基于Message板块基于Name板块 XSS(Stored)-medium level源代码姿势双写绕过大小写绕过Xss标签绕过 XSS(Stored)-high level源代码姿势&#xff1a;Xss标签绕过 XSS(S…

汇编学习教程:灵活寻址(四)

引言 在上篇博文中&#xff0c;我们学习了 [bxsi] 的灵活寻址形式&#xff0c;由此讲解了汇编中的多重循环实现。那么本篇博文中&#xff0c;我们将继续学习灵活寻址其他实现形式。 本次学习从一道编程案例开始学起。 编程示例如下&#xff1a; assume cs:code,ds:datadata…

【Jmeter第二章】将Jmeter界面切换为中文显示

1、Jmeter临时切换为中文显示 注意&#xff1a;上面的配置只能保证本次运行是中文&#xff0c;如果要永久中文&#xff0c;需要修改Jmeter的配置文件 2、通过修改Jmeter配置文件设置为中文显示 1、在 Jmeter/bin目录下&#xff0c;找到&#xff1a;jmeter.properties 文件 2…

K_A39_012 基于STM32驱动W25Q32 模块读写数据 串口+OLED0.96显示

K_A39_012 基于STM32驱动W25Q32 模块读写数据 串口OLED0.96显示 所有资源导航一、资源说明二、基本参数参数引脚说明 三、驱动说明时序对应程序: 四、部分代码说明1、接线引脚定义1.2、STM32F103C8T6W25Q32 模块 五、基础知识学习与相关资料下载六、视频效果展示与程序资料获取…

LeetCode 429. N 叉树的层序遍历

429. N 叉树的层序遍历 描述 给定一个 N 叉树&#xff0c;返回其节点值的层序遍历。&#xff08;即从左到右&#xff0c;逐层遍历&#xff09;。 树的序列化输入是用层序遍历&#xff0c;每组子节点都由 null 值分隔&#xff08;参见示例&#xff09;。 示例 示例1 输入&…

货拉拉Java开发实习

目录 1.Java的重载和重写有什么区别2.什么情况下需要用到重载3.有很多个字符串和变量&#xff0c;需要把它们加起来&#xff0c;这时候用String会有什么问题4.有没有其它的替代方案5.StringBuffer和StringBuilder有什么区别6.一个自定义对象&#xff0c;分别创建了两个实例&…

5分钟梳理银行测试,文末附带实战项目

很多银行招聘都要求有相关从业经验&#xff0c;这对于想跨入这个岗位的0经验从业同学可真犯了难 “你都不让我上岗&#xff0c;我哪来的工作经验呢&#xff1f;” 为了解决这个问题&#xff0c;小柠檬整理了本篇文章&#xff0c;从3个方面介绍银行项目是如何进行测试的 银行…

springboot---IoC 和 AOP

目录 引语IoC传统开发模式的弊端控制反转和依赖注入 AOP面向对象的局限性面向切面编程 总结 引语 Inversion of Control&#xff0c;缩写为IoC&#xff1a;控制反转 Aspect-oriented programming&#xff0c;缩写为AOP&#xff1a;面向切面编程 IoC和AOP是spring框架最核心的…

VMware Workstation 与 Device/Credential Guard 不兼容.在禁用 Device/Credenti

这个时候我们需要去关掉几个功能 1、关闭Hyper-V 打开控制面板首页&#xff0c;找到“程序”&#xff0c;然后找到“启用或关闭Windows功能”&#xff0c;找到“Hyper-V”&#xff0c;有勾中的全部都取消掉&#xff0c;如果这一步操作失败&#xff0c;不要紧&#xff0c;继续…

使用马哈鱼SQLFlow分析聚合函数中的数据流列

聚合函数通常将列作为参数&#xff0c;在本文中&#xff0c;我们将讨论在用作函数参数的列和聚合函数之间创建什么样的数据流。 1. COUNT() COUNT()可以采用COUNT()&#xff0c;也可以采用任何列名&#xff0c;甚至可以采用空参数。如果参数为空或为列&#xff0c;则参数和函…

DatenLord前沿技术分享 No.25

达坦科技专注于打造新一代开源跨云存储平台DatenLord&#xff0c;通过软硬件深度融合的方式打通云云壁垒&#xff0c;致力于解决多云架构、多数据中心场景下异构存储、数据统一管理需求等问题&#xff0c;以满足不同行业客户对海量数据跨云、跨数据中心高性能访问的需求。在本周…

【已解决】使用Arduino调试ARM时编译错误error: ordered comparison of pointer with integer zero的解决方法

在使用Arduino的资源库对STM32编程时&#xff0c;出现&#xff1a; error: ordered comparison of pointer with integer zero (byte* {aka unsigned char*} and int) 编译错误的解决方法。 Arduino因其开源和易用性&#xff0c;丰富的三方资源&#xff0c;受到很多人的喜欢…

Android无线调试

1、首先在系统环境变量——》新建——》"ANDROID_ADB_SERVER_PORT"&#xff0c;值&#xff1a;手机的端口号 2、通过adb kill-server&#xff0c;adb start-server&#xff0c;重启abd 3、最后使用&#xff1a;adb connect ip:port&#xff08;如&#xff1a;192.16…

【SpringBoot】SpringBoot 纯后端项目如何自定义异常页面(Whitelabel Error Page)

文章目录 背景安排方案步骤 验证 背景 一个短链服务&#xff0c;业务将长链接给我&#xff0c;我转换成短地址&#xff0c;用户访问短地址时&#xff0c;我再做redirect&#xff1b;没有前端&#xff0c;纯后端项目短链会有过期时间&#xff0c;过期后将返回错误信息某一天一个…

GPT 专业应用:如何让GPT策划方案

身为一名职场打工人&#xff0c;或多或少会面临需要写策划案的难题。 不管是策划一场线下活动&#xff0c;还是策划业务发展的方向&#xff1b; 甚至到生活中还需要策划婚礼&#xff0c;策划房屋装修&#xff0c;策划和朋友的聚会等等。那么如何快速积累经验&#xff0c;找准…

JavaScript全解析——Ajax教程(上)

AJAX 是Asynchronous JavaScript And XML的缩写。 它不是一种编程语言。它是一种基于HTML、CSS、JavaScript 和 XML&#xff0c;让开发更好、更快和更有互动的 Web 应用的技术。 什么是ajax 认识前后端交互 前后端交互就是前端与后端的一种通讯方式&#xff0c;主要使用的技…

关于一个C++项目:高并发内存池的开发过程(二)

文章目录 内存释放操作的总述thread cachecentral cachepage cachecentral cache的TODO实现何时维护这张映射表&#xff1f; tc_dealloc的修改申请大内存的适配写在最后 上篇文章梳理了内存申请操作的流程&#xff0c;大概测试了一下&#xff0c;没有发现什么问题。这篇文章将梳…

Simulink 自动代码生成电机控制:软件在环测试(SIL)步骤总结

目录 前言 模型配置 SIL模型生成 模型仿真对比 总结 前言 电机模型仿真可以叫做模型在环测试&#xff08;MIL&#xff09;&#xff0c;至于SIL就是软件在环仿真测试&#xff0c;说白了就是验证生成的代码有没有问题&#xff0c;如果有问题那在模型里面修复&#xff0c;不要…