1.命名空间域的作用域
命名空间域没有生命周期的概念,因为命名空间域里没有任何代码执行。
局部域,全局域,命名空间域,类域影响的是语法编译的查找规则(查找函数,声明,定义),局部域、全局域影响生命周期。
命名空间的作用域比较容易产生误解,下面我来讲解一下:
其中在命名空间里面定义的变量在初始化规则这块和全局变量相同,下面来检验一下:
#include <iostream>
using namespace std;
namespace space
{
int a;
}
using namespace space;
void fun()
{
cout << "fun " << space::a << endl;
space::a = 2;
}
int main()
{
cout << space::a << endl;
fun();
cout << space::a << endl;
return 0;
}
运行结果如图:
可以看到int a的初始化也遵循全局变量定义的规则,即默认初始化为0
作用域:命名空间域相当于定义了一个新的作用域,有且仅当主动调用它时,才能够访问和使用它。
在理解这一点上,我们可以通过熟悉的函数来理解。在任何函数里面定义的变量,你在函数外面都访问不了,因为这些变量的作用域都仅限于函数的那个大括号。namespace大括号定义的变量也是如此。
但是区别在于函数里的变量只有在进入函数后在函数里面访问,而namespace定义的变量可以在任何地方调用。调用时加的::是域作用限定符,是在namespace这个新的作用域内部对它进行访问。本质上和访问函数内部变量一致。
2.命名空间域的访问规则
一般情况下,命名空间域不会被访问,在使用using被展开后会在先局部再全局访问后,访问这个命名空间。
但是要注意局部是指在程序调用那部分局部域,局部域也有可能是一个命名空间域,下面看一段代码:
#include <iostream>
using namespace std;
int a = 0;
namespace space
{
int a = 60;
void fun()
{
cout << ::a << endl;
cout << a << endl;
}
}
int main()
{
space::fun();
return 0;
}
执行结果是:
调用cout << a << endl;时就会先访问局部域,前面已经说过命名空间域是一个新的作用域,所以这里访问的局部域就是这个命名空间域。这里极容易混淆。
在任何地方(可能是全局的函数,可能是命名空间域里定义的函数),在先局部再全局访问后,都会访问using的命名空间,下面来看一段代码:
#include <iostream>
using namespace std;
int a = 0;
namespace space2
{
int b = 20;
}
using namespace space2;
namespace space1
{
int a = 60;
void fun()
{
cout << ::a << endl;
cout << a << endl;
cout << "b == " << b;
}
}
int main()
{
space1::fun();
return 0;
}
运行结果:
注意:上面所说的“先局部再全局访问后,访问这个命名空间”中全局和命名空间的访问同级。也就是说如果全局和已被展开的命名空间中有命名冲突,会报错!
3.namespace的合并和使用
在不同文件中,如果定义的最外层namespace(namespace可以嵌套)名字相同,则最终运行程序时会合并,当然,如果是在同一个文件里定义了相同名字的namespace,也会合并。
使用的时候using可以局部展开,也可以将整个命名空间展开。但是在大型的项目中一般用局部展开,因为如果当同时展开多个命名空间时,根据访问规则,如果展开的这些命名空间有重名的情况,就会报错。
在使用namespace定义函数时,要注意声明和定义处都要使用namespace
否则会报错,因为链接时找不到这个函数
我们平时使用的<iostream>最外层就用了namespace std来包装,这也是为什么我们使用流插入cin和流提取cout时要用using namespace std或using std::cout;等
4.缺省参数的使用注意事项
当使用函数时遇到不确定参数时,可以使用缺省参数做默认值。
如果要使用多个文件,用到函数声明,缺省参数只能在声明处使用才生效且在定义时不能使用,就算定义的默认值和声明的默认值相同,因为语法检查时会被识别为重定义。
且如果要使用缺省参数必须在声明处使用,因为语法检查导致编译都无法通过,不会链接。报错会显示不能只接收一个参数
缺省参数需要从右向左给,不然会报错且会产生歧义
5.构成函数重载与使用同名函数之间的关系
函数重载实现条件:同一作用域、传参的类型个数、顺序、种类不同(返回值、缺省参数等不在实现条件内)
函数重载实现的根本:调用函数时类型修饰函数名
我们需要注意的是,两个同名函数如果满足传参的类型个数、顺序、种类不同,那么它们一定都能在using之后正常使用,即使它们不构成函数重载,下面举个例子:
#include <iostream>
using namespace std;
namespace space1
{
void fun(int a, float b)
{
cout << "space1" << endl;
}
}
namespace space2
{
void fun(float a, int b)
{
cout << "space2" << endl;
}
}
using namespace space1;
using namespace space2;
int main()
{
fun(1, 2.0f);
return 0;
}
该程序能正常运行,结果是:
我们需要知道,函数重载的条件是定义的两个函数在同一作用域,但这两个函数在两个namespace里面定义,一定不构成重载,但展开后却能正常使用。这是因为调用函数时函数名会进行修饰,所以就算同名,只要参数传递不引起歧义,也能正常使用。
所以两个同名函数就算不构成重载,在传参没有歧义下也能使用,函数重载可以说是使用同名函数的一种特殊情况。
在参数完全一致的情况下,同名函数就会产生歧义了:
这种情况下必须指定作用域才能正常使用
这种写法跟函数重载肯定也没什么关系了。
6.函数的调用
调用函数时,函数地址是执行第一句汇编指令的地址,要有定义才有这个地址。对于声明和定义分离的函数来说,程序是在.o(.obj)链接时去找函数
7.同名函数调用歧义
函数名、参数完全一致的函数调用歧义,还有一些调用歧义比较容易出错,这里分享一下:
对于整型家族,它们之间可以相互转换,这种情况下要注意参数一一对应。
还有一种是当存在缺省参数时,传参个数导致的歧义: