C++ 关键字 :using

        

        

        在 C++ 庞大语法体系中, using 关键字十分的灵活多用,它可不简单。

        除了常规的引入命名空间之外,它还可用于引入枚举类型枚举器、定义常规类型别名、模板类型别名等。在定义常规类型别名方面与C语言中的typedef、#define与之相似,但又不尽相同。

        简单归纳起来, using 的主要作用是 “引入” “定义别名”。

        

引入

引入命名空间       

using namespace ns-name ;

        引入命名空间是最常见也最好理解的用法,如:

#include <iostream>
using namespace std;int main() {cout << "Hello World!" << endl;// std::cout << "Hello World!" << endl;return 0;
}

         在这个示例中,我们使用 using namespace 将命名空间 std 的全部成员引入到当前命名空间(根空间)中。

        这样做的好处是,代码变得简洁,有效作用域内,都可以省略命名空间,直接访问。

        有效作用域是指从 using 声明点开始,直到包含该using声明的作用域结尾。

 using 关键字可以引入整个命名空间或者特定的命名空间成员。

        该怎样直观的理解这句话呢 ,看如下代码:

namespace
{int i; // 定义 ::(unique)::i
}void f()
{i++;   //  定义 ::(unique)::i
}namespace A
{namespace{int i;        // A::(unique)::iint j;        // A::(unique)::j}void g() { i++; } // A::(unique)::i++
}using namespace A; //  将 A 中的所有名称引入全局名称空间void h()
{i++;    // error: ::(unique)::i and ::A::(unique)::i 都在作用域中A::i++; // ok, 递增 ::A::(unique)::ij++;    // ok, 递增 ::A::(unique)::j
}

        由例子可见,因为引入了命名空间A,其下的所有成员都将暴露出来。变量 i  在两个命名空间中都有声明,因此产生了冲突。

        因此,在.h头文件中,一般不应该使用using声明,避免产生名字冲突。

        方便的同时,也带来了问题,如命名空间污染。        

#include <iostream>
#include <utility>
using namespace std;void move(int &&x) {cout << "Move: " << x << endl;
}int main() {move(10);  // 调用 void move(int &&x);std::move(10);  // 调用 std::movereturn 0;
}

        在这个示例中 move(10) 会调用本源文件的 move,也许这并不是你的预期。当然,我们依然可以使用 std::move 显式调用。

        💡 命名空间污染是十分隐晦和危险的,特别是当多个命名空间中有相同名称的成员时,需要特别的注意。

        另外,这里还有一个“后续扩展成员不可见的”规则,即在 using 后访问的命名空间,无法访问到后续扩展命名空间时新增的成员,有说起来点绕,看示例:        

namespace A
{int i;
}int main()
{A::i++; // okA::j++; // error
}namespace A
{int j;
}

        i 在 using 之前就定义了,可以正常访问, j 在using、访问之后定义,不可见。

        由此规则,可能会衍生一个潜在的混淆: 

namespace A
{void f(int) {}; // 定义一个接受int参数的函数f
}
using A::f; // 使得全局命名空间中的::f成为A::f(int)的同义词// 扩展命名空间A
namespace A
{void f(char) {}; // 定义一个接受char参数的函数f,不改变全局命名空间中::f的含义
}int main()
{f('a');     // 调用 void f(int),参数隐式转换using A::f; // 重新引入完整的命名空间Af('a');     // 调用 void f(char) 
}

        一个例外是函数模板特化:如果引入的是类模板,后续添加的特化版本是可见的,因为它们的查找是通过主模板进行的。

引入命名空间的实体

       

using ns-name :: member-name ;

        相对于引入整个命名空间,引入命名空间内的实体操作更为细腻,如:

#include <iostream>
using std::cin;
using std::cout;
using std::endl;int main() {cout << "Hello World!" << endl;// std::cout << "Hello World!" << endl;return 0;
}

        在这个示例中,我们只引入了 std命名空间中的cin、cout、endl 实体成员。当然这也仅仅是降低了命名空间污染的可能性,最终,需要我们自己审慎把握。

💡 从 C++ 17 开始,允许使用 分隔,同时引入多个命名空间成员。

using std::cin, std::cout, std::endl;

引入类的继承体系

        

using classscope::member;

       首先,在类的继承体系中,using 使得派生类显式地重新暴露基类的被隐藏或覆盖的成员函数,使得基类的成员函数在派生类的作用域内重新可用。


class Base {
public:virtual void foo() { /*...*/ }void bar() {}
};class Derived : public Base {
public:void foo() override { /*...*/ } // 重写foousing Base::foo; // 显式再次引入Base的foo版本void bar() {}using Base::bar;
};int main()
{Derived d;d.foo();		// 调用 Derived::foo();d.Base::foo();	// 调用被覆盖的	Base::foo();d.bar();		// 调用 Derived::bar();d.Base::bar();	// 调用被隐藏的 Base::bar();
}

        其次,在子类私有继承时,可以改变成员的可见性,如:

class Base {
public:void foo() {}void bar() {}
};class Derived : private Base {
public:using Base::foo;    // 显式引入Base的foo//using Base::bar;  // 未引入Base的bar
};int main()
{Derived d;d.foo();		// Base::foo() 可访问d.bar();		// Base::bar() 可访问
}

继承构造函数 (C++11)

派生类可以使用 using 声明从直接基类继承构造函数

        在C++11之前,如果基类有一个构造函数需要参数,那么在派生类中必须显式地调用这个构造函数,当基类的构造函数参数很多、版本很多的时候,这将是一件繁重、容易出错的工作。

        现在,使用 using 即可全部引入。

#include <iostream>
using namespace std;class Base
{
public:Base() { cout << "Base()" << endl; }Base(const Base& other) { cout << "Base(Base&)" << endl; }explicit Base(int i) : num(i) { cout << "Base(int)" << endl; }explicit Base(char c) : letter(c) { cout << "Base(char)" << endl; }private:int num;char letter;
};class Derived : Base
{
public:// 继承基类所有构造函数using Base::Base;private:// 不能从构造函数初始化成员,需要手动初始化int newMember{ 0 };
};int main()
{cout << "Derived d1(5) calls: ";Derived d1(5);cout << "Derived d1('c') calls: ";Derived d2('c');cout << "Derived d3 = d2 calls: " ;Derived d3 = d2;cout << "Derived d4 calls: ";Derived d4;
}

观察输出

Derived d1(5) calls: Base(int)
Derived d1('c') calls: Base(char)
Derived d3 = d2 calls: Base(Base&)
Derived d4 calls: Base()

         这种方式大幅减轻了继承基类构造函数的书写。另外,由于这是隐式声明继承的,假设一个继承构造函数不被相关的代码使用,编译器不会为之产生真正的函数代码,更加节省目标代码空间。

        缺憾就是不能方便的同时初始化成员,需要手动初始化。

💡 只能引入直接基类的构造函数,例如 class DD : public Derived { using Base::Base; } 是非法的。

引入枚举类型(C++20

        可以通过 using 引入到枚举类型枚举器名称,效果类似于每个枚举器定义在了作用域中,如

enum class fruit { orange, apple };struct S
{using enum fruit; // OK: 将 orange 和 apple 引入 S
};void f()
{S s;s.orange;  // OK: fruit::orangeS::orange; // OK: fruit::orange
}

         可以通过 using多次引入多个枚举类型,但如果枚举器名称有重复将产生冲突:

enum class fruit { orange, apple };
enum class color { red, orange };void f()
{using enum fruit;    // OK// using enum color; // error: color::orange and fruit::orange conflict
}

定义别名

定义类型别名

using alias = typename;

       在 C/C++ 中,定义类型别名的方法有 #define、typedefusing 多种方式。

        #define 是宏定义关键字,用途广法,尤其是 C 中,不局限于类型别名定义。

  在C++中,推荐使用 using 来定义类型别名,因为它更符合C++的现代编程风格。

        首先,直观的感受下三者在定义类型别名时的形式,typedef 的方式和其他两者顺序相反:

#define MY_INT inttypedef int MyInt;using MyInt = int;

         似乎三者没有明显区别,当然由于 #define 宏定义的本质,和后两者是可以明显区分开的。那么,typedef 和 using 的区别在哪里呢?

   首先,使用typedef定义的别名和使用using定义的别名在语义上是等效的。

   然后,我们看下一个定义类型别名的例子:

typedef std::unique_ptr<std::unordered_map<std::string, std::string>> UPtrMapSS;using UPtrMap = std::unique_ptr<std::unordered_map<std::string, std::string>>;

         using 的方式是不是更为直观易懂?

        当然,也许你很习惯 typedef 的方式,这个示例还不足以让你倒向 using ,那我们继续。

定义模板类型别名

        typedef 是不支持定义模板类型别名的,例如

template <typename T>
typedef map<int, T> type;	// error, 语法错误

        要实现这一点,需要一个类辅助,这样就很麻烦了。using 可以做到:

template <typename T>
using mymap = map<int, T>;

       typedef 为什么不可以呢,在 n1449 中有这样一段话:

"we specifically avoid the term “typedef template” and introduce the new syntax involving the pair “using” and “=” to help avoid confusion: we are not defining any types here, we are introducing a synonym (i.e. alias) for an abstraction of a type-id (i.e. type expression) involving template parameters."

        所以,这事标准委员会的观点与选择,涉及到模板类型时,我们必须使用 using 
 

总结

        using  关键字为程序书写、定义别名带来了极大的方便,使得代码更为简练,善用 using  ,使得代码更为简洁易懂。

        在 C++11 后续的语言标准中又增加了更多的适用场景如,变量模板(C++14)、类模板的默认实参和推导实参(C++17)、委托转隶构造函数(使用using)(C++20),最后以一段  特性大杂烩作为结束

// 类型别名声明 (C++11)
using IntVector = std::vector<int>;// 模板别名 (C++11)
template <typename T>
using Pair = std::pair<T, T>;// 委托构造函数 (C++11)
class Foo {
public:using FooBase::FooBase; // 委托给基类的构造函数
};// 继承构造函数 (C++11)
class Base {
public:Base(int i) {}
};class Derived : public Base {
public:using Base::Base; // 使用基类的构造函数
};// 构造函数的显式使用声明 (C++11)
struct A {A(int) {}
};struct B : A {using A::A; // 显式使用A的构造函数
};// 变量模板 (C++14)
template<typename T>
T template_var = 10;// 类模板的默认实参和推导实参 (C++17)
template <typename T = int>
struct C {};// 委托转隶构造函数 (C++20)
struct D : B {using B::B; // 委托给另一个构造函数
};

 


参考

C++ keyword: using - cppreference.com

Constructors (C++) | Microsoft Learn

http://isocpp.open-std.org/JTC1/SC22/WG21/docs/papers/2003/n1449.pdf

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/700651.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

wangEditor富文本编辑器与layui图片上传

记录&#xff1a;js 显示默认的wangEditor富文本编辑器内容和图片 <style>body {background-color: #ffffff;}.layui-form-select dl{z-index:100000;} </style> <div class"layui-form layuimini-form"><div class"layui-form-item"…

求学生平均成绩(C语言)

一、运行结果&#xff1b; 二、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>//声明平均数函数average; float average(float score[10]);int main() {//初始化变量值&#xff1b;float score[10], aver;int i 0;//填充数组&#xff1b;pr…

数据库系统概论(超详解!!!)第八节 数据库设计

1.数据库设计概述 数据库设计是指对于一个给定的应用环境&#xff0c;构造&#xff08;设计&#xff09;优化的数据库逻辑模式和物理结构&#xff0c;并据此建立数据库及其应用系统&#xff0c;使之能够有效地存储和管理数据&#xff0c;满足各种用户的应用需求&#xff0c;包…

运维别卷系列 - 云原生监控平台 之 01.prometheus 入门和部署

文章目录 [toc]什么是 PrometheusPrometheus 架构及其一些生态系统组件Prometheus 的工作模式Prometheus 的适用场景Prometheus 的不适用场景Prometheus 词汇表 Prometheus 启动参数Prometheus 配置文件通用占位符定义配置文件示例解释服务发现 Prometheus 部署创建 namespace创…

System V IPC(进程间通信)机制详解

文章目录 一、引言二、System V IPC的基本概念1、IPC结构的引入2、IPC标识符&#xff08;IPC ID&#xff09;3、S ystem V的优缺点 三、共享内存&#xff08;Shared Memory&#xff09;1、共享内存的基本概念2、共享内存的创建&#xff08;shmget&#xff09;3、共享内存的附加…

51. UE5 RPG 自定义FGameplayEffectContext

我们期望能够通过FGameplayEffectContext将此次技能造成的伤害是否触发格挡和暴击的参数传递到AttributeSet中&#xff0c;所以需要实现自定义一个FGameplayEffectContext类&#xff0c;来增加对应的配置。 创建自定义类文件 首先在Public目录上右键&#xff0c;选择添加一个…

考研数学|李林《880》做不动,怎么办!?看这一篇!

在考研数学的备考过程中&#xff0c;遇到难题是很常见的情况&#xff0c;尤其是当你尝试解决李林880习题集中的问题时。他以其难度和深度著称&#xff0c;旨在帮助考生深入理解数学分析的复杂概念。 如果你在解题过程中感到困难&#xff0c;这并不是你个人的问题&#xff0c;而…

Git 的原理与使用(中)

Git 的原理与使用&#xff08;上&#xff09;中介绍了Git初识&#xff0c;Git的安装与初始化以及工作区、暂存区、版本库相关的概念与操作&#xff0c;本文接着上篇的内容&#xff0c;继续深入介绍Git在的分支管理与远程操作方面的应用。 目录 五、分支管理 1.理解分支 2.创…

基于spingboot,vue线上辅导班系统

目录 项目介绍 图片展示 运行环境 获取方式 项目介绍 权限划分&#xff1a;用户&#xff0c;管理员 具有前后台展示&#xff0c;前台供用户使用&#xff1b;用户具有自己的后台&#xff0c;查看自己的老师课程等&#xff1b;管理员具有最大的权限后台。 用户&#xff1a…

Jmeter接口测试和性能测试

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 目前最新版本发展到5.0版本&#xff0c;需要Java7以上版本环境&#xff0c;下载解压目录后&…

数据结构之二叉树详解[1]

在前面我们介绍了堆和二叉树的基本概念后&#xff0c;本篇文章将带领大家深入学习链式二叉树。 1.预备知识 2.二叉树结点的创建 3.二叉树的遍历 3.1前序遍历 3.2中序遍历 3.3 后序遍历 4.统计二叉树的结点个数 5.二叉树叶子结点的个数 6.二叉树第k层的结点个数 7.总结 …

自动驾驶占据感知的综述:信息融合视角

24年5月香港理工的论文“A Survey on Occupancy Perception for Autonomous Driving: The Information Fusion Perspective“。 3D 占据感知技术旨在观察和理解自动驾驶车辆的密集 3D 环境。该技术凭借其全面的感知能力&#xff0c;正在成为自动驾驶感知系统的发展趋势&#x…