C语言–函数和递归(一)
什么是函数?
函数的而参数
实际参数(实参):
真实传递给函数的参数,叫实参,实参可以是:常量、变量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时,他们都必须有确定的值,以便把这些值传送给形参。
#include <stdio.h>
//计算并返回两个数的较大值
int Get_max(int x, int y){
int z;
if (x > y)
return x;
else
retrun y;
return z;
}
int main() {
int a = 10;
int b = 20;
int max_1, max_2, max_3;
max_1 = Get_max(a, b); //实参为变量
max_2 = Get_max(1, 1 + 1); //实参为常量和表达式
max_3 = Get_max(2, Get_max(3, 4)); //实参为函数
printf("max_1=%d\nmax_2=%d\nmax_3=%d\n", max_1, max_2, max_3); //结果分别为20,2,4
}
形式参数(形参):
形式参数是指函数名后面括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配內存单元),所以叫形式参数。形式参数当函数调用完后就 销毁了。因此形式参数只在函数内部有效。
#include <stdio.h>
int Add(int x, int y) { //int x,int y是函数的形参
int z = x + y;
return z; //返回z的值。将z作为函数调用的结果返回
}
int main() {
int a = 10;
int b = 20;
int sum = Add(a, b); //调用Add函数,实参和形参要一一对应。
printf("%d\n", sum);
}
库函数
为什么会有库函数?
1.我们知道在学习C语言编程的时候,总在一个代码编写完成之后迫不及待的想知道结果,想把这个结果打印到屏幕上看看。这个时候我们会频繁使用一个功能: 将信息按照一定的格式打印到屏幕上(printf)。
2.在编程的过程中我们会频繁的做一些字符串的拷贝工作(strcpy)。
3.在编程时我们也计算,总是会计算n的k次方这样的运算(pow)。
像上面这种基础功能,他们不是业务性的代码。在开发的过程中每个程序员都可能用得到,为了支持可移植性和提高程序的执行效率,所以C语言中提供了一系列类似的库函数,方便程序员门进行软件开发。
简单总结:C语言常用的库函数有:
I/O函数:输入输出函数
字符串操作函数
字符操作函数
内存操作函数
时间/日期函数
数学函数
其他函数库
使用文档学习库函数
库函数学习:www.cplusplus.com http://en.cppreference.com
msdn
自定义函数
自定义函数和库函数一样,有函数名,返回值类型和函数参数,但是不一样的是这些都是我们自己来设计。这给了程序员一个很大的发挥空间。
函数的组成
用一个函数交换两个整型变量的值
#include <stdio.h>
void Swap1(int* x,int* y){
int temp = *x;
*x = *y;
*y = temp; //*解引用,取地址中的值。
}
int main() {
int a = 5;
int b = 6;
printf("%d,%d\n", a, b);
Swap1(&a, &b); //调用Swap1函数,传入a和b的地址。&取地址
printf("%d,%d\n", a, b);
}
这里Swap函数的形参不能直接用整型,需要用指针来交换地址中的值。如果直接用整型变量x,y接收,在函数内部交换的只是x,y的值,并不会影响外面a和b的值
函数的形参和实参分别占用不同的内存块,形参只是实参的一份临时拷贝,二者之间没有联系,对形参的修改不会影响实参。 (专业的)
函数的调用
传值调用
函数的形参和实参分别占用不同的内存块,对形参的结果不会影响实参。
传址调用
传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用方式。
这种传参方式可以让函数和函数外部的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。(上面的交换值swap)
练习
1.写一个函数可以判断一个数是不是素数。
#include <stdio.h>
#include <math.h>
int Is_prime(int n)
{
int j;
for (j = 2; j <= sqrt(n); j++)
{
if (n%j==0) //等于0说明是偶数,返回0
{
return 0; //return也有break的作用
}
}
return 1; //如果上面没有返回,说明是素数,此时返回1
}
int main() {
int i = 0;
for (i = 100; i <= 200; i++)
{
//判断i是否为素数
if (Is_prime(i)==1) //如果返回值是1说明是素数,进行打印
{
printf("%d ", i);
}
}
}
2.写一个函数,实现一个整型有序数组的二分查找
第一次失败了。
#include <stdio.h>
int Binary_search(int arr[], int k) //这里的int arr[]本质上是一个指针,直接接收到了arr首个元素的地址
{
int left = arr[0];
int right = sizeof(arr)/sizeof(arr[0])-1;
int mid;
while (left<=right)
{
mid = (left + right) / 2; //中间元素下标
if (arr[mid]<k)
{
left = mid+1;
}
else if (arr[mid]>k)
{
right = mid-1;
}
else
{
return mid;
}
}
if (left > right)
return -1;
}
int main()
{
//二分查找,在一个有序数组中查找具体的某个数
//如果找到了返回下标,找不到返回-1
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int k = 2;
int ret = Binary_search(arr, k); //这里arr只传送了数组arr首元素的地址
if (ret == -1)
{
printf("找不到指定数字");
}
else
{
printf("找到了,下标是:%d\n", ret);
}
return 0;
}
调试后
#include <stdio.h>
//调试后发现,调用函数后,在监视窗口看到数组长度只有1。查阅资料后明白形参int arr[]只会接收实参的首个元素地址,并不会收到整个数组。
//需要在外部计算好数组长度之后,将数组长度作为实参传递给函数,才能让函数接收到数组长度,使后续运算可以继续进行。
int Binary_Search(int arr[], int k, int n) //这里的int arr[]本质上是一个指针,直接接收到了arr首个元素的地址
{
int left = 0;
int right = n;
int mid = (left + right) / 2; //中间元素下标
while (left <= right)
{
mid = (left + right) / 2; //每次左边或右边下标改变,重新计算mid
if (arr[mid] > k) //如果arr[mid]比k大,说明k在数组左半边,中间下标变成最右边下标
right = mid;
else if (arr[mid] < k) //如果arr[mid]比k小,说明k在数组右半边,中间下标变成最左边下标
left = mid;
else
return mid; //如果既不大于也不小于,就是找到了,mid就是下标,返回mid
}
return -1;
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; //二分查找法必须是有序数组
int k = 7; //要查找数字7
int n = sizeof(arr) / sizeof(arr[0]) - 1; //求出元素个数后记得 -1 ,才是末尾元素的下标
int ret = Binary_Search(arr, k, n); //这里arr只传送了数组arr首元素的地址
if (ret == -1) //如果返回值为1,说明函数内的while循环执行完了也没有返回值,说明没有找到指定数字
printf("找不到指定的数字");
else
printf("找到了,下标为%d", ret);
}
函数的嵌套调用和链式访问
函数和函数之间可以有机组合
嵌套调用
#include <stdio.h>
void New_line() //函数New_line用于打印hehe
{
printf("hehe\n");
}
void Three_line()
{
for (int i = 0; i < 3; i++)
{
New_line(); //函数Three_line里边调用了三次New_line函数
}
}
int main()
{
Three_line();
return 0;
}
链式访问
#include <stdio.h>
#include <string.h>
int main()
{
int len = 0;
len = strlen("abc"); //len 接收strlen函数的返回值
printf("%d\n", len);
printf("%d\n", strlen("len")); //将返回值再次作为strlen的参数。
return 0;
}
printf的返回值是打印字符的个数
#include <stdio.h>
int main()
{
printf("%d", printf("%d", printf("%d",43)));
return 0;
}
函数的声明和定义
函数声明
1.告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但具体是不是存在无关紧要。
2.函数的声明一般出现在函数的使用前。要满足先声明后使用。
3.函数的声明一般要放在头文件中的。
#include <stdio.h>
int Add(int x, int y); //main函数后面定义的函数,在前面声明一下就可以正常使用。
int main()
{
int a, b;
a = 10;
b = 20;
int sum = Add(a, b);
printf("%d", sum);
return 0;
}
int Add(int x, int y) //函数在这里定义,若不进行声明,会报错(Add未定义)
{
int z = x + y;
return z;
}
函数定义
函数的定义是指函数的具体实现,交代函数的功能实现。
test.g的内容,放置函数的实现。
头文件
函数文件
主文件
为什么?
这种方法可以将工程分工合并,不同程序员各自承担部分功能,写入不同的头文件,最后在主文件中引入头文件,直接调用功能即可
每个模块相对独立,不会杂乱无章,结构更加清晰。