C Primer Plus学习笔记(八)- 函数
函数简介
函数(function)是完成特定任务的独立程序代码单元
使用函数可以省去编写重复代码的苦差,函数能让程序更加模块化,提高程序代码的可读性,更方便后期修改、完善
- #include <stdio.h>
- void test(void); // 函数原型
- int main(void)
- {
- printf("Before run function\n");
- test(); // 调用函数
- printf("After run function\n");
- return 0;
- }
- void test(void) // 定义函数
- {
- printf("Running function\n");
- }
运行结果
函数原型(function prototype)告诉编译器函数 test() 的类型,函数原型指明了函数的返回值类型和函数接受的参数类型,这些信息称为该函数的签名(signature)
- void test(void); // 函数原型
圆括号表明 starbar 是一个函数名;第 1 个 void 是函数类型,void 类型表明函数没有返回值;第 2 个 void(在圆括号中)表明该函数不带参数;分号表明这是在声明函数,不是定义函数
函数原型要在使用前声明
函数调用(function call)表明在此处执行函数
- test(); // 调用函数
当执行到这条语句时,会找到该函数的定义并执行其中的内容,执行完 test() 中的代码后,计算机返回主调函数(calling function)继续执行下一行
函数定义(function definition)明确地指定了函数要做什么
- void test(void) // 定义函数
- {
- printf("Running function\n");
- }
函数头中 test() 后面没有分号,告诉编译器这是定义 test(),而不是调用函数或声明函数
识别不了 void 的编译器,要把没有返回值的函数声明为 int 类型
函数中的变量是局部变量,只在该函数中有效,即该变量只属于该函数。如果在其他函数中定义同名的函数,不会引起名称冲突,他们是同名的不同变量
函数参数
声明带形式参数函数的原型
在使用函数之前,要用 ANSI C 形式声明函数原型
- void test(char ch, int num);
当函数接受参数时,函数原型用逗号分隔的列表指明参数的数量和类型
也可以省略变量名
- void test(char, int);
在原型中使用变量名并没有实际创建变量,char 仅代表了一个 char 类型的变量
ANSI C 也接受过去的声明函数形式,即圆括号内没有参数列表
- void test();
定义带形式参数的函数
函数定义从下面的 ANSI C 风格的函数头开始
- void test(char ch, int num)
该行告诉编译器 test() 使用两个参数 ch 和 num,ch 是 char 类型,num 是 int 类型,这两个变量被称为形式参数(formal argument,标准推荐使用 formal parameter),简称形参
和定义在函数中的变量一样,形式参数也是局部变量,属该函数私有
每次调用函数,就会给这些变量赋值
ANSI C 要求在每个变量前都声明其类型,即不能像声明普通变量那样使用同一类型的变量列表
- void test(int x, y, z) // 无效的函数头
- void test(int x, int y, int z) // 有效的函数头
ANSI C 也接受 ANSI C 之前的形式,但是将其视为废弃不用的形式
- void test(ch, num)
- char ch;
- int num;
圆括号中只有参数名列表,而参数的类型在后面声明
普通的局部变量在左花括号之后声明,而上面的变量在函数左花括号之前声明
如果变量是同一类型,这种形式可以用逗号分隔变量名列表
- void test(x, y, z)
- int x, y, z;
调用带实际参数的函数
实际参数(actual argument,简称实参),即实际传给函数的值
形式参数是被调函数(called function)中的变量,实际参数是主调函数(calling function)赋给被调函数的具体值
实际参数可以是常量、变量或者是更复杂的表达式,无论实际参数是何种形式都要被求值,然后该值被拷贝给被调函数相应的形式参数
实际参数是具体的值,该值要被赋给作为形式参数的变量
因为被调函数使用的值是从主调函数中拷贝而来,所以无论被调函数对拷贝数据进行什么操作,都不会影响主调函数中的原始数据
注意:实际参数和形式参数
实际参数是出现在函数调用圆括号中的表达式
形式参数是函数定义的函数头中声明的变量
使用 return 从函数中返回值
关键字 return 后面的表达式的值就是函数的返回值
- #include <stdio.h>
- int test(int);
- int main(void)
- {
- int a = 2;
- int b;
- b = test(a);
- printf("%d\n", b);
- return 0;
- }
- int test(int n)
- {
- int m = 5;
- if (m > n)
- return m;
- else
- printf("n is bigger than m\n");
- }
运行结果
变量 m 属于 test() 函数私有,但是 return 语句把 m 的值传回了主调函数
return 返回值不一定是变量的值,也可以是任意表达式的值
如果函数返回值的类型与函数声明的类型不匹配的话,实际得到的返回值相当于把函数中指定的返回值赋给与函数类型相同的变量所得到的值
一个 int 类型的函数,要返回一个 double 类型的值,就把这个 double 类型的值赋给 int 类型的变量,然后返回 int 类型变量的值
使用 return 语句的另一个作用是,终止函数并把控制返回给主调函数的下一条语句
在函数中可以使用多个 return,用于条件判断
还可以这样使用 return 语句
- return;
这条语句会导致终止函数,并把控制返回给主调函数。因为 return 后面没有任何表达式,所以没有返回值,只有在 void 函数中才会用到这种形式
函数类型
声明函数的时候必须声明函数的类型
带返回值的函数类型应该与其返回值类型相同,而没有返回值的函数应声明为 void 类型
类型声明是函数定义的一部分
函数类型指的是返回值的类型,不是函数参数的类型
ANSI C 函数原型
主调函数把它的参数储存在被称为栈(stack)的临时存储区,被调函数从栈中读取这些参数
主调函数根据函数调用中的实际参数决定传递的类型,而被调函数根据它的形式参数读取值
当 float 类型被作为参数传递时会被升级为 double 类型
如果参数的类型不匹配,编译器会把实际参数的类型转换为形式参数的类型
无参数和未指定参数
一个支持 ANSI C 的编译器会假定用户没有用函数原型来声明函数,它将不会检查参数
为了表明函数确实没有参数,应该在圆括号中使用 void 关键字
- void test(void);
支持 ANSI C 的编译器解释为 test() 不接受任何参数,然后在调用该函数时,编译器会检查以确保没有使用参数
一些函数接受(如,printf() 和 scanf())许多参数
例如对于 printf(),第一个参数是字符串,但是其余参数的类型和数量都不固定。对于这种情况,ANSI C 允许使用部分原型
例如,对于 printf() 可以使用下面的原型:
- int printf(const char *, ...);
这种原型表明,第一个参数是一个字符串,可能还有其他未指定的参数
C 库通过 stdarg.h 头文件提供了一个定义这类(形参数量不固定的)函数的标准方法
递归
C 允许函数调用它自己,这种调用过程称为递归(recursion)
演示递归
- #include <stdio.h>
- void up_and_down(int);
- int main(void)
- {
- up_and_down(1);
- return 0;
- }
- void up_and_down(int n)
- {
- printf("Level %d: n location %p\n", n, &n); // #1
- if (n < 4)
- up_and_down(n + 1);
- printf("LEVEL %d: n location %p\n", n, &n); // #1
- }
运行结果
首先,main() 调用了带参数 1 的 up_and_down() 函数,执行结果是 up_and_down() 中的形式参数 n 的值是 1,所以打印语句 #1 打印 Level 1。然后,由于 n 小于 4,up_and_down()(第 1 级)调用实际参数为 n+1(或 2)的 up_and_down()(第 2 级)。于是第 2 级调用中的 n 的值是 2,打印语句 #1 打印 Level 2。与此类似,下面两次调用打印的分别是 Level 3 和 Level 4
当执行到第 4 级时,n 的值是 4,所以 if 测试条件为假。up_and_down() 函数不再调用自己。第 4 级调用接着执行打印语句 #2,即打印 LEVEL 4,因为 n 的值是 4。此时,第 4 级调用结束,控制被传回它的主调函数(即第 3 级调用)。在第 3 级调用中,执行的最后一条语句是调用 if 语句中的第 4 级调用。被调函数(第 4 级调用)把控制返回在这个位置,因此,第 3 级调用继续执行后面的代码,打印语句 #2 打印 LEVEL3。然后第 3 级调用结束,控制被传回第 2 级调用,接着打印 LEVEL 2,以此类推
每级递归的变量 n 都属于本级递归私有
Level 1 和 LEVEL 1 的地址相同,Level 2 和 LEVEL 2 的地址相同,Level 3 和 LEVEL 3 的地址相同,Level 4 和 LEVEL 4 的地址相同
递归的基本原理
第 1,每级函数调用都有自己的变量
第 2,每次函数调用都会返回一次。当函数执行完毕后,控制权将被传回上一级递归
第 3,递归函数中位于递归调用之前的语句,均按被调函数的顺序执行
第 4,递归函数中位于递归调用之后的语句,均按被调函数相反的顺序执行
第 5,虽然每级递归都有自己的变量,但是并没有拷贝函数的代码
最后,递归函数必须包含能让递归调用停止的语句
尾递归
最简单的递归形式是把递归调用置于函数的末尾,即正好在 return 语句之前
这种形式的递归被称为尾递归(tail recursion),因为递归调用在函数的末尾
尾递归是最简单的递归形式,因为它相当于循环
递归的优缺点
优点是递归为某些编程问题提供了最简单的解决方案
缺点是一些递归算法会快速消耗计算机的内存资源,递归也不方便阅读和维护
编译多源代码文件的程序
UNIX
假设在 UNIX 系统中安装了 UNIX C 编译器 cc(最初的 cc 已经停用,但是许多 UNIX 系统都给 cc 命令起了一个别名用作其他编译器命令,典型的是 gcc 或 clang)
假设 file1.c 和 file2.c 是两个内含 C 函数的文件,下面的命令将编译两个文件并生成一个名为 a.out 的可执行文件
- cc file1.c file2.c
另外,还生成两个名为 file1.o 和 file2.o 的目标文件
如果后来改动了 file1.c,而 file2.c 不变,可以使用一下命令编译第 1 个文件,并于第 2 个文件的目标代码合并:
- cc file1.c file2.o
UNIX 系统的 make 命令可以自动管理多文件程序
Linux
假定在 Linux 系统上安装了 GNU C 编译器 GCC
假设 file1.c 和 file2.c 是两个内含 C 函数的文件,下面的命令将编译两个文件并生成名为 a.out 的可执行文件:
- gcc file1.c file2.c
另外,还生成两个名为 file1.o 和 file2.o 的目标文件
如果后来改动了 file1.c,而 file2.c 不变,可以使用以下命令编译第 1 个文件,并于第 2 个文件的目标代码合并:
- gcc file1.c file2.o
使用头文件
在 UNIX 和 DOS 环境中,#include "hotels.h" 指令中的双引号表明被包含的文件位于当前目录中(通常是包含源代码的目录)
如果使用 IDE,需要知道如何把头文件合并成一个项目
C Primer Plus学习笔记(八)- 函数的更多相关文章
- Go语言学习笔记八: 数组
Go语言学习笔记八: 数组 数组地球人都知道.所以只说说Go语言的特殊(奇葩)写法. 我一直在想一个人参与了两种语言的设计,但是最后两种语言的语法差异这么大.这是自己否定自己么,为什么不与之前统一一下 ...
- 【opencv学习笔记八】创建TrackBar轨迹条
createTrackbar这个函数我们以后会经常用到,它创建一个可以调整数值的轨迹条,并将轨迹条附加到指定的窗口上,使用起来很方便.首先大家要记住,它往往会和一个回调函数配合起来使用.先看下他的函数 ...
- go微服务框架kratos学习笔记八 (kratos的依赖注入)
目录 go微服务框架kratos学习笔记八(kratos的依赖注入) 什么是依赖注入 google wire kratos中的wire Providers injector(注入器) Binding ...
- Matlab学习笔记 figure函数
Matlab学习笔记 figure函数 matlab中的 figure 命令,能够创建一个用来显示图形输出的一个窗口对象.每一个这样的窗口都有一些属性,例如窗口的尺寸.位置,等等.下面一一介绍它们. ...
- matlab学习笔记 bsxfun函数
matlab学习笔记 bsxfun函数 最近总是遇到 bsxfun这个函数,前几次因为无关紧要只是大概看了一下函数体去对比结果,今天再一次遇见了这个函数,想想还是有必要掌握的,遂查了些资料总结如下. ...
- Learning ROS forRobotics Programming Second Edition学习笔记(八)indigo rviz gazebo
中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS forRobotics Pro ...
- python3.4学习笔记(八) Python第三方库安装与使用,包管理工具解惑
python3.4学习笔记(八) Python第三方库安装与使用,包管理工具解惑 许多人在安装Python第三方库的时候, 经常会为一个问题困扰:到底应该下载什么格式的文件?当我们点开下载页时, 一般 ...
- matlab学习笔记13_1 函数返回值
一起来学matlab-matlab学习笔记13函数 13_1 函数返回值 觉得有用的话,欢迎一起讨论相互学习~Follow Me 参考文献 https://blog.csdn.net/qq_36556 ...
- Redis学习笔记八:集群模式
作者:Grey 原文地址:Redis学习笔记八:集群模式 前面提到的Redis学习笔记七:主从复制和哨兵只能解决Redis的单点压力大和单点故障问题,接下来要讲的Redis Cluster模式,主要是 ...
- Java IO学习笔记八:Netty入门
作者:Grey 原文地址:Java IO学习笔记八:Netty入门 多路复用多线程方式还是有点麻烦,Netty帮我们做了封装,大大简化了编码的复杂度,接下来熟悉一下netty的基本使用. Netty+ ...
随机推荐
- MySQL使用FEDERATED engine建立代理表
CREATE TABLE `yndzm` ( `city` varchar(40) DEFAULT NULL COMMENT '市(州)', `county` varchar(60) DEFAULT ...
- jqgrid的scroll参数的使用
scroll参数会影响addJSONData(data)方法的使用 存在scroll参数,addJSONData方法会往表格中追加数据: 不存在scroll参数时,addJSONData方法会覆盖表格 ...
- linux命令:head 命令
head 与 tail 就像它的名字一样的浅显易懂,它是用来显示开头或结尾某个数量的文字区块,head 用来显示档案的开头至标准输出中,而 tail 想当然尔就是看档案的结尾. 1.命令格式: hea ...
- Codeforces Round #374 (Div. 2) A , B , C 水,水,拓扑dp
A. One-dimensional Japanese Crossword time limit per test 1 second memory limit per test 256 megabyt ...
- Java-集合类源码List篇(二)
前言 上篇中,我们分析了ArrayList的常用方法及其实现机制.ArrayList是基于内存空间连续的数组来实现的,List中其实还提供了一种基于链表结构的LinkedList来实现集合.同时多线程 ...
- 解决没有referenced Libraries的方法
- PowerDesigner15生成数据库 同时自动生成字段说明(备注)信息
1.打开Database->Generate Database 2.切换到Format标签页,选中Generate name in empty comment即可生成每个字段的说明(备注)信息 ...
- 我总结的call()与apply()方法的区别
[call()与apply()的区别]在ECMAScript中每一个函数都是function类型(是javascript的基本引用类型)的实例,具有一定的属性和方法.call()和apply()则是这 ...
- 《Advanced Bash-scripting Guide》学习(三):自删除脚本和自读取内容的脚本
本文所选的例子来自于<Advanced Bash-scripting Gudie>一书,译者 杨春敏 黄毅 自删除脚本 #!/bin/rm #自删除脚本 #当你运行这个脚本时,基本上什么都 ...
- 51nod 1189 算术基本定理/组合数学
www.51nod.com/onlineJudge/questionCode.html#!problemId=1189 1189 阶乘分数 题目来源: Spoj 基准时间限制:1 秒 空间限制:131 ...