资讯专栏INFORMATION COLUMN

四·C语言之·函数全方位理解

legendaryedu / 3856人阅读

摘要:我们在开发的过程中每个程序员都可能用的到,为了支持可移植性和提高程序的效率,所以语言的基础库中提供了一系列类似的库函数,方便程序员进行软件开发。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。

?写在前面

  • ?博客主页:kikoking的江湖背景
  • ?欢迎关注?点赞?收藏⭐️留言?
  • ?本文由 kikokingzz 原创,CSDN首发!
  • ?首发时间:?2021年11月21日?
  • ?最新更新时间:?2021年11月21日?
  • ✉️坚持和努力一定能换来诗与远方!
  • ?作者水平很有限,如果发现错误,请留言轰炸哦!万分感谢感谢感谢!

?:本篇内容包含了C语言函数的全方位理解,同时配备了对应的例题理解,后续还会更新专门的习题,刷题板块!

L:大仙,终于来到函数板块了嘛!

?:yep!小子,这章内容很多,仔细看哦!


目录

?写在前面

?1.函数是什么? 

?2.1 库函数

?2.1.1为什么会有库函数?

?2.1.2如何学会使用库函数?

?自学案例1-strcpy函数自学

?自学案例2-memset自学

?自学案例3-strlen

?2.2自定义函数

?2.2.1自定义函数的组成

?2.2.2典型案例

?1.写一个函数可以找出两个整数中的最大值

?2.写一个函数可以交换两个整形变量的内容

?3. 函数的参数

?3.1 实际参数(实参)

?3.2 形式参数(形参)

?4. 函数的调用

?4.1 传值调用

?4.2 传址调用

?函数调用案例

?1.判断一个函数是不是素数

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

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

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

?函数返回类型

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

?5.1 嵌套调用

?5.2链式访问

?3.习题

 ?6. 函数的声明和定义

?6.1函数定义

?6.2函数声明

?公司写代码,会不会把所有代码都写在test.c中呢?

?7. 函数递归

?7.1 什么是递归?

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

 ?7.2 递归的两个必要条件

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

?练习3. 求n的阶乘(不考虑溢出)

?练习4.求第n个斐波那契数(不考虑溢出)


?1.函数是什么? 


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

✨✨✨我是分割线✨✨✨

?2.1 库函数


?2.1.1为什么会有库函数?

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

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


?2.1.2如何学会使用库函数?

  • 需要学会查询工具的使用:
  • MSDN(Microsoft Developer Network)
  • www.cplusplus.com
  • http://en.cppreference.com(英文版)
  • http://zh.cppreference.com(中文版)

?自学案例1-strcpy函数自学

·在MSDN中搜索strcpy

·了解函数的定义方法、返回值、参数

#include#includeint main(){    //strlen--string length --字符串长度有关的    //strcpy--string copy--字符串拷贝    char arr1[] = "bit";//原数据    char arr2[] = "#########";//目的地字符串    //             bit/0  打印时/0是结束标志    strcpy(arr2, arr1);    printf("%s/n", arr2);    return 0;}


?自学案例2-memset自学

·在MSDN中搜索memset

·了解函数的定义方法、返回值、参数

#includeint main(){    char arr[]="hello world";    memset(arr,"*",5);    //arr-这个数组空间的地址    //这里放的"*",因为存进去的是ASCII值,也是整型值(int型)    printf("%s/n",arr);//%s打印字符串    //***** world    return 0;}


?自学案例3-strlen

 ·在www.cplusplus.com中搜索memset

·了解函数的定义方法、返回值、参数

#include#includeint main(){	char arr[] = "abc";	size_t len = strlen(arr);	printf("%u/n", len);	//%d-有符号	//%u-无符号	return 0;}

✨✨✨我是分割线✨✨✨ 

?2.2自定义函数


L:库函数好好用啊,那是不是咱们只要熟练掌握库函数就好啦?

?:如果库函数能干所有的事情,那还要程序员干什么?所以更加重要的是自定义函数!


?2.2.1自定义函数的组成

·自定义函数和库函数一样,有函数名,返回值类型和函数参数。但是不一样的是这些都是我们自己来设计。这给程序员一个很大的发挥空间。
 

·函数的组成︰

 


?2.2.2典型案例

?1.写一个函数可以找出两个整数中的最大值

#includeint getmax(int x, int y){    if (x > y)        return x;    else        return y;}int main(){    int a = 10;    int b = 20;    //函数的使用    int max = getmax(a, b);    int max1 = getmax(100, 300+1);    printf("%d/n", max1);    return 0;}


?2.写一个函数可以交换两个整形变量的内容

❌错误示范:

#includevoid Swag(int x, int y)//void表示没有返回值{    int z = x;        x = y;        y = z;}int main(){    int a = 10;    int b = 20;    int tmp;    printf("a=%d b=%d/n", a, b);    Swag(a, b);    printf("a=%d b=%d/n", a, b);    return 0;}

L:为什么这里明明Swag函数里面两个值交换了,而最后的结果没有交换呢

?:小子,仔细看上图案例,Swag函数中的 形参x,y 的地址与 实参a、b 的地址不同,我们仅仅只是交换了Swag函数内的两值,对于a,b其实并未改变

?:也就是说:当函数调用时候,实参传给形参,形参其实是实参的一份临时拷贝;所以对形参的修改,不会影响实参

L:那么如何修改呢

?:我们只要通过指针将实参a、b 地址传递给 形参x,y ,再通过解引用操作就可以成功啦

✅正解(传址):

#includevoid Swag(int* pa, int* pb)//void表示没有返回值{    int tmp = 0;    tmp = *pa;//用解引用操作    *pa = *pb;    *pb = tmp;}int main(){    int a = 10;    int b = 20;    int tmp;    printf("a=%d b=%d/n", a, b);    Swag(&a, &b);    printf("a=%d b=%d/n", a, b);    return 0;}

✨✨✨我是分割线✨✨✨

?3. 函数的参数


?3.1 实际参数(实参)

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


?3.2 形式参数(形参)

  1. 形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。
  2. 形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。
  3. 当实参传给形参的时候,形参其实是对实参的一份临时拷贝,对形参的修改是不会改变实参的(例如交换数字案例中的错解)

  1. 上面 Swap1 Swap2 函数中的参数 x、y、px、py 都是形式参数
  2. main函数中传给 Swap1 num1 、num2 和传给Swap2 函数的 &num1 、&num2 实际参数。

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

✨✨✨我是分割线✨✨✨

?4. 函数的调用


?4.1 传值调用

  • 函数的形参和实参分别占有不同内存块(内存地址不同),对形参的修改不会影响实参。
void Swag(int x, int y)//void表示没有返回值{    int z = x;        x = y;        y = z;}

?4.2 传址调用

  • 传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式
  • 这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量
void Swag(int* pa, int* pb)//void表示没有返回值{    int tmp = 0;    tmp = *pa;//用解引用操作    *pa = *pb;    *pb = tmp;}

?函数调用案例


?1.判断一个函数是不是素数

#include#includeint is_prime(int n)//是素数返回1.不是素数返回0{    for (int j = 2; j <= sqrt(n); j++)//写出优化的方式sqrt(n)    {        if (n % j == 0)//被j整除            return 0;    }    return 1;}int main(){//打印100-200之间的素数    int i = 0;    for (i = 100; i <= 200; i++)//产生100-200的数    {        //判断i是否为素数        if (is_prime(i) == 1)            printf("%d/n", i);    }}


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

#define _CRT_SECURE_NO_WARNINGS 1#include//是闰年返回1,不是闰年返回0int is_leap_year(int x){	if(((x % 4 == 0) && (x % 100 != 0))||(x % 400 == 0))	    return 1;	else		return 0;}int main(){	int count = 0;	int y = 0;	for (y = 1000; y <= 2000; y++)	{		//判断y是不是闰年		if (is_leap_year(y))		{			count++;			printf("%d/n", y);		}	}	printf("%d个闰年/n", count);	return 0;}


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

重点:数组传递到形参时,传递的是首元素地址

#define _CRT_SECURE_NO_WARNINGS 1#includeint binary_search(int arr[], int k,int num){	//用折半查找	//int num = sizeof(arr) / sizeof(arr[0]);	//       指针大小== 4   /     4 =1	int left = 0;	int right = num - 1;	do 	{		int mid = (left + right) / 2;		if (arr[mid] < k)		{			left = mid + 1;		}					else if (arr[mid] > k)		{			right = mid - 1;		}		else			return mid;//找到了	}while(left<=right);	return -1;}//如果找到了就返回下标//找不到就返回-1int main(){	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };	int k = 7;	int num = sizeof(arr) / sizeof(arr[0]);	//数组arr传给binary_search函数的时候,其实传的是arr首元素的地址	int ret =binary_search(arr,k,num);	if (-1 == ret)		printf("找不到/n");	else		printf("找到了,下标是%d", ret);	return 0;}

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

·法1.需要改变实参——传址

#define _CRT_SECURE_NO_WARNINGS 1#includevoid add(int* p){	++*p;}int main(){	int num = 0;	add(&num);	printf("%d", num);	add(&num);	printf("%d", num);	add(&num);	printf("%d", num);	return 0;}

·法2.不使用传参,采取频繁赋值

#define _CRT_SECURE_NO_WARNINGS 1#includeint add(int m){	return m + 1;}int main(){	int num = 0;	num=add(num);//频繁赋值	printf("%d", num);	num = add(num);	printf("%d", num);	num = add(num);	printf("%d", num);	return 0;}

✨✨✨我是分割线✨✨✨

?函数返回类型


  1. void-无返回(可以使用return;来结束void函数)
  2. char int float-返回值为其对应类型
#define _CRT_SECURE_NO_WARNINGS 1#includevoid test1(){	int n = 5;	printf("hehe/n");	if (n == 5)		return;//void里用return;来返回	//可以提前使函数终止	printf("haha/n");}int test2(){	return 1;}int main(){	test1();	return 0;}

✨✨✨我是分割线✨✨✨ 

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


?5.1 嵌套调用

·函数可以嵌套调用;不可以嵌套定义
//函数不能嵌套定义void test1(){	void test2()	{	}}//但是可以嵌套调用void test1(){	test2();}

?5.2链式访问

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

int main(){	int len = strlen("abc");	printf("%d/n", len);	printf("%d/n", strlen("abc"));	//把strlen函数的返回值作为printf函数的参数	return 0;}


?3.习题

·考察链式访问——把一个函数的返回值作为另外一个函数的参数

int main(){	printf("%d", printf("%d", printf("%d", 43)));	//结果是啥?	//注:printf函数的返回值是打印在屏幕上字符的个数	return 0;}

 前情提要:了解printf函数的返回值

 ?6. 函数的声明和定义


?6.1函数定义

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

·解决办法:(添加一个函数声明)

ADD(int x, int y);//函数声明int main(){	int a = 10;	int b = 20;	int ret = ADD(a, b);	printf("%d", ret);	return 0;}int ADD(int x, int y){	int z = x + y;	return z;}

?6.2函数声明

1.告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了。

2.函数的声明一般出现在函数的使用之前。要满足先声明后使用

3.函数的声明一般要放在头文件中(.h文件中)的。


·本质上的应用:剥离出来

//add.h 放函数声明#includeint ADD(int x, int y);
//add.c 放函数定义#include"add.h"int ADD(int x, int y){	int z = x + y;	return z;}
//test.c  主函数#include"add.h"int main(){	int a = 10;	int b = 20;	int ret = ADD(a, b);	printf("%d", ret);	return 0;}

上述中把 add.c 和 add.h 合在一起合称加法模块


?公司写代码,会不会把所有代码都写在test.c中呢?

1.分函数模块来写!效率大大提高

2.可以使用静态库,出售函数

#define _CRT_SECURE_NO_WARNINGS 1#include"add.h"#include#pragma comment(lib,"add.lib")//使用静态库int main(){	int a = 10;	int b = 20;	printf("%d", ADD(10, 20));	return 0;}

✨✨✨我是分割线✨✨✨ 

?7. 函数递归


?7.1 什么是递归?

·程序调用自身的编程技巧称为递归( recursion)

·函数递归:函数自己调用自己

·递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解

·递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。

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


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

·例如: 输入:1234  ;输出 1 2 3 4

#define _CRT_SECURE_NO_WARNINGS 1#includePrint(unsigned int n){	if (n > 9)	{		Print(n / 10);//Print(123) 1 2 3	}	printf("%d/t", n % 10);//}int main(){	unsigned int num = 0;	scanf("%d", &num);//顺序打印每一位 	Print(num);}

 作图理解:


 ?7.2 递归的两个必要条件

1.存在限制条件,当满足这个限制条件的时候,递归便不再继续

2.每次递归调用之后越来越接近这个限制条件


?Tip1.每一次函数调用都会在内存的栈区申请一块内存空间

·系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者(死递归),这样有可能导致一 直开辟栈空间,最终产生栈空间耗尽的情况这样的现象我们称为栈溢出

 ?Tip2.stackoverflow-程序员版的知乎


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

·解法1.使用临时变量(不满足本题要求)

int my_strlen(char* s)//s相当于指向字符串中的 a{	int count = 0;//临时变量	while (*s != "/0")	{		count++;		s++;//char* 的指针是一个字节		//地址序列+1就可以跳到下一个字符        //如果是一个int型的指针 跳到下一个字符要+4	}	return count;}int main(){	//求字符串长度	char arr[] = "abc";	int len = my_strlen(arr); 	//arr里放着 a b c /0	//arr是数组名 数组名是数组首元素的地址	printf("%d", len);	return 0;}

·解法2.使用函数递归

思路:my_strelen("abc");1+my_strelen("bc");1+1+my_strelen("c");1+1+1+my_strelen("/0");1+1+1+0=3
int my_strlen(char* s)//s相当于指向字符串中的 a{	int count = 0;	while (*s != "/0")	{		count++;		s++;//char* 的指针是一个字节		//地址序列加1就可以变到下一个字符	}	return count;}int my_strlen(char* s)//s里面放的是a的地址{	if (*s == "/0")		return 0;	else		return 1 + my_strlen(s + 1);//s+1就是b的地址

笔记理解:


?练习3. 求n的阶乘(不考虑溢出)

·解法1.循环解法

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

·解法2.递归法

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

?练习4.求第n个斐波那契数(不考虑溢出)

·解法1.递归

int count = 0;int fib(int x){	if (x == 3)//看看计算3这步需要多少次		count++;	if (x <= 2)		return 1;	else		return fib(x - 1) + fib(x - 2);}int main(){	int n = 0;	scanf("%d", &n);	int ret = fib(n);	printf("第%d个斐波那契数是%d/n", n,ret);	return 0;}

 ·解法2.循环方法(从前往后)

int fib(int x){	int a = 1;	int b = 1;	int c = 1;	while (x>2)	{		c = a + b;		a = b;		b = c;		x--;	}	return c;}int main(){	int n = 0;	scanf("%d", &n);	int ret = fib(n);	printf("第%d个斐波那契数是%d/n", n,ret);	return 0;}


 ?Tip3.补码数的正溢出

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/124092.html

相关文章

  • 深度学习:你该知道八大开源框架

    摘要:作为当下最热门的话题,等巨头都围绕深度学习重点投资了一系列新兴项目,他们也一直在支持一些开源深度学习框架。八来自一个日本的深度学习创业公司,今年月发布的一个框架。 深度学习(Deep Learning)是机器学习中一种基于对数据进行表征学习的方法,深度学习的好处是用 非 监督式或半监督式 的特征学习、分层特征提取高效算法来替代手工获取特征(feature)。作为当下最热门的话题,Google...

    Rindia 评论0 收藏0
  • 深度学习大神新作,神经网络的自然语言翻译应用

    摘要:神经网络在自然语言处理方面,未来有巨大的应用潜力。讲座学者之一与深度学习大神蒙特利尔大学学者在大会上发表了论文,进一步展现神经机器翻译的研究结果。那些指令的语义就是习得的进入嵌入中,来较大化翻译质量,或者模型的对数似然函数。 在 8月7日在德国柏林召开的2016 计算语言学(ACL)大会上,学者Thang Luong、Kyunghyun Cho 和 Christopher D. Mannin...

    helloworldcoding 评论0 收藏0
  • 卷积神经网络(CNN)一维卷积、二维卷积、三维卷积详解

    摘要:一维卷积常用于序列模型,自然语言处理领域。三维卷积这里采用代数的方式对三维卷积进行介绍,具体思想与一维卷积二维卷积相同。 由于计算机视觉的大红大紫,二维卷积的用处范围最广。因此本文首先介绍二维卷积,之后再介绍一维卷积与三维卷积的具体流程,并描述其各自的具体应用。1、二维卷积 •   图中的输入的数据维度为 14 × 14 ,过滤器大小为 5 × 5,二者做卷积,输出的数据维度为 10 × 1...

    renweihub 评论0 收藏0
  • 方位解读this

    摘要:的构造函数等同于下。创建一个新的对象将构造函数的指向这个新对象指向构造函数的代码,为这个对象添加属性,方法等返回新对象。 原文链接 - http://www.jianshu.com/p/d647aa6d1ae6 首先,了解下执行上下文的生命周期。 showImg(https://segmentfault.com/img/bVJs0x?w=1000&h=290); 在执行上下文的创建阶段...

    yexiaobai 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<