1.单独编译
和C语言一样,C++允许甚至鼓励将组件函数放在独立的文件中,可以单独编译这些文件,然后把它们链接成可执行的程序。如果只修改了一个文件,则可以只重新编译该文件,然后与其他文件的编译版本进行链接,这会使得大程序的管理更加方便。
例如我们要将如下程序分解,将支持函数放在一个独立的文件中,我们需要怎么做呢?
#include<iostream>
using namespace std;
struct fruit
{
int apple;
int banana;
};
fruit fruitSum (fruit f1, fruit f2);
void showFruit (fruit f);
int main()
{
fruit f1 = {15, 20};
fruit f2 = {20, 30};
showFruit(f1);
showFruit(f2);
showFruit(fruitSum(f1, f2));
}
fruit fruitSum (fruit f1, fruit f2)
{
fruit fsum;
fsum.apple = f1.apple + f2.apple;
fsum.banana = f1.banana + f2.banana;
return fsum;
}
void showFruit (fruit f)
{
cout << f.apple << endl;
cout << f.banana << endl;
}
该程序是一个将结构内容相加,并显示结果,算上main()一共有三个函数,不能简单地以main()函数为界,简单的将原来的文件分为两个,因为main()和其他两个函数都使用了同一个结构声明,因此两个文件都应该包括结构声明,简单地将他们输入进去无疑会增加麻烦,因为需要修改结构的时候,需要对两个文件中的结构都进行修改,这简直比每次使用new的时候都使用delete都让人困扰。所以C++的开发人员提供了#include的方式。我们将函数原型,结构声明放入到头文件中,然后在每个文件中引入头文件的内容即可,所以原来的程序被分为了三个部分。
- 头文件:包含结构声明和使用这些结构的函数的原型
- 源代码文件:包含与结构有关函数的代码
- 源代码文件:包含调用与结构相关的函数的代码
注意:不要将函数定义或变量声明放到头文件中,这回导致一个程序中有两个相同变量或函数的定义,除非是内联函数,否则这种行为会报错。
- 头文件中可以包含的内容
1.函数原型
2.使用#define或const定义的符号常量
3.结构声明
4.类声明
5.模板声明
6.内敛函数
注意:#include语句中" "以及 < >之间的区别。
- #include"iostream" 文件名包含在双引号中,则编译器将首先查找当前的工作目录或源代码目录,如果在那里没有找到头文件,则在标准位置查找。所以包含自己的头文件时,应该用" "号。
- #include<iostream>文件名包含在尖括号中,则编译器将在存储标准头文件的主机系统的文件系统中查找,所以引用标准库的头文件时,应该使用<>号。
注意:在IDE中,不要把头文件加入到项目列表中,也不要在源代码文件中使用#include来包含其他源代码文件。
下面展示将上述代码分割为三个文件的文件内容:
//文件名:coordin.h
//头文件
#ifndef COORDIN_H_
#define COORDIN_H_
struct fruit
{
int apple;
int banana;
};
fruit fruitSum (fruit f1, fruit f2);
void showFruit (fruit f);
#endif
//文件1 file1.cpp
#include<iostream>
#include"coordin.h"
using namespace std;
int main()
{
fruit f1 = {15, 20};
fruit f2 = {20, 30};
showFruit(f1);
showFruit(f2);
showFruit(fruitSum(f1, f2));
return 0;
}
//文件2 file2.cpp
#include<iostream>
#include<cmath>
#include"coordin.h"
fruit fruitSum (fruit f1, fruit f2)
{
fruit fsum;
fsum.apple = f1.apple + f2.apple;
fsum.banana = f1.banana + f2.banana;
return fsum;
}
void showFruit (fruit f)
{
cout << f.apple << endl;
cout << f.banana << endl;
}
- 在linux中编译两个源代码文件命令:g++ file1.cpp file2.cpp -o fruit
输入命令之后,编译器会将两个文件进行编译,首先将#include的内容转化成对应的文本内容,然后生成一个新的cpp文件来存放他们,之后编译器创建每一个源代码文件的目标代码文件,这种文件格式可以放入整个的大程序之中,生成这种目标代码文件之后,链接程序将目标代码文件,库代码和启动代码进行合并,生成可执行文件。 - 头文件管理
C++中规定,在同一个文件中只能将同一个头文件包含一次,因为很有可能在不知情的情况下包含同一个头文件多次,比如使用了包含另一个头文件的头文件,为了防止这种错误的产生,C++有一种技术可以避免多次包含同一个头文件。它是基于预处理器编译指令#ifndef(if not defind)的。
//本代码块意味着,仅当以前没有使用预处理器编译指令#define定义名称COORDIN_H_,才处理其中的语句
#ifndef COORDIN_H_
#define COORDIN_H_
...
#endif
//当首次编译器遇到该文件时,名称COORDIN_H_未定义
//在这种情况下,编译器将查看#ifndef 与 #endif之间的内容
//并且会读取#define这一行
//如果在同文件中再遇到其他包含该头文件的代码
//系统就知道COORDIN_H_已经被定义过了,直接回调到#endif
//注意:这种方法并不能防止编译器将文件包含两次,但是会让他忽略第一次以外所包含的内容
注意:在C++标准中使用术语"翻译单元",而不是文件。也就是说我们上述的文件,标准称呼为翻译单元。
- 多个库的链接问题
C++标准允许每个编译器设计人员以他认为合适的方式实现名称修饰,也就是说不同编译器创建的二进制模块很可能没有办法正确的链接,也就是说两个编译器很可能为同一个函数时候生成不同的修饰名称。名称的不同将使链接器无法将一个编译器生成的函数调用与另一个编译器生成的函数定义匹配,为了防止出现这种问题,如果我们拥有源代码,我们可以自己的编译器重新编译源代码以防止链接错误。
2.存储持续性
- 四种存储方式的存储持续性
1.自动存储:在函数定义中声明的变量(包括函数参数)的存储持续性是自动的。他们在程序开始执行其所属函数或代码块时被创建,在执行完函数或者代码块后被销毁
2.静态存储:在函数定义外定义的变量和使用关键字static定义的变量的存储持续性都为静态。他们在程序整个运行过程中都存在
3.线程存储:C++11中出现了并行多线程编程,这在多核处理器普及的今天很常见,程序能将计算放在可并行处理的不同线程中,生命周期和线程一样长。
4.动态存储:用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止。这种内存的存储持续性为动态,有时被成为自由存储区或者堆。 - 作用域和链接
作用域:作用域描述了名称在文件的多大范围内可见,例如,函数中定义的变量可在该函数中使用,但是不能在其他函数中使用;而在文件中的函数定义之前定义的变量则可以在所有函数中使用。
链接性描述了名称如何在不同单元间共享。链接性为外部的名称可以在文件间共享,链接性为内部的名称只能由一个文件中的函数共享。自动变量没有链接性,所以他们不能共享。 - 作用域的分类
1.局部变量作用域:只在定义他的代码块中可用。
2.全局变量作用域:从定义位置一直到文件结束可用。
3.类成员的作用域:整个类。
4.名称空间变量的作用域:整个名称空间。
5.函数原型参数的作用域:只在包含参数列表的括号中可用
6.函数的作用域可以是整个类或者整个名称空间,而不能是局部的。
3.自动存储的持续性
在默认情况下,在函数中声明的函数参数和变量的存储持续性都为自动,作用域为局部,没有链接性。也就是说在main()函数中创建x变量,并且在函数show()中也创建了x变量,两个变量都会被独立的分配内存,而且之间并没有任何联系,是两个独立的变量,并且只在定义他们的函数中可使用。
有种情况需要特殊声明,在main()函数中定义了变量x,在main()函数中有一个代码块,也定义了变量x。此时C++并不认为程序有语法错误,C++同样认为两个x并不是同一个变量,并且在这种情况下,在代码块中所使用的x就是代码块中定义的x,在代码块外使用的x就是代码块外定义的x。这并不代表在代码块外定义的变量无法在代码块内使用,只是说明在命名一致的情况下,代码块里的变量会将代码块外的变量暂时隐藏起来。
4.自动变量的初始化
//可以使用任何在声明时,其值已知的表达式来对自动变量进行初始化
//举例:初始化变量x,y,z
int w;
int x = 5;
int big = INT_MAX - 1; //<climits>
int y = 2 * x;
cin >> w;
int z = 3 * w;
5.自动变量和栈
由于自动变量的数目随着函数的开始和结束而增减,所以程序必须在运行时对自动变量进行管理。最常用的一个方法就是留出一段内存,将其视为栈,所谓的栈就是一段后进先出的内存。程序会用两个指针来追踪栈,其中栈顶一个指针,表示下一个可用的内存单元,栈底一个指针,表示栈的开始位置。当函数被调用的时候,所需要的自动变量一次存放入栈中。函数结束后,栈顶的指针重新指回到函数使用前的位置。也就是说,新加入的变量并没有被删除,而是不在被标记,被下一次放入栈中的变量替代掉。
6.寄存器变量
关键字register是从C语言中引入的,意味着它建议编译器使用CPU寄存器来存储自动变量。目的是提高访问自动变量的速度。不过在C++11中,register关键字已经沦为显式的声明该变量为自动变量的关键字,也就是曾经auto的用法。不删除这个关键字是防止现有的代码非法。
7.静态持续变量定义
C++为静态存储持续性变量提供了三种链接性,即外部链接性,内部链接性,无链接性。这三种链接性都在整个程序执行期间存在,与自动变量相比,他们的寿命更长。
由于静态变量的数目在程序中是不变的,所以程序不需要使用特殊的装置来管理他们。编译器将分配固定的内存块来存储所有的静态变量,这些变量在整个程序执行期间一直存在。
另外,如果没有显示地初始化静态变量,编译器会将他设置为0。在默认情况下,静态数组和结构将每一个元素或成员都设置为0。
8.如何创建这三种静态持续变量
//创建链接性为外部的静态持续变量 --- 在代码块外部声明它
//创建链接性为内部的静态持续变量 --- 在代码块外部声明它,并且使用static限定符
//创建链接性为无链接性的静态持续变量 --- 必须在代码块内声明它,并且使用static限定符
//下面举例子
int global = 100; //链接性为外链接性的静态持续变量
static int one = 100; //链接性为内连接的静态持续变量
int main()
{
...
}
void show()
{
static int count = 0; //链接性为无链接的静态持续变量
}
9.三种静态持续变量的区别
- 无链接性的静态持续变量:
虽然它确实在整个程序执行期间都存在,但是他只能在函数内部进行使用,就好像自动变量一样。不过和自动变量不同的是,他在函数执行结束之后并不会被销毁,仍然会存在在内存中,等待下一次的调用。 - 内部链接性的静态持续变量:
这种变量的作用域和无链接性的静态持续变量不同,无链接性的静态持续变量的作用域为局部,而内部链接性的静态持续变量作用域为整个文件。即从文件声明位置开始到文件结尾范围内都可以被使用,不过由于链接性为内部链接,所以只能在包含上述代码的文件中使用它,不能在程序的其他文件中使用。 - 外部链接性的静态持续变量:
这种变量的作用域和内壁链接性的静态持续变量相同,都是整个文件,不过外部链接性的静态变量也可以在程序的别的文件中使用,并不是像内部链接性的静态持续变量那样,只可以在声明的文件中进行使用。
10.5种变量的存储方式
存储描述 | 持续性 | 作用域 | 链接性 | 如何声明 |
---|---|---|---|---|
自动变量 | 自动 | 代码块 | 无链接性 | 在代码块中 |
寄存器变量 | 自动 | 代码块 | 无链接性 | 在代码块中使用register |
无链接静态持续变量 | 静态 | 代码块 | 无链接性 | 在代码块中使用static |
内部链接静态持续变量 | 静态 | 文件 | 内部链接 | 在代码块外使用static |
外部链接静态持续变量 | 静态 | 文件 | 内部链接 | 在代码块外 |
11.静态变量的初始化
除了默认的零初始化以外,还对静态变量执行常量表达式初始化和动态初始化。初始化的形式是,首先先对所有的静态变量实行零初始化,无论是否他被显式的初始化,然后使用常量表达式初始化的变量,编译器仅根据文件内容就可计算表达式,编译器将执行常量表达式初始化。如果没有足够的信息,变量会被动态初始化。
#include<cmath>
int x; //被零初始化
int y = 15; //被静态初始化
const double pi = 4 * atan(1.0); //由于需要进行函数运算,会被动态初始化
12.静态持续性,外部链接性
链接性为外部的变量被称为外部变量,也被成为全局变量,他们的存储持续性为静态,作用域为整个文件,因为外部变量的链接性为外部,也就是在程序的所有文件中都可以使用它,那么为了使用这种外部变量就需要在使用它的文件中进行声明。而C++有“单定义规则”,这个规则指出,一个变量只能一次定义。为了满足这种要求,C++提供了两种变量声明,一种是定义声明,也叫作定义。另一种是引用声明,或者简称为声明。引用声明不分配存储空间,引用以前的变量。引用声明使用关键字extern,且不能进行初始化,如果进行初始化,则为定义声明,分配存储空间。
//定义声明
int a = 100;
extern int a = 100;
//引用声明
extern int a;
注意:单定义规则并不意味着不能有多个名相同的变量。你仍然可以在不同的函数中使用相同的名称来命名自动变量,他们仍然是不同变量,拥有彼此独立的地址。单定义规则说明,虽然程序中可能有同名变量,但是每个变量只有一个定义。下面举一个例子:
//file1.cpp
#include<iostream>
using namespace std;
double warming = 0.3; //定义一个全局变量
void update(double dt);
void local();
int main()
{
cout << "Global warming is " << warming << "degrees.\n";
update(0.1);
cout << "Global warming is " << warming << "degrees.\n";
local();
cout << "Global warming is " << warming << "degrees.\n";
return 0;
}
#include<iostream>
extern double warming;
void update(double dt);
void local();
using std::cout;
void update(double dt)
{
extern double warming;
warming += dt;
cout << "updating global warming to" << warming;
cout << "degrees.\n";
}
void local()
{
double warming = 0.8;
cout << "Local warming =" << warming << "degrees.\n";
cout << "But global warming = " << ::warming << " degrees.\n";
}
注意:update()函数中的extern double warming;意思是使用外部变量中的warming。这条语句并不是一定要写的,因为前面已经有过一个声明在函数外。local()函数表明定义与全局变量同名的局部变量后,局部变量会将全局变量隐藏。使用作用域解析运算符放在变量前面的时候,该运算符表示使用变量的全局版本,这就是local()中使用全局变量warming的方式。::warming是一个很好的方式,安全易懂。
13.全局变量和局部变量的选择
能够使用局部变量都使用局部变量,这样能让程序更加的稳定安全。一般使用全局变量存储常量数据。例如使用const char * const months[12];来存储十二个月份,其中第一个const保证字符串不被修改,第二个const保证指针指向位置不会被修改。
14.静态持续性,内部链接性
将static限定符用于作用域为整个文件的变量时,该变量的链接性将为内部的。在多文件的程序中每部链接性的静态变量只能在定义它的文件中使用。
注意:如果已经定义了一个外部变量,要在另一个文件中定义一个同名的变量,省略extern这种方式是错误的,因为违反了单定义原则。要想实现这种结果需要做的是使用static关键字,将要定义的静态变量声明为静态外部变量。在该文件中,静态外部变量会隐藏外部同名变量。
//file1
int errors = 20; //外部变量
//file2
//符合语法 定义的静态外部变量
//只能在file2文件之中使用 并且在file2中隐藏file1中的errors
static int errors = 15;
15.静态存储持续性,无链接性
这种变量将static限定符用于代码块中定义的变量,代码块中使用static时,将导致局部变量的存储持续性为静态的。这说明虽然变量只在代码块中可用,但是他在该代码块不处于活动状态的情况下仍然存在。两次调用之间,该变量的值不会改变。而且该变量只有第一次运行的时候初始化,之后不再进行初始化。
#include<iostream>
const int Arsize = 10;
void strcount(const char * str);
int main()
{
using namespace std;
char input[Arsize];
char next;
cin.get(input,Arsize);
//使用cin.get(char*,int)读取空行会返回false来结束读取
while (cin)
{
cin.get(next);
while(next != '\n')
cin.get(next);
strcount(input);
cin.get(input,Arsize);
}
return 0;
}
void strcount(cosnt char * str)
{
using namespace std;
static int total = 0; //定义静态变量 只在代码块内使用 只有第一次使用时进行初始化
int count = 0;
cout << "\"" << str << "\" contains";
while(*str++)
count++;
total += count;
cout << count << endl;
cout << total << endl;
}
16.说明符和限定符
- 存储说明符
1.auto (C++11版本以前是存储说明符,C++11的时候不再是存储说明符)
2.register
3.static
4.extern
5.thread_local(C++11新增的)
6.mutable
注意:在同一个声明中不能使用多个说明符,thread_local除外,他可以与static或者extern结合使用。C++11之前,register表示使用CPU寄存器存储,C++11中他表示使用的是自动类型变量。static被用于作用域为整个文件的声明中,表示内部链接性;被用于局部声明中,表示局部变量的存储类型是静态的。extern关键字表示引用声明,即使用的是其他地方定义的变量。thread_local关键字指出变量的持续性与其所属线程的持续性相同。mutable关键字在后面介绍。 - cv-限定符
1.const
2.volatile
cv所代表的就是const和volatile的首字母。最常用的cv-限定符就是const,他表明,内存被初始化之后,程序变不能再对它进行修改。而volatile我们并不是很熟悉,volatile关键字表明,及时程序代码没有对内存单元进行修改,其值也可能发生变化。这种变化可能来自硬件的修改,或者两个程序之间的相互影响。该关键字的作用是改善编译器的优化能力,例如,假设编译器发现,程序在几条语句中两次使用了某个变量的值,则编译器可能不是让程序查找整个值两次,而是将这个值存放在寄存器中,下一次从寄存器中取用。这种优化假设变量值在两次取用之间不会变化,而volatile限定符则是告诉编译器,拒绝使用这种优化。 - mutable
他可以用来指出,即使结构变量为const,其某个成员也可以被修改。
struct data
{
char name[30];
mutable int access;
...
};
cosnt data veep = {"SHIYUQI", 0, ...};
strcpy(veep.name, "Joye"); //不允许
veep.access++; //允许
veep的const限定符禁止程序修改veep的成员,但access成员的mutable说明符使之不受这个限制。
17.const限定符
- const说明符对于默认存储类型稍有影响,在默认情况下,全局变量的链接性为外部的,但是使用const限定符修饰的全局变量链接性为内部,也就好像是添加了static说明符一样。
- 为啥要这么做呢,这么做有啥好处呢?
1.C++修改了常量类型的规则,可以让程序员更加轻松,例如:假设有一组常量存放在头文件中,使用的是const int Arsize = 5; 并在同一个程序的多个文件中使用这个头文件,如果const int的类型术语全局变量,那么在一个程序的不同文件中引入头文件就相当于对于这个外部变量进行多次定义,这是违反C++中的单定义规则的,而链接性为内部的变量则不会造成这种麻烦事。
2.内部链接性还意味着每个文件都有自己的一组常量,而不是所有文件都共享这组常量,每个定义都是其所属文件私有的,这就是能够将常量定义放在头文件中的原因。这样只要两个源代码文件包含同一个头文件,则他们获得同一组常量。
注意:如果你实在希望某个常量的链接性为外部的可以使用extern来对其进行修饰。在这种情况下,必须在所有使用该常量的的文件中使用extern来声明它。
extern const int Arsize = 5;
18.函数和链接性
和变量一样,函数也有链接性,C++不允许在一个函数中定义另一个函数,所以C++中所有函数的存储持续性都是自动为静态的,即在整个程序执行期间都一直存在。在默认情况下,函数的链接性为外部,即可以在文件中共享。可以使用extern关键字说明函数是在另一个文件中定义的,还可以使用static将函数的链接性设置为内部的,使之只能在一个文件中使用(必须在原型和定义同时使用该关键字)。
static int private(double x);
...
static int private(double x)
{
...
}
使用static修饰的函数意味着只在这个文件中可见,还意味着其他文件中可以定义同名的函数。和变量一样,在定义静态函数的文件中,静态函数将覆盖外部定义,因此即使在外部定义了同名的函数,该文件仍使用静态函数。
单定义规则也适用于非内联函数,因此对于每一个非内联函数,程序只能包含一个定义。对于链接性为外部的函数来说,这意味着在多文件程序中,只能由一个文件包含该函数的定义,但使用该函数的每个文件都应该包含其原型。
内敛函数不受该规则约束,这允许程序员将内联函数放在头文件中。这样包含头文件的每个文件都有内联函数的定义,然而C++要求同一个内联函数的定义必须相同。
19.语言链接性
链接程序要求每个不同的函数独有不同的函数名,在C语言中这一点很好实现,因为C语言中不允许出现相同名字的函数,而在C++中允许函数重载,所以会有相同名称的函数出现,所以C++中引入了名称修饰的方法,编译器会给函数重新改名字,为重载函数提供不同的符号名称,这会导致C语言中的名称不再适用于C++之中,为了解决这个问题我们可以在函数原型中指出要使用的约定。
extern "C" void spiff(int); //使用C语言约定
extern void spiff(int); //使用C++约定
extern "C++" void spiff(int); //显式使用C++约定
20.存储方案和动态分配
//使用new分配的内存称为动态内存
float *pt = new float[20];
//由new分配的80个字节(假设float为4字节)将永久保存在内存中
//直到使用delete将其释放
//当包含该声明的语句块执行完毕之后,pt指针将会消失
- 使用new运算符初始化
//在C++98中
int *pt = new int(6); //初始化为6
double* pd = new double(6.77);//初始化为6.77
//在C++11中 列表初始化
int * pa = new int[5] {1,2,3,4,5};
int * pb = new int{6};
- new失败时
new如果找不到请求的内存量,这种情况下会引发异常 std::bad_alloc。 - new:运算符,函数和替换函数
//运算符new和new[]调用函数
//这些函数被称为分配函数,他们位于全局名称空间中
//std::size_t是一个type_def对应合适的整型
void * operator new(std::size_t);
void * operator new[](std::size_t);
//delete delete[]释放函数
void operator delete(void *);
void operator new[](void *);
//例子
//等价
int *pi = new int;
int *pa = new(sizeof(int));
//等价
int *pe = new int[40];
int *pb = new(40*sizeof(int));
//等价
delete pi;
delete(pi);
- 定位new运算符
new运算符常用语在堆中找到一个足以能够满足要求的内存块。new还有一种变体,被称之为定位new运算符,他的作用是让您能够指定要使用的位置。
下面举一个例子,介绍定位new运算符的使用方法:
//要使用定位new的特性,首先要在头文件中包含new
#include<new>
struct chaff
{
char dross[20];
int slag;
};
char buffer1[50];
char buffer2[500];
int main()
{
chaff *p1,*p2;
int *p3,*p4;
//将new运算符用于提供了所需地址的参数
//除了需要指定参数以外,句法和常规的new运算符相同
//常规的new
p1 = new chaff;
p3 = new int[20];
//定位new
//表示从buffer1中分配地址给chaff,从buffer2中分配空间给int型的数组
p2 = new (buffer1) chaff;
p4 = new (buffer2) int[20];
}
- 定位运算符的其他形式
int *p1 = new (buffer) int;
int *pi = new(sizeof(int),buffer);
21.名称空间
- 声明区域
声明区域是可以在其中进行声明的区域。例如,可以在函数外面声明全局变量,对于这种变量,其声明区域为其声明所在的文件,对于函数中声明的变量,其声明区域为其声明所在的代码块。 - 潜在作用域
变量的潜在作用域从其声明点开始,到期声明区域结尾。因此潜在作用域要比声明区域小。 - 新的名称空间特性
通过定义一种新的声明区域来创建命名的名称空间,这样做的目的之一是提供一个声明名称的区域。一个名称空间中的名称不会与另外一个名称空间的相同名称发生冲突,同时允许程序的其他部分使用该名称空间中声明的东西,下面的代码使用了新的关键字namespace创建了两个名称空间:
//使用namespace创建名称空间
namespace Jack{
double pail;
int pal;
struct Well{...};
void fetch();
}
namespace Jill{
double bucket(double n){...}
double fetch();
int pal;
struct Hill{...};
}
1.名称空间可以是全局的,也可以位于另一个名称空间中,但是不能在代码块内。
2.默认情况下,声明的名称链接性为外部的,除非他引用了常量。
3.除了用户声明的空间,还有一种全局名称空间,他对对应于文件级的声明区域,之前说的全局变量,也可以理解成在全局名称空间中。
4.名称空间是开放的,支持将名称加入到已有的名称空间中,而且这种方法很常用。
- 几种访问命名空间中名称的方法
1.使用作用域运算符
访问给定名称空间中的名称,最简单的方法是通过作用域解析运算符::,来使用名称空间来限定该名称。
2.使用using声明指令
using声明可以使特定的标识符可用,由uisng关键字和被限定的名称组成
3.使用using编译指令
using编译可以使全部名称空间可用,由using关键字和名称空间的名称组成
//1.使用作用域运算符
Jack::pail = 12.34;
Jill::Hill mole;
//2.使用using声明指令
char fetch;
//在声明区域外定义的变量使用fetch名称并不会进行报错
int main()
{
using Jill::fetch;
double fetch;
//这条语句是错误的,因为using声明将名称添加到局部声明区域中
//程序对于再对fetch进行定义报错
cin >> fetch;
cin >> ::fetch;
}
//using声明将特定的名称添加到它的所属的声明区域中
//例如上哪的代码就将using声明Jill::fetch将fetch添加到main()定义的声明区域中
//完成该声明后fetch就可代替Jill::fetch
//和其他局部变量一样,fetch会覆盖所有的全局变量
//可以使用 :: 作用域解析运算符来使用全局变量
//如果是在函数外进行using声明,相当于将名称添加到全局名称空间中
//3.使用uisng编译指令
using namespace Jack;
//编译指令使得名称空间中所有的名称都可用
//使用的时候也不再需要作用域解析运算符
#include<iostream>
using namespace Jack;
//上述语句将使Jack名称空间的名称能全局可用
int main()
{
using namespace Jack;
...
}
//在函数中使用using编译指令,使得其中名称在函数内可用
注意:使用using编译和using声明,会增加名称冲突的可能性。也就是说,如果名称空间有Jack以及Jill,在代码中使用作用域解析运算符则不会出现二义性。而如果使用using声明,则系统可能分不清使用的是哪个命名空间中的内容。
Jack::pal = 3;
Jill::pal = 10;
//上述代码系统能很清楚的明白
using Jack::pal;
using Jill::pal;
pal = 4;
//系统会迷茫 你他喵到底用的是哪个名称空间
22.using编译和using声明的比较
使用using编译指令导入一个名称空间中所有的名称和使用多个using声明是不一样的。使用using编译指令更像是大量使用作用域解析运算符。使用using声明时,就好像声明了对应名称,而如果某个名称已经在函数中声明了,则不能再用using声明导入相同的名称。而using编译则进行名称解析,就想在包含using声明和名称空间本省的最小声明区域中声明了名称一样。
namespace Jill{
double bucket(double n){...}
double fetch;
struct Hill{...};
}
char fetch;
int main()
{
using namespace Jill;
Hill Trill;
double water = bucket(2);
double fetch; //允许这种写法
cin >> fetch; //double fetch 刚刚定义的
cin >> ::fetch; //char fetch; 全局变量
cin >> Jill::fetch; //Jill中fetch
}
int foom()
{
Hill top; //不允许 因为他并没有使用名称空间
}
一般来说,使用using声明要比使用using编译指令更安全,这是由于它只是导入了指定名称。如果该名称与局部名称冲突,编译器会发生提示。如果使用using编译导入所用名称,可能包括并不需要的名称,如果与局部名称发生冲突,局部名称会覆盖名称空间版本,但是编译器不会发生警告。这样不安全。
//不建议使用using编译
using namespace std;
//这样并不是很好,因为可能会导致局部变量覆盖名称空间中的变量
//建议使用方式1
std::cout << "Hello World" << std::endl;
//建议使用方式2
using std::cout;
using std::endl;
cout << "Hello World" << endl;
23.嵌套名称空间与传递性
//嵌套名称空间
namespace elements{
namespace fire{
int flame;
}
float water;
}
//使用方式
//using声明
using namespace elements::fire;
//作用域解析运算符
element::fire::flame = 5;
//名称空间中使用uisng编译和using声明
namespace myth{
using Jill::fetch;
using namespace elements;
using std::cin;
using std::cout;
}
//访问方式
//因为using Jill::fetch 所以fetch也是在myth中可以使用如下语句
std::cin >> myth::fetch;
//当然fetch也在Jill中 如下语句也可以
std::cin >> Jill::fetch;
//命名空间的传递性
using namespace myth;
//等价于
using namespace myth;
using namespace elements;
//命名空间取别名
namespace my_very_favorite_things {...};
//下面的语句使mvft成为my_very_favorite_things 的别名
namespace mvft = my_very_favorite_things ;
//命名空间导入函数
namespace myth{
void showPerson(const Person &);
}
using myth::showPerson;
//这里并不加参数,原因是将所有名字为showPerson的函数全部引入到程序中
//因为也许有重载函数,所以根据不同的参数列表,来选对应的函数
24.未命名的名称空间
//未命名名称空间
namespace
{
int ice;
int fire;
}
//未命名名称空间就好像跟着using编译指令一样
//该名称空间中声明的名称潜在作用域为:从声明点一直到声明区域末尾。
//从这个方面看他与全局变量相似
//但是这种名称空间没有命名,因此不能显式的使用using编译或者using声明
//所以不能保证他在其他位置可用
//具体来说就是不能在未命名名称空间所属文件之外的文件使用,就好像链接性为内部的静态变量
static int counts;
//等价于
namespace
{
int counts;
}
25.一些命名空间指导原则
- 使用在已命名名称空间中声明的变量,而不是外部全局变量
- 使用在已命名名称空间中声明的变量,而不是静态全局变量
- 导入名称时,优先使用作用域解析运算符或者using声明的方法
- 对于using声明,首选将其作用域设置为局部而非全局
- 不要在头文件中使用uisng编译指令
- 如果开发一个函数库,应该将其放在名称空间中。