目录
- 引入
- 定义与使用
- 什么是函数?
- 函数的定义
- 函数的调用
- 函数的返回
- 函数的原型
- 函数定义与调用先后关系的讲究
- 函数的原型与声明
- 参数传递
- 类型不匹配
- 传参
- 本地变量
- 变量的生存期和作用域
- 本地变量的规则
- 补充
- 定义无参数的函数
- 逗号运算符
- 函数里的函数
- 奇形怪状
- 关于main函数
引入
求 0-10 和 20-30 和 35-45 的和
#include <stdio.h>
void main()
{
int i, sum;
for (sum = 0, i = 1; i <= 10; i++)
{
sum += i;
}
printf("%d到%d的和是%d\n", 1, 10, sum);
for (sum = 0, i = 20; i <= 30; i++)
{
sum += i;
}
printf("%d到%d的和是%d\n", 20, 30, sum);
for (sum = 0, i = 35; i <= 45; i++)
{
sum += i;
}
printf("%d到%d的和是%d\n", 35, 45, sum);
}
代码复制是程序质量不良的表现!
这是因为以后做代码维护的时候要修改很多处
#include <stdio.h>
void sum(int begin, int end){
int i;
int sum = 0;
for (i = begin; i <= end; i++)
{
sum += i;
}
printf("%d到%d的和是%d\n", begin, end, sum);
}
void main()
{
sum(1, 10);
sum(20, 30);
sum(35, 45);
}
将重复部分提取出来, 方便以后的调用
定义与使用
什么是函数?
函数是一块代码, 接收零个或多个值, 做一件事情, 并返回零个或一个值
函数的定义
void sum(int begin, int end){
int i;
int sum = 0;
for (i = begin; i <= end; i++)
{
sum += i;
}
printf("%d到%d的和是%d\n", begin, end, sum);
}
void 是返回类型
sum 是函数名
int begin, int end 是参数表
花括号内的代码块 称为函数体
函数的调用
函数名(参数值)
注意: ()
起到了重要的作用
+ ()
表示前面的表达式是一个函数并且在调用ta, 而不是一个函数指针
+ 即便没有参数也要()
+ 有参数的话, 需要给出正确的 数量和顺序
+ 这些值会按照顺序给函数参数表中的参数(相当于变量)初始化赋值
函数的返回
函数知道每一次是哪里调用了ta, 会返回到正确的地方
函数的原型
函数定义与调用先后关系的讲究
要在调用函数的上面定义函数, 原因如下:
+ C的编译器以自上而下的顺序分析代码
+ 在遇到调用的函数的时候, ta需要知道其定义
+ 即, 参数的 个数/类型/顺序
+ 这样才能检查调用是否正确
如果定义语句放在调用之下(且不做声明), 其结果要视编译器而定
比如, 如下的代码会发出警告, 在某些严格的编译器上甚至会出现编译失败
#include <stdio.h>
void main()
{
sum(1, 10);
sum(20, 30);
sum(35, 45);
}
void sum(int begin, int end)
{
int i;
int sum = 0;
for (i = begin; i <= end; i++)
{
sum += i;
}
printf("%d到%d的和是%d\n", begin, end, sum);
}
原因: 当编译器读到调用位置时, 会做出猜测, ta猜测
sum
函数的声明是如下的样子
int sum (int, int)
但当ta读到
sum
的定义时发现与之不符, 于是报出了类型冲突的警告
不过有些高级编译器能自行纠正, 仍旧能正确运行代码
联系上述分析, 如果做出如下修改就可以正确运行
#include <stdio.h>
void main()
{
sum(1, 10);
sum(20, 30);
sum(35, 45);
}
int sum(int begin, int end)
{
int i;
int sum = 0;
for (i = begin; i <= end; i++)
{
sum += i;
}
printf("%d到%d的和是%d\n", begin, end, sum);
return sum;
}
不过, 编译器仍旧会报出形如 “这个声明是我自己猜的哈” 这样的warning
函数的原型与声明
函数的原型用于告诉编译器一个函数的长相, 包括:
+ 名称
+ 参数(数量以及类型)
+ 返回类型
函数的声明就是展示函数原型的语句
通俗来讲, 声明就是告诉编译器 “你不用猜了”
#include <stdio.h>
void sum(int begin, int end);
void main()
{
sum(1, 10);
sum(20, 30);
sum(35, 45);
}
void sum(int begin, int end)
{
int i;
int sum = 0;
for (i = begin; i <= end; i++)
{
sum += i;
}
printf("%d到%d的和是%d\n", begin, end, sum);
}
注意:
+ 函数在声明之后, 遇到定义时, 会再次检验两者是否一致
+ 现在的编程习惯是将函数的原型声明写在调用位置函数的外面
+ 原型中可以不用写函数的名称(即便与定义不一样也不会warning), 但一般仍旧会写上(这对于人类阅读者是有意义的)
参数传递
可以传递给函数的值是表达式的结果, 这就包括了:
+ 字面量
+ 变量
+ 函数的返回值
+ 计算的结果
类型不匹配
调用函数时给的值与参数类型不匹配是C语言传统上最大的漏洞
编译器会自作主张地帮你转换类型, 但总会不尽如人意
传参
C语言在调用函数的时候, 永远只能传值给函数
注意: 每个函数都有自己的变量空间
参数也位于这个独立的空间中, 和其他函数没有关系
习惯上, 参数表中的参数称作 “形参” , 调用函数时传入的是 “实参”
但容易产生误解, 误认为把参数传给了函数
因此, 推荐称他们 “参数” 和 “值”
本地变量
函数的每次运行, 就会产生一个独立的 变量空间
在这个空间中的变量是只有这次运行独有的, 称作 本地变量(也可以成为局部变量/自动变量)
之所以有局部和本地的区别是因为英文都是local
之所以叫自动变量, 是因为ta的生存期是自动的
注意:
+ 定义在函数内部的变量就是本地变量
+ 参数也是本地变量
变量的生存期和作用域
生存期 变量开始出现到消亡
作用域 在(代码的)什么范围内可以访问这个变量(即可以其作用)
块 即一对大括号内的范围
#include <stdio.h>
void swap(int x, int y)
{
int t = x;
x = y;
y = t;
}
void main()
{
int a = 5, b = 6;
swap(a, b);
printf("a=%d, b=%d\n", a, b);
}
上述函数的结果是:
a=5, b=6
当进入swap函数的时候:
a
和b
仍旧存在, 即在生存期内
但a
和b
不在作用域内, 无法在当前上下文去访问他们作为生存来说, 他们还在
作为作用来说, 他们不在当前作用域内当返回main函数的时候:
t
和x
和y
都不存在了
因为swap
函数的运行已经结束了
本地变量的规则
- 本地变量是定义在块内的
- 既可以定义在函数的块内
- 也可以定义在语句的块内
语句可以是for和if之类
也可以随意拉一对大括号!
- 程序运行进入这个块之前, 其中的变量不存在
离开这个块, 其中的变量就消失了 - 块外面定义的变量在里面仍旧有效
- 块里面定义了和外面同名的变量, 则以块内的为准(会掩盖外侧的)
- 不能在一个块内定义同名的变量
- 本地变量不会被默认初始化
- 参数在进入函数的时候就被初始化了(一定要给函数参数要求的值 以初始化)
补充
定义无参数的函数
如果写做void f(void)
没有任何问题
在传统C中, 如果写做void f()
, 编译器会认为传参不确定, 而非不传参
好心的编译器又会帮你猜想, 然后把事情搞糟
当然最糟糕的是, 不报错
总之, 写void f(void)
不会有错
关于
int main()
的事儿, 很复杂, 在下面讨论
逗号运算符
之前有提到, 逗号,
也是一种运算符, 那和调用函数时用到的,
如何区分?
+ 调用函数时, 圆括号中的逗号,
是标点符号
+ 在独立的圆括号内, 表示一个运算式, 此时逗号,
是运算符
两个参数: f(a,b)
一个参数: f((a,b))
函数里的函数
C语言不允许函数嵌套定义
可以在函数里放函数的声明, 但不能放ta的BODY
奇形怪状
int i, j, sum(int a, int b);
这里定义的sum 函数返回为 int
不算错, 当然不推荐
return (i)
返回 i 值而已
但会误使人认为return是一个函数, 因此也不推荐
关于main函数
- 首先,
int main()
也是一个函数, ta被称为程序的入口 - 写成
int main(void)
不会有错 - return 的值是有意义的!!!
在Windows中, 用bat执行这一C语言程序, 会得到return的值
如果用if errorlevel 1...
就可以了解错误原因一般来说, 返回0表示函数正常的运行了
其他非零返回值用来表明错误原因