目录
什么是函数?
在计算机科学中,子程序,是一个大型程序中的某部分代码,由一个或者多个语句块组成,它负责完成某项特定的任务,而且相对于其他代码具备独立性。一般会有参数的输入并且有返回值,提供对过程的封装和细节的隐藏。
这些代码一般会被集成为软件库。
C语言的函数的分类
- 库函数(C语言本身封装好的函数):部分函数我们在开发的过程中每一个程序员都可能用得到,为了支持可移植性和提高程序效率,所以C语言的基础库提供了一系列类似的库函数,方便程序员进行软件开发。常见的库函数有:IO函数(输入输出函数)、字符串操作函数、字符操作函数、内存操作函数、时间/日期函数、数学函数,其他库函数...(size_t表示无符号整型)
- 库函数的使用(使用库函数必须包含#include对应的头文件)c语言里字符串存储保存的是地址
- 代码段
char arr1[20] = ""; char arr2[] = "panjaiyi"; strcpy(arr1, arr2); printf("%s\n", arr1);//panjaiyi printf("%s\n", arr2);//panjaiyi ------------------------------------------------------------------------------------------- char arr[] = "hello world"; memset(arr, 'x', 5); printf("%s\n", arr);//xxxxx world ------------------------------------------------------------------------------------------- char arr[] = "abc"; size_t len = strlen(arr); printf("%u\n", len);//3 printf("%u\n", strlen(arr));//3 //把函数的返回值作为新函数的参数 -------------------------------------------------------------------------------------------
- 自定义函数(自定义函数和库函数一样,由函数名,返回类型和函数参数)
- 写一个函数返回两个数的较大值。
- 函数可以没有参数也可以没有返回值
- 写一个函数实现两个两个数的交换
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int jiaohuan(int* pa, int* pb) { int tmp = 0; tmp= *pa; *pa = *pb; *pb = tmp; } int main() { int a = 99; int b = 11; printf("交换前:%d %d", a, b); jiaohuan(&a, &b); printf("交换后:%d %d", a, b); return 0; }
函数的调用
- 当函数调用时候,实参传给形参,形参其实是实参的一分临时拷贝,所以对形参修改不会影响实参。
- 写一个函数判断一个数是不是素数。
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int sushu(int n) { for ( int j = 2; j <n; j++) { if (n % j == 0) { return 0; } } return 1; } int main() { //判断0-100的素数 int count = 0; for (int i = 0; i <= 100; i++) { if (sushu(i)==1) { printf("%d是素数\n", i); count++; } } printf("%d\n", count); return 0; } --------------------------------------------------------------------------------------- #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int sushu(int m) { for (int i = 2; i < m; i++) { if ((m % i) == 0) { return 0; } } return 1; } //判断一个数是不是素数 int main() { int n = 0; scanf("%d", &n); int biu=sushu(n); if (biu==1) { printf("%d是素数\n",n); } else { printf("%d不是素数\n",n); } return 0; }
-
判断一年是不是闰年
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int runnian(int n) { if ((n % 400 == 0) || ((n % 100 != 0) && (n % 4 == 0))) { return 1; } return 0; } int main() { //判断一年是不是闰年 int count = 0; for (int i = 1000; i < 2001; i++) { if (runnian(i) == 1) { printf("%d是闰年\n", i); count++; } else { printf("%d不是闰年\n", i); } } printf("%d\n", count); return 0; } ----------------------------------------------------------------------------------------- #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int runnian(int n) { if ((n % 400 == 0) || ((n % 100 != 0) && (n % 4 == 0))) { return 1; } return 0; } int main() { int a = 0; scanf("%d", &a); int nianfen = runnian(a); if (nianfen == 1) { printf("%d是闰年\n", a); } else { printf("%d不是闰年\n", a); } return 0; }
-
写一个函数实现整型有序数组的二分查找
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int fun(int arr[], int n, int l) { int left = 0; int right = l; while (left<=right) { int mid = (left + right) / 2; if (arr[mid] < n) { left = mid + 1; } else if (arr[mid] > n) { right = mid - 1; } else { return mid; } } return 0; } int main() { int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; int k = 7; int len = sizeof(arr) / sizeof(arr[0]) - 1; int ret = fun(arr, k, len); if (ret == 0) { printf("没找到"); } else { printf("找到了,下标是%d", ret); } return 0; }
-
写一个函数,每调用一次这个函数num加一
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> void sum(int* pnum) { *pnum += 1; } int main() { int num = 0; sum(&num); printf("%d\n", num);//1 sum(&num); printf("%d\n", num);//2 sum(&num); printf("%d\n", num);//3 return 0; } -------------------------------------------------------------------------------------------#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int sum(int n) { n++; return n; } int main() { int num = 0; int m = sum(num); printf("%d\n", m);//1 int i = sum(m); printf("%d\n", i);//2 int j = sum(i); printf("%d\n", j);//3 return 0; }
函数的参数
- 函数的参数:调用函数时候传递的参数为实参;定义函数时候的参数为形参。
- 函数的调用:传值调用(函数中的形参和实参分别占有不同的内存块,对形参不会影响实参)和传址调用(传址调用是把函数外部创建变量的内存地址传递给函数的参数的一种调用函数的方式。这种传参的方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接函数外部的变量。)
- 函数的返回类型有void,int,char,float...(void里的return可以提前终止函数)
函数的嵌套调用和链式访问
函数和函数之间可以根据实际需求进行组合,也就是相互调用。
- 函数的嵌套调用(函数可以嵌套调用,但不能嵌套定义)
#define _CRT_SECURE_NO_WARNINGS 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; }
- 链式访问:把一个函数的返回值作为另外一个函数的参数
函数的声明和定义
- 函数的声明和定义
- 模块化开发 (在模块化开发中,.c文件一般用于写函数定义,.h文件用于写函数声明)
- 为什么.c和.h文件独立分开(文件产生的二进制文件称为静态库).h文件一般用于存放函数的声明
- 生成静态库的操作
静态库和头文件同时拷贝过来
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include "add.h"
#pragma comment(lib,"add.lib")//导入的静态库的项目的名字和内部包含的文件的名字尽量保持一致
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int sum = add(a, b);
printf("%d", sum);
return 0;
}
通俗来说,库就是把一些常用的函数的目标文件打包在一起,提供相应的接口,便于程序员使用。本质上来说,库是一种可执行的代码的二进制形式,可以被操作系统载入内存执行。库包含两种:静态库(.lib)动态库(.dll)。库函数的链接放在编译时期完成,程序运行时与库函数无关。
函数的递归
什么是函数的递归?(函数自己调用自己就是递归,递就是给出去,归就是回归)
是递归但是会死递归(死循环导致程序崩溃终止,产生栈溢出现象)
栈溢出现象核心点说明(函数调用占的空间会用尽栈中空间):
接受一个无符号整型值,按照顺序打印他的每一位(递归的归可以理解为每一次函数执行完之后的返回值)
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void print(int n) {
if (n > 9) {
print(n / 10);
}
printf("%d ", n % 10);
}
int main()
{
int num = 123;
print(num);
return 0;
}
编写函数不允许创建临时变量,求字符串长度。(重新实现一下strlen()函数)
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void fun(int* arr) {//传递数组
for (int i = 0; i < 9; i++)
{
printf("%d\n", arr[i]);
}
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9 };//arr是数组名,数组名是数组的首元素的地址
fun(arr);
return 0;
}
-------------------------------------------------------------------------------------------
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int fun(char *parr) {
for (size_t i = 0; i < 6; i++)
{
printf("%c\n", parr[i]);//a b c d e f
}
}
int main()
{
char arr[] = "abcdef";
fun(arr);
return 0;
}
(1)创建临时变量的程序:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int my_strlen(char* parr) {
printf("%c\n", *parr);//a
int count = 0;//临时变量
while (*parr != '\0') {
parr++;
count++;
}
return count;
}
int main()
{
char arr[] = "abc";
int len=my_strlen(arr);
printf("%d\n", len);//3
return 0;
}
(2)不创建临时变量的程序:(递归)
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int my_strlen(char * parr) {
if (*parr=='\0') {
return 0;
}
return 1 + my_strlen(parr + 1);
}
int main()
{
char arr[] = "abcd";
int len = my_strlen(arr);
printf("%d", len);
return 0;
}
(3)图解
递归与迭代(迭代就是循环)
- 求n的阶乘
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int main()//迭代的方式 { int n = 0; int ret = 1; scanf("%d", &n); for (size_t i = 1; i <(n+1); i++) { ret *= i; } printf("%d\n", ret); return 0; } -------------------------------------------------------------------------------------------#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int fun(int n) {//负数没有阶乘 //递归的方式 if (n<=1) { return 1; } else { return n * fun(n - 1); } } int main() { int n = 0; int ret = 0; scanf("%d", &n); ret = fun(n); printf("%d\n", ret); return 0; }
-
求第n个斐波那契数(斐波那契数的特点:前两个数之和等于第三个数)递归的缺陷:调用函数次数太多会沾满栈内存
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int fib(int n) { if (n <= 2) { return 1; } else { return fib(n - 1) + fib(n - 2); } } int main() { int n = 0; scanf("%d", &n); int num = fib(n); printf("%d\n", num); return 0; }
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int fib(int n) { int a = 1; int b = 1; int c = 1; while (n>2) { c = a + b; a = b; b = c; n--; } return c; } int main() { int n = 0; scanf("%d", &n); int num = fib(n); printf("%d\n", num); return 0; }
递归版本的代码结构更加清晰,但是效率往往比的迭代形式的要慢一些