前言
为提高代码的可读性和可维护性,在C语言中,针对复杂的程序代码往往采用代码分解成独立函数的方法来完成特定任务。因此可以说:
1.函数是大型程序中的某部分代码,由一个或多个语句块组成,复杂完成某项特定任务,具备相对的独立性。
2.一般会有输入参数并提供返回值,提供对过程的封装和细节的隐藏。
C语言中,基本组成单位是函数,函数主要分为库函数和自定义函数。
一、库函数
库函数(如printf、strlen、pow等)并不是由C语言标准提供的,C语言标准定义了常用函数使用标准的标准库。例如,标准规定:
1.函数的功能 - 字符串长度
2.函数名 - strlen
3.参数 - const char*str
4.返回类型 - size_t
基于此,编译厂商在其编译器上提供了一系列编译函数的实现,这些函数被称为库函数。
库函数相关头⽂件:https://zh.cppreference.com/w/c/header
库函数的使用方法:https://legacy.cplusplus.com/reference/clibrary/
例如:memset库函数
函数定义类型为:viod * memset(void * ptr, int walue, size_t num)
功能:ptr指向的内存空间块的前num个字节的内容设置成value值
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "hello C";
memset(arr, 'c', 5);
printf("%s\n", arr);//ccccc C
return 0;
}
二、自定义函数
和库函数一样,有函数名,返回值和函数参数,但这些都是自己设计的。
语法形式表示为:
ret_type fun_name(para1, *)
{
ststement;//语句项
}
其中:
1. ret_type 返回类型
2. fun_name 函数名
3. para1 函数参数,可以有1个、多个或没有
基于语法形式,我们可以自定义一个函数,实现交换两个整型变量的内容的功能。
按照交换整型变量的逻辑思路,我们编写了如下程序:
#include<stdio.h>
void Exc(int x, int y)
{
int tmp = 0;
tmp = x;
x = y;
y = tmp;
}
int main()
{
int m = 0;
int n = 0;
scanf("%d %d", &m, &n);
printf("交换前:m = %d, n = %d\n", m, n);
Exc(m, n);
printf("交换后:m = %d, n = %d\n", m, n);
return 0;
}
可是,当我们程序运行起来以后发现,并没有实现整型m和n内容的交换,程序运行结果为:
究其原因,我们对m、n、x、y的地址进行监视。由监视器可知,m、n、x、y的地址是不一样的,x、y另外分配了空间。
因此,我们引入了实参和形参的概念。在调用Exc函数时,传递给函数的参数m和n成为实际参数,简称实参。而执行Exc函数功能时,需要使用int x,int y来接受传递过来的参数,在这里x、y称为形式参数,简称形参。形参是形式上存在的,不会向内存申请空间,是在主函数寄存器压栈和子函数形参指针偏移来完成传参动作。因此,我们可以理解为,形参是实参的一份临时拷贝,对形参的修改不会改变实参,就比如在U盘中修改文件并不会改变原始文件的内容一样。
那么,如果能够建立一种联系,就是在函数内部操作外部这两个变量,就可以实现我们所需的功能。
我们应该知道这样一个程序:
#include<stdio.h>
int main()
{
int num = 10;
int* p = #
*p = 20;
printf("%d\n", num);//20
return 0;
}
通过地址找到对应内存空间,并完成对num的修改。因此,我们对交换程序作出如下修改:
#include<stdio.h>
void Swap2(int* p1, int* p2)//*p1就是外部的num1,*p2就是外部的num2
{
int tmp = 0;
tmp = *p1;//tmp=num1
*p1 = *p2;//num1=num2
*p2 = tmp;//num2=tmp
}
int main()
{
int num1 = 0;
int num2 = 0;
scanf("%d %d", &num1, &num2);
printf("交换前:num1=%d num2=%d\n", num1, num2);
Swap2(&num1,&num2);
printf("交换后:num1=%d num2=%d\n", num1, num2);
return 0;
}
程序执行后,实现了我们想要的功能,即
三、函数的参数
3.1 实际参数
真实传给函数的参数,可以是常量、变量、表达式、函数。但都必须有确定的值,以便把这些值传给形参。.
3.2 形式参数
函数名后括号中的变量。形参只有函数被调用时才被实例化,形参当函数调用完成之后就自动销毁,所以形参只在函数中有效。
四、函数调用
4.1 传值调用
形参和实参分别占有不同的内存块,对形参的修改不会影响实参。
4.2 传址调用
把函数外部创建变量内存的地址传递给函数参数的一种调用函数的方式。可以让函数参数与函数外部的变量建立起真正的联系,即在函数内部可以直接修改或操作函数外部的变量。
因此,我们可以总结为:
1. 只是需要用到传递进来的值,但并不会修改外边的变量,因此可用传值调用;
2. 当需要改变外边的变量,只修改形参没有意义,因此用传址调用。
五、return语句
在函数中,return表示执行结束当前函数,跳出并返回后面的内容。return后面返回的内容可以是值,可以是表达式,并呼应函数的返回类型。当不需要返回任何内容时,直接输入return;,此时对应函数的类型为void。
若return返回值与函数返回类型不一致时,则将返回值强制转换成函数返回类型。例如:
#include<stdio.h>
int test()
{
return 3.14;
}
int main()
{
int ret = test();
printf("%d\n", ret);//3
}
六、函数的嵌套调用和链式访问
6.1 函数的嵌套调用
嵌套调用就是某个函数调用另外一个函数(即函数嵌套允许在一个函数中调用另外一个函数)。
我们可以举个例子,写一个程序,判断输入一个年份是否是闰年,程序如下:
#include<stdio.h>
int is_year(int year)
{
if(((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))
return 1;
return 0;
}
int main()
{
int year = 0;
scanf("%d", &year);
if(is_year)
{
printf("%d是闰年!\n", year);
}
else
{
printf("%d不是闰年!\n", year);
}
return 0;
}
在上述这段代码中,main函数嵌套调用了scanf、is_year和printf函数。
值得注意的是,每个函数都是独立的,可以嵌套调用,但是不可以嵌套定义,如:
void test()
{
int Add(int x, int y) //err
{
return x + y;
}
}
int main()
{
return 0;
}
6.2 链式访问
把一个函数的返回值作为另一个函数的参数,将函数串起来形成函数链。
#include<stdio.h>
int main()
{
int len = strlen("abc");
printf("%d\n", len);
}
针对上述程序,根据链式访问定义,可以将strlen函数的返回值作为printf函数的参数,因此可以改为:
#include<stdio.h>
int main()
{
printf("%d\n", strlen("abc")); //链式访问
}
同理,我们也可以采用链式访问合并其他函数动作。
#include<stdio.h>
int main()
{
char arr1[20] = { 0 };//a,b,c,\0
char arr2[] = "abc";
printf("%d\n", strlen(strcpy(arr1, arr2))); //链式访问 //3
}
然而我们发现,printf函数往往是作为接受参数的一方,那么,如果我们连续访问printf,将printf作为返回值一方,结果会如何呢?为探究printf函数的返回值,我们查阅printf库函数并做代码如下:
#include<stdio.h>
int main()
{
printf("%d", printf("%d", printf("43")));
}
printf函数定义为:int printf ( const char * format, ... );,返回值为被写入字符的总数。因此,上述程序可解读为:
内层printf函数执行打印“43”后将返回值返回给中层printf函数,此时返回值为2。
printf("%d", printf("%d", 2));
中层printf函数执行打印“2”后将返回值返回给中层printf函数,此时返回值为1。
printf("%d", 1);
最外层函数在屏幕上打印“1”。
为进一步了解printf的返回值,我们将上述程序做进一步改写:
#include<stdio.h>
int main()
{
printf("%d\n", printf("%d\n", printf("43\n")));
}
此时,打印结果发生了改变,由此我们可以理解printf函数的返回值:在屏幕上打印几个字符,就返回几。