目录
函数的概述
1 函数的概念
所谓的函数,指的是具有特定功能的有序指令语句的集合,可以提供使用者调用以完成功能语句的执行。此时的功能语句块由设计者实现模块化编程设计,使用者通过函数的调用实现代码重用的效果。
函数的实质:是实现代码的模块化编程和代码重用的设计思想。
2 函数的意义
1) 函数的定义可以实现功能语句的模块化编程设计和代码重用;
2) 对于共用性的功能型语句通过函数的调用,简化程序设计的过程;
3) 简化项目的设计的框架或者逻辑;
4) 通过函数的模块化设计,以实现项目的分工协作。
函数的定义和使用
1 函数的定义
存储类型 数据类型 函数名(形参列表)/* 称为函数头,花括号包含的部分称为函数体 */
{
函数体中的功能性有序指令集合;
return语句 ;
}
注意:
1. 存储类型:修饰整个函数的属性,只能使用关键字static
1) 没有使用static关键字修饰,此时函数的作用域(或者链接属性)为整个程序域,也就是可以在当前程序中的任意位置访问;
2) 有使用static关键字修饰,此时函数的作用域为文件域,也就是只能在当前定义函数的文件中访问;
2. 数据类型:修饰函数返回结果数据值的数据类型
1) 如果函数不需要返回数据值,此时使用void数据类型表示;此时的return语句可以省略,也可以不省略但是只能使用(return ;)表示,不能跟返回结果;
2) 如果函数需要返回数据值,使用返回的数据值的数据类型表示
i. 返回实际数据值;
ii. 返回值函数调用的状态;
iii 返回值同时是数据和状态;
3. 函数名:提供给使用者访问有序执行指令集合的接口名称。
4. 形参列表:表示函数数据的输入和输出的参数
1) 无参数,函数功能语句的执行不需要数据的输入和输出,此时使用void表示。
2) 有参数:
可以有一个或者多个形参构成
每一个形参的组成:数据类型 形参变量名,需要注意形参变量为局部变量,在函数模块内有效;
如果是多个形参,形参之间使用逗号分隔;
5. 函数体的部分:使用花括号{}所包含的有序指令语句的集合。
/* 定义无参无返回值的函数 */
void test1(void)
{
return; /* 此处的return语句可以省略;同时如果不省略的时候,不能接返回数据值 */
}
/* 定义有参有返回值的函数 */
int add(int a, int b) /* 形参变量作用域为函数域 */
{
return a+b;
}
2 函数的调用
所谓函数的调用,指的是使用者根据函数名访问函数,在调用的同时会根据函数形参列表传递实际参数的过程。
2.1 在同一文件中函数定义后函数调用
函数定义和函数调用在同一个文件中,并且函数调用在函数定义之后,此时可以直接函数调用。
#include <stdio.h>
/* 函数的定义,在定义的同时也完成函数的声明过程 */
int add(int a, int b)
{
return a+b;
}
int main()
{
int x = 3;
int y = 4;
int z;
z = add(x, y); /* 在同一个文件中,函数定义后实现函数的调用 */
printf("z = %d\n", z);
}
2.2 在同一文件中函数定义前函数调用
函数定义和函数调用在同一个文件中,函数的调用在函数定义之前,需要对函数进行声明之后在调用。
函数的声明:
1. 函数声明的目的
是告诉编译器,函数定义在其它地方已经完成,在当前文件中可以直接调用;
2. 函数声明的语法:
extern 数据类型 函数名(形参列表);
1) extern 是对函数进行外部声明引用,可以省略;
2) 形参列表中的形参变量名可以省略;
int sub(int a, int b); /* 函数声明,可以等价于以下几种情况 */
#if 0
extern int sub(int a, int b);
extern int sub(int, int);
int sub(int, int);
#endif
int main()
{
int x = 3;
int y = 4;
int z;
z = sub(x, y); /* 函数的调用 */
printf("z = %d\n", z);
}
/* 函数定义 */
int sub(int a, int b)
{
return a-b;
}
2.3 调用其它文件中定义的函数
函数调用之前,需要对其它文件中所定义的函数进行函数声明
2.3.1 在函数调用文件中进行声明
根据所需要调用的函数,选择函数进行声明
#include <stdio.h>
/* 定义无参无返回值函数 */
void test1(void)
{
printf("test1\n");
}
int add(int a, int b)
{
return a+b;
}
int sub(int a, int b)
{
return a-b;
}
2. 函数调用的源文件:app.c
#include <stdio.h>
/* 函数声明:对需要调用的函数在函数调用之前进行函数声明 */
void test1(void);
extern int add(int a, int b);
int main()
{
test1();
printf("%d\n", add(5,2));
}
注意:程序的编译需要对函数定义和函数调用的源文件进行编译
gcc app.c func.c/* 编译成功默认生成的可执行程序文件为a.out */
2.3.2 在头文件中进行函数的声明
所定义的函数很多,且函数调用频率很高,设计者使用头文件进行函数的声明;对于使用者只需要包含头文件后直接进行函数的调用。
1. 函数定义的源文件:func.c
#include <stdio.h>
void test1(void)
{
printf("test1\n");
}
int add(int a, int b)
{
return a+b;
}
int sub(int a, int b)
{
return a-b;
}
2. 函数声明的头文件:func.h
#ifndef _FUNC_H_
#define _FUNC_H_
/* 函数声明 */
void test1(void);
extern int add(int a, int b);
extern int sub(int a, int b);
#endif
头文件中注意:
1) 防止头文件被重复展开标识符
#ifndef _FUNC_H_
#define _FUNC_H_
/* 数据类型的定义,或者函数和变量的外部声明应用*/
#endif
2) 在头文件中不能做数据类型变量的定义或者函数的定义,否则可能出现重复定义。
3. 函数调用的源文件:app.c
#include <stdio.h> /* 头文件的包含:尖括号<>所包含的头文件是在系统环境变量路径中所能找到的头文件 */
#include "func.h" /* 头文件的包含:双引号""所包含的头文件,在系统环境变量路径下不能找到,一般为自定义的头文件 */
int main()
{
test1();
printf("%d\n", add(5,2));
}
注意:程序的编译需要对函数定义和函数调用的源文件进行编译,不需要对函数声明的头文件进行编译
gcc app.c func.c/* 编译成功默认生成的可执行程序文件为a.out */
函数的参数
1 函数传参
在函数定义的时候,可以给所定义的函数设置形式参数,在函数调用的时候,需要将相同数据类型的实际参数传递给形式参数,需要完成函数传参的问题。在C语言中函数的传参注意包含三种形式,分别为值传参、地址传参和全局变量传参。
1.1 值传参
所谓的值传参,指的是形参为数据类型变量,在函数调用的时候,将相同数据类型实参值传递给形参的过程。其实质将实参值赋值给形参变量值。
特定:
1) 值传参只能实现数据值的输入,不能实现数据值的输出;
2) 对于形参变量值的修改,不会影响实参数据值。
3) 对于函数的返回值,也是采用值传参实现。
4) 形参变量存储空间和实参变量存储空间之间无任何关联。
#include <stdio.h>
void setVal(int val)
{
printf("val = %d\n", val);
val = 100; /* 对形参变量val值的修改不会影响实参变量a的数据值 */
printf("val -> %d\n", val);
}
int main()
{
int a = 1;
setVal(a); /* 函数的调用,将实参变量a的值赋值给形参变量val, 此时val和a存储空间无任务管理 */
printf("a = %d\n", a);
}
1.2 地址传参
所谓地址传参,指的是在形参为指针变量的时候,将实参数据的地址赋值给形参变量;此时形参指针变量便指向的空间为实参数据空间。
特定:
1) 地址传参,可以实现数据的输入和输出。
2) 通过形参地址的解引用访问的是实参,通过形参修改实参数据值;
3) 形参为指针变量所指向空间为实参空间。
#include <stdio.h>
void Swap(int *p, int *q)
{
int temp;
temp = *p;
*p = *q;
*q = temp;
}
int main()
{
int x = 3;
int y = 5;
printf("%d: x = %d, y = %d\n", __LINE__, x, y); /* x = 3, y = 5 */
Swap(&x, &y);
printf("%d: x = %d, y = %d\n", __LINE__, x, y); /* x = 5, y = 3 */
}
注意:
地址传参的使用,在需要在函数内部去修改实参数据值的时候使用。
如果需要修改的是数据变量,则使用数据变量相同数据类型的一级指针作为形参;
如果需要修改数据是指针变量,则使用指针变量数据类型的指针作为形参。
1.3 全局变量传参
所谓的全局变量传参,不是真正意义上利用参数实现数据的传递,实质是利用全局变量的属性实现数据值的传递过程。由于全局变量的作用域为整个程序域,所以可以在不同的函数模块内访问到全局变量的数据值。从而实现不同模块之间数据传递的过程。
特定:
1) 全局变量传参,实现简单。
2) 如果在函数模块内访问全局变量,则此时的模块依赖于全局变量的存在,模块的兼容性和可移植性会更低。
3) 在一般情况下,不建议使用。
#include <stdio.h>
int m = 4;
int n = 5;
void SWAP()
{
int temp;
temp = m;
m = n;
n = temp;
}
int main()
{
printf("%d: m = %d, n = %d\n", __LINE__, m, n);
SWAP();
printf("%d: m = %d, n = %d\n", __LINE__, m, n);
}
2 数组作为函数参数
数组的访问不能整体访问,只能通过数组名和元素序号逐一元素访问;
数组名[下标];下标范围:[0, 数组元素个数)
对于数组参数的传递,也不能整体传参;
#include <stdio.h>
extern void showArr(int a[10]);
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
showArr(arr);
}
void showArr(int a[10])
{
int i;
for (i = 0; i < sizeof(a)/sizeof(a[0]); i++)
printf("a[%d] = %d\n", i, a[i]);
}
注意:
程序的执行结果,不能将整个