月份查询(指针数组)

时间限制: 1 Sec 内存限制: 128 MB

题目描述

已知每个月份的英文单词如下,要求创建一个指针数组,数组中的每个指针指向一个月份的英文字符串,要求根据输入的月份数字输出相应的英文单词
1月 January
2月 February
3月 March
4月 April
5月 May
6月 June
7月 July
8月 August
9月 September
10月 October
11月 November
12月 December

输入

第一行输入t表示t个测试实例
接着每行输入一个月份的数字
依次输入t行

输出

每行输出相应的月份的字符串,若没有这个月份的单词,输出error

样例输入

3
5
11
15

样例输出

May
November
error

提示

解决方案

#include <iostream>

int main() {
    const char *calendar[] = {"January", "February", "March",
                              "April", "May", "June",
                              "July", "August", "September",
                              "October", "November", "December"};
    size_t T;
    std::cin >> T;
    while (T--) {
        unsigned int month;
        std::cin >> month;
        if (1 <= month && month <= 12) {
            std::cout << calendar[month - 1] << std::endl;
        } else {
            std::cout << "error" << std::endl;
        }
    }
    return 0;
}

货币兑换(指针与常量)

时间限制: 1 Sec 内存限制: 128 MB

题目描述

设定以下汇率常量
美元汇率为6.2619,表示1美元兑换6.2619元人民币
欧元汇率为6.6744,表示1欧元兑换6.6744元人民币
日元汇率为0.0516,表示1元日元兑换0.0516元人民币
港币汇率为0.8065,表示1元港币币兑换0.8065元人民币
定义一个常量指针,根据需求指针指向不同的汇率,然后计算出各种货币兑换为人民币的数量
要求:不能直接使用汇率常量进行计算,必须使用常量指针,只能使用一个指针

输入

输入一个t表示有t个测试实例
每行先输入一个字母,表示货币类型,然后再输入一个数字(正浮点数),表示货币数量
D表示美元,E表示欧元,Y表示日圆,H表示港币
依次输入t行

输出

每行输出兑换后的人民币数量,保留4位小数
在C++中,输出指定精度的参考代码如下:

#include <iostream>
#include <iomanip> //必须包含这个头文件

using namespace std;

void main( )
{
  double a =3.141596;
  cout<<fixed<<setprecision(3)<<a<<endl;  //输出小数点后3位
}

样例输入

4
Y 10000
D 88.3
H 200
E 73.1

样例输出

516.0000
552.9258
161.3000
487.8986

提示

解决方案

#include <iostream>
#include <iomanip>

const double USD_CNY = 6.2619, EUR_CNY = 6.6744, JPY_CNY = 0.0516, HKD_CNY = 0.8065;

int main() {
    size_t T;
    std::cin >> T;
    const double *exchange_rate = nullptr;
    while (T--) {
        char type;
        double money;
        std::cin >> type >> money;
        switch (type) {
            case 'D':
                exchange_rate = &USD_CNY;
                break;
            case 'E':
                exchange_rate = &EUR_CNY;
                break;
            case 'Y':
                exchange_rate = &JPY_CNY;
                break;
            case 'H':
                exchange_rate = &HKD_CNY;
                break;
            default:
                exit(-1);
        }
        std::cout << std::fixed << std::setprecision(4) << (money * *exchange_rate) << std::endl;
    }
    return 0;
}

密钥加密法(指针应用)

时间限制: 1 Sec 内存限制: 128 MB

题目描述

有一种方式是使用密钥进行加密的方法,就是对明文的每个字符使用密钥上对应的密码进行加密,最终得到密文
例如明文是abcde,密钥是234,那么加密方法就是a对应密钥的2,也就是a偏移2位转化为c;明文b对应密钥的3,就是b偏移3位转化为e,同理c偏移4位转化为g。这时候密钥已经使用完,那么又重头开始使用。因此明文的d对应密钥的2,转化为f,明文的e对应密钥的3转化为h。所以明文abcde,密钥234,经过加密后得到密文是cegfh。
如果字母偏移的位数超过26个字母范围,则循环偏移,例如字母z偏移2位,就是转化为b,同理字母x偏移5位就是转化为c
要求:使用三个指针p、q、s分别指向明文、密钥和密文,然后使用指针p和q来访问每个位置的字符,进行加密得到密文存储在指针s指向的位置。
除了变量定义和输入数据,其他过程都不能使用数组下标法,必须使用三个指针来访问明文、密钥和密文。
提示:当指针q已经移动到密钥的末尾,但明文仍然没有结束,那么q就跳回密钥头

输入

第一行输入t表示有t个测试实例
第二行输入一个字符串,表示第一个实例的明文
第三行输入一个数字串,表示第一个实例的密钥
依次输入t个实例

输出

每行输出加密后的密文

样例输入

2
abcde
234
XenOS
56

样例输出

cegfh
CksUX

提示

解决方案

#include <iostream>
#include <cstring>

typedef char *string;

char addCharRegular(char ch, int index) {
    index = index - '0';
    if ('a' <= ch && ch <= 'z') {
        return ((ch - 'a') + index) % 26 + 'a';
    } else {
        return ((ch - 'A') + index) % 26 + 'A';
    }
}

int main() {
    size_t T;
    std::cin >> T;
    while (T--) {
        const auto plaintext = new char[1024], cipher = new char[1024], ciphertext = new char[1024] {};
        std::cin >> plaintext >> cipher;
        size_t plaintext_length = strlen(plaintext), cipher_length = strlen(cipher);
        auto plaintext_ptr = plaintext, cipher_ptr = cipher, ciphertext_ptr = ciphertext;
        const auto plaintext_end = plaintext + plaintext_length, cipher_end = cipher + cipher_length;
        while (plaintext_ptr != plaintext_end) {
            *(ciphertext_ptr++) = addCharRegular(*(plaintext_ptr++), *(cipher_ptr++));
            if (cipher_ptr == cipher_end) {
                cipher_ptr = cipher;
            }
        }
        *ciphertext_ptr = '\0';
        std::cout << ciphertext << std::endl;
    }
    return 0;
}

动态数组(指针与数组)

时间限制: 1 Sec  内存限制: 128 MB

题目描述

一开始未知数组长度,根据要求创建不同类型的指针,并且使用指针创建相应长度的数组,然后再完成不同的要求
若要求创建整数数组,计算数组内所有数据的平均值
若要求创建字符数组,找出数组内的最大字母
若要求创建浮点数数组,找出数组的最小值
要求程序整个过程不能使用数组下标,从数组创建、输入到搜索、比较、计算,到输出都必须使用指针
提示:使用new关键字

输入

第一行输入t表示有t个测试实例
第二行先输入一个大写字母表示数组类型,I表示整数类型,C表示字符类型,F表示浮点数类型;然后输入n表示数组长度。
第三行输入n个数据
依次输入t个实例

输出

每个根据不同的数组类型输出相应的结果

样例输入

3
C 5
A D E B C
I 6
22 55 77 33 88 55
F 4
3.1 1.9 6.5 4.8

样例输出

E
55
1.9

提示

解决方案

不明白这种要求存在的意义。它对于让学生更好地理解指针这个问题上没有任何意义,反而降低代码质量。对于这个问题,我建议你去阅读《征服C指针》之类的图书,或许对你帮助会很大。

顺便一提,*(p+5)p[5]的语法糖,也就是说,p[5]是前者的简化形式,方便输入等。因此,在这个时候甚至5[p]p[5]也是一样的,不过别写成这样。具体内容详见上述图书。

#include <iostream>

int getAverageValue(const int *array, size_t size) {
    int sum = 0;
    size_t times = size;
    while (times--) {
        sum += *(array++);
    }
    return (sum / static_cast<int>(size));
}

char getMaximumChar(const char *array, size_t size) {
    char maxChar = *array;
    while (size--) {
        if (maxChar < *array) {
            maxChar = *array;
        }
        array += 1;
    }
    return maxChar;
}

double getMinimumFloat(const double *array, size_t size) {
    double minDouble = *array;
    while (size--) {
        if (minDouble > *array) {
            minDouble = *array;
        }
        array += 1;
    }
    return minDouble;
}

template<typename T>
T *handleArray(size_t size) {
    T *array = new T[size];
    for (size_t i = 0; i < size; ++i) {
        std::cin >> *(array + i);
    }
    return array;
}

int main() {
    size_t T;
    std::cin >> T;
    while (T--) {
        char type;
        size_t size;
        std::cin >> type >> size;
        switch (type) {
            case 'I': {
                const int *array = handleArray<int>(size);
                std::cout << getAverageValue(array, size) << std::endl;
                break;
            }
            case 'C': {
                const char *array = handleArray<char>(size);
                std::cout << getMaximumChar(array, size) << std::endl;
                break;
            }
            case 'F': {
                const double *array = handleArray<double>(size);
                std::cout << getMinimumFloat(array, size) << std::endl;
                break;
            }
            default:
                exit(-1);
        }
    }
    return 0;
}

成绩查询(指针运算)

时间限制: 1 Sec 内存限制: 128 MB

题目描述

已知一组学生成绩,然后根据输入的序号查询成绩
要求:

  1. 使用一个整数数组存储学生成绩,假设元素数是n。
  2. 使用一个指针指向数组中间元素,即n/2的位置。
  3. 使用++和–运算符,求出数组中间元素的前一个成绩和后一个成绩
  4. 输入一个序号,然后计算这个序号的元素和中间元素的距离,然后使用指针去访问
    例如有11个学生,指针指向中间的学生也就是第6个学生,若输入序号3,即查询第3个学生的成绩,第3和第6之间距离为3,那么指针应该怎么运算呢???
    如果有两个中间学生,则将后面那个看为中间学生。
  5. 整个程序除了输入时可以使用数组下标,其他部分尽量使用使用指针进行访问。

输入

第一行输入t表示有t个测试实例
第二行先输入n,表示有n个学生,然后再输入n个成绩(正整数)
第三行输入1个序号,表示要查询成绩的学生的序号。
依次输入t个实例
按自然意义,序号是从1开始计算
提示:在数组中是从……..

输出

对于每个测试实例:
第一行输出数组中间元素的前一个成绩和后一个成绩
第二行根据序号输出1个成绩

样例输入

2
7 66 99 88 44 77 33 11
2
11 60 80 50 20 90 35 70 40 10 95 30
9

样例输出

88 77
99
90 70
10

提示

解决方案

不想评论了…

#include <iostream>
#include <algorithm>

inline const unsigned int *getArrayMiddlePointer(const unsigned int *array, const size_t size) {
    return array + (size / 2);
}

int main() {
    size_t T;
    std::cin >> T;
    while (T--) {
        size_t ARRAY_SIZE;
        std::cin >> ARRAY_SIZE;
        auto *array = new unsigned int[ARRAY_SIZE];
        for (size_t i = 0; i < ARRAY_SIZE; ++i) {
            std::cin >> array[i];
        }
        auto ptr = getArrayMiddlePointer(array, ARRAY_SIZE);
        std::cout << *(--ptr) << ' ';
        ptr = getArrayMiddlePointer(array, ARRAY_SIZE);
        std::cout << *(++ptr) << std::endl;
        size_t number;
        std::cin >> number;
        const auto delta = (ARRAY_SIZE / 2) + 1 - number;
        ptr = getArrayMiddlePointer(array, ARRAY_SIZE);
        std::cout << *(ptr - delta) << std::endl;
    }
    return 0;
}

CLion嵌入式开发上手

CLion从2019.1开始正式加入了对嵌入式开发的支持,虽然仅仅是整合了之前已经有的OpenOCD和STM32CubeMX的插件,但也有很强的可玩性了。其实大概一年前这个特性刚出来的时候我就试过,因为某些原因不了了之,具体内容可以在我原来博客的存档里面找到。好久没有做嵌入式了,这次因为选修嵌入式课程开始重操旧业。

现在是2020年3月,我正在Windows 10上面使用CLion 2019.3.4,Toolbox检查已是最新版本。还是要说明,这篇文章不是什么教程,仅仅只是我个人尝试的经历,分享出来希望能和大家一起交流,如果有哪里不好烦请不吝赐教。底部有评论区,恳请大家留点经验意见。

表情包来自互联网,侵删。

准备工作

在开始之前,你需要准备一些东西:

一块开发板(废话)。等了几天快递,今天终于刚到手一块NUCLEO-F446RE。用什么板子应该是无所谓的,最多是后面选Board config file的时候没有,要自己写(其实大部分都没几句话,应该问题不大)。

表情包来自互联网,侵删。

有了开发板还要调试器,通常还要给调试器安装驱动程序,由于NUCLEO板载ST-LINK/V2,所以去到STSW-LINK009 – ST-LINK, ST-LINK/V2, ST-LINK/V2-1 USB driver signed for Windows7, Windows8, Windows10 – STMicroelectronics安装驱动程序。

事先安装好CLion(废话)。我推荐先安装JetBrains Toolbox,然后再通过Toolbox安装CLion,使用Toolbox来管理这些IDE会比较方便。当然,要是你真的从头到尾都只使用他家的CLion的话,直接安装CLion也没啥问题。

Toolbox长这样的,方便管理各个工具和最近的项目

Mingw-w64。我是使用MSYS2安装的,不用应该也可以(比如那些预构建包,在MinGW-w64 – for 32 and 64 bit Windows download | SourceForge.net)。

平时偶尔写写C/C++,所以事先基本包已经怼上了

这个环境是用来构建C/C++程序的,这里主要好像只是用到了make(就是那个mingw32-make.exe)。根据文档,你想使用MinGW应该也没有问题。另外,我之前用子系统Ubuntu(官方文档没说可以)的工具链好像构建总是会失败,忘记报错信息了,你可以试试然后来分享一下。

OpenOCD。不是很了解,不敢瞎BB。看到这篇好像不错?跟我一起学OpenOCD(一) – 知乎,有空再看看文档。根据官方文档,如果有MSYS2,那么安装比较简单:

pacman -S mingw-w64-x86_64-openocd

没有好像也没关系,Getting OpenOCD « Open On-Chip Debugger下面有非官方的预构建包可以下载,解压后即可使用。随后都需要在设置中指定其位置(找不到可以两下Shift来Search Everywhere,然后搜你想要的)。

Settings->Build, Execution, Deployment->Embedded Development
测试一下

STM32CubeMX。用来生成代码框架等,官网下载安装即可,如果是默认的安装路径的话CLion中应该能自动检测到。

STM32CubeMX

gcc-arm-none-eabi。最重要的工具,交叉编译用。虽然GitHub – msys2/MINGW-packages: Package scripts for MinGW-w64 targets to build under MSYS2.里面有PKGBUILD:

但是根据How to install mingw-w64-arm-none-eabi-gcc · Issue #4068 · msys2/MINGW-packages · GitHub,需要自行构建,pacman暂时没法直接装上。不过可以直接到ARM的官网上下载预编译好的工具链,链接找不到可以点上面的。CLion此时的文档仍然是写的支持2019-q3或者2018-q2及之前的版本。看了一下,2019-q3是GCC 8,2019-q4是GCC 9,估计是没测试完就没写支持吧,自己斟酌。而且印象中这个时候的CLion对于版本高于8的gdb仍然会给出不支持的警告(虽然还是能用)。

我下载的.zip包,解压到了MSYS2环境里(自己建了个3rd文件夹),放哪里应该都是无所谓的。然后要到设置中手动指定arm-none-eabi-gcc.exearm-none-eabi-g++.exearm-none-eabi-gdb.exe。(这个方法是我突然想到的,感觉应该有更好的?)

Settings->Build, Execution, Deployment->Toolchains

你会注意到,如果这个配置文件在队列里不是第一个的话,并不会显示default,也就是说平时并没有用这个配置。如果你平时在使用多个Toolchain于不同用途,你并不需要一直在这里切换,而是可以在这里为每个Project单独设置:

Settings->Build, Execution, Deployment->CMake

你会注意到上面有个地方可以设置环境变量,因为我没有在PATH中设置编译器位置时,后续建好Project的时候CMake还是会找不到,像这样:

Reload CMake Project

你可以写一个.cmake文件来设置,不过我在这里直接给这个Project设置环境变量(这样不会影响系统的):

Settings->Build, Execution, Deployment->CMake->Environment

此时应该就没有问题了:

开工吧!

准备了这么多东西,现在该来尝试一下了。你需要先打开CLion,然后新建一个Project,就像这样:

New Project

项目名字自己起一个,尽量别untitled一路下来(我真的见过一堆项目untitled一直到untitled200的)。

点击打开STM32CubeMX来配置

默认上来的型号通常不是你想要的,点击Open with STM32CubeMX来启动STM32CubeMX。你可以在这里点击型号来改变:

点按STM32F030F4Px
搜索你的开发板或是MCU等

这是文档里这么干的,感觉有点奇怪。这并不能直接改型号,而是新建了一个Project,我感觉还不如直接新建好,然后再用CLion打开呢…因为这是新建的,所以你需要到这里来设置好名称和工具链。你需要保证生成的代码的位置和刚刚你在CLion中新建的Project一样,不然就白折腾了半天。就像下面这样:

Project Manager

此时点按GENERATE CODE来生成代码,会提示是否覆盖CLion自动生成的那份.ioc文件。覆盖吧,反正没啥用。总之我感觉这么干有点多此一举。

生成正常的话,现在就可以回到CLion中了。此时右下角应该会有气泡窗口显示已经自动检测到了:

这次弹窗要我选择Board config file,由于这里有现成的,就直接用了:(没有的话,应该在OpenOCD目录下的scripts里面可以依葫芦画瓢一下)

Board Config File

由于我之前设置环境变量是单独为Project设置的,所以每次新建都需要设置,不然就会:

Reload CMake Project

同样地,像上面那样设置好环境变量就OK了。回来再点击一下上一个图里左上角这个Reload CMake Project图标,应该就没有问题了。

注意到右上角,要执行任务的配置还是没设置好,此时还不能Run:

右上角,Edit Configurations

很简单,点按Edit Configurations…来修复错误。注意到窗口下方的错误信息是:

Edit Configurations下方有错误信息

那么只要指定Target就好了:

指定Target

此时Run和Debug的图标应该就不是灰色的了。插上开发板,点按Run(此时的配置应该是那份OCD的而不是CMake的),顺利的话应该很快就会:

此时应该已经烧录成功了,你也可以尝试着写点小程序来测试一下。

只能Run还不够,Debug也很重要。打上断点,点按Debug图标来启动,应该没有问题的:

下方的Debug窗口应该会显示变量、堆栈等信息。不知道你会不会在Debug窗口的第一栏看到这个很好使的小工具:

划重点,点按后即可在当前现场执行你想要的语句,有点像脚本语言一样?总之很好使就对了。

如果你想查看引脚信息,需要导入.svd文件,根据SVD files on ARM website – Keil forum – Software Tools – Arm Community,你可以在MDK5 Software Packs里先下载pack,然后从里面拿到。

如果你之前就在使用Keil做嵌入式开发,那么你应该已经有了这个pack,直接去找就好了。导入后就可以正常查看了。

应该暂时就这么多吧。想到啥再补充。

引用:(文中已做超链接的就不再列出了)
CLion 2019.1 EAP: CLion for Embedded Development, Part III | CLion Blog
STM32CubeMX Projects – Help | CLion
CLion for embedded development | CLion Blog
CLion for Embedded Development Part II | CLion Blog
MSYS2 installation · msys2/msys2 Wiki · GitHub
How to cross compile with cmake + arm-none-eabi on windows? – Stack Overflow
[柴工带你学习LiteOS系列]之—Windows下的GCC环境搭建_云博客_云社区-华为云
使用CLion做嵌入式开发 | 王晓磊贴代码用的Blog

数字判断(指针为函数参数)

时间限制: 1 Sec 内存限制: 128 MB

题目描述

输入一个字符串,判断这个字符串是否一个完全整数值的字符串,例如输入”1234″,那么表示整数1234,输入”12a3″就表示只是一个字符串,不是一个整数
要求编写函数isNumber,参数是一个字符指针,返回值是整数类型
如果字符串表示一个整数,则计算出这个整数并且返回
如果字符串不是表示一个整数,则返回-1
主函数必须调用isNumber来判断字符串,不能使用任何C++自带或第三方的类似函数

输入

输入t表示有t个测试实例
每行输入一个字符串
依次输入t行

输出

每行输出判断结果

样例输入

3
1234
567a
0890

样例输出

1234
-1
890

提示

解决方案

有一说一,这题搞得我很火大。本来几句话就秒掉的题目,没想到后台数据竟然一份换行符是LR,一份是CRLF…搞得加了一堆删白符的代码,看着就不爽…早知道不这么写了。而且要是想让学生写的代码更好,至少也应该先讲过相关内容吧?不然一起怪OJ有问题很好玩么?

总而言之,说归说,最后还是一句话:我菜。

#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>

bool isDigit(const char ch) {
    return ('0' <= ch) && (ch <= '9');
}

bool isNumber(const char *string, size_t size) {
    return std::find_if(string, (string + size), [](char ch) { return !isDigit(ch); }) == (string + size);
}

int main() {
    size_t T;
    std::cin >> T;
    if (std::cin.get() == '\r') {
        std::cin.get();
    }
    char string[1024]{};
    while (T--) {
        std::cin.getline(string, 1024);
//        std::cout << string << std::endl;
        size_t string_length = strlen(string);
        if (string[string_length - 1] == '\r' || string[string_length - 1] == '\n') {
            string[string_length - 1] = '\0';
            string_length -= 1;
        }
        size_t leading_zero_amount = std::find_if_not(string, string + string_length, [](char ch) { return ch == '0'; }) - string;
        std::cout << (isNumber(string, string_length) ? std::string(string + leading_zero_amount, string + string_length) : "-1") << std::endl;
    }
    return 0;
}

三数论大小(指针)

时间限制: 1 Sec 内存限制: 128 MB

题目描述

输入三个整数,然后按照从大到小的顺序输出数值。
要求:用三个指针分别指向这三个整数,排序过程必须通过这三个指针来操作,不能直接访问这三个整数
输出时,必须使用这三个指针,不能使用存储三个整数的变量

输入

第一行输入t表示有t个测试实例
第二行起,每行输入三个整数
输入t行

输出

每行按照从大到小的顺序输出每个实例
在每行中,每个数据输出后都带有一个空格,即使该行最后一个数据输出后也要再输出一个空格

样例输入

3
2 4 6
88 99 77
111 333 222

样例输出

6 4 2 
99 88 77 
333 222 111 

提示

解决方案

#include <iostream>
#include <algorithm>

const size_t ARRAY_SIZE = 3;

int main() {
    unsigned int T;
    std::cin >> T;
    while (T--) {
        int integer_array[ARRAY_SIZE]{}, *integer_ptr_array[ARRAY_SIZE]{};
        for (size_t i = 0; i < ARRAY_SIZE; ++i) {
            std::cin >> integer_array[i];
            integer_ptr_array[i] = integer_array + i;
        }
        for (size_t i1 = 0; i1 < (ARRAY_SIZE - 1); ++i1) {
            for (size_t i2 = 0; i2 < (ARRAY_SIZE - i1 - 1); ++i2) {
                if (*integer_ptr_array[i2] < *integer_ptr_array[i2 + 1]) {
                    // swap pointers
                    auto tmp_ptr = integer_ptr_array[i2];
                    integer_ptr_array[i2] = integer_ptr_array[i2 + 1];
                    integer_ptr_array[i2 + 1] = tmp_ptr;
                }
            }
        }
        for (auto item : integer_ptr_array) {
            std::cout << *item << ' ';
        }
        std::cout << std::endl;
    }
    return 0;
}

JetBrains Quest (March 9, 2020) Write-Up

这篇文章是事先写好的,等到解谜到期后(“You have until the 15th of March at 12:00 CET to finish all the quests.”,也就是北京时间当日19:00)才放出的。总之我觉得,在结束前就到处发题解,大概就只能是破坏解谜气氛和便宜了黑产吧。

48 61 76 65 20 79 6f 75 20 73 65 65 6e 20 74 68 65 20 73 6f 75 72 63 65 20 63 6f 64 65 20 6f 66 20 74 68 65 20 4a 65 74 42 72 61 69 6e 73 20 77 65 62 73 69 74 65 3f

比较明显的十六进制的特征

看着很像是16进制表示的内容,尝试着转为ASCII字符。改行写Kotlin了,以后看到脚本长得跟你平时看见的不一样不要觉得奇怪…

import java.util.*

fun main() {
    val string = "48 61 76 65 20 79 6f 75 20 73 65 65 6e 20 74 68 65 20 73 6f 75 72 63 65 20 63 6f 64 65 20 6f 66 20 74 68 65 20 4a 65 74 42 72 61 69 6e 73 20 77 65 62 73 69 74 65 3f"
    val scanner = Scanner(string)
    while (scanner.hasNextInt(16)) {
        print(scanner.nextInt(16).toChar())
    }
}
输出的结果

得到输出:Have you seen the source code of the JetBrains website?

应该是一个提示。去看看官网的源码。访问JetBrains: Developer Tools for Professionals and Teams,然后Ctrl+U查看源代码(或者直接在网址前面打上view-source:)。比较长,于是尝试Ctrl+F搜索quest,发现了一段注释:

官网源代码中的注释,给出了hint

画个重点:

JetBrains has a lot of products, but there is one that looks like a joke on our Products page, you should start there… (hint: use Chrome Incognito mode)

产品页中有一个看起来像是个玩笑?提示用Chrome的无痕浏览。

其实还有一个更重要的重点:

It’s dangerous to go alone take this key: Good luck! == Jrrg#oxfn$

有一个奇怪的key。

先跳转产品页,也就是All Developer Tools and Products by JetBrains。3月12日的时候,直接访问相比Chrome的无痕浏览模式访问,会多出一个:

哈哈哈哈哈…很难笑欸
复制一个玩,当纪念吧。

点开康康:

仍然是hint

要求计算500到5000范围内的质数的数量。这个时候可能很多人会默默开始写脚本了,然而我选择WolframAlpha:

直接得到有574个质数

所以应该访问https://jb.gg/574。有一说一,这个域名挺难听的…被跳转到了https://www.jetbrains.com/help/pycharm/getting-help.html#quest

PyCharm的文档页

有用过YouTrack,或者是曾经发现JetBrains家的IDE有问题的人应该会知道这个编号的含义的。不知道的话,看上面的图标应该也能猜到要去看看这个产品是啥。到YouTrack的官网搜这个事件编号就可以了,甚至粗暴地Google一下也可以。所以就是访问JetBrains Quest : MPS-31816

有一串密文

考虑到之前给出的hint:Good luck! == Jrrg#oxfn$。能发现两个o都对应了r,应该只是进行了替换而已。好了,开工:

fun main() {
    val string = "Qlfh\$#Li#|rx#duh#uhdglqj#wklv#|rx#pxvw#kdyh#zrunhg#rxw#krz#wr#ghfu|sw#lw1#Wklv#lv#rxu#lvvxh#wudfnhu#ghvljqhg#iru#djloh#whdpv1#Lw#lv#iuhh#iru#xs#wr#6#xvhuv#lq#Forxg#dqg#iru#43#xvhuv#lq#Vwdqgdorqh/#vr#li#|rx#zdqw#wr#jlyh#lw#d#jr#lq#|rxu#whdp#wkhq#zh#wrwdoo|#uhfrpphqg#lw1#|rx#kdyh#ilqlvkhg#wkh#iluvw#Txhvw/#qrz#lw“v#wlph#wr#uhghhp#|rxu#iluvw#sul}h1#Wkh#frgh#iru#wkh#iluvw#txhvw#lv#‟WkhGulyhWrGhyhors†1#Jr#wr#wkh#Txhvw#Sdjh#dqg#xvh#wkh#frgh#wr#fodlp#|rxu#sul}h1#kwwsv=22zzz1mhweudlqv1frp2surpr2txhvw2"
    val stringBuilder = StringBuilder()
    string.forEach {
        stringBuilder.append(when (it) {
            'J' -> 'G'
            'r' -> 'o'
            'g' -> 'd'
            '#' -> ' '
            'o' -> 'l'
            'x' -> 'u'
            'f' -> 'c'
            'n' -> 'k'
            '$' -> '!'
            else -> it
        })
    }
    print(stringBuilder.toString())
}

得到的输出是这样的:

仍然是乱码

继续观察一下,结合ASCII表就发现,密文只是相对于明文右移了3位而已。那么可以尝试还原了:

fun main() {
    val string = "Qlfh\$#Li#|rx#duh#uhdglqj#wklv#|rx#pxvw#kdyh#zrunhg#rxw#krz#wr#ghfu|sw#lw1#Wklv#lv#rxu#lvvxh#wudfnhu#ghvljqhg#iru#djloh#whdpv1#Lw#lv#iuhh#iru#xs#wr#6#xvhuv#lq#Forxg#dqg#iru#43#xvhuv#lq#Vwdqgdorqh/#vr#li#|rx#zdqw#wr#jlyh#lw#d#jr#lq#|rxu#whdp#wkhq#zh#wrwdoo|#uhfrpphqg#lw1#|rx#kdyh#ilqlvkhg#wkh#iluvw#Txhvw/#qrz#lw“v#wlph#wr#uhghhp#|rxu#iluvw#sul}h1#Wkh#frgh#iru#wkh#iluvw#txhvw#lv#‟WkhGulyhWrGhyhors†1#Jr#wr#wkh#Txhvw#Sdjh#dqg#xvh#wkh#frgh#wr#fodlp#|rxu#sul}h1#kwwsv=22zzz1mhweudlqv1frp2surpr2txhvw2"
    val stringBuilder = StringBuilder()
    string.forEach {
        stringBuilder.append(it - 3)
    }
    print(stringBuilder.toString())
}

这下直接得到结果了:Nice! If you are reading this you must have worked out how to decrypt it. This is our issue tracker designed for agile teams. It is free for up to 3 users in Cloud and for 10 users in Standalone, so if you want to give it a go in your team then we totally recommend it. you have finished the first Quest, now it’s time to redeem your first prize. The code for the first quest is “TheDriveToDevelop”. Go to the Quest Page and use the code to claim your prize. https://www.jetbrains.com/promo/quest/

也就是前往上面这个链接兑奖就可以了。

片刻即可收到邮件:

三个月全家桶,算是个小惊喜吧。

Windows 10下使用Windows+Shift+S截图

好消息,好消息!如果你正在使用较新的Windows 10,那么截图只要:Windows徽标键+Shift+S即可触发截图和草图应用来抓取矩形截图、任意形状截图、窗口截图或是全屏幕截图Windows徽标键+PrtSc即可触发截图和草图应用直接抓取全屏幕截图不用再为了截图打开微信或QQ啦。

截图和草图应用

如果你觉得这个快捷键比较难按,你可以在设置->轻松使用->键盘中修改:

修改热键为PrtScn

什么?你说我火星了?好像不是我,是我的朋友们…(无中生友ing)

好,来说说这个功能。根据此文章Why doesn’t the screen clipping tool work anymore? – OneNote所述,该热键本是OneNote用户用于截图的,自Windows 10创意者更新(也就是1703)后,由截图与草图应用接管。相关文章还包括:What’s New in Windows 10’s Creators UpdateHow to take and annotate screenshots on Windows 10

还要注意的是,根据Snip & Sketch…. : Windows10 – Reddit。截取屏幕后,你通常直接从剪贴板中取得图片,但是在:%LOCALAPPDATA%\Packages\Microsoft.Windows.ShellExperienceHost_cw5n1h2txyewy\TempState\ScreenClip路径下仍然保存有图片的副本(也就是C:\Users\%USERNAME%\AppData\Local\Packages\Microsoft.Windows.ShellExperienceHost_cw5n1h2txyewy\TempState\ScreenClip)。可能有隐私泄露风险,请务必注意。