Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1816922
  • 博文数量: 496
  • 博客积分: 12043
  • 博客等级: 上将
  • 技术积分: 4778
  • 用 户 组: 普通用户
  • 注册时间: 2010-11-27 14:26
文章分类

全部博文(496)

文章存档

2014年(8)

2013年(4)

2012年(181)

2011年(303)

2010年(3)

分类: C/C++

2011-06-07 08:54:46

8章 域和生命周期

  C++中的一个名字可以指向不同的实体,只要编译器能够根据上下文区分出该名字的不同含义。用来区分名字含义的一般上下文就是域。C++支持三种形式的域:局部域,名字空间域,类域。

  • 局部域:函数块、语句块都是一个独立的局部域。
  • 名字空间域:程序的最外层名字空间域为全局域或全局名字空间域。也可以自定义名字空间域,它们被嵌套在全局域内。
  • 类域:每个类定义都收入一个类域。

名字解析:是把表达式中的一个名字与某一个声明相关联的过程,也是给出这个名字意义的过程。

  局部域内的名字解析是这样进行的:首先,查找使用该名字的域,如果找到一个声明,则该名字被解析。如果没有找到,则查找包含该域的域,这个过程会一直继续下去,直到找到一个声明或已经查找完整个全局域。如果后一种情况发生,即没有找到该名字的声明,这个名字的用法将被标记为错误。

  因为在名字解析期间查找域的顺序由内向外,所以在外围域中声明被嵌套域中的同名声明所隐藏

全局对象和函数

  函数声明:指定了函数的名字以及函数的返回类型和参数表。

  函数定义:为函数提供函数体,函数的实现。

  在全局域定义对象时,默认情况下如果未显示指定初始值,则存储区被初始化为0

  在一个程序中,一个全局对象只能定义一次(否则为多次定义,编译错误)。在多文件构成的程序中,存在如下问题,如果头文件中定义对象(int j;),默认j被声明定义并初始化为0。这样,当不同的文件include该头文件时,j就会被多次定义,产生编译错误!因此,需要有一种方法声明一个对象,而不定义它!

  extern就是这样功能的关键字,extern int j;这样就声明了j,而没做定义。可以在include该头文件的cpp中,做int j = 9;这样的对象定义。

  如果extern声明时,显示做了初始化。后续的对该对象的定义都标记为错误。如:

  1. extern int j = 0;
  2. int j;//重复定义错误

注意:重复定义是编译错误,重复声明(声明的意思是有一个全局变量它的名字是j)是允许的。如:extern int j; extern int j;


不同文件之前声明的匹配

  有如下情况:

  1. //--declar.c中—
  2. int func(unsigned char m) {……}//定义并实现一个uchar参数的函数

  3. //--use.c中—
  4. extern int func (char);//声明参数为char类型的func函数

  以上在use.c函数中可能找不到func函数定义,或者编译成功函数执行时失败。

  C++有一种机制,它可以把函数参数的类型和数目编码在函数名中,该机制为类型安全链接。这样,该机制捕捉到参数不匹配的情况。如将use.c中声明的函数标记为未定义函数。

  但是对于不同文件,同一对象或函数声明的其它类型不匹配(如返回类型)的情况,编译和链接时可能不被捕捉到。所以一般使用头文件机制,避免此类问题。

关于头文件

  头文件为extern对象声明,函数声明以及inline函数定义提供了一个集中的位置,这被称做声明的局部化。如果一个文件要使用或定义一个对象或函数时,它必须包含相应的头文件。

头文件提供了两个安全保证:

  第一:保证所有文件都包含同一个全局对象或函数的同一份声明。

  第二:如果需要修改声明,则只需要改变一个头文件。从而不至于再发生只修改了某一个特殊的文件中的声明。

  注意:头文件中不应该含有非inline函数或对象的定义!如头文件中出现:

  1. extern int ival = 0;
  2. double final_rate;
  3. extern void fun() {}

  因为以上定义出现在头文件中,当头文件被多次包含时,它们都会被多次定义,有重复定义的编译错误。

  其中,常量和inline函数是可以的。因为他们都是特殊的定义,可以被定义多次。

  程序编译期间,可能的情况下,符号常量会代替其名字出现。这个替换过程为常量折叠

  const  int value = 0;的定义,当value被用在一个文件中时,编译器用0代替名字value。为了使编译器能够用一个常量值替换它的名字,该常量的定义必须在它被使用的文件中可见。所以符号常量可以在同一程序的不同文件中被定义多次。

  关于inline函数,由于一些函数不适合内联,这时编译器在调用点不会内联该函数,而是为该函数生成一个定义,放到可执行文件中。如果在多个文件中生成同一函数的定义,那么会产生一个不必要的,过大的可执行文件,并且显然是不必要的。所以,应该慎重使用inline函数。

自动对象

  自动对象的存储分配发生在定义它的函数被调用时,分配给它的存储区来自于程序运行栈,它是函数活动记录的一部分。未初始化的自动对象的存储区值是上次该区域被使用的结果,是一个随机值,称为未指定的函数结束时,它的活动记录被从运行栈中弹出,自动对象的存储区被释放!

  当一个自动变量的地址被存储在一个生命期长于它的指针时,该指针被称为空悬指针。一定要注意!避免此种情况发生!

寄存器自动对象

  register声明!编译器把此种对象装载到机器的寄存器中。因为寄存器存取速度比内存快的多,当某一变量被频繁使用时,用寄存器变量可以提高函数执行效率。

  register对编译器来说只是一个建议,编译器可以忽略该建议,使用寄存器分配算法找出最合适的时候入到机器寄存器中。

静态局部变量

  声明为static,程序运行期间一直存在,但其可视性在局部域(语句块函数块)内。在编译时初始化,并且只初始化一次。如不显示初始,则自动赋值为0。如下例子:

  1. int f()
  2. {
  3.   static int j = 3; 
  4.    j++; 
  5.   return j; 
  6. }

  7. void output() 
  8. { 
  9.   for(int a= 0; a < 3; a++)
  10.   printf(%d,”, f()); 
  11. }

  12. 输出结果为:4,5,6,

动态分配的对象

  系统为每个程序提供了一个在程序执行时可用的内存池,它被称为程序的空闲存储区(freestore)(heap)。动态分配对象允许程序员完全控制它的分配和释放,它被分配在堆上。


三种形式的new表达式:

  支持单个对象的动态分配,支持数组的动态分配,定位new表达式

  单个对象的动态分配与释放new表达式由new关键字和其后面的类型指示符构成,如:new int;

  • 堆的一个特点是,其中分配的对象没有名字,new表达式没有返回实际分配的对象,而是返回指向该对象的指针。在运行时刻从堆中分配内存,所以叫动态分配
  • 第二个特点:分配的内存是未初始化的。是上次运行后的内存值。//当然可通过调用构造函数做初始化

  new表达式的操作顺序为:从堆上分配对象,然后用括号中的值初始化该对象。调用new()库操作符分配内存,内存不足,不够分配对象内存,会抛出异常。

  delete操作调用库操作符delete(),释放堆上分配的内存。如果指针为0,则C++会保证不调用操作符delete(),没有必要做非0测试。(C++本身有机制会做该测试)

注意delete操作符只能释放new在堆上分配的内存,其它delete操作是非法的(0指针的操作除外,虽不报错,也是不合理的)

动态内存分配相关的错误:

  1. 应用delete表达式失败,内存无法返回空闲存储区,内存泄漏。
  2. 对同一内存做两次delete操作。如:多个指针指向同一堆内存,再释放每个指针。
  3. 对象被释放后,操作其指针。

数组的动态分配与释放

  动态分配数组第一维不必是常量值,可以是任意表达式。

  delete[]  方式来释放。

定位new表达式

  允许程序员将对象创建在已经分配好的内存中。形式为:new (指针名类型

例如:

  1. char* buf = new char[5];
  2. int* pi = new (buf) int(0);//意为:在buf上创建一个int对象。buf持有内存,只需释放buf即可。

名字空间的定义

  缺省情况下,在全局域中声明的每个对象,函数,类型等都引入一个全局实体,在全局域引入的全局实体必须有唯一的名字,否则产生名字冲突问题,即全局名字空间污染

  名字空间就是用来解决这种问题的有效方案!

  用户声明的名字空间代表一个不同的名字空间域,它可以包含其它嵌套的名字空间定义。一个域中定义的名字是唯一的。

  名字空间内成员的名字会自动被空间名限定修饰,如webcore::string,  webcore::submitL()


  为了使用更方便,C++名字空间别名using声明using指示符等机制

用法:

  1. namespace myspace//需在域中唯一
  2. {
  3. .
  4. }

  名字空间的定义不一定是连续的,可以

  1. namespace myspace { part1 }
  2. namespace myspace { part2 }

  在不同位置共同定义一个完整的名字空间,并可跨跃不同文件,所以名字空间的定义是可累积的。

  这样可以分开编写代码的接口声明和实现部分,使代码结构清晰:如:

  1. namespace myspace 
  2. { 
  3.   void fun();
  4. }//声明部分

  5. namespace myspace 
  6. {
  7. void fun()
  8.   {
  9.     int a = 0;
  10.   }
  11. }//实现部分


域操作符 ::

  使用名字空间内的成员myclass,而不用域名限定修饰是错误的!因为编译器不知道成员名myclass指向的是哪个声明。

  名字空间内的成员声明被隐蔽在其名字空间中,如不进行域名限定,编译器将在当前域中查找该名字的声明。如:

  1. //primer.h
  2. namespace myspace 
  3. { 
  4.   class myclass {. };
  5. }

  6. //use.cpp
  7. #include “primer.h”
  8. class myclass {};//全局中声明的myclass和myspace域中同名类不冲突。
  9. void fun( myclass& m );//此时myclass没有加域名限定,其意为全局域中声明的myclass

  域操作符可以指向全局名字空间中的名字,用法如:   ::myclass//显示使用全局myclass,可用于全局域的变量被局部域中同名变量隐藏时,访问全局域中的变量。

嵌套名字空间

  1. namespace cplusplus_primer {
  2. // 第一个嵌套域
  3. // 定义了库的 matrix 部分
  4.   namespace MatrixLib 
  5.   {
  6.     class matrix { /* ... */ };
  7.     const double pi = 3.1416;
  8.     matrix operator+ ( const matrix &m1, constmatrix &m2 );
  9.     void inverse( matrix & );
  10.     // ...
  11.   }

  12. // 第二个嵌套域

  13. // 定义了库的 Zoology 部分

  14.   namespace AnimalLib {
  15.     class ZooAnimal { /* ... */ };
  16.     class Bear : public ZooAnimal { /* ... */ };
  17.     class Raccoon : public Bear { /* ... */ };
  18.     // ...
  19.   }

  20. }

  名字空间cplusplus_primer包含两个嵌套的名字空间:MatrixLibAnimalLib调用方法为:  cplusplus_primer::MatrixLib::inverse

  在外围名字空间中声明的实体,被里面的名字空间中的同名声明实体隐藏。

名字空间成员定义

  1. 在名字空间内定义。//可以声明和定义分开
  2. 使用限定符在名字空间定义外,做定义。

如:

  1. cplusplus_primer::MatrixLib::matrix  cplusplus_primer::MatrixLib::operator+ (const matrix& , const matrix&) { …… }

  应该注意:函数名必须要用域名做限定,局部域类型matrix返回值必须做限定,但是参数和函数实现块中matrix类的使用可以不用域名限定。因为operator+的名字被解析后,编译器已经找到其名字空间,可直接用名字空间域中的名字。

  但是注意:只能在包含该名字空间的域内做这种方式的定义。如:可在全局域,cplusplus_primerMatrixLib中定义。并且声明过才能被定义,否则是非法的。

未命名的名字空间

用法如:

  1. //somef.cpp

  2. namespace 
  3. {
  4.   void fun() {.. }
  5. }

这样声明定义的函数,只在somef.cpp中可见。

它同static声明函数有相同的性质,如:

static void  fun() { …. }//也是只在somef.cpp中可见的。


名字空间别名

用法如:

  1. namespace International_Business_Machine {.}
  2. //做一个International_Business_Machine的别名
  3. namespace IBM = International_Business_Machine;

之后使用IBM做域名限定即可。

阅读(423) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~