C语言学习记录—函数

本章分为:函数的分类、函数的参数、函数的调用、嵌套调用和链式访问、函数的声明和定义、函数递归。

第一节:函数的分类

库函数:老师推荐的库函数学习工具是MSDN。使用库函数必须包含#include对应的头文件。

自定义函数:返回类型+函数名+函数参数

第二节:函数的参数

实参:传递给函数的参数。可以是常量、变量、表达式、函数等。

形参:形参只有在函数被调用时才实例化,并且只在函数内部有效。形参是实参的一份临时拷贝。

第三节:函数的调用

传值调用:因为函数的形参和实参分别占有不同的内存空间,所以修改形参不能影响实参。

传址调用:这种调用方式是将变量的地址传给函数,因此函数修改了变量后会影响实参。

练习

1. 写一个函数可以判断100~200之间的数是不是素数。

两种方法:
第一种:用2~n-1的数去试除,如果这些数有能整除说明不是素数;反之,是素数。
第二种:假设m不是素数,那么它可以写成m=a*b,(a b不能是1或它本身) 并且a或b中一定有一个数小于m的平方根(<= sqrt(m)) 所以被除数j最大只需要到i的平方根。并且在此基础上可以继续优化。除了2以外,所有偶数都不是素数,遍历的时候从101开始,每次+2
#include <math.h>
int is_prime(int n)
{
	int i = 0;//除数
	for (i = 2; i <= sqrt(n); i++)
	{
		if (n % i == 0)
		{
			return 0;
		}
	}
	return 1;
}
int main()
{
	int n = 0;//100-200之间的数
	for (n = 101; n < 200; n+=2)
	{
		if (is_prime(n) == 1)
			printf("%d ", n);
	}
	return 0;
}

2. 写一个函数判断一年是不是闰年。

闰年判断规则:
1.能被4整除,且不能被100整除。2.能被400整除。
if的条件就是1或2。
int is_leap_year(int year)
{
	if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))
		return 1;
	else
		return 0;
}
int main()
{
	int year = 0;
	for (year = 1000; year <= 2000; year++)
	{
		if (is_leap_year(year) == 1)
			printf("%d ", year);
	}
	return 0;
}

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

实现方法在上一章。如果用函数实现,一定要注意不能再函数内部求数组大小,因为数组名传参,传的是数组的地址。需要将数组元素个数作为参数传过去。
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;//此时左下标left要变成mid+1
		else if (arr[mid] > k)//如果中间元素比要找的元素大,说明范围在左边边
			right = mid - 1;//此时右下标right要变成mid-1
		else
			return mid;
	}
	return -1;
}
int main()
{
	int arr[] = { 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 (ret == -1)
		printf("找不到\n");
	else
		printf("找到了,下标是:%d\n", ret);

	return 0;
}

4. 写一个函数,每调用一次这个函数,就会将 num 的值增加1

两种方法:
1. 传址调用:将变量的地址传过去,用指针变量接收,通过解引用操作符让指针指向的值++
2. 传值调用:函数返回n+1即可。注意不可以像指针方法那样使用++,因为这会先返回后++,导致没有增加。
//传址调用
void Add(int* p)
{
	(*p)++;
}
int main()
{
	int num = 0;
	Add(&num);
	printf("%d\n", num);
	Add(&num);
	printf("%d\n", num);
	return 0;
}

//传值调用
int Add2(int n)
{
	return ++n;
}
int main()
{
	int num = 0;
	num = Add2(num);
	printf("%d\n", num);
	num = Add2(num);
	printf("%d\n", num);
	return 0;
}

第四节:嵌套调用和链式访问

嵌套调用:A函数调用B函数,B函数调用C函数;函数之前可以根据需求相互调用。但不能嵌套定义。
链式访问:把一个函数的返回值作为另外一个函数的参数。
#include <stdio.h>
int main()
{
    printf("%d", printf("%d", printf("%d", 43)));
    return 0;
}

第五节:函数的声明和定义

函数的声明:

  • 声明就告诉编译器有一个什么样的函数(名字、参数、返回类型)。但是否存在声明决定不了。
  • 声明要在函数使用之前,先声明后使用。
  • 声明一般要放在头文件中

函数的定义:就是函数功能的具体实现。

第六节:函数递归

函数递归其实分两个动作,先完成所有的递,再全部归。并且是最后一次调用的自己是第一次返回,也就是深度优先。

void print(unsigned int n)
{
	//如果这里没有if这个限制条件,这个递归会无限调用形成栈溢出
	//内存中的栈区存放着局部变量、函数的形参。函数的每一次调用也会在栈区申请空间
	if (n > 9)			//1234	123	 12  1	
	{
		print(n / 10);	//123	12	 1	
	}
	printf("%d ", n % 10);
}

练习1:接受一个整型值(无符号),按照顺序打印它的每一位。

递归分 递和归,先执行完递延,最后一起回归。
1.n = 1234,大于9,继续递归,n变为1234 / 10 = 123。
2.n = 123,大于9,继续递归,n变为123 / 10 = 12。
3.n = 12,大于9,继续递归,n变为12 / 10 = 1。
4.n = 1,不大于9,结束递归,打印n % 10,即打印1。
5.回溯到n = 12,打印12 % 10,即打印2。
6.回溯到n = 123,打印123 % 10,即打印3。
7.回溯到n = 1234,打印1234 % 10,即打印4。
所以,最后打印的结果是1 2 3 4。

一开始学习递归函数时,总是搞不清调用自己以后函数执行到哪了,并且返回的时候也比较迷,通过画图的形式才一步一步搞清楚递归的整个过程。

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

思路:使用递归方式的核心就是拆解问题,将规模减小,直到最简单的情况。这题可以把问题看成查看第一个字符是否是\0,如果不是就+1,并向后移动一位。

去查看第一个字符是不是\0,如果不是下一步。具体如下:

my_strlen("abc")

1+my_strlen("bc")

1+1+my_strlen("c")

1+1+1+my_strlen("")

1+1+1+0

int my_strlen(char* str)
{
	if (*str != 0)
		return 1 + my_strlen(str + 1);//这里不能使用str++,因为先使用后++,每次都把相同的地址传到下一次
	else
		return 0;
}

作业

1. 以下关于函数设计不正确的说法是:( )

A.函数设计应该追求高内聚低耦合
B.要尽可能多的使用全局变量
C.函数参数不易过多
D.设计函数时,尽量做到谁申请的资源就由谁来释放
答案:B
 

2. 以下叙述中不正确的是:( )

A.在不同的函数中可以使用相同名字的变量
B.函数中的形式参数是在栈中保存
C.在一个函数内定义的变量只在本函数范围内有效
D.在一个函数内复合语句中定义的变量在本函数范围内有效(复合语句指函数中的成对括号构成的代码)
答案:D
 

3. 关于C语言函数描述正确的是:( )

A.函数必须有参数和返回值
B.函数的实参只能是变量
C.库函数的使用必须要包含对应的头文件
D.有了库函数就不需要自定函数了
答案:C
 

4. C语言规定,在一个源程序中,main函数的位置( )

A.必须在最开始
B.必须在库函数的后面
C.可以任意
D.必须在最后
答案:C
 

5. 关于实参和形参描述错误的是:( )

A.形参是实参的一份临时拷贝
B.形参是在函数调用的时候才实例化,才开辟内存空间
C.改变形参就是改变实参
D.函数调用如果采用传值调用,改变形参不影响实参
答案:C
 

6. 函数调用exec((vl,v2),(v3,v4),v5,v6); 中,实参的个数是:( )

A.3
B.4
C.5
D.6
答案:B
 

7. 关于函数的声明和定义说法正确的是:( )

A.函数的定义必须放在函数的使用之前
B.函数必须保证先声明后使用
C.函数定义在使用之后,也可以不声明
D.函数的声明就是说明函数是怎么实现的
答案:B

8. 在函数调用时,以下说法正确的是:(    )

A.函数调用后必须带回返回值
B.实际参数和形式参数可以同名
C.函数间的数据传递不可以使用全局变量
D.主调函数和被调函数总是在同一个文件里
答案:B
 

9. 关于函数调用说法不正确的是:(     )

A.函数可以传值调用,传值调用的时候形参是实参的一份临时拷贝
B.函数可以传址调用,传址调用的时候,可以通过形参操作实参
C.函数可以嵌套定义,但是不能嵌套调用
D.函数可以嵌套调用,但是不能嵌套定义
答案:C
 

10. 函数判断素数

实现一个函数is_prime,判断一个数是不是素数。
利用上面实现的is_prime函数,打印100到200之间的素数。
 

#include <math.h>
int is_prime(int n)
{
	int i = 0;//除数
	for (i = 2; i <= sqrt(n); i++)
	{
		if (n % i == 0)
		{
			return 0;
		}
	}
	return 1;
}
int main()
{
	int n = 0;//100-200之间的数
	int count = 0;
	for (n = 101; n < 200; n+=2)
	{
		if (is_prime(n) == 1)
		{
			printf("%d ", n);
			count++;
		}
	}
	printf("\n%d\n", count);
	return 0;
}

11. 函数判断闰年

int is_leap_year(int year)
{
	if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))
		return 1;
	else
		return 0;
}
int main()
{
	int year = 0;
	for (year = 1000; year <= 2000; year++)
	{
		if (is_leap_year(year) == 1)
			printf("%d ", year);
	}
	return 0;
}

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

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;//此时左下标left要变成mid+1
		else if (arr[mid] > k)//如果中间元素比要找的元素大,说明范围在左边边
			right = mid - 1;//此时右下标right要变成mid-1
		else
			return mid;
	}
	return -1;
}
int main()
{
	int arr[] = { 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 (ret == -1)
		printf("找不到\n");
	else
		printf("找到了,下标是:%d\n", ret);

	return 0;
}

13. 写一个函数,每调用一次这个函数,就会将 num 的值增加1。

//传址调用
void Add1(int* p)
{
	(*p)++;
}
int main()
{
	int num = 0;
	Add1(&num);
	printf("%d\n", num);
	Add1(&num);
	printf("%d\n", num);
	return 0;
}

//传值调用
int Add2(int n)
{
	return ++n;
}
int main()
{
	int num = 0;
	num = Add2(num);
	printf("%d\n", num);
	num = Add2(num);
	printf("%d\n", num);
	return 0;
}

14. 函数交换两个整数

void Swap(int* px, int* py)
{
	int tmp = *px;
	*px = *py;
	*py = tmp;
}
int main()
{
	int a = 10;
	int b = 20;
	printf("交换前:a=%d b=%d\n", a, b);
	Swap(&a, &b);
	printf("交换后:a=%d b=%d\n", a, b);
	return 0;
}

15. 乘法口诀表

实现一个函数,打印乘法口诀表,口诀表的行数和列数自己指定
如:输入9,输出9 * 9口诀表,输出12,输出12 * 12的乘法口诀表。
 

void multi_table(int n)
{
	int i = 0;
	int j = 0;
	for (i = 1; i <= n; i++)
	{
		for (j = 1; j <= i; j++)
		{
			printf("%-2d*%2d = %-2d  ", i, j, i * j);
		}
		printf("\n");
	}
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	multi_table(n);
	return 0;
}

16. 能把函数处理结果的2个数据返回给主调函数,在下面的方法中不正确的是:(   )

A.return 这2个数
B.形参用数组
C.形参用2个指针
D.用2个全局变量
答案:A
 

17. 根据下面递归函数:调用函数Fun(2),返回值是多少(       )

int Fun(int n)
{
    if (n == 5)
        return 2;
    else
        return 2 * Fun(n + 1);
}
//A.2
//B.4
//C.8
//D.16
//答案:D

18. 关于递归的描述错误的是:(        )

A.存在限制条件,当满足这个限制条件的时候,递归便不再继续
B.每次递归调用之后越来越接近这个限制条件
C.递归可以无限递归下去
D.递归层次太深,会出现栈溢出现象
答案:C
 

19. 打印一个数的每一位

递归方式实现打印一个整数的每一位

void print(int n)
{
	if (n > 9)
		print(n / 10);
	printf("%d ", n % 10);
}
int main()
{
	int n = 1234;
	print(n);
	return 0;
}

20. 求阶乘

递归和非递归分别实现求n的阶乘(不考虑溢出的问题)

//非递归
int main()
{
	int n = 0;
	scanf("%d", &n);
	int i = 0;
	int ret = 1;
	for (i = 1; i <= n; i++)
	{
		ret *= i;
	}
	printf("%d\n", ret);
	return 0;
}

//递归
int fac(int n)
{
	if (n == 1)
		return 1;
	else
		return n * fac(n - 1);
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	printf("%d\n", fac(n));
	return 0;
}

21. strlen的模拟(递归实现)

递归和非递归分别实现strlen

//非递归
#include <assert.h>
size_t my_strlen1(const char* str)
{
	assert(str);
	size_t count = 0;
	while (*str!='\0')
	{
		str++;
		count++;
	}
	return count;
}

//递归
size_t my_strlen2(const char* str)
{
	assert(str);
	if (*str != '\0')
		return 1 + my_strlen2(str + 1);
	else
		return 0;
}
int main()
{
	char arr[] = "abcdef";
	size_t ret1 = my_strlen1(arr);
	printf("%zu\n", ret1);

	size_t ret2 = my_strlen2(arr);
	printf("%zu\n", ret2);

	return 0;
}

22. 字符串逆序(递归实现)

编写一个函数 reverse_string(char* string)(递归实现)
实现:将参数字符串中的字符反向排列,不是逆序打印。
要求:不能使用C函数库中的字符串操作函数。
比如 :
char arr[] = "abcdef";
逆序之后数组的内容变成:fedcba

非递归版本
//循环非指针版本
#include <string.h>
void reverse_string(char arr[])
{
	int sz = strlen(arr);
	int left = 0;
	int right = sz - 1;

	while (left < right)
	{
		char tmp = arr[left];
		arr[left] = arr[right];
		arr[right] = tmp;
		left++;
		right--;
	}
}

//循环指针版本
void reverse_string(char* arr)
{
	char* left = arr;
	char* right = arr + strlen(arr) - 1;
	while (left <= right)
	{
		char tmp = *left;
		*left = *right;
		*right = tmp;
		left++;
		right--;
	}
}

int main()
{
	char arr[] = "abcdef";
	
	reverse_string(arr);
	printf("%s\n", arr);

	return 0;
}

递归版本
//递归版本(只能逆序元素个数为奇数的数组)
void reverse_string(char arr[], int left, int right)
{
	char tmp = arr[left];
	arr[left] = arr[right];
	arr[right] = tmp;

	//如果if条件在这个位置,遇到元素个数为偶数的的数组会有以下情况
	//当逆序到最后两个字符时,虽然字符被交换了,但下标还未 + 1,(调用自己时才 + 1)
	//所以此时的左下边仍然小于右下标,所以又再次调用自己,导致已经被正确交换的两个字符又被交换了。
	//下面函数为正确版本,即使交换也要左下标小于右下标。
	if (left < right)
		reverse_string(arr, left + 1, right - 1);
}

//递归版本(正确版本)
void reverse_string(char arr[], int left, int right)
{
	if (left < right)
	{
		char tmp = arr[left];
		arr[left] = arr[right];
		arr[right] = tmp;
		reverse_string(arr, left + 1, right - 1);
	}
}

int main()
{
	char arr[] = "abcdef";
	int left = 0;
	int right = strlen(arr) - 1;

	reverse_string(arr, left, right);
	printf("%s\n", arr);
	return 0;
}


23. 计算一个数的每位之和(递归实现)

写一个递归函数DigitSum(n),输入一个非负整数,返回组成它的数字之和
例如,调用DigitSum(1729),则应该返回1 + 7 + 2 + 9,它的和是19
输入:1729,输出:19
 

//自己的版本
int DigitSum(int n)
{
	int sum = 0;
	if (n > 9)
		sum += DigitSum(n / 10);
	return sum += n % 10;
}

//GPT版本
int DigitSum(int n)
{
	if (n == 0)
		return 0;
	return n % 10 + DigitSum(n / 10);
}

//老师版本
int DigitSum(unsigned int n)
{
	if (n > 9)
		return DigitSum(n / 10) + n % 10;
	else
		return n;
}

int main()
{
	int n = 1234;
	int ret = DigitSum(n);
	printf("%d\n", ret);
	return 0;
}

24. 递归实现n的k次方

编写一个函数实现n的k次方,使用递归实现。

double Pow(int n, int k)
{
	if (k == 0)
		return 1;
	else if (k > 0)
		return n * Pow(n, k - 1);
	else
		return 1.0 / (Pow(n, -k));
}
int main()
{
	int n = 0;
	int k = 0;
	scanf("%d %d", &n, &k);
	double ret = Pow(n, k);
	printf("%lf\n", ret);

	return 0;
}

25. 计算斐波那契数

递归和非递归分别实现求第n个斐波那契数
例如:
输入:5  输出:5
输入:10, 输出:55
输入:2, 输出:1
1,1,2,3,5,8,13,21,34,55,89
 

//非递归
//自己的方法
int fib(int n)
{
	int a = 1;
	int b = 1;
	int c = 0;
	if (n <= 2)
		return 1;

	n--;
	while (--n)
	{
		c = a + b;
		a = b;
		b = c;
	}
	return c;
}

//老师方法
int fib(int n)
{
	int a = 1;
	int b = 1;
	int c = 0;
	if (n <= 2)
		return 1;
	
	int i = 0;
	for ( i = 3; i <= n; i++)
	{
		c = a + b;
		a = b;
		b = c;
	}
	return c;
}

//递归版本
int fib(n)
{
	if (n <= 2)
		return 1;
	else
		return fib(n - 1) + fib(n - 2);
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = fib(n);
	printf("%d\n", ret);
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值