02.程序设计入门

程序设计入门

本章介绍程序设计的基础知识,重点讲解C语言的核心要素。内容涵盖头文件的作用、变量类型定义、主函数结构及程序语句规范,详细解析输入输出函数scanfprintf的格式化用法。通过顺序结构示例(如数值运算、图形输出)和分支结构(条件判断、运算符选择)演示基本逻辑设计,并结合评测系统机制强调代码格式与数据精度的重要性。本章通过典型例题帮助读者掌握程序设计的基本流程、语法规则及调试技巧,为后续算法学习奠定基础。

C语言基本要素

C++ 兼容 C 语言的语法,所以我们不必拘泥于“.c”还是“.cpp”这样的扩展名,新手不用管那么多,无论是写C++风格还是C语言风格,一律在C++的编译环境(.cpp文件)中写就好。

头文件

为了避免从电脑直接看得懂的“01011...”写起,或者从“MOV”、“ADD”这类非常底层的“汇编语言”写起,我们学习一个相对“高级”的语言,即C语言。那些高频使用的“向屏幕输出一些字符”等操作,C语言已经帮我们做好了,我们只需要“告诉”C语言,让C语言去要求电脑做这些事。

“告诉”这个动作需要特定的指令来做,而C语言怎么知道这个指令是做什么的呢?它把不同类型的功能封装在了不同的文件里,所以我们要先让编译器知道,我们要用哪些文件里的功能,就要把这些文件“包含”进来,这就是“include”指令,包含进来的文件叫“头文件”。

现在要包含一个处理“输入输出”这类操作的功能包,这个头文件是“stdio.h”,“std”是标准(standard)的缩写,“io”是“input/output”的缩写。

这样接下来的程序,就可以用“stdio.h”里包含的功能。

包含头文件的语法是:

  • 一个#符号,表示现在要做一些“特殊”的事情,“#”后面不同的内容对应不同的事情
  • #后紧跟“include”,表示我们现在要做的是“包含头文件”这样的事情,至于能做其它什么事情,以后再了解
  • 头文件用一对<>包裹
1
#include<stdio.h>

变量

要计算总要有计算的对象,基础的计算对象就是“变量”,可以类比学过的代数,\(x,y,z...\) 这样的代数,然后把数字代进去就能计算不同的数据。

代数有整数有小数,在电脑中还有字符,要让电脑看得懂,能合理的用“0101...”这样的二进制存储,需要告诉它这些代数是什么类型,这就是变量类型。

先了解最常用的:

  • int:整数(1,2,-6... 这样的数)
  • double: 浮点数(0.4、2.5、5.0...这样有小数部分的精度的数)

变量还有一些其它的类型,以后用到再了解。

主函数

C语言的功能是由“函数”组成的,初高中学过“函数”的概念,是把一些代数丢进去,完成固定的一套计算。比如 \(f(y)=x*2+1\) ,C语言的函数可以延申这个概念,把一些变量(变量可以是 \(x,y,z\) 这样的代数,也可以是更复杂的结构)丢进去,完成固定的程序逻辑。

一个程序运行,总得有一个开端,即从哪里开始,这就是“主函数”的作用,告诉系统从这里开始。

函数可以有返回值(算完之后告诉调用它的地方它算出了个什么类型的东西),也可以没有返回值,但主函数必须有返回值,这是C语言的规则,它用来告诉操作系统这个程序执行的是否成功。

1
2
3
4
int main() {
// 主函数的代码
return 0;
}

主函数的语法是:

  • int:主函数返回值必须是 int,不能是别的
  • main:表示函数的名字,main这个名字是专门用于主函数的,不能用其它名字,大小写也不能改
  • ():一个小括号里写函数的参数,主函数有特定的参数,不过可以省略,初学时可以先不管它的参数
  • {}:包裹定义主函数要做的事的代码
  • return 0;:主函数包裹的代码的末尾必须是return 0;,表示当执行到这里的时候,向调用主函数的“东西”(比如操作系统的某个进程)告知本程序正常结束。

这里“//”是注释语法,用于写一些帮助自己回忆代码是干什么的注释,对电脑来说这些内容不存在,纯粹是给人看的。

程序语句

C语言中常规程序的语句末尾都要有分号“;”,表示一个语句的结束(以下语句的含义后文会说明)。

1
2
3
4
int a, b;
a = 3;
b = 4;
int c = a + b;

定义变量

1
int a, b;

这里定义两个变量 ab,他们是 int 类型。

赋值

数学中“=”表示两个变量/数字相等,而程序中,“=”表示将右边的值存入左边的变量,变量是一个可以改变的代数。

1
2
int a = 3, b;
b = 5;

输入

让用户在弹出的黑框框中输入想要的数字,再用输入的数字计算,就有了用户交互,这样程序就可以做很多事了。

我们前面已经#include<stdio.h>,所以可以用输入输出相关的功能了:

1
2
int a, b;
scanf("%d%d", &a, &b);

scanf 也是函数,"%d%d", &a, &b就是传给它的参数。

  • "%d%d" 表示要输入两个整数,这里%用于标识输入,%之后跟的内容具体定义了要输入什么,好让电脑在内存中做好准备,%d就是要输入一个整数(int),如果是%lf就是要输入一个double
  • &a对应前一个%d,用户在黑框框输入的第一个数就会存入a这个变量,同理&b对应第二个%d&是“取地址”符号,&a表示指向a的内存地址,这里先不用了解那么多,知道这个格式能输入数据即可。

知识点:int的输入输出都用%ddouble的输入用%lf,输出用%f。这时你可能有个疑问,没错确实有个输入是%f的变量类型float,但编程之初我们不想讨论过多内容,减轻初学者压力,这里简单说就是float能表达的精度不够,而现代计算机性能很好,在算法竞赛中我们都用double就好了。

计算

计算a+b的和,很直观在代码中写a+b即可,它得到的结果可以存入一个变量,也可以直接当参数传给函数。

比如 int c = a + b; 就把 a+b 的结果存入 c

输出

printf("%d\n", a + b); 就把a+b 的结果作为参数传给 printf这个函数,这里的%dscanf%d含义一样,printf用于输出,所以就是把a+b的结果作为整数的格式输出。 \n 表示输出后换一行(在文本编辑器里换一行就是敲一下回车)。

double的输出用%f,注意这里没有“l”。

知识点:输出也会有一些特异的需求,比如输出小数保留多少位,输出整数能不能右对齐前面补空格让总长度固定等,就会有 %.3f%02d 等用法,当遇到时,可查资料寻找可用的格式化方法。

一个基本的a+b

1
2
3
4
5
6
7
#include<stdio.h>
int main() {
int a, b;
scanf("%d%d", &a, &b);
printf("%d\n", a + b);
return 0;
}

例:输出浮点数

读入一个双精度浮点数(double),分别按输出格式 %f%f 保留 5 位小数,%e%g 的形式输出这个数,每次在单独一行上输出。

输入:

1
12.3456789

输出:

1
2
3
4
12.345679
12.34568
1.234568e+01
12.3457
1
2
3
4
5
6
7
#include<stdio.h>
int main() {
double x;
scanf("%lf", &x);
printf("%f\n%.5f\n%e\n%g\n", x, x, x, x);
return 0;
}

评测系统的机制

把代码通过网页(或软件)提交到评测系统,评测系统会在后台编译并执行你的代码,将一系列你看不到的输入数据发送给你的程序,将输出的内容和答案对比,然后告诉你你的代码是否正确。

新手经常会问“我样例都通过了为什么提交了还不对呢”,这里再次强调一下:评测系统会测许多并不会让你看到的其它数据。

在电脑中,空格“”,回车等内容也都是由“字符”来表达的,它们都有编码,传统评测系统对答案的格式有严格的要求,你的程序的输出结果要与评测机上的答案完全一致才能通过

比如假设评测机上的答案是

1
2
3
1
2
3

如果你的程序输出的是 1231 2 3,就都无法通过。

找一个OJ的a+b的题,提交你的代码试试把

顺序结构

顺序结构是程序设计中最基本、最简单的控制结构之一。它指的是程序按照代码书写的先后顺序依次执行,从上到下逐条执行语句,没有跳跃、分支或循环。在顺序结构中,每一条语句都会被执行一次,并且严格按照它们在源代码中的排列顺序执行。

上文的例子都是顺序结构,先定义变量,然后输入,然后输出,然后return……

例:用“*”输出菱形

* 构造一个对角线长 5 个字符,倾斜放置的菱形。

1
2
3
4
5
  *
***
*****
***
*

因为图形很简单,不需要做特别的逻辑处理,简单粗暴用printf输出它就好。

图形分为 5 行,且要考虑每个空格也是答案的一部分,输出这样的内容肯定是错误的:

1
2
3
4
5
*
***
*****
***
*

每一行要把“”和“*”都打出来,且要把换行的符号(“\n”)也打出来,答案如下:

1
2
3
4
5
6
7
8
9
#include<stdio.h>
int main() {
printf(" *\n");
printf(" ***\n");
printf("*****\n");
printf(" ***\n");
printf(" *\n");
return 0;
}

例 计算 \((a+b)/c\) 的值

给定 3 个整数 \(a, b, c\), 计算 \((a+b)/c\) ,其中 \(/\) 是整除运算。

输入

1
1 1 3

输出

1
0

“整除运算”的意思是只取计算结果的整数部分,小数部分省略。

知识点:大多编程语言默认相同类型的变量在计算过程中类型不变,int只能存整数,它无法表达 1/2=0.5这样的概念,故intint之间的计算,编译器默认结果自动取整,也就刚好符合“整除运算”的要求,即1整除2=0,保留0.5的整数部分0

1
2
3
4
5
6
7
#include<stdio.h>
int main() {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
printf("%d\n", (a + b) / c);
return 0;
}

知识点:在读入多个数字的时候,scanf的格式化输入%d会自动跳过不是数字的符号,从而方便我们不用关心输入的数字之间有多少空格、多少换行,即无论输入的是

1
1 1 3
还是
1
2
3
1     1

3
scanf("%d%d%d", &a, &b, &c); 都能正确地完成输入

例 浮点数向零舍入

输入一个双精度浮点数,将其向零舍入到整数。说明:向零舍入的含义是,正数向下舍入,负数向上舍入。

输入数据范围是 \(-10^{15}\leq x \leq 10^{15}\)

这是一个 double 向整数转换的任务,在编程语言中向0舍入是默认机制,不需要我们写特别的代码逻辑。

关于强制转换,可以直接将double赋值给整数变量,也可以给值前面加(变量类型)来得到转换后的值。

知识点:题目告知输入范围,并不是让你来判断题目输入的对不对,而是给你一个信息,让你知道在处理一个什么规模的问题,从而调整自己的实现思路。

知识点:int可以表达的范围是 -2147483648~2147483647,也就是32个二进制位能表达的范围,更大的整数类型是 long long,它有64个二进制位,能表达 -9,223,372,036,854,775,808~9,223,372,036,854,775,807,大约 \(10^{19}\),它对应的输入输出占位符是%lld

输入

1
2
2.3
-2.3

输出

1
2
2
-2
1
2
3
4
5
6
7
#include<stdio.h>
int main() {
double x;
scanf("%lf", &x);
printf("%lld\n", (long long)x);
return 0;
}

分支结构

分支结构(也称为选择结构)是程序设计中用于实现条件判断和不同路径执行的重要控制结构。通过分支结构,程序可以根据特定条件选择执行不同的代码块,从而实现更复杂的功能和逻辑处理。

if语句

1
2
3
if(条件) {
// 满足条件则执行这里
}

或者

1
2
3
4
5
6
7
if(条件1) {
// 满足条件1则执行这里
} else if(条件2) {
// 满足条件2则执行这里
} else {
// 剩下的情况执行这里
}

例 整数大小比较

输入两个整数,比较它们的大小。若 \(x>y\) ,输出 > ;若 \(x=y\) ,输出 = ;若 \(x<y\),输出 <

数据范围 \(0\leq x \leq 2^{32}, -2^{31} \leq y \leq 2^{31}\)

输入:

1
1000 100

输出:

1
>
  • 这题范围很大,用long long
  • 因为=是赋值用的,判断相等得用==
  • 输出的好习惯:单行输出都带上\n在结尾换行
1
2
3
4
5
6
7
8
9
10
11
12
13
#include<stdio.h>
int main() {
long long x, y;
scanf("%lld%lld", &x, &y);
if(x > y) {
printf(">\n");
} else if(x == y) {
printf("=\n");
} else {
printf("<\n");
}
return 0;
}

三元运算符

在“非此即彼”且逻辑简单的时候,用三元运算可简化代码,写起来很“帅”

1
条件 ? 符合条件执行这里 : 不符合条件执行这里

上题用三元运算符也可以完成,不过它不是“非此即彼”的,有三种情况,可以三元运算套三元运算,这样写:

1
2
3
4
5
6
7
#include<stdio.h>
int main() {
long long x, y;
scanf("%lld%lld", &x, &y);
printf(x > y ? ">\n" : (x == y ? "=\n" : "<"));
return 0;
}

switch

一个变量或表达式有多种可能的目标值,可以用switch:

1
2
3
4
5
6
7
switch(变量) {
case1: 变量是值1时的逻辑; break;
case2: 变量是值2时的逻辑; break;
case3: 变量是值3时的逻辑; break;
// ...
default: 除了以上情况外时的逻辑;
}

注意:除最后的default外,每个 case 最后必须有 break ,否则它之后的 case 也会被执行

例 简单计算器

给两个数字、一个符号,如果符号不是加减乘除则输出Invalid operator!,如果除0则输出Divided by zero!

输入

1
1 2 +

输出

1
3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<stdio.h>
#include<math.h>
int main() {
int a, b;
char c;
scanf("%d%d %c", &a, &b, &c);
switch(c) {
case '+': printf("%d\n", a + b); break;
case '-': printf("%d\n", a - b); break;
case '*': printf("%d\n", a * b); break;
case '/':
if(b == 0) {
printf("Divided by zero!\n");
} else {
printf("%d\n", a / b);
}
break;
default: printf("Invalid operator!\n");
}
return 0;
}

知识点: - 字符需要用char这个变量类型, - 读入%d能自动跳过空白,但%c不行,它会读空白字符,题目输入由空格隔开,所以要加一个空格占位后再写%c - 字符常量用单引号',后面课程的字符串章节会更详细的讲解