如何做公司宣传网站,金泉网普通会员可以建设网站吗,wordpress纯代码,软件开发自学需要这篇博客名字起得可能太自大了#xff0c;搞得自己像C大牛一样#xff0c;其实并非如此。C有很多隐藏在语法之下的特性#xff0c;使得用户可以在不是特别了解的情况下简单使用#xff0c;这是非常好的一件事情。但是有时我们可能会突然间发现一个很有意思的现象#xff0… 这篇博客名字起得可能太自大了搞得自己像C大牛一样其实并非如此。C有很多隐藏在语法之下的特性使得用户可以在不是特别了解的情况下简单使用这是非常好的一件事情。但是有时我们可能会突然间发现一个很有意思的现象然后去查资料最终学到了C的一个特性。所以很可能每个人理解的C都有很大不同我只是从自己的角度去跟大家分享而已。 C的函数调用相比于C的函数调用要复杂很多这主要是由于函数重载、类、命名空间等特性造成的。 根据Stephan T. Lavavej的介绍C编译器在解析一次函数调用的时候要按照顺序做以下事情根据具体情况有些步骤可能会跳过的 1) 名字查找Name Lookup 2) 模板参数类型推导Template Argument Deduction 3) 重载决议Overload Resolution 4) 访问控制Access Control 5) 动态绑定Dynamic Binding 本篇博客主要跟大家分享下自己对Name lookup的理解。 对于编译器来说完成一次函数调用之前必须能够先找到这个函数。在C中这个问题很简单就是函数调用点向上找函数声明如果能找到就匹配如果找不到就报错。在C中有函数重载Function Overload和名字空间Namespace的概念使得这个问题变得有些复杂但非常有意思。 一、从一段程序讲起 首先问大家个问题在C程序中我们经常这样写 #include iostreamint main()
{std::cout Hello, Core C! std::endl;
} 请问上面main函数中的语句使用了重载操作符如果用普通函数调用的语法该怎么写 显然这个语句一共有两次operator函数调用。那么这两个operator函数调用是一样的函数吗如果不是区别在哪里 OK告诉大家答案吧上面的代码等价于这样写 #include iostreamint main()
{operator(std::cout, Hello, Core C!);std::cout.operator(std::endl);
} 大家看出来了吧第一次operator调用的是一个全局函数而第二次调用的是一个成员函数。 如果再深入一些std::endl到底是个什么东西直觉上这就是用来换行的可能就是一个\n。而事实上std::endl是一个函数。为什么呢我们先看看VC中std::endl的代码 templateclass _Elem,class _Traits inlinebasic_ostream_Elem, _Traits__CLRCALL_OR_CDECL endl(basic_ostream_Elem, _Traits _Ostr){ // insert newline and flush stream_Ostr.put(_Ostr.widen(\n));_Ostr.flush();return (_Ostr);} std::endl是一个全局函数接受一个basic_ostream参数_Ostr。函数内部做了两件事情一、调用_Ostr的put(const char*)成员函数输出\n二、调用_Ostr的flush()函数。其中第二步保证了ostream立即刷新这也就是std::cout”\n”和std::coutstd::endl的区别。也就只有std::endl是个函数才能完成这样的操作。 还是最开始的例子如果写成这样 #include iostreamint main()
{cout Hello, Core C! endl;
} 编译器会提示“undeclared identifier”因为我们没有指定任何namespace编译器默认到全局命名空间中查找相当于::cout Hello, Core C! ::endl;而程序中并没有提供的cout和endl因此找不到。这个大家应该都比较熟悉了。 再问大家一个问题 operator(std::cout, Hello, Core C!); 为什么这个语句不写成 std::operator(std::cout, Hello, Core C!); 也能通过编译呢毕竟operator是在std名字空间里全局名字空间里面并没有为什么没有报错呢 二、Name Lookup的主要机制 这就要从C标准中对于名字查找的描述说起了。C中有三种主要名字查找机制 a) 隐式名字查找Unqualified name lookup b) 基于参数的名字查找Argument-dependent name lookupADL c) 显式名字查找Qualified name lookup。 显然如果变量和函数之前不写任何名字空间就是隐式名字查找此时编译器只会从当前命名空间和全局命名空间中查找如果写了名字空间就是显式名字查找编译器会忠实地按照指定的命名空间去查找。 最有意思的是基于参数的名字查找简称ADL也叫Koenig Lookup这种名字查找方式是C大牛Andrew Koenig发明的。具体来说对于一个函数调用如果没有显式地写函数的名字空间编译器会根据函数的参数所在的名字空间里面去查找这个函数。最新的C标准加强了这个规则叫Pure ADL也就是只到参数所在的名字空间里去查找而不到其它名字空间里查找这样的好处是防止找到其它名字空间里具有相同签名的函数导致非常隐蔽的bug。 这就可以理解为什么 operator(std::cout, Hello, Core C!); 可以正常编译了因为函数中有std::cout这个参数所以编译器就会到std名字空间里去查找operator这个函数。 这个特点非常重要否则C中的操作符重载根本无法做到像现在如此简洁。可以想象下如果每次都要去指定操作符的命名空间语法该有多丑仅仅通过ADL就可以看出Andrew Koenig对于C的贡献。 注意 std::cout.operator(std::endl); 这个语句不能省略最前面的std::这是因为C中类本身也形成了一个名字空间就是类名也就是说std::cout.operator这个函数的名字空间是std:ostream而不是std而std::endl在std名字空间中ADL是不会向下去查找嵌套的名字空间的的只会在当前名字空间里去查找。因此最前面的std::不能省略。 三、名字空间污染 对已一开始的例子可能很多人更喜欢写成 #include iostream
using namespace std;int main()
{cout Hello, Core C! endl;
} 这样下面使用任何STL里面的类和算法的时候都不用加上std::前缀了这样是方便但是也是会带来问题的。using namespace std;这个语句将std里面所有的东西类、算法、对象等等都引入到我当前的名字空间中其中很多东西我是暂时使用不到的。如果我自己在当前名字空间中定义了一些和std中同名的东西的话就会导致一些意想不到的问题 #include iostream
using namespace std;class Polluted {
public:Polluted operator(const char*){return *this;}
};
int main()
{Polluted cout;cout Hello, Core C!\n;
} 上面这个程序看上去会输入Hello, Core C!实际上却什么都没做。因为cout已经不是std::cout了而是Polluted的一个对象这个对象恰巧也有一个operator(const char*)函数。因为名字空间查找和普通变量的作用域一样局部名字空间会覆盖全局名字空间和引入的名字空间所以编译器虽然两个cout都找到但根据局部优先于全局的规则选用了main函数中定义的cout而不是std::cout。 这样的危害在于当程序规模比较大的时候这样的问题会变得很隐蔽甚至测试都不一定能测试到但是却会引发非常奇怪的问题给调试带来非常大的麻烦。所以using namespace std;尽量少用最多使用using std::cout这样就只引入std中的cout其它东西都没有引入出问题的概率小些但问题依旧存在所以如果可能的话尽量将std::都加上保证不出问题。 四、using在STL中的使用 2005年C对STL进行了扩充就是所谓的TR1Technical Report 1里面加入了很多实用的库如shard_ptr、function、bind、regular exprestion等等它们都位于std::tr1名字空间下。到了C11TR1中的很多库得到了升级正式成为std名字空间中的一员。但是之前很多代码已经用了std::tr1为了确保已有的代码不被破坏并且不要重复定义相同的东西。STL采取这样的方式将原来std::tr1中的定义移到std中然后在std::tr1中使用using指令将库引入到std::tr1中。如VC中有这样的代码 namespace tr1 { // TR1 additions
using _STD allocate_shared;
using _STD bad_weak_ptr;
using _STD const_pointer_cast;
using _STD dynamic_pointer_cast;
using _STD enable_shared_from_this;
using _STD get_deleter;
using _STD make_shared;
using _STD shared_ptr;
using _STD static_pointer_cast;
using _STD swap;
using _STD weak_ptr;
} // namespace tr1 这样就达到了兼顾新标准和已有代码的目标。 五、名字空间别名 如果我们有一个很深的名字空间比如A::B::C::D::E并且经常会用到这里面的类和函数我们不希望每次都敲这么长的前缀当然也不希望通过using namespace A::B::C::D::E来污染名字空间C提供了名字空间别名的方式来简化使用。比如我们可以通过 namespace ABCDE A::B::C::D::E; 产生名字空间别名ABCDEABCDE::ClassT就等价于A::B::C::D::E::ClassT。 C11中这种方式的别名得到了扩展不仅仅用于名字空间可以用于任何别名 using ABCDE A::B::C::D::E;
using ABCDE_ClassT ABCDE::ClassT; 这样的语法基本上可以替代typedef了而且语法更简洁。 OK关于Name lookup相关的就想到这么多以后有新的了解再跟大家分享 转载于:https://www.cnblogs.com/codemood/p/3203537.html