本篇博客主要介绍C语言中函数的用法。
函数是从英文function翻译过来的,其实,function在英文中的意思既是函数也是功能。从本质意义上来说,函数就是用来完成一定的功能的。这样,对函数的概念就好理解了,所谓函数名就是给该功能起一个名字,如果该功能是用来实现数学运算的,就是数学函数。 注意:函数就是功能。每一个函数用来实现一个特定的功能。函数的名字应反映其代表的功能。 维基百科中对函数的定义:子程序。 在计算机科学中,子程序,是一个大型程序中的某部分代码,由一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代码,具备相对独立性。 一般会有输入参数和返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库。
库函数和自定义函数。
为什么会有库函数? 我们知道在我们学习C语言的时候,总是在一个代码编写完成之后迫不及待的想知道结果,想把这个结果打印到我们的屏幕上看看。
这个时候我们会频繁的使用一个功能:将信息按照一定的格式打印到屏幕上(printf)。在编程过程中我们会频繁的做一些字符串的拷贝工作(strcpy)。在编程时我们也经常需要计算,总是会计算n的k次方这样的运算(pow)。像上面我们描述的基础功能,它们并不是业务性代码。我们在开发的过程中每个程序猿都可能用的到,为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序猿进行软件开发。 如何学习库函数呢? 官方网站:www.cplusplus.com C语言常用的库函数:
IO函数。字符串操作函数。字符操作函数。内存操作函数。时间/日期函数。数学函数。其他库函数。来简单看两个库函数: strcpy:
char * strcpy ( char * destination, const char * source );printf:
int printf ( const char * format, ... );memset:
void * memset ( void * ptr, int value, size_t num );注意:使用库函数,必须包含#include对应的头文件。 如何学会使用库函数? 不需要全部记住,学会在官网上查就好。使用中记,不要刻意去记。 英文很重要,起码看得懂外文文献。
为什么需要自定义函数?
如果库函数能完成所有的事情,就不需要程序猿了。所以更加重要的是自定义函数。自定义函数和库函数一样,有函数名,返回值类型和函数参数。但是不一样的是这些都是我们自己来设计。这给程序猿很大的发挥空间。函数的组成:
ret_type func_name(pare1, *){ statement; } ret_type:返回值类型。 func_name:函数名。 para1:函数参数。求最大值:
#include <stdio.h> int maxNumber(int a, int b){ if(a > b){ return a; } return b; } int main(){ int a, b; printf("Please input a and b: \n"); scanf("%d %d", &a, &b); printf("The max number of %d and %d is: %d\n", a, b, maxNumber(a, b)); return 0; }交换两个数的值:
#include <stdio.h> void swap1(int a, int b){ int temp = a; a = b; b = temp; } void swap2(int* a, int* b){ int temp = *a; *a = *b; *b = temp; } int main(){ int num1 = 10; int num2 = 20; printf("The origin: num1 = %d, num2 = %d\n", num1, num2); swap1(num1, num2); printf("After swap1: num1 = %d, num2 = %d\n", num1, num2); swap2(&num1, &num2); printf("After swap2: num1 = %d, num2 = %d\n", num1, num2); return 0; }可以看到,swap1函数并未完成两个整数的内容交换,这是为什么呢? 要解释为什么,我们先来了解一下函数的参数。
上面swap1和swap2函数中的参数a,b都是形式参数。在main函数中传给swap1的num1,num2和传给swap2函数的&num1,&num2是实际参数。 这里可以看到swap1函数在调用的时候,a,b拥有自己的空间,同时拥有了和实参一模一样的内容。所以我们可以简单的认为:形参实例化之后其实相当于实参的一份临时拷贝。 前面的问题:为什么swap1不能交换num1和num2的值,因为在swap1中其实是对a和b的值进行了交换,这个a和b是函数中新创建的(与num1,num2在不同的空间,拥有自己独立的空间),交换a和b的值并不会影响到num1和num2的值。
传值调用: 函数的实参和形参分别占有不同的内存块,对形参的修改不会影响到实参。 传址调用:
传址调用是把函数外部创建变量的地址传递给函数参数的一种调用函数的方式。这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。函数和函数之间可以进行有机的组合。 嵌套调用: 在一个函数中调用另一个函数。
#include <stdio.h> #include <stdlib.h> #include <time.h> int randomNumber(){ return 11 + rand() % (99 - 11 + 1); } void arrInit(int arr[], int len){ int i = 0; for(i = 0; i < len; ++i){ arr[i] = randomNumber(); } } void arrDisplay(int arr[], int len){ int i = 0; for(i = 0; i < len; ++i){ printf("%d ", arr[i]); } printf("\n"); } int main(){ srand((unsigned int)time(0)); int arr[10] = {0}; arrInit(arr, 10); printf("The origin array is: \n"); arrDisplay(arr, 10); return 0; }链式访问: 把一个函数的返回值作为另一个函数的参数。
#include <stdio.h> #include <string.h> int main(){ printf("%lu\n", strlen("hello, world!")); return 0; }函数的定义是指函数的具体实现,交代函数的功能实现。 test.h:
#pragma once int add(int a, int b); int sub(int a, int b); int mul(int a, int b); int div(int a, int b);test.c:
#include "test.h" int add(int a, int b){ return a +b; } int sub(int a, int b){ return a - b; } int mul(int a, int b){ return a * b; } int div(int a, int b){ return a / b; }main.c:
#include <stdio.h> #include "test.h" int main(){ int a = 4; int b = 2; printf("a + b = %d + %d = %d\n", a, b, add(a, b)); printf("a - b = %d - %d = %d\n", a, b, sub(a, b)); printf("a * b = %d * %d = %d\n", a, b, mul(a, b)); printf("a / b = %d / %d = %d\n", a, b, div(a, b)); return 0; }程序调用自身的编程技巧称为递归。递归作为一种算法在程序设计语言中广泛应用。一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就课描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的主要思考方式在于:大事化小。
打印一个整数的每一位:
#include <stdio.h> void everyBitPrint(int n){ if(n > 9){ everyBitPrint(n / 10); } printf("%d ", n % 10); } int main(){ int num = 987654; everyBitPrint(num); printf("\n"); return 0; }递归求字符串长度:
#include <stdio.h> size_t myStrlen(const char* s){ if(*s == '\0'){ return 0; } return myStrlen(++s) + 1; } int main(){ char str[] = "hello, world!"; printf("%lu\n", myStrlen(str)); return 0; }求N的阶乘:
#include <stdio.h> int factor(int n){ if(n == 0 || n == 1){ return 1; } return factor(n - 1) * n; } int main(){ int n; printf("Please input the n: \n"); scanf("%d", &n); printf("The factorial of %d is: %d\n", n, factor(n)); return 0; }斐波那契数列:
#include <stdio.h> int fibonacci(int n){ if(n == 1 || n == 2){ return 1; } return fibonacci(n - 1) + fibonacci(n - 2); } int main(){ int n; printf("Please input the n: \n"); scanf("%d", &n); printf("The No.%d of fibonacci array is: %d\n", n, fibonacci(n)); return 0; }斐波那契数列的优化: 斐波那契数列的递归算法如下:
int fibonacci(int n) { if (n < 2) { return n; } if (n == 2) { return 1; } if (n == 3) { /*---统计第三项被计算了多少次---*/ ++count; } return fibonacci(n - 1) + fibonacci(n - 2); } /* ** 斐波那契数列的第40项 ** 结果如下 ** 102334155 ** 第三项被计算了39088169次! */从上述代码的打印结果可以看出,在求解斐波那契数列的第40项过程中斐波那契数列的第3项被计算了近4000万次,所以有必有对其进行优化。
优化后的递归算法:
int fibonacci(int n_1, int n_2, int n) { if (n < 3) { return 1; } if (n == 3) { /*---统计第三项被计算了多少次---*/ ++count; return n_1 + n_2; } return fibonacci(n_2, n_1 + n_2, n - 1); } /* ** 斐波那契数列的第40项 ** 结果如下 ** 102334155 ** 第三项被计算了1次! */非递归算法:
int fibonacci(int n) { if (n < 2) { return n; } if (n == 2) { return 1; } int n_1 = 1; int n_2 = 1; int n_n = 0; for (int i = 3; i <= n; ++i) { n_n = n_1 + n_2; n_1 = n_2; n_2 = n_n; } return n_n; }注意:
许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。当一个问题相当复杂时,难以用迭代实现,此时递归实现的简洁型便可以补偿它所带来的运行时开销。