一个函数可以接受多个参数(
(int x, int y) 不可以省略为 (int x, y)ReturnType myFunction(Type1 a1, Type2 a2, Type3 a3) {
// ...
}
一个函数可以没有返回值,这样的函数返回值类型为 void。
ReturnType do_some_work(void) {
// ...
}
和数学函数不同,C 函数不仅仅是“接受一些参数、返回一个值”,它实际上就是将一小段具有特定功能的代码单独拎出来,并且给它起个名字。
遗留问题 1
函数参数列表空着并不代表不接受参数
和 C++ 不同,C 的参数列表为空,并不代表不接受参数,而是表示 "accepting unknown parameters"。这项规则自 C23 起才被删除。
遗留问题 2
函数如果不写返回值类型,则返回值类型默认为
int
在 C99 之前,一个函数如果不写返回值类型,则返回值类型默认为 int。
事实上在旧的 C 语言中存在着大量的“默认为 int”的规则,在很多场合下这种写法极易引发难以预料的问题。
遗留问题 2
函数如果不写返回值类型,则返回值类型默认为
int
你可能见过一些人写这样的 main 函数:
main() {
// ...
}
这不符合现代 C 语言的语法,也是极不推荐的写法。
遗留问题 3
函数如果未经声明就被调用,C 和 C++ 的行为不同
调用一个未经声明的函数会发生什么?
#include <stdio.h>
int main(void) {
int x = my_mysterious_function();
printf("%d\n", x);
}
遗留问题 3
函数如果未经声明就被调用,C 和 C++ 的行为不同
C++ 编译器会给出一个十分正常的报错: "... was not declared in this scope"
a.cpp: In function ‘int main()’:
a.cpp:4:11: error: ‘my_mysterious_function’ was not declared in this scope
4 | int x = my_mysterious_function();
| ^~~~~~~~~~~~~~~~~~~~~~
遗留问题 3
函数如果未经声明就被调用,C 和 C++ 的行为不同
C 编译器会允许,并且给出一个令人困惑的 warning: "implicit declaration"(隐式的声明)
a.c: In function ‘main’:
a.c:4:11: warning: implicit declaration of function ‘my_mysterious_function’
[-Wimplicit-function-declaration]
4 | int x = my_mysterious_function();
| ^~~~~~~~~~~~~~~~~~~~~~
当然,它是无法运行的,链接器会抱怨“找不到名为 my_mysterious_function 的函数”。
遗留问题 3
函数如果未经声明就被调用,C 和 C++ 的行为不同
C 标准认为你“隐式地声明” (implicitly declare) 了这个函数,于是压力全都给到链接器。
这一规则 C99 起被删除,但为了向后兼容 (backward compatibility),编译器很可能仍然支持,只是给一个 warning。
main 函数的返回值函数的返回值是传给它的调用者的。
main 的“调用者”是谁?——程序的调用者/“宿主环境” (hosted environment)
main 函数返回 0 ——程序正常退出main 函数返回其它值——代表某种错误1 。
scanf 和 printf 函数scanf 和 printf 是 C 语言中最常用的输入输出函数,分别用于从标准输入读取数据和向标准输出打印数据。
虽然文档有点复杂,但也要试着在这里检索你需要的信息。——GKxx
scanf 和 printf 函数printf 函数
include <stdio.h> // 必须包含头文件
int printf(const char *format, ...);
format:字符串,包含要打印的文本和格式控制说明符。...:可变参数,其数量和类型必须与 format 中的格式控制说明符匹配。scanf 和 printf 函数printf 函数的格式控制说明符
| 说明符 | 用途 | 示例 |
|---|---|---|
%d |
输出有符号十进制整数 | printf("%d", 10); |
%f |
输出单精度浮点数 | printf("%f", 3.14f); |
%lf |
输出双精度浮点数 | printf("%lf", 3.14); |
%c |
输出单个字符 | printf("%c", 'A'); |
%s |
输出字符串 | printf("%s", "Hello"); |
%p |
输出指针地址 | printf("%p", &var); |
scanf 和 printf 函数printf Example
include <stdio.h>
int main() {
int age = 20;
float height = 1.75f;
char grade = 'A';
char name[] = "Li Hua";
printf("Hello, my name is %s.\n", name);
printf("I am %d years old and %.2f meters tall.\n", age, height);
printf("I got an %c in the last exam.\n", grade);
return 0;
}
scanf 和 printf 函数参考输出:
Hello, my name is Li Hua.
I am 20 years old and 1.75 meters tall.
I got an A in the last exam.
scanf 和 printf 函数scanf 函数
include <stdio.h> // 必须包含头文件
int scanf(const char *format, ...);
format:格式控制字符串,指定要读取的数据类型。...:可变参数,必须是变量的地址(使用 & 取地址操作符,字符串数组名称本身即是地址)。scanf 和 printf 函数scanf 函数的格式控制说明符
| 说明符 | 用途 | 示例 |
|---|---|---|
%d |
读取有符号十进制整数 | scanf("%d", &num); |
%f |
读取单精度浮点数 | scanf("%f", &score); |
%lf |
读取双精度浮点数 | scanf("%lf", &pi); |
%c |
读取单个字符 | scanf("%c", &ch); |
%s |
读取字符串(遇空白停止) | scanf("%s", str); |
注意:除 %s(字符串存储在字符数组中)外,其他基本类型变量前必须加 &。
scanf 和 printf 函数scanf Example
include <stdio.h>
int main() {
int num1, num2;
double price;
printf("Enter two integers and a double: ");
scanf("%d %d %lf", &num1, &num2, &price);
printf("You entered: %d, %d, %.2f\n", num1, num2, price);
return 0;
}
scanf 和 printf 函数重要注意事项
& 运算符:对于 int, float, char 等基本类型变量,必须使用 & 获取其地址。错误示例:scanf("%d", num); (会导致未定义行为)。scanf 的返回值:返回成功读取的数据项个数。可用于检查输入是否正确。if (scanf("%d %d", &a, &b) != 2) {
printf("Input error! Please enter two integers.\n");
// 清理输入缓冲区等后续处理
}
#include <stdio.h>
int main(void) {
int a, b;
scanf("%d%d", &a, &b);
printf("%d\n", a + b);
return 0;
}
约定输入格式:两行,每行一个整数,分别表示 a 和 b。
scanf("%d\n%d\n", &a, &b) 会发生什么?试一试。约定输入格式:两行,每行一个整数,分别表示 a 和 b。
scanf("%d\n%d\n", &a, &b) 会卡住,直到你输入了一个非空白字符为止。格式字符串中的任何一个空白字符都会消耗掉输入中所有可用的连续空白字符(其行为类似于通过循环调用
isspace函数来判断)。需要注意的是,在格式字符串中,"\n"、""、"\t"或其他任何空白字符之间没有区别。
让我们一步步模拟程序的执行过程:
scanf,等待用户输入。10 然后按下回车(回车键输入了换行符 \n)。此时输入缓冲区的内容是:'1' '0' '\n'。scanf 开始解析格式字符串 "%d\n%d\n":
%d:成功读取整数 10 并存入变量 a。读取完成后,输入缓冲区只剩下:'\n'。\n(格式字符串中的第一个换行符):这是一个空白字符。根据规则,它要求 scanf “丢弃输入缓冲区中所有的连续空白字符”。
\n',它被读取并丢弃了。现在缓冲区空了。\n 是一个指令,而不是一个必须匹配的字符,scanf 在清空缓冲区中的空白后,并不会停止,因为它还在等待格式字符串中 \n 所隐含的“任意非空白字符”来结束这个空白指令。20 然后按下回车。现在缓冲区的内容是:'2' '0' '\n'。2'。这个 '2' 满足了 \n 指令的停止条件(遇到非空白字符)。2' 不会被消耗,它被放回(push back)到输入缓冲区中。现在缓冲区的内容变回:'2' '0' '\n'。scanf 继续处理格式字符串的下一部分:%d。%d 正好从缓冲区中顺利地读取了整数 20 并存入变量 b。读取完成后,缓冲区剩下:'\n'。scanf 继续处理格式字符串的最后一部分:\n(第二个换行符)。
\n',缓冲区再次变空。scanf 函数,等待用户输入一个非空白字符(比如一个数字、一个字母等)。只有当你再输入一些内容(例如随便按一个字母 x 然后回车),scanf 遇到了非空白字符 'x',才会结束整个读取过程。约定输入格式:两行,每行一个整数,分别表示 a 和 b。
scanf("%d%d", &a, &b) 可以吗?
"%d"匹配一个十进制整数。该数字的格式与strtol函数以 10 为基数参数时所期望的格式相同。
strtol 是什么鬼?点进去看看:
strtol解释由str指向的字节字符串中的整数值。strtol会丢弃所有前导的空白字符(通过调用isspace识别)直到找到第一个非空白字符,然后...
关于 printf
double 需要用 %lf 输出。如果我用 %d 输出,会怎样?
#include <stdio.h>
int main(void) {
double pi = 3.14;
printf("%d\n", pi);
return 0;
}
关于 printf
它和先将 pi 转换为 int(小数部分截去)再输出的效果一样吗?
double pi = 3.14;
int pi_int = pi; // pi_int 值为 3
printf("%d\n", pi_int);
关于 printf
在 scanf 和 printf 中,conversion specifier 与对应的变量类型不匹配是 undefined behavior:你不能对程序的行为做任何假定。
以下说法统统是错的:
程序完全有可能在这里直接崩溃。此外,编译器可以假定你的程序不含 undefined behavior,所以它如果发现这里有 undefined behavior,就可以直接把这行代码删掉,这完全符合语言标准。
Undefined Behavior (未定义行为,简称 UB) 是指程序中的一种行为,对于这种行为,本国际标准(C/C++ Standard)没有施加任何要求。
"Undefined behavior": behavior for which this International Standard imposes no requirements.
- ISO/IEC 9899:2017 (§3.4.3) (C17 Standard)
- ISO/IEC 14882:2017 (§3.27) (C++17 Standard)
这意味着,当程序执行中出现未定义行为时:
Note: 标有星号(*)的工具很重要,但将在我们的课程中进行功能介绍。为了更深入地了解它们的用法,我们鼓励您独立探索
git init : 初始化一个git仓库git commit : 本地保存仓库更改git reset : 回溯到某个版本git log : 显示历史提交记录git push : 把本地的更改上传远程云端git pull : 从云端拉取远端最新的更改使得和云端仓库一致make 工具如何编译和链接程序。
gcc hello.c -o hello 这种命令,以及更复杂的其他命令,进行自动化make 来编译你的项目。CMakeLists.txt 文件来定义项目的构建过程。CMake 可以生成适用于不同平台和编译器的 Makefile 或项目文件。xmake.lua 文件来定义项目的构建过程。XMake 提供了简洁的语法和强大的功能,适合快速构建和管理项目。