C语言初阶-函数-1

系列文章目录

第一章 如何学习好C语言
第二章 C语言的编译工具的相关问题
第三章 对C语言的初步认识
第四章 C语言的初阶学习
第五章 C语言的高阶学习
第六章 C语言有关的小项目
第七章 C语言的思维导图及重要知识思维导图

第四章 C语言的初阶学习—函数初阶(1)

前言

本篇文章主要介绍什么是函数,以及函数的分类,函数的形参和实参的概念和关系和函数的传值和传址调用。

一、什么是函数?

函数简单来说,就是一连串的语句,函数是C程序的构建块,每个函数本质上是一个自带声明和语句的小程序,可以利用函数把程序划分为若干小块,这样便于理解和修改程序。负责完成某项特定任务,相较于其他的代码,具有相对的独立性,一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏,

二、函数的分类

1.库函数

由于有一些功能经常被使用,但程序员有自己的理解和编写代码的风格,导致代码不尽相同,为了规范和统一方便使用,便规定了一些函数,称之为库函数。在使用库函数,必须包含#include对应的头文件。
C语言常用的库函数:
IO函数
字符串函数
字符操作函数
时间/日期函数
数学函数
其他库函数
那么如何学会使用库函数呢?
查询工具:
MSDN
www.cplusplus.com
http://en.cpppreference.com
http://zh.cpppreference.com(中文版)
举个例子:要想实现字符串的拷贝功能
在www.cplusplus.com网址中,查找strcopy函数(从库函数命名,可以看出来函数的命名尽可能是能够传达出函数功能的名称),可以看到它对应的头文件,以及参考的示例-见下图。
在这里插入图片描述

2.自定义函数

自定义函数包含了函数名,函数参数,以及返回值类型,自定义函数的功能尽可能地单一,别人在使用时会比较方便。是否有返回值,以及返回值的类型是什么,都是基于你要实现什么样的功能。
在这里插入图片描述
形象理解:
在这里插入图片描述

三、函数的参数

3.1 实际的参数

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

3.2 形式的参数

形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数,形式参数当函数调用完之后就自动销毁了,因为形式参数只在函数中有效。看作用域。
调用函数的时候,才对变量分配空间。
//当实参传递给形参的时候,形参是实参的一份临时拷贝,对形参的修改不会影响到实参。

三、函数的调用

3.1 传值调用

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

3.2 传址调用

传址调用是把函数外部创建的内存地址传递给函数参数的一种调用函数的方式, 这种传参方式可以让函数和函数外边的变量建立起真正的联系也就是函数内部可以直接操作函数外部的变量。
//写一个函数可以交换两个整型变量的内容(图解进行分析)在这里插入图片描述

void Swap1(int* px, int* py)//传址调用
{
	int z = *px;//z=a;
	*px = *py;//a=b;
	*py = z;//b=z;
}
void Swap2(int x, int y)//传值调用
{
	int z1 = 0;
	z1 = x;
	y = z1;
	x = y;
}

//当实参传递给形参的时候,形参是实参的一份临时拷贝,对形参的修改不会影响到实参
int main()
{
	int a = 0;
	int b = 0;
	int c = 0;
	int d = 0;
	scanf("%d %d", &a, &b);
	//交换前后
	printf("交换前:a=%d b=%d\n", a, b);
	Swap1(&a, &b);//传址调用
	printf("Swap1交换后:a=%d b=%d\n", a, b);
	printf("交换前:c=%d d=%d\n", a, b);
	Swap2(a, b);//传值调用
	printf("Swap2交换后:c=%d d=%d\n", a, b);
	return 0;
}

在这里插入图片描述
在传值调用中,x和y确实实现了数值的交换,但x,y与a,b的内存地址不同,因此不改变a,b的值,故而输出为原本的数据。可以通过调试进行查看x,y与a,b的地址情况。
在这里插入图片描述
在这里插入图片描述
上图实现加法,只需要传数值,不需要传数值对应的指针。那么在什么情况下,需要传递地址呢?在要改变变量的数值的时候,一旦地址分配好之后,是没办法改变地址的,只能改变地址对应的数值。

三、函数初阶(1)练习题

1:写一个函数可以判断一个数是不是素数(打印100~200之间的素数),素数是只能被1和他本身整除的数。比如7就用2-6的数判断是否整除
方法一:利用循环进行实现

int main()
{
	int i = 0;
	int count = 0;

	for (i = 100; i <= 200; i++)
	{
		//判断i是否为素数,是的话就打印,素数是只能被1和他本身整除的数。拿2~i-1之间的数去试除。
		int flag = 1;//flag是1,表示是素数(每一次循环执行时,假设是素数)
		int j = 0;
		for (j = 2; j <= i - 1; j++) */
		{
			if (i%j == 0 )
			{
				flag = 0;
				break;
			}
		}
		if (flag == 1)
		{
			printf("%d ", i);
			count++;
		}	
	}
	printf("100~200一共有%d个素数", count);
	return 0;
}

方法二利用函数的方法进行实现
在这里插入图片描述
根据上图可以知道,只需要找开平方以下的数判断是不是质数就可以了。

#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,就不需要break,跳出了。break只能跳出循环,而不是跳出函数
		}
	}
	return 1;//经过上面 的所有值进行整除,都没有整除,说明它即为素数
}


int main()
{
	int i = 0;
	int count = 0;

	for (i = 100; i <= 200; i++)
	{
		//判断i是否为素数,是的话就打印,素数是只能被1和他本身整除的数。拿2~i-1之间的数去试除。
		if (is_prime(i))   //条件为真(1),执行语句
		{
			printf("%d ", i);
			count++;
		}	
	}
	printf("\n count= %d", count);
	return 0;
}

使用头文件输出的正确结果
在这里插入图片描述

我们知道,偶数肯定不是质数,因此将代码for (i = 100; i <= 200; i++)改为for (i = 101; i <= 200; i+=2),只在奇数里面找质数,效率也会提高不少。
调用sqrt()函数是在库文件#include<math.h>,因此要先引用这个头文件,这个会涉及后续讲到的对于函数需要先声明后调用。
如果没有调用头文件会怎么样呢?
你使用VS编译不会出现问题,但就是结果不对,进行调试的时候,会发现,会直接跳过包含sqrt函数的部分,我认为可能是因为没有包含头文件导致编译器对它识别不出,不知道这个函数的作用是什么,因而直接跳过去。(此处理解可能不太正确,还希望大佬可以多多指教指教)
在这里插入图片描述
2.写一个函数判断一年是不是闰年(打印1000~2000年之间的闰年)
闰年判断规则,1.能被4整除,并且不能被100整除的是闰年。2.能被400整除是闰年。
普通闰年:公历年份是4的倍数,且不是100的倍数的,为闰年(如2004年、2020年等就是闰年)。
世纪闰年:公历年份是整百数的,必须是400的倍数才是闰年(如1900年不是闰年,2000年是闰年)。1582年以后的惯例。
方法一:

int main()
{
	int year = 0;
	int count = 0;
	for (year = 1000; year <= 2000; year++)
	{
		//判断是否为闰年
		//判断方法一
		if (year % 4 == 0)
		{
			if (year % 100 != 0)
			{
				printf("%d ", year);
				count++;
			}
		}
		//判断方法二
		 if (year % 400 == 0)  //有243个素数(包含1200 1600 2000)
		{
			printf("%d ", year);
			count++;
		}
	}
	printf("\n %d", count);
	return 0;
}

方法二:利用逻辑关系综合进行实现

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

方法三:利用函数进行实现

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

		if (is_leap_year(year))//返回1是条件成立,执行打印,返回值为0时,不执行(函数调用)
		{
			printf("%d ", year);
			count++;
		}
	}
	printf("\n %d", count);
	return 0;
}

写代码的思维:先想好这个函数怎么用,再去实现这个函数该怎么写。函数的功能尽可能地单一,方便进行调用。
3.编写一个函数实现二分对数组的查找

int binary_search(int arr[],int k,int sz)//arr[]在自定义函数看上去是一个数组,这其实已经是个指针变量,存储的是主函数数组的首元素的地址
{
	//int sz = sizeof(arr) / sizeof(arr[0]);  //数组的长度,不要奢求在自定义函数中计算数组的长度,数组传参实际上传递的是数组首元素的地址,而不是整个参数,所以在函数内部计算一个函数参数部分的数组的元素个数是不靠谱的。
	int left = 0;//左边的下标
	int right = sz - 1;//右边的下标
	while (left <= right)  //循环执行条件
	{
		int mid = left + (right - left) / 2;//这样的做法在于避免溢出
		if (arr[mid] < k)   //中间值小于k,说明k的结果是在中间坐标的右边,因此改变左边的下标
		{
			left = mid + 1;
		}
		else if (arr[mid] > k)  //中间值大于k,说明k的结果是在中间坐标的左边,因此改变右边的下标
		{
			right = mid - 1;
		}
		else
		{
			return mid;//找到后,返回下标,下标不唯一,便不能单纯返回某个数
		}

	}
	return -1;//不要返回0,数组存在有下标0
}
主函数部分:

```c
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };//数值下标是从0开始的,这个得知道
	int k = 7;   //待查找的数字
	int sz = sizeof(arr)/ sizeof(arr[0]);  //数组的长度
	//找到了,返回下标,找不到,返回-1(没有下标是-1)
	int ret = binary_search(arr, k, sz);
		if (ret == -1)
		{
			printf("找不到\n");
		}
		else
		{
			printf("找到了,返回下标:%d", ret);
		}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值