重点章节
函数
函数非常重要,C语言中离不开函数!!!
本章主要掌握函数的基本使用和递归
函数是甚麽
- 数学中我们常见到函数的概念。但是你了解C语言中的函数吗?维基百科中对函数的定义:子程序
- 在计算机中,子程序,是一个大型程序中某部代分代码,由一个或多个语句块组成。他负责完成某项特定任务,而且相较于其他代码,具备相对的独立性。一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库。
C语言中函数的分类
- 1.库函数
- 2.自定义函数
首先我们先认识一下库函数。
为甚么会有库函数呢? - 我们知道在我们学习C语言编程的时候,总是在一个代码编写完成之后迫不及待的想知道结果,想把这个结果打印到我们的屏幕上看看。这个时候我们会频繁的使用一个功能:将信息按照一定的格式打印到屏幕(printf)。
- 在编程的过程中我们会频繁的做一些字符串的拷贝工作(strcpy)。
- 在编程时我们总是会计算n的k次方这样的运算(pow)。
- 像上面我们描述的基础功能,它们不是业务性的代码。我们在开发的过程中每个程序员都可能用的到,为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序员进行软件开发。
如何学习库函数?
这里我们简单的看看:www.cplusplus.com这个网站可以学习库函数。
http://en.cppreference.com 也是可以查询库函数的网站,它既有英文,也可转换为中文
库函数的一个特点是:库函数都是需要引用头文件的
C语言常用的库函数
- IO函数(输入输出函数):如 printf、scanf 、getchar、putchar
- 字符串操作函数:strcmp、strlen
- 字符操作函数:如 toupper(小写转大写的函数)
- 内存操作函数:如 memcpy、memcmp、mempset
- 时间/日期函数:如 time
- 数学函数:sqrt pow
- 其他库函数
下面我们参照文档,举学习几个库函数
[strcpy]:字符串拷贝(http://www.cplusplus.com/reference/cstring/strcpy/?kw=strcpy)
char * strcpy ( char * destination, const char * source );
函数名 目的地 源头
即把sourse指向的字符串拷贝到destination 里面去
eg:arr2里面的内容拷贝放到arr1里面去
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = { 0 };
char arr2[] = "hello bit";
strcpy(arr1, arr2);
printf("arr1=%s\n", arr1);
return 0;
}
[memset]:内存操作函数http://www.cplusplus.com/reference/cstring/memset/?kw=memset)
void * memset ( void * ptr, int value, size_t num );
指针ptr 整型的值 无符号整型的值num
memset把ptr所指向的那块内存的前num个字节的内容全部设置成我们想要的value值
eg:
#include<stdio.h>
#include <string.h>
int main()
{
char arr[] = "hello world!";
memset(arr, 'x', 5);//把arr空间的前5个字符设置成x
printf("%s\n", arr);//打印结果:xxxxx world!
return 0;
}
自定义函数
- 但,对于一个程序媛来说最最重要的是应该学会自定义函数,因为如果库函数能干所有事情,那还要程序员干什么!!!
- 自定义函数和库函数一样,有函数名、返回值类型和函数参数。但是不一样的是这些都是我们自己来设计。这给程序员一个很大的发挥空间。
eg1:将arr2里面的内容拷贝放到arr1里面去
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = { 0 };
char arr2[] = "abc";
strcpy(arr1, arr2);
printf("%s", arr1);//abc
return 0;
}
函数的组成
函数的组成
eg2:写一个函数可以找出两个整数中的最大值
#include<stdio.h>
//函数的定义
int get_max(int x, int y)//我在main中调用get_max,并传参,让其完成我的请求,得出结果,并将返回结果传给我
{
int z = 0;
if (x > y)
z = x;
else
z = y;
return z;//返回z(返回较大值),其中z和get_max前面的int类型前后呼应
}
int main()//我请求
{
int a = 10;
int b = 20;
//函数的调用
int max=get_max(a,b);//获取a和b的较大值,其中get_max是我自定义的函数
printf("max=%d\n", max);
return 0;
}
eg3: 写一个函数,可以交换两个整型变量的内容
void Swap1(int x, int y)
{
int z = 0;
z = x;
x = y;
y = z;
}
int main()
{
int a = 10;
int b = 20;
printf("交换前:a=%d b=%d\n", a, b);//a=10 b=20
//写一个函数,交换2个整型变量的值
Swap1(a, b);
printf("交换后:a=%d b=%d\n", a, b);//a=10 b=20
return 0;
}
代码分析:
- 函数返回类型的地方写成void表示这个函数不返回任何值,也不需要返回
- 此代码有问题,a和b不会交换内容,因为,a、b的空间和x、y的空间是两个独立的空间,二者空间不一样,所以互不影响
- 原因:swap1在被调用的时候,实参传给形参,其实形参是实参的临时拷贝(只是拷贝了a和b的值,而又给它创建了独立的空间x,y),因此改变形参,不能改变实参。
回忆指针
eg4:
int main()
{
int a = 10;//4个字节的空间
int* pa = &a;//pa就是一个指针变量
*pa = 20;
printf("%d\n", a);//20
return 0;
}
上面一个问题代码应用指针进行交换,此时会交换成功
eg5:
void Swap2(int* pa, int* pb)//pa里面放的是a的地址,pb里面放的是b的地址
{
int z = 0;
z = *pa;
*pa = *pb;
*pb = z;
}
int main()
{
int a = 10;
int b = 20;
printf("交换前:a=%d b=%d\n", a, b);//a=10 b=20
//写一个函数,交换2个整型变量的值
Swap2(&a, &b);
printf("交换后:a=%d b=%d\n", a, b);//a=20 b=10
return 0;
}
代码思路分析如下图:
疑问:设计函数的时候什么情况下传地址?
答:在不改变值得情况下,不需要传地址,在需要改变值的情况下,需要将函数外部(main)和函数内部(swap函数内)之间建立联系,此时需要传地址,只有把地址传过去,函数内部才能通过地址找回来。
函数的参数
- 实际参数(实参):就是真是传给函数的参数,叫实参。实参可以是:常量、变量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时,他们都必须有确定的值,以便把这些值传送给形参。
eg6:
//函数的定义
int get_max(int x, int y)
{
int z = 0;
if (x > y)
z = x;
else
z = y;
return z;//返回z(返回较大值),其中z和get_max前面的int类型前后呼应
}
int main()
{
int m = 0;
int n = 0;
//int max=get_max(m, n);//实参是变量m,n
//int max = get_max(2, 3);//实参是常量
int max = get_max(2 + 3, get_max(2, 7));//实参是表达式和函数
printf("max=%d\n", max);
return 0;
}
- 形式参数(形参):它只有在被调用的时候才会开辟空间,调用完之后就没有了.
- 形式参数就是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。(生命周期进入函数开始,出函数销毁)因此形式参数只有在函数中有效(生命周期的范围)。
对函数的实参和形参进行分析:
代码对应的内存分配如下:
这里可以看到Swap1函数在调用的时候,x,y拥有自己的空间,同时拥有了和实参一模一样的内容。所以我们可以简单的认为:形参实例化之后其实相当于实参的一份临时拷贝。
函数的调用
传值调用:函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。
传址调用:传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式;这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量.
eg7
void Swap1(int x, int y)
{
int z = 0;
z = x;
x = y;
y = z;
}
void Swap2(int* pa, int* pb)
{
int z = 0;
z = *pa;
*pa = *pb;
*pb = z;
}
int main()
{
int a = 10;
int b = 20;
Swap1(a, b);//传值调用
printf("Swap1交换后:a=%d b=%d\n", a, b);
Swap2(&a, &b);//传址调用
printf("Swap2交换后:a=%d b=%d\n", a, b);
return 0;
}
练习
1.写一个函数可以判断100-200之间的素数
#include<stdio.h>
int is_prime(int n)
{
//2到n-1之间的数字
int j = 0;
for (j = 2; j < sqrt(n); j++)
{
if (n%j == 0)
return 0;
}
return 1;
}
int main()
{
int i= 0;
int count = 0;
for (i = 100; i <= 200; i++)
{
//判断i是否为素数
if (is_prime(i) == 1)
{
count++;
printf("%d ", i);
}
}
printf("count=%d\n", count);
return 0;
}
2.写一个函数判断1000到2000之间的闰年
#include<stdio.h>
int is_leap_year(int m)
{
/*if (m % 4 == 0 && m % 100 != 0 || (m % 400 == 0))
{
return 1;
}
else
{
return 0;
}*/
/*if (m % 4 == 0 && m % 100 != 0 )
{
return 1;
}
else if (m % 400 == 0)
{
return 1;
}
else
return 0;*/
return ((m % 4 == 0 && m % 100 != 0) || (m % 400 == 0));
//三种方法均可
}
int main()
{
int y = 0;
int count = 0;
for (y = 1000; y <= 2000; y++)
{
if (is_leap_year(y) == 1)
{
count++;
printf("%d ", y);
}
}
printf("\ncount=%d\n", count);
return 0;
}
3.写一个函数,实现一个整型有序数组的二分查找
#include<stdio.h>
int binary_search(int a[], int k, int s)
{
int left = 0;
int right = s - 1;
while (left <= right)
{
int mid = (left + right) / 2;
if (a[mid] > k)
{
right = mid - 1;
}
else if (a[mid]<k)
{
left = mid + 1;
}
else
{
return mid;
}
}
return -1;//找不到了
}
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int key = 8;
int sz = sizeof(arr) / sizeof(arr[0]);
//找到了就返回找到的位置的下标
//找不到返回-1
//数组arr传参,实际传递的不是数组的本身,传的仅仅是数组首元素的地址。
int ret=binary_search(arr,key,sz);
if (-1 == ret)
{
printf("找不到\n");
}
else
{
printf("找到了,下标是:%d\n", ret);
}
}
4.写一个函数,每调用一次这个函数,就将num值增加1
#include<stdio.h>
void Add(int *p)
{
(*p)++;
}
int main()
{
int num = 0;
Add(&num);//传址操作
printf("%d\n", num);//1
Add(&num);
printf("%d\n", num);//2
Add(&num);
printf("%d\n", num);//3
return 0;
}
函数的嵌套调用和链式访问
函数不可以嵌套定义,但可以嵌套调用
eg8:
#include<stdio.h>
void test3()
{
printf("hehe\n");//hehe
}
int test2()
{
test3();//嵌套调用
return 0;
}
int main()
{
test2();
return 0;
}
eg9:
#include <stdio.h>
void new_line()
{
printf("hehe\n");//三个hehe
}
void three_line()
{
int i = 0;
for (i = 0; i<3; i++)
{
new_line();//调用三次new_line
}
}
int main()
{
three_line();
return 0;
}
链式访问:把一个函数的返回值作为另一个函数的参数
eg10:
#include<stdio.h>
#include<string.h>
int main()
{
int len =strlen("abc");
printf("%d\n", len);//3
//链式访问:strlen的返回值做了printf函数的参数
printf("%d\n", strlen("abc"));//3
return 0;
}
eg11:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = { 0 };
char arr2[] = "bit";
//链式访问:strcpy的返回值做了printf函数的参数
printf("%s\n", strcpy(arr1, arr2));
return 0;
}
eg13:
#include<stdio.h>
int main()
{
//链式访问
printf("%d", printf("%d", printf("%d", 43)));//4321
//printf函数返回值返回的是打印在屏幕上的字符的个数
return 0;
}
函数的声明和定义
函数声明
1.告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但具体是不是存在,无关紧要;
2.函数的声明一般出现在函数的使用之前。要满足先声明后使用;如果函数定义在后面,需要先声明再使用;如果函数定义在前面,就不需要声明了
eg14:
#include<stdio.h>
int main()
{
int a = 10;
int b = 20;
//函数的声明
int Add(int, int);
int c=Add(a, b);
printf("%d\n", c);
return 0;
}
//函数的定义
int Add(int x, int y)
{
return x + y;
}
3.函数的声明一般要放在头文件中。
#include<stdio.h>
#include "sub.h"//相当于int Sub(int x, int y);
#include "Add.h"//相当于int Add(int x, int y);
int main()
{
int a = 10;
int b = 20;
//int c = Add(a, b);
//printf("%d\n", c);//30
int c = Sub(a, b);
printf("%d\n", c);//-10
return 0;
}
注意:函数的定义是指函数的具体实现,交代函数的功能实现。函数定义一般放在.c文件中