本章讲述一些基础的 C/C++语法。
我们遵循程序界的 优良(?)传统,先展示一个输出“Hello World”的程序。
先是 C 版本(C++基本上 可以理解为 C 的超集,语法略有不同,但是大部分 C 程序可以直接通过编译。差别后面 可能 会介绍)
#include <stdio.h>
// 主函数体,程序就此开始
int main(void) {
printf("Hello World!\n");
return 0;
}
然后是 C++版本
#include <iostream>
using namespace std;
// 主函数体,程序就此开始
int main(void) {
cout << "Hello World" << endl;
// 如果编译器支持最新的C++草案(C++25),那么有更简单的
// include 一行修改成 #include <print>
// 然后 cout 就可以写成 std::println("Hello, world!\n");
return 0;
}
下面开始解释
-
#include <stdio.h>和#include <iostream>
这是“包含”(include)语句。stdio.h和iostream中定义了很多基本的输入和输出函数,比如这里使用的printf()和cout。要使用他们,就需要包含他们所在的头文件。include是预处理指令,也就是在编译(把代码翻译成机器能理解的二进制形式)环节之前做的工作,所以开头是#。include文件的目的就是把多个编译单元(也就是 C/C++代码)公用的内容,单独放在一个文件里减少整体代码尺寸。实际的作用就相当于把include的头文件复制整合一份在你的代码里面。就像做菜前先把菜谱准备好
include的语法是:#include <头文件>或者#include "头文件"。一般地,<>包裹的是标准库中的头文件;而""包裹的是不属于标准库中的文件(比如你自己写的头文件)。当然如果你混用了,大部分编译器也不会产生错误。
stdio.h是 C 语言的标准库之一,全称是 Standard Input & Output(标注输入输出),.h是 C 语言头文件的后缀,Header(头 文件),不过.h后缀并非必须项。
iostream是 C++的标准库之一,全称是 Input & Output Stream(输入输出流)。碎碎念:
C++也可以使用 C 语言的库,但是为了避免名称冲突,C++标准规定,C 语言库的头文件如 stdio.h,在 C++中对应的版本是
cstdio,并且其中定义的名称(如printf)位于std命名空间(别急,见下一条)中。 虽然大多数编译器允许直接使用stdio.h,但使用 cstdio 是更符合 C++标准的做法。比如建议使用#include <cstdio>而不使用#include <stdio.h>,虽然体量较小的项目后者多半能通过编译,但会污染全局命名空间(“全局”就是指不需要命名空间::标识符,直接访问或者使用::标识符明确指定访问的命名空间)。
使用<cstdio>时,就要记得std::printf了。 -
(C++)
using namespace std;C++独有的语句。为了避免名称冲突(比如较大项目中两个人各自定义了相同名字的变量、形式完全相同的函数等等),引入了命名空间的理念。标准库中的函数都在命名空间std中,包括以#include <cstdio>这种c开头的 C 语言兼容库。必须通过命名空间::标识符的格式来指定到底是哪一个标识符(标识符简单来说就是变量、函数名的统称),比如std::cout。上面的示例直接使用
cout,是因为语句using namespace std;,这句话告诉编译器,我说的标识符,默认可以从std命名空间查找。不过不建议在真正的生产环境这么做,因为大项目容易产生名称冲突;建议养成std::cout的习惯。碎碎念: 在 3.中提到了污染全局命名空间,这里举一个例子:
在 stdio.h 中,标识符都位于全局命名空间,其中有一个行输入函数int puts(const char *str)(名称是“puts”,返回整数,输入一个字符串(指针)的函数)。如果你不巧习惯不好也定义了一个puts,那么现在全局有 2 个puts。如果这两个puts长得不一样(参数列表不一样),在 C++里面那还勉强可以区分(这种区分叫做函数重载;C 年纪大了老眼昏花看不清,会直接报错),但如果这两个外表长得一模一样,就不免上演一出真假美猴王的大戏。当然,编译器不吃瓜,会及时地产生一个error叫停。题外话: C/C++是大小写敏感的语言,所以关键字、标识符等等必须注意大小写,
int varibleA;≠int VARIBlEa;,int function1() { ... }≠int Function1() { ... },、int是类型关键字但INT如果没有特殊处理不是类型关键字。 -
int main(void) { ... }定义了一个返回值为int(Integer,整数),函数名称为main(主函数),不接受任何参数(void,空)的函数。 而{}标记了函数的开始和结束。
main是 C/C++函数开始执行的位置(现阶段暂时这么理解)。程序会依次执行里面的指令,直到达到}或者return语句(返回,后面会细说) 。
函数的定义语法是:返回类型 函数名(参数类型 参数 1, 参数类型 参数 2, ...) { ... }如果没有返回值,就用
void占位置。比如void func1(int a) { ... },void func2() { ... }。后一个例子参数列表是空的等效于void。
至于具体有哪些类型、哪些名字是允许、规范的,将在下一章详细阐述。这一章我们先把握整体概念。碎碎念: 不像 Python,C/C++其实不依赖缩进(就是一行代码前面带上空白),但是为了美观
以及方便你故地重游,我们习惯上会一个大括号一层缩进
编辑 C/C++时,缩进一般是 4 个空格,或者一个Tab,不过有些编辑器会自动统一,将 4 个空格统一成一个Tab,或者反过来。 强烈建议使用空格进行缩进,并将编辑器设置为将Tab键输出为固定数量的空格(通常是 4 个,有些人是 2 个)。这可以保证代码在任何环境和编辑器下看起来都是一致的。 -
printf("Hello World!\n");和cout << "Hello World!" << endl;
这两个语句都是打印语句。同样会打印"Hello World!"然后换行。\n和std::endl效果上都是换行,但是内部不一样,\n 会快一些。
C/C++的“语句”都以 英文 分号结束,如果你忘记加了,编译器(或者代码分析器)会在你忘记写分号的 下一行 报错,以 GCC 为例内容大概是:xxx.c: In function ‘main’: xxx.c:5:5: error: expected ‘;’ before ‘printf’ 5 | printf("Hello, World!\n"); --> 这是你漏掉分号的下一行,到了这一行编译器才能真正确定你没有打分号 | ^~~~~~意思是你第五行前面没打分号。报错位置是第五行第五个字符(
xxx.c:5:5)
顺带一提,//后面和/* ... */里面的内容表示注释,会被忽略。两者区别在于//只管一行,而/* */可以横跨多行为什么
'\n'表示换行?std::endl又是什么?<<是什么标识符?怎么输出变量?这些问题你将会在后来的章节得到答案。 -
return 0;这是“返回”语句,也就是程序的执行流程归还给上一层调用者,并且给出一个值(这个值叫做“返回值”,类型就是上文3.所说的“返回类型”)。这里只有一个函数main,那返回给谁呢?由于程序由操作系统调用,所以最后当然是返回给系统啦!(其实中间还有很多过程,但是我们暂时选择性眼瞎)
因此,main函数统一的返回值都是int(整形)。void main() { ... }的形式 可能 会被编译器支持,但这是非标准的形式,所谓“法无禁止皆可为”,好一点的情况是,编译器可能会发出警告,而且默认返回0;最坏的情况下,编译器会产生完全不可预期的行为。
一般地,0表示程序成功执行,没有错误;而其他的值表示程序有不同的状况。至于main除了void这种空参数列表,还有什么形式?为什么有的程序启动行为每次不一样?你将在未来的学习中了解到这些知识。
这就是一个朴素的 Hello World 程序了。下面找出程序的错误,检验下学习成果吧:
-
(12 处错误和严重不规范;1 处编码习惯不佳)
井inculde “studio。h” INT mian(){ print("Hello World\n") /* 打印语句 } -
(2 处错误;有两种较好的改法)
#include <iostream> int main() { cout << "Hello World" << endl; return 0;习题答案另附
本章我们阅读了第一个 C/C++程序,并理解了基本结构。下一章我们将深入学习变量与数据类型。