C语言基础 7. 函数
文章目录
7.1. 初见函数
-
函数:
-
之前学过的函数有printf(), scanf()输入输出函数, 这些都是C语言标准库里面的函数
-
但是现在我们要开始学着写自己的函数, 例如, 素数求和程序当中, 可以将判断该数是否为素数也就是isPrime写成一个函数, 这样主函数main()做的事情就比较简单, 在main()当中的for ()if (isPrime(num1)), 循环的每一步都会调用自己写的函数isPrime(num1)来判断num1是否为素数, 而这个写好的isPrime函数, 将来也可以用到其他的地方, 直接调用即可
-
-
求和:
- 求1到10, 20到30, 35到45的和
for (i = 1; i <= 10; i++) { sum += i; } printf("%d\n", sum); for (i = 20; i <= 30; i++) { sum += i; } printf("%d\n", sum); for (i = 35; i <= 45; i++) { sum += i; } printf("%d\n", sum); //三段几乎一模一样的代码
- "代码复制"code copy是程序质量不良的表现, 也就是说代码质量不好, 将来要是想对代码做修改或者做维护时, 不是只维护一处, 而是很多处
-
函数实现素数求和
#include <stdio.h>
int isPrime(int num1) {
//判断素数的变量
int ret = 1;
//内层循环产生2到num1-1之间的数
//接收这些数的变量
int num2 = 0;
for (num2 = 2; num2 < num1; num2++) {
//num1试除num2
if (num1 % num2 == 0) {
ret = 0;
break;
}
}
return ret;
}
int main() {
//输入mn
int m, n;
scanf("%d %d", &m, &n);
//计数和求和变量
int count = 0;
int sum = 0;
//处理1
if (m == 1) {
m = 2;
}
//外层循环产生m到n之间的数
//定义接收这些数的变量
int num1 = m;
while (num1 <= n) {
if (isPrime(num1)) {
count++;
sum += num1;
}
num1++;
}
printf("%d %d", count, sum);
return 0;
}
- 代码复制实现求和
#include <stdio.h>
int main() {
int i;
int sum = 0;
for (i = 1, sum = 0; i <= 10; i++) {
sum += i;
}
printf("%d到%d的和是%d\n", 1, 10, sum);
for (i = 20, sum = 0; i <= 30; i++) {
sum += i;
}
printf("%d到%d的和是%d\n", 20, 30, sum);
for (i = 35, sum = 0; i <= 45; i++) {
sum += i;
}
printf("%d到%d的和是%d\n", 35, 45, sum); //三段几乎一模一样的代码, "代码复制"code copy是程序质量不良的表现
return 0;
}
- 使用函数对求和函数代码进行优化
#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);
}
int main() {
sum(1, 10);
sum(20, 30);
sum(35, 45);
return 0;
}
7.2. 函数的定义和使用
-
什么是函数?
-
上一节只是看了两个函数的例子, 还没有深入的研究
-
函数是一块代码, 接收零个或多个参数, 做一件事情, 并返回零个或一个值, 刚才的素数返回了一个值, 而累加不返回值
-
可以先想象成数学中的函数: y = f(x), 但是不完全一样
-
-
函数定义:
返回类型 函数名 参数表
函数头: 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)
-
sum(1, 10);
sum(20, 30);
sum(35, 45); //sum当中的值会按顺序依次初始化函数中的参数
-
-
函数的返回:
- 函数知道每一次是哪里调用它, 会返回到正确的地方
-
cheer函数
void cheer() {
printf("cheer\n");
}
int main() {
//调用函数
cheer();
return 0;
}
7.3. return
-
从函数中返回值:
返回类型 int isPrime(int num1) { int ret = 1; int num2 = 0; for (num2 = 2; num2 < num1; num2++) { if (num1 % num2 == 0) { ret = 0; break; } } return ret; // 如果需要返回值, 要使用return关键字 }
-
return:
- 停止函数的执行, 并返回一个值, 交给调用这个函数的地方
- 可以return;
- 也可以return 表达式;
- 一个函数中可以有多个return
- 停止函数的执行, 并返回一个值, 交给调用这个函数的地方
-
返回值:
- 返回值可以赋值给变量
- 可以再次传递给函数
- 甚至可以丢弃
-
没有返回值的函数:
- void 函数名(参数表)
- 不能使用带值的return, 可以没有return, 如果函数有返回值, 则必须使用带值的return
- 调用的时候不能做返回值的赋值
-
max函数
#include <stdio.h>
//max函数
int max(int a, int b) {
int ret = 0;
if (a > b) {
ret = a;
}
else {
ret = b;
}
return ret; // 单一出口理念, 强烈建议使用
}
int main() {
int num1;
int num2;
scanf("%d %d", &num1, &num2);
int maxNum = max(num1, num2);
printf("%d ", maxNum);
return 0;
}
7.4. 函数原型
-
函数先后关系:
- 像这样把sum()函数写在main()函数的上面, 是因为:
-
C的编译器自上而下顺序分析你的代码
-
在看到sum(1, 10);的时候, 它需要知道sum()的样子
-
也就是sum()要几个参数, 每个参数的类型如何, 返回什么类型
-
这样它才能检查你对sum()的调用是否正确
-
如果不知道, 也就是把要调用的函数放到了下面
- 而编译器会猜sum的返回类型是int, 到了下面找到sum时发现它的返回类型竟然是void, 则会报错
-
- 像这样把sum()函数写在main()函数的上面, 是因为:
-
声明:
- 函数声明不是函数, 而是告诉编译器sum()的返回类型和参数表, 这样编译器就会记住sum()的样子, 到了下面遇到sum()时就会判断声明和定义的函数是否一致
-
函数原型:
-
函数头, 以分号";"结尾, 就构成了函数的原型
- void sum(int begin, int end);
-
函数原型的目的是告诉编译器这个函数长什么样子(名称, 参数(数量及类型), 返回类型)
-
函数原型里面可以不写参数的名字, 但是建议写上以增加程序的可读性
- void sum(int , int );
-
-
程序中的错误:
int main(){sum(1, 10);}
void sum(int begin, int end){}
错误信息: warning C4013: “sum”未定义;假设外部返回 int
error C2371: “sum”: 重定义;不同的基类型
错误原因: sum()函数定义在了main()函数的下面, 而编译器会猜sum的返回类型是int, 到了下面找到sum时发现它的返回类型竟然是void
解决办法: 在main()函数的上方声明sum()
int sum(int begin, int end);
void sum(int begin, int end){}
错误信息: 声明与 "int sum(int begin, int end)" (已声明 所在行数:33) 不兼容
错误原因: 声明和定义不一致
解决办法: 改成一致的
#include <stdio.h>
void sum(int begin, int end); // 声明
int main() {
sum(1, 10);
sum(20, 30);
sum(35, 45);
return 0;
}
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);
}
7.5. 参数传递
-
调用函数:
-
如果函数有参数, 调用函数时必须传递给它数量, 类型正确的值
-
可以传递给函数的值是表达式的结果, 包括:
- 字面量, 变量, 函数的返回值, 计算的结果
-
-
类型不匹配?
-
调用函数时给的值与参数的类型不匹配是C语言传统上最大的漏洞
-
编译器总是悄悄地替你把类型转换好, 但是这很可能不是你所期望的
void cheer(int i){}
int main(){cheer(2.4);}
warning C4244: “函数”: 从“double”转换到“int”,可能丢失数据 -
后续的语言, C++/Java在这方面很严格
-
-
传过去的是什么?
-
void swap(int x, int y), 这样的代码能交换a和b的值吗?
-
是不能的, C语言在调用函数时. 永远只能传值给函数
- main()当中swap()只是将ab的值给了参数xy, 但是这ab和xy是完全不一样的
-
-
传值:
-
每个函数都有自己的变量空间, 参数也位于这个独立的空间中, 和其他函数没有关系
-
过去, 对于函数参数表中的参数, 叫做形式参数, 也就是void swap(int a, int b); 调用函数时给的值, 叫做实际参数, 也就是swap(a, b);
-
但是这容易让初学者误会实际参数就是实际在函数中进行计算的参数, 所以不建议继续用这种古老的方式来称呼它们
-
我们认为, 它们是参数和值的关系, void swap(int x, int y);这里面的x和y是参数, 而swap(a, b);里面的a和b是值
-
-
程序中的错误:
void swap(int x, int y) {
int tmp = x;
x = y;
y = tmp;
}
错误信息: 形参“x”的重定义, 形参“y”的重定义
错误原因: 重复定义了参数x, y
解决办法: 将他们前面的变量类型去掉
- swap函数
#include <stdio.h>
void swap(int x, int y);
int main() {
int a = 5;
int b = 6;
swap(a, b);
printf("a=%d b=%d\n", a, b);
return 0;
}
void swap(int x, int y) {
int tmp = x;
x = y;
y = tmp;
}
7.6. 本地变量
-
本地变量:
-
函数的每次运行, 就会产生一个独立的变量空间, 在这个空间中的变量, 是函数的这次运行独有的, 称作本地变量, 也称作局部变量
-
定义在函数内部的变量就是本地变量, 当然, 现在能接触到的变量都是本地变量, 都是定义在main()函数或者自定义函数当中的变量
-
参数也是本地变量, 它们和变量有着同样的生存期和作用域
-
-
生存期和作用域:
-
生存期: 什么时候这个变量开始出现了, 到什么时候它消亡了
-
作用域: 在代码的上面范围内可以访问这个变量(这个变量可以起作用)
-
对于本地变量, 这个两个问题的答案是统一的: 大括号内–代码块
-
-
本地变量的规则:
-
本地变量是定义在代码块内的, 可以是函数的块内, 也可以是语句的块内, 甚至可以随便拉一对大括号来定义变量
-
程序运行进入这个块之前, 其中的变量不存在, 离开这个块, 其中的变量就消失了
-
块外面定义的变量在里面仍然有效
-
块里面定义了和外面同名的变量则掩盖了外面的
-
不能在一个块内定义同名的变量
-
本地变量不会被默认初始化, 参数除外, 参数在进入函数的时候会被初始化
-
#include <stdio.h>
void swap(int x, int y);
int main() {
int a = 5;
int b = 6;
swap(a, b);
if (a < b) {
int a = 0;
int i = 10; //i只限于这个if语句当中
printf("a = %d\n", a);
}
printf("a=%d b=%d\n", a, b); //a和b只在main()函数的变量空间里面有效
return 0;
}
void swap(int x, int y) { //x和y只在swap()函数的变量空间里面有效
int tmp = x;
x = y;
y = tmp;
}
7.7. 函数庶事
-
没有参数时:
- 写成void f(void);, 这个表示没有参数
- 还是void f();, 在传统C中, 它表示f函数的参数表未知, 并不表示没有参数, 这样的形式不建议, main()除外
-
逗号运算符?
-
调用函数时的逗号和逗号运算符怎么区分?
-
调用函数时的圆括号里的逗号是标点符号, 不是运算符, 比如: f(a, b)
-
但是f((a, b))这个就要先把(a, b)这个逗号运算给做了, 所以说这里函数里面是一个参数
-
-
函数里的函数?
- C语言不允许函数嵌套定义
-
main():
-
main()也是一个函数, 它是主函数, 是程序的入口, 程序执行先做一些准备工作, 之后就会调用main()
-
要不要写成main(void)?
- 可以, 没有任何问题
-
return 0;?
- 程序结束之后, 要将这个0返回给调用main()函数的地方, 会返回一小段代码, 这段代码会检查main()返回了一个什么东西, 然后报告给操作系统, 所以这个值对它们是有意义的
-
7.8. PAT
-
07-0. 写出这个数 (20)
-
疑问:
- 1234567890987654321123456789这个数输入之后如何接收?
- 函数法不会
-
读入一个自然数n,计算其各位数字之和,用汉语拼音写出和的每一位数字。
-
输入格式:
每个测试输入包含1个测试用例,即给出自然数n的值。这里保证n小于10100。 -
输出格式:
在一行内输出n的各位数字之和的每一位,拼音数字间有1空格,但一行中最后一个拼音数字后没有空格。 -
输入样例:
1234567890987654321123456789 -
输出样例:
yi san wu -
分析:
-
1.输入n
-
2.正序整数分解
-
3.数数几位数
-
4.n/10^n=digit
-
5.n%=10^n
-
6.10^n/=10 7.
-
n=12345
12345 / 10000 = 1
12345 % 10000 = 2345
10000 / 10 = 1000
2345 / 1000 = 2
2345 % 1000 = 345
1000 / 10 = 100
345 / 100 = 3
345 % 100 = 45
100 / 10 = 10
45 / 10 = 4
45 % 10 = 5
10 / 10 = 1
5 / 1 = 5
5 % 1 = 0
-
-
-
常规方法:
#include <stdio.h>
int main() {
//输入
unsigned long long n;
scanf("%llu", &n);
//由n得到num
int num = 0;
while (n > 0) {
int digit = n % 10;
n /= 10;
num += digit;
}
//保留num的值
int i = num;
//数数几位数
int ten = 1;
while (i > 9) {
i /= 10;
ten *= 10;
}
/*printf("%d", ten);*/
//循环
while (ten > 0) {
//取出num的第一位
int digit = num / ten;
//取出剩余位
num %= ten;
//ten /= 10
ten /= 10;
/*printf("%d ", digit);*/
switch (digit) {
case 0:
printf("ling");
break;
case 1:
printf("yi");
break;
case 2:
printf("er");
break;
case 3:
printf("san");
break;
case 4:
printf("si");
break;
case 5:
printf("wu");
break;
case 6:
printf("liu");
break;
case 7:
printf("qi");
break;
case 8:
printf("ba");
break;
case 9:
printf("jiu");
break;
}
if (ten > 0) {
printf(" ");
}
}
/*printf("hello");*/
return 0;
}
-
07-1. 换个格式输出整数 (15)
-
疑问:
- 写出来了, 但是代码复制现象严重, 写成函数的形式不太会
-
让我们用字母B来表示“百”、字母S表示“十”,用“12…n”来表示个位数字n(<10),换个格式来输出任一个不超过3位的正整数。例如234应该被输出为BBSSS1234,因为它有2个“百”、3个“十”、以及个位的4。
-
输入格式:每个测试输入包含1个测试用例,给出正整数n(<1000)。
-
输出格式:每个测试用例的输出占一行,用规定的格式输出n。
-
输入样例1:
234 -
输出样例1:
BBSSS1234 -
输入样例2:
23 -
输出样例2:
SS123 -
分析:
- 1.输入n
- 2.取出个位数, 循环
- 3.取出十位数, 循环
- 4.取出百位数, 循环
-
-
常规方法:
int main() {
//输入
int n;
scanf("%d", &n);
//取出个位数
if (n > 0) {
int digit1 = n % 10;
//取出十位数
n /= 10;
if (n > 0) {
int digit2 = n % 10;
//取出百位数
n /= 10;
if (n > 0) {
int digit3 = n % 10;
for (int k = 1; k <= digit3; k++) {
printf("B");
}
}
for (int j = 1; j <= digit2; j++) {
printf("S");
}
}
for (int i = 1; i <= digit1; i++) {
printf("%d", i);
}
}
return 0;
}
-
07-2. A+B和C (15)
-
给定区间[-231, 231]内的3个整数A、B和C,请判断A+B是否大于C。
-
输入格式:
输入第1行给出正整数T(<=10),是测试用例的个数。随后给出T组测试用例,每组占一行,顺序给出A、B和C。整数间以空格分隔。 -
输出格式:
对每组测试用例,在一行中输出“Case #X: true”如果A+B>C,否则输出“Case #X: false”,其中X是测试用例的编号(从1开始)。 -
输入样例:
4
1 2 3
2 3 4
2147483647 0 2147483646
0 -2147483648 -2147483647 -
输出样例:
Case #1: false
Case #2: true
Case #3: true
Case #4: false -
分析:
- 1.输入
- 2.循环t次
- 3.每次循环输入
- 4.函数判断a+b和c的关系
- 5.函数返回值, 判断该值
- 6.输出结果
-
-
函数实现:
#include <stdio.h>
//函数声明
int BiDaXiao(int x, int y, int z);
int main() {
int t, a, b, c;
scanf("%d\n", &t);
for (int x = 1; x <= t; x++) {
//每次循环输入
scanf("%d %d %d", &a, &b, &c);
//调用函数
if (BiDaXiao(a, b, c)) {
//输出结果
printf("Case #%d: true\n", x);
}
else {
printf("Case #%d: false\n", x);
}
}
return 0;
}
//函数定义
int BiDaXiao(int x, int y, int z) {
//用于判断a+b和c的关系的变量
int is = 1;
//判断
if (x + y > z) {
is = 1;
}
else {
is = 0;
}
return is;
}
-
07-3. 数素数 (20)
-
疑问:
- 函数法还是不会
-
令Pi表示第i个素数。现任给两个正整数M <= N <= 10^4,请输出PM到PN的所有素数。
-
输入格式:
输入在一行中给出M和N,其间以空格分隔。 -
输出格式:
输出从PM到PN的所有素数,每10个数字占1行,其间以空格分隔,但行末不得有多余空格。 -
输入样例:
5 27 -
输出样例:
11 13 17 19 23 29 31 37 41 43
47 53 59 61 67 71 73 79 83 89
97 101 103 -
分析:
- 1.输入mn
- 2.循环产生m到n之间的数num1变量
- 3.内层循环产生2到num1-1之间的数
- 4.num1试除这些数
- 5.能除尽直接跳出内层循环, 开始下一轮的外层循环
- 6.这些数都试除完之后说明num1是素数
- 7.格式化
-
-
常规方法
#include <stdio.h>
int main() {
//输入
int m, n;
scanf("%d %d", &m, &n);
//计数变量
int count = 0;
//格式化变量
int format = 9;
//产生1到10000之间的数num1
int num1;
for (num1 = 1; num1 <= 10000; num1++) {
//素数变量
int isPrime = 1;
//处理1
if (num1 == 1) {
isPrime = 0;
}
//产生2到num1-1之间的数num2
for (int num2 = 2; num2 < num1; num2++) {
//num1试除num2
if (num1 % num2 == 0) {
isPrime = 0;
break;
}
}
//判断素数
if (isPrime == 1) {
count++;
while (count >= m && count <= n) {
printf("%d", num1);
//格式化
if (count == format + m) {
printf("\n");
format += 10;
}
else printf(" ");
}
}
}
return 0;
}