@toc

1.函数是什么?

数学中我们常见到函数的概念。但是你了解C语言中的函数吗? 维基百科中对函数的定义:子程序

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

 

2.C语言中函数的分类:

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

(1)库函数

那怎么学习库函数呢?
C语言库查询:www.cplusplus.com
如下:
图片.png
简单的总结,C语言常用的库函数都有:

  • IO函数
  • 字符串操作函数
  • 字符操作函数
  • 内存操作函数
  • 时间/日期函数
  • 数学函数
  • 其他库函数
    库函数实例
    图片.png
    1.代码如下(strcpy示例):
    将字符串拷贝到另一地址
    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    #include <string.h>
    int main()
    {
    char arr1[] = "hello";
    char arr2[] = "###########";
    strcpy(arr2, arr1);
    //strcpy->string copy 字符串拷贝,拷贝字符串(含‘\0’)
    //strcpy(拷贝目的地,字符串拷贝地址)
    printf("%s\n", arr1);

    图片.png
     
    2.代码如下(memset示例):
    字符串替换

    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    #include <string.h>
    int main()
    {
    char arr1[] = "hello world";
    memset(arr1, '*', 5);
    //memset(目的地,改变值(整形),改变个数)
    printf("%s\n", arr1);
    return 0;
    }

    图片.png

 
: 但是库函数必须知道的一个秘密就是:使用库函数,必须包含 #include对应的头文件。 这里对照文档来学习上面几个库函数,目的是掌握库函数的使用方法。
如何学会使用库函数?
 
学会查询工具的使用:

MSDN(Microsoft Developer Network)
www.cplusplus.com
zh.cppreference.com

(2)自定义函数

函数的组成:
图片.png
 
定义函数
函数体:即定义的函数中{}中的部分,交代的是函数的实现
我们举一个栗子:

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

#define _CRT_SECURE_NO_WARNINGS
#include &lt;stdio.h&gt;
#include &lt;string.h&gt;
//求两数中的较大值
//定义函数
int get_max(int x, int y)
{
if (x &gt; y)
return x;
else
return y;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d%d", &a, &b);
//函数的使用
int max = get_max(a, b);
printf("max = %d\n", max);
max = get_max(100, 300);
printf("max = %d\n", max);
return 0;
}

图片.png

//使用函数编写代码实现两数交换
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
//直接传递值进行交换,无效
//对形参的修改不会改变实参
//形参是实参的一份临时拷贝
void Swap1(int x,int y)//形参
//void表示空,无,此函数没有返回值,所有用void
{
    int tmp = 0;
    tmp = x;
    x = y;
    y = tmp;
    printf("x = %d  y = %d\n", x, y);//x,y交换成功,但x,y的值与a,b交换没有关系
}
//通过指针,取地址交换a,b的值有效
void Swap2(int* pa, int* pb)//形参
{
    int tmp = 0;
    tmp = *pa;
    *pa = *pb;
    *pb = tmp;
    printf("*pa = %d  *pb = %d\n", *pa, *pb);
}
int main()
{
    int a = 0;
    int b = 0;
    scanf("%d%d", &a, &b);
    printf("a = %d  b = %d\n", a, b);
    int tmp = 0;
    //tmp = a;
    //a = b;
    //b = tmp;//将这三条交换程序写成函数
    //调用Swap1函数——传值调用
    Swap1(a, b);//实参
    printf("a = %d  b = %d\n", a, b);//无效交换
    //调用Swap2函数——传址调用
    Swap2(&a, &b);//实参
    printf("a = %d  b = %d\n", a, b);//交换有效
    return 0;
}

图片.png
 

3.函数的参数

(1)实际参数(实参):真实传给函数的参数,叫实参。实参可以是:常量、变量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。
(2)形式参数(形参):形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。

上面Swap1和Swap2函数中的参数 x,y,pa,pb 都是形式参数;在main函数中传给Swap1的a,b和传给Swap2的a,b是实际参数。<br/>形参实例化之后其实相当于实参的一份临时拷贝。
 

4.函数的调用

(1)传值调用:
函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。
(2)传址调用:
传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。
这种传参方式可以让函数和函数外边的变量建立起正真的联系,也就是函数内部可以直接操作函数外部的变量。

 

5.函数实例

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

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
//求100-200间的素数
//素数返回1,不是素数返回0
int is_prime(int n)
{
    int m = 0;
    //用小于n的值试除n,能整除即不是素数
    for (m = 2; m<n; m++)//任何一个数的值必不大于其开根号的平方和,9<=3^2+3^2
    {
        if (n%m == 0)
            return 0;
        //在此处不能写else,return 1
        //因为只做了一次判断
    }
    if (m == n)//只有当for循环m<n不成立时跳到这,即m == n,此判断可不写
        return 1;
}
int main()
{
    int i = 0;
    for (i = 100; i <= 200; i++)
    {
        if (is_prime(i) == 1)
            printf("%d ", i);
    }
    return 0;
}

2)写一个函数,实现一个整形有序数组的二分查找

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
//数组元素查找
// 能找到返回数组下标,不能找到返回-1
 //arr实际上是一个地址
int binary_search(int arr[], int k, int sz)
{
    int left = 0;
    //数组传参仅传数组首元素地址,在函数内部不能得到数组个数
    //int sz = sizeof(arr) / sizeof(arr[0]);
    int right = sz - 1;
    while (left <= right)
    {
        int mid = (left + right) / 2;
        if (arr[mid] > k)
            right = mid -1;
        else if (arr[mid] < k)
            left = mid +1;
        else
            return mid;
    }
    return -1;
}
int main()
{
    int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
    int k = 7;
    int ret = 0;
    int sz = sizeof(arr) / sizeof(arr[0]);
    ret = binary_search(arr, k, sz);
    if (ret == -1)
    {
        printf("找不到指定的数字\n");
    }
    else
    {
        printf("找到了,下标是%d\n",ret );
    }
    return 0;
}

 

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

嵌套调用:

#include <stdio.h>
void new_line()
{
 printf("hehe\n");
}
void three_line()
{
    int i = 0;
 for(i=0; i<3; i++)
   {
        new_line();
   }
}
int main()
{
 three_line();
 return 0; 
 }

链式访问:把一个函数的返回值作为另外一个函数的参数。
实例:

#include <stdio.h>
int main()
{
    printf("%d", printf("%d", printf("%d", 43)));
    return 0; 
}

图片.png
这是因为printf的返回值是打印字符的个数,即printf("%d", printf("%d", printf("%d", 43)))—>printf("%d", printf("%d", 2))—>printf("%d", 1),即打印4321.

 

7.函数的声明和定义

函数声明:
1.告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,无关
紧要。
2.函数的声明一般出现在函数的使用之前。要满足先声明后使用。

  1. 函数的声明一般要放在头文件中的。
     
    函数定义:

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

图片.png

一般来说,我们最好将函数的定义部分放到函数调用前面,这样就可以避免函数声明的部分(编译器读取代码是从上往下依次扫描的),例如上面的这个代码可以写成这样:

图片.png
 
函数分写
test.h的内容 放置函数的声明

#ifndef __TEST_H__
#define __TEST_H__
//函数的声明
int Add(int x, int y); 
#endif //__TEST_H__

test.c的内容 放置函数的实现

#include "test.h"
//函数Add的实现
int Add(int x, int y) 
{
return x+y;
}

我们看到过很多t头文件都会加上类似上面的代码,为什么要加上这些代码呢?

防止同一个头文件被多次使用
头文件在使用的时候,实际上是将整个头文件的内容复制到这条语句处,如果同一个头文件多次使用,就会产生大量重复且无效的代码,降低代码的效率和增加代码所在内存。