C语言完全听懂系列 - day5

  • 我们之前实现了一段代码,可以计算狗狗的岁数:
#include <stdio.h>
int main() {
    int dog_age;
    printf("Input the age of dog:\n");
    scanf("%d", &dog_age);
    float human_age;
    if (dog_age <= 2) {
        human_age = dog_age * 10.5;
    } else {
        human_age = 2 * 10.5 + (dog_age - 2) * 4;
    }
    printf("%f", human_age);
}
  • 如果我们想每次运行计算两只狗的岁数呢?最简单的办法就是把代码复制一遍:
#include <stdio.h>
int main() {
    //1
    int dog_age;
    printf("Input the age of dog:\n");
    scanf("%d", &dog_age);
    float human_age;
    if (dog_age <= 2) {
        human_age = dog_age * 10.5;
    } else {
        human_age = 2 * 10.5 + (dog_age - 2) * 4;
    }
    printf("%f", human_age);

    //2
    int dog_age;
    printf("Input the age of dog:\n");
    scanf("%d", &dog_age);
    float human_age;
    if (dog_age <= 2) {
        human_age = dog_age * 10.5;
    } else {
        human_age = 2 * 10.5 + (dog_age - 2) * 4;
    }
    printf("%f", human_age);
}

然后我们就遇到了我们编程生涯的第一个报错:
image

  • redeclaration of blabla的大概意思是这个变量重复声明了,在同一个代码块内(被同一个大括号括起来的代码),我们取的变量名应该是唯一的,毕竟重名的东西总是要被区分一下的。所以我们给第二段代码的两个变量名,后面分别加个2,现在代码没问题了。
  • 变量名取名的规则是:
    • 仅由字母,数字,下划线组成。
    • 不可以与关键字重名。关键字就是像if, int, float这种,有特殊含义的单词,编译器需要区分。
    • 必须以字母或者下划线开头。
      • 想一下为什么不能以数字开头?
  • 我们在给变量取名的时候约定一个习惯:单词只用小写,单词之间用下划线区分,比如dog_age。这个习惯跟编译器就没关系了,单纯是为了代码可读性更好。这种习惯只是三大流派之一,你之后还会遇到不同流派的程序员,都是些没什么意义又永远没办法统一的日经争议话题了,暂时先跟我保持一致吧。
  • 假如说我们想算四只狗呢?那代码还要再复制两遍,然后变量名要改出3和4,情况看起来不太对了;假如说算每只狗的岁数的时候,dog_age<=3的部分要乘10.5,而不再是2了呢?那四处代码都要改了。这样写的话,代码的屎山味就呼之欲出了。客户都是一天一个样张嘴就要改的**,需求变动是非常频繁的,所以我们就要防着点客户,让代码尽量不怕改,要不然客户改需求我们改代码,改着改着改出bug了,锅就全到我们自己头上了,还会被客户吊我们技术不行,代码全是bug。
  • 所以重构上面这段代码很简单,把“算人的岁数”这件事,抽出来只写一份,然后每次想算的时候,都“调用”这同一处代码,就算要改,也只改这一份:
#include <stdio.h>

float get_human_age(int dog_age) {
    float human_age;
    if (dog_age <= 2) {
        human_age = dog_age * 10.5;
    } else {
        human_age = 2 * 10.5 + (dog_age - 2) * 4;
    }
    return human_age;
}

int main() {
    int dog_age;
    printf("Input the age of dog:\n");
    scanf("%d", &dog_age);
    float human_age = get_human_age(dog_age);
    printf("%f", human_age);

    int dog_age2;
    printf("Input the age of dog:\n");
    scanf("%d", &dog_age2);
    float human_age2 = get_human_age(dog_age2);
    printf("%f", human_age2);
}
  • 上面这部分,我们在“定义”一个函数,定义函数的意思是,描述一个函数该做什么。我们把最核心的岁数换算的逻辑拿了出来,而输入和输出,还是由主函数来完成。我们在主函数的视角来看,从用户输入拿到了一个岁数,丢给了get_human_age这个函数之后,就可以拿到换算后的岁数,赋值给human_age这个变量,后面就拿去输出了。主函数不知道是怎样做的岁数换算,只需要知道这件事被完成了就好。
  • 你可以认为每个函数都是不同的人,不同人之间的记忆是不共享的,全靠主动沟通来共享一部分记忆,比如说现在你想叫我下楼拿快递,那你就需要把取件码发给我,我拿到了取件码,经过了一段时间执行一些步骤,最后把快递拿给你,这是现实世界的逻辑;现在我们来看get_human_age的定义,小括号里面的叫参数列表,参数的意思就是传递给这个函数的数据,很显然他需要一个dog_age,而且既然doge_age是一个数字,就需要确定一下类型是小数还是整数。最前面的float是返回值类型,也就是这个函数最后会返回给你什么,大括号括起来的代码块,都属于这个函数,用于具体说明“怎么做”。
  • 我们“定义”好了一个函数之后,我们来捋一下执行逻辑。要记住程序永远是从主函数的第一句开始执行,执行到最后一句退出,所以当他在主函数执行完输入之后,走到了float human_age = get_human_age(dog_age);这一行,主函数读取自己的变量dog_age的值,将数值本身传递给get_human_age,“调用”这个函数,这个时候代码执行会进入这个函数的内部,函数接收到了参数,定义了一个自己的变量,也叫dog_age,来保存接收到的参数数据,然后从函数定义的第一句开始,执行到最后一句,返回了一个float值。返回的float也是在这一行,被主函数用human_age这个变量存起来了,这一行结束。
    • 不同人之间的记忆是不共享的,不同函数定义的变量也是不共享的。两次调用分别传递了主函数的dog_agedog_age2作为get_human_age的参数,他们的“值”被传递进去之后,在get_human_age内部,都被保存到了他自己的dog_age变量里面,进行后续的计算。主函数的dog_ageget_human_agedog_age只是重名而已。
  • 我们现在这个函数,有输入(参数),有输出(返回值),只关心岁数换算这一件事。函数也可以没有输入,没有返回,类似你叫我扫个地,不需要给我传递信息,我也不需要给你什么,我做完了你就能直接看到地扫完了就行了,写起来像这样:
#include <stdio.h>

float get_human_age(int dog_age) {
    float human_age;
    if (dog_age <= 2) {
        human_age = dog_age * 10.5;
    } else {
        human_age = 2 * 10.5 + (dog_age - 2) * 4;
    }
    return human_age;
}

void calculate_a_dog() {
    int dog_age;
    printf("Input the age of dog:\n");
    scanf("%d", &dog_age);
    float human_age = get_human_age(dog_age);
    printf("%f", human_age);
}

int main() {
    calculate_a_dog();
    calculate_a_dog();
}
  • 我们定义了一个新函数calc什么什么的,把每一只狗相关的输入和输出都放在了这个函数里,这个函数每次就可以处理一只狗。void是空的意思,表示我什么都不会返回,空的参数列表表示我什么都不需要。更标准的写法应该是void calculate_a_dog(void),没有参数的时候,参数列表也标一下void,不过我懒得管,又不是不能跑,懒得多写一个词。返回值的void要是能省了就好了,可惜不行。现在我们想处理几只狗,就在主函数调用几次就好了,不需要复制粘贴一坨代码,更不怕逻辑变动,因为他们都在复用同一块代码。编程语言的发展,就是不断的在研究怎么复用更多的代码,为了省事,为了不怕改。
    • 可以尝试从主函数开始捋一下执行逻辑,能理解函数调用时的跳转就好。
  • 函数的参数可以传入多个值,但每次只能返回一个值。int do_someting(int x, int y) { ... }。我们后面会讲怎么返回多个值。定义函数的时候,要给参数取名字,比如说x和y,因为后续的逻辑要读取这两个值,甚至可能读取多次,但是返回值不用取名字,因为函数一旦return了,就结束了,函数内部不会再有其他的代码读取这个值了,取了名字也不会被用到。
    • 假如说你在return 语句后面写了代码,这些代码不会执行的。return 的意思就是函数到这里就结束了。
  • 不返回值的代码可以不写return,编译器会在最后一行代码之后,自动补一个return;。如果你想提前返回,可以写出这样的逻辑:
void do_something() {
	if (...) {
		return; //只有if满足的时候才触发这个return,没满足不触发return
	} else {
		// do something...
		// do something...
	}
}
  • 假如说a用到了b,b用到了c,那写的时候c的定义应该在最上面,函数是“先定义,后使用”的。
  • 其他所有函数,都是你自己写好了定义,然后直接或者间接的,在主函数里调用了,现在回头看看主函数,其实这也只是一个函数的“定义”,至于是谁在哪调用的主函数呢,是操作系统,操作系统会找到每个程序的主函数,调用这个函数。int作为主函数的返回值,也是返回给了操作系统,返回0时表示你的程序正常执行结束了,返回任何非0的数字,操作系统都会理解为程序运行有问题,并记录下来这件事。你可以在主函数最后加一句return 1,并运行看看。
  • 作业:
    image
  • 暂时没有别的饮品,都是奶茶,主函数要求最多三行代码,主函数不需要写return 0.
  • 像美团,饿了么这些软件,表示全糖,半糖,其实也是这样的逻辑。任何现实里,有限数量的选项,加冰少冰,全糖半糖,其实在代码里都是映射到了某个数字而已。
  • 提示
    • 输出的时候,如果我分别写printf("a ")printf("b"),a后面没加换行符,最终a和b会显示到同一行。这道题可以在不同的位置调用printf输出少冰、全糖、奶茶这三个文本,只要不加换行,他们最终会显示在同一行。
posted @ 2025-04-16 21:10  merlbc  阅读(22)  评论(0)    收藏  举报