只要你愿意 开始总比放弃好。 Roman.
愿我们都有自己的目标并正在为其不懈努力。
---------------------------------------------------------------------------------------
一、什么是函数?
函数其实就是子程序。
子程序:负责完成某项特定任务,具有相对独立性。 一般会有参数及返回值,提供对过程的封装和对细节的隐藏。
二、C语言中函数的分类
一)库函数
1、可在网站www.cplusplus.com 上进行查找
1)界面组成:function:函数-功能 函数原型(即:语法结构) Parameters:形式参数 return value:返回值 example:函数的使用举例
2)小写null 一般表示'\0'
3)举例:
- strcpy:char* strcpy(char* destination, const char* resource)
- 拷贝函数时 ‘\0’ 也被拷贝
- 该函数包含在头文件<string.h>中
--------------------------------------------------
- memset 内存设置
- void* memset(void* ptr, int value, size_t num) 将ptr 指向的内存块的前num个字节的内容设置为指定的value ptr是返回值
- size_t 实际上是 unsigned int 类型
- 通过该函数,可以将数组的前 num个字节改变为 value
- 设置内存时是以字节为单位的
- 每个字节的内容都被设置为一样的 value
- 如果想要从某个位置开始修改而不是首位开始,则将 ptr 处改为数组名+数字 如:memset(arr+2, 'x',3)
- 头文件是<string.h>
2、C语言常用库函数:IO函数、字符串操作函数、字符操作函数、内存操作函数、时间/日期函数、数学函数、其他库函数
3、使用库函数,必须包含对应的头文件
4、学会如何使用库函数:查询工具
MSDN www.cplusplus.com
http://en.cppreference.com(英文版) http://zh.cppreference.com(中文版)
二)自定义函数
1、与库函数一样,都有:函数参数、函数名、返回值类型
2、实例:写一个函数实现交换两个数的值
//写一个函数实现交换两个数的值
//函数参数名可以省略
#include<stdio.h>
void swap(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
int main()
{
int a = 0;
int b = 0;
printf("请任意输入两个整数的值:\n");
scanf("a=%d b=%d", &a, &b);
swap(&a, &b);
//要通过函数改变外部变量的值,必须要传入地址,否则只改变函数内部的数值,对外部变量无影响
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
*******************************
当实参(以值得形式)传给形参时,形参是实参的一份临时拷贝(拷贝了数据,但是地址是新开辟的),对形参的修改不会影响实参。
三、函数的参数
一)形式参数(形参)
1、形式参数:是指函数名括号内的变量,因为形式参数只有在函数被调用时才实例化(即:在函数被调用时才分配内存单元),所以叫形式参数
2、形式参数在函数被调用完后会自动销毁,因此形式参数只在函数中有效
3、形参实例化之后相当于实参的一份临时拷贝
二)实际参数(实参)
1、真实传给函数的参数叫做实参
2、实参可以是:常量、变量、表达式、函数等
3、无论实参是何种类型,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参
*****************************************
注意:
参数传递需要指针的情况:函数形参的变化要能够操纵main函数中的实参的变化
而如果仅仅只是比较大小等,不需要改变实参的值,只需要在传参时形参得到实参的具体值就好,则直接传送参数就好
所以:传递地址的功能比传递数值的功能更加强大。
四、函数的调用
一)传值调用
函数的实参和形参分别占有不同的内存块,对形参的修改不会影响实参
二)传址调用
1、传址调用是把函数外部创建的变量的地址传给函数参数的一种调用函数的方式
2、这种传参方式可以让函数与函数外面的变量建立起真正的联系,也就是函数内部可以直接操纵函数外部的变量
*****************************************************************************练习
练习:
//1. 写一个函数可以判断一个数是不是素数。
#include<stdio.h>
#include<math.h>
//判断是否为素数
//若为素数则输出1 不是素数则输出0
int is_prime_number(int n)
{
int i = 0;
for (i = 2; i <= sqrt(n); i++)
{
if (0 == n % i)
{
return 0;//return 0 ;功能强大 直接返回 退出函数
}
}
return 1;
}
int main()
{
int n = 0;
printf("请任意输入一个整数:\n");
scanf("%d", &n);
int ret = is_prime_number(n); //注意命名规则
if (1 == ret)
{
printf("%d是素数\n", n);
}
else
{
printf("%d不是素数\n", n);
}
return 0;
}
//2. 写一个函数判断一年是不是闰年。
#include<stdio.h>
int IsLeapYear(int y)
{
return (((0 == y % 4) && (y % 100 != 0)) || (0 == y % 400));//注意写法
}
int main()
{
int n = 0;
printf("请任意输入一个年份:\n");
scanf("%d", &n);
int tmp = IsLeapYear(n);
if (0 == tmp)
{
printf("%d不是闰年\n", n);
}
else
{
printf("%d是闰年\n", n);
}
return 0;
}
//3. 写一个函数,实现一个整形有序数组的二分查找。
#include<stdio.h>
//二分查找
int binary_search(int* num, int sz, int n)
{
int left = 0;
int right = sz - 1;
while (left <= right)
{
int mid = left + (right - left) / 2;
if (num[mid] < n)
{
left = mid + 1;
}
else if (num[mid] > n)
{
right = mid - 1;
}
else
{
return mid;//找到则输出下标mid
}
}
return 0;//否则输出0
}
int main()
{
//首先有一个整型有序数组
int num[8] = { 2,6,8,12,26,28,80,82 };
//查找的数字
int n = 0;
printf("请输入你想要查找的数字:\n");
scanf("%d", &n);
int sz = sizeof(num) / sizeof(num[0]);
//注意:计算数组长度必须在函数外面
// 因为数组传参传入的是首元素地址,则其sizeof大小是4 or 8
//二分查找函数
int ret = binary_search(num, sz, n);
if (0 == ret)
{
printf("找不到\n");
}
else
{
printf("找到了 下标为:%d\n", ret);
}
return 0;
}
//4. 写一个函数,每调用一次这个函数,就会将 num 的值增加1。
#include<stdio.h>
int Add(int n)
{
return (n+1);
}
int main()
{
int num = 0;
int ret = Add(num);
printf("%d\n", ret);
return 0;
}
- return 0; 功能比 break 更强大。 return 0; 就此返回,函数直接结束
- 先写函数怎么使用--测试 测试驱动开发 test driven development
- 写函数时,要确保函数功能足够单一:打印与返回要区分开,只打印or只返回
- 命名习惯:如:is_leap_year() or IsLeapYear()
- 注意:数组传参传入的不是整个数组(整个数组就会浪费很多空间),传入的是首元素地址 所以,在函数中计算 sizeof(数组名)相当于计算sizeof(类型*),即大小为4 or 8
- 故:不能在函数内计算数组长度
五、函数的嵌套调用和链式访问
一)函数的嵌套调用
函数可以嵌套调用,但是不能嵌套定义
二)函数的链式访问
1、把一个函数的返回值作为另一个函数的参数
2、实例:
***注:
- printf 的返回值是所打印的字符的个数
- 空格也算是字符
六、函数的声明和定义
一)函数的声明
1、函数需要经历: .c 编译 链接 .exe 在该过程中,是从上到下依次扫描的
2、若未在函数调用前定义该函数而是直接调用,则会出现函数未声明的警告,要解决该问题就需要在函数调用前进行声明
3、声明: 返回值类型 函数名 (参数类型) 参数名可以省略
4、函数具体存不存在,函数声明不能决定
5、函数要 先声明后使用
6、函数声明一般放在头文件中
二)函数的定义
是指函数的具体实现,交代函数的功能的实现
三)补充
1、注意:企业一般会使用声明和定义作为一个模块
如:Add 模块包含 Add.c (放入Add函数的定义) 和 Add.h (放入Add函数的声明)
在main函数中进行引用即可: #include"Add.h"
2、为什么要把函数声明和定义区分开?
1)为了隐藏函数的具体实现手段(代码),只是能够让别人能够使用该函数
2)如何做到不暴露代码却可以使用?
如果想要不暴露代码,就将函数编译为静态库
(以Add模块为例)
①(该步骤在写好Add(函数)模块内进行)(在写好的Add.c文件中)右击 解决方案下方的函数名 --属性--常规--配置类型--静态库--确定
项目栏上的 生成--生成解决方案 生成了Add.lib文件
②将Add.h 以及 Add.lib 文件发送给别人即可使用该模块:要将该模块均拷贝到需要使用该模块的.c文件的同一层次上
then 头文件--添加--现有项--Add.h文件
在.c文件中 #include 下方写入 #pragma comment(lib,"Add.lib") //导入静态库
还要在该.c文件中引入头文件#include"Add.h"
3、在函数声明的头文件.h中一般会写为:
#ifndef __TEST_H__
#define __TEST_H__
//函数声明
int Add(int x, int y);
#endif //__TEST_H__
七、函数递归
一)相关知识点
1、函数递归:是程序调用自身的编程技巧,是一种算法
2、注意会出现的一种错误: Stack overflow 栈溢出
3、解决多次重复计算问题,转化为与原问题相似的规模较小的问题
4、每一次递归完成后都会进行栈区的释放
5、不能死递归,要有条件限制来结束递归,否则会造成栈溢出
6、递归的两个必要条件:
- 存在限制条件,当满足这个限制条件时,递归便不再继续
- 每次递归调用后,会越来越接近该限制条件
7、实例
1)写一个函数,要求按顺序输出一个整数的各个位
// 1)写一个函数,要求按顺序输出一个整数的各个位
#include<stdio.h>
void Order(int n)
{
if (n > 9)
{
Order (n / 10);
}
printf("%d ", n % 10);
}
int main()
{
int n = 0;
printf("请任意输入一个整数:\n");
scanf("%d", &n);
//函数
Order(n);
return 0;
}
*************************************************
2)编写函数求字符串长度,要求不创建临时变量
// 2)编写函数求字符串长度,要求不创建临时变量
#include<stdio.h>
int GetLen(char* arr)
{
while (*arr != '\0')
{
return 1 + GetLen(arr + 1);
}
return 0;
}
int main()
{
char arr[] = "welcome to the beauty.";
//计算字符串长度
int ret = GetLen(arr);
printf("Length=%d\n", ret);
return 0;
}
二)递归与迭代、
1、循环也是一种迭代
2、练习
1)求n的阶乘。(不考虑溢出)
// 1)求n的阶乘。(不考虑溢出)
#include<stdio.h>
int Fac(int n)
{
if (n >= 1)
{
return (n * Fac(n - 1));
}
return 1;
}
int main()
{
int n = 0;
printf("请任意输入一个整数:\n");
scanf("%d", &n);
//函数递归
int ret = Fac(n);//阶乘
printf("Fac(%d)=%d\n", n, ret);
return 0;
}
*****************************************************
2)求第n个斐波那契数。(不考虑溢出)
注意:此题中的count 是全局变量 以及其使用
//2)求第n个斐波那契数。(不考虑溢出)
//菲波那切数列:第一二个数:1 1 后面每个数均为前两个数相加之和
#include<stdio.h>
int count = 0;//注意创建变量的位置 全局变量
int Fib(int n)
{
if (3 == n)
{
count++;//在该函数进行调用 改变数值
}
if ((1 == n) || (2 == n))
{
return 1;
}
return (Fib(n - 2) + Fib(n - 1));
}
int Sum(int n)
{
int a = 1;
int b = 1;
int sum = 0;
if ((1 == n)||(2 == n))
{
return 1;
}
while (n >= 3)
{
sum = a + b;
a = b;
b = sum;
n--;
}
return sum;
}
int main()
{
int n = 0;
printf("请输入任意一个整数:\n");
scanf("%d", &n);
//递归:但是递归算法有一个弊端:会对同一个数字的计算重复多次,效率低
int ret = Fib(n);
printf("Fib(%d)=%d\n", n, ret);
//普通算法:
int sum = Sum(n);
printf("Sum(%d)=%d\n", n, sum);
printf("3count=%d\n", count);
//注意创建该变量是在所有函数以外,但是在所有函数中均可以被调用且保留值
return 0;
}
//求菲波那切数列 一般不用递归,存在缺陷:有些数计算经历次数太多,效率降低
**经典题目:
1、汉诺塔问题
2、青蛙跳台阶问题
----------------------一个人所有的愤怒都来自于对自己无能的痛苦。---------------------------