C语言函数详解

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

目录

一、函数的定义与分类

1.定义

2.分类

二、库函数

1.库函数存在的意义

2.库函数的学习和使用

三、自定义函数

1.自定义函数的组成

2.示例

1写一个函数找出两个整数的最大值

2写一个函数交换两个整型变量的内容

四、函数的参数

1. 实际参数实参

2.形式参数形参

五、函数的调用

1.传值调用

2.传址调用

3.错误讲解

4.练习

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

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

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

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

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

1.函数的嵌套调用

2.函数的链式访问

3.链式访问的经典例题

七、函数的定义和声明

1.函数的定义

2.函数的声明

3.程序的分块化编写

4.函数的声明和定义为什么不写在同一个.c文件内

八、函数递归与迭代

1.函数递归的定义与条件

2.讲解练习

3.函数的递归与迭代


函数是C语言的基本单位在C语言程序中发挥着极其重要的作用

一、函数的定义与分类

1.定义

在维基百科中函数的定义叫做子程序

1一个大型程序中的某部分代码 由一个或多个语句块组成。它负责完成某项特定任务而且相较于其他代 码具备相对的独立性。

2一般会有输入参数并有返回值提供对过程的封装和细节的隐藏。这些代码通常被集成为软

件库。

2.分类

1库函数C语言内部提供的函数。

2自定义函数自我发挥写出的函数。

二、库函数

1.库函数存在的意义

我们在编写C语言代码的时候总会频繁地使用一些功能

比如将信息按照一定的格式打印到屏幕上printf、在编程的过程中我们会频繁的做一些字符串的拷贝工作strcpy、在编程是我们也计算总是会计算n的k次方这样的运算pow......

像上面的这些基本的功能在编写程序时经常会用到。所以C语言的基础库中提供了一系列类似的库函数方便程序员进行软件开发。

2.库函数的学习和使用

库函数的使用不需要专门去记我们可以通过查找了解它们的使用方式。

这里推荐一个网站和一个应用程序

1www.cplusplus.com

2msdn

通过这些方式我们可以查找到它们的信息例如函数名、形式参数、需要的头文件和返回值等必要的信息。

这些工具的语言都是英文在学习编程的工程中我们需要学习英文保证以后在第一时间可以了解计算机的最新技术。

三、自定义函数

1.自定义函数的组成

自定义函数由程序员自主设计和普通的函数一样有函数名、返回类型、形式参数等。

基本结构如下

ret_type fun_name(para1, * )
{
    statement;//语句项
}
ret_type 返回类型
fun_name 函数名
para1    函数参数

2.示例

1写一个函数找出两个整数的最大值

#include<stdio.h>
int islarge(int a, int b)
{
    if (a>=b)
    {
        return a;
    }
    else
    {
        return b;
    }
}
//上述为实现程序的函数

int main()
{
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);
    int c = islarge(a, b);
    printf("%d", c);
    return 0;
}
//输入:10 20
//输出:20

2写一个函数交换两个整型变量的内容

错误示范

#include<stdio.h>
void swap(int a,int b)
{
    int temp = 0;
    temp = a;
    a = b;
    b = temp;
}
int main()
{
    int a = 0;
    
int b = 0;
     scanf("%d %d", &a, &b);
     printf("交换前a=%db=%d\n", a, b);
     swap(a, b);
     printf("交换前a=%db=%d\n", a, b);
     return 0;
}
//输入10 20
//输出
//交换前a=10b=20
//交换后a=10b=20
//

正确程序

#include<stdio.h>
void swap(int* pa, int* pb)
{
    int temp = 0;
    temp = *pa;
    *pa = *pb;
    *pb = temp;
}
int main()
{
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);
    printf("交换前a=%db=%d\n", a, b);
    swap(&a, &b);
    printf("交换前a=%db=%d\n", a, b);
    return 0;
}
//输入10 20
//输出
//交换前a=10b=20
//交换后a=20b=10
//

这个程序我先不讲错在哪里到后面形参的部分再详细解释。

四、函数的参数

1. 实际参数实参

真实传给函数的参数叫实参。

实参可以是常量、变量、表达式、函数等。

在调用函数时它们都必须有确定的值以便把这些值传送给形参。

2.形式参数形参

形式参数是指函数名后括号中的变量。

形式参数只有在函数被调用的过程中才实例化分配内存单元所以叫形式参数。因此形式参数只在函数中才有效。

下面是函数在处理数据时的处理思路

#include<stdio.h>
int islarge(int a, int b)
//int是返回类型括号里的int a和int b
{
    if (a>=b)
    {
        return a;
    }
    else
    {
        return b;
    }
}
//上述为实现程序的函数

int main()
{
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);//输入ab的值
    int c = islarge(a, b);//islarge有两个实参a和b定义变量c接收islarge函数的返回值
    printf("%d", c);
    return 0;
}

形参实例化之后其实相当于实参的一份临时拷贝。

五、函数的调用

1.传值调用

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

所以我们在不改变函数实参的时候可以使用传值调用。

比如我们写一个程序计算两个整数的和

#include<stdio.h>
int add(int x,int y)
{
    return x+y;
}
int main()
{
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);
    int c= add(a,b);
    printf("%d\n",c);
    return 0;
}

在这个程序中我们只是使用a和b进行操作而没有改变a和b的数值等属性这时我们就可以使用传值调用再将操作得到的值返回。

2.传址调用

传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。这种传参方式可以让函数和函数外边的变量建立起真正的联系也就是函数内部可以直接操作函数外部的变量。

#include<stdio.h>
void swap(int* pa, int* pb)
{
    int temp = 0;
    temp = *pa;
    *pa = *pb;
    *pb = temp;
}
int main()
{
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);
    printf("交换前a=%db=%d\n", a, b);
    swap(&a, &b);
    printf("交换前a=%db=%d\n", a, b);
    return 0;
}

在这个程序中我们改变了a和b的数值这时我们就需要使用传址调用因为在传值调用中形参的改变是不会影响实参的。

3.错误讲解

讲到这里我们讲一讲上面使用传值调用交换数值的程序错在哪里

#include<stdio.h>
void swap(int a,int b)//返回类型为void表示不返回此处的int a与int b表示形式参数和它们的类型
{
    int temp = 0;//定义一个临时变量
    temp = a;//把a的值赋给temp
    a = b;//把b的值赋给a
    b = temp;//把temp的值赋给b完成交换操作
    //注意因为形参只是实参的一份临时拷贝在整个函数中我们改变的只是实参出函数后形参被销毁无法改变实参
}
int main()
{
    int a = 0;//创建变量a
    
int b = 0;//创建变量b
    scanf("%d %d", &a, &b);//输入数值
    printf("交换前a=%db=%d\n", a, b);//展示
    swap(a, b);//交换函数将ab传进去
    printf("交换前a=%db=%d\n", a, b);//实参依旧是a和b的原始值没有达到我们的目的
    return 0;
}

打个比方就好像老师在练习册上留作业你确实是写了就是写在了你同学的练习册上。虽然确实做了正确的事但是做完了也没什么用你的作业本依旧是空的。PS偷把别人作业写了阻止他学习内卷的高级境界

传址调用的程序传递的是实参的地址这是实参的本质属性。

#include<stdio.h>
void swap(int* pa, int* pb)//返回类型为void表示不返回此处的int* pa与int* pb表示形式参数和它们的类型
{
    int temp = 0;//定义临时变量
    temp = *pa;//用地址找到实参a并赋给temp
    *pa = *pb;
    //把用地址找到的实参b赋给用地址找到的实参a
    *pb = temp;//用地址找到实参b并赋给temp
    //跳出函数时被销毁的形参只是两个指针变量此时实参的交换已经完成
}
int main()
{
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);
    printf("交换前a=%db=%d\n", a, b);
    swap(&a, &b);//传入地址
    printf("交换前a=%db=%d\n", a, b);
    return 0;
}

这次我们也干同样的事情比如写作业。但这次我们定位到了你自己的作业本上就可以实现写作业的任务。

4.练习

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

函数1

int isprime(int x)//这个形参用于接收需要判断的数字
{
    int i = 2;
    for (i=2; i<x; i++)//从2到这个数字减一逐一试除
    {
	if (x%i == 0)//如果有能除开的就表明它不是素数返回0
        {
	    return 0;
	}
    }
    return 1;//在除完所有的数字均除不开时为素数返回1
}

这个程序是可以改进的

比如说4×4=16而2×8=16或8×2=16也成立16×1=16或1×16=16依旧成立。

我们不难看出被乘数和乘数一定会有一个大于等于这个积开根号一个小于等于这个积开根号那么我们只需要试除到根号下x就完全可以判断一个数字的是否为素数。

函数2

#include<math.h>
int isprime(int x)
{
    int i = 2;
    for (i=2; i<=sqrt(x); i++)//sqrt表示对参数开平方
    {
	if (x%i == 0)
        {
	    return 0;
	}
    }
    return 1;
}

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

判定条件 对于整百的年份闰年必定是400的倍数 对于不是整百的闰年闰年是4的倍数

函数1

int isleap(int year)
{
    if (year % 400 == 0)
    {
        return 1;
    }
    if (year%4==0)
    {
        if (year % 100 != 0)
	{
	    return 1;
	}
    }
    return 0;
}

我们把这两个条件集成一下得到函数2

函数2

#include<stdio.h>
isleap(int year)
{
    if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))
    //((year是4的倍数)并且(year不是100的倍数))或者(year是400的倍数)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

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

int search(int arr[], int a, int sz)//形参为数组、需要查找的整数、数组的元素个数
{
    int left = 0;
    int right = sz - 1;
    int mid = 0;
    while (left <= right)
    {
        mid = left + (right - left) / 2;//找中间的元素
	if (arr[mid] > a)//中间元素大于查找值就从右缩小一半的范围
	{
	    right = mid-1;//可以使用--mid不推荐
	}
	else if (arr[mid] < a)//中间元素小于查找值就从左缩小一半的范围
	{
	    left = mid+1;//可以使用++mid不推荐
	}
	else
	{
	    return mid;//找到了返回下标
	}
    }
    if (left>right) //正常情况下不会出现
    {
        return -1;//找不到返回-1
    }
}

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

#include<stdio.h>
void test(int* p)//在主程序内定义一个变量储存调用的次数因为需要改变变量的值所以进行传址调用
{
    printf("hehe\n");
    (*p)++;//解引用找到变量再加1注意这个括号不能忘
    //否则*p++就表示每次这个指针先向后移动4个字节然后解引用
}

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

1.函数的嵌套调用

函数可以根据需要进行相互调用。

#include<stdio.h>
int main()
{
    printf("Hello world\n");
    return 0;
}

这是每一个初学者都会写的代码我们先调用了main函数然后在main函数的内部又调用了printf函数这就是嵌套调用。

2.函数的链式访问

我们为了减少不必要变量的定义可以直接把一个函数的返回值作为另一个函数的参数。

#include<string.h>
#include<stdio.h>
int main()
{
    char arr[20] = "abcdef";
    printf("%d", strlen(arr));
    return 0;
}

strlen函数的返回值变成了printf函数的参数这就把这两个函数像锁链一样串联起来也就是链式访问。

3.链式访问的经典例题

这个程序的输出是什么

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

答案4321

printf这个函数的返回值是它打印字符的个数首先进入最外层的printf函数

这层函数需要第二层函数printf("%d", printf("%d", 43))的返回值

而第二层的printf函数又需要第三层函数printf("%d", 43)的返回值

在执行完第三层的printf("%d", 43)函数后返回打印字符的个数2

printf("%d", printf("%d", 2))

第二层得到返回值2打印2而此时第二层函数也返回它打出的字符的个数1

printf("%d", 1)

最后打印1也就形成了4321的输出结果

七、函数的定义和声明

1.函数的定义

1函数的定义是指函数的具体实现交待函数的功能实现。相当于我们平常创建自定义函数的步骤。

2函数不能嵌套定义

错误的定义方法

int add(int x,int y)//加法函数
{
    return x + y;
    int sub(int x, int y)//这个减法函数被嵌套定义在了加法函数内部这种写法是错的
    {
        return x - y;
    }
}

正确的定义方法

int add(int x,int y)//加法函数
{
    return x + y;
}
int sub(int x, int y)//减法函数
{
    return x - y;
}

对于函数来讲数数平等不能搞特权。

2.函数的声明

函数的声明

  • 函数的声明主要的目的在于告诉编译器有一个函数叫什么参数是什么返回类型是什么。
  • 但是这个函数具体存在不存在函数声明决定不了。
  • 函数的声明一般出现在函数的使用之前。要满足先声明后使用。
  • 函数的声明一般要放在头文件中的。

3.程序的分块化编写

我们在写代码的时候可能会想我把所有的代码写在一个源文件中这样找起来不就方便了吗。

其实这样的习惯对日后程序的开发是不利的。

我们的社会是有各自的分工的当我们在开发一个程序的时候我们往往只需要负责一个大的工程中的部分内容比如一个人去写主程序一个人写函数等等而我们将工程的各个部分分开就可以更快地快找到bug并对应修复。

这样当我们写一个函数时就需要这样的文件分配

  • 函数声明——头文件.h
  • 函数定义——函数实现的源文件.c

每一个函数都可以分成这两个文件编写也可以几个函数写在两个文件中。

4.函数的声明和定义为什么不写在同一个.c文件内

这里涉及到一个代码加密的问题我会补充。

八、函数递归与迭代

1.函数递归的定义与条件

1递归的定义

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

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

2递归的两个必要条件

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

2.讲解练习

接受一个整型值无符号按照顺序打印它的每一位

例如输入1234输出 1 2 3 4

#include <stdio.h>
void print(int n) 
{
    if(n>9)
    {
        print(n/10);
    }
    printf("%d ", n%10);
}
int main()
{
    int num = 1234;
    print(num);
    return 0; 
}

1思想层面大事化小

我们如果想要得到一个数字的每一位就需要我们先%10得到最后一位后/10除去最后一位因为/10最后一位为余数,可以继续向前查找直到这个数字成为一个一位数停止程序因为如果这里是个一位数a/10的值就是0我们并不想打印0的每一位所以在这里我们定义一个函数print()它可以按顺序打印每一个值。

分步解决就是这样:

print(1234)

print(123) 4

print(12) 3 4

2实践讲解

print(1234);//这个函数从上到下先递进后回归
//1234大于9进入if语句,第一层
print(1234) 
{
    if(n>9)//n=1234,满足条件进入if
    {
        print(123);
    }
    printf("%d ", n%10);//第一层a%10=4
}
//print(123)展开n=123满足条件继续进入下一层
print(123) 
{
    if(n>9)//a/10=123,满足条件进入if
    {
        print(12);
    }
    printf("%d ", n%10);//第二层a%10=3
}
//print(12)展开a/10=1此时不满足条件不会继续进入下一层的if语句
print(12)
{
    if(n>9)//n=12,不满足条件不进入if
    {
        print(1);
    }
    printf("%d ", n%10);//第三层a%10=2
}
print(1)
{
    if(n>9)//n=1,不满足条件不进入if
    {
        print(0);
    }
    printf("%d ", n%10);//第三层a%10=1
}
递归的“递”此时已经完成我们将这个代码整理一下查看它时如何“归”的
print(1234) 
{
    {
        {
            {
                printf("%d ",n%10);//第四层a%10=1
            }
            printf("%d ", n%10);//第三层a%10=2
        }
        printf("%d ", n%10);//第二层a%10=3
    }
    printf("%d ", n%10);//第一层a%10=4
}
//代码从第四层开始向外执行故可以实现数字的按位打印
//输出1 2 3 4

3.函数的递归与迭代

1什么是迭代

迭代实际上就是重复如果只讨论我们比较熟悉的程序设计操作迭代在程序中就表示循环。

2函数递归和迭代的优缺点

函数递归中我们一层一层调用函数它的优点是所需代码量少简洁。但缺点主要有两个一方面大量重复的计算拖慢了程序的运行速度另一方面函数每一次被调用的时候都需要在栈区开辟相应的空间当递归过深时可能会出现栈溢出。栈区的空间已经被用完了程序无法继续进行了

当我们使用迭代时循环不需要大量调用函数重复的计算会少很多这个程序的运行速度会加快不少只是这个程序的代码量会大很多。下面这个程序不是很明显但也确实更短

程序应用递归求斐波那契数列的第n项

斐波那契数列1 1 2 3 5 8 13 ...规律第一二项为1后一项等于前两项的和

递归程序

#include<stdio.h>
int fib(int m)
{
    int ret = 0;
    if (m<=2)
    {
        ret = 1;//第一二项为1
    }
    else 
    {
        ret = fib(m - 1) + fib(m - 2);//三项及三项以后后一项等于前两项的和
    }
    return ret;
}
int main()
{
    int n = 0;
    scanf("%d", &n);
    printf("%d",fib(n));
    return 0;
}

迭代程序

#include<stdio.h>
int fib(int m)
{	
    if (m < 2)//前两项为1
    {
        return 1;
    }
    else//后两项为前两项之和
    { 
        int i = 0;
	int a = 1;
	int b = 1;
	int c = 0;
	for (i=m; i>2; i--)
	{
	    c = a + b;
     	    a = b;//把原来的第二个数变成新计算中的第一个数
	    b = c;//把算出的结果变为新计算的第二个数
	}
	return c;
    }
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	printf("%d",fib(n));
	return 0;
}

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6