函数

函数是什么

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

C语言中函数的分类:

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

库函数

为什么会有库函数?

  • 在学习C语言编程的时候,总是在一个代码编写完成之后迫不及待的想知道结果,想把这个结果打印到我们的屏幕上看看。这时候我们会频繁的使用一个功能:将信息按照一定的格式打印到屏幕上(printf)。
  • 在编程的过程中我们会频繁的做一些字符串的拷贝工作(strcpy)。
  • 在编程时我们也会计算,总是会计算n的k次方这样的运算(pow)。
  • 简单来说,库函数就是把常用的一些函数放在一起,当调用头函数是就可以方便使用这些函数。

怎么学习库函数(库函数查找网站) : www.cplusplus.com

简单的总结,C语言常用的库函数有:

  1. IO函数

  2. 字符串操作函数

    //示例:使用strcpy();函数,将字符串1复制到字符串2中
    int main()
    {
        char arr1[] = "bit";
        char arr2[20] = "##########";
        
        strcpy(arr2,arr1);
        printf("%s\n",arr2);
        
        return 0 ;
    }
    
    //使用时,要保证对象和目标的内存空间足够大,防止数据溢出
    
  3. 字符操作函数

  4. 内存操作函数

    //示例:使用memset();函数将字符串“hello world”的前5个字符改成*号。
    int main()
    {
        char arr[] = "hello world";
        memset(arr,'*',5);
        printf("%s\n",arr);
        
        return 0 ;
    }
    
  5. 时间/日期函数

  6. 数学函数

  7. 其他库函数

注:使用库函数,必须包含#include对应的头文件。

如何学会使用库函数?

需要学会查询工具的使用:

MSDN(Microsoft Developer Network)

www.cplusplus.com

http://en.cppreference.com

自定义函数

自定义函数和库函数一样,有函数名,返回值类型和函数参数。但是不一样的是这些都是我们自己来设计。

函数的组成:

ret_type fun_name(para1,*)
{
    statement;
}
ret_type 返回类型 
fun_name 函数名
para1 函数参数
    
int Add(int x,int y)
{
    int z = 0;
    z = x+y;
    return 0 ;
}
//写一个函数,找出两个数的最大值
int get_max(int x,int y)
{
    if(x>y)
        return x;
    else
        return y;
    
    //return (x>Y)?(x):(y);
}

int main()
{
    int a = 0;
    int b = 0;
    max = get_max(a,b);
    printf("max = %d\n",max);
    
    return 0 ;
}
//写一个函数可以交换两个整型变量的内容

//没有返回值 - 返回类型为void
//Swap1();函数中两个参数的地址和主函数中a、b的地址不同,所以Swap1();不起作用
void Swap1(int x,int y)
{
    int tmp = 0;
    tmp = x;
    x = y;
    y = tmp;
}

void Swap2(int* pa,int* pb)
{
    int tmp = 0;
    tmp = *pa;
    *pa = *pb;
    *pb = tmp;
}

int main()
{
    int a = 10;
    int b = 20;
    int tmp = 0;
    
    Swap1(a,b);
    printf("a=%d b=%d\n",a,b);
        
    Swap2(&a,&b);
    printf("a=%d b=%d\n",a,b);
    
    return 0;
}

函数参数

  1. 实际参数(实参):

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

  2. 形式参数(形参):

    形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。

上面Swap1和Swap2函数中的参数x,y,pa,pb都是形式参数。在main函数中传给Swap1的a、b,和传给Swap2函数的&a、&b是实际参数。

形参其实是实参的一份临时拷贝,对形参的修改是不会改变实参的。

函数调用

  1. 传值调用

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

  2. 传址调用

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

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

  3. 练习

//写一个函数判断素数
int is_prime(int n)
{
    int j = 0;
    //sqrn(n) - 开平方 - 是代码的优化
    //for(j=2;j<sqrt(n);j++)
    for(j=2;j<n;j++)
    {
        if(n%j == 0)
            return 0 ;
    }
    return 1;
}

int main()
{
    //打印100-200之间的素数
    int i = 0;
    for(i=0;i<=200;i++)
    {
        //判断i是否为素数
        if(is_prime(i) == 1)
            printf("%d\n",i);        
    }
    
    return 0 ;
}
//写一个函数判断是否为闰年
int is_leap_year(int y)
{
    if ((y % 4 == 0 && y % 100 != 0) || y%400==0)
        return 1;
    else
        return 0;
}
int main()
{
    int year = 0;
    for (year = 1000; year <=2000; year++)
    {
        //判断是否为闰年
        if (is_leap_year(year) == 1)
        {
            printf("%d\n", year);
        }
    }

    return 0;
}
//写一个函数,实现一个整型有序数组的二分查找
//本质上int arr[]只是数组的首地址,而不是数组整体
int binary_search(int arr[],int k,int sz)
{
    int left = 0;
    int right = sz-1;
    
    while(left<=right)
    {
        int mid = ( (left+right)/2 );
        if(arr[mid]<k)
        {
            left = mid+1;
        }
        else if(arr[mid]>k)
        {
            right = mid-1;
        }
        else
        {
            return mid;
        }
    }    
}

int main()
{
    //二分查找
    //在一个有序数组中查找具体的某个数
    int arr[] = {1,2,3,4,5,6,7,8,9,10};
    int k = 7;
    int sz = sizeof(arr)/sizeof(arr[0]);
    
    //找到的话返回下标,没找到返回-1
    int ret = binary_search(arr,k,sz);
    if(ret == -1)
    {
        printf("找不到指定数字\n");
    }
    else
    {
        printf("找到了,下标是:%d\n",ret);
    }
    
    return 0 ;
}
//写一个函数,每调用一次这个函数,就会将num的值加1
void Add(int* p)
{
    (*p)++;
}

int main()
{
    int num = 0;
    int i =0;
    
    for(i=0;i<6;i++)
    {
        Add(&num);
    	printf("%d\n",num);
    }
    
    return 0 ;
}

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

函数和函数之间可以有机的结合

  1. 嵌套调用
//嵌套调用
#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 ;
}
  1. 链式调用

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

#include <stdio.h>
#include <string.h>

int main()
{
    char arr[20] = "hello";
    int ret = strlen(strcat(arr,"bit"));
    printf("%d\n",ret);
    
    return 0; 
}
int main()
{
    int len = 0;
    //1
    len = strlen("abc");
    printf("%d\n",len);
    //2
    printf("%d\n",strlen("abc"));
    
    return 0 ;
}
int main()
{
    //printf();返回的值是打印内容的个数
    printf("%d",printf("%d",printf("%d",43)));
}

//打印结果:4321

函数的声明和定义

函数声明:

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

函数定义:

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

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

int main()
{
    int a = 10;
    int b = 20;
    int sum = 0;
    
    //函数调用
    sum = Add(a,b);
    printf("sum = %d\n",sum);
    
    return 0 ;
}

//函数的定义

//***********add.h***********
//在头文件里进行函数声明
#define _CRT_SECURE_NO_WARNINGS 1
#ifndef _ADD.H_
#define _ADD.H_
int Add(int x,int y);

//***********add.c***********
//在c文件中进行函数的定义
#define _CRT_SECURE_NO_WARNINGS 1
int Add(int x,int y)
{
    int z = x+y;
    return 0 ;
}

//***********test.c***********
//在主函数文件内进行调用
#include "add.h"
int main()
{
    int a = 10;
    int b = 20;
    int sum = 0;
    
    //函数调用
    sum = Add(a,b);
    printf("sum = %d\n",sum);
    
    return 0 ;
}

函数递归

什么是递归

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

递归的两个必要条件

  • 存在限制条件,当满足这个限制条件的时候,递归便不再继续。
  • 每次递归调用之后越来越接近这个限制条件。