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; 这一行,这表示我们声明了两个整形变量 ab ,逗号是用来分隔的。当然,你也可以写成这样:

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;

在保留小数、向右对齐方面,一般用 scanfprintf ,其他用 cincout

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 声明这个类型只能存正数

下面是其他头文件中拓展的数据类型:

头文件 类型名 作用
cstringstring.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 ifelseelseelse if 也可以没有其中的一个,但是 else ifelse 必须有 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;
}

这里就相当于我们把实际参数 ab 的值赋给了函数里面的形式参数 ab ,然后以形式参数 ab 的和返回了过来, 然后输出。

如果你的函数只是输出一些内容,没有返回值的话请把它的返回值写成 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 里的 stringiostream 里面的 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;
}

可以看到,类里面分了两大块:privatepublic 。其实应该是三大块:

private :只有在类里面才可以访问的成员。

protected :只有在类里面或子类才可以访问。

public :在类内、类外或子类都可以访问。

这里我们写了一些成员函数用来返回当前类中的一些信息。this 指针指向的就是当前对象,而类的末尾我们定义了 haokeebluemoon 两个对象, 并给了他们初始值。然后我们通过调用成员函数的方式输出了他们的信息。

在调用对象中的成员的时候,我们需要使用 . 运算符,这样程序才能知道是调用哪个对象中的那个成员。

  • -> 运算符:他是通过指针来访问类中的元素,等同于:(*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;

由于 Sson 类,是 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\))。

注意

  1. Pascal 使用 integer 会爆掉哦!
  2. 有负数哦!
  3. 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;
}

与圆相关的计算 - 洛谷

首先,我们需要知道这三个公式:

  1. \(d=2r\)
  2. \(C=\pi d\)
  3. \(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");
}

写了半天,点个赞吧~😜

posted @ 2023-11-05 21:46  haokee  阅读(72)  评论(0)    收藏  举报