Copyright 2021 by Qiujun Lv,All rights reserved.
文章仅供交流、学习之用;转载需保留本版权声明并注明原文作者、出处及链接;
其他用途,皆需另行授权。
publish time : 2021-02-03T23:30:00+08:00
自c++11开始“using”关键字的语义被扩展为3种,分别是:
使用指令(using-directive):c++11以前就有的;
使用声明(using-declaration):c++11新增的;
别名声明(alias-declaration):c++11新增的;
本文中部分案例涉及标识符隐藏问题,如有需要,欢迎浏览我的另一篇博文《c++标识符的隐藏与歧义现象》。
使用指令(using-directive):将指定命名空间中的全部标识符导入当前作用域,该语义及语法在c++11中没有改变;
使用声明(using-declaration):将指定标识符在当前作用域中重新声明;需要注意的是:
与使用指令不同,使用声明视同在当前作用域中直接声明,所以如果当前作用域中存在同名标识符将因命名冲突(name conflict)而无法通过编译(重载函数标识符除外);也可以将此处的命名冲突理解为重复声明(redeclaration);
若使用声明的目标是重载函数的标识符,将重新声明全部的重载函数而不是仅声明其中一个;
别名声明(alias-declaration):为目标类型取别名;含义与typedef相同,但是对于需要保留范式的类型,使用using比typedef更方便。c++11中,别名声明的功能涵盖了typedef。
// @brief 使用指令-基本语法示例
// @author Qiujun Lv
// @date 2021-02-03T22:30:00+08:00
// @standard c++11
// @compile 代码的测试编译器列表:X86-64 gcc 10.1、X86-64 clang 11.0.0、
// X86-64 msvc v19.27、X86 msvc v19.27、mingw 73_64、mingw 73_32、
// ARM64 gcc 6.4、ARM gcc 6.4、 ARM64 gcc 6.3.0(linux)
#include <iostream>
int main(){
//将命名空间std中的全部声明/定义导入当前作用域
using namespace std ;
//命名空间“std”中的“std::cout”、“std::endl” 都可以直接使用了
cout << 123 << endl ;
return 0;
}
// @brief 使用声明-例1-基本语法
// @author Qiujun Lv
// @date 2021-02-03T23:30:00+08:00
// @standard c++11
// @compile 代码的测试编译器列表:X86-64 gcc 10.1、X86-64 clang 11.0.0、
// X86-64 msvc v19.27、X86 msvc v19.27、mingw 73_64、mingw 73_32、
// ARM64 gcc 6.4、ARM gcc 6.4、 ARM64 gcc 6.3.0(linux)
#include <iostream>
int main(){
//将命名空间std中的cout在当前作用域中重新声明
using std::cout ;
//命名空间“std”中的“std::cout”可以直接使用了
//但“std”中“std::endl” 及其他标识符仍然需要通过作用域限定符访问
cout << 123 << std::endl ;
return 0;
}
由于使用声明视同在当前作用域中直接声明,所以存在引发命名冲突的可能性。相对地,使用指令不会引发命名冲突,若有同名只会优先级低的标识符被隐藏。
// @brief 使用声明-例2-引发命名冲突
// @author Qiujun Lv
// @date 2021-02-03T23:30:00+08:00
// @standard c++11
// @compile 该代码需要修改才能通过编译
// 代码的测试编译器列表:X86-64 gcc 10.1、X86-64 clang 11.0.0、
// X86-64 msvc v19.27、X86 msvc v19.27、mingw 73_64、mingw 73_32、
// ARM64 gcc 6.4、ARM gcc 6.4、 ARM64 gcc 6.3.0(linux)
#include <iostream>
namespace package{
std::string var = "package::var" ;
}
int main(){
//将命名空间package中的var在当前作用域中重新声明
using package::var ;
//编译失败:命名冲突,声明与使用声明引入的var发生冲突
std::string var = "package::var" ;
//解决方案A:为局部作用域或命名空间作用域中的var换个名字
//解决方案B: 以使用指令导入命名空间,并以作用域限定符加以区分
std::cout << package::var << std::endl ;
return 0;
}
还是因为使用声明视同在当前作用域中直接声明,在下面的例子中标识符的可见性被提升了。
// @brief 使用声明-例3-可见性提升
// 本例演示通过“使用声明”将当前位置可以观察到的元素开放
// @author Qiujun Lv
// @date 2021-02-03T23:30:00+08:00
// @standard c++11
// @compile 代码的测试编译器列表:X86-64 gcc 10.1、X86-64 clang 11.0.0、
// X86-64 msvc v19.27、X86 msvc v19.27、mingw 73_64、mingw 73_32、
// ARM64 gcc 6.4、ARM gcc 6.4、 ARM64 gcc 6.3.0(linux)
#include <iostream>
class ClassA{
protected://若此处使用private关键字,子类的using就行不通了,因为使用声明的前提是在当前位置可见
int var ;
ClassA () :var(0){ }
void foo( ){ printf("ClassA::foo( ) \n"); }
};
//注意:此处为私有继承,正常情况下生成ClassB的对象后是无法通过对象访问ClassA的变量和方法的。
//此处通过“使用声明”实现了访问权的开放。
class ClassB : private ClassA{
public:
using ClassA::var;//使用声明:父类变量的可见性被提升了
using ClassA::foo;//使用声明:父类函数的可见性被提升了
};
int main()
{
ClassB obj_b ;
obj_b.foo();//访问被声明的函数
std::cout << obj_b.var/*访问被声明的变量*/ << std::endl;
return 0;
}
在c11之前,跨类型的函数重载是麻烦的,因为子类中的函数标识符会将基类中的全部同名函数标识符隐藏,下面的例子演示了该问题在c11中的解决方案。
// @brief 使用声明-例4-跨类型函数重载
// @author Qiujun Lv
// @date 2021-02-03T23:30:00+08:00
// @standard c++11
// @compile 代码的测试编译器列表:X86-64 gcc 10.1、X86-64 clang 11.0.0、
// X86-64 msvc v19.27、X86 msvc v19.27、mingw 73_64、mingw 73_32、
// ARM64 gcc 6.4、ARM gcc 6.4、 ARM64 gcc 6.3.0(linux)
#include <iostream>
class ClassB;
class ClassA{
public:
virtual void foo(){ printf("ClassA::foo() \n"); }
void foo(int){ printf("ClassA::foo(int) \n"); }
protected:
//下行定义的函数是保护的,但在子类的“使用声明”行为导致可见性被提高了
void foo(int,int){ printf("ClassA::foo(int,int) \n"); }
private:
//若存在私有重载函数,则必须配合friend关键字,否则子类中的使用声明无法通过编译,
//因为对于using-declaration来说,声明的目标必须是在当前作用域可见的。
friend ClassB;
void foo(int,int,int){ printf("ClassA::foo(int,int,int) \n"); }
};
class ClassB:public ClassA{
public:
void foo() override { printf("ClassB::foo() \n"); }
//使用声明,下面的语句协助实现父子两类间的函数重载
//尽管在父类中存在的重载函数多于一个,但仅需声明一次
using ClassA::foo;
};
int main()
{
ClassB b;
b.foo( );//ClassB::foo()
b.foo( 1 );//ClassA::foo(int)
b.foo( 1,2 );//ClassA::foo(int,int)
b.foo( 1,2,3 );//ClassB::foo(int,int,int)
}
使用声明与使用指令有以下2点区别:
(1) 导入对象不同:使用指令导入的是整个命名空间的内容,而使用声明只导入指定的内容;
(2) 结果优先级不同:使用指令导入的内容优先级比当前作用域内固有的内容要低,而使用声明具有与当前作用域固有内容相同的优先级,使用声明行为视同在本作用域内声明。
使用指令可以避免命名冲突,但可能会不必要地扩大导入的范围;使用声明可以只导入必要声明/定义,但可能引发冲突;使用时须稍加斟酌。
为类型“std::int64_t”取了一个别名——“int64”:
using int64 = std::int64_t;
这等价于:
typedef std::int64_t int64;
又比如一些类型书写冗长的情况:
using seconds = std::chrono::seconds;
为函数指针类型取别名:
#include <iostream>
// @brief 一个用于演示的函数
int plus(int one,int another){
return one+another;
}
int main(){
//声明函数指针的类型别名,等价于:typedef int (*fun_ptr_t) (int,int);
using fun_ptr_t = int (*) (int,int);
//定义指针
fun_ptr_t fun_ptr = plus ;
//通过指针调用函数
std::cout<< fun_ptr(3,5) ;
return 0 ;
}
显而易见地,typedef的语法更具备一致性,与声明高度相似;而别名声明的语法可读性更强、更贴近常人的思维;请感受以下两行代码:
typedef int (*fun_ptr_t) (int,int);//一致性
using fun_ptr_t = int (*) (int,int);//可读性
还有模板类型的别名:
//等价于typedef std::map<int,int> MapOfInt2Int;
using MapOfInt2Int = std::map<int,int> ;
值得注意的是,此处无论使用using还是typedef,MapOfInt2Int的模板参数都已经被固定为<int,int>了;如果希望保留全部或部分模板特性,则需要使用“模板别名”。
使用“模板别名”可以在为类型取别名的同时保留全部或部分模板特性:
#include <iostream>
#include <vector>
#include <string>
//模板别名
template<typename element_t>
using List = std::vector<element_t> ;
int main(){
//生成&装载
List<std::string> list;
list.push_back(std::string("One"));
list.push_back(std::string("Two"));
//遍历
for( auto& element : list ) std::cout << element << std::endl;
return 0 ;
}
c++11之前,如果要使用typedef实现同样的效果,则会略显繁琐:
#include <iostream>
#include <vector>
#include <string>
//typedef是不支持模板的,需要借助一个模板结构体/类对typedef语句进行包装
template<typename element_t>
struct TemplateWrapper{
typedef std::vector<element_t> List ;
};
int main(){
//生成&装载
TemplateWrapper<std::string>::List list;
list.push_back(std::string("One"));
list.push_back(std::string("Two"));
//遍历
for( auto& element : list ) std::cout << element << std::endl;
return 0 ;
}
本文简单讲解了c++11中using的三种语义及语法,如有不够详尽乃至谬误之处,还望不吝斧正。
语义和语法只是基础,实践过程中的语用才是关键,就好像我明明掌握中文却写不出红楼梦一样。
最后,感谢阅读,江湖再会。
本文章使用limfx的vscode插件快速发布