当前位置:   article > 正文

C语言——函数详解_c语言函数

c语言函数

目录

1.函数是什么?

2.C语言中函数的分类和使用

2.1.库函数的使用

2.2.自定义函数的使用

3.函数的参数

3.1.实际参数

3.2.形式参数

4.函数的调用

4.1.传值调用

4.2.传址调用

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

5.1.嵌套调用

5.2.链式访问

6.函数的声明和定义 

6.1.函数的声明

6.2.函数的定义

7.函数递归

7.1.什么是递归?

7.2.递归的两个必要条件

 7.3.递归与迭代


1.函数是什么?

前言:我们最常见的函数概念出现在数学中,但是C语言中函数概念你清楚吗?就让本篇文章来帮助你来了解C语言中的函数吧。

        让我们来看看百度百科对于函数定义的基本概括:

        计算机的函数,是一个固定的一个程序段,或称其为一个子程序,它在可以实现固定运算功能的同时,还带有一个入口和一个出口,所谓的入口,就是函数所带的各个参数,我们可以通过这个入口,把函数的参数值代入子程序,供计算机处理;所谓出口,就是指函数的函数值,在计算机求得之后,由此口带回给调用它的程序。


2.C语言中函数的分类和使用

        在C语言中函数分为两大类:

1、库函数

2、自定义函数

2.1.库函数的使用

什么是库函数?        

        在C语言的早期,有一些函数在编程时总是需要频繁的调用,为了更高的开发效率以及更好的代码可移植性,C语言官方提供了一系列的库函数以便于我们使用。例如:当我们在编写完一个程序想要验证结果时,就会使用库函数printf()函数来就结果进行打印。当我们需要计算字符串长度时,就可以直接使用库函数strlen()函数。这样可以大大提升我们的效率。

如何学会使用库函数?

        如上所述,合理的使用库函数可以有效地提高我们的编程效率。那要如何学会使用C语言中提供的库函数呢?这是我们可以借助以下网站和软件:

1、MSDN(Microsoft Developer Network)

2、www.cplusplus.com

3、http://en.cppreference.com(英文版)

4、http://zh.cppreference.com(中文版)

        下面我简单示范一下如何使用www.cplusplus.com来学习库函数。

 这里还有C语言标准库中的一些库函数:

 通过上图,我给大家简单介绍一下C语言中常用的库函数类型:

1、IO函数

2、字符串操作函数

3、字符操作函数

4、内存操作函数

5、时间/日期函数

6、数学函数

7、其他库函数

 下面我就参照文档学习库函数strcpy()函数的使用:

 

        想要使用库函数就要了解它的语法规则,strcpy()函数语法规则如下:

 strcpy(char * destination, const char * source);

char * destination参数的为拷贝源字符串的目标地址。

char * source参数是被拷贝的字符串的地址,提供字符串内容的地址。

可以理解成:strcpy(被拷贝的地址,提供源字符串地址);

        使用库函数需要进行相关的函数声明,也就是引用相关的头文件。通过阅读文档我们可以知道,使用strcpy函数需要我们引用头文件(#include<string.h>)。引用头文件语法如下

引用库函数头文件:#include<库函数头文件名.h>

引用自定义头文件:#include"自定义头文件名.h"

        看完了文档我们就可以上手敲一敲代码来使用这个库函数:

  1. #include<stdio.h>
  2. #include<string.h>
  3. int main()
  4. {
  5. char ch1[] = "abcde";
  6. char ch2[20] = {0};
  7. printf("库函数使用前\n");
  8. printf("ch1 = %s\n", ch1);
  9. printf("ch2 = %s\n", ch2);
  10. strcpy(ch2,ch1);
  11. //我们将ch1中的"abcde"内容拷贝到ch2中。
  12. printf("库函数使用后\n");
  13. printf("ch1 = %s\n", ch1);
  14. printf("ch2 = %s\n", ch2);
  15. return 0;
  16. }

总结:查找和使用库函数文档,英文阅读很重要。虽然这需要我们有一定的英文阅读能力,但只有这样才能够学好编程。千万不要看到文档时英文的就头大,就想着放弃,只要愿意尝试去读英文文档,我相信你的英文文档阅读能力就会越来越好,编程之路也会走得越来越远。

2.2.自定义函数的使用

为什么有了库函数还需要自定义函数呢?

        库函数是无法解决所有问题的,这也就是为什么需要我们学习的自定义函数。学习自定义函数是为了让我们根据实际的需求来编写相应的函数。

自定义函数的组成:

ret_typ  fun_name(para1,...)

{

        statement;(语句项)

}

ret_typ:返回值类型

fun_name:函数名

para:形式参数类型

下面我们通过代码实现求两个数较大值的自定义函数:

  1. //这里需要返回较大数的值,所以函数的返回类型为int
  2. int GetMax(int a, int b)//形式参数可以和实参同名
  3. {
  4. //判断较大值的函数体实现
  5. if (a > b)
  6. return a;
  7. else
  8. return b;
  9. }
  10. int main()
  11. {
  12. int a = 10;
  13. int b = 20;
  14. //创建一个变量接受函数的返回值
  15. int max = GetMax(a,b);//(a,b)为实际参数
  16. printf("max = %d\n", max);
  17. return 0;
  18. }

代码运行结果如下:

补充:定义自定义函数可以不需要设置形式参数,但是不能没有函数类型和函数名。


3.函数的参数

函数的参数分为:

1.形式参数(形参)

2.实际参数(实参)

3.1.实际参数

        实际参数如字面意思就是我们实际传递给函数的参数。上面举例写的求两数较大值的函数中,我们在主函数内传递的a,b就是函数的实参。实际参数可以是变量常量表达式函数等。

注意:无论是何种类型的量,在进行函数调用时,它都必须要有一个确定的值,以便于把具体的值传递给形式参数。

3.2.形式参数

        形式参数是指在定义或者声明函数时函数后括号内的变量。因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫做形式参数。形式参数在函数调用完成后自动销毁,因此形式参数只作用函数体内部。

为了更好理解函数传参,下面我们来编写一个函数交换两个变量的值:

  1. #include<stdio.h>
  2. //这里的返回类型设置为void,因为我们只需要函数交换一下两变量的值。
  3. void Swap1(int x, int y)
  4. {
  5. int tmp = 0;
  6. tmp = x;
  7. x = y;
  8. y = tmp;
  9. }
  10. int main()
  11. {
  12. int a = 10;
  13. int b = 20;
  14. printf("a = %d b = %d\n", a, b);
  15. //通过Swap1函数来交换两个变量
  16. Swap1(a,b);
  17. printf("a = %d b = %d\n", a, b);
  18. return 0;
  19. }

为什么我们这里两个变量的值没有发生交换呢?让我们通过F10对代码进行一个调试:

        通过上面截图我们可以看到在Swap1函数中的x 和 y就是函数的形式参数,主函数中的传递给Swap1函数的a 和 b是函数的实际参数。这里可以看到 Swap1 函数在调用的时候, x , y 拥有自己的空间,同时拥有了和实参一模一样的内容。所以我们可以简单的认为:形式参数就是实际参数的一份临时拷贝。


4.函数的调用

4.1.传值调用

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

        由于我们在上面编写的Swap1函数是传值调用,所以无法交换两个变量的值。

4.2.传址调用

传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。

        这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。

下面我们通过传址调用来更改一下我们之前写的Swap1函数,以实现交换两个变量的值。

  1. //用指针变量px 和 py来接收a和b的地址
  2. void Swap2(int* px, int* py)
  3. {
  4. //通过解引用操作符交换两个变量的值
  5. //这里可以把传址调用理解成通过指针远程操控两个变量的交换
  6. int tmp = *px;
  7. *px = *py;
  8. *py = tmp;
  9. }
  10. int main()
  11. {
  12. int a = 10;
  13. int b = 20;
  14. printf("a = %d b = %d\n", a, b);
  15. //实参传递的是a和b变量的地址
  16. Swap2(&a, &b);
  17. printf("a = %d b = %d\n", a, b);
  18. return 0;
  19. }

所以我们需要使用传址调用,才能实现交换两个变量值的函数。


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

5.1.嵌套调用

函数的嵌套调用就是在函数a中调用另一个函数b,然后在另一个函数b上调用另一个函数c。代码演示如下:

  1. #include<stdio.h>
  2. void print_line()
  3. {
  4. printf("haha\n");
  5. }
  6. void print_three_line()
  7. {
  8. int i = 0;
  9. for (i = 0; i <3; i++)
  10. {
  11. print_line();
  12. }
  13. }
  14. int main()
  15. {
  16. print_three_line();
  17. return 0;
  18. }

 注意:函数可以嵌套调用,但是不能嵌套定义。

5.2.链式访问

函数的链式访问就是把一个函数的返回值作为另一个函数的参数。

下面我还是用代码进行举例:

  1. #include <stdio.h>
  2. int main()
  3. {
  4. printf("%d", printf("%d", printf("%d", 43)));
  5. return 0;
  6. }

这个代码运行起来会在屏幕上输出什么呢?这里我们需要注意的是printf函数的返回值是他打印的个数。

 所以经过我的分析,屏幕上应该输出的是4321。让代码跑起来验证一下:


6.函数的声明和定义 

6.1.函数的声明

  1. #include<stdio.h>
  2. int main()
  3. {
  4. int a = 10;
  5. int b = 20;
  6. int c = Add(a,b);
  7. printf("c = %d",c);
  8. return 0;
  9. }
  10. int Add(int x, int y)
  11. {
  12. return x+y;
  13. }

 因为编译器是从上到下进行编译,程序在调用Add函数前没有对其进行声明,编译器表示我也不知道什么是Add函数,虽然后面有Add函数,但是编译器这里还是报错,提醒我们Add函数未定义。这时只要在主函数前对Add函数进行声明就可以正常调用Add函数了。

  1. //函数的声明
  2. int Add(int x,int y)

函数的声明规则:

1、函数声明就是告诉编译器有一个函数名叫什么?函数返回值类型是什么?函数的参数是什么?但是这个函数是否具体存在,函数声明决定不了。

2、函数声明一般出现在函数调用前,要满足先声明再调用。

3、在模块化编程时,函数声明一般放在头文件中。

6.2.函数的定义

函数的定义就是交代函数的功能和函数的具体实现。

  1. //Add函数的定义
  2. int Add(int x, int y)
  3. {
  4. return x+y;
  5. }

补充:函数定义在函数的调用前是一种特殊的声明。


7.函数递归

7.1.什么是递归?

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

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

7.2.递归的两个必要条件

1.存在限制条件,当递归满足该限制条件时,递归不再继续。

2.每次递归后都会更加接近这个限制条件。

下面我通过对练习《接受一个整型值(无符号),按照顺序打印它的每一位》来对函数递归进行一个演示。

首先我们先画图来理清一下思路:

根据思路我们可以用%10取余和/10减去一位,把打印1234的每一位通过递归实现。

  1. #include<stdio.h>
  2. void Print(int n)
  3. {
  4. if (n > 9)//递归限制条件
  5. {
  6. Print(n / 10);//每次递归后越来越接近限制条件
  7. }
  8. printf("%d ",n % 10);//回归时打印各个位数
  9. }
  10. int main()
  11. {
  12. unsigned int a = 1234;
  13. Print(a);
  14. return 0;
  15. }

 

 7.3.递归与迭代

通过递归实现斐波那契数列:

下面编写这个代码:

  1. #include<stdio.h>
  2. int Fib(int n)
  3. {
  4. if (n > 2)
  5. {
  6. return Fib(n-1)+Fib(n-2);
  7. }
  8. else
  9. {
  10. return 1;
  11. }
  12. }
  13. int main()
  14. {
  15. int n = 0;
  16. scanf("%d", &n);
  17. int ret = Fib(n);
  18. printf("ret = %d\n",ret);
  19. return 0;
  20. }

 当我们需要求第50个斐波那契数列时,程序一直运行许久都没有运行结果,同时也没有报错(栈溢出)。这是为什么呢?让我们简单分析一下递归:

可以看到程序在这里进行了大量重复的运算所以导致求第50个斐波那契数列程序运行了很久很久也没出结果。这时我们也需要应该尝试一下非递归方法。

迭代(非递归)实现斐波那契数列:

  1. #include<stdio.h>
  2. int Fib(int n)
  3. {
  4. int a = 1;
  5. int b = 1;
  6. //将返回的值设为1,是为了n <= 2情况下直接return c;
  7. int c = 1;
  8. while(n > 2)
  9. {
  10. c = a + b;
  11. a = b;
  12. b = c;
  13. n--;
  14. }
  15. return c;
  16. }
  17. int main()
  18. {
  19. int n = 0;
  20. scanf("%d", &n);
  21. int ret = Fib(n);
  22. printf("ret = %d\n", ret);
  23. }

注意:

1、当使用递归形式能够更好解释程序的运行,且递归无明显缺陷时,不妨使用递归解决问题。

2、通常情况下,迭代往往比递归来的更加高效,但是代码量会比递归多。当递归有效解决问题时,则需要使用迭代解决问题。

3、当一个问题相当复杂难以使用迭代解决时,此时递归的简洁性可以弥补程序运行时的开销。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/367944
推荐阅读
相关标签
  

闽ICP备14008679号