初阶C语言-函数(上)

“追光的人,终会光芒万丈!” 今天我们一起来学习一下函数的相关知识点。

1.函数是什么?

数学中我们常见函数的概念,在数学中,函数通常是解决自变量与因变量之间关系的问题。那么,在C语言中,函数是做什么的呢?维基百科中对函数的定义为:子程序

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

2.C语言中函数的分类

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

2.1库函数

为什么会有库函数呢?

1.我们在学习C语言编程的时候,总是在一个代码编写完成后迫不及待的想知道结果,想把这个结果打印在我们的屏幕上,这个时候我们就会频繁的使用一个功能:将信息按照一定格式打印到屏幕上(printf
2.在编程的过程中,我们会频繁的做一些字符串的拷贝工作(strcpy
3.在编程的过程中我们也会计算,比如计算n的k次方(pow

像上述我们描述的基本功能,它们不是业务性的代码。我们在开发的过程中每一个程序员都可以用到,为了支持可移植性和提高程序的效率,所有C语言的基础库中提供了一系列类似的库函数,方便程序员进行软件开发。

需要注意的是库函数不是C语言提供的,是C语言标准中约定好,由编译器的厂商提供实现。

下面给出示例:

在C语言的标准中,对于strlen函数做出以下规定:
1.函数的功能-求字符串的长度
2.函数名-strlen
3.参数- const char*str
4.返回类型- size_t
以上规定是C语言规定好的,具体的函数实现是由不同的厂商实现的。

那么,库函数到底有哪些呢?我们可以去查看该网站:库函数查找的链接
在这里插入图片描述
简单的总结,C语言中常用的库函数都有:

扫描二维码关注公众号,回复: 16174401 查看本文章
  1. IO函数(标准输入输出头文件#include <stdio.h>
  2. 字符串操作函数(strlen、strcpy等函数的头文件为#include <string.h>
  3. 内存操作函数(头文件为#include <malloc.h>
  4. 时间/日期函数(头文件为#include <time.h>
  5. 数学函数(头文件为#include <math.h>
  6. 其他库函数

具体的函数及用法,大家可以查阅上面的网站学习。这里,我们给大家简单介绍几种库函数及其使用。

1.pow函数(头文件为#include <math.h>
通过查阅网站我们可以了解到该函数的使用方法:
在这里插入图片描述
接下来,让我们一起来试着用一下这个函数吧!

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <math.h>
int main()
{
    
    
	int ret = (int)pow(3, 5);//由于pow函数是float型,所以可以强制类型转化为int型
	printf("%d", ret);
	return 0;
}

这里,我们建议大家把pow的返回类型强转为int型,因为我们给出的样例输入是整型,不强转的话会给出警告。
在这里插入图片描述

在这里插入图片描述
2.strcpy函数(头文件为#incude <string.h>
同样的,我们可以先查找网站,看看该函数的相关用法。
在这里插入图片描述

//strcpy函数
#include <stdio.h>
int main()
{
    
    
	char arr1[20] = "xxxxxxxxxxxxxxxxxxxx";
	char arr2[] = "Hello C World!";
	strcpy(arr1, arr2);
 printf("%s\n", arr1);
	return 0;
}

在这里插入图片描述
不难发现strcpy函数就是起到的一个复制的作用,那么字符串的结束标志是否被一同复制过去呢?这里,我们可以调试运行着看一下。
在这里插入图片描述
注:我们可以发现strcpy函数会将'\0'一同复制过去!

3.memset函数(头文件为#incude <string.h>
在这里插入图片描述

//memset函数
#include <stdio.h>
#include <string.h>
int main()
{
    
    
	char arr[] = "Hello C World!";
	memset(arr, 'x', 5);
	printf("%s\n", arr);
	return 0;
}

在这里插入图片描述
注:使用库函数必须要包含#include对应的头文件

2.2自定义函数

如果库函数能解决所有的问题,那么还需要程序员干嘛呢?所以,更多的时候还是需要用到自定义函数。自定义函数和库函数一样,有函数名、返回类型和函数参数。但是不一样的是这些是我们自己来设计的,给了程序员很大的发挥空间。

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

下面我们举一个例子,写一个函数可以找出两个整数中的最大值。

//写出一个函数求两个数的最大值
#include <stdio.h>
int get_max(int x, int y)
{
    
    
	if (x > y)
		return x;
	else
		return y;
}
int main()
{
    
    
	int num1 = 0;
	int num2 = 0;
	scanf("%d%d", &num1, &num2);
	int max = get_max(num1, num2);
	printf("%d\n", max);
	return 0;
}

在这里插入图片描述
我们再举一个例子,写一个函数可以交换两个整型变量的内容。

#define _CRT_SECURE_NO_WARNINGS 1
//写一个函数可以交换两个整型变量的内容
#include <stdio.h>
void swap(int x, int y)//形式参数
{
    
    
	int t;
	t = x;
	x = y;
	y = t;
}
int main()
{
    
    
	int a = 0;
	int b = 0;
	scanf("%d%d", &a, &b);
	printf("交换前:%d %d\n", a, b);
	swap(a, b);//实际参数
	//函数调用时将实参传给形参,形参其实是实参的一份临时拷贝
	//对形参的修改不会改变实参
	printf("交换后:%d %d\n", a, b);
	return 0;
}

通过前面所学,我们知道了交换两个变量的值需要用到中间变量t,那么这样写出来的代码是否正确呢?
在这里插入图片描述
我们可以发现两个整型变量的内容并没有发生交换,遇到问题的时候,我们试着自己调试着发现问题!
在这里插入图片描述
通过调试,我们发现,形参和实参的地址不一样,因此对形参的修改不会改变实参,这时候考虑到地址,我们应该就想到了前面提及的指针,那我们再把代码修改一下来实现我们希望的功能。

#include <stdio.h>
void swap2(int *x, int *y)
{
    
    
	int t;
	t = *x;//t = a
	*x = *y;//a = b
	*y = t;//b = t
}
int main()
{
    
    
	int a = 0;
	int b = 0;
	scanf("%d%d", &a, &b);
	printf("交换前:%d %d\n", a, b);
	swap2(&a, &b);//实际参数
	printf("交换后:%d %d\n", a, b);
	return 0;
}

在这里插入图片描述
在这里插入图片描述

3.函数的参数

3.1实际参数(实参)

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

int max(int x, int y)
{
    
    
	if (x > y)
		return x;
	else
		return y;
}
int main()
{
    
    
	int a = 0;
	int b = 0;
	scanf("%d%d", &a, &b);
	max(3, 6);//实参是常量
	max(a, 6);//实参是变量
	max(b, max(a, 4));//实参是函数
	max(a, 3 + 5);//实参是表达式
	return 0;
}

3.2形式参数(形参)

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

#include <stdio.h>
void swap1(int x, int y)
{
    
    
	int tep = 0;
	tep = x;
	x = y;
	y = tep;
}
void swap2(int* p1, int* p2)
{
    
    
	int tep = 0;
	tep = *p1;
	*p1 = *p2;
	*p2 = tep;
}
int main()
{
    
    
	int num1 = 1;
	int num2 = 2;
	swap1(num1, num2);
	printf("swap1:%d %d\n", num1, num2);
	swap2(&num1, &num2);
	printf("swap2:%d %d\n", num1, num2);
	return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这里我们可以看到swap1函数在调用的时候,x、y拥有自己的空间,同时拥有了和实参一模一样的内容。所以,我们可以简单认为:形参实例化之后相当于实参的一份临时拷贝

4.函数的调用

4.1传值调用

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

4.2传址调用

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

4.3练习

1.写一个函数可以判断一个数是不是素数。

//写一个函数可以判断一个数是不是素数(打印100~200之间的素数)
#include <stdio.h>
#include <math.h>
int is_prime(int x)
{
    
    
	int j = 0;
	for (j = 2; j <= sqrt(x); j++)
	{
    
    
		if (x % j == 0)
			return 0;
	}
	return 1;
}
int main()
{
    
    
	int i = 0;
	for (i = 100; i <= 200; i++)
	{
    
    
		if (is_prime(i))
			printf("%d ", i);
	}
	return 0;
}

在这里插入图片描述
这里,我们给大家介绍一下布尔类型(bool),bool类型的变量只有两种取值,true和false.因此,我们也可以用bool类型来写这段程序。

#include <stdio.h>
#include <math.h>
#include <stdbool.h>//bool类型需要添加该头文件
//C语言中有一个bool类型,它的取值只有两种取值true和false
bool is_prime(int x)
{
    
    
	int j = 0;
	for (j = 2; j <= sqrt(x); j++)
	{
    
    
		if (x % j == 0)
			return false;
	}
	return true;
}
int main()
{
    
    
	int i = 0;
	for (i = 100; i <= 200; i++)
	{
    
    
		if (is_prime(i))
			printf("%d ", i);
	}
	return 0;
}

在这里插入图片描述

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

//写一个函数判断一年是不是闰年。(打印1000~2000之间的闰年)
#include <stdio.h>
#include <stdbool.h>//bool类型的头文件
bool is_leap_year(int x)
{
    
    
	if (x % 4 == 0 && x % 100 != 0 || x % 400 == 0)
		return true;
	else
		return false;
}
int main()
{
    
    
	int y = 0;
	for (y = 1000; y <= 2000; y++)
	{
    
    
		if(is_leap_year(y))
			printf("%d ",y);
	}
	return 0;
}

在这里插入图片描述
3.写一个函数,实现一个整型有序数组的二分查找。

//写一个函数,实现一个整型有序数组的二分查找。
//找到了返回下标,找不到返回-1
int binary_search(int arr[], int n, int sz)
{
    
    
	int left = 0;
	int right = sz - 1;
	while (left <= right)
	{
    
    
		int mid = (left + right) / 2;
		if (arr[mid] > n)
			right = mid - 1;
		else if (arr[mid] < n)
			left = mid + 1;
		else
			return mid;
	}
	return -1;
}
#include <stdio.h>
int main()
{
    
    
	int arr[] = {
    
     1,2,3,4,5,6,7,8,9,10 };
	int n = 0;
	scanf("%d", &n);
	int sz = sizeof(arr) / sizeof(arr[0]);
	int ret = binary_search(arr,n,sz);
	if (ret == -1)
	{
    
    
		printf("找不到!\n");
	}
	else
		printf("找到了,下标是%d", ret);
	return 0;
}

在这里插入图片描述
在这里插入图片描述
但是,上述代码依然存在着弊端,int mid = (left + right) / 2;针对于left和right数值较小的情况,一旦它们的数值大到一定的时候,会出现溢出的情况。那么,我们该如何求两个数的平均值呢?下面给出图示,我们发现,只需要将b比a多的那部分的一半加到a上就是两者的平均值了,那么代码该如何改进呢?
在这里插入图片描述

int binary_search(int arr[], int n, int sz)
{
    
    
	int left = 0;
	int right = sz - 1;
	while (left <= right)
	{
    
    
		int mid = left + (right - left) / 2;//更改的地方,目的为了防止数值太大溢出
		if (arr[mid] > n)
			right = mid - 1;
		else if (arr[mid] < n)
			left = mid + 1;
		else
			return mid;
	}
	return -1;
}
#include <stdio.h>
int main()
{
    
    
	int arr[] = {
    
     1,2,3,4,5,6,7,8,9,10 };
	int n = 0;
	scanf("%d", &n);
	int sz = sizeof(arr) / sizeof(arr[0]);
	int ret = binary_search(arr, n, sz);
	if (ret == -1)
	{
    
    
		printf("找不到!\n");
	}
	else
		printf("找到了,下标是%d", ret);
	return 0;
}

我们可以发现这样写出来的程序运行结果与刚刚是一样的!
在这里插入图片描述
在这里插入图片描述
4.写一个函数,每调用一次这个函数,就会将num的值增加1。

//写一个函数,每调用一次这个函数,就会将num的值增加1。
#include <stdio.h>
void add(int *a)
{
    
    
	*a = *a + 1;
}
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;
}

在这里插入图片描述

好啦,关于函数的知识点就先讲到这里,后期会继续更新,欢迎大家持续关注、点赞和评论!

猜你喜欢

转载自blog.csdn.net/qq_73121173/article/details/131983962
今日推荐