函数是一个程序的部分代码,用来实现某些特定的功能,与主main
函数分离,使程序结构模块化,代码更加清晰。
思想是高内聚低耦合。
C语言中包含了许多种类的库函数,把一些实现特定功能的代码(如:输入输出、字符串比较、数学中的一些函数实现、申请内存等)封装成一个个函数,方便我们使用。在使用某个函数时只需要知道它在哪个库函数中,然后在自己程序的开始添加相应的库函数即可。
.h
结尾的文件是头文件。
输入输出库函数 | stdio.h |
---|---|
字符处理库函数 | ctype.h |
字符串处理库函数 | string.h |
数学库函数 | math.h |
内存分配库函数 | stdlib.h |
时间处理库函数 | time.h |
布尔库函数 | stdbool.h |
其他库函数 |
除了C语言提供的基本的库函数,我们还可以自己实现一个个函数,即自定义函数,以此来满足更具体的、专一的需求,同时也可以了解函数实现的原理,增长自身的能力。
对函数的返回值类型、函数名、具有的参数、实现的功能进行定义。定义之后,便具有了一个可以实现一定功能的函数。 格式:
函数返回值类型 函数名(变量数据类型 变量名1,...... ,变量数据类型 变量名2){
函数功能实部分
}
一个具体的函数定义:
//实现两个整数的相加并以整型返回结果
int Add_sum(int a, int b){
int ret = a + b;
return ret;
}
函数一般都会有一个返回值,void除外。 void为返回类型意为函数没有返回值,可以在程序的末尾写上
return;
,或者不写return;
,对这个函数无影响。void*
为返回值意为,函数返回一个不指向任何类型的为"空"的指针。
一些返回值类型举例 | |
---|---|
char | 字符型 |
int | 整型 |
float | 单精度浮点型 |
double | 双精度浮点型 |
char* | 字符指针 |
int* | 整型指针 |
float* | 浮点型指针 |
double* | 浮点型指针 |
函数名的命名与变量的命名相同,由大小写字母,数字和下划线组成,且开头不能是数字。
函数的定义可以放在程序的开头,但函数的定义一般会跨越多行,当有多个函数被定义时main函数前面将会变得繁杂,不利于我们写程序。函数一般满足先声明后使用。
#include <stdio.h>
//函数定义 - 两个整数相加
int Add_sum(int a, int b){
return a + b;
}
int main(){
int a = 3;
int b = 4;
int sum = Add_sum(a,b);
printf("%d\n",sum);
return 0;
}
运行结果:
#include <stdio.h>
//函数声明
int Add_sum(int a, int b);
int main(){
int a = 3;
int b = 40;
int sum = Add_sum(a,b);
printf("%d\n",sum);
return 0;
}
//函数定义 - 两个整数相加
int Add_sum(int a, int b){
return a + b;
}
运行结果:
另一种写法是:
把所有的函数定义都写在一个.c
文件中,把所有的函数声明都写在一个.h
文件中。
程序更加模块化 程序易于与他人协作
传递给函数的具有确定的值的参数称为实参。 实参可以是常量、变量、函数、表达式等。
函数名后括号内定义的各种变量。
函数声明时函数返回类型、函数名、函数的形参的数据类型是必需的,而形参中的变量名是可有可无的。也就是说函数声明关心的是函数返回类型、函数名、函数的形参的数据类型,不关心形参的变量名是什么,可以省略,但一般与函数头保持一致。
一个失败的交换两个整数的值的代码
#include <stdio.h>
//交换变量x与y的值
void swap(int x, int y);
int main(){
int a = 0;
int b = 0;
scanf("%d%d",&a, &b);
swap(a, b);
printf("a = %d b = %d\n", a, b);
return 0;
}
void swap(int x, int y){
int t = x;
x = y;
y = t;
}
实际运行结果:a与b并没有交换!
若想通过形参改变实参的值,需要得到实参的地址,所以需要使用类型为指针的形参来接收实参的地址,通过间接访问操作符
*
通过地址改变实参的值。
对交换两个数代码的改进:
#include <stdio.h>
//交换变量x与y的值
void swap(int *px, int *py);
int main(){
int a = 0;
int b = 0;
scanf("%d%d",&a, &b);
swap(a, b);
printf("a = %d b = %d\n", a, b);
return 0;
}
//函数swap的形参为两个整型指针
void swap(int *px, int *py){
int t = *px;
*px = *py;
*py = t;
}
运行结果:a与b完成了交换
实参的值传递给非指针的形参,由于实参与形参具有不同的储存空间,形参也不知道实参的地址,所以形参无法通过实参的地址影响实参的值。 形参与实参相互隔绝,没有任何关系。
传址调用实际上也是传值调用,只不过有些特殊,传递的是实参的地址的值。 实参的地址传递给指针类型的形参,实参与形参也具有不用的储存空间,但是形参中存放的是实参的地址,所以可以通过储存的实参的地址来影响实参的值。 形参可以通过实参的地址访问实参,形参与实参便联系了起来。
对有序数组的元素进行排序并输出
#include <stdio.h>
//函数声明
int Binary_search(int arr[], int sz, int input);
int main(){
int arr[] = {1,3,5,7,9,10,13,15,17,19};
int sz = sizeof(arr) / sizeof(arr[0]);
int input = 0;
scanf("%d", &input);
//函数调用
int index = Binary_search(arr, sz, input);
if(index != -1){
printf("找到了,下标为:%d\n",index);
}
else{
printf("没找到\n");
}
return 0;
}
//函数定义
//二分查找(折半查找),找到了返回数组下标,找不到返回-1
int Binary_search(int arr[], int sz, int input){
//数组左下标
int left = 0;
//数组右下标
int right = sz - 1;
//数组中间下标
int middle = 0;
while(left <= right){
middle = left + (right - left)/2;
//待查找input小于数组中间元素,则input只可能在数组左半边
if(input < arr[middle]){
right = middle - 1;
}
//待查找input大于数组中间元素,则input只可能在数组右半边
else if(input > arr[middle]){
left = middle + 1;
}
//待查找input等于于数组中间元素
else{
return middle;
}
}
//左下标大于右下标还没有找到则一定找不到了
return -1;
}
一些运行结果:
函数可以嵌套调用
#include <stdio.h>
void two_num_swap(int* px, int* py);
void arr_print(int arr[], int sz);
void compare_sort(int arr[], int sz);
int main() {
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
//求数组的长度
int sz = sizeof(arr) / sizeof(arr[0]);
printf("排序前:\n");
arr_print(arr, sz);
comment_sort(arr, sz);
printf("排序后:\n");
arr_print(arr, sz);
return 0;
}
//
void compare_sort(int arr[], int sz) {
int i = 0;
for (i = 0; i < sz - 1; i++) {
int j = 0;
for (j = i + 1; j < sz; j++) {
if (arr[i] > arr[j]) {
//嵌套调用
//main函数调用compare_sort函数,compare_sort函数调用two_num_swap函数
two_num_swap(&arr[i], &arr[j]);
}
}
}
return;
}
//
void two_num_swap(int* px, int* py) {
int t = *px;
*px = *py;
*py = t;
}
//
void arr_print(int arr[], int sz) {
int i = 0;
for (i = 0; i < sz; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
运行结果:
一个函数的返回值作为这个函数或另一个函数的参数。 一个例子:
#include <stdio.h>
int main(){
printf("%d",printf("%d",printf("%d", 1514)));
return 0;
}
运行结果:
最内层的printf打印1514,返回值为4,。 第二层的printf打印4,返回值为1。 最外层的printf打印1,返回值为1。
scanf
的返回值为接受的成功输入个数。
printf
返回值是其打印字符的个数,包括空白符(换行符、空格符、水平制表符、回车符)。
把复杂的问题按照一定的方法一直分解,每次都把问题复杂度降低,最终分解成简单的问题。 函数自己调用自己,满足条件时停止调用。 只需要少量的代码,就可以实现复杂问题的求解。
#include <stdio.h>
int My_strlen(char *pstr);
int main() {
//字符串,末尾为'\0'
char str[] = "Hello world!";
int ret = My_strlen(str);
printf("%d\n", ret);
return 0;
}
int My_strlen(char *pstr) {
//指针pstr指向的字符不是'\0',即字符串没到末尾,字符数+1
if (*pstr != '\0') {
return 1 + My_strlen(pstr + 1);
}
//指针pstr指向的字符是'\0',即到达了字符串的末尾,字符数不变
else {
return 0;
}
}
运行结果:
循环是迭代的一种,但迭代不一定是循环 一些问题既可以用递归实现,也可以用循环实现。 相同的问题,递归实现往往比循环实现会占用更多的时间和更多的内存,如求一个正整数的阶乘,斐波那契数列。 相同的问题,递归实现一般比循环代码简洁。 而一些问题只能用递归实现,比如汉诺塔问题。 每次函数调用都会在内存的栈上占用内存,但栈的内存是有限的,递归多次调用自身过多时可能会使栈的内存溢出,即栈溢出。
#include <stdio.h>
int main(){
unsigned int n = 0;
scanf("%u", &n);
int ret = Factorial(n);
printf("%d\n", ret);
return 0;
}
//递归的实现
int Factorial(unsigned int n){
if(n > 1){
return n * Factorial(n - 1);
}
else{
return 1;
}
}
一个运行结果:
循环实现:
#include <stdio.h>
int main() {
int n = 0;
int ret = 1;
int i = 0;
scanf("%d", &n);
for (i = 1; i <= n; i++) {
ret = ret * i;
}
printf("%d\n", ret);
return 0;
}
运行结果:
在不考虑数据超出in范围的情况下,求正整数n的阶乘n!递归运行速度慢于循环。
#include <stdio.h>
int Fibonacci(int n);
int main(){
int n = 0;
scanf("%d", &n);
int ret = Fibonacci(n);
printf("%d\n", ret);
return 0;
}
//递归实现
int Fibonacci(int n){
if(n > 2){
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
else{
return 1;
}
}
运行结果:
#include <stdio.h>
int Fibonacci(int n);
int main(){
int n = 0;
scanf("%d", &n);
int ret = Fibonacci(n);
printf("%d\n", ret);
return 0;
}
//循环实现
int Fibonacci(int n){
int a = 1;
int b = 1;
int c = 1;
while(n >= 3){
c = a + b;
a = b;
b = c;
n--;
}
return c;
}
在不考虑数据超出in范围的情况下,求第n个斐波那契数列递归运行速度慢于循环。
END