目录
函数的概念
两类函数
C语言中的函数分为两大类,分别是标准函数(库函数)和自定义函数。
标准函数
标准函数是C语言系统为方便用户而预先编好的函数。按应用分类,C语言提供了大量的标准函数(库函数),每类库函数都定义了自己专用的常量、符号、数据类型、函数接口和宏等,这些信息都在他们专用的头文件中被定义。使用相应库函数的程序都要在使用之前写上包含其头文件的预处理命令。
以下是常用的头文件:
- stdio.h:输入输出库函数,如printf()和scanf(),以及文件操作相关函数。
- stdlib.h:系统库函数,定义了一些通用的函数,如内存分配、字符串转换、随机数生成等。
- string.h:字符串处理函数库函数,如strcpy()、strcat()、strlen()等。
- math.h:数学库函数,定义了一些数学运算函数,如sin()、cos()、sqrt()等。
- time.h:时间库函数,定义了一些时间和日期处理函数,如time()、gmtime()、localtime()等。
- ctype.h:字符处理库函数,如isalpha()、isdigit()、toupper()等。
- malloc.h:动态存储分配库函数。
自定义函数
除了标准函数外,C语言也允许用户定义自己的函数。自定义函数通常用于执行特定任务,并可以在程序中多次调用。
函数的定义与声明
函数的定义:
函数由函数首部和函数体组成,具体格式为
<函数类型> 函数名 (<形参列表>)
{
函数体
}
说明:
1.函数类型:指定函数返回的数据类型。可以是任何合法的C数据类型,例如int
、float
、double
、char
等。
2.函数名:函数的名称。命名规则与变量相同,使用有意义的名称以便于理解。
3.形参列表:函数参数列表,包含多个参数,每个参数由类型和名称组成,参数之间使用逗号分隔。如果函数不需要参数,则参数列表为空。例如,int add(int num1, int num2)
中的num1
和num2
就是参数。
4.函数体:函数执行的代码块,包括声明、表达式、循环、条件语句等程序代码。在函数体内,可以通过调用其他函数来执行特定的操作。
5.当函数体是由零个语句组成时,称该函数为空函数。函数体无论语句多少,大括号是不可能省的。
6.C语言中所有函数都是平行的,一个函数并不从属于另一个函数,C语言中不允许函数嵌套定义。
7.未进行函数声明时,函数定义必须在调用它的函数之前。
#include <stdio.h>
int multiply(int a, int b) //函数首部(未进行函数声明)
{
int product = a * b;
return product;
}
int main()
{
int x = 5;
int y = 10;
int result = multiply(x, y);//函数使用
printf("The product of %d and %d is %d", x, y, result);
return 0;
}
函数的声明:
对于用户自定义函数,应该在源代码中说明数据原型,函数声明是一条程序语句,它由函数首部和分号组成,一般形式为:
<函数类型> 函数名 (<形参列表>);
说明:
1.各部分内容与函数首部完全相同。
2.函数声明中的形参可以省略名称只声明参数类型,而函数首部不能。
3.函数声明是语句,而函数首部不是。
4.当函数声明在函数调用之后时,必须在调用它之前进行函数声明。
#include <stdio.h>
int multiply(int a, int b); //函数声明
int main() {
int x = 5;
int y = 10;
int result = multiply(x, y);//函数使用
printf("The product of %d and %d is %d", x, y, result);
return 0;
}
int multiply(int a, int b)//函数定义
{
int product = a * b;
return product;
}
函数的调用和返回
一个函数可以被其他函数调用,返回相应的结果。
函数的调用
在 main 函数中调用函数时,需要使用函数名和实际参数列表,实际参数的类型和数量应该与函数定义或声明中的形式参数匹配。一般形式如下:
函数名(<实参列表>);
说明:
1.<实参列表>中的参数称为实际参数(简称实参),实参可以是常数、变量或表达式,各实参之间也是用逗号分隔。
2.实参的个数、次序和类型必须与形参完全一致,对无参函数调用时无实际参数表。
3.函数调用有两种形式:函数表达式和函数语句。
·函数表达式:函数调用出现在一个表达式中,这种表达式称为函数表达式。举例如下:
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int main()
{
int (*func_ptr)(int, int) = &add; // 将函数名作为指针,即可用函数表达式形式调用该函数
int result = (*func_ptr)(2, 3); // 调用函数表达式
printf("The result is: %d", result);
return 0;
}
·函数语句:把函数调用作为一个语句。举例如下:
#include <stdio.h>
int main()
{
int x = 3, y = 5;
int result = sum(x, y);
printf("The result is %d", result);//调用函数语句
return 0;
}
4.在调用函数时不能在实参前面带有实参类型。
max=maxu(int a,int b);//错误
max=maxu(a,b);//正确
函数的返回值
函数调用后得到一个返回值,需要返回值函数用return将计算结果(返回值)返回给调用程序。return语句的一般格式为:
return (<表达式>); 或 return <表达式>;
说明:
1.如果函数无返回值,return可以省略或写为return。
2.一个函数如果有一个以上的return语句,当执行到一条return语句时,函数返回确定的值并退出函数,其他语句不被执行。
3.如果return语句中表达式的值与函数的值类型不一致,则以函数类型为准。
4.为了说明函数没有返回值,可以用void(空类型)表示。
5.如果没有使用return返回一个具体的值,而函数又不是void型,而返回值为一个随机整数。
6.数组作为形参,返回位置从0开始。
函数的参数传递
值传递(单向传递)
值传递指的是将实参的值复制一份,传递给形参。在函数内部修改形参的值不会对实参产生影响。这类函数有对原始数据的保护作用。
#include <stdio.h>
void change(int num)
{
num = 100;
}
int main()
{
int n = 10;
change(n);
printf("%d",n); // 输出为10
return 0;
}
注:
1.实参可以是常量、变量、表达式,但是要求它们必须有确定的值。在调用时将实参赋给形参。
2.实参和形参的类型应相同或赋值兼容。
地址(指针/数组)传递(双向传递)
形式参数定义类型为指针或数组时,函数参数按地址传递。调用时主函数将实参的地址传递给形参,实参和形参共享同一个或一组存储地址。形参通过操作该地址上的值来改变实参的值。
举例如下:
#形参为指针类型
#include <stdio.h>
void change(int *p)
{
*p = 100;
}
int main()
{
int n = 10;
change(&n);
printf("%d",n); // 输出为100
return 0;
}
#形参为数组类型
#include <stdio.h>
void printArray(int s[5], int length)
{
for (int i = 0; i < length; i++)
{
printf("%d ", s[i]);
}
printf(" ");
}
int main()
{
int arr[] = { 1, 2, 3, 4, 5 };
int length = sizeof(arr) / sizeof(arr[0]);
printArray(arr, length);
return 0;
}
注:
1.使用指针传递可以减少内存的开销,因为只需要传递一个地址即可。但是需要注意的是,在使用指针传递时,要确保指针所指向的内存空间已经被分配。
2.通过函数调用语句,被调函数只能向主调函数返回一个值,地址传递方式可以让被调函数向主调函数传递多个值。
数组作为函数参数
数组元素作为函数参数,遵循值传递;数组名称作为函数参数,遵循地址传递。举例如下:
//数组名作为函数参数
#include <stdio.h>
void printArray(int arr[], int size)
{
for (int i = 0; i < size; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
int myArray[5] = {1, 2, 3, 4, 5};
printArray(myArray, 5);
return 0;
}
在上面的代码中,printArray 函数接受一个名为 arr 的整数数组和一个整数 size,它打印出 arr 数组的所有元素。
//数组元素作为函数参数
#include <stdio.h>
void printElement(int element)
{
printf("%d", element);
}
int main()
{
int myArray[5] = {1, 2, 3, 4, 5};
printElement(myArray[2]);
return 0;
}
在上面的代码中,printElement 函数接受一个名为 element 的整数,并打印出该元素的值。在 main 函数中,我们通过传递 myArray 数组的第三个元素(下标为 2)来调用 printElement 函数。使用数组元素作为函数参数时,将传递单个元素的值。这种方式不太常用,因为仅能传递单个元素,而不是整个数组。
函数的嵌套与递归
函数的嵌套调用
函数的嵌套调用即在调用一个函数固定过程中,又调用另一个函数。
#include <stdio.h>
void print_three_numbers(int a, int b, int c)
{
printf("%d %d %d", a, b, c);
}
void print_six_numbers(int a, int b, int c, int d, int e, int f)
{
print_three_numbers(a, b, c);
print_three_numbers(d, e, f);
}
int main()
{
print_six_numbers(1, 2, 3, 4, 5, 6);
return 0;
}
//在此示例中,有两个函数 print_three_numbers 和 print_six_numbers。
//函数 print_six_numbers 调用了函数 print_three_numbers 两次,每次传递三个整数参数。
//最后,在 main 函数中调用了 print_six_numbers,并传递了六个整数参数。
函数的递归调用
函数的递归调用是指在函数体内部直接或简介的自己调用自己,即函数嵌套调用的是函数本身。
#include <stdio.h>
int factorial(int n)
{
if (n == 0 || n == 1)
{
return 1;
}
else
{
return n * factorial(n - 1);
}
}
int main()
{
int n = 5;
int result = factorial(n);
printf("%d! = %d", n, result);
return 0;
}
//在上面的代码中,定义了一个名为 factorial 的函数,它以一个整数参数 n 作为输入,并返回 n 的阶乘。如果 n 等于 0 或 1,则函数直接返回 1。
//否则,函数将 n 乘以 (n-1) 的阶乘,这个过程通过递归实现。
//在 main 函数中,我们定义并初始化了变量 n,然后调用 factorial 函数来计算 n 的阶乘,并将结果存储在 result 变量中。
//最后,我们使用 printf 函数输出结果。
注:在递归函数定义中,必须确定无论什么情况下,都能结束递归。
变量和函数的作用域
全局变量和局部变量
全局变量
全局变量是在函数以外定义的变量(也称外部变量),它们的值在整个程序运行期间都是有效的。全局变量的作用域是整个程序,即从变量被定义的地方开始到程序结束。全局变量会在程序启动时自动被初始化。
#include <stdio.h>
int global_variable = 10;
void func()
{
printf("Global variable: %d", global_variable);
}
int main()
{
func();
return 0;
}
//在这个例子中,global_variable是一个全局变量。它被定义在函数以外,可以被整个程序访问和使用。
//在func()函数中,我们输出了global_variable的值,它将输出10。
说明:
1.全局变量存放在静态存储区,如果没有赋初值,系统默认数值型变量的值为0,字符型初值是空格。
2.定义在文件中间的全局变量只能被其下面的函数所使用,全局变量定义之前的所有函数不会知道该变量。
3.全局变量为函数间数据的传递提供了通道。(可以传递多个数据的值)
4.其他源程序文件中的函数也可以使用全局变量。
局部变量
局部变量是在函数内部定义的变量。它们只能在定义它们的函数内部使用,不能被其他函数或程序代码访问。局部变量的作用域仅限于定义它们的函数,当函数执行完毕后,局部变量的值就会被销毁。
#include <stdio.h>
void func()
{
int local_variable = 20;
printf("Local variable: %d", local_variable);
}
int main()
{
func();
return 0;
}
//在这个例子中,local_variable是一个局部变量。它被定义在函数func()内部,只能在该函数内使用。
//在func()函数中,我们输出了local_variable的值,它将输出20。
说明:
1.局部变量存放在动态存储区,使用前必须赋值。
2.不同函数可以定义同名变量,它们的使用范围只是在各自函数内。
3.在复合语句中定义变量,变量的作用域只是本复合语句。
4.重名局部变量和全局变量作用域规则:如果局部变量和全局变量同名,则程序将使用局部变量,而不是全局变量。这是因为 C 语言的作用域规则指定了在程序中范围最小的变量拥有更高的优先级。实例如下:
#include <stdio.h>
int x = 10; // 全局变量
void test()
{
int x = 5; // 局部变量
printf("x = %d", x); // 输出 5
}
int main()
{
test();
printf("x = %d", x); // 输出 10
return 0;
}
变量的存储类别
从变量值存在的时间来说,变量的存储类别可以分为动态存储方式和静态存储方式。
动态存储方式是指在程序运行过程中通过调用系统函数或者库函数来动态分配内存,然后释放内存的方式。动态存储方式的特点包括:在函数内部定义、生命周期短,只在所在函数执行期间有效、需要手动释放内存、可以在程序运行过程中动态改变内存大小。
静态存储方式中,变量的内存空间在程序编译时就已经分配好了,并且一直存在于整个程序的生命周期中。因此静态变量也被称为全局变量。静态变量的特点包括:在函数外定义、生命周期长,整个程序都可访问、只会被初始化一次、默认值为0,如果没有显示的初始化。
动态存储方式
自动变量
定义:auto 类型名 局部变量名;
说明:
1.函数内定义的局部变量为自动变量。
2.变量存储单元随函数的调用而取得。
3.若变量未被赋值,则其初值不确定。
寄存器变量
定义:register 类型名 局部变量名;
说明:
1.变量存储单元分配在CPU寄存器中。
2.适用于同一变量频繁出现的地方。
3.只能定义有限数目的寄存器变量。
4.局部静态变量不能定义为寄存器变量。
静态存储方式
外部变量
定义:extern 类型名 全局变量名;
说明:
1.变量可被同程序前面的函数引用。
2.变量可被另外一个C源程序文件引用。
3.适用于多人或团队合作编写大程序。
4.函数独立性差,尽量不用外部变量。
静态变量
静态局部变量和静态全局变量两种
静态局部变量是在局部变量前加static,静态全局变量是在全局变量前加static。
定义:static 类型名 全局/局部变量名;
说明:
1.程序执行时静态内部变量始终存在。
2.变量仅仅在程序编译时被赋值一次。
3.变量未初始化时自动赋以0或'\0'.
内部函数和外部函数
函数按存储类别可分为内部函数和外部函数。
内部函数
内部函数也称为静态函数,它们仅在定义它们的源文件中可见,同一个程序的其他文件中不可调用。这意味着,即使在其他源文件中定义相同名称的函数,它们也不会发生冲突。内部函数使用 static 关键字进行声明。格式为:
static <函数类型><函数名>(<参数列表>)
{
<函数体>
}
外部函数
外部函数是在一个源文件中定义的可以被其他源文件调用的函数。外部函数不使用 static 关键字进行声明。格式为:
extern <函数类型><函数名>(<参数列表>)
{
<函数体>
}
//如果定义时没有声明函数的存储类型,系统默认为 extern 型
如果要在一个源文件中使用另一个源文件中定义的函数,需要在使用的源文件中声明该函数的原型,或者包含定义该函数的头文件。
例如,在使用 add 函数的源文件中,需要添加以下代码行:
int add(int a, int b);
内部函数具有文件作用域,只能在定义它们的源文件中访问,而外部函数则可以在多个源文件之间共享并被调用。