Cpp 小技巧/冷知识记录
记录一下C++的小坑/冷知识。
int8_t 输入
虽然C++提供了很多数据类型,但是最基础的其实还是有无符号的字符和整数浮点数等,其他的数据类型是对它们的简单包装,因此还是表现原本的行为,例如下面两种类型在msvc可能的定义为
1
2typedef signed char int8_t
typedef unsigned char uint8_t
这表明int8_t
和uint8_t
实际上还是char类型的重命名,这会影响很多地方的处理,例如cin在接收字符流输入时,会根据接收变量的数据类型进行转换:如果输入1
,可能会被解释为ASCII字符1
(值为49),也可能会被解释为整数1,这完全取决于接收变量的类型
1
2
3
4
5
6
7char 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_t
和uint8_t
只是char类型的重命名,它们接收到的数据是字符1
而非整数1。
这个异常情形在16位固定类型中就不会出现,因为int16_t
和uint16_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
6int 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
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
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
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
,当然某些编译器也可能发出警告。