记录一下C++的小坑/冷知识。

int8_t 输入

虽然C++提供了很多数据类型,但是最基础的其实还是有无符号的字符和整数浮点数等,其他的数据类型是对它们的简单包装,因此还是表现原本的行为,例如下面两种类型在msvc可能的定义为

1
2
typedef signed char int8_t
typedef unsigned char uint8_t

这表明int8_tuint8_t实际上还是char类型的重命名,这会影响很多地方的处理,例如cin在接收字符流输入时,会根据接收变量的数据类型进行转换:如果输入1,可能会被解释为ASCII字符1(值为49),也可能会被解释为整数1,这完全取决于接收变量的类型

1
2
3
4
5
6
7
char a; cin >> a; // '1' = 49

int b; cin >> b; // 1

int8_t c; cin >> c; // '1' = 49

uint8_t d; cin >> d; // '1' = 49

这种情况下,由于int8_tuint8_t只是char类型的重命名,它们接收到的数据是字符1而非整数1。 这个异常情形在16位固定类型中就不会出现,因为int16_tuint16_t通常是short类型的重命名。

绝对值函数

在C语言中,stdlib.h提供的abs()函数可以用来获取绝对值,但是非常坑的一点是,这个函数只支持获取整数的绝对值!因为C语言函数参数的类型必须是固定的

1
int abs(int x);

使用math.h专门提供的fabs()函数才能正确获取浮点数的绝对值

1
double fabs(double x);

由于C++支持函数重载,可以直接支持不同类型的参数,因此它提供的std::abs()对于不同的数据类型结果都是合理的

1
2
3
4
5
6
int abs(int n);
long abs(long n);
long long abs(long long n);
float abs(float x);
double abs(double x);
long double abs(long double x);

当然C++也提供了浮点数对应的std::fabs()函数。

这里非常不建议缺省std::,因为到底会调用C语言的abs()函数还是C++的std::abs()函数取决于很多外部因素,并不总是保证调用的是C++的版本。

实参依赖查找(ADL)

C++提供了一个简化命名空间使用的机制——实参依赖查找(ADL),它的基本内容为:如果函数和它的至少一个实参都属于某个命名空间中,那么在明确了实参的命名空间之后,对该函数调用时是可以省略命名空间前缀的,此时我们即使没有导入这个命名空间的符号,编译器仍然会尝试在对应的命名空间中进行查找。

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>

namespace demo {
struct Foo {
int s;
};

int foo(Foo f) { return f.s; }

} // namespace demo

int main(int argc, char *argv[]) {
std::cout << foo(demo::Foo{1}) << "\n";
return 0;
}

这里会自动根据参数类型所属的命名空间进行查找。

但是如果我们在外侧也提供了同名的定义,编译器就无法确定到底调用哪一个实现,导致编译报错,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>

namespace demo {
struct Foo {
int s;
};

int foo(Foo f) { return f.s; }

} // namespace demo

int foo(demo::Foo f) { return f.s + 10; }

int main(int argc, char *argv[]) {
std::cout << foo(demo::Foo{1}) << "\n"; // compile error
return 0;
}

存在名称冲突时,我们必须加上命名空间来消歧义,在这个例子中加上::demo::会分别调用两个版本的foo函数,此时才能编译通过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>

namespace demo {
struct Foo {
int s;
};

int foo(Foo f) { return f.s; }

} // namespace demo

int foo(demo::Foo f) { return f.s + 10; }

int main(int argc, char *argv[]) {
std::cout << ::foo(demo::Foo{1}) << "\n";
std::cout << demo::foo(demo::Foo{1}) << "\n";
return 0;
}

这个小技巧对std也生效,例如常用的std::get在很多情况下可以直接省略为get,当然某些编译器也可能发出警告。