[C语言]C语言基础学习第五篇:函数

函数这一篇章我们主要学习一下内容:

目录

函数是什么?

库函数:

strcpy:

memset:

 自定义函数

比如我们想写一个哪个数字更大的函数:

交换两个数的函数:

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

函数的递归

栈溢出


函数是什么?

c语言中函数的分类:

1. 库函数

2. 自定义函数

库函数:

库函数相当于一个已经包装好的工具,我们只要用就可以了。我没有必要重复造轮子。

我们可以到
网站来看具体有哪些库函数。

  • IO函数:printf, scanf, getchar, putchar
  • 字符串操作函数:strcmp, strlen
  • 字符操作函数:toupper
  • 内存操作函数:memcpy, memcmp, memset
  • 时间/日期函数:time
  • 数学函数:sqrt, pow
  • 其他库函数:

strcpy:

#include<string.h>
int main()
{
	char arr1[20] = { 0 };
	char arr2[] = "hello";
	strcpy(arr1, arr2);
	printf("%s\n", arr1);
	return 0;
}

str 字符串, cpy = copy, 就可以简单理解为复制字符串, strcpy(a, b), 就是将b的内容复制到a的内容中。可以点击标题仔细看清细节。

memset:

​翻译过来就是把起始地址,你想要更改的符号,和更改符号的数量,就能够将一个起始地址的内容更改。

int main()
{
	char arr[] = "zaoshanghaowoaichibingchiling";
	memset(arr, 'x', 5);
	printf("%s\n", arr);
	return 0;
}

 自定义函数

我们的库函数始终是有限的,所以我们就需要创建一些自定义函数。

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

比如我们想写一个哪个数字更大的函数:

int get_max(int x, int y)
{
	if (x > y)
	{
		return x;
	}
	else
		return y;

}

int main()
{
	int a = 10;
	int b = 20;
	int max = get_max(a, b);
	printf("%d\n", max);
	return 0;
}

主函数里调用的get_max函数内的参数都是实际存在的a和b——实际参数(实参),而我们创建的函数get_max的括号内x和y都是只在函数内有用的参数——形式参数(形参),到了函数外就被销毁了。所以参数名和主函数的实参一样也没有问题。只有要调用的时候形参才会开辟空间。

交换两个数的函数:

void Swap(int x, int y)// 在被调用的时候,实参传给形参,形参是实参的一份拷贝,改变形参,不能改变实参
{
	int tmp = 0;
	tmp = x;
	x = y;
	y = tmp;
}
int main()
{
	int a = 10;
	int b = 20;
	// 交换2个整型变量的值
	printf("交换前:a = %d, n = %d\n", a, b);
	Swap(a, b);
	printf("交换后:a = %d, n = %d\n", a, b);

	return 0;
}

 

 我们发现结果并没有交换。

 我们通过调试我们发现,我们的形参x和y的地址和对应的a和b的地址完全不同,所以修改值的时候,我们的a和b不会被修改,所以我们就需要用指针,直接找到a和b的地址然后改变对应的值。

void Swap(int* x, int* y)
{
	int tmp = 0;
	tmp = *x;
	*x = *y;
	*y = tmp;
}
int main()
{
	int a = 10;
	int b = 20;
	int* pa = &a;
	int* pb = &b;
	// 交换2个整型变量的值
	printf("交换前:a = %d, n = %d\n", a, b);
	Swap(pa, pb);
	printf("交换后:a = %d, n = %d\n", a, b);

	return 0;
}

 第一个swap是传值调用,第二个swap是传址调用。

练习:

1.打印100-200的素数。

int is_prime(int i)
{
	int a = 2;
	for (a = 2; a <= sqrt(i - 1); a++)
	{
		if (i % a == 0)
		{
			return 0;
		}
	}
	return 1;
}

int main()
{
	int i = 0;
	for (i = 100; i <= 200; i++)
	{
		//判断i是不是素数
		int x = is_prime(i);
		if (x)
		{
			printf("%d\n", i);
		}
	}
	return 0; 
}

2.写一个函数,实现一个整数有序数组的二分查找。

int search(int a, int arr[], int n)
{
	int Left = 0;
	int Right = n - 1;
	int Mid = (n - 1) / 2;
	while (Left <= Right)
	{
		if (a > arr[Mid])
		{
			Left = Mid + 1;
			Mid = (Left + Right) / 2;
		}
		else if (a < arr[Mid])
		{
			Right = Mid - 1;
			Mid = (Left + Right) / 2;
		}
		else if (a == arr[Mid])
			return 1;
	}
}
int main()
{
	int a = 7;
	int arr[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int n = sizeof(arr) / sizeof(arr[0]);
		if (search(a, arr, n) == 1)
		{
			printf("找到了");
		}
		else 
		{
			printf("没找到");
		}

	return 0;
}

3.每调用一次,num增加1

void Add(int* x)
{
	(*x)++;
}

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

	return 0;
}

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

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

void test2()
{
	test3();
}

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

但是我们不能够嵌套定义。

int main()
{
	int len = strlen("abc");
	printf("%d\n", len);
	printf("%d\n", strlen("abc"));// 这个就是链式访问,就是一个函数的返回值作为别的函数的参数。
	return 0;
}

int main()
{
	char arr1[] = "abc";
	char arr2[20] = {0};
	printf("%s\n", strcpy(arr2, arr1));
}

int main()
{
	printf("%d", printf("%d", printf("%d", 43)));
	return 0;
}

 这里的结果为什么是4321呢,我们去cplusplus网站看一下printf函数的定义。

当我们的函数定义在后面的时候,我们就需要函数的声明。

int main()
{
	int a = 10;
	int b = 20;
	//int Add(int, int);
	int c = Add(a, b);
	printf("%d\n", c);
	return 0;
}

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

}

 只要把主函数里面的那句注释掉的语句还原就能声明成功

int Add(int, int);

 但在一般情况下,函数的声明是放在函数的头文件里面的。

我们声明的时候放在.h头文件,定义的时候放在.c文件。

 

 然后我们要用加函数和减函数的时候在我们要用的文件里使用。

#include "add.h"
#include "Sub.h"//函数的定义放在对应的.c文件和声明放在对应的.h文件。
int main()
{
	int a = 10;
	int b = 20;
	int c = Add(a, b);
	int d = Sub(a, b);
	printf("%d\n", c);
	printf("%d\n", d);
	return 0;
}

 将声明和定义分别放在两个不同的文件的意义在于,不想被别人直接看到函数的源码。

函数的递归

函数的调用就是自己调用自己。

int main()
{
	printf("%d\n", 1);
	main();
	return 0;
}

这里死循环的原因就是我们一直循环,没有停止循环的条件,直到栈溢出。

递归的主要思考方式:把大事化小。

递归的两个必要条件:

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

但即使这样递归也有可能有错误。

void test(int n)
{
    if(n < 100000)
    {
        test(n + 1);
    }
}


int main()
{
    test(1);
    return 0;
}

 这里的原因是因为条件的达成条件过于难,不断循环导致栈溢出。

栈溢出

写递归代码的时候:
1. 不能死递归,都要有跳出条件,每次递归逼近跳出条件

2. 递归层次不能太深。

练习1:

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

void print(unsigned int n)
{
	if (n > 9)
	{
		print(n / 10);
	}
	printf("%d", n % 10);
}


int main()
{
	unsigned int num = 0;
	scanf("%u", &num);// 1234 unsigned int 的scanf接受&n
	print(num);
	return 0;
}
// print(1234)
// print(123) 4
// print(12) 3 4
// print(1)  2 3 4
// print(0) 1 2 3 4 

练习2

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

// 模拟实现一个strlen函数
int MyStrlen(char* str)
{
	int count = 0;
	while (*str != '\0')
	{
		count++;
		str++;
	}
	return count;

}
int main()
{
	char arr[] = "bit";
	printf("%d\n", strlen(arr));
	printf("%d\n", MyStrlen(arr));
	return 0;
}

 这个代码并不符合要求,因为它临时创建了变量。

// 模拟实现一个strlen函数
int MyStrlen(char* str)
{
	if (*str != '\0')
	{
		return 1 + MyStrlen(str + 1);
	}
	else
		return 0;
}
int main()
{
	char arr[] = "bit";
	printf("%d\n", strlen(arr));
	printf("%d\n", MyStrlen(arr));
	return 0;
}
// MyStrlen("bit")
// 1 + MyStrlen("it")
// 1 + 1 + MyStrlen("t")
// 1 + 1 + 1 MyStrlen("")
// 1 + 1 + 1 + 0

练习3:

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

int Fac(int n)
{
	if (n != 0)
	{
		return n * Fac(n - 1);
	}
	else
		return 1;
}


int main()
{
	int n = 0;
	scanf("%d", &n);
	int num = Fac(n);
	printf("%d\n", num);
	return 0;
}

练习4:

求第n个斐波那契数

int Fei(int n)
{
	if (n <= 2)
		return 1;
	else
		return Fei(n - 2) + Fei(n-1);

}

int main()
{
	int n = 0;
	scanf("%d", &n);
	int num = Fei(n);
	printf("%d\n", num);
	return 0;
}

如果我们输入50的时候,我们会发现效率非常低,结果出来的非常慢。

当我们计算这个算法在n==3的时候计算多少次的时候,输入40找第40个斐波那契数,我们发现count的数量极大。

 我们发现这个问题用递归的方式,效率非常的低。


int Fei(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("%d", &n);
	int num = Fei(n);
	printf("%d\n", num);
	return 0;
}

将前三个元素分别设为a, b, c。我们通过a和b相加的值存储到c中,b的值传到a,c的值传到b。进行传递,参数一直是只有a,b和c。

这就是迭代的方式(迭代)

但是我们发现有问题:

  • 在使用fei函数的时候如果我们要计算第50个斐波那契数的时候特别耗费时间。
  • 视同factorial函数求100000的阶乘(不考虑结果的正确性),程序会崩溃

函数递归的经典问题:

1. 汉诺塔问题

2. 青蛙跳台问题

  • 37
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值