目录
1. 有参函数
首先,需要清楚什么是函数呢?简单来说,函数就是按照一定的书写规范,完成相应功能的代码模块。
1.1 有参无返回值
所谓的有参无返回值函数指的是一个函数在执行时需要接收一些参数,但在执行完毕后不返回任何值给调用者,即函数的返回值类型为void。通过实验来验证:
#include <stdio.h>
void B(int a, int b)
{
int c;
c=a+b;
printf("c=%d\n",c);
}
int main()
{
int x=1,y=2,z=0;
B(1,2);
return 0;
}
运行结果:
1.2 有参有返回值
了解了有参无返回值之后,对有参有返回值的定义就很容易理解了,所谓的有参有返回值函数指的是一个函数在执行时需要接收一些参数,并且在执行完毕后返回一个值给调用者。通过实验来验证:
#include <stdio.h>
int add(int a, int b)
{
int c=a+b;
return c;
}
int main()
{
int x=1,y=2,z=0;
z=add(1,2);
printf("z=%d\n",z);
return 0;
}
运行结果:
注意:对于有参函数,接收返回值的z要与函数的返回值类型保持一致。
1.3 小结
函数由返回值类型、函数名称、参数列表、函数体、返回值这几部分组成。
函数通过明确的返回值类型规定其输出数据的类型,通过唯一的函数名称供调用者引用,利用参数列表接收调用者提供的数据,并在函数体内执行特定的任务或计算。执行完毕后,函数通过返回值将数据传递回调用者,从而实现函数与调用者之间的数据交换和逻辑处理。
2. 无参函数
2.1 无参无返回值
所谓的无参无返回值函数在定义时既不接受任何参数(即没有参数列表),也不向调用者返回任何值(即没有返回值)。通过实验来验证:
#include <stdio.h>
void A()
{
printf("***********\n");
}
int main()
{
A();
}
运行结果:
2.2 无参有返回值
无参有返回值函数指的是在定义时不接受任何参数(即没有参数列表),但会向调用者返回一个值。通过实验来验证:
#include <stdio.h>
double C()
{
double pi=3.14;
return pi;
}
int main()
{
double ppi=0;
ppi=C();
printf("ppi=%lf\n",ppi);
return 0;
}
运行结果:
3. 参数的传递
3.1 按值传递
所谓的按值传参指的是形参的改变不影响实参,实参并不会发生变化,作用是能够有效的保护实参。通过实验来验证:
#include <stdio.h>
void swp (int a, int b)//定义的a和b是形参
{
int c = 0;
printf ("交换前a=%d\n", a);
printf ("交换前b=%d\n", b);
c = a;
a = b;
b = c;
printf ("交换后a=%d\n", a);
printf ("交换后b=%d\n", b);
}
int main ()
{
int x = 100, y = 200;
printf ("交换前x=%d\n", x);
printf ("交换前y=%d\n", y);
swp(x, y);//传递的x和y是实参
printf ("交换后x=%d\n", x);
printf ("交换后y=%d\n", y);
return 0;
}
运行结果:
注意:实参将值传递给形参,此时形参的a、b有了值,然后在函数swp中执行,但是并不改变函数main中实参x、y的值。
3.2 按地址传递
相对于按值传递,使用按地址传递时实参的值会随着形参的改变而改变。通过实验来验证:
#include <stdio.h>
void swp (int *a, int *b)
{
int c = 0;
printf ("交换前*a=%d\n", *a);
printf ("交换前*b=%d\n", *b);
c = *a;
*a = *b;
*b = c;
printf ("交换后*a=%d\n", *a);
printf ("交换后*b=%d\n", *b);
}
int main ()
{
int x = 100, y = 200;
printf ("交换前x=%d\n", x);
printf ("交换前y=%d\n", y);
swp(&x, &y);
printf ("交换后x=%d\n", x);
printf ("交换后y=%d\n", y);
return 0;
}
运行结果:
注意:实参在向形参传值时传递的是实参的的地址,一个地址只对应一个值,所以在函数swp中执行的语句实际上改变的是实参唯一地址对应的唯一值,因此实参的值也会变化。
4. 数组参数的传递
数组的传参相当于按地址传参,因为数组名有和指针相似的性质
4.1 一维数组参数的传递
通过实验来验证:
#include <stdio.h>
//void arr2 (int *a, int n)
//两种形参的的定义方法都是对的,
//因为数组的首地址相当于一级指针,其中n表示数组的长度
void arr1 (int a[], int n)
{
int i = 0;
for (i = 0; i < n; i++) {
printf ("a[%d]=%d\n", i, a[i]);
}
}
int main ()
{
int a[5] = {1,2,3,4,5};
arr1(a, sizeof (a)/sizeof (a[0]));
return 0;
}
运行结果:
4.2 二维数组参数的传递
通过实验来验证:
#include <stdio.h>
void arr (int a[][4], int n, int m)
{
int i, j;
for (i = 0; i < n; i++){
for (j = 0; j < m; j++){
printf ("a[%d][%d]=%d\n", i, j, a[i][j]);
}
}
}
int main ()
{
int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
arr(a,3,4);
return 0;
}
注意:需要注意的是二维数组形参的定义必须明确列数,而且不能用int **a的方式定义形参的二维数组,m是行n是列
运行结果:
5. 函数的使用(调用)
5.1 一般的调用
函数在调用前应先进行声明,如下图所示:
源代码:
#include <stdio.h>
void A()
{
printf("A\n");
}
void B()
{
printf("B\n");
}
void C()
{
printf("C\n");
}
int main()
{
A();
B();
C();
return 0;
}
运行结果:
5.1.1 函数的声明
5.1中说了函数的一般调用,但这种调用方法有个弊端,就是被调用的函数必须在前面已经被声明过的,否则就无法正常使用,如下图所示:
针对上面的问题,我们可以在函数调用之前先进行声明,因此可以使用如下方法进行优化:
源代码:
#include <stdio.h>
//声明
void A();
void B();
void C();
void A()
{
B();
printf("A\n");
}
void B()
{
printf("B\n");
}
void C()
{
printf("C\n");
}
int main()
{
A();
B();
C();
return 0;
}
运行结果:
但是,又出现了一个新问题,就是函数的声明要是太多了怎么办,如果按照这种方式声明,函数的可读性就会变低,所以就有了函数的跨文件夹使用,详情见下一节。
5.2 跨文件夹调用
5.2.1 常见调用
针对上一节遗留的问题,如果声明的函数过多时,就可以将定义的函数放入一个.h结尾的文件夹里,之后在进行调用,如下图所示:
源代码:
//t.c
#include <stdio.h>
#include "t.h"
void A()
{
B();
printf("A\n");
}
void B()
{
printf("B\n");
}
void C()
{
printf("C\n");
}
int main()
{
A();
B();
C();
return 0;
}
//t.h
void A();
void B();
void C();
运行结果:
5.2.2 重复调用
但是,上节的那种调用方式还存在一个新的问题,就是当有一个.h函数被调用两次以上时,系统会报错,但是重复调用又是难免的,所以解决这个问题的方法如下图:
上图中展示了在x.c和z.c中重复调用z.h后,程序能够正常运行。可以看到在.h文件中使用了“头文件卫士”,避免了重复调用。
源代码:
//x.c
#include <stdio.h>
#include "y.h"
#include "z.h"
int main()
{
A();
B();
printf_stu();
return 0;
}
//y.c
#include <stdio.h>
void A()
{
printf("A\n");
}
void B()
{
printf("B\n");
}
//z.c
#include <stdio.h>
#include "z.h"
void printf_stu()
{
struct student s={111, "王四", 100};
printf("s.id=%d\n",s.id);
printf("s.name=%s\n",s.name);
printf("s.math=%d\n",s.math);
}
//y.h
#ifndef Y_H
#define Y_H
struct student
{
int id;
char name[32];
int math;
};
void A();
void B();
#endif
//z.h
#ifndef Z_H
#define Z_H
#include "y.h"
//导头文件,就是把函数复制过来
void printf_stu();
#endif
运行结果: