C语言学习笔记函数

Posted 小倪同学 -_-

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言学习笔记函数相关的知识,希望对你有一定的参考价值。

函数是什么

维基百科中对函数的定义:子程序

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

函数的分类

1.库函数

为什么会有库函数?
1.我们知道在我们学习C语言编程的时候,总是在一个代码编写完成之后迫不及待的想知道结果,想把这个结果打印到我们的屏幕上看看。这个时候我们会频繁的使用一个功能:将信息按照一定的格式打印到屏幕上( printf )。
2.在编程的过程中我们会频繁的做一些字符串的拷贝工作( strcpy )。
3.在编程是我们也计算,总是会计算n的k次方这样的运算( pow )。
像上面我们描述的基础功能,它们不是业务性的代码。我们在开发的过程中每个程序员都可能用的到,为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数 ,方便程序员进行软件开发。

注意:使用库函数时必须包含对应的#include头文件

2.自定义函数

如果库函数能干所有的事情,那还要程序员干什么 ?
所有更加重要的是自定义函数。
自定义函数和库函数一样,有函数名,返回值类型和函数参数。但是不一 样的是这些都是我们自己来设计。这给程序员一个很大的发挥空间。

函数的组成:

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

举例
写一个函数可以找出两个函数中的最大值

#include<stdio.h>
int get_max(int m, int n)
{
	if (m > n)
		return m;
	else
		return n;
}
int main()
{
	int a = 10;
	int b = 20;
	int ret = get_max(a, b);
	printf("%d\\n", ret);
	return 0;
}

函数的参数

写一个函数交换两个数

void swap1(int m, int n)
{
	int c = 0;
	c = n;
	n = m;
	m = c;
}
void swap2(int* i, int* j)
{
	int d = 0;
	d = *i;
	*i = *j;
	*j = d;
}
int main()
{
	int a = 10;
	int b = 20;
	printf("交换前:a=%d b=%d\\n", a, b);
	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函数并没有达到了预期的效果,如下图
在这里插入图片描述
这是为什么呢

上面Swap1和Swap2函数中的参数m,n, i,j 都是形式参数。在main函数中传给Swap1的a,b和传给Swap2函数的&a,&b是实际参数。swap1在被调用的时候,实参传给形参,其实形参是实参的一份临时拷贝改变形参,不能改变实参。

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

函数的调用

1.传值调用

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

2.传址调用

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

3.练习

1.写一个函数求100到200之间的素数
2.写一个函数判断一年是不是闰年。
3.写一个函数,实现一个整形有序数组的二分查找。
4.写一个函数,每调用一次这个函数,就会将num的值增加1

#include<stdio.h>
#include<math.h>
int is_prime(int n)
{
	int j = 0;
	for (j = 2; j <= sqrt(n); j++)
	{
		if (n%j == 0)
			return 0;
	}
	return 1;
}
int main()
{
	int i = 0;
	int count=0;
	for (i = 100; i <= 200; i++)
	{
		if (is_prime(i) == 1)
		{
			count++;
			printf("%d ", i);
		}
	}
	printf("\\n共有%d个\\n", count);
	return 0;
}

在这里插入图片描述
2.

#include<stdio.h>
int is_leap_year(int n)
{
	return ((n % 4 == 0 && n % 100 != 0) || (n % 400 == 0));
}
int main()
{
	int y = 0;
	for (y = 1000; y <= 2000; y++)
	{
		if (is_leap_year(y) == 1)
		{
			printf("%d ", y);
		}
	}
	return 0;
}

在这里插入图片描述
3.

#include<stdio.h>
int binary_search(int a[], int k, int s)
{
	int left = 0;
	int right = s - 1;
	int mid = 0;
	while (left <= right)
	{
		mid = (left + right) / 2;
		if (a[mid] < k)
		{
			left = mid + 1;
		}
		else if (a[mid]>k)
		{
			right = mid - 1;
		}
		else
		{
			return mid;
		}
	}
	return -1;
}
int main()
{
	int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int k = 7;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int ret = binary_search(arr, k, sz);
	if (-1 == ret)
	{
		printf("找不到!\\n");
	}
	else
	{
		printf("找到了,arr[%d]=%d\\n", ret,k);
	}
	return 0;
}

在这里插入图片描述
4.

#include<stdio.h>
void Add(int* p)
{
	(*p)++;
}
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 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;
}

链式访问

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

#include<stdio.h>
#include<string.h>
int main()
{
	int len = strlen("abc");
	printf("%d\\n", len);
	//链式访问
	printf("%d\\n", strlen("abc"));
	return 0;
}

函数的声明和定义

函数声明

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

函数定义

函数的定 义是指函数的具体实现,交待函数的功能实现。

#include<stdio.h>
int main()
{
	int a = 10;
	int b = 20;

	//函数声明一下 - 告知
	int Add(int x, int y);

	int c = Add(a, b);
	printf("%d\\n", c);
	return 0;
}

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

编译器读取文件是从上往下读取的,如果没有声明,编译器可能不认识那个函数。一般来说,函数的定义都是写在主函数之前的,这时便不需要声明。函数的定义和声明多用于跨文件调用中。

函数的递归

什么是递归?

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

递归的两个必要条件

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


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

#include<stdio.h>
void print(unsigned int n)
{
	if (n > 9)
	{
		print(n / 10);
	}
	printf("%u ", n % 10);
}
int main()
{
	unsigned int num = 0;
	scanf("%u", &num);
	print(num);
	return 0;
}

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

#include<stdio.h>
int Fac(int m)
{
	if (m > 1)
		return m*Fac(m - 1);
	else
		return 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 …(前两个数为1,后面的数为前两项之和)

我们可以用递归求得这样的解法

int Fib(int m)
{
	if (m <= 2)
		return 1;
	else
		return Fib(m - 1) + Fib(m - 2);
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = Fib(n);
	printf("%d\\n", ret);
	return 0;
}

在我们运行时,当输入数字较大时,运行会非常迟缓,这是为什么呢?
在这里插入图片描述
如上图,当我们想计算第50位斐波那契数时就要计算48位和49位,接着就要计算46,47位和47,48位…这用递归进行了大量重复的计算,导致运行效率低下。

下面换一种思路,取斐波那契数列前面三个数记为a,b,c。每次同时移动a,b,c这三个数:把先前的b赋值给a,把c赋值给b,c为a,b的和。这样c就为我们要求的值。
在这里插入图片描述

将上述思路转化为代码

#include<stdio.h>
int Fib(int m)
{
	int a = 1;
	int b = 1;
	int c = 1;
	while (m > 2)
	{
		c = a + b;
		a = b;
		b = c;
		m--;
	}
	return c;
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = Fib(n);
	printf("%d\\n", ret);
	return 0;
}

虽然有时递归比非递归的形式更清晰,但是递归可能存在效率低下,栈溢出问题。

函数递归的几个经典题目

  1. 汉诺塔问题
  2. 青蛙跳台阶问题

以上是关于C语言学习笔记函数的主要内容,如果未能解决你的问题,请参考以下文章

C语言学习笔记C语言函数执行成功时,返回1和返回0,究竟哪个好?

PREACT学习笔记

c语言学习笔记_4

python学习笔记012——pdb调试

C#学习笔记——需要注意的基础知识

C语言进阶学习笔记二指针的进阶(重点必看+代码演示+练习)