目录
在学习函数之前我们应该了解一下函数是什么?
函数是一个大型程序中的某部分代码,是由一条或者多条语句块组成的。他会负责完成某项特定的任务,而且相较于其他代码,具有相对独立性;即函数是一个相对独立的代码块,他能完成某项特定的任务!
函数的分类
C语言中将函数分为库函数和自定义函数 ;
库函数
1.为什么会有库函数?
在平时进行程序开发的时候会必然有大量重复使用的功能块即函数,而每次用的时候再去实现就会很麻烦,使得编程效率大大降低。因此基于此,C语言基础库中题库提供了一系列的库函数方便平时开发时使用!例如:printf scanf pow等,也就是说C语言本身是没有这些函数的,某些功能因为平时用的频繁而被封装好放在某个库里的,C语言只是规定了标准而并未对具体实现作出要求,即具体实现是看编译器的;
2.如何进行学习库函数呢?
建议是在网站按照文档学习!C/C++的官网或者:http://www.cpulspuls.com
我们一起来看几个库函数的使用的吧:
(1)strcpy
ok,通过学习完上面的文档我们大概了解到该函数的用法我们来试用一下吧:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[40] = "hello";
char arr2[20] = { 0 };
strcpy(arr1, " bit");
strcpy(arr2, " bit");
printf("%s\n", arr1);
printf("%s\n", arr2);
return 0;
}
代码运行结果:
(2)memset
该函数说的是,将ptr指向的那块内存空间的值从第一个字节开始设定为value,设定的大小为size_t(sizeof 返回值),而尽管你传的是int类型但人家在用的时候会自动转换为unsigned char;
OK,我们来用一下这个函数:
#include<stdio.h>
#include<string.h>
int main()
{
char arr[20] = "abcdef";
memset(arr, 42, 2);
printf("%s", arr);
return 0;
}
运行结果和代码解释:
当然你也可以直接把字符给进去:
OK我们举例就到这里!
这里你肯定还有很多疑问,库函数都要记住吗?NO!不用,你用的时候再查。关键是掌握使用(学习)的方法,下面是查询学习的几个网站!
库函数学习网站:
2.C/C++官网:
http://en.cppreference.com(中文版)
http://zh.cppreference.com(英文版)
自定义函数
为什么有库函数了还要有自定义函数呢?
库函数不是万能的,如果库函数能把所有的事情都做了那程序员就失业了!正是因为各种现实需求库函数不能很好的解决所以就由程序员自己来实现相应的需求!
自定义函数语法:
返回值类型 函数名 (参数列表)
{
函数体;
}
OK让我们来实现一个自定义函数吧:
#include<stdio.h>
int GetMax(int x, int y)
{
return x > y ? x : y;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int max = GetMax(a,b);
printf("%d", max);
return 0;
}
运行结果:
怎么样是不是很简单?下面我们再来个栗子:交换两个整数的值:
#include<stdio.h>
void Swap(int a, int b)
{
int temp = a;
a = b;
b = temp;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
printf("交换前:a = %d b = %d\n", a, b);
Swap(a, b);
printf("交换后:a = %d b = %d\n", a, b);
return 0;
}
你是不是这样写的呢?这样对吗?
看运行结果:
哎,这怎么没交换呢?这里就要说一下函数的参数了;
函数的参数
函数的参数有两种:实际参数和形式参数;
实际参数:也叫实参,是真实传给函数的参数;实参可以是 常量, 变量, 表达式, 以及函数;
无论实参是何种类型的值,在进行函数调用时,他们都必须有确定的值,以便把这些值赋给形参!
形式参数 :也叫形参,因为只有在该函数被调用时其参数才开辟空间(实例化),所以叫形参!形参当函数调用完之后就会自动销毁,即形参在该函数内部有效!
也即是说如果没有返回值的话只改变形参是无法带到函数外面改变实参的,而且 return 的时候只能返回一条语句无法解决上面问题。那么该如何解决上面的问题呢?
我们采用传址调用(代码演示):
#include<stdio.h>
void Swap(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
printf("交换前:a = %d b = %d\n", a, b);
Swap(&a, &b);
printf("交换后:a = %d b = %d\n", a, b);
return 0;
}
运行结果和代码解释:
传址调用是函数调用的一种,下面我们先一起来看看什么是函数的调用:
传址调用和传址调用:
传值调用:顾名思义,函数调用时传的是值;此时函数的实参和形参分别指向不同的内存空间,改变形参不会影响实参!也即是说形参是实参的一份临时拷贝,改变形参不改变实参!
传址调用:顾名思义,函数调用时传的是地址!是指把函数外部创建的变量的地址作为函数的实参!这时函数的实参和形参指向同一块内存空间,改变形参可以改变形参!
也即是说上面第一个没交换成功是因为只把实参值赋值给形参,此时形参也开辟了内存空间,而他和实参不是一块空间,改变形参实参还是没变,所以上面才没有交换成功!而第二次传的是地址,是参和形参指向同一块空间改变了形参等再次访问实参时已被改变!
理解:张三很忙没时间打扫房间,于是请他的朋友李四帮他打扫,张三在早上出门时把钥匙给了李四,李四随后打开门把房子打扫了,等张三晚上回来时房间已经是干净的!
OK,理解了上面之后我们再来看几个栗子:
栗子:
(1)写一个函数可以判一个数是不是素数
int The_num(int num)
{
int i = 0;
for (i = 2; i < num; i++)
{
if (num % i == 0)
{
break;
}
}
if (i == num)
{
return 1;
}
return 0;
}
int main()
{
int num = 0;
scanf("%d", &num);
int r = The_num(num);//是素数返回 1 不是返回 0
if (r == 1)
{
printf("%d是素数 ", num);
}
else
{
printf("%d不是素数", num);
}
return 0;
}
当然判断素数的方法有很多这里几就介绍这一种!
(2)写一个函数判断是不是闰年
int LeapYear(int year)
{
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
{
return year;
}
return 0;
}
int main()
{
int year = 0;
scanf("%d", &year);
int y = LeapYear(year);
if (y)
printf("%d润年 ", year);
else
printf("%d不是润年 ", year);
return 0;
}
总结:4年一润,百年不润,400年再润!
(3)写一个函数,实现一个整型有序数组的二分查找
int BinarySearch(int arr[], int sz, int k)
{
int left = 0;
int right = sz - 1;
int mid = 0;
while (left <= right)
{
mid = (left + right) / 2;
if (k > arr[mid])
{
left = mid + 1;
}
else if (k < arr[mid])
{
right = mid - 1;
}
else
return mid ;
}
return -1;
}
int main()
{
int arr[10] = { 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
int k = 0;
printf("请输入%d个有序的数组元素:> ", sz);
for (i = 0; i < sz; i++)
{
scanf("%d", &arr[i]);
}
printf("请输入你要查找的元素:> ");
scanf("%d", &k);
int n = BinarySearch(arr, sz, k);
if ( n != -1)//找到了返回 下标, 否则返回 -1
{
printf("找到了下标是:%d\n", n);
}
else
{
printf("没找到!\n");
}
return 0;
}
注意: 二分查找必须是有序的 !
(4)写一个函数,每调一次这个函数num就加1
void test(int* num)
{
(*num)++;
}
int main()
{
int num = 0;
test(&num);
test(&num);
printf("%d\n", num);
return 0;
}
注意:这里要搞清楚*和++的优先级!
OK,以上栗子您是否明白?如果有问题可私信我或留言!
函数的嵌套调用和链式访问
函数的嵌套调用:就是函数里面可以调函数!
代码演示:
void test3()
{
printf("haha\n");
}
void test2()
{
test3();
}
void test1()
{
test2();
}
int main()
{
test1();
return 0;
}
运行截图和代码解释:
这就是一个简简单单的函数嵌套调用的代码!
注意:函数可以嵌套调用但绝不可以嵌套定义!!!
函数的链式访问:就是把一个函数的返回值作为另一个的参数。
举个栗子:
int Add(int a, int b)
{
return a + b;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d%d", &a, &b);
printf("%d\n", Add(a, b));
return 0;
}
运行结果及代码解释:
提到函数的链式访问就不得不说一下这一段代码了:
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
return 0;
}
停3秒想想代码的结果是什么?
3
2
1
代码运行结果:
解释:这里要介绍一下printf的返回值:
函数的声明和实现
函数的声明:告诉编译器有一个函数叫什么(函数名),参数叫什么,返回值类型是什么;
函数的声明仅仅是声明,具体存不存在是实现的问题了;
函数的声明一般出现在函数使用之前,要满足先声明在使用。
函数的声明一般放在头文件中,后续直接引用头文件即可!
函数的实现:是指函数功能的具体实现即函数的功能。
注意:引用自己的头文件用" ",引用库里面的用<>
函数的递归
什么是递归?
递归是一种算法,递归可以分开理解成 递进 和 归并,先递进到一个结束条件后归并;在此过程中是不断调用自身的,而且是在调用过程中接近这个结束条件的;(即把大事化小来处理)
递归的两个必要条件:
1.存在限制条件,当满足这个条件之时递归不在继续;
2.每次递归完之后越来越接近这个条件;
OK,理解了递归后我们来看几个栗子吧:
递归栗子:
1.接受一个整型数值(无符号),按照顺序打印它的每一位
void print(int n)
{
if (n > 9)
{
print(n / 10);
}
printf("%d ", n % 10);
}
int main()
{
int num = 0;
scanf("%d", &num);
print(num);
return 0;
}
运行截图:
代码解释:
上述递归的限制条件就是n > 9;
2.写一个函数要求不使用临时变量求字符串长度
#include<assert.h>
int MyStrlen(char* str)
{
assert(str != NULL);
if (*str == '\0')
return 0;
else
return 1 + MyStrlen(str + 1);
}
int main()
{
char str[] = "abcdef";
int len = MyStrlen(str);
printf("%d\n", len);
return 0;
}
运行截图:
代码解释:
3.获取一个整数的每一位之和
int GetBitSum(unsigned int n)
{
if (n > 9)
{
return n % 10 + GetBitSum(n / 10);
}
else
return n;
}
int main()
{
int n = 0;
scanf("%d", &n);
int sum = GetBitSum(n);
printf("%d\n", sum);
return 0;
}
运行截图:
代码解释:
上面几个递归不知你是否理解?
下面我们再来看一个递归:
4.求n个斐波那契数
int Fib(int n)
{
if (n == 1 || 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;
}
代码运行结果:
代码解释:
第六个数确实是 8!
斐波那契这个的递归可不是一般的递归,他可是王维诗里的递归!!!上面1---3都是单路递归而这个是双路递归!!!其原理类似于二叉树(后面会专门介绍),而且您也看了递归路线图,会发现它是有很多重复的这会导致运算速度大大下降!!!因此斐波那契一般不用递归实现而是用迭代实现!
int fib(int n)
{
int i = 0;
int f1 = 1;
int f2 = 1;
int f3 = 1;
for (i = 3; i <= n; i++)
{
f3 = f2 + f1;
f1 = f2;
f2 = f3;
}
return f3;
}
int main()
{
int n = 0;
scanf("%d", &n);
int num = fib(n);
printf("%d\n", num);
return 0;
}
运行截图:
用迭代会效率高的多!!!
OK,兄弟我们下期再见!