Tools for Large Programs

namespace

            (如此装X(A<X<C)的标题明显是学C plus plus Primer的,不要喷我)

C++中using的用法

1.    当前文件作为该命名空间的作用域

       using namespace std;

2.    在子类中使用 using 声明引入基类成员名称(参见C++ primer)

(注:在private或者protected继承时,基类成员的访问级别在派生类中受限,一般不用protected继承,详细参考effective c++)

class Base {
public:
    std::size_t size() const { return n; }
protected:
    std::size_t n;
};

class Derived : private Base { . . . };

在这一继承层次中,成员函数 size 在 Base 中为 public,但在 Derived 中为 private。为了使 size 在 Derived 中成为 public,可以在 Derived 的 public
部分增加一个 using 声明。如下这样改变 Derived 的定义,可以使 size 成员能够被用户访问,并使 n 能够被 Derived 的派生类访问:

class Derived : private Base {
public:
    using Base::size;
protected:
    using Base::n;
// ...
};

另外,当子类中的成员函数和基类同名时,子类中重定义的成员函数将隐藏基类中的版本(这个和下面重载和覆盖都不同),即使函数原型不同也是如此。如果基类中成员函数有多个重载版本,派生类可以重定义所继承的 0 个或多个版本,但是通过派生类型只能访问派生类中重定义的那些版本,所以如果派生类想通过自身类型使用所有的重载版本,则派生类必须要么重定义所有重载版本,要么一个也不重定义(因为前面讲过,子类中重定义的成员函数将隐藏基类的版本,即使函数原型不同),如果我们有需求,嫌麻烦想要用基类的所有版本,使用using声明将名字加入基类作用域之后,派生类只需要重定义自己需要添加的函数就可以了。代码如下:

#include <iostream>
using namespace std;

class Base
{
public:
    int menfcn(){cout<<"Base function"<<endl; return 0;}
};
class Derived : Base { public: using Base::menfcn;//using声明只能指定一个名字,不能带形参表 int menfcn(int); }; int main() { Base b; Derived d; b.menfcn(); d.menfcn();//如果去掉Derived类中的using声明,会出现错误:error C2660: 'Derived::menfcn' : function does not take 0 arguments std::cin.ignore(std::cin.gcount()+1);//清空缓冲区 std::cin.get();//暂停程序执行
}

3.    可以在函数里和函数外using语句声明引入该命名空间的成员

std::cout << std::hex << 3.4 << std::endl;
//可以该为下面的程序
using std::cout;
using std::endl;
cout << std::hex << 3.4 << endl;
//上面的叫做using的声明
一个using每次只能引入一个成员。using声明的作用域与常规作用域规则同。函数内,函数外等等。
using namespace这个叫做using的指示,using的声明和using的指示是不同的。
因为using指示会注入另一个命名空间的所有名字,如果程序使用很多库,并且使用using指示使得这些库中的名字可见,那么全局名字空间污染的问题就会重新出现。因此使用using声明是不错的方法。

 4.   嵌套命名空间

namespace cpp_primer{

    namespace QueryLib{
        class Query{/*......*/};
        Query operator+(const Query&, const Query&);
    }
    
    namespace Bookstore{
        class Item_base{/*....*/};
        class Bulk_item::public Item_base{/*......*/};
    }
}

规则:外围命名空间中声明的名字被嵌套命名空间中同一名字的声明所屏蔽。嵌套命名空间内部定义的名字局部于该命名空间,外围命名空间之外的代码只能通过限定名引用嵌套命名空间的名字。因此,当库提供者需要防止库中每个部分的名字与库中其他部分的名字冲突时,嵌套命名空间是一种解决的方法。

5.   匿名命名空间(Anonymous namespace)

要使用好using就要掌握好using的作用域的问题,首先讲匿名命名空间(Anonymous namespace)。

namespce {
    char c;
    int i;
    double d;
}

//编译器在内部会为这个命名空间生成一个唯一的名字,而且还会为这个匿名的命名空间生成一条using指令。所以上面的代码在效果上等同于:
namespace __UNIQUE_NAME_ {
    char c;h
    int i;
    double d;
}
using namespace __UNIQUE_NAME_;
在匿名命名空间中声明的名称也将被编译器转换,与编译器为这个匿名命名空间生成的唯一内部名称(即这里的__UNIQUE_NAME_)绑定在一起。还有一点很重要,就是这些名称具有internal链接属性,这和声明为static的全局名称的链接属性是相同的,即名称的作用域被限制在当前文件中,无法通过在另外的文件中使用extern声明来进行链接。如果不提倡使用全局static声明一个名称拥有internal链接属性,则匿名命名空间可以作为一种更好的达到相同效果的方法。
注意:命名空间都是具有external 连接属性的,只是匿名的命名空间产生的__UNIQUE_NAME__在别的文件中无法得到,这个唯一的名字是不可见的.
C++ 新的标准中提倡使用匿名命名空间,而不推荐使用static,因为static用在不同的地方,涵义不同,容易造成混淆.另外,static不能修饰class

命名空间声明的作用域:

一个个using声明一次只引入一个命名空间成员。using声明引入的名字遵循作用域规则,从using声明点开始,直到包含该using声明的作用域的末尾,命名空间内名字都是可见的。注意:外部作用域定义的同名实体被屏蔽。

换通俗的话说:using声明的作用域与常规的作用域的规则相同。

命名空间的别名:

可以使用命名空间别名,将较短的名字与命名空间的名字关联。如

namespace liuxirui;
namespace lxr = liuxirui;

6.    using声明和uisng指示的区别:

下面的的代码:

namespace lxr
{
    int i,j;
}
void f()
{
    using namespace lxr;//将i,j提升到包含namespace a和f的全局空间中。
    cout<<i*j<<endl;
}

注意如果using声明以using namespace开头,后面接命名空间的名字lxr,然后using namespace将名字lxr放到其声明的作用域,但是using将命名空间成员(i,j)提升到max(包含命名空间的作用域,包含using指示所在的作用域)注意上面代码中的注释。此种提升规则可能会导致命名空间的名字会与外围作用域中定义的其他名字冲突。如果在f外的全局作用域定义int j ;此时的using namespace lxr;将lxr的成员提升到全局作用域,在f内使用j就会出现歧义的情况。这种冲突是允许的(因为我们可以采取下面的方法避免歧义(ambiguity)。但是为了使用j必须指明想使用哪个j。使用::j引用全局作用域的变量,使用lxr::j引用lxr中的变量。因此用这种方法是非常危险的,但是最好用最简单的方法回避,只要你保证使用命名空间内的变量加上lxr::这样当然是最好的。如果出现这种问题,就是coder的问题了。实现的不好。

同样的举一个和上面相同的例子进行对比,代码如下:

namespace lxr
{
    void Func1(int a)
    {
         cout << "Func1(int a)\n" << endl ;
    }
    void Func1(char b)
    {
         cout << "Func1(char b)\n << endl";
    }
};
// 全局域中的重载
void Func1(char *)
{
     cout << "Func1(char *) << endl";
}

void Show()
{
     // 在局部域中使用using 声明, 会隐藏全局的重载函数
     using  lxr::Func1;
     char *p = "hello";
     Func1(p); // 调用失败,因为char* 的重载已经被using声明隐藏了
}

void Show1()
{
     // 在局部域中使用using指示符,不会影响重载的解析,此时有三个重载
     using namespace lxr;
     char *p = "hello";
     Func1(p);
}

7.    函数类型推倒(function type deduction)

std::string s;
getline(std::cin,s);

为什么无需使用std::getline就可以使用该函数?它给出了屏蔽命名空间名字规则的一个重要例外:与类本身定义在同一命名空间的函数,在用类类型对象作为实参时是可见的。当编译器看到getline(std::cin,s)时,它会在当前作用域、包含调用的作用域以及定义cin的类型和string类型的命名空间中查找匹配的函数。(因为std::cin用类类型对象作为实参是明显可见的)

8.    重载和覆盖(overloaded and override)

重载,首先明确一个概念,在同一个作用域的函数才有重载的概念。

特殊的在类的内部,也有重载和覆盖的概念。类的内部的重载的概念与一般的重载的概念相同,类的内部的覆盖的概念是在父类和子类有相同的函数名和参数名,方法体不同,子类的方法覆盖了父类的方法。可以看见覆盖指的是在不同的类中,重载是在相同的类中。

但是下面令你惊讶的事情发生了:

#include <cstdlib>
#include <cstdio>
#include <iostream>
using namespace std;
 
class base
{
 public:
     void test(){cout << "1 " <<endl;}
 };

class derived:public   base
{
public:
using base::test; //就是这句话 void test() {cout << "2 " << endl;}//与基类完全相同 };
int main(){ derived d; d.base::test(); d.test() return 0; }

看到我的d.test()是可以运行通过,现在派生类中的test与基类完全相同了(参数列表也相同了),可是编译竟然没有出错,而运行时显示“2”,表明调用的是派生类的test函数。为什么呢?

我们通过上面的总结知道:using base::test的作用就是把派生类和基类中的test函数当作同一个重载函数集,使得在派生类有test的重载版本的情况下,仍然能够正确地访问基类中的test版本,这个我们在1中已经讨论过了。上面为什么不冲突nm liuxirui | grep test ,test是函数名,会出现在符号表中,nm找到liuxirui可执行文件的中的test的符号表发现符号表的名字是不同的(和类名有关)因此不会冲突,nm是list symbols from object files的linux命令。

9.    实现代码(implement codes)

头文件不要用using声明,指示,会造成污染,别人可能会引用你的头文件,自己的C++文件可以用using声明。

int i = 3; 
namespace lxr
{
int i; };
int main()
{
using namespace lxr;//去除这句就会使用全局变量的i printf("%d",i); return 0; }

上面的代码会造成ambiguous。

所以如果你在头文件lxr_header.h用using的话,会找称你自己namespace中的变量,和别人namespace中的变量有冲突。就相当与文件a.h中用了using namespace lxr;

b.h文件引用a.h,b.h文件中同样声明了i,这样就会起命名冲突,但是这个和类中用using Base::test的那个例子(8.重复与覆盖)是不一样的,一个是命名空间的语法规则一个是类中的作用域的规则,类的函数构造有其特殊性,因为会多一个this的参数。所以这点也是概念上容易混乱的地方。

反正= =你咋写,都是听老外的。。。

posted on 2012-10-11 18:08  刘奚睿  阅读(189)  评论(0)    收藏  举报

导航