Last Updated on 2020年7月22日
名字
- 在C++中,有各种各样的名字,变量名和函数名(可调用对象名)是最重要的两个
- 命名空间:形如
namespace XX{}
的作用域.- 我们的代码默认都位于全局空间内,该空间直接通过
::
访问 - 可以定义匿名的namespace,其效果和inline命名空间类似,匿名的namespace是文件私有的,在文件内部共享,在文件之间独立.在C++中,该特性设计用于替代C风格的全局
static
型变量.
- 我们的代码默认都位于全局空间内,该空间直接通过
- 作用域:用{}分割的各种区域都是作用域,常见的如class,namespace,函数体,代码块等.作用域是可以嵌套的.
- 嵌套的命名空间需要注意名字隐藏的问题,例如,对于
namespace A{#include <vector>}
- 相当于在
::A
内定义了一个std
命名空间,::A::std
会将外部的::std
名字隐藏 - 当在
A
内使用std::vector<>
时,实际使用的是::A::std::vector<>
,由于::A::std
内不存在vector
的定义,所以会导致链接错误. - 命名空间内不使用
#include
是一种很好的风格.
- 相当于在
名字查找
- 当我们使用一个名字时,编译器就会向上查找名字的声明语句.变量名的查找和函数名的查找有一定区别.
- 变量名查找:最终仅会确定唯一的对象.
- 函数名查找:最终会确定候选函数集合,主要是为后续的函数重载服务.
- 函数名和变量名都是名字,彼此可以相互hide.
- 函数体内的名字搜索是顺着域说明符向外的.例如,
int A::B::f (){return i;}
,查找i
时, 先从f()
的局部开始,再到B->A->::
的顺序
变量名字查找流程
- 从当前作用域开始,逐层向上查找名字.
- 找到名字后就完成查找,否则失败.
- 注:名字查找成功后,编译器材开始进行类型检查,即判断找到的名字是否符合使用处的规则.
函数名字查找流程
- 函数名字
fun
查找的实际是确定搜索域,搜索域内的同名可调用对象都被加入候选函数集. - 搜索域是一个常规搜索域和若干实参类型搜索域的并集.
- 常规搜索域确定:从当前作用域开始,逐层向上查找名字声明,名字首次出现的作用域就是常规搜索域
- 对于函数调用的每个实参
arg_i
,其类型decltype(arg_i)
及其所有基类所在的作用域都加入实参类型搜索域
- 搜索域内所有(无论先后)名为
fun
的可调用对象都将纳入重载候选列表.重载候选列表确定后,才开始重载匹配.
重载匹配规则
假设已经确定了候选函数集,那么,在候选函数集中选择函数时,依照以下规律进行重载匹配.
- 函数重载的基础是
传入参数
的类型,和返回类型无关.对于非引用的形参,const不作为重载的依据. - 首先根据实参类型排除不能产生调用的函数,得到一个可行函数列表.
- 可行函数中选择最优匹配,具体的判定流程.
-
- 类型转换越少越好,最少的那个就是最优匹配.
-
- 如果几个函数一样好:
- 有多个普通函数 -> 匹配失败
- 一个普通函数+模板 -> 普通的函数优先于模板
- 多个匹配 -> 特化的模板优先于通用的模板. (模板特化的比较规则不太常用,在使用时应当慎重)
-
- 匹配失败.
-
- 注意:
- 仅函数/数组向指针的转换是精确匹配,只要类型不同,都被视作类型转换,例如:派生类指针到基类指针,非const对象绑定到到const &,,都是类型转换
- 类型转换彼此没有优先级,从int 到uint并不比从int 到double要优秀.
- 注意: 很多系统中,整数最多提升到
unsigned int
,这意味着以long
为形参的函数很可能不会被重载系统匹配到.
using XX::name.
- 常规作用:将某个名字引入当前作用域.
- 引入的名字位于当前作用域,可以隐藏外层作用域的名字,且不能与当前作用域已有的名字冲突.
- 对于函数,引入的名字将参与当前作用域的重载.
- 在类定义中,将基类名字引入派生类时有不同的特性
- using仅可用于引入基类中的名字
- 这种场合下引入的函数名可以部分被派生类重写/override,而非重定义冲突.
using namespace XX
using namespace X
:对当前作用域而言,相当于将名字空间X
内的名字全部引入某个外层作用域.- 从
using namepsace X
所在语句逐层向外,当某个作用域包含X时,将名字插入这个作用域. using namespace
引入的名字在外层作用域,所以不会和当前作用域的名字产生冲突,我们甚至可以在后续代码中隐藏引入的名子.(using引入的名字也不会和插入位置的名字重定义,但是需要在使用时手动消除歧义.)using namespace X
引入的函数名若被查找到,是在逻辑上的插入位置查找到的,而非当前作用域.因此,当前作用域定义的函数不会和using namespace
引入的函数形成重载.
- 从
using namespace
的有效区间仍仅限于所在的作用域.- 在类定义内不能使用
using namespace
namespace Top{
namespace A{
int i=0;
}//namespace A
double i=1;
namespace B{
void fun(){
using namespace A;//将A内的名字引入了Top作用域,此时不会产生重定义错误.
++i;//错误,因为使用了i而触发二义性错误
++Top::A::i;//正确,使用Top::A::i;
++Top::i;//正确,使用Top::i
void i();//正确,隐藏之前所有的i;
}
float i=2;//定义B内的i.
}// namespace B
}// namespace Top