C语言答疑-03
C 语言答疑-03
By Von Brank | 2020/11/12
目录
- 网站推荐
- 近期知识梳理
- 几道题
- 答疑
网站推荐
- 算法可视化:visualgo.net/zh
- 算法学习:oi-wiki.org
知识点梳理
字符串简介
ASCII 码表
-
char
既是整数也是字符,取决于你怎么看它以下两个
printf
语句输出都是1
1
2printf("%c\n", 49);
printf("%d\n", 1);以下两个
printf
语句输出都是49
1
2printf("%c\n", '1');
printf("%d\n", 49);以"
char
的方式"看待49
这个整数,它是字符1
以"
int
的方式"看待1
这个字符,他是整数49
-
'\n'
,'\t'
与空格
字符串的定义与初始化
1 | char s[100]; |
字符串的输入与输出
%s
会将当前位置与下一个空格或回车之间的字符串赋值给字符串s
1 | char s[100]; |
- 上述写法中,
scanf
会将输入字符串的首位赋值给s[0]
- 每个字符串都以
'\0'
结尾
常用字符串函数
- 需要
#include <string.h>
getchar()
单字符输入,putchar()
单字符输出strlen()
计算字符串长度strcmp()
比较两个字符串是否相同strcpy()
将一个字符串赋值给另一个字符串strcat()
将一个字符串拷贝到另一个字符串的后面,连成一个长的字符串
数组补充
将变量作为数组定义的元素数量,并作集成初始化的注意事项
以下定义方式在 C 语言中可以通过编译
1 |
|
以下定义方式在 C 语言中无法通过编译,但是在 C++中可以
1 |
|
初识动态规划(DP)
记忆化搜索
洛谷 P1464 Function
对于一个递归函数
- 如果就返回值
- 如果就返回
- 如果并且$b<c w(a,b,c-1)+w(a,b-1,c-1)-w(a,b-1,c)$
- 其它的情况就返回
这是个简单的递归函数,但实现起来可能会有些问题。当均为时,调用的次数将非常的多。你要想个办法才行.
对于一些特殊情况,比如 既满足条件又满足条件,这种时候我们就按最上面的条件来算,所以答案为
-
不使用数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int w(int a, int b, int c)
{
if(a <= 0 || b <= 0 || c <= 0) return 1;
else if(a > 20 || b > 20 || c > 20) return w(20, 20, 20);
else if(a < b && b < c) return w(a, b, c-1) + w(a, b -1, c - 1) - w(a, b, c - 1);
else return w(a-1, b, c)+w(a-1, b-1, c)+w(a-1, b, c-1)-w(a-1, b-1, c-1);
}
int main()
{
int a, b, c;
while(1)
{
scanf("%d %d %d", &a, &b, &c);
if(a == -1 && b == -1 && c == -1)
{
break;
}
printf("w(%d, %d, %d) = %d\n", a, b, c, w(a, b, c));
}
return 0;
}运行结果:
-
使用数组(记忆化)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
int vis[25][25][25];
void init()
{
for(int i=0; i<=20; i++)
{
for(int j=0; j<=20; j++)
{
for(int k=0; k<=20; k++)
{
vis[i][j][k] = -1;
}
}
}
}
int w(int a, int b, int c)
{
if(a <= 0 || b <= 0 || c <= 0)
a = b = c = 0;
if(a > 20 || b > 20 || c > 20)
a = b = c = 20;
if(vis[a][b][c] != -1) return vis[a][b][c];
else if(a <= 0 || b <= 0 || c <= 0) return vis[0][0][0] = 1;
else if(a < b && b < c) return vis[a][b][c] = w(a, b, c - 1) + w(a, b -1, c - 1) - w(a, b, c - 1);
else return vis[a][b][c] = w(a - 1, b, c) + w(a - 1, b- 1, c) + w(a - 1, b, c - 1) - w(a - 1, b - 1, c - 1);
}
int main()
{
int a, b, c;
while(1)
{
init(); //计算每组数据前要初始化数组
scanf("%d %d %d", &a, &b, &c);
if(a == -1 && b == -1 && c == -1)
{
break;
}
printf("w(%d, %d, %d) = %d\n", a, b, c, w(a, b, c));
}
return 0;
}运行结果:AC!!! :)
最长上升子序列
给出一个序列,找出其中最长的子序列,满足
1 | int a[MAXN], d[MAXN]; |
C 语言庶事
整数是怎么表达的——原码,反码,补码
- 这些规定没什么别的目的,只是为了方便计算
各数据类型的表达范围与自然溢出
int
类型有 32 位,表达范围为unsigned int
数据范围是
各数据类型的输入输出
- 整数类型的输入输出
数据类型 | |
---|---|
%d |
int |
%u |
unsigned int |
%lld |
long long |
%llu |
unsigned long long |
-
二进制,八进制,十六进制的整数(引用自:c.biancheng.net/view/1759.html)
-
二进制
- 二进制数以
0b
或OB
开头,只能包含0
和1
两个数 - 标准 C 语言不支持该二进制写法,只有某些编译器支持
1
2
3
4
5
6
7//合法的二进制
int a = 0b101; //换算成十进制为 5
int b = -0b110010; //换算成十进制为 -50
int c = 0B100001; //换算成十进制为 33
//非法的二进制
int m = 101010; //无前缀 0B,相当于十进制
int n = 0B410; //4不是有效的二进制数字 - 二进制数以
-
八进制
- 使用时以
0
开头,只能包含0
~7
这八个数
1
2
3
4
5
6
7//合法的八进制数
int a = 015; //换算成十进制为 13
int b = -0101; //换算成十进制为 -65
int c = 0177777; //换算成十进制为 65535
//非法的八进制
int m = 256; //无前缀 0,相当于十进制
int n = 03A2; //A不是有效的八进制数字 - 使用时以
-
十六进制
- 以
0x
或0X
开头
1
2
3
4
5
6
7//合法的十六进制
int a = 0X2A; //换算成十进制为 42
int b = -0XA0; //换算成十进制为 -160
int c = 0xffff; //换算成十进制为 65535
//非法的十六进制
int m = 5A; //没有前缀 0X,是一个无效数字
int n = 0X3H; //H不是有效的十六进制数字 - 以
-
输入与输出
short int long 八进制 %ho
%o
%lo
十进制 %hd
%d
%ld
十六进制 %hx
或%hX
%x
或%X
%lx
或%lX
%hx
、%x
和%lx
中的x
小写,表明以小写字母的形式输出十六进制数%hX
、%X
和%lX
中的X
大写,表明以大写字母的形式输出十六进制数。
-
浮点数
-
令人头疼的精度问题
1
2
3
4
5
6
7
8
9
10
int main()
{
if(1.1 + 0.1 == 1.2)
printf("YES!!!");
else
printf("NO!!!");
return 0;
}以上程序输出的是
NO
-
1.1 + 0.1
为什么不等于1.2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
260.1转化为二进制的结果是一个循环小数:
0.00011001100110011001100110011001100110011001100110011010
double类型在二进制下的精度是53位有效数字,超出部分四舍五入
同理将1.1转化为二进制:
1.0001100110011001100110011001100110011001100110011010
二者相加
0.00011001100110011001100110011001100110011001100110011010
+ 1.0001100110011001100110011001100110011001100110011010
---------------------------------------------------------------
1.0011001100110011001100110011001100110011001100110100
将结果转化为十进制:
1.20000000000000018
将1.2转化为二进制:
1.0011001100110011001100110011001100110011001100110011
与1.1+0.1的二进制结果:
1.0011001100110011001100110011001100110011001100110100
相比较
二者不同 -
解决方案
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const double eps = 1e-6;
double eps(double x)
{
return x < 0 ? -x : x;
}
int main()
{
if((abs(1.1 + 0.1 - 1.2) < eps)
printf("YES!!!");
else
printf("NO!!!");
return 0;
}
以"\"
开头的字符
-
格式控制字符串中的一类字符
字符 意义 \b
回退一格 \t
转到下一个制表位 \n
换行 \r
回车 \"
双引号 \'
单引号 \\
反斜杠 -
基本的文件读写
-
思考:如何输出
%d
这个字符串本身
#include <stdio.h>
是什么——头文件简介
-
头文件是扩展名为 .h 的文件,包含了 C 函数声明和宏定义,被多个源文件中引用共享。有两种类型的头文件:程序员编写的头文件和编译器自带的头文件
-
gcc 的编译过程
-
分步编译:
-
预处理:
gcc -E hello.c -o hello.i
-
编 译:
gcc -S hello.i -o hello.s
-
汇 编:
gcc -c hello.s -o hello.o
-
链 接:
gcc hello.o -o hello_elf
选项 含义 -E
只进行预处理 -S
大写只进行预处理和编译 -c
小写只进行预处理、编译和汇编 -o file
指定生成的输出文件名为 file
文件后缀 含义 .c
C 语言文件 .i
预处理后的 C 语言文件 .s
编译后的汇编文件 .o
编译后的目标文件
-
-
一步编译:
1
gcc hello.c -o demo
此指令依然经过:预处理、编译、汇编、链接的过程
-
-
编译预处理指令——以
#
开头的指令 -
在编译预处理中,编译器会将头文件中的所有内容原封不动地“抄”到
.c
文件中 -
头文件内应只包含变量与的函数声名,不应包含变量与函数的定义
-
1.h
的编写
算术表达式,关系表达式与逻辑表达式
-
算数表达式
1
2
3
4
5
6
7a = a + b;
b = b + 1
a = a * +b;
a = a * -b;
a=b+=c++-d+--e/-f;优先级:
(++i --i) > (* /) > (+ -) > (i++ i--) > (= += *= -= /= %=)
思考:下列程序的输出值
1
2
3int a = 0, b = 10, c = 8, d = 1, e = 1, f = 1;
a=b+=c++-d+--e/-f;
printf("%d", a); -
关系表达式
考虑下列程序对于输入
3
的结果1
2
3
4
5
6
7
8
9
10
11
12
13
14int n;
scanf("%d", &n);
switch(n)
{
case 3 || 4:
printf("YES!!!");
break;
case 5 || 6:
printf("NO!!!");
break;
default:
printf("No idea.");
break;
}考虑下列语句的含义
1
2
3
4printf("%d", 5 > 3);
printf("%d", 3 && 0);
printf("%d", !0);
a = 7 > 2 + 4;关系运算优先级:(算术运算)> (关系运算) > (赋值)
如:
1
2
3
4
5if(a * b + c > c / d -e)
{
printf(“hello world”);
}
int f = a * b + c > c / d –e; -
逻辑表达式
下列两个函数实现的效果完全相同
1
2
3
4
5int abs(int x)
{
if(x < 0) return -x;
else return x;
}1
2
3
4int abs(int x)
{
return x < 0 ? -x : x;
}
变量的作用域与递归函数的本质
- 本地变量的作用域
- 全局变量在定义时会自动初始化为 0,而本地变量不会,这意味着本地变量的初值是随机的,其取决于上一个使用这块内存的程序
- 离开作用域的变量会被销毁
- 递归的过程中究竟发生了什么?
C 语言程序设计上机测试其他注意事项
-
不要把SSE当成IDE
-
C 语言上机测试提交前一定要在本地编译通过并用样例数据测试
-
善用输出调试与单步调试
几道题
洛谷 P1059 明明的随机数
明明想在学校中请一些同学一起做一项问卷调查,为了实验的客观性,他先用计算机生成了个到之间的随机整数,对于其中重复的数字,只保留一个,把其余相同的数去掉,不同的数对应着不同的学生的学号。然后再把这些数从小到大排序,按照排好的顺序去找同学做调查。请你协助明明完成“去重”与“排序”的工作。
洛谷 P1177 【模板】快速排序(朴素算法)
给出个数,将其从小到大排序.
对于的数据,
洛谷 P3375 【模板】KMP 字符串匹配(朴素算法)
给出两个字符串 和 ,若 的区间 子串与$ s_2 s_2 s_1 l$。
现在请你求出 在 $s_1 $中所有出现的位置。
对于的数据,的长度为,
洛谷 P1216 [USACO1.5][IOI1994]数字三角形 Number Triangles
观察下面的数字金字塔。
写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。
1 | 7 |
在上面的样例中,从 的路径产生了最大
洛谷 P1219 [USACO1.5]八皇后 Checker Challenge
一个如下的 的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。
上面的布局可以用序列 来描述,第 个数字表示在第行的相应位置有一个棋子,如下:
行号
列号
这只是棋子放置的一个解。请编一个程序找出所有棋子放置的解。
并把它们以上面的序列方法输出,解按字典顺序排列。
请输出前 个解。最后一行是解的总个数。
输入样例#1
1 | 6 |
输出样例#2
1 | 2 4 6 1 3 5 |
P1784 数独【选做】(困难但有趣)
数独是根据$ 9 \times 9$盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫内的数字均含 ,不重复。每一道合格的数独谜题都有且仅有唯一答案,推理方法也以此为基础,任何无解或多解的题目都是不合格的。
芬兰一位数学家号称设计出全球最难的“数独游戏”,并刊登在报纸上,让大家去挑战。
这位数学家说,他相信只有“智慧最顶尖”的人才有可能破解这个“数独之谜”。
据介绍,目前数独游戏的难度的等级有一到五级,一是入门等级,五则比较难。不过这位数学家说,他所设计的数独游戏难度等级是十一,可以说是所以数独游戏中,难度最高的等级。他还表示,他目前还没遇到解不出来的数独游戏,因此他认为“最具挑战性”的数独游戏并没有出现。
输入一个一个未填的数独,输出一个填好的数独。
输入样例#1
1 | 8 0 0 0 0 0 0 0 0 |
输出样例#1
1 | 8 1 2 7 5 3 6 4 9 |