Cpp 小技巧/冷知识记录
记录一下C++的小坑/冷知识。
int8_t 输入
虽然C++提供了很多数据类型,但是最基础的其实还是有无符号的字符和整数浮点数等,其他的数据类型是对它们的简单包装,因此还是表现原本的行为,例如下面两种类型在msvc可能的定义为
1 | typedef signed char int8_t |
这表明int8_t
和uint8_t
实际上还是char类型的重命名,这会影响很多地方的处理,例如cin在接收字符流输入时,会根据接收变量的数据类型进行转换:如果输入1
,可能会被解释为ASCII字符1
(值为49),也可能会被解释为整数1,这完全取决于接收变量的类型
1 | char a; cin >> a; // '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 | int abs(int n); |
当然C++也提供了浮点数对应的std::fabs()
函数。
这里非常不建议缺省
std::
,因为到底会调用C语言的abs()
函数还是C++的std::abs()
函数取决于很多外部因素,并不总是保证调用的是C++的版本。
实参依赖查找(ADL)
C++提供了一个简化命名空间使用的机制——实参依赖查找(ADL),它的基本内容为:如果函数和它的至少一个实参都属于某个命名空间中,那么在明确了实参的命名空间之后,对该函数调用时是可以省略命名空间前缀的,此时我们即使没有导入这个命名空间的符号,编译器仍然会尝试在对应的命名空间中进行查找。
例如
1 |
|
这里会自动根据参数类型所属的命名空间进行查找。
但是如果我们在外侧也提供了同名的定义,编译器就无法确定到底调用哪一个实现,导致编译报错,例如
1 |
|
存在名称冲突时,我们必须加上命名空间来消歧义,在这个例子中加上::
和demo::
会分别调用两个版本的foo
函数,此时才能编译通过。
1 |
|
这个小技巧对std
也生效,例如常用的std::get
在很多情况下可以直接省略为get
,当然某些编译器也可能发出警告。