1.函数是什么?
数学中我们常见到函数的概念。但是你了解C语言中的函数吗?
维基百科中对函数的定义:子程序
1.在计算机科学中,子程序,是一个大型程序中的某部分代码, 由一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代 码,具备相对的独立性。
2.一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库。
2.C语言中函数的分类
在C语言中函数主要分为两种,一个是自带的库函数,还有就是我们自定义的函数。
2.1.库函数
像我们熟悉的printf()函数,scanf()函数就是C语言自带的函数,我们在使用它的时候只需要#include包含对应的头文件即可,使用printf()、scanf()函数包含stdio.h即可。
如何学习查找需要的库函数?
这里给大家分享个网站,供大家学习。
链接: www.cplusplus.com
我们在search栏里搜索memset的库函数,我们来看看结果:
上面有函数的各个参数以及返回值等的介绍,如果英文看着难受也可以翻译成中文来学习,我们来看最下面,最下面非常直观的给出了一个例子,包括头文件的引用,以及输出结果。
2.2.自定义函数
我们要知道并不是所有的事库函数都能干。很多时候都要自己设计一个函数来完成要求,这便是自定义函数。
自定义函数和库函数一样,有函数名,返回值类型和函数参数。但是不一样的是这些都是我们自己来设计。这给程序员一个很大的发挥空间。
函数的组成:
ret_type fun_name(para1, * ) { statement;//语句项 } ret_type 返回类型 fun_name 函数名 para1 函数参数
我们举一个例子:
写一个函数可以找出两个整数中的最大值。
#include <stdio.h> //get_max函数的设计 int get_max(int x, int y) { return (x>y)?(x):(y); } int main() { int num1 = 10; int num2 = 20; int max = get_max(num1, num2); printf("max = %d\n", max); return 0; }
再举个例子:
写一个函数可以交换两个整形变量的内容。
#include <stdio.h> //实现成函数,但是不能完成任务 void Swap1(int x, int y) //只改变了形参的值,实参并未发生变化。 { int tmp = 0; tmp = x; x = y; y = tmp; } //正确的版本 void Swap2(int *px, int *py) //通过变量地址解引用来找到实参,并将其改变。 { int tmp = 0; tmp = *px; *px = *py; *py = tmp; } int main() { int num1 = 1; int num2 = 2; Swap1(num1, num2); printf("Swap1::num1 = %d num2 = %d\n", num1, num2); Swap2(&num1, &num2); printf("Swap2::num1 = %d num2 = %d\n", num1, num2); return 0; }
3.函数的参数
3.1.实际参数(实参):
真实传给函数的参数,叫实参。
实参可以是:常量、变量、表达式、函数等。
无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形
参。
3.2.形式参数(形参):
形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内
存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。
在上面代码中:Swap1 和 Swap2 函数中的参数 x,y,px,py 都是形式参数。在main函数中传给 Swap1 的 num1 ,num2 和传给 Swap2 函数的 &num1 , &num2 是实际参数。
这里我们对函数的实参和形参进行分析:
这里可以看到 Swap1 函数在调用的时候, x , y 拥有自己的空间,同时拥有了和实参一模一样的内容。所以我们可以简单的认为:形参实例化之后其实相当于实参的一份临时拷贝。
形参px、py存放的是num1、num2的地址,我们可以通过解引用来找到num1、num2并进行操作。
4.函数的调用
4.1.传值调用
函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。
4.2.传址调用
传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。
这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操
作函数外部的变量。
4.3. 练习
1.写一个函数可以判断一个数是不是素数。
void f(int m) { int i = 0; int p = 0; for (i = 2; i < m; i++) { if (m % i == 0) { printf("不是素数\n"); p++; break; } } if (p == 0) printf("是素数\n"); }
2.写一个函数判断一年是不是闰年。
void jud(int x) { if ((x % 100 != 0 && x % 4 == 0) || (x % 400 == 0)) printf("是闰年\n"); else printf("不是闰年\n"); }
3.写一个函数,实现一个整形有序数组的二分查找。
(要求:找到了就打印数字所在的下标,找不到则输出:找不到。)
#include<stdio.h> void f(int arr[],int b) { int a = 0; int c = 0; int m = 0; scanf("%d", &c); //输入要查找的数 while (a <= b) { m = (a + b) / 2; if (c > arr[m]) { a = m + 1; } else if (c < arr[m]) { b = m - 1; } //为什么要m+-1呢? else //如果要查找的数不在数组内,那a一直+1或b一直-1,直到a>b退出循环 { printf("找到了,下标为:%d\n", m); break; } } if (a > b) printf("找不到\n"); } int main() { int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; int b = sizeof(arr) / sizeof(arr[0]) - 1; f(arr,b); return 0; }
4.写一个函数,每调用一次这个函数,就会将 num 的值增加1。
void f(int* m) { (*m)++; } int main() { int num = 0; //调用函数,使得num每次增加1 f(&num); f(&num); printf("%d", num); return 0; }
5. 函数的嵌套调用和链式访问
函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的。
5.1. 嵌套调用
#include <stdio.h> void new_line() { printf("hehe\n"); } void three_line() { int i = 0; for(i=0; i<3; i++) { new_line(); //该函数被调用三次 } } int main() { three_line(); return 0; }
函数可以嵌套调用,但是不能嵌套定义(函数里面不能定义函数)。
5.2. 链式访问
把一个函数的返回值作为另外一个函数的参数。
#include <stdio.h> #include <string.h> int main() { char arr[20] = "hello"; int ret = strlen(strcat(arr,"bit"));//strlen函数是将后面的字符追加到前面的字符串后面,返回值是前面字符串的起始地址 printf("%d\n", ret); //strlen函数是计算字符串的长度并返回长度大小 return 0; }
输出:8
#include <stdio.h> int main() { printf("%d", printf("%d", printf("%d", 43))); //结果是啥? //注:printf函数的返回值是打印在屏幕上字符的个数 return 0; }
输出:4321
为什么是4321呢,我们来看最右边printf,它打印43并返回2,然后中间的printf,打印2并返回1,然后左边的printf,打印1。三个打印就是4321了。
6. 函数的声明和定义
6.1 函数声明:
- 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数
- 声明决定不了。
- 函数的声明一般出现在函数的使用之前。要满足先声明后使用。
- 函数的声明一般要放在头文件中的
6.2 函数定义:
函数的定义是指函数的具体实现,交待函数的功能实现。
test.h的内容
放置函数的声明
#ifndef __TEST_H__ #define __TEST_H__ //函数的声明 int Add(int x, int y);
test.c的内容
放置函数的实现
#include "test.h" //包含头文件 //函数Add的实现 int Add(int x, int y) { return x+y; }
在三子棋和扫雷时已经使用过这种书写形式。