函数
定义
在维基百科中,函数的定义叫做子程序。
(1)一个大型程序中的某部分代码, 由一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代 码,具备相对的独立性。
(2)一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库
分类
1.库函数
2.自定义函数
库函数
定义
我们在编写C语言代码的时候,总会频繁地使用一些功能:
比如:将信息按照一定的格式打印到屏幕上(printf)、在编程的过程中我们会频繁的做一些字符串的拷贝工作(strcpy)、在编程是我们也计算,总是会计算n的k次方这样的运算(pow)…
像上面的这些基本的功能,在编写程序时经常会用到。所以C语言的基础库中提供了一系列类似的库函数,方便程序员进行软件开发。
例-strcpy
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include<string.h> //引strlen等字符串函数
#include<windows.h> //引sleep
#include <stdio.h>
#include<stdlib.h> //引system
#include <stdio.h>
int main()
{
char arr1[] = "bit"; //也拷贝了\0
char arr2[20] = "###########";
strcpy(arr2, arr1);
printf("%s\n", arr2);
//strcpy
}
例-memset
- void * memset (void * ptr,int value,size_t num);
- 把指定位置的前num个字符设置为value
#include <stdio.h>
#include<string.h>
int main()
{
char arr[] = "hello world";
memset(arr, '*', 5);
printf("%s\n", arr);
return 0;
}
自定义函数
组成
ret_type fun_name(para1, * )
{
statement;//语句项,函数体,交代函数的实现
}
ret_type 返回类型
fun_name 函数名
para1 函数参数
举个栗子
- 写一个函数找出两个整数的最大值
#include <stdio.h>
#include<string.h>
int get_max(int x, int y)
{
if (x > y)
return x;
else
return y;
}
int main()
{
int a = 10;
int b = 20;
int max = get_max(a,b);
printf("max = %d\n", max);
return 0;
}
-
写一个函数交换两个整型变量的内容
-
示例一:在主函数里写
#include <stdio.h> #include<string.h> int main() { int a = 10; int b = 20; int tmp = 0; printf("a=%d b=%d\n", a, b); tmp = a; a = b; b = tmp; printf("a=%d,b=%d\n", a, b); return 0; } //并没有在自己写的函数里面实现
-
示例二:错误示范
#include <stdio.h> #include<string.h> void Swap(int x, int y) //void代表这个函数没有返回值,void是空,无的意思 { int tmp = 0; tmp = x; x = y; y = tmp; } int main() { int a = 10; int b = 20; int tmp = 0; printf("a=%d b=%d\n", a, b); Swap(a,b); /*tmp = a; a = b; b = tmp;*/ printf("a=%d,b=%d\n", a, b); return 0; } //x,y变并不会影响a和b,不是同一块空间 //上面xy和下面ab没有关系
-
示例三
前置
int main() { int a = 10; int* pa = &a; //pa指针变量 *pa = 20; //解引用操作 printf("%d\n", a); return 0; }
正确
void Swap2(int* pa, int* pb) { int tmp = 0; tmp = *pa; *pa = *pb; *pb = tmp; } int main() { int a = 10; int b = 20; int tmp = 0; printf("a=%d b=%d\n", a, b); //Swap1(a,b); Swap2(&a, &b); printf("a=%d,b=%d\n", a, b); return 0; }
-
函数的参数
实际参数(实参)
真实传给函数的参数,叫实参。
实参可以是:常量、变量、表达式、函数等。
在调用函数时,它们都必须有确定的值,以便把这些值传送给形参。
形式参数(形参)
形式参数是指函数名后括号中的变量。
形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。因此形式参数只在函数中才有效。
结论
当实参传给形参的时候,形参其实是实参的一份临时拷贝,对形参的修改是不会改变实参的
函数的调用
传值调用
-
函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。
-
比如求两个数较大值,不需要改变a和b,不需要传地址,和swap不同
传址调用
- 传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。
这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。
- 需要改变地址
练习
写一个函数可以判断一个数是不是素数
#include <stdio.h>
#include<string.h>
//是素数返回1,不是返回0
int is_prime(int n)
{
//2->n-1
int j = 0;
for (j = 2; j < n; j++) //j<=sqrt(n),sqrt表示对参数开平方进行优化
{
if (n % j == 0)
return 0;
}
//if(j ==n)
return 1;
}
int main()
{
//打印100-200之间的素数
int i = 0;
for (i = 100; i <= 200; i++)
{
//判断i是否为素数,规定如果是素数返回1,不是返回0
if(is_prime(i) == 1)
printf("%d ",i);
}
}
写一个函数判断一年是不是闰年
#include <stdio.h>
#include<string.h>
int is_leap_year(int y)
{
if ((y % 4 == 0 && y % 100 != 0) || (y % 400 == 0))
return 1;
else
return 0;
}
//不要在上述函数打印,直接告诉返回1或0就行,完成单一功能,简单,干净
int main()
{
int year = 0;
for (year = 1000; year <= 2000; year++)
{
//判断year是否为闰年
if (1 == is_leap_year(year))
{
printf("%d ", year);
}
}
return 0;
}
写一个函数,实现一个整形有序数组的二分查找
- 下面这个是个错误滴
#include <stdio.h>
#include<string.h>
//本质上arr是个指针,地址4&8
int binary_search(int arr[], int k) //看上去是数组形式,但这里的arr不是数组
{
//算法的实现
int sz = sizeof(arr) / sizeof(arr[0]); //下面数组传过去,在内部就不能这样求元素个数,等于1或2喽
int left = 0;
int right = sz - 1;
while (left <= right)
{
int mid = (left + right) / 2;
if (arr[mid] < k)
{
left = mid + 1;
}
else if (arr[mid] > k)
{
right = mid - 1;
}
else
{
return mid;
}
}
}
int main()
{
//二分查找
//在一个有序数组中查找具体的某个数
//如果找到了返回,这个数的下标,找不到的返回-1
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int k = 7;
//传递过去的是数组arr首元素的地址
int ret = binary_search(arr, k); //arr此时代表的仅仅是首元素的地址
if (ret == -1)
{
printf("找不到指定数字\n");
}
else
{
printf("找到了,下标是:%d\n", ret);
}
}
//先想函数怎么用,再考虑函数怎么实现
-
看个正确滴
#include <stdio.h> #include<string.h> //本质上arr是个指针,地址4&8 int binary_search(int arr[],int k,int sz) //看上去是数组形式,但这里的arr不是数组 { int left = 0; int right = sz- 1; while (left <= right) { int mid = (left + right) / 2; //不能放到循环外 if (arr[mid] < k) { left = mid + 1; } else if (arr[mid] > k) { right = mid - 1; } else { return mid; } } } int main() { //二分查找 //在一个有序数组中查找具体的某个数 //如果找到了返回,这个数的下标,找不到的返回-1 int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; int k = 7; int sz = sizeof(arr) / sizeof(arr[0]); //主动传sz //传递过去的是数组arr首元素的地址 int ret = binary_search(arr, k,sz); //arr此时代表的仅仅是首元素的地址 if (ret == -1) { printf("找不到指定数字\n"); } else { printf("找到了,下标是:%d\n", ret); } } //先想函数怎么用,再考虑函数怎么实现
写一个函数,每调用一次这个函数,就会将 num 的值增加1
#include <stdio.h>
#include<string.h>
void Add(int* p)
{
(*p)++; //++优先级比较高所以把*p括起来
}
int main()
{
int num = 0;
Add(&num);
printf("num = %d\n", num); //1
Add(&num);
printf("num = %d\n", num); //2
Add(&num);
printf("num = %d\n", num); //3
return 0;
}
函数的嵌套调用和链式访问
函数和函数之间可以有机的结合的
嵌套调用
#include <stdio.h>
#include<string.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;
}
链式访问
把一个函数的返回值作为另外一个函数的参数
#include <stdio.h>
#include<string.h>
int main()
{
int len = 0;
//1
len = strlen("abc");
printf("%d\n", len);
//2
printf("%d\n", strlen("abc"));
}
小小题目,看下列打印值
#include <stdio.h>
#include<string.h>
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
//printf返回值是打印在屏幕上的个数
return 0;
}
结果:4321
函数的声明和定义
函数声明
- 函数的声明主要的目的在于告诉编译器有一个函数叫什么,参数是什么,返回类型是什么,但是具体是不是存在,无关紧要。
- 函数的声明一般出现在函数的使用之前。要满足先声明后使用。
- 函数的声明一般要放在头文件中的。
函数定义
定义
程序调用自身的编程技巧称为递归( recursion)。表示一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
两个必要条件
- 存在限制条件,当满足这个限制条件的时候,递归便不再继续。
- 每次递归调用之后越来越接近这个限制条件。
简单递归
#include <stdio.h>
#include<string.h>
int main()
{
printf("hehe\n");
main();
return 0;
}
//上述跑起来会栈溢出
练习
- 接受一个整型值(无符号),按照顺序打印它的每一位,例如:输入:1,2,3,4,输出:1,2,3,4,
#include <stdio.h>
#include<string.h>
void print(int n)
{
if (n > 9) //递归要满足这个条件
{
print(n / 10);
}
printf("%d ", n % 10);
}
int main()
{
unsigned int num = 0;
scanf("%d", &num);
//递归
print(num);
return 0;
}
-
编写函数不允许创建临时变量,求字符串长度
先看一个创建的
#include <stdio.h> #include<string.h> int my_strlen(char* str) { int count = 0; while(*str !='\0') { count++; str++; } return count; } int main() { char arr[] = "bit"; /* int len = strlen(arr); //求字符串长度 printf("%d\n", len);*/ //模拟实现了一个strlen函数 int len = my_strlen(arr); //arr是数组,数组传参,传过去的不是整个数组,而是第一个元素的地址 printf("len = %d\n", len); return 0; }
下面是不创建临时变量的递归解法:
#include <stdio.h>
#include<string.h>
//递归的方法
int my_strlen(char* str)
{
if (*str != '\0')
return 1 + my_strlen(str + 1);
else
return 0;
}
int main()
{
char arr[] = "bit";
/* int len = strlen(arr); //求字符串长度
printf("%d\n", len);*/
//模拟实现了一个strlen函数
int len = my_strlen(arr); //arr是数组,数组传参,传过去的不是整个数组,而是第一个元素的地址
printf("len = %d\n", len);
return 0;
}
递归与迭代
练习:求n的阶乘
- 循环的方式
#include <stdio.h>
#include<string.h>
int Fac1(int n)
{
int i = 0;
int ret = 1;
for (i = 1; i <= n; i++)
{
ret *= i;
}
return ret;
}
int main()
{
int n = 0;
int ret = 0;
scanf("%d", &n);
ret = Fac1(n); //循环的方式
printf("%d\n", ret);
return 0;
}
-
下面用一种递归的方式
#include <stdio.h> #include<string.h> int Fac2(int n) { if (n <= 1) return 1; else return n * Fac2(n - 1); } int main() { int n = 0; int ret = 0; scanf("%d", &n); ret = Fac2(n); //循环的方式 printf("%d\n", ret); return 0; }
练习:求第n个斐波那契数(不考虑溢出)
-
int count = 0; int Fib(int n) { if (n == 3) //测试第三个斐波那契数的计算次数 { count++; } if (n <= 2) return 1; else return Fib(n - 1) + Fib(n - 2); }
-
递归解决,大量重复计算,并不是很好的方法
#include <stdio.h> #include<string.h> int count = 0; int Fib(int n) { if (n == 3) //测试第三个斐波那契数的计算次数 { count++; } if (n <= 2) return 1; else return Fib(n - 1) + Fib(n - 2); } int main() { int n = 0; int ret = 0; scanf("%d", &n); //TDD-测试驱动开发 ret = Fib(n); printf("ret = %d\n", ret); printf("count = %d\n,count"); return 0; }
-
循环的方式
#include <stdio.h> #include<string.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; int ret = 0; scanf("%d", &n); //TDD-测试驱动开发 ret = Fib(n); printf("ret = %d\n", ret); printf("count = %d\n,count"); return 0; }