完全理解C语言函数

文章目录

  • 1.函数是什么
  • 2.C语言中的函数分类
    • 2.1 库函数
      • 2.1.1 如何使用库函数
    • 2.2自定义函数
  • 3.函数的参数
    • 3.1 实际参数(实参)
    • 3.2 形式参数(形参)
  • 4.函数调用
    • 4.1传值调用
    • 4.2 传址调用
    • 4.3 练习
  • 5.函数的嵌套调用和链式访问
    • 5.1 嵌套调用
  • 5.2 链式访问
  • 6.函数的声明和定义
    • 6.1 函数声明
    • 6.2 函数的定义
  • 7.函数递归
    • 7.1 什么是递归
    • 7.2 递归的两个必要条件
      • 7.2.1 练习1
      • 7.2.2 练习2
    • 7.3递归与迭代
      • 7.3.1 练习3
      • 7.3.2 练习4

1.函数是什么

在数学中,我们经常能听到各种函数,什么指数函数,对数函数,三角函数…
在维基百科中对函数的定义为:子程序

  • 在计算机科学中,子程序(subroutine, subprogram, callable unit),是一个大型程序中的某部分代码,由一个或多个语句块组成。它负责完成某项特殊任务,而且相较于其他代码,具有相对的独立性。
  • 一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库

程序中的函数就相当于一座工厂,你提供给工厂一些原材料,工厂就会给你想要的家具。
函数比喻

2.C语言中的函数分类

1.库函数
2.自定义函数

2.1 库函数

为什么要有库函数?
1.当我们在学习C语言编程的时候开始,总是会在屏幕上打印各种的信息,为了打印信息我们用到了,将信息按照一定个数打印到屏幕的printf
2.当我们想要对一个数求它的根号时的sqrt
3.当我们想要对一个数求n次幂时的pow
当我用上面这些函数的时候有没有想到为什么他们可以实现我们的需求。
这是因为,在我们开发的过程当中每个程序员都可能会用到,为了支持可移植和提高按程序的效率,C语言提高提供了一系列类似的库函数,方便程序员进行软件开发。
一个方便学习了解库函数的网站:cplusplus.com
c库

通过这个网站去了解库函数的功能 名字 参数 返回值。
简单总结,C语言常用的库函数有:

IO函数(输入输出函数)
字符串操作函数
字符操作函数
内存操作函数
时间/日期函数
数学函数
其他库函数

如何使用文档学习库函数
以strcpy为例
strcpy
在使用库函数时一定要包含对应的头文件
小知识
C语言并不是直接实现库函数 而是提供了C语言的标准和库函数的约定
如:功能 名字 参数 返回值
库函数的实现一般是由编译器去实现

2.1.1 如何使用库函数

对应库函数是不需要全部记住的,当你需要的时候去查资料就可以了。
查询查询工具:

MSDN(Microsoft Developer Network)
cplusplus.com
cppreference.com (英文版)
cppreference.com (中文版)

对于学习编程来说,英语也很重要。

2.2自定义函数

尽管C语言给我们提供了丰富的库函数,但是对于我们要实现的很多功能来说是远远不给的。为此更加重要的是自定义函数
自定义函数和库函数一样,有函数名,返回值和函数参数。
但是不一样的是这些都是我们自己来设计的,这就给了程序员很大的发挥空间。
函数的组成

ret_type fun_name(paral)
{
	statement;//语句项
}
//ret_type 是返回类型
//fun_name 是函数名
//paral 是函数参数

举例例子,找出两个数中较大的一个数

#include <stdio.h>
int get_max(int x,int y)
{
	return x>y?x:y;
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d",&a,&b);
	int max = get_max(a,b);
	printf("%d\n",max);
	return 0;
}

再来一个,交换两个数的值

#include <stdio.h>
void swap(int x,int y)
{
	int tmp = x;
	x = y;
	y = tmp;
}
int main()
{
	int a = 5;
	int b = 3;
	printf("交换前a = %d,b = %d\n",a,b);
	swap(a,b);
	printf("交换后a = %d,b = %d\n",a,b);
	return 0;
}
//打印结果
//交换前a = 5,b = 3
//交换后a = 5,b = 3

这是为什么呢?

3.函数的参数

3.1 实际参数(实参)

真实传给函数的参数,叫实参。
实参可以是:变量、常量、表达式、函数
无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便于将这些值传递给形参。

3.2 形式参数(形参)

形式参数是指函数名括号中的变量,因为形式参数只有在函数被调用的过程中才能实例化(分配内存单元),所有叫形式参数。形式参数当函数调用之后就自动销毁了。因此形式参数只有在函数中有效。

这也就说明了当实参传递给形参时,形参只是实参的一份临时拷贝。对形参的改变是不会影响到实参的。这也就是在上面交换时a和b没能够交换成功的原因。

#include <stdio.h>
//错误写法
/*void swap(int x,int y)
{
	int tmp = x;
	x = y;
	y = tmp;
}*/
//正确写法
void swap(int* pa, int* pb)
{
	int tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}
int main()
{
	int a = 5;
	int b = 3;
	printf("交换前a = %d,b = %d\n", a, b);
	swap(&a, &b);
	printf("交换后a = %d,b = %d\n", a, b);
	return 0;
}
//打印结果
//交换前a = 5,b = 3
//交换后a = 3,b = 5

下面对实参和形参进行分析:

#include <stdio.h>
void swap1(int x,int y)
{
	int tmp = x;
	x = y;
	y = tmp;
}
void swap2(int* pa, int* pb)
{
	int tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}
int main()
{
	int a = 5;
	int b = 3;
	swap1(a, b);
	printf("swap1:a = %d,b = %d\n", a, b);
	swap2(&a, &b);
	printf("swap2:a = %d,b = %d\n", a, b);
	return 0;
}
//打印结果
//swap1:a = 5,b = 3
//swap2:a = 3,b = 5

swap1swap2函数中的x,y,pa,pb都是形式参数。在main函数中传给 swap1a,b
swap2函数的 &a,&b是实际参数。

调用vs2022的调试窗口,
监视窗口

代码对应的内存分配:
监视窗口
这里可以看到swap1函数在调用的时候,x 和 y 拥有自己的空间,同时拥有了和实参一模一样的内容(图中所标xy的值已交换)。所以我们可以简单的认为:形参实例化后其实相当于实参的一份临时拷贝

4.函数调用

4.1传值调用

函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。

4.2 传址调用

  • 传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。
  • 这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。

4.3 练习

1.写一个函数判断一个数是不是素数,输出100-200间所有的素数。
2.写一个数判断一年是不是闰年,输出1900·2000年间为闰年的年份。
3.写一个函数,实现一个整型有序数组的二分查找。
4.写一个函数,每调一次这个函数,就会将 num 的值增加1。

维基百科对素数 的定义:

质数,又称素数,指在大于1的自然数中,除了1和该数自身外,无法被其他自然数整除的数(也>可定义为只有1与该数本身两个正因数的数)。

试除法
一个数x = a*b 若为合数,一定存在其中一个约数a或b大于sqrt(x)。所以我们只需要枚举到sqrt(x)就可以了。如果在2~sqrt(x)都没有能够整除x的数,那就说明了x为素数。

//1.写一个函数判断一个数是不是素数,输出100-200间所有的素数。
#include <stdio.h>
#include <stdbool.h>// 布尔值(bool)的头文件
#include <math.h>//sqrt的头文件
bool is_prime(int x)
{
	for(int i = 2;i<=sqrt(x);++i)
	{
		if(x%i==0)
			return false;
	}
	return x>=2;
}
int main()
{
	for(int i = 100;i<=200;++i)
	{
		if(is_prime(i))
		{
			printf("%d ",i);
		}
	}
	return 0;
}
//打印结果:
//101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199
//2.写一个数判断一年是不是闰年,输出1900·2000年间为闰年的年份。
#include <stdio.h>
#include <stdbool.h>// 布尔值(bool)的头文件
bool is_leap_year(int year)
{
	return (year%4==0&&year%100!=0||year%400==0);
}
int main()
{
	for(int i = 1900;i<=2000;++i)
	{
		if(is_leap_year(i))
		{
			printf("%d ",i);
		}
	}
	return 0;
}
//打印结果:
//1904 1908 1912 1916 1920 1924 1928 1932 1936 1940 1944 1948 1952 1956 1960 1964 1968 1972 1976 1980 1984 1988 1992 1996 2000

二分查找
二分查找是在有序数里面找一个数最优的一个算法,下面为二分查找的原理。
就像猜数字的游戏,我随便给你给一百以内的数字,比如62。
开始你在猜的时候,肯定会去猜50,以此来排除一半的数,当得知50小了,那1-50就全排除了。继续猜75,猜大了说明75-100都不可能,继续猜62,答对啦。这就是二分查找的精髓,每次排除剩余数的一半。如果需要在2的32次方中猜一个数,也最多只需要猜测32次就可以了。

//3.写一个函数,实现一个整型有序数组的二分查找。
#include <stdio.h>
int binary_search(int arr[],int x,int n)
{
	int left = 0,right = n-1;
	while(left<=right)
	{
		int mid = (left+right)/2;
		if(arr[mid] == x)
		{
			return mid;
		}
		else if(arr[mid]>x)
		{
			right = mid-1;
		}
		else
		{
			left = mid+1;
		}
	}
	return -1;//没找到返回-1
}
int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	int x = 7;
	int ret = binary_search(arr,x,10);
	if(ret == -1)
	{
		printf("没找到!\n");
	}
	else
	{
		printf("找到了,下标为%d\n",ret);
	}
	return 0;
}
//打印结果:
//找到了,下标为6
//4.写一个函数,每调一次这个函数,就会将 num 的值增加1。
#include <stdio.h>
//定义一个全局变量num
int num = 0;
void test()
{
	num+=1;
}
int main()
{
	test();
	test();
	printf("%d ",num);
	return 0;
}
//打印结果:
//2

5.函数的嵌套调用和链式访问

函数和函数之间可以根据实际的需求进行组合,也可以互相调用。

5.1 嵌套调用

#include <stdio.h>
void print()
{
	printf("hello\n");
}
void test()
{
	print();
}
int main()
{
	test();
	return 0;
}

函数可以嵌套调用但不可以嵌套定义。

//以下为嵌套定义,不能这样写!
#include <stdio.h>

void test()
{
	void print()
	{
		printf("hello\n");
	}
}
int main()
{
	test();
	return 0;
}

5.2 链式访问

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

//代码1
#include <stdio.h>
#include <string.h> //strlen的头文件
int main()
{
	printf("%d\n",strlen("hello"));
	return 0;
}
//打印结果:
//5

//代码2
#include <stdio.h>

int main()
{
	printf("%d",printf("%d",printf("%d",43)));//printf 返回是字符个数
	return 0;
}
//打印结果:
//4321

6.函数的声明和定义

6.1 函数声明

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

6.2 函数的定义

函数的定义是指函数的具体实现,交代函数的功能实现.

以Add函数为例
test.h的内容
放置函数的声明

//函数的声明
int Add(int x,int y);

test.c的内容
放置函数的实现

//函数的实现
int Add(int x,int y)
{
	return x+y;
}

这是分文件的书写形式.
值得注意的是:函数定义也是一种特殊的声明,放前面就相当于声明.

7.函数递归

最简单的递归程序.

#include <stdio.h>
int main()
{
	printf("hello!\n");
	main();
	return 0;
}
//死循环打印hello!
//然后栈溢出导致程序死亡。

直白点说递归就是自己调用自己。

7.1 什么是递归

程序调用自身的编程技巧称为递归(recursion)
递归作为一种算法在程序设计语言中广泛应用.一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型问题层层转化为一个于原问题相似的规模较小的问题来求解.
递归策略
只需要少量的代码就可以描述出解题所需要的多重计算,大大的减少程序的代码量.
递归的主要思想在于:把大事化小

7.2 递归的两个必要条件

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

7.2.1 练习1

接受一个整型值(无符号),按照顺序打印它的每一位.
例如:
输入:1234 输出 1 2 3 4

问题拆分,我们要依次打印1 2 3 4。可以将问题拆分成,打印1的尾数,12的尾数,123的尾数,1234的尾数。这4个相似的问题,同时123可以由1234/10得到,其他类似。为此满足了递归的主要思想,把大事化小,且问题类似。所以代码里我们也要将大事化小,为此我们可以将传入的数/10后再调用该函数,但是要注意的是当最后传入的数是一位数时,除10就变成了0,题目可没有让我们打印0,所以我们要加一个限制条件,当传入的数满足大于9时才让它调用自己。写完递归的主要步骤还要完成题目所说的打印,所以最后我们应该打印传入数字的尾数,num%10。

#include <stdio.h>
void print(size_t num)
{
	if (num > 9)//数字为两位数
	{
		print(num / 10);
	}
	printf("%d ",num%10);
}
int main()
{
	size_t num = 1234;
	print(num);
	return 0;
}
//打印结果:
//1 2 3 4

当我们传入1234时,因为满足数字为两位数的条件,再次调用函数print传入123。又因为同样原因再次调用print传入12,又因为同样原因调用print传入1。注意此时,传入的数字已不满足条件,执行打印函数,打印1%10(1),完后本次函数执行完毕,返回上一层,执行打印函数12%10(2)完后,开始返回上一层,执行打印函数123%10(3)完后,开始返回上一层,执行打印函数1234%10(4)完后。完成任务,返回主函数main。根据下图理解。

递归调用图

7.2.2 练习2

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

问题拆分:在求一个字符串长度时,以"hello"为例。我们求它的长度时,因为‘h’的长度肯定是1,所以我们是不是可以转化成求1+“ello”的长度。依次类推还可以写成1+1+“llo”的长度…
最后求到’\0’时,因为’\0’不能作为字符串长度所以+0。依照这个思路写成的代码就是这样。

#include <stdio.h>
/*
//迭代写法
size_t my_strlen(char* s)
{
	size_t count = 0;
	while(*s!='\0')
	{
		s++;
		count++;
	}
	return count;
}
*/
//递归写法
size_t my_strlen(char* s)
{
	if(*s == '\0')
		return 0;
	return 1+my_strlen(s+1);
}
int main()
{
	char ch[] = "hello!";
	size_t ret = my_strlen(ch);//数组名就是数组首元素的地址
	printf("%d\n",ret);	
	return 0;
}
//打印结果:
//6

7.3递归与迭代

7.3.1 练习3

求n的阶乘。(不考虑溢出)

阶乘:n! = n*(n-1)*(n-2)*(n-3)…*3*2*1;
问题拆分
看着上面的式子,我们是不是可以把求n!转化成求n*(n-1)!呢。然后我们在同理就可以把问题一直拆分成一个个很小的相似的问题。代码如下:

#include <stdio.h>
int factorial(int n)
{
	if(n <= 1)
		return 1;
	return n*factorial(n-1);
}
/*
//迭代方法
int factorial(int n)
{
	int ret = 1;
	for(int i = 1;i<=n;++i)
	{
		ret*=i
	}
	return ret;
}
*/
int main()
{
	int n = 0;
	scanf("%d",&n);
	int f = factorial(n);
	printf("%d\n",f);
	return 0;
}

在调试factorial函数时,如果参数过大,那就会报错:stack overflow(栈溢出)这样的信息。
系统分配给程序的栈空间是有限的,但如果出现了死循环或者(死递归),这样有可能导致一直开辟栈空间,最终产生栈空间耗尽的情况,做这样的现象我们称指为栈溢出。

7.3.2 练习4

求第n个斐波那契数 (不考虑溢出)

斐波那契数(部分):1 1 2 3 5 8 13 21 34 …
除第1和第2个外,后面的每个数都等于前两个数的和。所以代码如下:

#include <stdio.h>
int fib(int n)
{
	if(n<=2)
		return 1;
	return fib(n-1)+fib(n-2);
}
/*
//迭代方法
int fib(int n)
{
	int a = 1;
	int b = 1;
	int c = 1;
	for(int i = 2;i<n;++i)
	{
		c = a+b;
		a = b;
		b = c;
	}
	return c;
}
*/
int main()
{
	int n = 0;
	scanf("%d",&n);
	int ret = fib(n);
	printf("%d\n",ret);
	return 0;
}

但是在用递归求解斐波那契数时:

  • 如果我们计算第50个斐波那契数时会特别耗费时间,可是如果用迭代方法就很块,
    为什么呢
    其实在计算斐波那契数数时有很多的重复计算。
    斐波那契数
    光是我列举出来的这几项里,f(47)就计算了3次。
    利用程序来看看f(40) 里发(3)计算了几次吧。
#include <stdio.h>
int num = 0;
int fib(int n)
{
	if(n == 3)
		num++;
	if(n<=2)
		return 1;
	return fib(n-1)+fib(n-2);
}
int main()
{
	fib(40);
	printf("%d\n",num);
	return 0;
}
//打印结果:
//39088169


//代码2,记忆化搜索,不作要求
/*#include <stdio.h>
#include <string.h>
int memo[55];//当你想求更大的数时,修改memo的数组长度即可。
int fib(int n)
{
	if (n <= 2)
		return 1;
	if (memo[n] != -1)
	{
		return memo[n];
	}
	return memo[n] = fib(n - 1) + fib(n - 2);
}
int main()
{
	memset(memo, -1, sizeof memo);
	int n = 0;
	scanf("%d", &n);
	int ret = fib(n);
	printf("%d\n", ret);
	return 0;
}*/

如何解释上述问题呢?

1.递归写成非递归
2.使用static对象代替nonstatic局部变量。在递归函数设计中,可以使用static对象代替nonstatic局部对象(栈对象),这不仅可以减少每次递归调用和返回时产生和释放nonstatic对象的开销,而且static对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。

提示:

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

课后作业

1.汉诺塔问题
2.青蛙跳台阶问题(类斐波那契)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LLMs之gpt_academic:gpt_academic的简介、安装和使用方法、案例应用之详细攻略

LLMs之gpt_academic&#xff1a;gpt_academic的简介、安装和使用方法、案例应用之详细攻略 目录 gpt_academic的简介 1、版本更新历史 版本: 1、新增功能及其描述 新界面&#xff08;修改config.py中的LAYOUT选项即可实现“左右布局”和“上下布局”的切换&#xff09; 所…

【代码随想录】【算法训练营】【第57天】 [卡码99]岛屿数量 [卡码100]岛屿的最大面积

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 卡码网。 day 57&#xff0c;周三&#xff0c;再ding一下~ 题目详情 [卡码99] 岛屿数量 题目描述 卡码99 岛屿数量 LeetCode类似题目200 岛屿数量 解题思路 前提&#xff1a; 思路&#xff1a; 重点&#…

STM32MP135裸机编程:使用软件触发硬件复位

0 参考资料 STM32MP13xx参考手册.pdf 1 使用寄存器实现软件复位 1.1 复位电路概述 重点关注下面标红的路线&#xff1a; 通过这条路线可以清楚看到&#xff0c;我们可以通过设置RCC_MP_GRSTCSETR寄存器让RPCTL&#xff08;复位脉冲控制器&#xff09;给NRST&#xff08;硬件复…

Vue组件如何“传话”?这里有个小秘诀!

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;vue篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来vue篇专栏内容:vue-组件通信 目录 Vue组件通信 &#xff08;1&#xff09; props / $emit 1. 父组件向子组件传…

线段树知识总结

线段树这个东西&#xff0c;这次是第二次学习&#xff0c;怎么说呢&#xff0c;感觉理解还是不是特别的透彻&#xff0c;因此&#xff0c;在后面彻底完学习之后这个东西再改成公开 线段树概念引入 线段树是一种数据结构&#xff0c;其并不算是一种算法&#xff0c;而是一种减…

8.12 矢量图层面要素单一符号使用十五(栅格线渲染边界)

前言 本章介绍矢量图层线要素单一符号中标记符号渲染边界&#xff08;Outline: Marker line&#xff09;的使用说明&#xff1a;文章中的示例代码均来自开源项目qgis_cpp_api_apps 栅格线渲染边界&#xff08;Outline: Raster Line&#xff09; Outline系列只画边界&#xf…

【代码随想录】【算法训练营】【第58天】 [卡码101]孤岛的总面积 [卡码102]沉没孤岛 [卡码103]水流问题 [卡码104]建造最大岛屿

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 卡码网。 day 58&#xff0c;周四&#xff0c;ding~ 题目详情 [卡码101] 孤岛的总面积 题目描述 卡码101 孤岛的总面积 解题思路 前提&#xff1a; 思路&#xff1a; 重点&#xff1a; 代码实现 C语言 […

mac外接显示屏,切换程序坞和启动台在哪个屏幕显示,最实用教程

程序坞和启动项是同步的 首先&#xff0c;程序坞和展开启动项是同步出现在同一个屏幕的&#xff0c;所以只需要把程序坞“呼唤”到指定的显示器就行。 无需设置&#xff0c;动对了鼠标就行 无所谓哪个是主屏&#xff0c;设置中都没有切换程序坞位置的选项&#xff0c; 想要…

vue单独部署到宝塔教程

配置反向代理 注意:如果目标网站是https则写https否则写http 2.关于解决部署后无法刷新,直接报错404 location / { try_files $uri $uri/ /index.html; }

代码随想录算法训练营第四十三天| 121. 买卖股票的最佳时机、122.买卖股票的最佳时机II、 123.买卖股票的最佳时机III

121. 买卖股票的最佳时机 题目链接&#xff1a;121. 买卖股票的最佳时机 文档讲解&#xff1a;代码随想录 状态&#xff1a;做出来了 贪心思路&#xff1a; 因为股票就买卖一次&#xff0c;那么贪心的想法很自然就是取最左最小值&#xff0c;取最右最大值&#xff0c;那么得到的…

新建Vue工程的几种方法

文章目录 vue-clivue/clivue3Viteparcel vue-cli vue-cli是针对构建vue的脚手架CLI2&#xff0c;只能新建vue2工程。 全局安装vue-cli之后&#xff0c;构建vue2项目的格式为&#xff1a; vue init 构建方式 project_name&#xff1a;比如以下5种构建方式 vue init webpack pr…

UNIAPP_顶部导航栏右侧添加uni-icons图标,并绑定点击事件,自定义导航栏右侧图标

效果 1、导入插件 uni-icons插件&#xff1a;https://ext.dcloud.net.cn/plugin?nameuni-icons 复制 uniicons.ttf 文件到 static/fonts/ 下 仅需要那个uniicons.ttf文件&#xff0c;不引入插件、单独把那个文件下载到本地也是可以的 2、配置页面 "app-plus":…

PID算法介绍以及代码实现过程说明

写在正文之前 在上一篇文章就说会在这两天会基于PID写一个文章&#xff0c;这里的原理部分值得大家都看一下&#xff0c;代码部分的实现是基于python的&#xff0c;但是对于使用其他编程语言的朋友&#xff0c;由于我写的很通俗易懂&#xff0c;所以也值得借鉴。 一、PID算法…

Java:JDK、JRE和JVM 三者关系

文章目录 一、JDK是什么二、JRE是什么三、JDK、JRE和JVM的关系 一、JDK是什么 JDK&#xff08;Java Development Kit&#xff09;&#xff1a;Java开发工具包 JRE&#xff1a;Java运行时环境开发工具&#xff1a;javac&#xff08;编译工具&#xff09;、java&#xff08;运行…

偏微分方程笔记(驻定与非驻定问题)

椭圆方程可以看成抛物方程 t → ∞ t\rightarrow\infty t→∞的情况。 抛物&#xff1a; 双曲&#xff1a;

学习aurora64/66b.20240703

简介 The AMD LogiCORE™IP Aurora 64B/66B core是一种可扩展的轻量级高数据速率链路层协议&#xff0c;用于高速串行通信。该协议是开放的&#xff0c;可以使用AMD设备技术实现。 Aurora 64B/66B是一种轻量级的串行通信协议&#xff0c;适用于多千兆位链路 (如下图所示)。它…

微信小程序禁止PC端打开防止白嫖广告或抓接口

前言 晓杰每日靠着微薄的小程序广告度日&#xff0c;继之前检测手机端微信跳过小程序广告插件检测后又发现小程序广告在电脑端经常没广告&#xff0c;导致收入备降&#xff01;虽然每天只有几块钱的收入&#xff0c;哈哈哈&#xff01;那么怎么做到禁止小程序使用电脑端微信打…

nginx的匹配及重定向

一、nginx的匹配&#xff1a; nginx中location的优先级和匹配方式&#xff1a; 1.精确匹配&#xff1a;location / 对字符串进行完全匹配&#xff0c;必须完全符合 2.正则匹配&#xff1a;location ^~ ^~ 前缀匹配&#xff0c;以什么为开头 ~区分大小写的匹配 ~* 不区分…

Unity | Shader基础知识(第十七集:学习Stencil并做出透视效果)

目录 一、前言 二、了解unity预制的材质 三、什么是Stencil 四、UGUI如何使用Stencil&#xff08;无代码&#xff09; 1.Canvas中Image使用Stencil制作透视效果 2.学习Stencil 3.分析透视效果的需求 五、模型如何使用Stencil 1.shader准备 2.渲染顺序 3.Stencil代码语…

【TypeScript】TS入门到实战(详解:高级类型)

目录 第三章、TypeScript的数据类型 3.1 TypeScript的高级类型 3.1.1 class 3.1.1.1 熟悉class类 3.1.1.2 class类继承的两种方式 3.1.1.3 class类的5种修饰符 3.1.2 类型兼容 3.1.3 交叉类型 3.1.4 泛型 3.1.4.1 创建泛型函数 3.1.4.2 泛型函数的调用 3.1.4.3 泛型…