欢迎来到算法的世界
0.安装编译器
为了省去逆天的配置操作,我们用最简单的 dev-c++
进行演示:
安装包在迎新群里有,名字叫 Dev-CPP 5.9.2
。
运行安装包之后:
这里只有英文没关系啊,这里是安装时的语言,安装好了之后有中文的。
选 OK
:
(你爱我吗)选 I Agree
:
选 next
:
选个你喜欢的安装路径,因为这玩意不用配置文件,所以随便装哪都没事,然后Install
:
选 Finish
:
如果你进去之后是英文的,在上面一栏的工具(tool)这个地方,点第二个环境选项(Environment option),选中文:
编译器装好了,在上面一栏的文件这里,选择新建->源代码,或者用快捷键 Ctrl+N
新建一个文件,建完之后就会出现一个未命名1:
在里面填入以下代码:
#include<bits/stdc++.h>
using namespace std;
int main()
{
cout<<"hello world!"<<endl;
return 0;
}
2
3
4
5
6
7
填入之后还不能运行呢,在文件那里保存以下这个文件,或者是用快捷键 Ctrl+S
保存一下这个文件。
会弹出这个保存框,保存的地点自己选,文件名中前面自己随便改,后面的.cpp
后缀要保留,否则这就不是一个可以编译的文件了。
保存成功之后,点这里的编译运行,或者摁快捷键 F11
:
成功编译运行,输出了hello world!
:
恭喜你的第一份代码运行成功,你的世界自动触发开启隐藏主线任务:程序员。
1.认识程序结构
代码中,以;
来作为一行的结尾,遇到分号则表示这一句话结束。
abc;
ab
cd;
2
3
上面的例子中,一共只有两句话,第一句话是abc
,第二句话是abcd
。
#include<bits/stdc++.h>
using namespace std;
int main()
{
cout<<"hello world!\n";
return 0;
}
2
3
4
5
6
7
回顾刚才编译成功的代码,我们来逐条解释:
#include<bits/stdc++.h>
这个的意思是,接下来我要写的代码,要用到头文件<bits/stdc++.h>
里面的工具。
比如说,我们把编程比作写数学题,我们可以能要用到一个叫勾股定理的东西,这个叫勾股定理显然不是我们自己发明的(不然你每次做数学题都要从数字的定义来开始写吗?)而是我们可以直接拿来用的。
同样的,编程也是,不是所有的功能都要自己来实现(比如读取数据、输出数据等),有些功能是被前辈们实现好的,他们就储存在各式各样的头文件里面,只要我们include
了指定的头文件,就可以直接用这些写好的功能了。
假如,勾股定理存在在一个叫math
的头文件里,我们在代码在最前面写下#include<math>
,就可以在后面直接使用勾股定理,而不用去证明它是对的。
第二行:
using namespace std;
使用 命名空间 std
。
命名空间中也是一些功能的存在,和头文件里的功能储存方式不同,但是一开始不需要分辨,只需要知道这里面也是各种功能。
第三行:
int main()
{
abc;
def;
return 0;
}
2
3
4
5
6
7
这个被称为主函数。每一个程序,当他开始运行的自己的功能的时候(也就是说不算之前调用写好的功能),第一句话从主函数里面开始,也就是说第一句执行的语句是abc
。
这个代码中,除了abc;
和def;
以外的语句,都是主函数的形式,这个形式的具体解释需要用到函数方面的知识,所以先把框架放在这里,后面再进行解释。
cout<<"hello world!"<<endl;
这句话的功能是在屏幕上输出 hello world!
并换行。
为啥写这句话就能进行输出呢?
因为前面调用的 #include<bits/stdc++.h>
和 using namespace std;
帮你实现了这个功能!
输出语句的格式是这样的:
首先,写一个 cout
,表示你要输出东西。
然后,写一个 <<
,表示后面是要输出的内容。
然后,写下你要输出的内容 "hello world!"
,输出的内容要用双引号框起来。
双引号框起来的内容叫字符串,在输出中,表示这里面的内容要原样输出,没有任何指代含义。
然后写分号,表示这句话结束。
合起来就是
cout<<"hello world";
唉后面还有一半呢。
如果你想一次输出多个内容,不用写多个 cout
,只要在前面不写分号,然后再写一个<<
,后面接输出内容就行了。
比如
cout<<"hello "<<"world!";
这样也可以输出 hello world!
。
如果你的输出想换行,就在输出内容那里写endl
,它表示换行的意思,这个不加双引号。
cout<<"hello world!"<<endl;
如果你的 endl
也加了双引号,那它就不认为这是换行了,它认为你要输出这四个字母,最后就会给你输出一个 hello world!endl
这个东西了。
最后一句
return 0;
是主函数结束的标志,读到这句话,主函数就结束了,后面写的语句将被无视。
总的来说,在新手期,程序的基本框架就是
#include<bits/stdc++.h>
using namespace std;
int main()
{
//在这个地方写东西,其他的别动。
return 0;
}
2
3
4
5
6
7
8
9
2.变量、赋值、输入、输出。
在说明之前,我们写的内容都是在上面代码中给定的地方,其他的代码默认不动。
我们的计算机初心是为了更快的计算,而计算就要有数字,我们现在还没说怎么得到数字呢。
计算机的数字是通过变量来表示出来的,变量的得到形式是 int a;
。
它的意思是,定义一个整数类型的变量,名字叫 a
,其中 int
表示这个变量是整数类型,a
表示变量的名字叫 a
。
通俗的讲,我找来了一个盒子,这个盒子专门装整数,我给这个盒子取名叫 a
,一个盒子同时只能装一个整数。
我想把数字 3
放到 a
盒子里。
在程序里,方法是 a=3;
。
这里的等于号不是指左右两边相等,是指把右边的数字放到左边的变量里面,=
代表的含义是赋值。
=
左边必须是一个变量,右边必须是一个可以算出来数字的式子。
过了一会,我又想把数字 5
放到 a
盒子里。
那就改成 a=5;
在之后的代码中,只要提到单独的 a
,那么就代指 a
里面的数字。
比如我们写了
cout<<a<<endl;
最后会输出一个 5
,因为 a
是一个变量,这里的 a
指的是它里面的数字。
如果你想输出 a
这个字符,请加双引号。
如果你定义了变量,而没有赋值,就等于拿了个盒子过来,而没有规定里面是什么
赋值这个符号也可以玩出花的,比如,我们现在有两个变量
int a=3,b=5;
怎么才能交换两个变量里面的值呢?
交换两个盒子里的数字,可以先找来第三个盒子 c
,先把 a
里面的东西放到 c
里面,再把 b
里面的东西放到 a
里面,再把 c
里面的东西放到 b
里面。
int a=3,b=5,c=a;
a=b;
b=c;
2
3
但是如果是普通的盒子,你或许可以先把 a
里面的数字放到 b
,再把 b
里面的数字放回 a
。
而这个盒子有一个特性,同一时间里面只能存在一个数字,当你把 a
里的数字放进 b
,那么原来数字 b
里的值就被覆盖了,不知道原来是多少了。
如果是日常做题,你发现 a=3,b=5
,那只要反过来赋值,让 a=5,b=3
就算交换了。
而在计算机中,你设计的题目不仅仅是为了解决这一种情况。
上面这种方法,不管 a
,b
是多少,都能把它们的值进行交换。
把上面这个问题拓展成一个比较一般的情况,随便输入两个变量,交换这两个变量的值。
这里用到一个输入,写法和输出类似。
当你要输入的时候,先写一个 cin
。
然后在要输入的地方写一个 >>
,后面接要输入的值。
int a,b,c;
cin>>a>>b;//从键盘上输入数字 a,b
c=a;
a=b;
b=c;
2
3
4
5
这里的 cin
就是输入的意思,从键盘上输入变量 a
,b
中的值。
这里输入可以看作是,往某个你已经拿来的盒子里放一个数字进来,所以输入的 >>
后面必须接一个能装填数字的变量。
这里 //
后面的内容是注释,就是解释代码的含义,在 c++
中, //
后面的内容只能读代码的人看到,编译器会直接忽视它。
例如,在写了这份代码,编译运行,在黑框中输入了 114 514
。
然后程序读到两个输入的变量,开始执行,最后输出了 514 114
。
再举个例子:输入两个数字,输出它们的和:
思考题:尝试一下不借助额外的变量,交换 a,b
的值。
3.各种数据类型
因为存储一个数字,是需要空间的,比如你需要一个盒子来放整数,那你拿的盒子肯定不会是无限大的,所以这个盒子能放的数字也是有范围限制的。
比如说,你需要一个盒子,用来装 100
以内的自然数,那我给你一个能装 -200~200
的盒子就差不多了,再大就浪费了。
但是如果你想装 10000
以内的自然数,我就要给你换大盒子了。
short
类型的变量:存的数字的大小范围是
int
类型的变量:存的数字的大小范围是
long long
类型的变量:存的数字的大小范围是
还有一些特殊的类型:
bool
类型变量:只能存 0
或者 1
,0
代表结果为假,1
代表结果为真。
这个 bool
类型适用于只需要判断两种情况的时候,比如你投一个硬币,只想知道哪面朝上,可以定义一个 bool flag;
,在 flag
里面存投硬币的结果。
char
类型变量:用来存字符类型的变量,数值范围是
为什么字符类型还有数值呢?
计算机中,比如你想存一个字符 x
,但是计算机只有 0/1
格子,它只能存数字,所以计算机是靠每个字符和一个数字来对应(对应规则常用的是 ASCII
码),在字符变量中存储数字实现的。
比如,x
和 120
对应,那么你写 char ch=x;
的时候,变量 ch
里面真正存的其实是数字 120
,而 char
解释了这是一个字符类型的变量,所以当你对 char
输入 x
的时候,它自动把 x
对应成 120
放进 ch
,当你要输出 ch
的时候,它又自动把 120
解释为 x
进行输出。
如果你在代码中,只写一个 x
,那么一般代表的是有个变量的名字是 x
。
如果你想指的是字符x
,那么请加单引号 'x'
。
char x,y;
y='b';
x=y; // 这时候 x 里面是 'b'。
x='y' // 这时候 x 里面是 'y'。
char ch=120;
int num=120;
cout<<ch<<' '<<num<<endl;
//输出结果是:x 120
2
3
4
5
6
7
8
string
类型变量:字符串类型,里面存的是由许多个char
拼接成的字符串,每个字符也遵守对应规则。
如果不想表示变量,而想表示字符串,请加双引号。
string ab,cd;
cd="qaq";
ab=cd; //此时 ab 里面是 "qaq"。
ab="ac"; //此时 ab 里面是 "ac"。
2
3
4
double
类型变量:用来存储小数,没有范围限制,但是当你存的数字过大或者过小时,会产生一定的误差。
如果想要输出一个小数,可以用下面的句子来控制输出小数点后多少位数:
double a=1.234;
cout<<fixed<<setprecision(2)<<a<<endl;
2
这样最后就只会输出 1.23
。这个控制遵循四舍五入原则。
4.运算符
+
两个数字或变量或式子的结果相加,没啥好说的。
-
两个数字或变量或式子的结果相减,没啥好说的。
*
两个数字或变量或式子的结果相乘,没啥好说的。
/
左边的变量除以右边的变量,并得到不大于结果的最大的整数值(或者说,把小数部分、余数部分都舍去,只保留商)。
比如 7/3
得到 2
。
%
取模,左边的变量除以右边的变量,得到的余数,与乘除同一优先级,只能对整数操作。
比如 7%3
得到 1
,因为 7/3=2……1
。
取模通常是有用的,比如求一个数字的个位数是多少,直接将这个数字对 10
取模,就可以得到答案。
=
右边的值赋值给左边的数字。
这个也有注意事项,比如 a=b/c
。
如果 a
是 double
,b,c
是 int
,而且 b=5,c=2
呢?
因为 a
可以存小数,所以 a
里面应该是 2.5
吗?
其实不是因为 =
的运算顺序是,先把右边的数字给算出来,而右边的数字在运算过程中,没有任何小数参与其中,所以得到的结果是整数类型的。
也就是,先算出了 b/c
得到 2
,然后再把 2
放入 a
中。
解决办法是 a=(double)b/c
。
这个办法是强制类型转化,可以强行把 b
变成 double
类型,这样右边参与运算的数字中有了小数类型,结果就会保留成 2.5
。
或者可以是 a=1.0*b/c
。
注意:
double a,b=2.0;
int c=5,d=2;
a=c/d*b;
2
3
这样 a
最后的值是 4.0
。因为除法和乘法同一优先级,所以还是先算了 c/d
的值,然后再把算出来的值和 b
相乘,就变成 2*2
了。
++
,自增运算符,在一个变量前或者后都可以,写成 a++;
或者 ++a;
。
两者还是有区别的:
int a=3,b;
b=a++; //等价于 b=a,a=a+1;
b=++a; //等价于 a=a+1,b=a;
2
3
分不清的话,不写就完了,这玩意也不是必须要写。
--
,自减运算符,跟上面一样。
对于各种运算语句,有一种特殊的缩写方式:
a+=b
等价于 a=a+b
a*=b
等价于 a=a*b
这个法则对于 +,-,*,/,%
和将来要学的位运算 &,|,^,<<,>>
都适用。
对于自增 ++
和自减 --
暂时不适用。
上面提到的所有的语句,都是顺序结构的,你将语句们一条一条的写出来,用 ;
隔开,计算机就从头到尾一条一条执行。
5.练习方法
洛谷网站比较常用,上面有很多练习题:
注册登录洛谷之后,点击左侧的题单
洛谷这个题单并不太适合新手自己做,但是看完这章先做入门123还可以,建议做完之后问问学长应该练习哪里。
点开顺序结构,题目列表之后,会出现很多题目:
点开第一个题:
可以看到有样例输入输出,意思是,根据题目要求,你的代码在输入左边的之后,应该输出右边的结果(这个题特殊,不用进行输入),如果你的输出结果和右边不符,就是代码写错了。
当然如果相符也不一定代表你全写对了,这个样例只是一个例子,你设计的算法应该是能解决一类问题,而不是这一类问题中的某一个情况。
如果写完了交答案,点左上角的提交答案,然后会出现一个框,把代码完整的复制进去,提交就行了。
语言可以选自动识别,或者 C++17
。
如果有题目不会做,可以点击右边的查看题解(也可以找学长学姐问)。
当你提交之后,过几秒时间和给你代码评测结果,AC
代表答案正确, WA
代表答案错误,其他的情况之后再细讲,反正也代表答案错误,需要修改代码。
如果有的是 AC
有的是 WA
以及其他,说明代码在有的情况下是正确的,有的情况下是错误的,也需要修改代码,到全部 AC
为止。
另一个练习方案是看群文件里的《信息学奥赛一本通》,这本书也讲的很好课后有练习题,练习题提交网址在这里 http://ybt.ssoier.cn:8088/index.php
练习方式和上面的类似,不重复讲了。
练习题:
6.判断
如果我们要根据不同的数据选择不同的结果呢,比如看见红灯选择闯,看见绿灯选择等。
上面普通方法不能实现这个功能,必须要有一个能够进行判断,并做出不同行为的语句。
语句是这样的:
if(/* 判断条件 */)
{
//条件成立,执行这里的内容
}
else
{
//条件不成立,执行这里的内容
}
2
3
4
5
6
7
8
举个例子,判断一个数字是不是偶数:
int a;cin>>a;
if(a%2==0)
{
cout<<"是偶数"<<endl;
}
else
{
cout<<"不是偶数"<<endl;
}
2
3
4
5
6
7
8
9
成立和不成立范围内的代码要用花括号框起来,防止和外面的部分混淆。
如果没有花括号做区分,那么默认只有 if
后面一句话(以;
为准)是判断条件内部的语句。
注意 if
里面的内容,是不是偶数就看 a
除以 2
的余数就可以,判断这个余数是 0
还是 1
,而我们判断这个余数是不是 0
,用的是 ==
,因为 =
是赋值符号,在 c++
里, ==
才是判相等的符号。
一些其他的符号:
<
是否小于
<=
是否小于等于
>
是否大于
>=
是否大于等于
==
是否相等
!=
是否不相等
上面代码中的 else
可以省略不写,只写 if
部分。
注意一个坑点,不要把 ==
写成 =
,因为写成 =
也可以通过编译,而且不会报错。
int x;cin>>x;
if(x=5)
{
cout<<"yes";
}
2
3
4
5
如果你这么写,就会先把 x
赋值成 5
,然后判断 5
这个数字,当单独判断一个数字的时候,如果这个式子不等于零,则认为条件成立,否则认为条件不成立,所以这时候不管输入什么,都会输出 yes
。
如果不只有一个判断条件,用以下符号连接起来:
&&
并且,左右两边的条件同时成立,整个式子才成立,比如 a
是偶数且大于 4
,表示为 if(a%2==0&&a>4)
||
或者,左右两边的条件只要有一个成立,式子就成立,比如 a
是偶数或大于 4
,表示为 if(a%2==0||a>4)
!
取反,表示感叹号后面的式子的成立性取反,比如a
不等于 5
,表示为 if(!(a==5))
。
如果判断条件比较复杂,需要这几个符号组合,怎么记住它的优先级呢?
你记它干嘛,你想让哪个式子先判断,就在外面加个小括号。
if(a>5||(b>=3&&b<=5))
这个式子的含义是 a
大于五,或者 b
在 3~5
范围内。
特别注意:if
小括号后面是没有分号的,如果加了,就不会认为 {}
是 if
的一部分了,那么 {}
不管怎样都会按顺序执行到里面的内容。
P5709 【深基2.习6】Apples Prologue / 苹果和虫子
7.循环
计算机主要是为了解决重复工作,要是每个式子都得亲自去写,那不得累死。
所以计算机中有循环,可以多次做重复工作。
循环方式有很多种,挨个说
第一种:
while
循环,跟 if
的方式类似。
在 if
语句中,先判断条件是不是成立,如果成立,则进入第一个花括号中,按顺序执行语句,执行完毕跳出花括号,执行下一句。
在 while
中,不同之处是,执行完花括号中的语句,回到判断条件,如果成立,再执行一次,如果不成立,跳出花括号
因为每次执行完都会回到判断,所以一般要设置一个条件,在循环一定次数后,能结束循环。
比如,我们要输出 10
个 a
。
int cnt=1;
while(cnt<=10)
{
cout<<'a'<<endl;
cnt=cnt+1;
}
2
3
4
5
6
这样,在循环 10
遍之后,就不满足 cnt<=10
了,循环就会结束。
第二种:
do
{
//执行语句
}while(/*判断条件*/)
2
3
4
这种和第一种的区别是,第一种如果条件不成立,会直接不进行循环里的内容。
这种不管条件成立不成立,先执行语句一次,然后再进行判断,如果成立重新执行,不成立则结束。
这种和上面类似,而且不常用,不多讲了。
第三种:
for
循环,与前面不大相同。
for(int i=1;i<=10;++i)
{
cout<<'a'<<endl;
}
2
3
4
这个语句也能做到输出十个 a
的要求。
for
的执行逻辑是这样的:
这里大致按编号顺序执行,没有加说明文字的箭头就是,做完自己的地方就做下一个。
刚开始,先进行(1)的语句执行,执行完毕后,进行(2)的判断条件,如果成立,执行(3)的语句执行,如果不成立,跳出(5)。
(3)执行完之后,进行(4)的语句执行,(4)执行完之后,回到(2)判断条件。
for(int i=1;i<=10;++i)
{
cout<<'a'<<endl;
}
2
3
4
结合这里讲就是,先定义一个变量 i=1
,然后判断 i<=10
,然后执行 cout<<'a'<<endl;
然后执行 ++i
,再判断 i<=10
。
这样的好处是,给出了专门的定义控制循环次数的变量的位置(1),和操作循环次数的位置(4),可以把循环相关的东西都放在 for
后面,而花括号里面只用写真正的执行语句。
第二个注意的地方是,我们关注到 if
和 for
里面都有花括号,花括号相当于一个小的独立区域。
这个怎么解释呢:比如
{
int a=1;
}
cout<<a;
2
3
4
你这样写,编译器会报错,说你没有定义过变量 a
。
这是因为,花括号里面定义的变量,只能在花括号里面使用,在遇到右括号的时候,之前定义的变量全部销毁不存在了。
在循环语句中,连带 for
语句后面小括号里的内容,都属于 for
的一个小独立区域的范围。
for(int i=1;i<=n;++i)
{
}
cout<<i;
2
3
4
5
这样也会报错,因为 i
只在 for
的区域内存在。
但是花括号内是可以用在之前的外面定义的变量的。
int a=5;
for(int i=1;i<=n;++i)
{
cout<<a;
}
2
3
4
5
这样是可以的,因为可以认为外面是花括号的上级,如果在花括号内没找到变量 a
,就会去外面找。
int a=5;
for(int i=1;i<=n;++i)
{
int a=3;
cout<<a;
}
2
3
4
5
6
所以根据上面的逻辑,如果你在花括号里面也定义了变量 a
,输出的时候会优先认为 a==3
。
也就是里面定义的 a
可以和外面定义的 a
不是一个东西。
就好像有一个 a
名字的箱子,你房子里的和你卧室里的不是一个东西。
但是这样会报错,重复定义:
int a=5;
int a=3;
for(int i=1;i<=n;++i)
{
cout<<a;
}
2
3
4
5
6
所以,一个区域内不能定义多次同一个变量,而不同区域可以分别使用。
特别注意:while()
和 for()
的小括号后面也没有分号,原因同上。
对于上面有疑问的地方,先写个代码验证一下,看看跑出来是什么结果,还不理解就找学长学姐们问一下。
循环可以里面套用循环,注意不要两层循环的变量名混着用:
for(int i=1;i<=100;++i)
{
for(int j=1;j<=i;++j)
{
cout<<i*j<<' ';
}
cout<<'\n';
}
2
3
4
5
6
7
8
循环里的特殊语句:
continue
:跳过单次循环。
例如:求 n
的所有因子
for(int i=1;i<=n;++i)
{
if(n%i!=0) continue;
cout<<i<<endl;
}
2
3
4
5
如果 n
不是 i
的倍数,则跳过本次循环,判断下一个数字,否则输出这个数字。
这里的 continue
是跳到 ++i
部分。
break
:结束循环。
例如:判断 n
是不是质数
bool isprime=1;
for(int i=2;i<n;++i)
{
if(n%i==0)
{
isprime=0;
break;
}
}
2
3
4
5
6
7
8
9
如果 2~n-1
中有一个数字能整除 n
,则根据定义 n
不是质数,那么剩下的数字就没必要再判断了,可以直接结束循环。
这里的 break
是直接跳到 for
语句后面的的花括号 {}
之外。
这两个语句一般都是配合 if
语句使用的,根据不同的条件执行。
P1217 [USACO1.5] 回文质数 Prime Palindromes 这个题要动点脑子,不要挨个枚举数字然后判断,否则你会得到超时。
8.输入输出效率
如果你遇到了 TLE
标志,那么说明你运行超时了,试着降低代码中循环的运行次数,或者像下面这样优化效率:
C语言中的输入输出:
int x,y;
scanf("%d%d",&x,&y);
printf("%d %d\n",x,y);
2
3
其中scanf
是输入函数,其中输入两个整数要用两个占位符 %d
占位,然后在后面分别写入要读入的变量名字,用逗号隔开。
变量名字前面要加 &
。
printf
是输出函数,第一个双引号里面是字符串的意思,字符串中的内容都会原样输出,如果要输出变量,也要用占位符 %d
表示,表示这里要输出一个变量,变量按顺序在后面写出来,用逗号分隔。
其中双引号里两个 %d
中间的空格就是单纯的两个变量中间隔一个空格的意思,后面的 \n
是换行的标识符。
对于不同的类型,有不同的占位符,比如 int
类型是 %d
,long long
类型是 %lld
。
这种方法比较麻烦。
C++:
int x,y;
cin>>x>>y;
cout<<x<<y<<endl;
2
3
更方便,不用考虑格式和变量的类型。
一般情况下cin很费时间,因为 c++
为了兼容 c
语言设置了一个缓存区,可以手动解除这个缓冲区绑定:
加入代码:
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
2
就可以让cin和scanf一样快,但是注意不能cin,cout和scanf、printf、putchar、puts等混合使用。
也就是说,加上解绑之后,只用 cin
和 cout
就可以了,不需要也不能再用 scanf
等。
之前的换行语句是这样的:
int a,b,c,d;
cin>>a>>b;
cout<<a+b<<endl;
cin>>c>>d;
cout<<c+d<<endl;
2
3
4
5
先输入两个数字,比如3 5
摁下回车就会出现 8
。再输入 10 20
摁下回车就会出现 30
。
如果改成这样:
int a,b,c,d;
cin>>a>>b;
cout<<a+b<<'\n';
cin>>c>>d;
cout<<c+d<<'\n';
2
3
4
5
先输入两个数字,比如3 5
摁下回车,什么都不会出现。再输入 10 20
摁下回车,会出现两行,第一行是 8
,第二行是30
。
所以 endl
比一般换行操作还多了一个清空缓存区(把待输出的内容输出)的操作,而它的效率也往往更低,所以推荐用 \n
来换行,而不是 endl
。(除非你想即时查看运行结果)。
9.数组
请保证在此之前至少做过20道题。
在用循环批量处理了执行语句后,还需要有方法批量处理数据,避免一个一个变量定义的情况。
int a[100];
相当于定义了 100
个 int
变量,名字分别叫做 a[0],a[1],…,a[99]
。
其中,a
称为数组的名字,[]
中的数字称为数组的下标,可以通过数组的下标访问这一排数组中的某一个变量。
因为计算机从零开始,所以定义 int a[100]
之后,下标的范围是 a[0]~a[99]
。
但是我们看着不方便,所以一般都会稍微定义大一点,比如 a[105]
,然后只用 a[1]~a[100]
,反正计算机内存资源还是比较充足的,浪费几个变量的空间没关系。
下标可以用变量来表示:
在 int x=5
的前提下,a[x]
等价于 a[5]
。
当我们要存入一百个数据时:
int a[105];
for(int i=1;i<=100;++i)
{
cin>>a[i];
}
2
3
4
5
数组定义时,[]
中一定要是具体的数字,而不能是变量。
也就是说这个句子是非法的:int x=105; int a[x];
这种定义方法是非法的。
有的题目可能一开始不知道要输入多少个数据,比如他第一行输入一个数字 n
,第二行输入 n
个数字这样的。
这种就去找 n
的范围有多大,比如 n
的范围是 1~1000
,那么就提前定义数组大小 int a[1005];
保证比 n
的最大值还要大。
10.多维数组
数组不只可以像上面那样,写成编号 0~n
的变量,还可以给编号加层次:
定义 int a[15][15];
相当于定义了 15*15
个 int
类型的变量,范围是 a[0][0]~a[14][14]
。
同理,还可以 int a[15][15][15][15];
。
比如 n
行 m
列的网格,每个位置输入一个数字:
int a[105][105];
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
cin>>a[i][j];
}
}
2
3
4
5
6
7
8
(循环嵌套应该没有必要单独写一节了。)
数组的下标可以用来当作一种表示法,最常用的就是上面这一种:a[i][j]
表示网格中第 i
行,第 j
列上的数字是多少?
还有其他的表示法,比如:
int a[105];
for(int i=1;i<=n;++i)
{
int x;cin>>x;
a[x]++;
}
2
3
4
5
6
在这里,a[i]
可以用来表示数字 i
出现过多少次。
如果你在代码中得到了 RE
说明你可能访问到了没有定义的地方,比如你定义了 a[100]
,然后输出了 cin>>a[200];
。
P5731 【深基5.习6】蛇形方阵 这个也需要一点脑子
11.结构体
有时候一个物体不只有一个信息,比如一个学生有姓名,有学号等等。
假如一个学生有 n
条信息,要交换代表两个学生的变量,就要进行 n
次交换,非常不方便。
用结构体可以把相关的信息全部打包成一个变量:
struct student
{
string name;
int id;
};
student a,b;
2
3
4
5
6
其中 struct
表示我要定义一个结构体,student
表示这个结构体的名字,在定义完毕之后,student
这个单词就作为一种新的变量的名字了,地位和 int
是等价的。
然后一个花括号,里面表示你要打包在一起的信息,比如姓名(字符串类型)、学号(整数类型)等。
在右括号后面接;
,表示定义完毕。
然后就可以把 student
当作变量类型来定义变量了。
当我们想用到结构体中的某个信息时,用变量名+.
+信息名,如输出学生 a
的姓名:
cout<<a.name<<endl;
如果我们想整体交换两个变量,可以直接:
student a,b,c;
c=a;
a=b;
b=c;
2
3
4
结构体自带赋值功能,但是 +,-,*,/,%
还有输入输出等是不自带的(编译器不知道你自己定义的类型该怎么做加法等),需要你对结构体里的单个变量操作。
12.函数
同一项功能我们可能要用到很多次,比如选择两个数中较大的数字:
(当 if
和 for
后面不接 {}
时,默认只有后面一句话属于 if
或 for
)
int a,b,c;
if(a>=b) c=a;
else c=b;
2
3
这个功能我们可能要用很多次,每次都重新写不太方便,而函数可以把这个功能封装起来:
函数的形式:
int max(int x,int y)
{
int z=x;
if(x<y) z=y;
return z;
}
int main()
{
int a,b,c;
cin>>a>>b>>c;
c=max(a,b);
cout<<c<<endl;
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
前面提到过,main
是主函数,它也是一个函数,所以其他函数和它应该是并列关系。
在上面的函数里,int
是函数类型,表示使用这个函数之后会得到一个整数类型的数值。
其他函数类型还有:
bool
使用这个函数之后得到一个 bool
类型的数值。
其他变量类型也是如此。
void
使用这个函数之后不得到任何数值。
这个适用于比如交换两个变量的值这种,我们只需要交换完成,不需要有任何额外结果。
max
是函数的名字,也是使用这个函数的方法。
(int x,int y)
后面的小括号里面的是函数的参数,表示这个函数需要用哪些信息进行操作,这里表示要有两个 int
类型的参数来操作。其中 x,y
表示这两个 int
类型在这个函数里分别叫做 x
和 y
。
而参数在传入的时候,只要变量类型一致就可以,如主函数中调用 c=max(a,b)
其中 a,b
都是 int
类型,然后 a
的值会传入 x
里面,b
的值会传入 y
里面。
你还可以:
int c=max(a,5);
这样 a
的值会传入 x
里面,5
会传入 y
里面。
在函数最后,有一个 return z;
表示把 z
里面的值返回函数调用的地方,也就是最后 c=max(a,b);
之后,等式右边的值就变成了这个 z
的值。
这里数值在传入的时候,x
和 a
并不是同一个变量,a
是一个盒子,x
是另一个盒子,只不过 x
这个盒子在拿过来的时候,看了一眼 a
里面的数字是多少,然后自己也装了个一样的,所以对变量 x
进行操作的时候,并不会改变 a
里面的值。
也就是说:
void work(int x)
{
x=x+5;
}
int main()
{
int a=3;
work(a);
cout<<a<<endl;
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
这样最后还是会输出 3
,因为改变的 x
并不是 a
本身。
如果你想要在函数里改变传入的数字的值:
void work(int &x)
{
x=x+5;
}
int main()
{
int a=3;
work(a);
cout<<a<<endl;
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
这样输出的就是 8
了。
将传递参数的部分的类型后面加一个 &
,表示要将原本的变量传过来,这个叫作传引用或者传址。
相当于一份资料,只说需要的情况下,别人会给你一份复印件,在你特别声明后,别人才会给你原件。
在你自己定义的 swap
函数中,交换两个函数的值,如果不传引用,那么只是两份复印件换来换去,真正要换的东西没变。
同理,主函数也是一个 int
类型的函数,它的返回值是最后一句 return 0;
表示程序正常结束,如果返回值非零,代表异常结束。
函数可以在一个函数里面调用另一个已经定义了的函数。
int min(int a,int b)
{
if(a>=b) return b;
return a;
}
int max(int a,int b)
{
return a+b-min(a,b);
}
2
3
4
5
6
7
8
9
也可以在函数里面调用自己,叫作递归:
int fac(int n)//求 n! 是多少
{
if(n==1) return 1;
return n*fac(n-1);
}
2
3
4
5
跟循环同理,递归也要有一个终点,不然就会永远进行下去。
递归适用于解决一些可以把问题规模变小,但是问题本身没变的情况,比如求 n!
,我们知道 n!=n*(n-1)!
,那么就可以把问题规模从 n
变到 n-1
,而问题仍然是求阶乘,直到 1!
不需要再继续求解。
比如,求 ans = fac(3)
,这个函数会先让 ans=3*fac(2)
,然后 ans=3*2*fac(1)
,这时 fac(1)
的值可以直接得到,刚才引申出来的函数一层一层的返回算出来的值 1,1*2,1*2*3
,变成 ans=3*2*1
。
变量或数组可以定义在函数内部,也可以定义在函数外部
#include<bits/stdc++.h>
using namesapce std;
int a;
int aa[5];
void love()
{
int b;
}
int main()
{
int c[3];
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
这样,变量 a
和数组 aa
里面一开始都是 0
,而变量 b
和数组 c
里面一开始是随机数。
定义在外面的变量,一开始就会被清理成 0
,而定义在函数里面的变量,一开始里面是随机的。
13.字符串
计算机毕竟只是存数字的,所以对各种字符和字符串,都有着一些额外的操作。
对于string
类型,输入的时候是以空格或换行来区分两个变量的。
string s;
cin>>s;
2
如果想得到字符串 s
的长度,使用库函数 int len=s.length();
。
此时,字符串中的每一位存在 s[0]~s[len-1]
中。
其中,s[len]
里面一般存着一个字符 \0
,表示这一位是一个字符的结尾,这一位通常我们用不到,但是它真实存在。
字符串中 +
代表字符串拼接:
string s,t;
cin>>s>>t;
s=s+t;
s+=t;
2
3
4
两种加法均表示把 t
拼在 s
的后面变成一个长字符串。
但是实现有区别,第一种是先把 s
和 t
都拿出来,拼成一个字符串,再把这个字符串赋值给 s
。
第二种是直接把 t
拼在 s
后面。
显然第一种耗时要多一点。
char s[105]
也可以当一个字符串用,但是略有不同。(字符数组也可以当字符串用)
输入一个长度为 n
的字符串:
char s[105];
cin>>s;
int n=strlen(s);//这个是字符数组求字符串长度的函数。
2
3
同样,s[0]~s[n-1]
中存的是字符,s[n]
中存的是 \0
表示结束标志,这一位不能被乱改。
如果修改了 s[n]
里面的 \0
,那么各种操作就不知道字符数组在哪里结束了,比如 n=strlen(s)
的时候就会得到错误的结果。
字符数组就没有 +
操作了。
其他的字符串函数可以自己上网了解。
字符在计算机中的存储是用一个数字对应一个字符的形式,对应的规则一般是采用 ASCII
码表,可以上网查阅一下。
例如 a~z
对应的数字是 97~122
而 A~Z
对应的是 65~90
。
如果有一个小写字母,想把它变成大写字母,可以:
char ch;
cin>>ch;
if(ch>='a'&&ch<='z') ch=ch-'a'+'A';
2
3
字典序:
字符串中比较大小的一种方法,两个字符串比较大小,先比较第一位,第一位小的字符串更小,如果前面都相同,再比较下一位。
如果前面都一样,那么长度短的字符串字典序更小。
如果长度也一样,那么两个字符串相等。