C++入门小教程
c++ 小教程
前言
- 由于其教程作者经历了很久的时间,因此码风可能有较大的差别。
基础语法
Hello world
在配完一个新环境的时候,为了检测配置有没有问题,我们一般都会以“输出Hello world”来检测。现在,我们就用 c++ 来写一个 Hello world 的程序扒 😄
#include<iostream>
int main() {
std::cout << "Hello world!" << endl;
return 0;
}
看不懂?没关系,我们一步一步看。
首先是最醒目的 #include<iostraem 。# 代表宏定义,这是要告诉编译器,这一行要优先编译。include 代表要让 c++ 提前加载尖括号里面的 iostream 文件。
如果你需要自己定义一个自己的头文件,请把它写成这样:
#include "head.h"
" 代表 c++ 编译器应该首先在当前目录查找,找不到才去标头路径查找,而尖括号内的头文件无法实现此功能。
然后是一行 int main() 。这里的 int 是一种数据类型,是 integer 的缩写,他代表这个函数的返回值是整形。 main 就是主要的的意思,这里是程序运行的入口。
括号里面一般是没有参数的,不过你可以写成 int main(void) 或 int main(int argc,char *argv[](**argv)) , 原因请问度娘。
然后是一对花括号,程序员一般把它写在一行的末尾,最好空一行;但是如果你是要学信奥的话一般把它写在第二行的最左边。括号里面的就是程序执行的内容啦!
std::cout 的意思是在命名空间 std 里面调用对象 cout 。命名空间是为了防止一些函数或对象重名才来使用的。当你要用到多次 std 的时候,你最好在前面加一句 using namespace std; ,这样后面所有有关 std 的东西就可以不写了!cout 是 c++ 的标准输出,需要用 << 运算符进行输出,而引号里面的就是字符串。cout 会把字符串直接输出,显示在屏幕上。cout 还可以连续输出,用 << 连接。endl 就是 cout 里面的换行,也可以用字符 '\n' (字符是单引号)来代替,但 endl 是可以刷新缓冲区的,最好用 endl 。
下一行 return 0; 就是指这个函数是要返回零的,如果运行到这一行,不管后面还有什么代码,直接结束主函数。(main 函数不可以返回其他值哟~)
关于 c++ 写代码的好习惯
- 每一行都需要一个分号,除非这一行特别长需要换行;
- 变量最好要有实质性的内容,如果没有就用单个字符代替,最好不要起特别长的名字;
main函数的末尾一定要return 0;,虽然有些编译器会帮你补上,但有风险;cin一定是>>,cout一定是<<;- 最好代码中空点格,虽然看着很松散,但是调代码特别好调(亲身经历)。
输入、运算符
上面一段只是输出一些固定的东西,而生活是千变万化的,所以我们就需要输“入”。c++ 为我们提供的标准输入是 cin ,而 cin 的运算符是 >> ,与 cout 恰恰相反。(不要搞混了哦!)下面就是 a+b 问题的代码:
#include<iostream>
using namespace std;
int main(void) {
int a, b;
cin >> a >> b;
cout << a + b << endl;
return 0;
}
现在这个程序与刚才的是不是不一样了呢?
首先我们先来看 int a, b; 这一行,这表示我们声明了两个整形变量 a 和 b ,逗号是用来分隔的。当然,你也可以写成这样:
int a;
int b;
接着我们要输入他们,也就是 cin >> a >> b; 这一行。
最后我们要输出它们的和,这里用 + 运算符:
cout << a + b << endl;
最后结束 main 函数:
return 0;
怎么样?是不是特别简单?
c++ 的运算符:
| 名称 | 符号 | 作用 |
|---|---|---|
| 加法运算符 | + | 返回两个值的和 |
| 减法运算符 | - | 返回两个值的差 |
| 乘法运算符 | * | 返回两个数相乘的积 |
| 除法运算符 | / | 返回被除数除以除数的商 |
| 模运算符 | % | 返回余数 |
| 逻辑与 | && | 若两边都为真返回真,否则返回假 |
| 逻辑或 | \ | \ |
| 逻辑非 | ! | 真返回假,假返回真 |
| 赋值运算符 | = | 将右边的值赋给左边,并返回赋完的值 |
| 加运算符 | += | 将自己的值加上右边的值,并返回自己 |
| 减运算符 | -= | 将自己的值减去做右边的值,并返回自己 |
| 乘运算符 | *= | 将自己的值乘上右边的值,并返回自己 |
| 除运算符 | /= | 将自己的值除上右边的值,并返回自己 |
| 模等于运算符 | %= | 将自己的值模上右边的值,并返回自己 |
| 成员运算符 | . | 访问类中的成员 |
| 长度运算符 | sizeof() | 返回括号中的字节长度 |
| 位与运算符 | & | 返回二进制与的结果 |
| 位或运算符 | \ | |
| 位异或运算符 | ^ | 返回二进制异或的结果 |
| 取反运算符 | ~ | 返回二进制取反的结果 |
| 位复合运算符 | 在位运算符后面添上= | 将当前变量对右边的值做相应的位运算并返回 |
| 下标运算符 | [ ] | 返回数组中的某一位 |
| 三目运算符? | ? | 类似于 if |
| 地址运算符 | &(只有一个参数) | 返回当前变量的内存地址 |
| 自增后缀运算符 | 变量++ | 先用这个变量,用完之后 +1 |
| 自减后缀运算符 | 变量-- | 先用这个变量,用完之后 -1 |
| 自增前缀运算符 | ++变量 | 先把这个变量 +1,然后再用 |
| 自减前缀运算符 | --变量 | 先把这个变量 -1,然后再用 |
| 左移运算符 | << | 二进制位左移,低位用 0 补齐 |
| 右移运算符 | >> | 二进制位右移,低位忽略 |
怎么多?别着急,用的多自然就熟了。
C 语言输入输出(c++ 兼容)
在 c++ 之前的 C 语言,也是有输入输出的,但是由于 C 语言功能没有 c++ 丰富,所以输入输出自然也不一样。C 语言输入是这样的:
scanf("控制符",&变量一,...);
输出是这样的:
printf("控制符",变量一,...);
这是 C 语言的 a+b:
#include<stdio.h>
using namespace std;
int main(void) {
int a, b;
scanf("%d%d",&a,&b);
printf("%d",a+b);
return 0;
}
可以看出,输入输出发生了很大的变化。首先头文件变了,变成了 stdio.h (在 c++ 里面也可以写成 cstdio)我们慢慢来看:
scanf 函数里面的控制字符串里面的每一个 %... 都是对应着后面的参数的,%d 就是代表下一个输入的值我们要把它们转成整数。后面的参数每一个一定都要加上 & ,因为他要改变实际的值是需要地址的,而 C 语言没有引用变量(后面会讲)。
printf 的格式与 scanf 类似,都是一样的,不过变量不用加 & 。
scanf 的输入控制符不只有 %d ,还有这些:
| 符号 | 作用(也可做输出用) |
|---|---|
| %d | 读入整数 |
| %ld | 读入 long 整数 |
| %lld | 读入 long long 整数 |
| %ulld | 读入 unsigned long long 整数 |
| %c | 读入一个字符 |
| %s | 读入一个字符串(char*) |
| %i | 读入\(i\) 后面的数的字节的整数 |
| %x | 读入一个十六进制整数 |
在 % 后面的整数 |
向右对齐 |
在 % 后面的负整数 |
向左对齐 |
| 直接写符号 | 表示读入对应的符号 |
| %f | 读入单精度小数 |
| %lf | 读入双精度小数 |
保留小数
在保留小数方面,C 语言输入明显由于 c++:
#include<stdio.h>
using namespace std;
double a;
int main(void) {
scanf("%lf",&a);
printf("它保留的三位小数是:%.3lf",a);
return 0;
}
3.1415926535
它保留三位小数是:3.141
使用 C++ 风格进行小数对齐:首先我们需要使用 iomanip 库,然后在输出的时候 << setprecision(x) 表示保留 \(x\) 位。值得注意的是,必须在前面 << fixed,这时告诉 cout 我们保留的是小数位数而非整数。
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
double pi = 3.1415926535897;
cout << fixed << setprecision(3) << pi << '\n';
return 0;
}
对齐
比如将一个数 \(11\) 向右对齐 \(5\) 位,输出就是(如下)。
11 (三个空格)
使用 C 就简单多了,可以这样写:
printf("%5d", 11);
C++ 比较麻烦,同样需要用到 iomanip 库,可以使用 setw(int x) 函数来进行控制:
#include <iomanip>
cout << setw(5) << 11;
在保留小数、向右对齐方面,一般用 scanf 和 printf ,其他用 cin 和 cout 。
PS:
在输入多的时候,cin 由于不仅需要检测参数的类型,还要与 scanf 绑定,因此特别慢。这时候你就需要用 scanf 了。但是也有两行可以优化 cin 的代码:
std::ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
std::ios::sync_with_stdio(bool x)表示是否与 C 的缓冲区绑定,赋为 \(0\) 之后你就不能混用 C++ 和 C 的读写了;std::istream/ostream::tie(bool x)表示是否与cin/cout的缓冲区绑定,因为正常情况下当cin的缓冲区改变一次cout的缓冲区也要跟着改变,因此关闭缓冲区了之后速度回得到提升。
数据类型
c++ 也提供了丰富的数据类型,下面是基础数据类型:
| 关键字 | 名称 |
|---|---|
| int | 整形 |
| char | 单字符型 |
| bool | 布尔型 |
| float | 单精度小数 |
| double | 双精度小数 |
下面是类型修饰符以及可以修饰的数据类型:
| 修饰符 | 可修饰类型 | 作用 |
|---|---|---|
long |
int , long int , double |
让数的精度变高 |
signed |
int , long int , long long int , double |
声明这个类型既可以存正数也可以存负数 |
unsigned |
int , long int , long long , double |
声明这个类型只能存正数 |
下面是其他头文件中拓展的数据类型:
| 头文件 | 类型名 | 作用 |
|---|---|---|
cstring 或 string.h |
string |
存储字符串 |
| 请百度 | 请百度 | 请百度 |
现在我们试验一下:
#include<iostream>
using namespace std;
int a;
char ch;
bool f;
float b;
double c;
int main(void)
{
cin >> a >> ch >> f >> b >> c;
cout << "整数是" << a << "字符是" << ch << "bool型是" << f << "单精度浮点型是" << b << "双精度浮点型是" << c << endl;
return 0;
}
分支结构
if :如果括号里的条件满足,那么执行里面的内容。
if...else :如果 if 条件满足执行里面的内容,否则执行 else 里面的内容。
if...else if...else :如果 if 条件满足执行里面的内容,否则如果 else if 里面的条件满足,执行 else if 里面的内容, 否则执行 else 里面的内容。
注意:
if 可以没有 else if 和 else ,else 和 else if 也可以没有其中的一个,但是 else if 和 else 必须有 if !
例如以下代码:
else if (...) {
...;
} else { }
无法通过编译。
/project/main.cpp: In function 'int main()':
/project/main.cpp:6:3: error: 'else' without a previous 'if'
else if (...) {
^~~~
循环结构
while
while(表达式) :如果表达式满足,那么执行内容,一直重复。
--->如果不满足--->跳出循环
|
如果条件满足--->执行循环
^ |
|_________________|
比如打印 \([1,10]\) 的整数:
int i = 1;
while (i <= 10) {
cout << i << ' ';
}
do-while
do...while(表达式) :首先执行一次内容,然后判断表达式有没有满足,如果满足重复步骤。
这个语句可以说是下水道语句,因为几乎没有什么人用。
for
for(表达式一;表达式二;表达式三) :这是最复杂的循环语句。
我们可以实验一下:
for (int i = 1; i <= 10; i++) {
cout << i << ' ';
}
1 2 3 4 5 6 7 8 9 10
每一次的循环:
- 最开始 \(i\) 附了一个初始值 \(1\);
- \(i=2\) 并输出,然后
i++; - \(i=3\) 并输出,然后
i++; - \(i=4\) 并输出,然后
i++; - \(i=5\) 并输出,然后
i++; - \(i=6\) 并输出,然后
i++; - \(i=7\) 并输出,然后
i++; - \(i=8\) 并输出,然后
i++; - \(i=9\) 并输出,然后
i++; - \(i=10\) 仍然满足条件,并输出,然后
i++; - \(i=11\) 不满足条件,跳出循环。
这里 \(i\) 也就相当于遍历完了 \([1,10]\) 当中的每一个整数,然后输出。大概就是这样的,比较适合从 \(1-n\) 逐步递增/递减的场景,需要多多理解。
胖头鱼式 for 循环
例如一道题,你需要输出 \(n\) 个 \(114514\),使用 while 如下:
cin >> n;
int i = 1;
while (i <= 10) {
cout << 114514 << '\n';
i++;
}
使用 for 如下:
cin >> n;
for (int i = 1; i <= n; i++) {
cout << 114514 << '\n';
}
使用 胖头鱼式 for 循环:
for (cin >> n; n; n--) {
cout << 114514 << '\n';
}
或者:
for (cin >> n; n--; cout << "114514\n") {
}
数组
我们之前都是用单个单个的变量来输入的。那如果现在要你输入 \(n\) 个数呢?我们就 for(int i=1;i<=n;++i) ,但如果你输完之后要在输出呢?如果 \(n\le 10000\) 呢?难道你还需要定义 \(10000\) 个变量吗?
这是我们就需要运用到“数组”了。数组的定义方式是这样的:
数据类型 数组名[数组需要的元素大小]
在用的时候,你只需用下标运算符 [] 就可以访问数组了。注意:
- 数组下标是从零开始的,不过你也可以从一开始赋值;
- 数组下标没有负数;
- 下标可以使任何变量,甚至是别的数组的其中一个元素;
- 数组下标只能是
int类型。
试验一下:
#include<iostream>
using namespace std;
int main(void)
{
int n;
cin >> n;
int a[n];
for(int i=0;i<n;++i)
{
cin >> a[i];
}
for(int i=n-1;i>=0;--i)
{
cout << a[i] << " ";
}
return 0;
}
3
1 2 3
3 2 1
PS:
在 c++ 中,数组大小可以是一个变量,但是只能是一维的,而且不太稳定,本渴鹅亲测过。最好开一个固定的数组来用,虽然内存多了一些,但是稳定。
数组也是可以赋初始值的,如果你回赋初始值的话,[] 内的大小是可以不用写的。比如:
int a[] = {1,2,3,4,5};
多维数组
在 c++ 中数组也是可以多维的。比如这样:
#include<iostream>
using namespace std;
int a[1005][1005];
int main(void)
{
int n, m;
cin >> n >> m;
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
cin >> a[i][j];
}
}
for(int i=n;i>=1;--i)
{
for(int j=m;j>=1;--j)
{
cout << a[i][j] << " ";
}
cout << endl;
}
return 0;
}
2 3
1 2 3
2 3 4
4 3 2
3 2 1
函数
在 c++ 中,除了主函数和系统函数,你还可以定义自己的函数,我们可以把函数看做一个对变量做操作的方法。函数定义格式是这样:
返回值 函数名(参数类型 参数名,...)
{
函数体;
}
比如,我们可以定义一个返回 a+b 的值的函数:
int plus(int a,int b)
{
return a+b;
}
然后在主函数内调用它:
int main(void)
{
int a, b;
cin >> a >> b;
cout << plus(a,b) << endl;
return 0;
}
这里就相当于我们把实际参数 a 和 b 的值赋给了函数里面的形式参数 a 和 b ,然后以形式参数 a 和 b 的和返回了过来, 然后输出。
如果你的函数只是输出一些内容,没有返回值的话请把它的返回值写成 void 类型。
递归函数
先看 \(n\) 的阶乘的代码:
#include<iostraem>
using namespace std;
int work(int a)
{
if(a<=1)return 1;
return a*work(a-1);
}
int main(void)
{
int n;
cin >> n;
cout << work(n) << endl;
return 0;
}
看不懂?没关系,我们慢慢的把实际的算式捋出来(假设 \(n\) 是 \(4\) ):
1.首先,我们往函数里面放了一个值 \(4\) ,函数返回了 \(a\times work(a-1)\) 表达式现在是 \(4\) ;
2.然后,work 函数又返回了 \(a\times work(a-1)\) ,与前面的数相乘,就是 \(4\times 3\) ;
3.接着,里面的 work 函数又返回了 \(a\times work(a-1)\) ,与前面的数相乘, 就是 \(4\times 3\times 2\)
3.最后,最里面的 \(a\) 已经是 \(1\) 了,work 函数返回 \(1\) ,整个调用函数产生的式子就是 \(4\times 3\times 2\times 1\) ,正好是 \(n!\) 。
这就是一个最简单的递归调用过程,需要多练才能熟哟~
指针
- Update: 进行了一次大升级,内容更多了。
指针,是C语言中的一个重要概念及其特点,也是掌握C语言比较困难的部分。指针也就是内存地址,指针变量是用来存放内存地址的变量,在同一CPU构架下,不同类型的指针变量所占用的存储单元长度是相同的,而存放数据的变量因数据的类型不同,所占用的存储空间长度也不同。有了指针以后,不仅可以对数据本身,也可以对存储数据的变量地址进行操作。
指针描述了数据在内存中的位置,标示了一个占据存储空间的实体,在这一段空间起始位置的相对距离值。在 C/C++语言中,指针一般被认为是指针变量,指针变量的内容存储的是其指向的对象的首地址,指向的对象可以是变量(指针变量也是变量),数组,函数等占据存储空间的实体。 ——百度百科
光说说不懂,还是实践一下吧。👩💼
在介绍指针之前,我们需要了解一个特殊的运算符 & 。注意这里不能与逻辑与 && 混淆,& 只有一个!!!之前也讲了,& 也表示位或运算,但那个东西太复杂了,后面再讲。& 一般会放在一个对象、变量的前面(注意只有一个,而且是变量在后,不是符号在后)。它的名字叫做 取地址符。🙉
如果你使用 cout ,那么取出来的地址就会被识别成十六进制输出;如果你使用 printf ,那么你是用的 %d 只会输出整数。想要输出十六进制,需使用 %x 。现在看示范程序:👀
#include <iostream>
using namespace std;
int a = 1145141;
int main() {
cout << &a << '\n';
return 0;
}
(可能)0x403010
接下来就是关键性的问题了,为什么是可能呢?这就要说说最底层的知识了。
计算机的内存是不断波动的,因此当你在不同情况下,程序申请的内存都有可能不相等。因此无法确定一个准确的值。
现在,有了这些前缀芝士🧀,指针的学习就可以开始了!
指针的定义🖕
指针的定义与变量差不多,指向什么类型的东西就用什么类型名定义。定义规则为:
(指针指向的类型) *(指针名);
就好比定义了:
int a;
那么指向 \(a\) 的指针就该定义成
int *p;
指针里面装的是地址,因此我们可以定义指针 \(p\) 指向 \(a\) :
int a;
int *p = &a;
但是——————* 运算符只对一个指针变量有效。因此这样定义:
int* a, b, c;
其实只有 \(a\) 是指针变量,其他的都只是普普通通的整数变量而已。所以,建议大家尽量把 * 写到靠近变量的那边,而非类型那边。
int *a, b, c; // 更好的方式是:int b, c, *a;
而 *p 就是通过指针 \(p\) 来访问 \(p\) 指向的值。这个值是可以更改的,也可以输出。
现在,使用指针来控制变量的值吧!
int a;
int *p = &a;
*p = 1145141;
cout << a << ' ' << *p << '\n';
1145141 1145141
我们可以发现,a 和 *p 输出出来其实是同一个数,而第三行我们对 \(p\) 指向的值,也就是存储 \(a\) 的值改为了 \(114514\) ,因此原本没有值的变量 \(a\) 被赋予了值。现在还有一段代码:
int *n = new int;
这段代码又是做什么的呢?这里运用了内存申请符 new 来申请了一个 int 大小的空间,并把这个地址赋予了 \(n\) 。注意:不要这样做:
int n = new int; // 这里的n是变量,定义时就已经有了空间,这样子会引发编译错误。
现在想必大家已经知道指针的只因本用法了吧?后面我会继续更新更高难度的指针操作,只因请期待!
类与对象
在 c++ 中,有很多基础数据类型。但我们能不能在这些基础数据的基础上,定义出我们自己的“类型”呢?当然可以,包括 cstring 里的 string ,iostream 里面的 istream 和 ostream,都是“类”。现在,我们来定义一个学生的类,类中有设置属性、获取属性的一些方法:
#include<iostream>
#include<cstring>
using namespace std;
class student
{
private:
int score, id;
string name;
public:
int newStudent(string name, int score, int id)
{
this->score = score;
this->id = id;
this->name = name;
}
string getName(void)
{
return this->name;
}
int getScore(void)
{
return this->score;
}
int getId(void)
{
return this->id;
}
}haokee("haokee", 100, 7), bluemoon("bluemoon", 0, 1);
int main(void)
{
cout << haokee.getName() << ":score:" << haokee.getScore() << "id:" << haokee.getId() << endl;
cout << bluemoon.getName() << ":score:" << bluemoon.getScore() << "id:" << bluemoon.getId() << endl;
return 0;
}
可以看到,类里面分了两大块:private 和 public 。其实应该是三大块:
private :只有在类里面才可以访问的成员。
protected :只有在类里面或子类才可以访问。
public :在类内、类外或子类都可以访问。
这里我们写了一些成员函数用来返回当前类中的一些信息。this 指针指向的就是当前对象,而类的末尾我们定义了 haokee 和 bluemoon 两个对象, 并给了他们初始值。然后我们通过调用成员函数的方式输出了他们的信息。
在调用对象中的成员的时候,我们需要使用 . 运算符,这样程序才能知道是调用哪个对象中的那个成员。
->运算符:他是通过指针来访问类中的元素,等同于:(*this).name。
重载运算符
在类里面,我们不仅可以定义成员函数,还可以重载运算符。当符号两边的参数符合重载的参数时,就会自动调用重载的内容。
- 注意:如果在类里面重载,那么是不写第一个参数的,第一个参数就是
this指针。
我们可以在刚才那个程序的 main 的前面写:
ostream &operator<<(ostream &out, const student &stu)
{
cout << stu.getName() << ":score:" << stu.getScore() << "id:" << stu.getId() << endl;
return in;
}
这里我们重载了左参数为 ostream 、右参数为 student 的 << ,每当被调用的时候,他就会执行里面的内容,返回 out 对象是因为这样就可以连续输出了。所以输出就可以直接写为:
cout << haokee << bluemoon;
继承
在 c++ 里面,可以定义类的“儿子”。比如我们定义一个 haokee 类:
class haokee {
public:
void out(void) {
cout << "我是haokee" << endl;
}
};
然后我们在定义一个 haokee 类的派生类 son :
class son : public haokee {
}S;
由于 S 是 son 类,是 haokee 类的“儿子”,所以 S 可以调用原来基类的函数:
S.out();
这时,protected 就有用了。你可以在派生类内访问一些成员,但是在类外无法访问,达到“保护”的效果。
class haokee {
protected:
int x = 1;
public:
void out(void) {
cout << "我是haokee" << endl;
}
};
class son : public /*标记*/ haokee {
public:
void out2(void) {
this->out(); //访问原类的公开成员
cout << x; //访问原类的保护成员
}
}S;
S.out2();
注意到继承时的注释标记没有?这里的 public 表示的是 ”继承类型“。一般都写 public ,但是也可以写其他的:
| 继承类型 | 符号 | 规则 |
|---|---|---|
| 公有继承 | public |
除了私有成员,都可以访问 |
| 私有继承 | private |
基类的公有成员和保护成员都变成私有成员 |
| 保护继承 | protected |
基类的公有成员的保护成员都是派生类的保护成员 |
在调用基类的函数之上,我们也可以对基类的函数进行重写:
class haokee {
public:
void print(){cout << "我是好渴鹅";}
};
class bluemoon : public haokee {
public:
void print(){cout << "我不是好渴鹅,我是蓝月";}
}bl;
int main() {
bl.print(); //我不是好渴鹅,我是蓝月
return 0;
}
多继承
在 c++ 中,一个派生类也可以继承多个基类,中间用 , 分隔。
class 派生类 : 继承类型 , 继承类型 基类, 继承类型 基类, ……
virtual
虚函数
class F {
public:
F(){}
void print(){cout << "父类方法";}
virtual ~F(){}
};
class S : public F {
public:
S(){}
void print(){cout << "派生类方法"} //重写了父类的方法
};
int main() {
F *f = new S();
f->print();
return 0;
}
可以看到,这里我们的指针类型与实际指向的地址的类型是不一样的。这是输出:
基类方法
但是我们明明实际指向的是派生类的指针,c++ 把他误以为是基类给输出了。现在我们把基类的函数改成这样:
virtual void print()
输出就会变成:
派生类方法
很显然,添加 virtual 可以区分基类和派生类的同名方法。
注意:
- 只用在基类加上
virtual关键字,派生类会自动加上; - 析构函数必须也改成虚函数,不然会有对象释放错误的问题;
- 重写是对其他类的函数“改造”,而重载是在同一类同函数的不同参数列表的多个函数。
算法
照着附录(在后面)说的方法,打开洛谷(没登录的要先登录),点击 题库 页面,就可以看到一大堆题了。
我们点进第二题:\(\texttt{A+B Problem}\) ,就可以看到题面了。
A+B Problem
题目背景
强烈推荐新用户必读帖。
不熟悉算法竞赛的选手请看这里:
算法竞赛中要求的输出格式中,不能有多余的内容,这也包括了“请输入整数 \(\bm a\) 和 \(\bm b\)” 这一类的提示用户输入信息的内容。若包含了这些内容,将会被认为是 Wrong Answer,即洛谷上的 WA。在对比代码输出和标准输出时,系统将忽略每一行结尾的空格,以及最后一行之后多余的换行符。
若因此类问题出现本机(看起来)AC,提交 WA 的现象,请勿认为是洛谷评测机出了问题,而是你的代码中可能存在多余的输出信息。用户可以参考在题目末尾提供的代码。
另外请善用应用中的在线 IDE 功能,以避免不同平台的评测中所产生的一些问题。
还有一点很重要的是,请不要在对应的题目讨论区中发布自己的题解,请发布到题解区域中,否则将处以删除或禁言的处罚。若发现无法提交题解则表明本题题解数量过多,仍不应发布讨论。
题目描述
输入两个整数 \(a, b\),输出它们的和(\(|a|,|b| \le {10}^9\))。
注意
- Pascal 使用
integer会爆掉哦! - 有负数哦!
- C/C++ 的 main 函数必须是
int类型,而且 C 最后要return 0。这不仅对洛谷其他题目有效,而且也是 NOIP/CSP/NOI 比赛的要求!
好吧,同志们,我们就从这一题开始,向着大牛的路进发。
任何一个伟大的思想,都有一个微不足道的开始。
输入格式
两个以空格分开的整数。
输出格式
一个整数。
样例 #1
样例输入 #1
20 30
样例输出 #1
50
提示
本题各种语言的程序范例:
C
#include <stdio.h>
int main()
{
int a,b;
scanf("%d%d",&a,&b);
printf("%d\n", a+b);
return 0;
}
C++
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int a,b;
cin >> a >> b;
cout << a+b << endl;
return 0;
}
Pascal
var a, b: longint;
begin
readln(a,b);
writeln(a+b);
end.
Python2
s = raw_input().split()
print int(s[0]) + int(s[1])
Python3
s = input().split()
print(int(s[0]) + int(s[1]))
Java
import java.io.*;
import java.util.*;
public class Main {
public static void main(String args[]) throws Exception {
Scanner cin=new Scanner(System.in);
int a = cin.nextInt(), b = cin.nextInt();
System.out.println(a+b);
}
}
JavaScript (Node.js)
const fs = require('fs')
const data = fs.readFileSync('/dev/stdin')
const result = data.toString('ascii').trim().split(' ').map(x => parseInt(x)).reduce((a, b) => a + b, 0)
console.log(result)
process.exit() // 请注意必须在出口点处加入此行
Ruby
a, b = gets.split.map(&:to_i)
print a+b
PHP
<?php
$input = trim(file_get_contents("php://stdin"));
list($a, $b) = explode(' ', $input);
echo $a + $b;
Rust
use std::io;
fn main(){
let mut input=String::new();
io::stdin().read_line(&mut input).unwrap();
let mut s=input.trim().split(' ');
let a:i32=s.next().unwrap()
.parse().unwrap();
let b:i32=s.next().unwrap()
.parse().unwrap();
println!("{}",a+b);
}
Go
package main
import "fmt"
func main() {
var a, b int
fmt.Scanf("%d%d", &a, &b)
fmt.Println(a+b)
}
C# Mono
using System;
public class APlusB{
private static void Main(){
string[] input = Console.ReadLine().Split(' ');
Console.WriteLine(int.Parse(input[0]) + int.Parse(input[1]));
}
}
Visual Basic Mono
Imports System
Module APlusB
Sub Main()
Dim ins As String() = Console.ReadLine().Split(New Char(){" "c})
Console.WriteLine(Int(ins(0))+Int(ins(1)))
End Sub
End Module
Kotlin
fun main(args: Array<String>) {
val (a, b) = readLine()!!.split(' ').map(String::toInt)
println(a + b)
}
Haskell
main = do
[a, b] <- (map read . words) `fmap` getLine
print (a+b)
Scala
object Main extends App {
println(scala.io.StdIn.readLine().split(" ").map(_.toInt).sum)
}
Perl
my $in = <STDIN>;
chomp $in;
$in = [split /[\s,]+/, $in];
my $c = $in->[0] + $in->[1];
print "$c\n";
很明显,实际就是输出 \(a+b\) ,根本就不用看程序范例,用我们之前学过的知识就可以轻松写出这样的代码:
#include <iostream>
using namespace std;
int a, b;
int main(int argc, char *argv[]) {
cin >> a >> b;
cout << a + b << '\n';
return 0;
}
然后点击 提交评测 ,如果你的代码没有错误,应该会出现类似的界面:

然后点击右上角的头像(因人而异),点击 练习 就可以看到你做了哪些题目。

接着,我们写第二道题—— 超级玛丽游戏 吧!
题目很简单,就是让我们直接输出这个——
********
************
####....#.
#..###.....##....
###.......###### ### ###
........... #...# #...#
##*####### #.#.# #.#.#
####*******###### #.#.# #.#.#
...#***.****.*###.... #...# #...#
....**********##..... ### ###
....**** *****....
#### ####
###### ######
##############################################################
#...#......#.##...#......#.##...#......#.##------------------#
###########################################------------------#
#..#....#....##..#....#....##..#....#....#####################
########################################## #----------#
#.....#......##.....#......##.....#......# #----------#
########################################## #----------#
#.#..#....#..##.#..#....#..##.#..#....#..# #----------#
########################################## ############
大家伙。
但是如果我们使用 cout 一行一行地打,是要打很久的。
cout << " ********" << endl;
cout << " ************" << endl;
cout << " ####....#." << endl;
... ...
不过,c++ 会将相邻的两个字符串合并成一个,所以我们可以这样写:
printf(" ********\n"" ************\n"... ...)
然后我们再简单地换一下行,就可以变成这样子:
#include<stdio.h>
int main(int argc, char *argv[]) {
printf(
" ********\n"
" ************\n"
" ####....#.\n"
" #..###.....##....\n"
" ###.......###### ### ###\n"
" ........... #...# #...#\n"
" ##*####### #.#.# #.#.#\n"
" ####*******###### #.#.# #.#.#\n"
" ...#***.****.*###.... #...# #...#\n"
" ....**********##..... ### ###\n"
" ....**** *****....\n"
" #### ####\n"
" ###### ######\n"
"##############################################################\n"
"#...#......#.##...#......#.##...#......#.##------------------#\n"
"###########################################------------------#\n"
"#..#....#....##..#....#....##..#....#....#####################\n"
"########################################## #----------#\n"
"#.....#......##.....#......##.....#......# #----------#\n"
"########################################## #----------#\n"
"#.#..#....#..##.#..#....#..##.#..#....#..# #----------#\n"
"########################################## ############\n"
);
return 0;
}
但是这时还是不够简便,怎么办呢?
C++11 raw string literal! (破音
开头是 R"( ,结尾 )" ,中间的字符可以跨行,而且里面没有转义字符。不过中间不能穿插 )" 。怎么办呢?用全角符号!
可以这样写:
R"--(Embedded )" in string)--"
这样就很简单了:
#include<iostream>
using namespace std;
int main(int argc, char *argv[])
{
cout<<R"( ********
************
####....#.
#..###.....##....
###.......###### ### ###
........... #...# #...#
##*####### #.#.# #.#.#
####*******###### #.#.# #.#.#
...#***.****.*###.... #...# #...#
....**********##..... ### ###
....**** *****....
#### ####
###### ######
##############################################################
#...#......#.##...#......#.##...#......#.##------------------#
###########################################------------------#
#..#....#....##..#....#....##..#....#....#####################
########################################## #----------#
#.....#......##.....#......##.....#......# #----------#
########################################## #----------#
#.#..#....#..##.#..#....#..##.#..#....#..# #----------#
########################################## ############ )";
return 0;
}

输出第二个整数 - 洛谷
这一题很简单,按照之前我们学过的方法,进行操作。
\(\mathcal{Code}\) :
#include <iostream>
using namespace std;
int a, b, c;
int main() {
cin >> a >> b >> c; // 读入三个整数
cout << b << '\n'; // 输出第二个
return 0;
}
时间复杂度 \(\mathcal{O(1)}\) 。(这是一个时间复杂度,\(\mathcal{O}\) 里面表示的就是程序在最坏情况下需要运行的基本次数)
时间复杂度就是用来方便开发者估算出程序的运行时间。
我们该如何估计程序运行时间呢,我们通常会估计算法的操作单元数量,来代表程序消耗的时间, 这里我们默认 \(\mathsf{CPU}\) 的每个单元运行消耗的时间都是相同的。
假设算法的问题规模为 \(n\) ,那么操作单元数量便用函数 \(f(n)\) 来表示。
随着数据规模n的增大,算法执行时间的增长率和 \(f(n)\) 的增长率相同,这称作为算法的渐近时间复杂度,简称时间复杂度,记为 \(O(f(n))\) 。
例如以下程序
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
cout << "1";
}
}
我们就说这个程序的时间复杂度是 \(\mathcal{O(n^2)}\) 。
计算 2 的幂 - 洛谷
题目大意
给定 \(n\) ,求出 \(2^n\) 。
思路
暴力
\(2^n\) 其实就是 \(n\) 个 \(2\) 相乘,因此我们可以这样写出一个 for 循环。
由于 \(2^0=0\) ,因此需要特判:
int solve(int n) {
int ans;
if (n == 1) {
return 1;
}
for (int i = 2; i <= n; ++i) {
ans *= 2;
}
return ans;
}
事实上,我们可以使用位运算中的左移运算符( <<)。它的操作是把一个数的二进制位全部往左移动 \(n\) 位,低位用 \(0\) 补齐。由于是二进制,因此使用 m << n 等效于 \(m\times 2^m\) 。关于位运算更多的知识,请联系前面的基础知识。
int n;
cout << (1 << n); //由于cout也使用<<运算符,因此需要打上括号
\(\mathcal{Q}\) :int 不是有范围的吗?为什么不会爆?
\(\mathcal{A}\) :因为 \(n\leq 30\) ,而 int 最大能存 \(2^{31}-1\) ,因此不会爆炸。💥
浮点数向零舍入 - 洛谷
题意
给出 \(n\) ,将 \(n\) 向 \(0\) 取整(整数向下舍入,负数向上舍入)。
思路
很简单,由于 c++ 的类型转换自带说明的方法,因此可以使用强制类型转换或者直接输入 int 。
double n;
int main() {
cin >> n;
cout << long long(n) << '\n';
return 0;
}
long long n;
int main() {
cin >> n;
cout << n << '\n';
return 0;
}
与圆相关的计算 - 洛谷
首先,我们需要知道这三个公式:
- \(d=2r\)
- \(C=\pi d\)
- \(S=\pi r^2\)
依次完成计算即可。
const long double pi = 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086513282306647093844609550582231725359408128481117450284102701938521105559644622948954930381964428810975665933446128475648233786783165271201909145648566923460348610454326648213393607260249141273724587006606315588174881520920962829254091715364367892590360011330530548820466521384146951941511609433057270365759591953092186117381932611793105118548074462379962749567351885752724891227938183011949129833673362440656643086021394946395224737190702179860943702770539217176293176752384674818467669405132000568127145263560827785771342757789609173637178721468440901224953430146549585371050792279689258923542019956112129021960864034418159813629774771309960518707211349999998372978049951059731732816096318595024459455346908302642522308253344685035261931188171010003137838752886587533208381420617177669147303598253490428755468731159562863882353787593751957781857780532171226806613001927876611195909216420198938095257201065485863278865936153381827968230301952lf;
long double r;
int main() {
cin >> r;
printf("%.4lf %.4lf %.4lf", r * 2, pi * r * 2, pi * r * r)
return 0;
}
在 OJ 中的基本事项我们已经学会了,那么就要开始疯狂刷题了。
这里比较推荐[题单广场](题单列表 - 洛谷),里面有很多的题可以刷。现在,我们讲讲顺序结构里面的几道题吧!
【深基2.例5】苹果采购
题目描述
现在需要采购一些苹果,每名同学都可以分到固定数量的苹果,并且已经知道了同学的数量,请问需要采购多少个苹果?
输入格式
输入两个不超过 \(10^9\) 正整数,分别表示每人分到的数量和同学的人数。
输出格式
一个整数,表示答案。保证输入和答案都在 int 范围内的非负整数。
样例 #1
样例输入 #1
5 3
样例输出 #1
15
十分简单,根据小学二年级所学的芝士,我们需要采购的苹果数量就等于 同学的数量 \(\times\) 每人分到的数量 。可以写出如下程序
#include <iostream>
using namespace std;
int a, b;
int main() {
cin >> a >> b;
cout << a * b << '\n';
return 0;
}
【深基2.例6】字母转换
题目描述
输入一个小写字母,输出其对应的大写字母。例如输入 q[回车] 时,会输出 Q。
输入格式
输出格式
样例 #1
样例输入 #1
q
样例输出 #1
Q
从题目意思来看,我们并没有处理字符串大小写的标准函数。但是我们需要知道 ASCII 码!
ASCII (American Standard Code for Information Interchange):美国信息交换标准代码是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是最通用的信息交换标准,并等同于国际标准 ISO/IEC 646。ASCII第一次以规范标准的类型发表是在1967年,最后一次更新则是在1986年,到目前为止共定义了128个字符。 ——百度百科
ASCII 码就是用整数来记录字符,因此不同字母的 ASCII 码其实是有规律的。ASCII 码有 \(128\) 个字符,但我们没必要全部记住。我们只需要知道大写转小写是 \(+32\) (这里的 \(+\) 是加的 ASCII 值,会因为类型转换而变为字符,即 (空格)),小写转大写是 \(-32\) 。
代码就不用说了。
#include <iostream>
using namespace std;
char a, b;
int main() {
cin >> a;
b = a - 32;
cout << b << '\n';
return 0;
}
[NOIP2017 普及组] 成绩
题目背景
NOIP2017 普及组 T1
题目描述
牛牛最近学习了 C++ 入门课程,这门课程的总成绩计算方法是:
总成绩=作业成绩\(\times 20\%+\)小测成绩\(×30\%+\)期末考试成绩\(\times 50\%\)
牛牛想知道,这门课程自己最终能得到多少分。
输入格式
三个非负整数 \(A,B,C\),分别表示牛牛的作业成绩、小测成绩和期末考试成绩。相邻两个数之间用一个空格隔开,三项成绩满分都是 \(100\) 分。
输出格式
一个整数,即牛牛这门课程的总成绩,满分也是 \(100\) 分。
样例 #1
样例输入 #1
100 100 80
样例输出 #1
90
样例 #2
样例输入 #2
60 90 80
样例输出 #2
79
提示
输入输出样例 1 说明
牛牛的作业成绩是 \(100\) 分,小测成绩是 \(100\) 分,期末考试成绩是 \(80\) 分,总成绩是 \(100 \times 20\%+100 \times 30\%+80 \times 50\%=20+30+40=90\)。
输入输出样例 2 说明
牛牛的作业成绩是 \(60\) 分,小测成绩是 \(90\) 分,期末考试成绩是 \(80\) 分,总成绩是 \(60 \times 20\%+90 \times 30\%+80 \times 50\%=12+27+40=79\)。
数据说明
对于 \(30\%\) 的数据,\(A=B=0\)。
对于另外 \(30\%\) 的数据,\(A=B=100\)。
对于 \(100\%\) 的数据,\(0≤A,B,C≤100\) 且 \(A,B,C\) 都是 \(10\) 的整数倍。
思路
对于一个百分数如 \(25\%\) ,那可以化成 \(\dfrac{25}{100}\) ,也就是 \(0.25\) 。知道了这个规律,代码轻轻松松:
#include <cstdio>
using namespace std;
int a, b, c;
int s;
int main() {
scanf("%d%d%d", &a, &b, &c);
s = (int)(a * 0.2 + b * 0.3 + c * 0.5);
printf("%.lf", (double)s);
return 0;
}
附录
洛谷——步入 \(\text{OI}\) 大门
当你学完这些基础知识后,就可以去洛谷刷题了。首先,打开网站,是这个样子:

然后我们点一下右上角的 注册 按钮,就来到了用户注册界面。按照对应的填进去,你就注册成功了!

可以先试着摸索摸索功能。
然后点击“题库”,你就来到了题目的海洋。

随便点进一个题目进去,点击提交答案,就可以提交你的代码哦!😘
注意:洛谷的题目 \(99.99\%\) 都是使用的标准输入输出(stdio),而非文件输入输出。
配置 \(\text{OI}\) 刷题环境
最开始的时候,老师一般都会给我们使用 Dev-c++ ,但是 Dev-c++ 已经停更很多年了,调试功能也有一些缺陷。况且现在一般 c++ 标准都是 c++14 ,而 Dev-c++ 是 c++11。可能好用的原因就是它可以支持单文件编译运行吧。。。
目前主流的高颜值、高扩展性的 IDE 应该就是微软的。
微软的 IDE 主要是 \(\texttt{Visual Studio}\) 或者 \(\texttt{Visual Studio Code}\) 。前者更适合大型项目,而后者一般编译单文件。
\(\texttt{Visual Studio}\)
下载链接:官网
下载之后打开它,就会弹出安装界面。有各种各样的工作负载可以选,但 c++ 开发一般只用 使用c++的桌面开发 和 通用Windows平台开发 ,其他的酌情选择。勾选上,再改一改安装路径,尽量别安装在 C 盘上,避免卡得要死。

安装包可能有点大,等一小会就好了。
装完之后点击启动,然后没有项目的话就点击 创建新项目 。

我们选择 控制台应用 。输完相关信息后就来到了编辑区域:

当然,这款软件支持各种拓展以及颜色配置,感兴趣的话可以去网上找一找。(我的 VS 是装了插件,所以可能颜色跟你们不一样)
使用事项(避雷)
- 千万不要直接在资源管理器里面新建文件,不然项目里面是看不到的。
- 如果想只生成一个源代码,那么右键其他的所有源文件,点
属性,把从生成中排除改为是。 - 一般情况下不要调试,按
以非调试模式运行。调试器的内存占用实在是太大了!

这就是一个窗口的内存大小,大约 \(1.7~\text{GB}\) 。请保证你的电脑有 \(8~\text{GB}\) 的内存空间,不然用起来会有点卡。
\(\texttt{Visual Studio Code}\)
一样,还是去官网下载。

点击 我同意此协议 (应该没有人认真看吧?),然后下一步,这些选项酌情选择,不会太影响后续使用。最后安装!
安装好后打开它(你们应该是英文的):

我们先点开左边的“拓展按钮”,搜索 chinese ,点击 Install ,它会叫你 Restart ,按照它的操作走就行了。
但是 \(\texttt{VS Code}\) 没有安装编译器,所以你需要自己安装。
官网下载较慢,可以试试这个链接:MinGW-w64 - for 32 and 64 bit Windows - Browse Files at SourceForge.net
然后我们解压一下(记住解压的路径),就需要配置环境变量。
打开文件资源管理器,右键此电脑,点击属性,点击高级系统设置,双击 Path 系统变量,点击新建,值为 解压的路径\bin\ 文件夹。然后一路点击 \(\times\) 或者 确定,一直点到没有窗口就行。
然后我们测试一下是否安装正确。按 Win + R 键打开运行,输入 cmd 回车,就来到了命令行。输入 gcc -v 或者 g++ -v ,如果没有报错则说明安装完毕 。

最后打开 VS Code ,安装 Code Runner 插件,然后写好代码并保存,点击右上角的运行按钮旁边的下切按钮,调成 code runner 之后再按运行按钮,就可以看到运行结果了!

做黑框框小游戏的函数:
使用头文件:
#include<windows.h>
#include<conio.h>
#include<ctime>
#include<cstdlib>
函数:
void cls(void) // 清屏
{
system("cls");
}
char get(void) // 返回按下的键
{
return getch();
}
int random(int minN,int maxN) // 在他俩中间随机选一个值
{
srand((unsigned)time(NULL));
return rand()%(maxN-minN)+minN;
}
void sleep(double s) // 原函数是毫秒,我这里为了方便给他改成秒
{
Sleep(s*1000);
}
void color(signed ForgC = 0, signed BackC = 15) //设定从设定开始的前景色和背景色
{
WORD wColor = ((BackC & 0x0F) << 4) + (ForgC & 0x0F);
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), wColor);
}
void pause(void)
{
system("pause");
}
写了半天,点个赞吧~😜

浙公网安备 33010602011771号