C/C++ 中的算术运算及其陷阱(详解,举例分析)

news2024/11/15 3:27:28

在C/C++编程中,算术运算是非常基础且常用的操作。然而,这些看似简单的运算背后却隐藏着一些潜在的陷阱,如果不加以注意,可能会导致程序出现难以预料的错误。本文将探讨C/C++中常见的算术运算及其潜在的陷阱,并通过实例进行说明。

C/C++ 有自己特殊的算术运算规则,如整型提升和寻常算术转换,并且存在大量未定义行为,一不小心就会产生 bug,解决这些 bug 的最好方法就是熟悉整数性质以避免 bug。

bug复现

以下代码示例,猜猜看,运算结果是什么? 都是unit16类型 ,0和65535+1 相等吗?

#include <iostream>

int main()
{
     std::cout<<"Hello World\n";
    
     uint16_t counter = 0;
     
     uint16_t value = 65535;
     
     if(counter == (value + 1)){
         std::cout<<"相等";
     }else{
         std::cout<<"不相等";
     }

    return 0;
}

以上输出的正确答案是不相等。

无符号数的溢出是合法的,有符号数溢出是未定义行为。上面利用整形溢出去跟0比较,这没问题。但问题出在另一个问题上,即整型提升。有网友说是隐式类型转换,也算是吧。准确的解释是整形提升。这个value65535,虽然它的类型是unit16,然而在算术运算时,出现溢出,整型提升,转为了int型,所以65535+1的结果并不是0!

关于整型提升

定义如下:

C11 6.3.1.1

If an int can represent all values of the original type (as restricted by the width, for a bit-field), the value is converted to an int; otherwise, it is converted to an unsigned int. These are called the integer promotions.

在算术运算中,秩小于等于intunsigned int的整型(把它叫做小整型),如char_Bool等转换为intunsigned int,如果int可以表示该类型的全部值,则转换为unsigned int,否则转换为unsigned int。由于在 x86 等平台上,int 一定可以表示这些小整型的值,因此不论是有符号还是无符号,小整型都会隐式地转换为 int,不存在例外(otherwise 所说的情况)。

在某些平台上,int可能和short一样宽。这种情况下,int无法表示unsigned short的全部值,所以unsigned short要提升为unsigned int。这也就是标准中说的“否则,它将转换为unsigned int

// C++17
// 有符号数溢出是未定义行为,但在许多编译器上能看到正常的结果,
// 这里只是观察现象,请不要认为有符号数溢出是合法的
#include <cfloat>
#include <climits>
#include <cstdio>
#include <type_traits>
int main()
{
  signed char cresult, a, b;
  int iresult;
  a = 100;
  b = 90;
  // a,b 提升为整型,a + b = 190 在 int 表示范围内,没有溢出。
  // int 类型的 a + b 赋给表示范围更小的 char 类型 cresult(窄化),
  // 发生溢出,值为 190 - 256 = -66。
  cresult = a + b; /* C++17: cresult {a + b}; 编译器报错,不能将 int 窄化为 signed char */
  // a,b 提升为整型,a + b = 190 在 int 表示范围内,没有溢出。
  // int 类型的 a + b 赋给表示范围相同的 int 类型 iresult,没
  // 发生溢出,值为 190。
  iresult = a + b;
  printf("cresult: %dn", cresult);
  printf("cresult: %dn", iresult);

// ======== output ========
// cresult: -66
// cresult: 190

寻常算术类型 转换规则如下:

6.3.1.8 Usual arithmetic conversions

Many operators that expect operands of arithmetic type cause conversions and yield result types in a similar way. The purpose is to determine a common real type for the operands and result. For the specified operands, each operand is converted, without change of type domain, to a type whose corresponding real type is the common real type. Unless explicitly stated otherwise, the common real type is also the corresponding real type of the result, whose type domain is the type domain of the operands if they are the same, and complex otherwise. This pattern is called the usual arithmetic conversions:

  • First, if the corresponding real type of either operand is long double, the other operand is converted, without change of type domain, to a type whose corresponding real type is long double.
  • Otherwise, if the corresponding real type of either operand is double, the other operand is converted, without change of type domain, to a type whose corresponding real type is double.
  • Otherwise, if the corresponding real type of either operand is float, the other operand is converted, without change of type domain, to a type whose corresponding real type is float.
  • Otherwise, the integer promotions are performed on both operands. Then the following rules are applied to the promoted operands:
    • If both operands have the same type, then no further conversion is needed.
    • Otherwise, if both operands have signed integer types or both have unsigned integer types, the operand with the type of lesser integer conversion rank is converted to the type of the operand with greater rank.
    • Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type.
    • Otherwise, if the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, then the operand with unsigned integer type is converted to the type of the operand with signed integer type.
    • Otherwise, both operands are converted to the unsigned integer type corresponding to the type of the operand with signed integer type.

在算术运算中,不仅整数要转换类型,浮点数也要转换类型。浮点数没有有符号/无符号之分,直接转换为能够容纳操作数的最小浮点类型即可,如单精度浮点数和双精度浮点数运算,单精度浮点数转换为双精度浮点数。

整数之间由于存在无符号/有符号的差异,转换稍微复杂一点:

  1. 进行整型提升
  2. 如果类型相同,不转换
  3. 如果符号相同,将秩低的类型转换为秩高的类型
  4. 如果无符号类型的秩高于或等于其他操作数,将其他操作数转换为该无符号数的类型
  5. 如果有符号数的类型可以表示其他操作数类型的全部值,将其他操作数转换为该有符号数的类型
  6. 如果以上步骤都失败,一律转换为无符号数,再进行上述步骤

算术类型转换是为了找到合理的公共类型,所以当整数符号相同时将较小的整型转换为较大的整型,将精度较小的浮点数转换为精度较大的浮点数。但 C 语言很古怪,当整型符号不同时会尝试将整型转换为无符号类型(无符号类型的秩不低于有符号类型时),这会导致负数被错误的当成非常大的正数。C 语言的算术类型转换很可能是一个失败的设计,会导致非常多难以发现的 bug,比如无符号和有符号数比较:

#include <stdio.h>
int main()
{
    unsigned int a = 100;
    int b = -100;
    printf("100 > -100: %dn", a > b); // b 被转换为 unsiged int,-100 变成一个很大的正数
    return 0;
}
// ===== output =====
100 > -100: 0

整型提升讲的是的小整型转换为秩更高的unsiged intint,当参加算术运算时发生,是寻常算术转换的第一步,因此可以认为是寻常算术类型转换的特例。结合之前的介绍的整形字面量,我们应该已经理解了 C/C++ 算术运算的完整过程:

  1. 推导出字面量的类型
  2. 进行寻常算术转换
  3. 计算

第一步中,如果字面量是十进制字面量,字面量会被推导为有符号数;如果是八进制、十六进制字面量,可能会被推导为无符号数。第二步中,可能会同时出现无符号数和有符号数,寻常算术转换可能会将有符号数转换为无符号数,一定要小心再小心。

不仅发生寻常算术类型转换可能导致 bug,误以为发生了寻常算术类型转换也可能导致 bug,就连 Apple 这样的巨头都在自己的安全编码规范中翻了车,详见Buggy Security Guidance from Apple:

// int n, m;
if (n > 0 && m > 0 && SIZE_MAX/n >= m) {
    size_t bytes = n * m;
    // allocate “bytes” space
}

Apple 的原意是先判断乘法是否溢出,再将乘积赋给一个足够宽的变量避免溢出,但这个实现有两个错误:

  • int型变量的最大值是INT_MAX而不是SIZE_MAX
  • nm都是int型变量,乘积溢出后会被截取到int的表示范围内,然后再赋给bytes

所以,在涉及类型宽度不同的算术类型时要格外小心,可能会出现结果被截取后再赋给变量的情况。

有了这些知识,回头看这一节中的 stackoverflow 问题。第一个代码块中,两变量的类型是intunsigned int,发生寻常算术类型转换,int转换为unsigned int,负数变正数 UINT_MAX - 1,相加后得到UINT_MAX,因此(1 - 2) > 0;第二个代码块中,两变量的类型是charunsigned char,发生整型提升,转换为int,相加的到负数,因此(1 - 2) > 0

再举一例

整型提升与寻常算术转换

再看一个 stackoverflow 上的提问Implicit type promotion rules,通过这个例子来了解 C/C++ 算术运算中的整型提升integer promotion)和寻常算术转换usual arithmetic conversion)。提问者编写了以下两段代码,发现在第一段代码中,(1 - 2) > 0,而在第二段代码中(1 - 2) < 0。这种奇怪的现象就是整型提升和寻常算术转换导致的。

unsigned int a = 1;
signed int b = -2;
if(a + b > 0)
    puts("-1 is larger than 0");
// ==============================================
unsigned short a = 1;
signed short b = -2;
if(a + b > 0)
    puts("-1 is larger than 0"); // will not print

整型提升和寻常算术转换涉及到整型的秩(优先级),规则如下:

  • 所有有符号整型的优先级都不同,即使宽度相同。

    假如intshort宽度相同,但int的秩大于short

  • 有符号整型的秩大于宽度比它小的有符号整型的秩

    long long宽度为 64 比特,int宽度为32比特,long long的秩更大

  • long long的秩大于longlong的秩大于intint的秩大于signed char

  • 无符号整型的秩等于对应的有符号整型

    unsigned int的秩等于对应的int

  • char的秩等于unsiged charsigned char

  • 标准整型的秩大于等于对应宽度的拓展整型

  • _Bool的秩小于所有标准整型

  • 枚举类型的秩等于对应整型

上面的规则看似复杂,但其实就是说:内置整型是一等公民,拓展整型是二等公民,_Bool是弟弟,枚举等同于整型。

算术溢出检测

(omega) 位整数的和需要 (omega + 1) 位才能表示,(w) 位整数的积需要 (2omega) 位才能表示,计算后 C/C++ 仅截取低 (omega) 位,可能会发生溢出。C/C++ 不会自动检测溢出,一旦发生溢出,程序就会在错误的状态中运行。

由于编译器会进行死代码消除dead code elimination)和未定义行为消除undefined behavior elimination),依赖 UB 的代码很可能会被编译器消除掉,即使没被消除掉,发生未定义行为就无法保证程序处于正确状态,参考It’s Time to Get Serious About Exploiting Undefined Behavior。以一种错误的缓冲区溢出检测方法来说明编译器优化对代码的影响。

// 这个例子来自 https://www.kb.cert.org/vuls/id/162289
char buf[1024];
int len;
len = 1<<30;
// do something
if(buf+len < buf) // check
    // do something

如果len是一个负数,那么buf + len < buf一定为真。这个逻辑是对的,但 C 语言中数组越界是未定义行为,编译器可以忽略依赖未定义行为的代码,直接消除掉if语句,因此上面的检测实际上没有任何用处。因此必须在有符号数溢出之前进行检测。

对于无符号加法 (c = a + b),溢出后 (c < aspace and space c < b);对于有符号加法 (c = a + b),当且仅当 (a,b) 同号,但 (c) 与 (a, b) 符号相反时溢出,即 (a, b > 0 rightarrow c < 0 space 或 space a, b < 0 rightarrow c > 0)。注意,加法是一个阿贝尔群,不论是否溢出,(c - a) 都等于 (b),所以不能以和减加数的办法检测溢出。

#include <limits.h>

int signed_int_add_overflow(signed int a, signed int b)
{
    // 检测代码不能导致有符号数溢出
    return ((b > 0) && (a > (INT_MAX - b))) || ((b < 0) && (a < (INT_MIN - b)));
}

int unsigned_int_add_overflow(unsigned int a, unsigned int b)
{
    // 无符号数溢出合法,检测代码可以依赖溢出的值
    unsigned int sum = a + b;
    return (sum < a) && (sum < b);
}

乘法发生溢出时,将 (2omega) 的积截取到 (w) 位,得到的积一定不等于正常的数学运算的积。

#include <limits.h>
#include <stdio.h>

int unsigned_int_multiply_overflow(unsigned int a, unsigned int b)
{
    if (a == 0 && b == 0) {
        return 0;
    }
    unsigned int product = a * b; // 无符号溢出是合法的
    return (a != 0) ? product / a == b : product / b == a;
}

int signed_int_multiply_overflow(signed int a, signed int b)
{
    // a 和 b 可能为负,也可能为正,需要考虑 4 种情况
    if (a > 0) {     // a is positive
        if (b > 0) { // a and b are positive
            if (a > (INT_MAX / b)) {
                return 1;
            }
        } else { // a positive, b nonpositive
            if (b < (INT_MIN / a)) {
                return 1;
            }
        }            // a positive, b nonpositive
    } else {         // a is nonpositive
        if (b > 0) { // a is nonpositive, b is positive
            if (a < (INT_MIN / b)) {
                return 1;
            }
        } else { // a and b are nonpositive
            if ((a != 0) && (b < (INT_MAX / a))) {
                return 1;
            }
        } // End if a and b are nonpositive
    }     // End if a is nonpositive
    return 0;
}

位运算技巧

位运算是 C/C++ 的一大利器,存在大量的技巧,我不是这方面的高手,这里只是介绍几个最近学习中碰到的让我打开眼界的技巧,感兴趣的可以参考这份清单(我没有看)Bit Twiddling Hacks。

  • 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
// 利用异或消除相同元素
int SingleNumber(std::vector<int>& nums)
{
  int ret = 0;
  for (ssize_t i = nums.size() - 1; i >= 0; --i) {
    ret ^= nums[i];
  }
  return ret;
}
  • 消去二进制数最低位的 1

可以观察到,整数减一会消去最低位的 1(0 反转为 1),低位的 0 全部反转为 1,因此val & (val - 1)可以消去最低位的 1 且不再后面生成新的 1。

unsigned int val = /* something */;
val &= (val - 1); /* 消去最低位的 1 */

利用这个性质,可以快速计算出二进制数中 1 的个数:

int CountOfBinaryOne(unsigned int val) {
    int cnt = 0;
    while (val != 0) {
        val &= (val - 1);
        ++cnt
    }
    return cnt;
}

当整数是 2 的整数幂时,二进制表示中仅有一个 1,所以这个方法还可以用来快速判断2 的幂。

int IsPowerOf2(unsigned int val) {
    return (val & (val - 1)) == 0;
}
  • 找出不大于 N 的 2 的最大幂

从二进制的角度看,求不大于 N 的最大幂就是将 N 位数最高的 1 以下的 1 全部清空。可以不断消除低位的 1,直到整数为 0,整数变成 0 之前的值就是不大于 N 的 2 的最大幂。

这里还有更好的方法,在 O(1) 时间, O(1) 空间实现功能。先将最高位的 1 以下的比特全部置为 1,然后加一(清空全部为 1 的比特,并将进位),右移一位。举例如下:

01001101 --> 01111111 --> 01111111 + 1 --> 10000000 --> 01000000

代码如下:

unsigned int MinimalPowerOf2(unsigned int val) {
    n |= n >> 1;
    n |= n >> 2;
    n |= n >> 4;
    n |= n >> 8;
    n |= n >> 16;
    return (n + 1) >> 1;
}

这个实现无法处理最高位为 1 的情况,这时val会被或操作变成UINT_MAX,最后(n + 1) >> 1得到0。正确的版本如下:

unsigned int MinimalPowerOf2(unsigned int n)
{
    if ((int)n < 0) { // 最高位为 1
        return 1 << 31;
    }
    n |= n >> 1;
    n |= n >> 2;
    n |= n >> 4;
    n |= n >> 8;
    n |= n >> 16;
    return (n + 1) >> 1;
}

整数溢出

整数溢出是C/C++编程中常见的一个陷阱。当一个整数变量的值超过了其类型所能表示的最大值时,就会发生溢出,导致结果变得不可预测。

示例代码

#include <stdio.h>

int main() {
    int a = 2147483647; // 这是int类型的最大值
    int b = 1;
    int result = a + b; // 这里会发生溢出
    printf("Result: %d\n", result); // 输出结果会是-2147483648
    return 0;
}

在这个例子中,a的值是int类型的最大值,加上b的值1后,结果超出了int类型的表示范围,导致溢出,结果变成了负数。

C/C++ 整数的阴暗角落

C/C++ 期望自己可以在所有机器上运行,因此不能在语言层面上把整数的编码、性质、运算规定死,这让 C/C++ 中存在许多未规定的阴暗角落和未定义行为。许多东西依赖于编译器、操作系统和处理器,这里通称为运行平台。

  • 标准没有规定整数的编码,编码方式依赖于运行平台。

  • char是否有符号依赖于运行平台,编译器有选项可以控制,如 GCC 的 -fsign-char。

  • 移位大小必须小于整数宽度,否则是未定义行为。

  • 无符号数左移 K 位结果为原来的 2^K 次方,右移 K 位结果为原来的数除 2^K 次方。仅允许对值非负的有符号数左移右移,运算结果同上,对负数移位是未定义的

  • 标准仅规定了标准内置整数类型(如int等)的最小宽度和大小关系(如long不能小于int),但未规定具体大小,如果要用固定大小的整数,请使用拓展整数类型(如uint32_t)等。

  • 无符号数的溢出是合法的,有符号数溢出是未定义行为

整型字面量

常常有人说 C/C++ 中的整数字面量类型是int,但这种说法是错误的。C/C++ 整形字面量究竟是什么类型取决于字面量的格式和大小。StackOverflow 上有人问为什么在 C++ 中(-2147483648> 0)返回true,代码片段如下:

if (-2147483648 > 0) {
    std::cout << "true";
} else {
    std::cout << "false";
}

 现在让我们来探索为什么负数会大于 0。一眼看过去,-2147483648似乎是一个字面量(32 位有符号数的最小值),是一个合法的int型变量。但根据 C99 标准,字面量完全由十进制(1234)、八进制(01234)、十六进制(0x1234)标识符组成,因此可以认为只有非负整数才是字面量,负数是字面量的逆元。在上面的例子中,2147483648是字面量,-2147483648是字面量2147483648的逆元。

字面量的类型取决于字面量的格式和大小,C++11(N3337 2.14.2)规则如下:

N3337 2.14.2

对于十进制字面量,编译器自动在intlonglong long中查找可以容纳该字面量的最小类型,如果内置整型无法表示该值,在拓展整型中查找能表示该值的最小类型;对于八进制、十六进制字面量,有符号整型无法表示时会选择无符号类型。如果没有足够大的内置/拓展整型,程序是错误的,GCC/Clang 会发出警告。

在 C89/C++98 没有long long和拓展整型,因此在查找完long后查找unsigned long

现在再看上面的代码段就很清晰了,在 64 位机上,不论是 C89/C++98 还是 C99/C++11,都能找到容纳该值的long类型(8 字节),因此打印false。在 32 位机上,long占据 4 个字节无法容纳字面量,在 C89/C++98 下,2147483648的类型为unsigned long,逆元-2147483648是一个正数(2^32 - 2147483648),打印true;在 C99/C++11 下,2147483648的类型为long long,逆元-2147483648是一个负数(-2147483648),打印false

经过以上分析,可以判断出提问者是在 32 位机上使用 C++98 做的实验。

和字面量有关的另一个有意思的问题是INT_MIN的表示方法。《深入理解计算机系统(第 3 版)》2.2.6 中介绍的代码如下:

/* Minimum and maximum values a ‘signed int’ can hold. */
#define INT_MAX 2147483647
#define INT_MIN (-INT_MAX - 1)

《深入理解计算机系统》没有给出解释,但这种写法很显然为了避免宏INT_MIN被推导为long long(C99/C++11)或unsigned long(C89/C++98)。

浮点数精度问题

浮点数在计算机中的表示是近似的,而不是精确的。因此,在进行浮点数运算时,可能会遇到精度问题,导致结果与预期不符。

示例代码

#include <stdio.h>

int main() {
    float a = 0.1;
    float b = 0.2;
    float result = a + b;
    printf("Result: %.10f\n", result); // 输出结果会是0.3000000119
    return 0;
}

在这个例子中,ab的值分别是0.1和0.2,但它们的和并不是精确的0.3,而是一个接近0.3的值。

除零错误

在C/C++中,除法运算中除数不能为零,否则会导致程序崩溃或产生未定义行为。

示例代码

#include <stdio.h>

int main() {
    int a = 10;
    int b = 0;
    int result = a / b; // 这里会发生除零错误
    printf("Result: %d\n", result);
    return 0;
}

在这个例子中,b的值为0,进行除法运算时会导致除零错误,程序可能会崩溃。

类型转换问题

在进行算术运算时,不同类型的数据之间可能会发生隐式类型转换,这可能会导致结果与预期不符。

示例代码

#include <stdio.h>

int main() {
    int a = 5;
    float b = 2.5;
    float result = a + b; // a会被隐式转换为float类型
    printf("Result: %.2f\n", result); // 输出结果会是7.50
    return 0;
}

在这个例子中,aint类型,bfloat类型,进行加法运算时,a会被隐式转换为float类型,结果是7.5。

总结

C/C++中的算术运算虽然基础,但其中隐藏着许多陷阱。程序员在进行算术运算时,需要特别注意整数溢出、浮点数精度问题、除零错误以及类型转换问题,以避免程序出现难以预料的错误。通过本文的介绍和示例代码,希望读者能够更好地理解和避免这些陷阱,编写出更加健壮的C/C++程序。

  1. 尽可能不要混用无符号数和有符号数,如果一定要混用,请小心谨慎。

  2. 在涉及不同大小的数据类型时要小心,可能存在溢出和截断。

  3. 只要存在有符号数就要考虑溢出导致的未定义行为和可能的符号反转。

  4. 尽量不对小于int的整数类型执行算术运算,可能溢出和涉及整型提升。

  5. 如果要利用整数溢出,必须使用无符号数。

参考

  • C语言的整型溢出问题

  • C99/C++11 标准草案

  • Buggy Security Guidance from Apple

  • 《深入理解计算机系统》

  • 找出不大于 N 的最大的 2 的幂指数

  • Impilicit type promotion rules

  • How do I detect unsigned integer multiply overflow?

  • (-2147483648> 0) returns true in C++?

  • 位运算有哪些奇技淫巧?

https://www.cnblogs.com/kongj/p/14612362.html

C/C++ 中的算术及其陷阱 | 高性能架构探索 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2107888.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

「Python程序设计」模块式编程:函数

​小时候&#xff0c;我们都或多或少的玩过一些积木玩具。通过把动物&#xff0c;或者是人物的各个组成部分&#xff0c;一小块&#xff0c;一小块地搭建起来&#xff0c;最终&#xff0c;就组成了我们最终想要的形状。这有点类似于乐高积木&#xff0c;通过把固定的块状物&…

Android Launcher3

一、定义与功能 Android Launcher是Android操作系统中的一个重要组件&#xff0c;它负责管理和呈现用户界面&#xff0c;包括桌面、应用程序抽屉和部件。Launcher不仅为用户提供了一个启动应用程序的入口&#xff0c;还允许用户自定义手机的主屏幕、图标、小部件布局以及一些基…

家里有宠物应该用哪款宠物空气净化器?希喂、美的真实测评

养了猫之后&#xff0c;从此我的生活开始有颜色&#xff0c;终于有声音了&#xff0c;每天下班回家终于不是直接就躺在沙发上然后洗洗就睡&#xff0c;现在有猫咪陪着我一起玩&#xff0c;甚至还会和它聊聊天&#xff0c;家里我走到哪它就跟到哪&#xff0c;身后多了一个小跟屁…

智能手机、汽车新应用,星纪魅族幸运星号”卫星即将发射

朋友们&#xff01;你想象过我们的智能手机和汽车能与卫星直接通信吗&#xff1f; 这听起来像是科幻小说里的情节&#xff0c;但很快&#xff0c;这将成为现实&#xff01;星纪魅族科技最近宣布了一个振奋人心的消息——他们将与时空道宇合作发射“星纪魅族幸运星号”卫星。这…

专业软件测试服务机构分享:小程序测试步骤和作用

在数字经济飞速发展的今天&#xff0c;小程序因其轻量、便捷的特点受到了广泛关注。作为技术服务的重要组成部分&#xff0c;软件测试成为确保小程序质量的关键环节。 一、小程序测试的定义   小程序测试是指对小程序进行系统性验证和验证的过程&#xff0c;旨在检查其功能、…

身份证实名认证-实名认证API接口文档

1、接口介绍及适用范围 身份证实名认证是指通过验证个人身份证信息的真实性&#xff0c;来确认用户身份的一种安全验证方式。这种认证方式广泛应用于各种需要身份验证的场合&#xff0c;如金融交易、社交媒体注册、网络游戏登录、电子商务平台购物等。 2、接口地址 输入姓名和…

ElasticSearch-集群读写

ES跨集群搜索&#xff08;CCS&#xff09;分片的设计和管理 算分不准 dfs_query_then_fetch如何设计分片数 ES底层读写工作原理 ES写入数据的过程ES读取数据的过程写数据底层原理 提升集群的读写性能 提升集群读取性能提升集群写入性能 ES跨集群搜索&#xff08;CCS&#xff0…

git管理历险记

本篇文章主要是记录一下公司内git管理策略的变更&#xff0c;又如何因地制宜的磨合出适合团队的方法论&#xff0c;以便未来的职业生涯遇到类似的问题可以稍微触类旁通下。 传统git策略 dev -> test -> pre -> main 这也是比较经典的一个环境对应一个分支&#xff…

[pytorch] --- pytorch基础之模型验证套路

利用已经训练好的模型&#xff0c;拿出训练集中的部分数据进行测试 下面给出完整的示例代码&#xff1a; # -*- coding: utf-8 -*- # 作者&#xff1a;小土堆 # 公众号&#xff1a;土堆碎念 import torch import torchvision from PIL import Image from torch import nnimage…

游戏行业社招上岸指南

鉴于游戏行业的蓬勃发展&#xff0c;游戏领域的各大头部企业每年仍然有大量用人需求。即将到来的“金九银十”可谓是招聘者与应聘者的双向奔赴。 然而除了校招之外&#xff0c;社招同样是秋招中不可忽视的组成部分。因此&#xff0c;这份社招求职指南也终于千呼万唤始出来&…

FPGA第 9 篇,Verilog 中的关键字和基数

前言 在 Verilog 中&#xff0c;关键字&#xff08;Keywords&#xff09;和基数&#xff08;Radix&#xff09;是语言的重要组成部分&#xff0c;它们有助于描述和定义硬件设计。上期分享了 Verilog 的基本使用&#xff0c;以及数据类型、逻辑值和算数运算符的简单应用&#x…

服务器安装pytorch-阿里云-centos7

原文阅读&#xff1a;【巨人肩膀社区专栏分享】服务器安装pytorch-阿里云-centos7 1、创建一个虚拟环境 conda create -n pytorch python3.10 安装成功&#xff1a; &#xfeff; &#xfeff;&#xfeff; 但是使用上面的命令会失败&#xff08;疑问&#xff1f;&#xf…

Apache Guacamole 安装及配置VNC远程桌面控制

文章目录 官网简介支持多种协议无插件浏览器访问配置和管理应用场景 Podman 部署 Apache Guacamole拉取 docker 镜像docker-compose.yml部署 PostgreSQL生成 initdb.sql 脚本部署 guacamole Guacamole 基本用法配置 VNC 连接 Mac 电脑开启自带的 VNC 服务 官网 https://guacam…

编程式路由跳转

点击左侧的导航栏&#xff0c;会跳转到相应的页面。可以在导航栏上定义一个点击事件&#xff0c;在vue-router中解构useRouter,把useRouter赋给router,使用***router.push()***就可以实现编程式路由跳转。 路由跳转有:to,push,replace push模式在浏览器上有历史记录&#xff…

【华为】测试工程师面试题汇总,你可知道华为的高薪技术岗有多香~

华为一直是求职者重点投递的热门企业&#xff0c;面对丰厚的薪资福利&#xff0c;无数985、211的学子挤破脑袋都想占据一席之地。 华为2021年发放工资、薪金及其他福利方面的费用达1371亿元人民币&#xff0c;按华为19.5万员工计算&#xff0c;华为员工人均年薪为70.3万&#…

[每日一题]3174. 清除数字

本专栏内容为&#xff1a;算法学习专栏&#xff0c;分为优选算法专栏&#xff0c;贪心算法专栏&#xff0c;动态规划专栏以及递归&#xff0c;搜索与回溯算法专栏四部分。 通过本专栏的深入学习&#xff0c;你可以了解并掌握算法。 &#x1f493;博主csdn个人主页&#xff1a;小…

Vue Router 入门指南:基础配置、路由守卫与动态路由

Vue Router 入门指南&#xff1a;基础配置、路由守卫与动态路由 简介&#xff1a; Vue Router 是 Vue.js 官方的路由管理工具&#xff0c;用于在 Vue 应用中实现页面导航。掌握 Vue Router 的基本配置、路由守卫以及动态路由和懒加载是构建复杂 Vue 应用的基础。本文将详细介绍…

web前端-HTML标签

一、HTML语法规范 1.基本语法概述 &#xff08;1&#xff09;HTML标签是由尖括号包围的关键词&#xff0c;例如<html> &#xff08;2&#xff09;HTML标签通常是成对出现的,例始<html>和</html>&#xff0c;我们称为双标签。标签对中的第一个标签是开始标…

STM32(一)简介

一、stm32简介 1.外设接口 通过程序配置外设来完成功能 2.系统结构 3.引脚定义 4.启动配置 5.最小系统电路

解决:axios 请求头url传参数组时发生400错误

一、前言 axios封装的网络请求&#xff0c;url传参时&#xff0c;数组作为参数传递&#xff0c;发生400错误请求时数组参数转url会保留 [] 二、原因 RFC3986&#xff1a;除了 数字 字母 -_.~ 不会被转义&#xff0c;其他字符都会被以百分号&#xff08;%&#xff09;后跟两位…