C++第九章__将原来的程序分为三部分:头文件、主函数和子函数__存储类型、作用域和链接性__自动存储持续性__静态持续变量&static的两种用法__静态持续性、外部链接性__静态持续...

被刻印的时光 ゝ 提交于 2020-05-06 02:57:37

目录

将原来的程序分为三部分:头文件、主函数和子函数

/*
01)原来的源程序可以分为三部分:
 A 头文件:包含结构声明和使用这写结构的函数的原型
 B 源代码文件:包含与结构有关的函数的代码(主函数)
 C 源代码文件:包含调用与结构相关的代码(子函数)
02)头文件中常包含的内容:
 函数原型(函数声明)、使用#define或const定义的符号常量
 结构声明、类声明、模板声明、内联函数
03)在包含头文件时,我们使用"coordin.h",而不是<coordin.h>。
 如果头文件包含在尖括号内,则c++编译器将在存储标准头文件的主机系统的文件系统中查找;
 如果头文件包含在双引号中,则编译器将首先查找当前的工作目录或源代码目录;如果没有在
 那里找到头文件,则将在标准位置查找,因此在包含自己的头文件时候,应该使用双引号而不是尖括号
*/

存储类型、作用域和链接性

01)自动存储:在函数内部定义的常规变量使用自动存储空间,被称为自动变量,这意味着它们在所属的函数被调用时
 自动生成,在该函数结束时消亡。自动变量通常存储在栈中,这意味着其中变量依次加入到栈中,而在离开栈时,
 将按相反的顺序释放这些变量,这被称为"后进先出(LIFO)",因此在程序执行的过程中,栈将不断的减小或增大。
02)静态存储:是一种在整个程序执行期间都存在的存储方式,使变量称为静态的方式有两种:一种是在函数外满定义
 变量,另一种是在声明变量时使用关键字stctic。
03)动态存储:new是delete提供的存储方式。它们管理一个内存池,这在c++这被称为自由存储空间(free store)或
 堆(heap),该内存池同用于自动变量和自动变量的内存是分开的。

04)作用域: 描述了名称在文件(翻译单元)的多大范围内课件。例如在函数定义的变量可在该函数中使用,但在
 其他函数则不可以;而在函数外面定义的变量则可以在所有函数中使用。
05)链接性: 描述了名称如何在不同单元间共享。链接性为内部的名称只能由一个文件中的函数共享。
 自动变量没有连接性,因为它们不能共享。

自动存储持续性

01)在默认情况下,在函数中声明的函数参数和变量的存储持续性为自动,作用域为局部,没有链接性。也就是说
 如果mian()函数中声明了一个texas的变量,在子函数oil()中也声明了一个texas变量,则创建两个独立的变量,
 只有在定义它们的函数中才可以使用它们。对oil()函数中的texas变量执行的任何操作不会影响main()函数中的
 texas变量,反之亦然。例如如下代码块
 int main()
 {
   int teledeli = 5;
   //定义代码块
   {
     int teledeli = 23;
     teledeli = teledeli+1;
     cout<<"Hello"<<teledeli<<endl; //打印的是24
   }
   teledeli = teledeli+1;
   cout<<"Hello"<<teledeli<<endl; //打印的是6
 }
02)寄存器变量: 关键字register最初是由c语言提出的,它建议编译器使用CPU寄存器来存储自动变量,这旨在
 提高访问变量的速度。
 register int cout_fast; //声明一个寄存器变量

静态持续变量&static的两种用法

01)c++为静态存储持续性变量提供了三种链接性:外部链接性(可在其他文件中访问,用static声明)、

    内部链接性(只能在当前文件中访问,用static声明)和无连接型(只能在当前函数代码块或子函数

    中访问,用static声明)。这三种链接性的变量在整个程序执行期间都存在,

 与自动变量相比,他们的寿命更长。由于静态变量在程序运行期间是不变的,故程序不需要使用特殊的装
 置(如栈)来存储他们,编译器只需要给他们分配固定的内存块即可。另外,如果没有显式的初始化静态变量,编
 译器将把他们设置为0。在默认情况下,静态数组和结构将每个元素或成员的所有位设置为0.
 现举例说明如何创建三种链接类型的变量:
 ...
 int global = 1000; //global具有外部链接性,在程序的其他文件只也可以使用

    //如何在其他cpp文件中使用另外一个cpp文件中的外部变量(即上面的变量global)?使用关键字extern,接下来将会介绍
 //下面一句中的static表示的是内部链接性,变量是静态持续性
 static int one_file = 50; //one_file具有内部链接性,只能在包含上述代码的文件中使用它
 int main()
 {
   ....
 }
 void fun1(int n)
 {
 //此时static表示的是存储持续性
 static int count = 0; //count没有链接性,作用域为局部,这意味着只能在fun1只使用它
 int llama = 0; //与llama不同的是,即使fun1()函数没有被执行,count也留在内存中
 }
02)static的两种用法:
 A 用于局部声明(函数内声明变量),以指出变量时无连接型的静态变量时,static标示的是存储持续性;
 B 用于代码块外的声明时,static表示的是内部链接性,而变量已经是静态持续性了

总结如下表:

  

 静态持续性、外部链接性 

01)变量的定义方法(两种):
 A 定义声明(简称定义):它给变量分配存储空间
 B 引用声明(简称声明):它不给变量分配存储UKon关键,因为它引用已有的变量,
 引用变量使用关键字extern,且不能给变量初始化,否则声明变为定义,导致分配存储空间
 double up; //定义
 extern int blem; //声明,不能初始化
 extern char gr = 'A'; //定义,虽然使用了extern,但是给变量初始化了
02)一方面,在每个使用外部变量(在函数外定义的变量)都必须声明它,
 另一方面:变量只能定义一次(单定义规则)
03)如果在多个cpp文件中使用main.cpp或其他cpp文件中的外部定义,只需在一个cpp文件中包含该变量
 的定义(单定义规则),但在其他使用改变量的其他cpp文件中,都必须使用关键字extern声明它,如:
 //file01.cpp
 extern int cats = 20; //extern可以省略,加上也是可以的
 int dogs = 22;
 int fleas;
 //file02.cpp
 extern int cats; //使用extern之后,在file02.cpp之后就可以使用file01.cpp中的cats变量
 extern int dogs; //使用extern之后,在file02.cpp之后就可以使用file01.cpp中的dogs变量
 //file03.cpp
 extern int cats; //使用extern之后,在file03.cpp之后就可以使用file01.cpp中的cats变量
 extern int dogs; //使用extern之后,在file03.cpp之后就可以使用file01.cpp中的dogs变量
 extern int fleas; //使用extern之后,在file03.cpp之后就可以使用file01.cpp中的fleas变量

 1 //external.cpp
 2 //compile with support.cpp
 3 
 4 #include <iostream>
 5 
 6 using namespace std;
 7 
 8 double warming = 0.3;  //定义外部变量(全局变量),在support.cpp中也可以使用的
 9 
10 void update(double dt);  //声明一个函数,该函数在support.cpp中定义
11 void local();  //声明一个函数,该函数在support.cpp中定义
12 
13 int main()
14 {
15     cout << "Global waiming is " << warming << endl;
16     update(0.1);  //调用support.cpp中定义的函数
17     cout << "Global waiming is " << warming << endl;
18     local();  //调用support.cpp中定义的函数
19     cout << "Global waiming is " << warming << endl;
20 
21     system("pause");
22     return 0;
23 }
external.cpp
 1 //surpport.cpp
 2 //compile with external.cpp
 3 
 4 #include <iostream>
 5 
 6 void update(double dt);  //声明一个函数
 7 void local();  //声明一个函数
 8 
 9 using std::cout;
10 using std::endl;
11 
12 extern double warming;  //声明引用变量,即声明在外部文件(external.cpp)中定义的变量
13 
14 void update(double dt)
15 {
16     extern double warming;  //声明引用变量,即声明在外部文件(external.cpp)中定义的变量,由于在函数外已经使用extern声明了warming,所以此处不写这一句也是可以的
17     warming = warming + dt;  //对在external.cpp中的外部变量进行修改,此处的修改会在external.cpp中保存
18     cout << "Updating global warming to " << warming << endl;
19 }
20 
21 void local()
22 {
23     double warming = 0.8;  //声明一个局部变量
24     cout << "Local warming is " << warming << endl;
25     cout << "But global warming is " << ::warming << endl;  //c++提供了作用域解析运算符::,放在变量前该运算符表示使用全局版本
26 
27 }
surpport.cpp

程序说明:

  01) c++提供了作用域解析运算符::,放在变量前该运算符表示使用全局版本

  02) 另外在external.cpp中定义的外部(全局)变量,如果要在support.cpp中使用,但是必须是在support.cpp中函数外部用extern声明一下

  03)如果在外部定义了一个warming变量,在一个子函数内也定义了一个相同名字的变量warming,那么执行到该子函数的时候,子函数内的

         warming会覆盖掉外部定义的warming变量

在external.cpp文件中执行的结果:

 

 静态持续性、内部链接性 

01)在函数外定义的变量具有外部链接性,可以在不同程序文件之间共享数据
 在函数内定义或者是用关键字static定义的变量具有内部连接性,只能在本cpp文件内共享用该变量
 名称空间提供了另外一种共享数据的方法
02)下面介绍一种错误的做法:
 //file01.cpp
 int errors = 20;
 //file02.cpp
 int errors = 5;//这里将会报错,因为同时在两个cpp文件中同时定义了一个名字相同的变量,是不合法的
03)改进(使用static关键字):
 //file01.cpp
 int errors = 20; //外部(全局)变量errors具有外部链接性
 void main()
 {
 ....
 }
 //file02.cpp
 static int errors = 5; //这样是允许的,因为这个errors变量具有内部连接性
 void support()
 {
 ....
 }
04)在多程序文件中,可以在一个文件(且只能在一个文件)中定义一个外部变量。使用该变量的其他文件使用
 关键字extern声明该变量。
05)可以将作用域为单独某一个cpp文件的变量设置为静态的(使用static声明),就不必担心其名称与外部变量
 发生冲突。即防止出现02)类似的错误。

//该程序演示了c++佮处理链接性为外部和内部的变量

 1 //twofile1.cpp  该文件件包含了main()
 2 //compile with twofile2.cpp
 3 //该程序演示了c++佮处理链接性为外部和内部的变量
 4 #include <iostream>
 5 
 6 int tom = 3;  //定义外部(全局)变量,链接性为外部
 7 int dick = 30;  //定义外部(全局)变量,链接性为外部
 8 static int harry = 300;  //定义静态外部变量,链接性为内部,即harry的作用域为twofile1.cpp,不能作用于外部cpp文件
 9 
10 void remote_access();  //在twofile2.cpp中定义的函数
11 
12 int main()
13 {
14     using namespace std;
15 
16     cout << "外部变量tom的地址:" << &tom << " tom的值为:" << tom << endl;
17     cout << "外部变量dick的地址:" << &dick << " dick的值为:" << dick << endl;
18     cout << "静态外部变量harry的地址:" << &harry << " harry的值为:" << harry << endl;
19     remote_access();  //调用在twofile2.cpp中定义的函数
20 
21     system("pause");
22     return 0;
23 }
twofile1.cpp 该文件件包含了main()
 1 //twofile2.cpp
 2 //compile with twofile1.cpp
 3 
 4 #include <iostream>
 5 
 6 extern int tom;  //使用twofile1.cpp中的外部变量tom
 7 static int dick = 10;  //使用自己cpp文件中定义的静态全局变量dick
 8 int harry = 200;  //不会与twofile1.cpp中定义的静态外部变量harry冲突,因此此处的harry的作用域为全部cpp文件
 9 
10 void remote_access()
11 {
12     using namespace std;
13 
14     cout << "在子函数remote_access()中:" << endl;
15     cout << "外部变量tom的地址:" << &tom << " tom的值为:" << tom << endl;
16     cout << "twofile2.cpp中静态外部变量harry的地址:" << &harry << " harry的值为:" << harry << endl;
17 }
twofile2.cpp

执行结果:

 静态存储持续性、无链接性(涉及到一个应用,要看一下的) 

01)无链接性的局部变量:将static限定符用于在代码块中定义的变量。在代码块使用static时,将导致局部
 变量的持续性为静态的。这意味着该变量只在该代码块内可用,但它在该代码块不处于活动状态时仍然存
 在。因此在函数调用之前,静态局部变量的值将保持不变。(静态局部变量适用于再生---可以将用静态局
 部变量存储银行密码,传递到下一个地方去)。
02)如果初始化了静态局部变量。则程序只在启动时进行一次初始化,以后再调用该函数时,将不会像自动变量
 那样再次被初始化。

 1 #include <iostream>
 2 
 3 const int ArSize = 10;  //定义一个值不可被改变的外部(全部)变量,加了const之后,ArSize的作用域为该cpp文件,相当于加了static
 4 
 5 void strcount(const char* str);  //声明一个子函数
 6 
 7 int main()
 8 {
 9     using namespace std;
10 
11     char input[ArSize];  //定义一个c风格字符串数组,最多存储9个字符
12     char next;  //定义一个字符变量,作用域为main()
13 
14     cout << "请输入一行字符:";
15     cin.get(input, ArSize);  //输入到字符串数组input中,最多可输入ArSize-1个字符,有一个位置要为空字符'\0'保留
16 
17     while (cin)  //如果输入的字符数小于ArSize,那么最后的回车键会被保留在输入流中。
18     {            //如果输入流中有字符,那么cin的返回值一直是true(因为没有接收的变量)
19         cin.get(next);  //如果输入的字符多于9个,那么输入到next中去
20         while (next != '\n')
21             cin.get(next);  //一直将多输入的字符接收完
22         strcount(input);  //子函数调用
23         cout << endl;
24         cout << "请输入一行字符:";
25         cin.get(input, ArSize);
26     }
27     cout << "Bye" << endl;
28 
29     system("pause");
30     return 0;
31 }
32 void strcount(const char* str)
33 {
34     using namespace std;
35 
36     static int total = 0;  //只会初始化一次,以后被修改的值会被保留下来
37     int count = 0;  //每次调用该函数的时候count都会被初始化一次
38 
39     cout << "\"" << str << "\" contains ";  //其中\"表示显示",\表示消除歧义的意思
40     while (*str != '\0')
41     {
42         count++;
43         str++;
44     }
45     total += count;  //由于total是静态局部变量,所以total每次的值都会保存下来
46     cout << count << " characters" << endl;
47     cout << total << " characters total" << endl;
48 }
静态局部变量的应用

函数说明:

    01)使用关键字static,在子函数中定义了一个静态局部变量total,total只会在子函数中被初始化一次,以后total

         的值就会被保存下来,在函数开始的时候不会被初始化。*****

   02)cin返回值相关,多输入的字符的处理方法等

   03)定义的全局变量,加上const关键字之后,其作用域就是该cpp文件,而不是整个文件夹中的cpp文件了

执行结果:

 存储方案和动态分配

01)通常,编译器使用三块独立的内存:一块用于静态变量(可能再细分)、一块用于自动变量、一块用于动态存储
02)动态内存由运算符new和delete控制,而不是由作用域和链接性控制
03)虽然存储方案不适用于动态存储,但适用于用来跟踪动态内存的额自动和静态指针变量。例如在一个函数中包含
 语句: float* p_fees = new float [20];
 由new分配的80个字节(假设float为4个字节)的内存将一直保留在内存中,知道使用delete运算符将其释放。
 但当包含该声明的语句块执行完毕时,p_fees指针将消失,如果希望在另一个函数能够使用这80个字节的内容,
 必须将其地址传递或返回给该函数。
 另一方面,如果将p_fees的链接性声明为外部的,则文件中位于该声明之后的所有函数都可以使用它。另外,
 通过在另一个cpp文件中使用下述声明,便可以使用该指针:
 extern float* p_fees;
04)对单值变量使用new运算符初始化:使用小括号或者是大括号都可以
  int* pi = new int (6); //表示*pi=6
  double* pd = new double(99.99); //表示*pd = 99.99
  int* pin = {6}; //表示*pin = 6
  double* pd = new double{99.99}; //表示*pd = 99.99
05)对常规结构或数组使用new运算符进行初始化:需要使用大括号的列表初始化
 struct where {double x; double y; double z}; //声明一个结构where
 where* one = new where {2.5,5.3,7.2}; //新建一个结构one,使one指向where结构
 int* ar = new int[4] {2,4,6,7}; //新建一个指针ar,指向一个包含四个元素的数组
06)运算符new和new[]运算符分别调用如下函数:
 void * operator new(std::size_t); //used by new
 void * operator new[](std::size_t); //used by new[],其中std::size_t是一个typedef
 这些函数被成为分配函数,它们位于全局名称空间中
 对于int* pi = new int;将被转换为int* pi = new(sizeof(int));
 int* pa = new int [40];将被转换为int* pa = new(40*sizeof(int));
07)运算符delete和delete[]调用如下函数:
 void operator delete(void *); //used by delete
 void operator delete[](void *); //used by delete[]
 这些函数被称为释放函数

定位new运算符 

01)通常,new运算符负责在堆(heap)中找到一个足以能够满足要求的内存块。
  new运算符还有另一种变体,被称为定位new运算符,它能够让程序员指定要使用的地方。程序员使用这种特性
  来设置其内存管理规程、处理需要通过特定地址进行访问的硬件或在特定位置创建对象。
02)要使用定位new特性,首先要包含头文件new,它提高了这种版本的new运算符的原型(函数声明);然后将new运算符
  用于提供了所需地址的函数,变量后面可以有大括号,也可以没有;

 1 #include <iostream>
 2 #include <new>  //for new定位符
 3 
 4 const int BUF = 512;
 5 const int N = 5;
 6 char buffer[BUF];  //定义一个字符串数组buffe,没有进行初始化.c风格字符串的大小用sizeof()函数,string类用size()
 7 
 8 int main()
 9 {
10     using namespace std;
11 
12     double* pd1;
13     double* pd2;
14     pd1 = new double[N];  //pd1是一个指针(地址),new double[N]返回一个可以存储N个double型数据的地址
15     pd2 = new (buffer) double[N]; //pd2是一个指针(地址),new double[N]返回一个可以存储N个double型数据的地址
16                                  //而这个地址是buffer的地址,即pd2和buffer的地址是一样的,
17                                //虽然buffer内数据类型为char,但是pd2指向的空间内存数据为double型的
18     cout << "Now calling new and new placement for first time" << endl;
19     cout << "使用new给pd1分配内存,指针pd1(一个地址)为:" << pd1 << endl;
20     cout << "使用定位符new将buffer的地址赋给pd2" << endl;
21     cout << "使用new定位符给pd2分配内存,指针pd2(一个地址)为:" << pd2 << endl;
22     cout << "buffer的地址为:" << (void *)buffer << endl;  //由于buffer是一个字符串数组的名字,且数组的名字
23     //就是数组内第一个元素的地址,如果直接输出buffer的话,cout不会输出地址,而是输出buffer内的字符串
24     //所以要输出地址,所以要对buffer进行强制转换为void*,void*表示任意类型的指针,或表示“该指针与一地址值相关,
25     //但是不清楚在此地址上的对象的类型
26     for (int i = 0; i < N; i++)
27         pd2[i] = pd1[i] = 1000 + 20.0 * i; //对pd2和pd1指向的内存空间进行赋值
28     for (int i = 0; i < N; i++)  //显示pd2和pd1指向的内存空间的值和地址
29     {
30         cout << "pd1[" << i << "] = " << pd1[i] << " at " << &pd1[i] << endl;
31         cout << "pd2[" << i << "] = " << pd2[i] << " at " << &pd2[i] << endl;
32     }
33     
34     cout << endl;
35     cout << "Now calling new and new placement for second time" << endl;
36     cout << "重新为一个指针使用new定位符,为pd4定位到buffer数组的地址" << endl;
37     double *pd3, *pd4; //注意double* pd3,pd4只是声明了一个指针pd3
38     pd3 = new double[N];
39     pd4 = new (buffer) double[N]; //重新将buffer的地址赋给pd4,buffer的地址还是原来的地址
40 
41     cout << "使用new给pd3分配内存,指针pd3(一个地址)为:" << pd3 << endl;
42     cout << "使用定位符new将buffer的地址赋给pd4" << endl;
43     cout << "使用new定位符给pd4分配内存,指针pd4(一个地址)为:" << pd4 << endl;
44     cout << "buffer的地址为:" << (void *)buffer << endl;
45 
46     for (int i = 0; i < N; i++)
47         pd3[i] = pd4[i] = 1000 + 40.0 * i; //对pd2和pd1指向的内存空间进行赋值
48     for (int i = 0; i < N; i++)  //显示pd2和pd1指向的内存空间的值和地址
49     {
50         cout << "pd3[" << i << "] = " << pd3[i] << " at " << &pd3[i] << endl;
51         cout << "pd4[" << i << "] = " << pd4[i] << " at " << &pd4[i] << endl;
52     }
53 
54     cout << endl;
55     cout << "Now calling new and new placement for third time" << endl;
56     delete[] pd1;  //释放常规new运算符分配的内存
57     pd1 = new double[N];  //重新给pd1分配一块内存
58     pd2 = new (buffer + N * sizeof(double)) double[N]; //提供一个从数组buffer开头算起的偏移量
59 
60     cout << "使用new给pd1分配内存,指针pd1(一个地址)为:" << pd1 << endl;
61     cout << "使用定位符new将buffer的地址赋给pd2" << endl;
62     cout << "使用new定位符给pd2分配内存,指针pd2(一个地址)为:" << pd2 << endl;
63     cout << "buffer的地址为:" << (void *)buffer << endl;
64 
65     for (int i = 0; i < N; i++)
66         pd2[i] = pd1[i] = 1000 + 20.0 * i; //对pd2和pd1指向的内存空间进行赋值
67     for (int i = 0; i < N; i++)  //显示pd2和pd1指向的内存空间的值和地址
68     {
69         cout << "pd1[" << i << "] = " << pd1[i] << " at " << &pd1[i] << endl;
70         cout << "pd2[" << i << "] = " << pd2[i] << " at " << &pd2[i] << endl;
71     }
72 
73     delete[] pd1;  //释放常规new运算符分配的内存
74     delete[] pd2;  //释放常规new运算符分配的内存
75     //关于是否使用delete释放new定位符分配的内存的问题:
76     //如果此处使用delete [] pd3; 将会导致程序报错
77     //因为buffer指定的内存是进来内存,而delete只能用于删除这样的指针:指向常规new运算符分配的堆内存
78     //也就是说数组buffer位于delete的管辖之外。
79     //因为buffer是在函数外部定义的数组变量,所以buffer的存储方式为静态存储,在函数内定义的变量为自动存储
80     //在函数外部定义或者是使用关键字static定义的变量存储方式都为静态存储
81     system("pause");
82     return 0;
83 }
使用new定位符的例程

程序说明:

01)void*表示任意类型的指针,或表示“该指针与一地址值相关,但是不清楚在此地址上的对象的类型,程序中使用了(void *) buffer; 对buffer进行了强制转换,

     以便于cout<<buffer<<ebdl;  显示的是地址,而不是字符串;

02)关于是否使用delete释放new定位符分配的内存的问题(怎么样释放由new定位运算符分配的内存,见第十二章):

  如果此处使用delete [] pd3; 将会导致程序报错
  因为buffer指定的内存是进来内存,而delete只能用于删除这样的指针:指向常规new运算符分配的堆内存
 也就是说数组buffer位于delete的管辖之外。
 因为buffer是在函数外部定义的数组变量,所以buffer的存储方式为静态存储,在函数内定义的变量为自动存储
 在函数外部定义或者是使用关键字static定义的变量存储方式都为静态存储

执行结果:

 名称空间特性 

01)通过定义一种新的声明区域来创建命名的名称空间,这样做的目的之一是提供一个声明的区域。一个名称空间中
  的名称(变量)不会与另外一个名称空间的相同名称发生冲突,同时允许程序的其他部分使用该名称空间中的声明
  的东西。例如,下面代码使用关键字namespace创建了两个名称空间:Jack和Jill
  namespace Jack
  {
   double pail; //声明变量
   void fetch(); //声明函数
   int pal; //声明变量
   struct Well {...}; //声明结构Hill
  }
  namespace Jill
  {
   double bucket(int n) //函数定义
   {
     ...
   }
   double fetch; //声明变量
   int pal; //声明变量
   struct Hill {...}; //声明结构
  }
02)名称空间可以是全局的,也可以位于另一个名称空间中,但是不能位于代码块中。因此默认情况下,在名称空间中
  声明的变量的链接性为外部(除非它引用了常量)
03)任何名称空间中的名称都不会与其他名称空间中的名称发生冲突。因此Jcak中的变量fetch和Jill中的fetch共存
04)名称空间是开放的,可以把名称加入到已有的名乘客空间中,如下将名称goose添加到Jill中已有的名称列表中:
  namespace Jill
  {
   char* goose (const char*); //在已有的名称空间中添加一个函数声明,返回值为char型指针
  }
05)同样在Jack名称空间为fetch()函数提供了原型,可以在该文件后面(或另外一个文件中)再次使用Jack名称空间
  来提供该行拿书定义的代码:
  namespace Jack
  {
   ...
  }
06)如何访问名称空间中的变量?最简单的方法是通过作用域解析运算符::,使用名称空间来限定该名称,如下:
  Jcak::pail = 12.34; //使用Jack名称空间中的名称(变量)pail
  Jill::Hill mole; //使用Jill名称空间中的名称(结构或者是结构新类型)Hill声明一个结构mole
  Jcak::fetch(); //调用Jack名称空间中的函数fetch()
07)我们并不希望每次使用每次时都对它进行限定(名称空间名+::+变量形式),因此c++提供了using声明和using
  编译指令。
  A、 using声明使特定的标识符可用,使用方法如下:
      using Jill::fetch;//a using declaration,使用该声明之后,就可以在某一个函数中直接使用fetch,而不用再使用Jcak::fetch
   例1: 在函数内使用using声明:只是在该函数内可用
    namespace Jill
    {
      double fetch; //声明变量
      int pal; //声明变量
    }
    int main()
    {
      using Jill::fetch; //在main()函数中就可以直接使用fetch变量,而不用再次使用Jill::fetch
             //此时的fetch为局部变量(或将fetch添加到局部名称空间中)
             //局部的fetch也将覆盖全局的fetch(如果有全局变量fetch的话)
      double fetch; //不合法!,因为已经有一个局部变量fetch变量了
      cin>>fetch; //读一个值到Jill::fetch
    }
  例2:在函数外使用using声明:将把名称添加到全局名称空间中
    void other();
    namespace Jill
    {
      double fetch; //声明变量
      int pal; //声明变量
    }
    using Jill::fetch; //此时的fetch为全局变量(或将fetch添加到全局名称空间中)
    int main()
    {
      cin>>fetch: //读一个值到Jill::fetch
      other();
      ...
    }
    void other()
    {
      cout<<fetch; //输出Jill::fetch
      ...
    }
 B、 using编译指令使用方法(using编译指令使所有的名称都可用,using声明只可以使一个名称可用):
        using namespace Jcak; //使Jack名称空间中的所有名称都可用
08)变量Jacl::pal和Jill::pal是不同的标识符,表示不同的内存单元。然而使用using声明,情况将会发生变化:
   using Jack::pal;
   using Jill::pal;
   pal = 4; //此处将会导致二义性,到底使用哪个名称空间中的pal呢?
   事实上,编译器不允许程序员同时使用上述两个using声明,因为这将导致二义性
09)在函数内使用using编译指令,全局变量 vs 局部变量 vs 名称空间中的变量 (这三个变量的名字都一样)
  namespace Jill
  {
    double bucket(double n) {...} //定义一个函数
    double fetch; //声明一个变量
    struct Hill {...}; //声明一个结构
  }
  char fetch; //声明一个全部变量fetch
  int main()
  {
    using namespace Jill; //名称空间Jill中的名称将作为局部变量,添加到mian()
    Hill Thrill; //使用结构新类型新建一个结构
    double water = bucket(2.0); //调用名称空间Jill中定义的一个函数
    double fetch; //不会导致错误,定义局部变量fetch,注意和上边使用using声明比较(例1)

                                             //且局部定义的fetch将会覆盖掉全局fetch和Jill::fetc

                                  //但是如果使用using声明,在main()中声明了Jill::fetch,再去定义局部变量fetch将会出错

    cin>>fetch; //读取一个值给局部变量fetch
    cin>>::fetch; //读取一个值给全局变量fetch
    cin>>JIll::fetch; //读取一个值给全局变量JIll::fetch
  }
  int foom()
  {
    Hill top; //不合法
    Jill::Hill creat; //合法
  }
 
 说明:I 在mian()中,名称Jill::fetch被放在局部名称空间中,但其作用域不是局部的,因此
      不会覆盖全局的fetch。局部声明的fetch将会覆盖掉全局fetch和Jill::fetch。
      然而,如果在main()中不使用using编译指令,使用作用域解析运算符::(Jill::fetch),
      则全局fetch和Jill::fetch都是可用的,但是人继续去定义局部变量fetch将会报错。使用using编译指令
      将不会导致这样的错误,局部版本将隐藏名称空间版本。(即本例和例1进行了比较)
    II 虽然函数中的using编译指令将名称空间的名称视为在函数之外声明的,但它不会使得该文件
       中的其他函数能够使用这些名称,比如foom()函数中不可以直接使用名称空间Jill中的结构Hill

 

10)#include <iostream> //将iostream放到名称空间std中
 using namespace std; //using编译指令指出std名称空间在全局可用
 main()
 { ... }
 其中#include <iostream> //将iostream放到名称空间std中
 using namespace std;
 等价于#include <iostream.h>
11)可以将名称空间进行嵌套:
  namespace elements
  {
    namespace fire
    {
      int flame;
    }
      float water;
  }
  使用内部的flame方法为:using namespace element::fire::flame;
  同样可以使用下面的using编译指令使内部的名称可用:
  using namespace element::fire;
12)可以在名称空间中使用using声明和using编译指令,如下所示:
  namespace myth
  {
   using Jill::fetch; //使用名称空间Jill中的名称(变量)fetch
   using namespace elemebts; //使用名称空间elements下的所有名称
   using std::cout: //使用名称空间std中的cout
   using std::cin; //使用名称空间中的cin
  }
   假设现在要访问Jill::fetch,由于Jill::fetch现在在名称空间myth中(在myth中,它被叫做fetch)
     因此可以这样访问它:std::cin>>myth::fetch; //读数据到myth::fetch
  当然,由于fetch也位于Jill名称空间中,因此也可以称作JIll::fetch
  std::cout<<Jill::fetch; //显示读进myth::fetch中的值
  当然如果没有与之冲突的局部变量,则也可以这样做:
  using namespace myth;
  cin >> fetch;
  另外下面的语句将导入名称空间myth和elements(因为elements位于myth中)
  using namespace myth;
13)可以给名称空间创建别名
  namespace muft = my_very_favorite_things; //表示让mvft成为my_very_favorite_things的别名
  可以使用这种技术来简化对嵌套名称空间的使用:
  namespace MEF = myth::elements::fire;
  using MEF::flame;
14)未命名的名称空间: 其内部的名称作用域为本cpp文件,其他cpp文件不可访问名称空间中的名称,这就提供
     了链接性为内部的静态变量的替代品,例如:
  static int counts; //声明一个链接性为内部的静态变量(这里静态的意思不是counts的值不可改变)
  int other(); //声明一个函数
  int mian()
  {
    ...
  }
  int other()
  {
    ...
  }
  以上代码等价于:
  namespace
  {
    int counts; //没有名字的名称空间中的变量,其链接性为内部,即只能够在本cpp文件内使用counts
  }
  int other(); //声明一个函数
  int mian()
  {
    ...
  }
  int other()
  {
    ...
  }

 

 名称空间示例 

namesp.h

 1 #pragma once  //自动添加进来的,表示仅编译一次
 2 //相同作用命令
 3 //#ifndef ABC_H 
 4 //#define ABC_H
 5 
 6 //namesp.h
 7 #include <iostream>
 8 #include <string>
 9 
10 namespace pers  //新建一个名称空间
11 {
12     struct Person  //新建一个结构新类型
13     {
14         std::string fname;  //新建一个名字为fname的string类变量
15         std::string lname;  //新建一个名字为lname的string类变量
16     };
17     void getPerson(Person & rp);  //声明一个函数,形参为指向Person结构的引用变量rp
18     void showPerson(const Person &rp);  //声明一个函数,形参为指向Person结构的引用变量rp
19 }
20 namespace debts
21 {
22     using namespace pers;  //使用using编译指令,使名称空间pers中的名称在debts中可用
23     struct Debt
24     {
25         Person name;  //使用名称空间pers中的结构新类型新建一个结构name
26         double amount;  //新建一个double型变量amount
27     };
28     void getDebt(Debt & rd);  //声明一个函数,形参为指向Debt结构的引用变量rd
29     void showDebt(Debt & rd);  //声明一个函数,形参为指向Debt结构的引用变量rd
30     double sumDebts(const Debt arr[], int n);  //声明一个函数,第一个形参为结构数组arr
31 }
namesp.h
 1 //子函数namesp.cpp
 2 #include <iostream>  //用尖括号表示在系统文件中查找
 3 #include "namesp.h"  //用双引号表示在自己新建的h文件中查找
 4 
 5 namespace pers
 6 {
 7     using std::cout;  
 8     using std::cin;  //使用using声明,表示以后可以使用cin
 9     void getPerson(Person & rp)  //函数定义,在h文件中只是声明了一下
10     {
11         cout << "Enter the first name: ";
12         cin >> rp.fname;
13         cout << "Enter the lats name: ";
14         cin >> rp.lname;
15     }
16     void showPerson(const Person &rp)  //提供函数定义
17     {
18         std::cout << rp.lname << ", " << rp.fname;  //std::cout表示直接使用运算符::来访问名称空间中的名称
19     }
20 }
21 namespace debts
22 {
23     void getDebt(Debt & rd)  //提供函数定义
24     {
25         getPerson(rd.name);  //调用名称空间pers中的getPerson(Person & rp)函数,
26         //name是名称空间debts下结构Debt中的一个成员,而name是pers名称空间下由Person结构创建的
27         //debts和pers的联系就是可以在debts中使用pers中的名称的那一句using编译指令
28         std::cout << "Enter debt: ";
29         std::cin >> rd.amount;
30     }
31     void showDebt(Debt & rd)   //提供函数定义
32     {
33         showPerson(rd.name);  //调用名称空间pers中的showPerson(Person & rp)函数,
34         std::cout << ": $" << rd.amount << std::endl;
35     }
36     double sumDebts(const Debt arr[], int n)
37     {
38         double total =0;  //局部变量必须初始化,否则会报错的
39         for (int i = 0; i < n; i++)
40             total += arr[i].amount;
41         return total;
42     }
43 }
namesp.cpp
 1 //main()
 2 
 3 #include <iostream>
 4 #include "namesp.h"
 5 
 6 void other(void);  //声明一个函数
 7 void another(void);  //声明一个函数
 8 
 9 int main()
10 {
11     using debts::Debt;  //使用using声明,使名称空间中的新结构类型Debt可用
12     using debts::showDebt;  //使用using声明,使void showDebt(Debt & rd)函数可用
13 
14     Debt golf = { {"Benny","Goatsniff"},120.0 };  //使用新结构类型Debt创建结构golf并初始化
15     showDebt(golf);  //调用名称空间debts中的函数
16     other();
17     another();
18 
19     system("pause");
20     return 0;
21 }
22 
23 void other(void)
24 {
25     using std::cout;
26     using std::endl;
27     using namespace debts;  //使用using编译指令,使名称空间debts中的名称在other()函数中都可用
28 
29     Person dg = { "Doodles","Glister" };  //使用结构新类型Person创建一个结构dg并初始化
30     showPerson(dg);  //调用名称空间pers中的函数showPerson(),而pers又在debts可用,所以只声明一个debts就可以了
31     cout << endl;
32     Debt zippy[3];  //使用结构新类型Debt创建一个结构数组zippy,包含了3个Debt型结构
33     for (int i = 0; i < 3; i++)
34         getDebt(zippy[i]);  //首先是输入first name和last name,再输入debt
35     for (int i = 0; i < 3; i++)
36         showDebt(zippy[i]);  // 首先是显示last name和first name ,再输出debt
37     cout << "Total debt: $" << sumDebts(zippy, 3) << endl;  //调用名称空间debts下的函数double sumDebts(const Debt arr[], int n)
38 }
39 
40 void another(void)
41 {
42     using pers::Person;  //使用using声明仅使名称空间pers中的结构Person可用
43     Person collector = { "Milo","Rightshift" };  //使用结构新类型创建结构collector并初始化
44     pers::showPerson(collector);  //使用作用域运算符::使用名称空间中的void getPerson(Person & rp)
45     std::cout << std::endl;
46 }
namesp_main.cpp

程序说明:涉及到了不同文件中的不同函数之间的函数调用

执行结果为:

名称空间的使用规范 m12

01)使用在已命名的名称空间中声明的变量,而不是使用外部全局变量
  使用在已命名的名称空间中声明的变量,而不是使用静态全局变量
02)如果开发了一个函数库或者是类库,将其放在一个名称空间中。
03)不要在头文件中使用using编译指令。首先,这样掩盖了要让那些名称可用;另外,包含头文件的顺序可能会
  影响程序的行为。如果非要使用using编译指令,应将其放在所有预处理器编译指令#include之后
04)导入名称时,首选使用作用域解析运算符或using声明的方法
05)对于using声明,首选将其作用域设置为局部而不是全局

 

2019.04.04 早 于 杭州

haijing miss you

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!