C语言入门知识——(1)函数基础-创新互联

1、什么是函数

在数学函数是一种对应关系,C语言里的函数和数学中的函数还是极其相似的

专注于为中小企业提供成都网站制作、网站建设、外贸网站建设服务,电脑端+手机端+微信端的三站合一,更高效的管理,为中小企业潼关免费做网站提供优质的服务。我们立足成都,凝聚了一批互联网行业人才,有力地推动了超过千家企业的稳健成长,帮助中小企业通过网站建设实现规模扩充和转变。(1)维基百科对函数的定义

在这里插入图片描述
①维基百科:子程序 - 维基百科,自由的百科全书 (shenyinjise.icu)
②子程序。是大型程序中的某部分代码,由一个或多个语句组成。负责完成某项特定的任务,而且相较于其他代码具有相对的独立性。

(2)另外的补充说明

C语言中的函数,一般会有输入参数并由返回值,提供对过程的封装和细节的隐藏,这些代码通常集成为一个软件库。

2、C语言中函数的分类(“库函数”和“自定义函数”) (1)库函数

①C语言自带的函数,可以直接使用,方便程序员进行软件开发
可以在这个网站里面查找: https://cplusplus.com/
或者C/C++官网: cppreference.com(中文版: cppreference.com)
或者使用工具MSDN,可以离线查看,但是更新度不够
②使用库函数,需要包含#include对应的头文件
③常用的库文件如下:

  • IO函数
  • 字符串操作函数
  • 字符处理函数
  • 内存操作函数
  • 时间/日期函数
  • 数学函数
  • 错误消息
  • 功能函数
  • windows
  • 其他库函数<……>
(2)自定义函数

不可能所有的库函数都符合我们的需求,因此我们还需要自己写符合自己需求的函数,所以学会写自定义函数是最为重要的!
①函数必须有这些构成:函数的返回类型(注意这不是函数的类型)、函数名、函数参数列表、函数体
②伪代码:函数的结构

返回类型 函数名(参数列表)//注意返回指和参数列表可以是“空”,即void的情况
{函数体(具体代码);
}//如果忘记写返回类型,则默认返回一个整数,但不写返回类型是一个坏习惯!

③一些例子
例子1:比大小的函数

int MAX(int x, int y)//自定义“相加”函数
{return (x>y)?(x):(y);//使用了三目操作符
}
int main()//主体函数
{int num_1 = 10;
    int num_2 = 100;
    int max = MAX(num_1, num_2);
    printf("max = %d\n",max);
    return 0;
}

例子2:查找闰年

int is_leap_year(int y)//自定义“判断是否为闰年”函数
{return ((y % 4 == 0) && (y % 100 != 0)) || y % 400 == 0;
}

int main()//主体函数
{int y = 0;
    for (y = 1000; y<= 2000; y++)
    {//判断y是否为闰年
        //如果是闰年返回1 
        //如果不是闰年返回0
        if (is_leap_year(y))
        {printf("%d ", y);
        }
    }
    return 0;
}

④一般来说要先在主函数(主程序)里面写如何去用、在哪里用这个函数,再去写自定义函数的具体实现会比较好一点。(这其实是TDD思想,TDD指“测试驱动开发(Test-Driven Development)”)

3、函数的参数 (1)实际参数(实参)

真实传给函数的参数,实参可以是:常量、变量、表达式、函数等。
实参在进行函数调用的时候,必须有确定的值,然后传值给形参使用。

(2)形式参数(形参)

①简单来讲就是函数定义后,函数名后面括号的内容中的变量,只有在函数被调用的时候,实参传值给形参时,形参才会有值(即“才会实例化“,“才会分配内存单元”,因此就是形式上的参数)。
②更简单理解就是,“形参”是“实参”的临时拷贝,对形参的改动和修改是不会影响到实参的。
③如果需要通过形参来改动实参,则不能只是传递值,而是应该传递地址!
④另外形式参数在函数调用完后就自动销毁了,因此形式参数只有在函数内部才是有效的
⑤函数中的形式参数是在栈中开辟空间的

(3)有关函数参数易错题
int founction((a1, a2), (a3, a4), a5, a6);//函数有几个参数呢?

实际上应该是4个参数,(a1, a2)和(a3, a4)是逗号表达式,他们都各有一个结果,分别为a2和a4,故实际上函数的参数为a2,a4,a5,a6四个参数

(4)函数参数个数设置得越少越好

不然用户使用的时候,还得搞清楚每个参数的意义,并且还要输入很多参数才能使用

4、函数的调用(使用函数交换两个数字) (1)传值调用

函数的形参和实参分别占有不同的内存,对形参的修改不会影响实参。
如果写成下面这个代码就会交换失败(本质就是形参和实参的问题)

//一个典型的例子(写一个错误交换两个数的函数)
void function(int a, int b)//这里自定义函数只是临时拷贝(传过来)num_1和num_2两个数的值,所以下面只是x和y的值交换了,而num_1和num_2却没有进行实际上的交换
{int t = 0;
    t = a;
    a = b;
    b = t;
}

int main()
{int num_1 = 1;
    int num_2 = 2;
    printf("%d %d\n",num_1, num_2);
    
    function(num_1, num_2);//使用交换函数
    printf("%d %d\n", num_1, num_2);
    return 0;
}//这个函数通过编译器的调试功能会更加清晰看到
(2)传址调用
//一个典型的例子(写一个正确交换两个数的函数)
void function(int *pa, int *pb)
{int t = 0;
    t = *pa;
    *pa = *pb;
    *pb = t;
}
int main()
{int num_1 = 1;
    int num_2 = 2;
    printf("%d %d\n", num_1, num_2);
    
    function(&num_1, &num_2);//使用交换函数
    printf("%d %d\n", num_1, num_2);
    return 0;
}

总之函数的调用无论是传值还是传址都有利弊,看情况选择传值方式。从功能上传址调用比较强大,但是容易出现更多问题。

(3)高内聚低耦合

函数设计应该追求“高内聚低耦合”:在函数体内实现修改,尽量不要对外部产生影响,否则代码不便修改

5、函数的“嵌套调用”和“链式访问” (1)嵌套调用(在一个函数里调用了另一个函数)
#includevoid function_1()
{printf("aaaaa\n");
}
void function_2()
{int i = 0;
    for(i=0; i<3; i++)
    {function_1();//调用了另外一个函数
    }
}
int main()
{function_2();
    return 0;
}

注意:函数可以嵌套调用但是不能嵌套定义(一个A函数里面又定义了一个B函数,但是可以在A定义里面调用一个已经定义好的函数B(或者B定义里面调用一个已经定义好的函数A))

//以下“嵌套定义”是不被允许的!
返回类型 f1(参数列表)
{//一些代码
    返回类型 f2(参数列表)
    {//一些代码    
    }
    //一些代码
}
(2)链式访问(把一个函数的返回值作为另一个函数的参数)
//链式访问例子1
#include#includeint main()
{printf("%d\n", strlen("abcd"));
    return 0;
}
//链式访问例子2
#includeint main()
{printf("%d",printf("%d",printf("%d",43)));//printf返回值是读取到字符的个数
    return 0;
}//结果为4321这四个字符,注意不是四千三百二十一,只是单纯的输出4、3、2、1这四个字符
6、函数的声明和定义
//实际上函数的声明和变量的声明很像
#includeint a;变量的声明
int main()
{printf("%d\n",a);//变量的使用
    return 0;
}
int a = 10;//变量的定义
//这种写法也是可以正常打印出来10的
(1)函数的声明

①是为了告诉编译器存在一个自定义的函数,需要声明函数的结构
(函数名字、函数参数、返回类型)
②一般在使用函数之前声明函数
③函数的声明一般放在头文件中

(2)函数的定义

交代函数的具体实现方式

(3)教科书里常见函数声明的使用:
#includeint Add(int x, int y);//函数的声明

int main()
{int a = 1, b = 2;
    printf("%d", Add(a, b));//函数的使用
    return 0;
}

int Add(int x, int y)//函数的定义
{return x + y;
}
//这种写法虽然对,但是实际情况一般不会这么写,函数的声明一般放在头文件中,函数的实现则放到另外一个专门用来自定义函数的源文件中
(4)实际上的工程里常见的,函数声明较多的写法是:
//头文件add.h中
#ifndef __ADD_H__
#define __ADD_H__

int Add(int x, int y);//函数的声明

#endif
//函数定义源文件add.h
int Add(int x, int y)//函数的定义
{return x + y;
}
//程序主体文件main.h
#include#include "Add.h"
int main()//主函数
{int a = 1,b = 2;
    printf("%d", Add(a, b));//函数的使用
    return 0;
}
(5)一是适合分工、二是有保密措施(静态库.lib的使用)

①总不可能一堆程序员挤在一个电脑屏幕上写代码吧?多文件使得有程序员得以有效分工,每一个程序员写各种工作的头文件和源文件,最后再开始整合比较好
②使用VS产生静态库详细步骤,见我另外一篇博客:VS静态库的产生和使用

7、函数递归(递推与回归)

递归适当使用会大大减少代码量

(1)什么是递归

函数“直接”或者“间接”调用自己的一种编程技巧,它通常把一件“大事”化成“小事”来进行求解,函数不断递推出去,然后再不断地回归,组合成“递归”一词。
①最简单的递归

//最简单的递归,main函数自己调用自己
#includeint main()
{printf("abcd\n");
    main();
}//不断打印abcd此时main将不断消耗栈区的空间,最后栈溢出,程序崩溃

②按照顺序打印一个数的每一位数字字符

#includemy_printf(unsigned int x)
{if(x >9)
    {my_printf(x / 10);
        printf("%u ", x % 10);
    }
    else
    {printf("%d ", x);
    }
}
//大事化小
//my_printf(1234)
//my_printf(123)4
//my_printf(12)34
//my_printf(1)234
int main()
{unsigned int number = 0;
    scanf("%u",&number);
    my_printf(number);
    return 0;
}

理解递归函数可以像上面一样大事化小,或者是画多个图来理解
③自定义一个函数,实现和strlen函数一样的功能

#includemy_strlen(const char *str)
{if(*str == '\0')
    {return 0;
    }
    else
    {return (1 + my_strlen(str+1));
    }
}

int main()
{char arr[10] = "abcdef";
    printf("%d", my_strlen(arr));

    return 0;
}

④第n个斐波那契数列(不考虑溢出的情况)

#includeint fun(int n)
{if (n<= 2)
        return 1;
    else
        return fun(n - 1) + fun(n - 2);
}
int main()
{int n = 0;
    while (scanf("%d", &n) == 1)
    {printf("第%d个斐波那契数列为%d\n", n, fun(n));
    }
    return 0;
}

⑤阶乘的实现(不考虑溢出的情况)

#includeint fun(int n)
{if (n<= 1)
    {return 1;
    }
    else
    {return n * fun(n - 1);
    }
}
int main()
{int n = 0;
    scanf("%d", &n);
    printf("%d", fun(n));
    return 0;
}
(2)递归的必要条件

①存在限制条件(不能像上面main自己调用自己一样,否则迟早栈溢出(stack overflow))让递归停下
②每次递归都要接近这个条件,不能原封不动
递归有可能大量消耗栈空间,计算量极大,有的时候反而效率低,例如:求斐波那契数列的时候求第50个数就会很吃力了,运算时间很久

(3)迭代(循环)方式代替递归,斐波那契数列的另外一种写法
#includeint function(int n)//用递归的方法求斐波那契数列
{int a=1;
    int b=1;
    int c=1;
    while(n >= 3)
    {c = a + b;
        a = b;
        b = c;
        n--;
    }
        return c;
}
int main()
{return 0;
}
#includeint function(int n)
{int a = 1;
    while(n >1)
    {a *= n;
        n--;    
    }
    return a;
}
int main()//利用迭代求n的阶乘
{int n = 0;
    scanf("%d", &n);
    printf("%d", function(n));
    return 0;
}
(4)何时选递归

看情况,那种方便少缺陷就选哪种,没必要一定要写出递归(当然递归能大大减少代码量)。况且迭代(循环)的方法会更加清晰。

8、函数如何返回多个值

(1)使用多个全局变量,在函数内部改变,则相当于函数返回了多个值
(2)返回一个数组指针,就可以通过数组返回多个值
(3)形参使用多个指针变量,在函数体内部解引用,改变指针指向的值
唯独不能单用return,只能返回一个值,即一个函数只能返回单个结果,不能是多个

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


分享名称:C语言入门知识——(1)函数基础-创新互联
文章网址:http://myzitong.com/article/dijhog.html