学习了博主的《漫谈继承技术》系列博文之后,相信大家都有所收获吧!这次博主将和大家一起探讨 《灵活而奇特的C++语言特性》 ,主要包括引用、常量(const)、常量表达式(constexpr)、静态(static)、外部(expert)、类型定义(typedef)、类型别名(aliases)、类型转换、作用域解析、统一初始化、显示转换运算符、特性(attribute)、用户自定义文本、头文件、可变长度参数列表和预处理器宏。尽管这个知识清单显得有点凌乱,但是这些话题都是博主经过精心挑选,是容易混淆的语言特性。本篇我们来学习一下作用域解析的观念以及使用场景,增进大家对《灵活而奇特的C++语言特性》的理解。
C++程序猿必须熟悉作用域(scope)的概念。程序中的所有名称,包括变量、函数和类名,都具有某种作用域。可以使用名称空间、函数定义、花括号界定的块和类定义创建作用域。当试图访问某个变量、函数或者类时,首先在最近作用域中查找这个名称,然后是相邻的作用域,依次类推,知道全局作用域。任何不在有名名称空间、函数、花括号界定的块和类中的名称都被认为在全局作用域中。如果在全局作用域中也找不到这个名称,编译器会给出一个未定义符号错误。那什么是作用域运算符呢?作用域运算符就是“::”。下面就让我们一起先结合作用域和可见性来探讨一下作用域运算符的使用。
作用域
作用域是一个标识符在程序正文中有效的区域。C++中标识符的作用域有函数原型作用域、局部作用域(块作用域)、类作用域和命名空间作用域。
函数原型作用域
在函数原型声明时形式参数的作用范围就是函数原型作用域。
如:double area(double radius);//函数声明
标识符radius的作用范围在函数area形参列表的左右括号之间,在程序的其他地方不能引用这个标识符。注意,由于在函数原型的形参列表中起作用的只是形参类型,形参标识符并不起作用,因此形参标示符是允许省去的。但是考虑到程序的可读性,通常还是要在函数原型声明时给出形参标识符。
局部作用域(块作用域)
在函数内部一对大括号中声明的变量的作用域为局部作用域。在函数定义时,形参列表中的参数属于该函数的局部变量,作用域为局部作用域。
类作用域
类的成员函数可以访问类的所有数据成员和成员函数。在类外,类的对象可以访问类的公有成员。公有成员,在类外也不能直接使用,必须通过类的对象来访问。对于类的静态公有成员,可以通过类名加作用域解析运算符来访问或者通过对象来访问。
命名空间作用域
命名空间定义:
namespace 命名空间名
{
命名空间内的各种声明(函数声明、类声明、变量声明……)
}
一个命名空间确定了一个命名空间作用域,凡是在该命名空间之内声明的、不属于前面所述各种作用域的标识符,都属于该命名空间作用域。在该命名空间内部可以直接引用当前命名空间中声明的标识符(前提是标识符的声明在引用之前)。
如果需要引用其他命名空间的标识符(变量、函数、类……),需要使用下面的语法:
①命名空间名::标识符名
如:
std::cout << “hello” << std::endl;//每次都要在使用的标识符前加上命名空间名,使用不方便。
②using 命名空间名::标识符名
如:
using std::cout;//使用std命名空间里的cout标识符
cout << “hello” << std::endl;//要使用的所有标识符都得使用一次using语句(using std::cout;),否则就只能采用第一种方式使用了(如std::endl)。
③using namespace 命名空间名;
如:
using namespace std; //使用std命名空间里的所有标识符
cout << “hello” << endl;//可以直接使用std命名空间里的所有标识符
当两个命名空间中有相同的标示符,并且都使用的using namespace命令,那么在引用标示符时,必须采用“命名空间名::标识符名”引用来加以区分。否则编译时报二义性错误。举个栗子吧。
#include <iostream>
using namespacestd;
//开发者TF的命名空间
namespace TF
{
intnValue = 10;
};
//开发者Jock的命名空间
namespace Jock
{
intnValue = 20;
};
int main(intargc,char**argv)
{
//cout<< "nValue: " << nValue << endl;
cout<< "TF::nValue: "<< TF::nValue << endl;
cout<< "Jock::nValue: "<< Jock::nValue << endl;
return0;
}
程序运行结果:
如果将上例注释去掉,编译器将会报以下错误:
在一个文件中,全局命名空间和匿名命名空间中可以定义同名的标示符。但是如果直接使用同名的标示符,编译时会报二义性错误。举个栗子。
#include <iostream>
using namespacestd;
//匿名命名空间
namespace
{
intnValue = 10;
};
//全局命名空间
int nValue = 50;
int main(intargc,char**argv)
{
//cout<< "nValue: " << nValue << endl;
//通过作用域解析运算符将使用全局命名空间中变量名称
//匿名命名空间中的同名变量被屏蔽
//只要有全局nValue的存在,在匿名命名空间外将永远无法访问匿名命名空间中的nValue
cout<< "TF::nValue: "<< ::nValue << endl;
return0;
}
程序运行结果:
如果将上例注释去掉,编译器将会报以下错误:
命名空间可以嵌套。命名空间分为全局命名空间和匿名命名空间。全局命名空间是默认的命名空间,在显示声明的命名空间之外声明的标识符都在一个全局命名空间中(如:在显示声明的命名空间之外声明的全局变量、全局函数、全局类……)。匿名命名空间是一个需要显示声明的没有名字的命名空间。在一个源文件中没有办法访问其他源文件的匿名命名空间,但可以直接引用本源文件中的所有匿名空间中的所有标示符(前提是命名空间的定义在引用之前)。具有命名空间作用域的变量也称为全局变量。
可见性
程序运行到某一点,能够引用到的标识符,就是该处可见的标识符。
作用域之间的大小关系:命名空间作用域 > 类作用域 > 局部作用域(块作用域)> 函数原型作用域
作用域可见性的一般规则:
①标识符要声明在前,引用在后
②在同一个作用域中,不能声明同名的标识符
③在没有互相包含关系的不同作用域中声明的同名标识符,互不影响
④如果在两个或多个具有包含关系的作用域中声明了同名标识符,则外层标识符在内层不可见。
关于可见性的问题,前面的栗子中已经有涉及到,这里只是做个总结,就不再赘述啦。在本篇博文中,我们一起结合作用域和可见性来探讨一下作用域运算符的使用,相信大家都有所收获。如果想了解更多关于作用域解析的知识,请关注博主的《灵活而奇特的C++语言特性——作用域解析(二)》一篇博文。
如果想了解更多关于C++语言特性相关的知识,请关注博主《灵活而奇特的C++语言特性》系列博文,相信你能够在那里寻找到更多有助你快速成长和加深你对C++语言特性相关的知识和一些特性的理解和掌握。当然,如果你想了解关于继承方面的技术,请关注博主《漫谈继承技术》系列博文。