摘要:语言在设计中考虑了函数的高效性和易用性两个原则。在语言中,最常见的当属函数了。以上就是一个函数,它被称为语言的入口函数,或者主函数。例如和都是函数名。形式参数当函数调用完成之后就自动销毁了。
函数可以把大的计算任务分解成若干较小的任务,然后通过调用的方式达到代码复用;一个逻辑不写多遍,减少代码维护成本。
调用函数的一方不需要了解函数的具体实现,对于它来说,这部分是一个“黑盒子”,从而使得程序结构更加清晰。
C语言在设计中考虑了函数的 高效性 和 **易用性 **两个原则。
函数的实现应该尽量简短,因为函数可以套函数,一个程序应该尽量由许多小的函数组成,而不是少量较大的函数组成。
在刷题的过程中,系统会给我们事先提供一个函数让我们来实现,而它则是调用函数的一方。
在C语言中,最常见的当属main
函数了。
int main(){ printf("5201314/n"); return 0;}
以上就是一个函数,它被称为C语言的 入口函数,或者 主函数。
所有程序执行都是从这个函数开始的,以它为例,我们引出函数的一些基本概念。
如图:
通过这个图,我们类比main()
这个函数,它的:
返回类型 是int
32位整型,
函数名 为main
,
参数列表 为空
,
函数体 为 printf("5201314/n");
,
返回值 为0
。
函数的返回类型可以是任意类型;
例如:整型int
,浮点型float
,字符型char
,自定义类型等待;
返回类型 和 返回值 是配套的,当返回类型为void
时,函数内部的返回值可以写return
;也可以省略不写。
函数名可以类比我们自己的名字。是给函数调用方用的。
例如:main
和printf
都是函数名。
函数的参数列表必须用()
括起来,参数是函数需要处理的数据;
例如:
printf("hello/n");
这一段代码用来输出字符串,"hello/n"
就是一个参数,参数类型是字符串。
下面会详细解释:printf
;
函数内部就是你可以任意发挥的部分,也就是函数的核心逻辑部分,可以是各种语句的组合;当然也可以是另一个函数,也就是函数说,函数是支持嵌套的。
函数的返回值则表示:这个函数最后返回给调用方的数据;
如果返回值的类型和函数的返回类型不一致,则会进行强制类型转换;前提是能够强制转换的情况下。
为什么会有库函数?
我们学习C语言编程的时候,总是在一个代码编写完成之后迫不及待的想知道结果, 把这个结果打印到我们的屏幕上看看。这个时候我们会频繁的使用一个功能:将信息按照一定的格式打印到屏幕上(printf
)。
在编程的过程中我们会频繁的做一些字符串的拷贝工作(strcpy
)。
编程是我们也计算,总是会计算n的k次方这样的运算(pow
)
像上面我们描述的基础功能,它们不是业务性的代码。
我们在开发的过程中每个程序员都可能用的到, 为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序员进行软件开发。
这里推荐一个网站:cplusplus.com
简单的总结,C语言中,我们常用的库函数都有:
IO函数:输入和输出函数
字符串操作函数:比如求字符串长度的strlen
函数
字符操作函数:比如判断字符的大小写,将小写字母转化为大写字母
内存操作函数:内存复制、查找等操作
时间/日期函数:获取时间等
数学函数:开平方sqrt
等函数
等等…
我们参照文档,学习几个库函数;
strcpy
从上面的库函数网站可以找到strcpy的具体用法:
其实strcpy
就是字符串拷贝的意思
因此在使用这个函数的时候,我们需要向函数参数传入destination(目标数组)和source(源数组)两个字符串,同时该函数的返回值是char*
,它会将拷贝后的destination(目标数组)起始地址返回给我们。
strcpy
时要引用头文件#include
官方写法:
char* strcpy(char* strDestination, const char* strSource);
即第一个参数是目的地地址,第二个参数源地址;
代码示例:
#include #include int main(){ char arr1[] = "Fighting Boy"; char arr2[] = "AAA"; strcpy(arr1, arr2);//将arr2数组的内容拷贝到arr1数组中 printf("arr1 = %s/n", arr1);//arr1 = "AAA" printf("arr2 = %s/n", arr2); return 0;}
运行结果:
注意:
这里的复制其实并不是真正的复制,准确的说是覆盖!
即把arr2的所有内容(包括/0
)都覆盖到arr1
对应位置;
也就是说,虽然打印出来arr1
是AAA
,但是实质上arr1
等于AAA/0ting Boy
;
我们可以运行调试一下:
补充:
/0
结束。如果源字符串没有/0
,就会一直拷贝源字符串地址后面的所有内容,直到找到值为0
。/0
拷贝到目标数组中。const
修饰(关于const
的用法后续会写一篇博客详解)
注意: 使用库函数,必须包含 #include
对应的头文件。
如果库函数能干所有的事情,那还要程序员干什么?
所有更加重要的是自定义函数。
自定义函数和库函数一样,有函数名,返回值类型和函数参数。
但是不一样的是这些都是我们自己来设计。
自定义函数的定义语法为:
ret_type fun_name(para1, *){ statement;//语句项 return 返回值;//该返回值与返回类型必须相同,如果是void型函数,则不需要返回值,因此可以不写。}ret_type 返回类型fun_name 函数名para1 函数参数
在使用函数的过程中,我们用函数名(函数参数)
的形式来调用自定义函数;
由于一般函数在调用完以后产生一个返回值(比如一个两个数相加的加法函数,实现两个数相加以后返回两个数的和),因此我们可以用一个变量来接收这个返回值。
例题:写一个函数可以找出两个整数中的最大值。
代码示例:
int get_max(int x, int y){ return (x > y) ? (x) : (y); //三目操作符,在操作符中讲过,如果x>y则返回x,反之则返回y;}int main(){ int a = 20; int b = 10; int max = get_max(a, b);//定义一个max变量,用来接受最大值; printf("max=%d/n", max);}
运行结果:
在程序运行过程中,给get_max
这个函数传入a、b
这两个参数,函数调用完后会返回a和b中的最大值;
因此可以用max
来接收这个返回值。当然也可以不用接收,因为在函数运行完以后,get_max(a, b)
就相当于这个返回值,该返回值可以当做printf
的参数直接进行打印操作。
例题:写一个函数可以交换两个整形变量的内容。
void Swap(int* pa, int* pb) //我们只需要交换a和b当中的内容,不需要返回值,所以用void{ int t = 0;//定义一个临时变量用于交换 t = *pa; *pa = *pb; *pb = t;}int main(){ int a = 1; int b = 2; printf("交换前: a=%d b=%d/n", a, b); Swap(&a, &b); printf("交换前: a=%d b=%d/n", a, b); return 0;}
运行结果:
分为:
真实传给函数的参数,叫实参。
实参可以是:常量、变量、表达式、函数等。
无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。
代码示例:
void Swap1(int x, int y){ int tmp = 0; tmp = x; x = y; y = tmp;}void Swap2(int* px, int* py) //我们只需要交换a和b当中的内容,不需要返回值,所以用void{ int tmp = 0; tmp = *px; *px = *py; *py = tmp;}int main(){ int a = 1; int b = 2; 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;}
运行结果:
Swap1
和Swap2
函数中的参数 x,y,px,py
都是形式参数。
在main函数中传给Swap1
的a,b
和传给Swap2函数的&a,&b
是实际参数。
这里我们对函数的实参和形参进行分析:
可以看出:
实参a、b与形参x、y不是同一空间
&a:0x00d3fa28&b:0x00d3fa1c &x:0x00def944&y:0x00d3f948
这里可以看到Swap1
函数在调用的时候,x,y
拥有自己的空间,同时拥有了和实参一模一样的内容。
简单来说:形参实例化之后其实相当于实参的一份临时拷贝。
分为:
传值调用
传址调用
例题:写一个函数来实现对两个数的交换
void Swap1(int x, int y){ int tmp = 0; tmp = x; x = y; y = tmp;}int main(){ int a = 1; int b = 2; Swap1(a, b); printf("Swap1: a=%d b=%d/n", a, b); return 0;}
运行结果:
由于形参并不影响实参,函数在调用过程中只是对形参x,y进行了交换,并没有影响到a,b;
并且函数在调用结束以后,x,y就已经被销毁了。
调试看一下:
从监视中就可以看出,x,y
的地址和a,b
不同,他们相当于一块独立的空间,自身的改变并不会影响a,b
。
还是看这个例题,只不过我们稍微做点改变
void Swap2(int* px, int* py){ int tmp = 0; tmp = *px; *px = *py; *py = tmp;}int main(){ int a = 1; int b = 2; Swap2(&a, &b); printf("Swap2: a=%d b=%d/n", a, b); return 0;}
运行结果:
通过这种方式可以使得变量进行真正的交换。
还是调试看一下:
通过监视可以看出,px、py
就是一个指针,其中放的就是a,b
的地址,*
是解引用操作符,它可以通过地址找到地址中存放的变量值;
比如px
就是a
的指针,它的值是a的地址,对px
解引用就可以找到a地址中存放的变量1,然后我们就可以对变量1进行操作了。
函数内部的形参只需要借用函数外部实参的值的时候用传值调用,比如求两个数的较大值。
当函数内部需要对函数外部变量进行操作时用传址调用,比如交换两个数。
关于函数的形参、实参和传值、传址,可以看我之前写的这篇文章:重点详解函数的形参和实参、传值和传址
函数和函数之间是可以互相调用的。
代码示例:
void fun2(){ printf("hello/n");}void fun1(){ int i = 0; for (i = 0; i < 3; i++) { fun2(); }}int main(){ fun1(); return 0;}
运行结果:
我们通过main
函数调用fun1
,通过fun1
调用三次fun2
,这就是函数的嵌套调用。
注意:函数可以嵌套调用,但是不能嵌套定义。
把一个函数的返回值作为另外一个函数的参数。
int get_max(int x, int y){ return (x > y) ? x : y;}int main(){ int num1 = 10; int num2 = 20; int max = get_max(num1, num2); printf("max=%d/n", get_max(num1, num2));}
运行结果:
下面我们看一个有趣的代码:
int main(){ printf("%d", printf("%d", printf("%d", 43))); //结果是啥? //注:printf函数的返回值是打印在屏幕上字符的个数 return 0;}
运行结果:
这个程序实际上就是用printf
的返回值作为printf
的参数;
因此想要弄明白这个程序,我们得先知道printf
的返回值;
通过查找printf的用法可以知道printf
返回值:
所以printf
返回值是写入的字符总数,也就是字符的个数。
最内层的printf
打印43;
第二层的printf
打印的是最内层printf的返回值,也就是43
这个内容的元素个数2;
最外层打印的是printf("%d",2)
的返回值,返回值是其元素个数1;
所以会打印出4321
这四个数。
函数的定义是指函数的具体实现,交待函数的功能实现。
test.h
的内容
#ifndef __TEST_H__#define __TEST_H__//函数的声明int Add(int x, int y);
test.c
的内容
#include "test.h"//函数Add的实现int Add(int x, int y){ return x + y;}
这种分文件的书写形式,在后期写三字棋和扫雷的时候,就可以用分模块来写。
我之前写过一篇三子棋的小游戏,就是用这种分模块的方式来写的;
这里我们通过代码来详解函数的递归
接受一个整型值(无符号),按照顺序打印它的每一位。
例如: 输入:1234,输出 1 2 3 4.
思考一下:
我们想要从高位向低位输出,就必须要依次获取:最高位到最低位的数字;
因此我们可以这1234
每次除以10,并把1234 / 10
的结果进行判断,看其是否小于10;
如果小于10则不再进行除以10的操作,这样我们就可以得到最高位1
了;
为了获取其他位的数字,我们可以在每次除以10之前进行取模10的操作,得到剩下的位。
代码示例:
void print(int n){ if (n > 9) { print(n / 10); } printf("%d ", n % 10);}int main(){ int num = 1234; print(num); //这里的print是我们自己定义的函数,不是库函数printf return 0;}
运行结果:
关于这道题,我之前也做过详解,并且画了图,可以去看下:深入理解C语言中函数的递归算法
有些问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销
其实后面关于函数的嵌套、递归、迭代都只是写了一些知识点,所以还是打算通过例题的方式来详解。
更多的一些重点函数(比如strlen
,strcmp
,strcat
等)的实现,我会在之后多带带拿出来说明。
函数这一章的很多东西都需要练题+深刻理解,为后续的知识打下基础!
等例题剖析完了,会附上链接!
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/124776.html
摘要:二什么是文件磁盘上的文件就是文件。文件指针变量定义是一个指向类型数据的指针变量。表示向何种流中输出,可以是标准输出流,也可以是文件流。文件结构体指针,将要读取的文件流。 ...
摘要:大家好,我是冰河有句话叫做投资啥都不如投资自己的回报率高。马上就十一国庆假期了,给小伙伴们分享下,从小白程序员到大厂高级技术专家我看过哪些技术类书籍。 大家好,我是...
目录 一、什么是C语言? 二、第一个C语言程序 代码 程序分析 程序运行 一个工程中出现两个及以上的main函数 代码 运行结果 分析 三、数据类型 数据各种类型 为什么会有这么多的数据类型? 计算机单位 各个数据类型的大小 注意事项 数据类型的使用 四、变量和常量 变量的分类 变量的使用 变量的作用域和生命周期 常量 五、字符串+转义字符+注释 字符串 转义字符 注释 六、选择语句 ...
阅读 1697·2023-04-26 01:02
阅读 4837·2021-11-24 09:39
阅读 1802·2019-08-30 15:44
阅读 2872·2019-08-30 11:10
阅读 1782·2019-08-30 10:49
阅读 982·2019-08-29 17:06
阅读 607·2019-08-29 16:15
阅读 902·2019-08-29 15:17