std::abs 和 abs 是一样的吗?
- time: 2024.10.26
- tags: C, C++
float 型参数
C++ 有 float std::abs(float)
函数 (https://en.cppreference.com/w/cpp/numeric/math/fabs),
但 C 语言没有 float abs(float)
, 只有 int abs(int)
或 long/long long等整数类型的 ( https://en.cppreference.com/w/cpp/numeric/math/abs ).
于是乎,在 .cpp 文件中得到不一样的结果:
#include <cmath>
#include <iostream>
int main()
{
float a = 0.2f;
float b = std::abs(a);
std::cout << b << std::endl; // 0.2
float c = abs(a); // note here
std::cout << c << std::endl; // 0
return 0;
}
cpp 代码中的 using namespace std
人们大都知道, .h/.hpp
文件中不要用 using namespace std
。 那么在 .cpp
中使用,就 ok 了么?
对于前一节的样例代码,我们增加 using namespace std
, 运行结果会不一样
#include <cmath>
#include <iostream>
using namespace std; // note here
int main()
{
float a = 0.2f;
float b = std::abs(a);
std::cout << b << std::endl; // 0.2
float c = abs(a); // note here
std::cout << c << std::endl; // 0.2
return 0;
}
代码和运行结果: https://godbolt.org/z/oWYYq31zr
为什么 abs(float)
函数,之前是 ::abs(float)
, 现在是 std::abs(float)
? 这是编译器的 Name Lookup 的结果:
https://en.cppreference.com/w/cpp/language/lookup
具体说来,是 Non-member function definition 中的 name lookup, 是从当前 block 开始找,自内向外。
https://en.cppreference.com/w/cpp/language/unqualified_lookup
以变量的查找为例,解释如下; 函数的查询也是一样的
int n = 1; // declaration
namespace N
{
int m = 2;
namespace Y
{
int x = n; // OK, lookup finds ::n
int y = m; // OK, lookup finds ::N::m
int z = k; // Error: lookup fails
}
int k = 3;
}
std::abs(int) 和 abs(int) 一样吗?
前面两小节,我们确定了的两件事情:
std::abs(float)
和::abs(int)
是不同的函数,::abs(0.2)
得到0而不是0.2。 编译器很「友好」的不报告任何 warningabs(0.2)
可能被查找为::abs(int)
, 也可能被查找为std::abs(float)
, 要看是否using namespace std
了
而对于 abs(1)
和 std::abs(1)
, 并不存在前面提到的 name lookup 得到不同结果的情况。abs(int) 和 std:abs(int) 是同一个函数.
在 cmath 文件中存在如下声明:
namespace std {
using ::abs;
}
当然了, 实际的 cmath 比这复杂,比如 AppleClang 里的:
_LIBCPP_BEGIN_NAMESPACE_STD
using ::abs _LIBCPP_USING_IF_EXISTS;
展开后:
namespace std { inline namespace __1 {
using ::abs;
}
为了进一步验证 abs(int)
和 std::abs(int)
是同一个函数,可以获取和比较这两个函数指针:
auto stdAbsPtr = static_cast<int(*)(int)>(std::abs);
auto absPtr = static_cast<int(*)(int)>(abs);
比较它们:
template<typename T>
bool isSameFunction(T func1, T func2) {
return func1 == func2;
}
完整代码:
#include <iostream>
#include <cmath>
#include <cstdlib>
#include <type_traits>
// 模板函数,用于比较两个函数指针是否相同
template<typename T>
bool isSameFunction(T func1, T func2) {
return func1 == func2;
}
int main() {
// 获取 std::abs 和 abs 的函数指针
auto stdAbsPtr = static_cast<int(*)(int)>(std::abs);
auto absPtr = static_cast<int(*)(int)>(abs);
// 比较两个函数指针是否相同
bool areSameFunction = isSameFunction(stdAbsPtr, absPtr);
std::cout << "std::abs and abs point to the same function: "
<< (areSameFunction ? "Yes" : "No") << std::endl;
return 0;
}
输出:
std::abs and abs point to the same function: Yes
扩展
进一步的,不必拘泥于 std::abs 和 abs, 我们可以自己写个函数,并且同时提供使用了 namespace 限定符的版本.
例如如下的 nb::wudi(int)
和 wudi(int)
是同一个函数, 而 sb::wudi(int)
和 wudi(int)
就不是同一个函数:
https://godbolt.org/z/asKdb49o8
#include <stdio.h>
#include <type_traits>
#include <iostream>
// 模板函数,用于比较两个函数指针是否相同
template<typename T>
bool isSameFunction(T func1, T func2) {
return func1 == func2;
}
void wudi(int a) {
printf("%d is wudi\n", a);
}
namespace nb {
using ::wudi;
}
namespace sb {
void wudi(int a)
{
printf("sb, you are not wudi\n");
}
}
int main()
{
// 获取 函数指针
auto nbWudiPtr = static_cast<void(*)(int)>(nb::wudi);
auto wudiPtr = static_cast<void(*)(int)>(wudi);
// 比较两个函数指针是否相同
bool areSameFunction = isSameFunction(nbWudiPtr, wudiPtr);
std::cout << "nb::wudi and wudi point to the same function: "
<< (areSameFunction ? "Yes" : "No") << std::endl;
// 获取 函数指针
auto sbWudiPtr = static_cast<void(*)(int)>(sb::wudi);
// 比较两个函数指针是否相同
bool areSameFunction2 = isSameFunction(nbWudiPtr, sbWudiPtr);
std::cout << "nb::wudi and wudi point to the same function: "
<< (areSameFunction2 ? "Yes" : "No") << std::endl;
return 0;
}
nb::wudi and wudi point to the same function: Yes
sb::wudi and wudi point to the same function: No