首页 > 其他分享 >快读快写 原理详解

快读快写 原理详解

时间:2023-07-29 17:12:34浏览次数:40  
标签:int sign buffer 详解 wq 快读 原理 部分

快读快写 原理详解

目录

C++ 的 cin cout 和 C 的 scanf printf 等 IO 函数已经够我们是用了,但是它们很慢,尽管 cin cout 可以取消同步以优化,但还是不够快.

所以我们需要找一种更快的方式来输入输出,以防在 OI 中出现 TLE 的情况,便有了快读快输.

代码

注:它们只能读取整数

快读 read quickly

template<class T>
inline void rq(T& x) {
    x = 0;
    char c;
    bool sign = 0;
    while(!isdigit(c = getchar()))
        if(c == '-')
            sign = 1;
    do
        x = (x << 3) + (x << 1) + (c ^ 48);
    while(isdigit(c = getchar()));
    if(sign)
        x = -x;
    return;
}

快写 write quickly

template<class T>
inline void wq(const T& x) {
    T t = x;
    static char _wq_buffer[39];
    int bp = -1;
    if(!t) {
        putchar('0');
        return;
    }
    if(t < 0)
        putchar('-'), t = -t;
    while(t)
    {
        _wq_buffer[++bp] = t % 10 + '0';
        t /= 10;
    }
    for(int i = bp; i >= 0; i--)
        putchar(_wq_buffer[i]);
    return;
}

代码解释

快读

第一部分

template<class T>
inline void rq(T& x)

template 是一个 C++ 的关键字,在这里它的作用是声明函数模板. 尖括号里的 class T 可以理解为声明一个类型 T ,这里的 class 也可以替换成 typename ,并无两样.

这个 T 是什么类型一般取决于这里的 x 是什么类型. 例如:

int x;
rq(x); // T = int

long long y;
rq(y); // T = long long

char z;
rq(z); // T = char

inline内联 ,在这里是 内联函数 ,加上它可能会让程序运行的速度变快,类似于宏展开,但它做出的优化取决于编译器和函数的复杂程度, 有兴趣的可以看这位大佬的博客.

void 在这里的意思是无返回类型函数.

T& x 在这里是 引用传参 ,在此函数内更改 x 对应传进来的参数也会更改,这里它和指针极为相似:

void swap1(int& a, int& b) {
    int t = a;
    a = b;
    b = t;
}

void swap2(int* a, int* b) {
    int t = *a;
    *a = *b;
    *b = t;
}

int main() {
    int x = 1, y = 2;
    swap1(x, y); // x = 2, y = 1
    swap2(&x, &y); // x = 1, y = 2
}

这两个的函数的功能都一样,在汇编层面也一样,只不过 swap1swap2 的写法更简便,更像是 C++ 相对于 C 特有的语法糖.

第二部分

x = 0;
char c;
bool sign = 0;

x = 0 ,这里一般清零,因为传进来的 x 的值不确定,如果不是 \(0\) 会导致接下来的运算出错,从而导致读入无效. 如果可以保证传进来的 x 等于 \(0\) ,则可以省略这一行.

char c ,这个 c 是读入的字符,一般 IO 都是靠字符,然后再转化为数字等别的类型.

bool sign = 0 ,这个 sign 表示 x 的符号,\(0\) 表示 \(x \geq 0\) ,\(1\) 表示 \(x < 0\) . 在这里必须清零,因为这是在函数内,如果不清零,它的值是不确定的,从而导致 x 的正负性受到影响. 如果可以保证读入的数是一个非负整数,则可以省略这一行以及相关的内容.

第三部分

while(!isdigit(c = getchar()))
    if(c == '-')
        sign = 1;

此部分是跳过不是数字的时候并确定 x 的符号.

c = getchar() 是读入一个字符,getchar() 是一个较快的方法,比 cin >> cscanf("%c", &c) 快.

!isdigit(...) 是判断该字符是否是一个非数字. 为什么不用 a < '0' || '9' < a 之类的?因为直接 !isdigit(...) 比这个快,isdigit 内部实现类似于打表,有兴趣的可以看这位大佬的博客.

if(c == '-')
    sign = 1;

显然是确定符号. 但是这种判断有个缺点,当输入:

ho-mo114514

时,sign = 1 ,显然这是不正确的.

如果可以保证一开始读入的就是符号或是有效数字位,或已知输入的格式,则可以删除这个 while 循环并做出相应的更改. (我建议还是不要删,删了之后处理格式会很麻烦)

第四部分

do
    x = (x << 3) + (x << 1) + (c ^ 48);
while(isdigit(c = getchar()));

此部分是边读边更改 x .

(x << 3) + (x << 1)x * 10.

x << 3 表示将 x 的二进制位左移三位,x << 1 同理.

什么是左移、右移?

假设 a 是一个 \(8\) 位的数,并且 a = 4 ,则 a 在二进制下是 00000100 .
左移三位得 00010000 ,表示 \(32\) ,也就是 \(4 \times 2^3\)

不难得出 a << b \(=a \times 2^b \space (b \geq 0)\) ,a >> b \(=a \div 2^b \space (b \geq 0)\) .

则上面的表达式等价于 \(x \times 2^3 + x \times 2^1 = x \times 8 + x \times 2 = x \times 10\) .

c ^ 48c - '0' ,将一个数字字符化为数字.

其中 ^ 表示异或,在数学中用 \(\bigoplus\) 表示,有基本的四种基本的运算法则:\(0 \bigoplus 0 = 0\) ,\(0 \bigoplus 1 = 1\) ,\(1 \bigoplus 0 = 1\) ,\(1 \bigoplus 1 = 0\). (同0异1)

\(48\) 在 ASCII 中对应的字符是 0 ,二进制是 00110000

第五部分

if(sign)
    x = -x;

此部分是添加 x 的符号. 有的可能把 -x 写成 ~(x - 1) ,这都表示取 x 的倒数,在汇编层面都一样,会被编译器优化.

快写

第一部分

template<class T>
inline void wq(const T& x)

此部分与快读的第一步分基本一样,只不过多了个 const . const 在这里表示 x 在此函数内不可被更改,这样就可以写类似这样的代码 wq(1) wq(a + b) .

第二部分

T t = x;
static char _wq_buffer[39];
int bp = -1;

因为 x 不可变,所以复制了一份给 t ,下面关于 x 的操作一律用 t 代替.

static 在这里表示静态的,相当于在全局定义 _wq_buffer . 给 buffer_wq_ 这个前缀是为了避免和其他的关键字冲突. \(39\) 是 __uint128_t 的十进制下最大位数( \(2^{127} - 1 = 340282366920938463463374607431768211455\) \(39\) 位),如果需要的话可以开更多位.

bp_wq_buffer 当前的有效位数的位置,这里初始值为 \(-1\) 是为了下面方便计算.

第三部分

if(!t) {
    putchar('0');
    return;
}
if(t < 0)
    putchar('-'), t = -t;

特判一些情况,但是直接 -t 的会导致像 \(-128\) \(-65536\) \(\cdots\) \(-2^{8 \times \text{sizeof(T)}}\) 这样的数会输出乱码.

第四部分

while(t)
{
    _wq_buffer[++bp] = t % 10 + '0';
    t /= 10;
}

每次取 t 的个位,然后转化为数字字符倒序存到 _wq_buffer 里,直到 t = 0 .

第五部分

for(int i = bp; i >= 0; i--)
    putchar(_wq_buffer[i]);

输出,因为是倒序存,所以要倒着输出.

参考文献

函数模板

pathenon. isdigit()极品实现.

恋喵大鲤鱼. C++ inline 函数简介

标签:int,sign,buffer,详解,wq,快读,原理,部分
From: https://www.cnblogs.com/kuailedetongnian/p/17589202.html

相关文章

  • PHP8的标记风格-PHP8知识详解
    欢迎来到PHP服务网学习PHP8的知识详解系列教程,本文学习的是PHP8的标记风格,本文教程纠正了很多网站的错误知识,补充了很多教程网站的遗漏之处,虽然很多网站的文章标题也是PHP标记风格。但是很多教程却不适合PHP8的版本了。当PHP8解析一个文件时,PHP8会寻找起始标记和结束标记,也就是<?......
  • PHP8的注释-PHP8知识详解
    欢迎你来到PHP服务网,学习《PHP8知识详解》系列教程,本文学习的是《PHP8的注释》。什么是注释?注释是在程序代码中添加的文本,用于解释和说明代码的功能、逻辑或其他相关信息。注释通常不会被编译器或解释器处理,而是用于帮助程序员理解代码。在大多数编程语言中,注释以特定的语法结构或......
  • PHP8开发工具VS Code的安装-PHP8知识详解
    作为PHP8的开发工具有很多,具有IDE功能的有phpstorm、VisualStudioCode、SublimeText、NetBeans、Eclipse、Codelobster、PHPDesigner等,当然还有很多轻量的工具,比如Notepad、Editplus等。本文给你介绍的是万能编辑器VisualStudioCode,简称VSCode。我为什么选择VisualStudioC......
  • ThinkPHP8是什么?-ThinkPHP8知识详解
    欢迎你来到PHP服务网学习最新的ThinkPHP8开发教程,本文介绍一下ThinkPHP8是什么?1、ThinkPHP8是ThinkPHP框架的最新版本,它在之前版本的基础上进行了改进和优化。它采用了现代化的设计理念和架构,提供了更好的性能和更丰富的功能。该框架具有良好的可扩展性,可以根据项目的需求进行灵活......
  • ThinkPHP8特性和功能介绍-ThinkPHP8知识详解
    ThinkPHP8是一个开源的PHP框架,采用了面向对象编程和MVC(Model-View-Controller)设计模式,提供了丰富的功能和易于使用的API,是一个适用于web应用开发的高效框架。ThinkPHP8具有许多强大的特性和功能,包括但不限于:1、高度灵活的路由机制:可以灵活定义URL路由规则,实现友好的URL访问......
  • 给PHP 8和MySQL 8添加到环境变量-ThinkPHP8知识详解
    在PHPenv安装的时候,环境变量默认的PHP版本是7.4的,MySQL的版本是5.7的,要想使用ThinkPHP8来开发,就必须修改环境变量,本文就详细讲解了如果修改PHP和MySQL的环境变量。1、添加网站启动phpenv,网站,添加网站,域名,根目录,端口,PHP版本都设置好,如图:打开的网站,虽然显示的是php8.0的信息,实际上环......
  • 安装ThinkPHP8-ThinkPHP8知识详解
    我们在讲解前面的文章《搭建PHP8集成环境》和《给PHP8和MySQL8添加到环境变量》以后,现在可以正式的安装ThinkPHP8啦、1、打开phpenv,启动服务,打开昨天新建的tp8.com的目录(D:\phpEnv\www\tp8.com),把里面默认的文件index.php删除。2、在当前目录的地址栏里面,输入cmd,启动命令提示符,在命......
  • 浏览器缓存原理
    本文可以配合本人录制的视频一起食用目的通常说到浏览器缓存,大多是和性能优化有关,使用缓存,通常是两个主要目的,第一是提高访问速度,第二是减少网络IO消耗。当合理配置了缓存,可以得到提升用户体验、减轻服务器负担、节省带宽等效果,这是一种效果显著的前端性能优化手段。四个方面......
  • 管道液位传感器的工作原理图
    管道光电液位传感器是一种有效解决传统机械式传感器低精度和卡死失效问题的新型传感器。它也解决了电容式传感器感度衰减导致的不可控性失效问题。该传感器利用红外光学组件,通过设计形成感应线路,能够判断水与空气中的光折率差异,从而快速稳定地做出状态判断。管道光电液位传感器在许......
  • JavaScript学习 -- SM3算法基本原理
    SM3算法是一种由国家密码管理局发布的哈希算法,被广泛用于数字签名和消息认证等应用中。在JavaScript中,我们可以使用第三方库来计算数据的SM3哈希值。本篇文章将介绍SM3算法的基本原理和相关技术,并提供一些实例来演示如何在JavaScript中使用SM3算法。SM3算法基本原理与MD5、SHA-1、S......