赞
踩
维基百科中对函数的定义:子程序
在计算机科学中,子程序(英语:Subroutine, procedure, function, routine, method, subprogram, callable unit),是一个大型程序中的某部分代码, 由一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代 码,具备相对的独立性。
一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库。
比如常用的
printf——打印函数
scanf——输入数据
被c语言封装到一起了 库函数(stdio.h)
包含这个库函数
c语言把常用的功能,进行了封装,封装成一个个函数,提供出来大家一起使用。
比如:scanf,printf,strlen,rand,srang,time-----c语言直接提供的(库函数)
c语言并不去直接实现库函数,而是直接提供了C语言的标准和库函数的约定
规定:scanf 功能,名字,参数,返回值 下面会具体介绍
简单的总结,C语言常用的库函数都有:
IO函数 输入/输出函数 scanf printf getchar putchar
字符串操作函数 strlen strcmp...
字符操作函数 islower isupper
内存操作函数 memset memcmp memory-记忆 (计算机:内存)
时间/日期函数 time
数学函数 sqrt pow..
其他库函数
1. 我们知道在我们学习C语言编程的时候,总是在一个代码编写完成之后迫不及待的想知道结果,想 把这个结果打印到我们的屏幕上看看。这个时候我们会频繁的使用一个功能:将信息按照一定的格 式打印到屏幕上(printf)。
2. 在编程的过程中我们会频繁的做一些字符串的拷贝工作(strcpy)。
3. 在编程是我们也计算,总是会计算n的k次方这样的运算(pow)。 像上面我们描述的基础功能,它们不是业务性的代码。我们在开发的过程中每个程序员都可能用的到, 为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序员 进行软件开发。
这里我们简单的看看:www.cplusplus.com
这里arr2[]实际的字符个数(‘0’)是比arr1[]多的 但是我们运行完查看没有看到多余的‘0’
注: 但是库函数必须知道的一个秘密就是:使用库函数,必须包含 #include 对应的头文件。 这里对照文档来学习上面几个库函数,目的是掌握库函数的使用方法。
需要全部记住吗?No
需要学会查询工具的使用:
MSDN(Microsoft Developer Network)
www.cplusplus.com http://en.cppreference.com(英文版)
http://zh.cppreference.com(中文版)
都是类似的文档,大家只看一个即可。英文很重要。最起码得看懂文献。
如果库函数能干所有的事情,那还要程序员干什么?
所有更加重要的是自定义函数。
函数的组成:
ret_type fun_name(para1, * )
{
statement;//语句项
}
ret_type 返回类型
fun_name 函数名
para1 函数参数
但是不一样的是这些都是我们自己来设计。这给程序员一个很大的发挥空间。
我们举一个例子: 拿库函数strlen举例子
实现我们用普通的方法计算两个值中较大的一个值
接着我们可以实现一个同样功能的自定义函数
首先我们看下面的主函数部分 只修改了//计算 行 意为调用函数
对比
之后我们看自定义函数的部分
真实传给函数的参数,叫实参。 实参可以是:常量、变量、表达式、函数等。 无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。
形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内 存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数 中有效。
首先我们实现成函数:交换num1和num2的数值;并进行打印,但是运行后不能完成任务
成功传递1,2
而计算机会用一个新的内存地址来接受这个1.
在这个自定义函数运行结束后,就会自动销毁这个新的内存地址,而原来的值(a)并没有发生变化,而传递的这个参数,就叫做形式参数。
所以我们要用原本的内存地址来进行参数传递,利用指针来传递,这样传递的参数,就叫实际参数
函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。
- #define _CRT_SECURE_NO_WARNINGS 1
- #include <stdio.h>
-
- //写一个函数,每调用一次这个函数,就会将 num 的值增加1。
- void test(int num)
- {
- num++;
- }
-
- int main()
- {
- int num = 0;
- //调用函数
- test(num);
- //二次调用
- test(num);
- //输出num
- printf("%d", num);
- return 0;
- }
答案:0
答对的话大家应该就是已经理解的了,赞!!
还不理解的话看看下面的定义并且已经附上正确的代码了
传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。
这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。
- void test(int* num)//指针接收
- {
- (*num)++;
- }
-
- int main()
- {
- int num = 0;
- //调用函数(并且传递地址)
- test(&num);
- //二次调用
- test(&num);
- //输出num
- printf("%d", num);
- return 0;
- }
函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的。
上代码
- #include <stdio.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;
- }
错误代码
- void test()
- {
-
- int fun()
- {
- printf("错误的");
- }
- }
-
-
-
- int main()
- {
- test();
- return 0;
- }
把一个函数的返回值作为另外一个函数的参数。
- #include <stdio.h>
- #include <string.h>
- int main()
- {
- char arr[20] = "hello";
- int ret = strlen(strcat(arr,"bit"));//strlen函数是计算字符个数的
- printf("%d\n", ret);
- return 0;
- }
不好理解的话再看更简单的
- #include <stdio.h>
- int main()
- {
- printf("%d", printf("%d", printf("%d", 43)));
- //注:printf函数的返回值是打印在屏幕上字符的个数
- return 0;
- }
答案:4321
- #define _CRT_SECURE_NO_WARNINGS 1
- #include <stdio.h>
- //函数定义
- int add(int x, int y)
- {
- return x + y;
- }
-
- int main()
- {
- int a = 0;
- int b = 0;
- //输入
- scanf("%d %d", &a, &b);
- //加法运算
- int c = add(a, b);//函数调用
- //打印
- printf("%d", c);
- return 0;
- }
为避免这种问题 就要在函数第一行加上函数声明
- #ifndef __TEST_H__
- #define __TEST_H__
- //函数的声明
- int Add(int x, int y);
- #endif //__TEST_H__
我们可以把add()函数单调分离开 放在头文件里面 这样可以把函数和主函数区分开。
然后在原来主函数的文件头上应用add.h文件,就可以直接使用文件内的函数add()。
开头记得写上#pragma once用于表示头文件
我们先看一个例子
在公司里写代码,是需要协作分工的,而不是把所有的代码都写在一个文件中。
所以你们要分模块来写,方便协作,最后做整合。
而大家为实现这三个功能每人都写了两个文件
.h就是实现函数的定义
.c就是主函数,可以实现函数的调用
这是add.h文件内部(实现加法)
所以这个步骤就可以在sub.c文件中实现add.h(加法)的功能
同理我们也可以根据需求来包含其他的头文件(add.h,sub.h,div.h)
所以大家明白了吧 这样可以根据自己的需求来使用对应函数,就不会让函数都堆在一起形成混乱
再举个例子
在解决方案下的add文件中右键,选择属性
选择静态库(.lib)
运行代码成功之后我们打开目标目录
程序调用自身的编程技巧称为递归( recursion)。
递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接 调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问 题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程 序的代码量。
老规矩 上代码
接收一个整型值(无符号),按照顺序打印它的每一位。
例如: 输入:1234,输出 1 2 3 4.
当我们需要打印1234的每一位数时。
我们可以分为打印4 3 2 1
而1234%10得到的是4,1234/10得到的是123。
再者123%10得到的是3,123/10得到的是12。
12%10得到的是2,12/10得到的是1。
1%10得到的是1,1/10得到的是0。
之后我们可以把打印1234拆分成两个步骤:递归调用print(123)然后再输出4,以此类推。
- void print(unsigned int n)
- {
- if (n > 9)//判断此处的n是否为两位数
- {
- print(n / 10);//递归传值并且去掉最后一位数
- }
- printf("%d ", n % 10);//输出最后一位数
- }
-
- int main()
- {
- unsigned int num = 0;
- scanf("%d", &num);
- print(num);
- return 0;
- }
主函数输入1234获得num数,进入print()函数
进入函数后传值1234给n,if判断为Turn进入,并且再次调用print()函数
传值123给下一个print(),if判断为Turn进入,并且再次调用print()函数
传值12给下一个print(),if判断为Turn进入,并且再次调用print()函数
传值1给下一个print(),if判断为fales不进入,跳过if()语句直接打印1
返回上一个函数还没有执行完的printf,在屏幕打印了2,打印完后函数结束,继续执行上一个没有执行完的函数。
打印3之后继续执行上一个函数
打印出4,print()函数结束,并且代码也结束了。
首先我们不管限制条件,直接写出答案
- #include <string.h>
-
- int main()
- {
- char arr[] = "abc";
- int len = strlen(arr);
- printf("%d", len);
- return 0;
- }
非常简单
之后我们进行一个小小的升级,不使用strlen函数,自己来编写实现一样的功能。
- int my_strlen(char* s)
- {
- int count = 0;
- while (*s != '\0')
- {
- count++;
- s++;
- }
- return count;
- }
-
- int main()
- {
- char arr[] = "abc";//数组中存放是这样的[a b c \0]
- int len = my_strlen(arr);
- printf("%d\n", len);
-
- return 0;
- }
接下来,就离我们答案更进一步了,之后把限制条件加入。
我们运用递归的思想
首先放入函数my_strlen("abc")
之后判断第一个字符是否为‘\0’,如果不是,去掉第一位数再调用my_strlen("bc")并且+1;
以此类推
再最后字符为‘\0’时,返回0;
实现函数:
- int my_strlen(char* s)
- {
- if (*s == '\0')
- {
- return 0;
- }
- else
- {
- return 1 + my_strlen(s + 1);
- }
- }
-
- int main()
- {
- char arr[] = "abc";//数组中存放是这样的[a b c \0]
- int len = my_strlen(arr);
- printf("%d ", len);
- return 0;
- }
- int factorial(int n)
- {
- if(n <= 1)
- return 1;
- else
- return n * factorial(n-1);
- }
- int fib(int n)
- {
- if (n <= 2)
- return 1;
- else
- return fib(n - 1) + fib(n - 2);
- }
但是我们发现有问题;
使用 fib 这个函数的时候如果我们要计算第50个斐波那契数字的时候特别耗费时间。
使用 factorial 函数求10000的阶乘(不考虑结果的正确性),程序会崩溃。
要重复计算2的48次方次,计算量非常的大
- int count = 0;//全局变量
- int fib(int n)
- {
- if(n == 3)
- count++;
- if (n <= 2)
- return 1;
- else
- return fib(n - 1) + fib(n - 2);
- }
最后我们输出看看count,是一个很大很大的值。
那我们如何改进呢?
在调试 factorial 函数的时候,如果你的参数比较大,那就会报错: stack overflow(栈溢出) 这样的信息。
系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者(死递归),这样有可能导致一 直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢出。
1. 将递归改写成非递归。
2. 使用 static 对象替代 nonstatic 局部对象。在递归函数设计中,可以使用 static 对象替代 nonstatic 局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放 nonstatic 对象的开销,而且 static 对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。
- //求n的阶乘
- int factorial(int n)
- {
- int result = 1;
- while (n > 1)
- {
- result *= n ;
- n -= 1;
- }
- return result;
- }
- //求第n个斐波那契数
- int fib(int n)
- {
- int result;
- int pre_result;
- int next_older_result;
- result = pre_result = 1;
- while (n > 2)
- {
- n -= 1;
- next_older_result = pre_result;
- pre_result = result;
- result = pre_result + next_older_result;
- }
- return result;
- }
1. 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
2. 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
3. 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。