类与命名空间

来源:互联网 发布:mac 安装hbuilder 编辑:程序博客网 时间:2024/05/17 17:58
 

类与命名空间

分类: C/C++ 48人阅读 评论(0) 收藏 举报
作用域C++类友元函数调用命名空间

类与命名空间

<<C++primer>>12.1.4中讲到:

        在一个给定的源文件中,一个类只能被定义一次。如果在多个文件中定义一个类,那么每个文件中的定义必须是完全相同的。

解析:定义一个类,指的是定义同一个类。如果名字相同,实现不同,那属于不同的类。

由此我们容易想到以下问题:

(1)  此处类的定义指什么?若是普通变量的定义,那是不可以在多个文件中同时定义的,只能在一处定义,多处声明,否则会发生重定义。但为何类的定义却可以,值得斟酌。

(2)  如果实现不同,名字相同会出现什么情况呢?

(3)  用include和不用include又有何不同?

(4)  头文件为.h和不为.h又如何呢?

 

1     实例一

  1. <span style="font-size:18px;">  
  2.   
  3.   
  4. //car1.h  
  5.   
  6. #include<iostream>  
  7.   
  8.    
  9.   
  10. class car{  
  11.   
  12.       
  13.   
  14. public:  
  15.   
  16.      car(){std::cout<<"car1:"<<std::endl;}  
  17.   
  18.      car(int v,intc):value(v),capacity(c)  
  19.   
  20.           {std::cout<<"car1:"<<std::endl;}  
  21.   
  22.           void show()  
  23.   
  24.           {  
  25.   
  26.               std::cout<<"show car1:"<<std::endl;  
  27.   
  28.               std::cout<<"value:"<<value<<"     
  29.   
  30.                         capacity:"<<capacity<<std::endl;  
  31.   
  32.           }  
  33.   
  34.           int GetV(){return value;}  
  35.   
  36.            
  37.   
  38.      private:  
  39.   
  40.           int value;  
  41.   
  42.           int capacity;  
  43.   
  44. };</span>  

//car2.h

  1. <span style="font-size:18px;">  
  2. #include<iostream>  
  3.   
  4. class car{  
  5.   
  6.       
  7.   
  8. public:  
  9.   
  10.      car(){std::cout<<"car1:"<<std::endl;}  
  11.   
  12.      car(int v,intc):value(v),capacity(c)  
  13.   
  14.           {std::cout<<"car1:"<<std::endl;}  
  15.   
  16.           void show()  
  17.   
  18.           {  
  19.   
  20.               std::cout<<"show car1:"<<std::endl;  
  21.   
  22.               std::cout<<"value:"<<value<<"     
  23.   
  24.                         capacity:"<<capacity<<std::endl;  
  25.   
  26.           }  
  27.   
  28.           int GetV(){return value;}  
  29.   
  30.            
  31.   
  32.      private:  
  33.   
  34.           int value;  
  35.   
  36.           int capacity;  
  37.   
  38. };</span>  

//namespace.cpp

 

#include"car1.h"

#include"car2.h"

 

//很奇怪,没有出现重定义,我们可以猜测其实这里说的类定义其实就是类似于结构体声明。多次声明不会出现任何问题。只是c才是变量的实例化。声明类并不分配实实在在的空间。

 

  1. <span style="font-size:18px;">  
  2. int main()  
  3.   
  4. {  
  5.   
  6. car c(100,10);  
  7.   
  8. c.show();  
  9.   
  10. std::cout<<c.GetV()<<std::endl;  
  11.   
  12. return 0;  
  13.   
  14. }</span>  


 

由以上的小测试程序我们基本可以得出:类的定义实际上是一种声明活动,并没有空间的分配。

 

上面头文件中只是类里有内联函数,我们知道内联函数定义在头文件不会引起重定义,那么我们试着实现一个非内联函数:

 

  1. <span style="font-size:18px;">  
  2. int  GetC();//在类内部的声明  
  3.   
  4. int car::GetC()  
  5.   
  6. {  
  7.   
  8.      return  capacity;  
  9.   
  10. }</span>  

结果我们惊奇的发现,结果仍然正常运行。这是否说明类的成员函数也必须在实例化对象后才会实例化呢,这样每一个对象会维护一个成员函数的副本?

为了验证这个问题,我们用一个静态函数来测试

  1. <span style="font-size:18px;">  
  2. static void test();  
  3.   
  4. void car::test()  
  5.   
  6. {  
  7.   
  8.       std::cout<<“for test!“<<std::endl;  
  9.   
  10. }</span>  

结果仍然好使,这说明类的整个定义过程都视为声明过程,不会出现重定义错误。

保险起见,我们在.h中再尝试一个全局函数:

  1. <span style="font-size:18px;">  
  2. void test2()  
  3.   
  4. {  
  5.   
  6.        std::cout<<"outer Func!"<<std::endl;  
  7.   
  8. }</span>  

结果仍然编译运行正常,无语真把我逼急了,声明一个全局变量int  a=10;结果还是正常,但是如果两次包含carx.h(x=1 or 2)就会出现重定义错误。后来尝试把car2.h中的输出部分car1字符串改为car2,发现car2.h覆盖了car1.h。这是什么原因呢,是C++对C头文件兼容的一些额外处理吗?不懂

 

我们再来测试一下C++ 标准头文件格式(直接把后缀名.h去掉即可):结果惊人的发现:

              包括类名在内的所有命名都发生了重定义行为。

这是否说明C++的类定义实际不是一种类声明行为呢,不尽然。我们可以在namespace.cpp文件中尝试写两条extern int  a =10一样的语句,结果同却不会有重定义错误。这样我们可以得出一个初步的猜测:

 

C++中,类的定义 并不是一种声明行为,而是一种定义行为,所以在同一作用域内是不可以定义多次的。

 

然后我们只让namespace.cpp包含一个头文件,然后执行下面命令:g++ namespace.cpp car1 car2

 

报错:car1:file not recognized: File format not recognized

collect2: ld returned 1 exitstatus

说明该命令中的car1  car2无法识别,那么改为.cpp,同时在两个文件的类中都加入一个静态内联函数:

  1. <span style="font-size:18px;">  
  2. static void view()  
  3.   
  4. {  
  5.   
  6.    std::cout<<"inline static!"<<std::endl;  
  7.   
  8. }</span>  

 

然后执行:g++namespace.cpp car1.cpp car2.cpp

此时出现重定义的函数只有:

GetC(),test()----都在类外部实现

test2()-----全局函数

 

由此可得出本文最开始列出的那句书上的话具有一定的局限性,你可以在不同的文件中定义同一个类,但该类决不可以有类外实现的函数,否则会出现重定义。

 

推荐:C++中类的定义和实现须放在不同的文件中,类体应该放在头文件中。但外部实现的成员函数应该放在对应的.cpp中,这是最佳实践,避免不必要的错误。

 

这里似乎有一个困惑的地方,那就是既然类的定义并非声明动作,那么为何在不同文件中定义链接时不会出现重定义呢,而它在外部实现的成员函数却出现重定义呢?

 

解释:

(1)   C++中类的代码区属于文件作用域,类似于全局static数据。

(2)   C++中类外实现的函数属于全局作用域,类似全局的非static成员,具有external属性。

(3)  再次强调,类的定义是一种定义行为。

在不同源文件中定义同一个类,但实质上这两个类没有内在的联系,改变其中一个不会影响另外一个,两个类只不过是恰好特征完全一样罢了,就好比牛顿和莱布尼茨尽管都发明了微积分,但它们谁了没抄谁的,谁也不受水的约束。所以这样的设计类的方法是糟糕的,我们并不提倡。

 

其实上述重定义问题,在C++中是完全有方法避免的,那就是命名空间,如下代码所示:

//car1

  1. <span style="font-size:18px;">  
  2. #include<iostream>  
  3.   
  4. namespace nsp{  
  5.   
  6. class car{  
  7.   
  8.     public:  
  9.   
  10.          car(){std::cout<<"car1:"<<std::endl;}  
  11.   
  12.          car(int v,int c):value(v),capacity(c){std::cout<<"car1:"<<std::endl;}  
  13.   
  14.          void show()  
  15.   
  16.          {  
  17.   
  18.              std::cout<<"show car1:"<<std::endl;  
  19.   
  20.              std::cout<<"value:"<<value<<" capacity:"<<capacity<<std::endl;  
  21.   
  22.          }  
  23.   
  24.          int GetV(){return value;}  
  25.   
  26.          int GetC();  
  27.   
  28.          static void test();  
  29.   
  30.          static void view()  
  31.   
  32.          {  
  33.   
  34.              std::cout<<"inline static!"<<std::endl;  
  35.   
  36.          }  
  37.   
  38.          friend void brother(car &c)  
  39.   
  40.          {  
  41.   
  42.              std::cout<<"inner brother!"<<std::endl;  
  43.   
  44.          }  
  45.   
  46.          friend void sister();  
  47.   
  48.     private:  
  49.   
  50.          int value;  
  51.   
  52.          int capacity;  
  53.   
  54. };  
  55.   
  56. void sister()  
  57.   
  58. {  
  59.   
  60.     std::cout<<"outer sister!"<<std::endl;  
  61.   
  62. }  
  63.   
  64. int car::GetC()  
  65.   
  66. {  
  67.   
  68.     return capacity;  
  69.   
  70. }  
  71.   
  72.    
  73. void car::test()  
  74.   
  75. {  
  76.   
  77.     std::cout<<"for test!"<<std::endl;  
  78.   
  79. }  
  80.   
  81.    
  82.   
  83. void test2()  
  84.   
  85. {  
  86.   
  87.     std::cout<<"outer Func!"<<std::endl;  
  88.   
  89. }  
  90.   
  91.   
  92. }</span>  

//car2

  1. <span style="font-size:18px;">  
  2. #include<iostream>  
  3.   
  4. namespace liao{  
  5.   
  6. class car{  
  7.   
  8.      
  9.     public:  
  10.   
  11.          car(){std::cout<<"car2:"<<std::endl;}  
  12.   
  13.          car(int v,int c):value(v),capacity(c){std::cout<<"car2:"<<std::endl;}  
  14.   
  15.          void show()  
  16.   
  17.          {  
  18.   
  19.              std::cout<<"show car2:"<<std::endl;  
  20.   
  21.              std::cout<<"value:"<<value<<" capacity:"<<capacity<<std::endl;  
  22.   
  23.          }  
  24.   
  25.          int GetV(){return value;}  
  26.   
  27.          int GetC();  
  28.   
  29.          static void test();  
  30.   
  31.          static void view()  
  32.   
  33.          {  
  34.   
  35.              std::cout<<"inline static!"<<std::endl;  
  36.   
  37.          }  
  38.   
  39.          friend void brother(car& c)  
  40.   
  41.          {  
  42.   
  43.              std::cout<<"inner brother!"<<std::endl;  
  44.   
  45.          }  
  46.   
  47.          friend void sister();  
  48.   
  49.        
  50.     private:  
  51.   
  52.          int value;  
  53.   
  54.          int capacity;  
  55.   
  56. };  
  57.   
  58.    
  59.   
  60. void sister()  
  61.   
  62. {  
  63.   
  64.     std::cout<<"outer sister!"<<std::endl;  
  65.   
  66. }  
  67.   
  68.    
  69. int car::GetC()  
  70.   
  71. {  
  72.   
  73.     return capacity;  
  74.   
  75. }  
  76.   
  77.    
  78. void car::test()  
  79.   
  80. {  
  81.   
  82.     std::cout<<"for test!"<<std::endl;  
  83.   
  84. }  
  85.   
  86.   
  87. void test2()  
  88.   
  89. {  
  90.   
  91.     std::cout<<"outer Func!"<<std::endl;  
  92.   
  93. }  
  94.   
  95. }</span>  
  1. <span style="font-size:18px;">  
  2. //namespace.cpp  
  3.   
  4. #include"car1"  
  5.   
  6. #include"car2"  
  7.   
  8. using namespace std;  
  9.   
  10. class A{  
  11.   
  12. public:  
  13.   
  14.     A(int i):a(i){}  
  15.   
  16.      
  17.   
  18.     friend void f(){}  //can’t call(无法调用)  
  19.   
  20.     friend void f(A& obj)  
  21.   
  22.     {  
  23.   
  24.          //cout<<obj.a;  
  25.   
  26.     }  
  27.   
  28.     friend ostream& operator<<(ostream& os,A& obj)  
  29.   
  30.     {  
  31.   
  32.          os<<obj.a;  
  33.   
  34.     }  
  35.   
  36. private:  
  37.   
  38.     int a;  
  39.   
  40. };  
  41.   
  42.    
  43.   
  44.    
  45.   
  46. int main()  
  47.   
  48. {  
  49.   
  50.     cout<<"***********car1*************"<<endl;  
  51.   
  52.     nsp::car c(100,10);  
  53.   
  54.     c.show();  
  55.   
  56.     std::cout<<c.GetV()<<std::endl;  
  57.   
  58.     std::cout<<c.GetC()<<std::endl;  
  59.   
  60.     nsp::car::test();  
  61.   
  62.     nsp::car::view();  
  63.   
  64.     brother(c);  
  65.   
  66.     //nsp::brother(c);  
  67.   
  68.     nsp::sister();  
  69.   
  70.     nsp::test2();  
  71.   
  72.      
  73.   
  74.      
  75.   
  76.     cout<<"***********car2*************"<<endl;  
  77.   
  78.     liao::car b(100,10);  
  79.   
  80.     b.show();  
  81.   
  82.     std::cout<<b.GetV()<<std::endl;  
  83.   
  84.     std::cout<<b.GetC()<<std::endl;  
  85.   
  86.     liao::car::test();  
  87.   
  88.     liao::car::view();  
  89.   
  90.     brother(b);  
  91.   
  92.     //liao::brother(b);  
  93.   
  94.     liao::sister();  
  95.   
  96.     liao::test2();  
  97.   
  98.      
  99.   
  100.     cout<<"***********test for friend Func*************"<<endl;  
  101.   
  102.     A a(3);  
  103.   
  104.     cout<<a<<endl;  
  105.   
  106.     //f();  
  107.   
  108.     f(a);  
  109.   
  110.     return 0;  
  111.   
  112. }</span>  

输出:

***********car1*************

car1:

show car1:

value:100 capacity:10

100

10

for test!

inline static!

inner brother!

outer sister!

outer Func!

***********car2*************

car2:

show car2:

value:100 capacity:10

100

10

for test!

inline static!

inner brother!

outer sister!

outer Func!

***********test for friend Func*************

3

    通过两个命名空间我们就可以区分两个同名的类,把它们的作用域局限在某个空间中,这样就可以防止重定义的行为。道理非常简单不肖多说。

    有一个值得说明的发现,这个也与上面得出的结论有关。那就包括友元函数在内的成员函数如果在类内部定义则只有文件作用域,在外部定义的具有全局作用域。此处的全局作用域是指链接属性,访问时还必须加上空间名及作用域操作符。

 

友元函数在类内部实现有点特殊,如本例中,brother()不直接属于空间nsp和liao而且属于类域构成的空间中,可是友元函数却不是类的一个成员,这就不可以直接用对象调用对象.函数名(),也不能使用类名::函数名()那么怎么调用呢,只能传递一个该类类型的实参,用这个实参来定位函数所在的类域。

但是如果f的形参不是A类型的行不行呢?当然行,但必须具备有效的从A到f形参类型的转换,例如:


  1. <span style="font-size:18px;">#include <iostream>  
  2.   
  3.    
  4.   
  5. class A  
  6.   
  7. {  
  8.   
  9. public:  
  10.   
  11.     friend void f( int a ){ }  
  12.     operator int( ){ return a; }  
  13.   
  14. private:  
  15.   
  16.     int a;  
  17.   
  18. };  
  19.   
  20.    
  21. int main( void )  
  22.   
  23. {  
  24.   
  25.     A a;  
  26.   
  27.     f( a );  
  28.   
  29.     return 0;  
  30.   
  31. }</span>  

 

小结:友元函数的调用(包括显示声明为inline的函数)

(1)  若在外部实现,内部声明,链接属性为external。调用形式为空间名::函数名();

(2)   若在内部声明,内部实现,链接属性为internal。

1)    若参数无本类类型,也不能通过隐式类型转换接受本类类型的参数。那么无法调用

2)   反之,则直接用函数名调用(传递正确参数即可)。

 

关于类与命名空间的问题就到这了,遇到问题再说!

 命名空间:命名空间祥解


C++命名空间<转>

熟练掌握C/C++语言,熟悉Windows开发平台,能熟练运用MFC自主编开发出一些应用程序;
熟练掌握SQL语句,对数据库有很好的认识,能熟练使用SQL Server2000软件;
熟练掌握JAVA语言,熟悉J2ME对手机软件开发一定的基础;
深入理解面向对象的思想,并能熟练应用于具体的程序设计开发中;
熟悉Unix/Linux下C语言的编程以及常用的命令,熟悉汇编语言;
熟悉网络的TCP/IP、UDP等协议,能处理解决电脑系统软件常见的故障;

 

C++ using namespace std 详解
 所谓namespace,是指标识符的各种可见范围。C++标准程序库中的所有标识符都被定义于一个名为std的namespace中。
一 :

<iostream>和<iostream.h>是不一样,前者没有后缀,实际上,在你的编译器include文件夹里面可以看到,二者是两个文件,打开文件就会发现,里面的代码是不一样的。

后缀为.h的头文件c++标准已经明确提出不支持了,早些的实现将标准库功能定义在全局空间里,声明在带.h后缀的头文件里,c++标准为了和C区别开,也为了正确使用命名空间,规定头文件不使用后缀.h。

因此,当使用<iostream.h>时,相当于在c中调用库函数,使用的是全局命名空间,也就是早期的c++实现;当使用<iostream>的时候,该头文件没有定义全局命名空间,必须使用namespacestd;这样才能正确使用cout。

二:

所谓namespace,是指标识符的各种可见范围。

C++标准程序库中的所有标识符都被定义于一个名为std的namespace中。

由于namespace的概念,使用C++标准程序库的任何标识符时,可以有三种选择:

1、直接指定标识符。例如std::ostream而不是ostream。完整语句如下:

std::cout << std::hex<< 3.4<< std::endl;

2、使用using关键字。

using std::cout;
using std::endl;

以上程序可以写成

cout << std::hex<< 3.4<< endl;

3、最方便的就是使用using namespace std;

例如:

#include <iostream>
#include <sstream>
#include <string>
using namespace std;
这样命名空间std内定义的所有标识符都有效(曝光)。就好像它们被声明为全局变量一样。那么以上语句可以如下写:

cout << hex<< 3.4<< endl;

因为标准库非常的庞大,所程序员在选择的类的名称或函数名时就很有可能和标准库中的某个名字相同。所以为了避免这种情况所造成的名字冲突,就把标准库中的一切都被放在名字空间std中。但这又会带来了一个新问题。无数原有的C++代码都依赖于使用了多年的伪标准库中的功能,他们都是在全局空间下的。

        所以就有了<iostream.h>和<iostream>等等这样的头文件,一个是为了兼容以前的C++代码,一个是为了支持新的标准。

命名空间std封装的是标准程序库的名称,标准程序库为了和以前的头文件区别,一般不加".h"

 

 

using namespace std 的用法

摘自

using namespacestd;用的并不少!   
---------------------------------------------------------------

实际上就是告诉编译器,你类型是什么,在哪能找到。

常用的是using namespace std,就是说用C++的标准名字空间。

你也可以引用你自己的名字空间。比如说:

import "C:\\MyTest\\test.tlb"
using namespace CMyTest

就可以引用CMyTest内的各个类型名

看C++ prime
---------------------------------------------------------------

声明该文件使用C++标准库吧!
比如
#include <iostream>
using namespace std;
void main()
{
   cout<< "hello!"<< endl;
}

如果不用using namespace std;这句,那么
std::cout << "hello!"<<endl;
这是名字空间的问题!具体参看有关书籍吧,新版的C++ 书应该都有介绍的!

---------------------------------------------------------------

using 指示符!
这是个名字空间问题,是标准C++引入的新概念!
具体在《C++Primer》第8.6节有详细说明!
---------------------------------------------------------------

因为标准库非常的庞大,所程序员在选择的类的名称或函数名时就很有可能和标准库中的某个名字相同。所以为了避免这种情况所造成的名字冲突,就把标准库中的一切都被放在名字空间std中。但这又会带来了一个新问题。无数原有的C++代码都依赖于使用了多年的伪标准库中的功能,他们都是在全局空间下的。

      所以就有了<iostream.h>和<iostream>等等这样的头文件,一个是为了兼容以前的C++代码,一个是为了支持新的标准。
---------------------------------------------------------------

名字空间,实质上也是为了方便程序在不同平台上正确的运行。
---------------------------------------------------------------

namespace是为了解决C++中的名字冲突而引入的。
什么是名字冲突呢?比如,在文件x.h中有个类MyClass,
在文件y.h中也有个类MyClass,而在文件z.cpp中要同时
引用x.h和y.h文件。显然,按通常的方法是行不能的,
那怎么办呢?引入namespace即可。例如:
      在x.h中的内容为
// x.h
namespace MyNamespace1
{
   class MyClass
   {
   public:
      void f();
   private:
      int m;
   }
};

      在y.h中的内容为
// y.h
namespace MyNamespace2
{
   class MyClass
   {
   public:
      void f();
   private:
      int m;
   }
};

   然后在z.cpp中引入x.h和y.h
// z.cpp
#include"x.h"   
#include"y.h"   

void z::f()
{
   //声明一个文件x.h中类MyClass的实例x
   MyNamespace1::MyClass x;
    //声明一个文件x.h中类MyClass的实例x
   MyNamespace2::MyClass y;

   //调用文件x.h中的函数f
   x.f();
   //调用文件y.h中的函数f
   y.f();
}
      名字空间实质上是一个作用域。
      通过上面的一个实例应该知道名字空间的作用了吧

尽量不要使用using namespace std;VC++2005使用有感
Posted on 2007-11-06 20:28 Samson小天 阅读(1163) 评论(6) 编辑 收藏 网摘 所属分类:C++/C++.net
今天用了VISUAL C++写了个小程序(VS2005),很简单很简单的,但是就是编译不通过
出现一个奇怪的问题:错误 1 error C2668: “max”: 对重载函数的调用不明确

最初代码如下

#include <iostream>
using namespace std;

template <typename T>
T max (T a,T b)
{
return ((a>b)?a:b);
}
void main()
{
double x,y;
cin>>x>>y;
cout<<"Max number is"<<(max(x,y))<<endl;
cin>>x;
}

   我将这段代码放到VC++ 6.0下竟然通过了,程序运行也正常。这让我百思不得其解。后来终于弄明白了!
   其实在std命名空间下还有一个MAX函数,而且实现的功能也是一样的……我昏。利用转到定义功能可以看到微软是怎么写MAX函数的。这里为了不被鄙视就不贴微软的代码了。
   明白了为什么出现这个错误我们就改写代码如下:
#include <iostream>
using std::cin;
using std::cout;
using std::endl;

template <typename T>
T max (T a,T b)
{
return ((a>b)?a:b);
}
int main()
{
double x,y;
cin>>x>>y;
cout<<"Max number is"<<(max(x,y))<<endl;
cin>>x;
}
   这是我比较推荐的做法,因为C++ PRIMER, EFFECTIVE C++上都是用这种方式的,但是谭浩强的书上都是一句usingnamespace std;就搞定,我觉得蛮简洁的就一直用了,没想到带来那么多的问题,以前在友元函数上还碰到莫名的错误呢。
   其实还有两个简单的解决方案,那就是把自己定义的函数改成其他的名字,或者直接用微软提供的函数。相信微软提供的效率绝对不会比我们写的低~
   好了,就写到这了。希望大家养成良好的编程习惯,^-^

很多C++程序员还在使用而不是用更新的标准的库。
这两者都有什么不同呢?首先,5年前我们就开始反对把.h符号继续用在标准的头
文件中。继续使用过时的规则可不是个好的方法。从功能性的角度来讲,
<iostream>包含了一系列模板化的I/O类,相反地<iostream.h>只仅仅是支持字符
流。另外,输入输出流的C++标准规范接口在一些微妙的细节上都已改进,因此,
<iostream>和<iostream.h>在接口和执行上都是不同的。最后,<iostream>的各组
成都是以STL的形式声明的,然而<iostream.h>的各组成都是声明成全局型的。

因为这些实质上的不同,你不能在一个程序中混淆使用这两个库。做为一种习
惯,在新的代码中一般使用<iostream>,但如果你处理的是过去编写的代码,为了
继承可以用继续用<iostream.h>旧保持代码的一致性。

 

///////////////////

<iostream>表示你使用的是标注命名空间,也就是在程序开始应该有这么一句话
using namespace std ;
这是遵循c++标准的
<iostream.h>
则没有遵循c++标准
////////////////
<string.h>是旧的C头文件,对应的是基于char*的字符串处理函数;
<string>是包装了std的C++头文件,对应的是新的strng类;
<cstring>是对应旧的C头文件的std版本。


在C++语言编写的程序中,变量和函数等的作用范围是有一定限制的。比如,在函数体中定义的一个临时变量就不可以在函数体外使用。为了解决变量和函数等的作用范围,在C++语言中引入了名空间的概念,并增加了关键字namespace和using  
         在一个名空间中可以定义一组变量和函数,这些变量和函数的作用范围一致,可以将这些变量和函数称为这个名空间的成员。  
         通过名空间,可以在同一个文件中使用相同的变量名或函数名,只要它们属于不同的名空间。另外,名空间可以使得代码操作具有相同名字但属于不同库的变量。而且,名空间也可以提高C语言与C++语言的兼容性。  
   
下面通过例程说明关键字namespace的用法。  
#include  <conio.h>  
#include  <iostream.h>  
namespace  car  //  名空间的定义  
 
     int  model;  
     int  length;  
     int  width;  
 
   
namespace  plane  
 
     int  model;  
     namespace  size  //  名空间的嵌套  
      
         int  length;  
         int  width;  
      
 
   
namespace  car  //  添加名空间的成员  
 
     char   name;  
 
   
namespace  c=car;  //  定义名空间的别名  
int  Time;  //  外部变量属于全局名空间  
   
void  main()  
 
     car::length=3;  
     //  下面一句错误,故屏蔽掉  
     //  width=2;  //  对于非全局变量和当前有效临时变量应该指定名空间  
     plane::size::length=70;  
     cout<<"the  length  of  plane  is  "<<plane::size::length<<"m."<<endl;  
     cout<<"the  length  of  car  is  "<<car::length<<"m."<<endl;  
     //  使用名空间的别名  
     cout<<"the  length  of   is  "<<c::length<<"m."<<endl;  
     int  Time=1996;  //  临时变量,应区别于全局变量  
     ::Time=1997;  
     cout<<"Temp  Time  is  "<<Time<<endl;  
     cout<<"Outer  Time  is  "<<::Time<<endl;  
     //  使用关键字using  
     using  namespace  plane;  
     model=202;  
     size::length=93;  
     cout<<model<<endl;  
     cout<<size::length<<endl;  
     getch();  
 
   
运行结果:  
the  length  of  plane  is  70m.  
the  length  of  car  is  3m.  
the  length  of   is  3m.  
Temp  Time  is  1996  
Outer  Time  is  1997  
   
说明:  
&#8226;  从上面可以看出,名空间定义了一组变量和函数,它们具有相同的作用范围。对于不同的  
     名空间,可以定义相同的变量名或函数名,在使用的时候,只要在变量名或函数名前区分  
     开不同的名空间就可以了。  
&#8226;  名空间可以被嵌套定义,使用时要逐级对成员用名空间限定符:  :来引用。  
&#8226;  系统默认有一个全局名空间,它包含了所有的外部变量。这个名空间没有名字,引用这个  
     名空间里的变量时要使用名空间限定符:  :,前面没有名字。在不使用名空间的情况下,我  
     们知道,不可以在不同文件中定义相同名字的外部变量,这是因为它们属于同一个全局名  
     空间,名字不可以重复。  
&#8226;  可以给名空间取一个别名。一般别名是一个比较短的名字,来简化编程。  
&#8226;  在原有定义好的名空间的基础上,随时可以往里增加成员。  
   
<<using>>  
在前面的例程中可以看到,为了使用时的方便,又引入了关键字using。利用using声明可以在引用名空间成员时不必使用名空间限定符::。此外,关键字namespace和using的使用,对函数重载有一定的影响。  
   
下面通过例程进行具体说明。  
#include  <conio.h>  
#include  <iostream.h>  
namespace  car  //  名空间的定义  
 
     void  ShowLength(double  len)  //  参数类型为d       
      
         cout<<"in  car  namespace:  "<<len<<endl;  
      
 
   
namespace  plane  //  名空间的定义  
 
     void  ShowLength(int  len)  //  参数类型为i    
        
         cout<<"in  plane  namespace:  "<<len<<endl;  
      
 
   
void  main()  
 
     using  namespace  car;  
     ShowLength(3);  
     ShowLength(3.8);  
     using  namespace  plane;  
     ShowLength(93);  
     ShowLength(93.75);  
     getch();  
 
   
运行结果:  
in  car  namespace:   
in  car  namespace:  3.8  
in  plane  namespace:  93  
in  car  namespace:  93.75  
   
说明:  
         如果没有名空间的干扰,函数重载时选择规则将是非常简单。只要实参是double类型,则调用的是前面的函数;如果实参是int类型,则调用后面的函数。但是由于名空间的参与,就出现了上面的运行结果。所以在编程的时候一定要注意名空间对函数重载的影响。  
   
   
         应注意:调用函数时,如果实参和形参的数据类型实在没有办法完全匹配,可能会对实参进行适当的数据类型转换。比如,将char类型转换为int类型,或进一步将int类型转换为double类型。这种是将数据类型从简单往复杂转换,一般不会丢失信息。另外一种转换是反过来,将double类型转换为int类型,或进一步将int类型转换为char类型。这种是将数据类型从复杂往简单转换,可能会丢失部分信息。在调用函数的时候,不同的情况下,C++对上述两种转换的优先级是不同的。当引入了名空间后,则参与了上述优先级顺序的分配。

 

全局空间最大的问题在于它本身仅有一个。在大的软件项目中,经常会有不少人把他们定义的名字都放在这个单一的空间中,从而不可避免地导致名字冲突。例如,假设library1.h定义了一些常量,其中包括:

const double lib_version = 1.204;
类似的,library2.h也定义了:
const int lib_version = 3;

 如果某个程序想同时包含library1.h和library2.h就会有问题。作为程序员,尽力使自己的程序库不给别人带来这些问题。例如,可预先想一些不大可能造成冲突的某种前缀,加在每个全局符号前。当然得承认,这样组合起来的标识符看起来不是那么令人舒服。

  另一个比较好的方法是使用c++namespace。namespace本质上和使用前缀的方法一样,只不过避免了别人总是看到前缀而已。所以,不要这么做:
const double sdmbook_version =2.0;     // 在这个程序库中, 每个符号以"sdm"开头
                                        // class sdmhandle { ... };
sdmhandle&sdmgethandle();            // 为什么函数要这样声明? 
而要这么做:
namespace sdm {
  const double book_version = 2.0;
  class handle { ... };
  handle& gethandle();
}

 用户于是可以通过三种方法来访问这一名字空间里的符号:将名字空间中的所有符号全部引入到某一用户空间;将部分符号引入到某一用户空间;或通过修饰符显式地一次性使用某个符号:

void f1()
{
  using namespacesdm;          // 使得sdm中的所有符号不用加修饰符就可以使用
  cout <<book_version;         // 解释为sdm::book_version
  ...
  handle h =gethandle();       // handle解释为sdm::handle,
                                // gethandle解释为sdm::gethandle
 ...                           
}
void f2()
{
  usingsdm::book_version;       // 使得仅book_version不用加修饰符就可以使用
  cout <<book_version;          // 解释为sdm::book_version
  ...
  handle h =gethandle();        // 错误! handle和gethandle
                                 // 都没有引入到本空间
 ...                            
}
void f3()
{
  cout <<sdm::book_version;     // 使得book_version在本语句有效
 ...                           
  double d =book_version;       // 错误! book_version不在本空间
  handle h =gethandle();        // 错误! handle和gethandle都没有引入到本空间
 ...                           

}

 有些名字空间没有名字。这种没命名的名字空间一般用于限制名字空间内部元素的可见性。
  名字空间带来的最大的好处之一在于:潜在的二义不会造成错误。所以,从多个不同的名字空间引入同一个符号名不会造成冲突(假如确实真的从不使用这个符号的话)。例如,除了名字空间sdm外,假如还要用到下面这个名字空间:
namespace acmewindowsystem {
  ...
  typedef int handle;
  ...
}

 只要不引用符号handle,使用sdm和acmewindowsystem时就不会有冲突。假如真的要引用,可以明确地指明是哪个名字空间的handle:
void f()
{
  using namespacesdm;                // 引入sdm里的所有符号
  using namespaceacmewindowsystem;   // 引入acme里的所有符号
 ...                                 // 自由地引用sdm和acme里除handle之外的其它符号
  handleh;                           // 错误! 哪个handle?
  sdm::handleh1;                     // 正确, 没有二义
  acmewindowsystem::handleh2;        // 也没有二义
  ...
}

 假如用常规的基于头文件的方法来做,只是简单地包含sdm.h和acme.h,这样的话,由于handle有多个定义,编译将不能通过。

 名字空间的概念加入到c++标准的时间相对较晚,所以有些人会认为它不太重要,可有可无。但这种想法是错误的,因为c++标准库里几乎所有的东西都存在于名字空间std之中。它有一种直接的影响方式:c++提供了那些没有扩展名的头文件,如<iostream>,<string>等。

 由于名字空间的概念引入的时间相对较晚,有些编译器可能不支持。就算是这样,那也没理由污染全局名字空间,因为可以用struct来近似实现namespace。可以这样做:先创建一个结构用以保存全局符号名,然后将这些全局符号名作为静态成员放入结构中:

// 用于模拟名字空间的一个结构的定义
struct sdm {
  static const double book_version;
  class handle { ... };
  static handle&gethandle();
};
const double sdm::book_version =2.0;     // 静态成员的定义

现在,如果有人想访问这些全局符号名,只用简单地在它们前面加上结构名作为前缀:
void f()
{
  cout <<sdm::book_version;
  ...
  sdm::handle h = sdm::gethandle();
  ...
}

 如果全局范围内实际上没有名字冲突,用户就会觉得加修饰符麻烦而多余。幸运的是,还是有办法来让用户选择使用它们或忽略它们。

 对于类型名,可以用类型定义(typedef)来显式地去掉空间引用。例如,假设结构s(模拟的名字空间)内有个类型名t,可以这样用typedef来使得t成为s::t的同义词:
typedef sdm::handle handle;

 对于结构中的每个(静态)对象x,可以提供一个(全局)引用x,并初始化为s::x:
const double& book_version = sdm::book_version;

 处理函数的方法和处理对象一样,但要注意,即使定义函数的引用是合法的,但代码的维护者会更喜欢使用函数指针:
sdm::handle& (* const gethandle)()=     // gethandle是指向sdm::gethandle
 sdm::gethandle;                        // 的const 指针
 
 注意gethandle是一个常指针。因为当然不想让用户将它指向别的什么东西,而不是sdm::gethandle。
如果真想知道怎么定义一个函数的引用,看看下面:
sdm::handle& (&gethandle)()=     // gethandle是指向
 sdm::gethandle;                 // sdm::gethandle的引用

 除了初始化的方式外,函数的引用和函数的常指针在行为上完全相同,只是函数指针更易于理解。

 有了上面的类型定义和引用,那些不会遭遇全局名字冲突的用户就会使用没有修饰符的类型和对象名;相反,那些有全局名字冲突的用户就会忽略类型和引用的定义,代之以带修饰符的符号名。还要注意的是,不是所有用户都想使用这种简写名,所以要把类型定义和引用放在一个单独的头文件中,不要把它和(模拟namespace的)结构的定义混在一起。

 struct是namespace的很好的近似,但实际上还是相差很远。它在很多方面很欠缺,其中很明显的一点是对运算符的处理。如果运算符被定义为结构的静态成员,它就只能通过函数调用来使用,而不能象常规的运算符所设计的那样,可以通过自然的语法来使用:
// 定义一个模拟名字空间的结构,结构内部包含widgets的类型
// 和函数。widgets对象支持operator+进行加法运算
struct widgets {
  class widget { ... };

   static const widgetoperator+(const widget& lhs,constwidget& rhs);
  ...
};
// 为上面所述的widge和operator+ 建立全局(无修饰符的)名称
typedef widgets::widget widget;

const widget (* const operator+)(constwidget&,       // 错误!
                                constwidget&);      // operator+不能是指针名
 
widget w1, w2, sum;
sum = w1 +w2;                          // 错误! 本空间没有声明
                                        // 参数为widgets 的operator+
sum = widgets::operator+(w1,w2);       // 合法, 但不是"自然"的语法

正因为这些限制,所以一旦编译器支持,就要尽早使用真正的名字空间。


原创粉丝点击