using:C++里的"大自然搬运工",啥都能给你搬过来!
我们刚开始写代码时总会顺手敲句 using namespace std;,结果被老司机怒怼:"头文件里别用这个!污染全局命名空间!" 然后默默改成 using std::cout;,心里嘀咕:"这俩到底有啥区别?"
后来你在类里看到这样的代码:
c++
class Derived : public Base
{
public:
using Base::func; // 这又是干啥的?
void func(int); // 自己的重载
};
再后来,你发现有人这么写:
c++
using IntPtr = int*; // 这跟 typedef int* IntPtr; 有啥不一样?
你盯着这个 using 关键字,陷入了沉思:
"这玩意儿到底有多少种用法?怎么一会儿搬命名空间,一会儿搬类型,一会儿还能搬基类的函数?它是不是 C++ 里的'神偷'啊?"
没错,你可以把 using 想象成一个 "大自然的搬运工":
- 它能从命名空间这个大仓库里,帮你把需要的"货物"(名字)搬到你面前(using std::cout)。
- 它也能帮你把复杂的类型起个顺口的外号,比 typedef 更亲民(using IntPtr = int*)。
- 它甚至能在继承时,偷偷把基类的成员函数"偷"到派生类里来(using Base::func),让你能在派生类里重载或使用它们。
- 到了 C++20,这个搬运工又学了新技能------using enum,直接把枚举的成员搬到当前作用域,省得你每次写 Enum::Value 写到手指抽筋。
今天我们就来扒一扒这个"大自然搬运工"的几副面孔,看看它到底是怎么帮你搬东西的,以及什么时候该用它,什么时候该小心它搬来的东西"砸了你的脚"。
基础篇:命名空间相关的 using
这次专门聊聊命名空间相关的 using------也就是 using 声明、using 指令 和 命名空间别名。
这三兄弟在代码里天天见,但用不好就容易翻车。
一、命名空间:C++ 的"小区"
想象一下,你写了个函数叫 print,同事也写了个 print,两人都放在全局区域,编译器就懵了:"到底调用哪个?"
于是 C++ 发明了命名空间------相当于给代码划分了小区,每个小区有自己的 print,互不干扰。
c++
namespace MyCode
{
void print() { std::cout << "MyCode\n"; }
}
namespace YourCode
{
void print() { std::cout << "YourCode\n"; }
}
但问题来了:每次调用都得写全名 MyCode::print(),手指累不累?这时候 using 三兄弟就来帮忙了。
二、命名空间别名:给长名字起个外号
有些命名空间名字长得离谱,比如 namespace very_long_and_annoying_namespace_name。 每次写都像在练打字,这时候命名空间别名就是救星:
c++
namespace v = very_long_and_annoying_namespace_name;
v::foo(); // 舒服!
使用场景:
- 简化标准库嵌套:namespace fs = std::filesystem; 然后爽用 fs::path。
- 版本切换:比如你有 namespace v1 和 namespace v2,可以 namespace current = v2;,代码里全用 current::,切换版本只改一行。
小贴士:别名只在当前作用域有效,头文件里慎用,避免污染别人。
三、using 声明:精准点名,只引入需要的
using 声明就像你去食堂只打一个菜,不碰别的。语法:using 命名空间::名字;
c++
using std::cout;
using std::endl;
cout << "hello world!" << endl; // 直接爽用,不用加 std::
好处:
- 精准,只把用到的名字引入当前作用域。
- 不会把整个命名空间都倒进来,避免命名冲突。
坑: 如果当前作用域已经有同名实体,会冲突。比如:
c++
int cout = 5;
using std::cout; // 错误:cout 已经有定义了
继承中的应用(后面会说明):using Base::method; 可以改变基类成员的访问权限,也是 using 声明的一种。
四、using 指令:打开整个命名空间的大门
using 指令 就是 using namespace 命名空间;。
相当于你直接把整个小区的门打开,别人可以随意光顾(里面所有名字都暴露在当前作用域)。
c++
using namespace std;
cout << "Hello" << endl; // 可以,但危险
危险在哪?
- 如果当前作用域也有 cout 变量,就会冲突。
- 如果两个命名空间都有同名函数,调用时编译器不知道选哪个,就会报二义性错误。
经典翻车案例:
c++
namespace A { void foo(int) {} }
namespace B { void foo(double) {} }
using namespace A;
using namespace B;
foo(5); // 二义性错误!A::foo(int) 和 B::foo(double) 都能匹配?
实际上,foo(5) 会同时匹配 A::foo(int)(完美匹配)和 B::foo(double)(int 转 double),编译器无法抉择,直接罢工。
永远不要在头文件里写 using namespace! 头文件会被无数源文件包含,相当于你在所有人家里泼了一地水。源文件里写也要谨慎,最好写在局部作用域(比如函数内)。
总结
| 特性 | 命名空间别名 | using 声明 | using 指令 |
|---|---|---|---|
| 作用 | 给命名空间起短名 | 引入单个名字 | 引入整个命名空间的所有名字 |
| 语法 | namespace 别名 = 原名; | using 命名空间::名字; | using namespace 命名空间; |
| 引入范围 | 别名本身,不引入名字 | 只引入指定的名字 | 引入所有名字 |
| 冲突风险 | 无(仅别名) | 低,只引入一个 | 高,可能引起二义性 |
| 推荐使用场景 | 长命名空间、版本切换 | 需要频繁使用某个名字,且明确无冲突 | 局部作用域(如函数内)简化代码 |
类型别名:using = typedef 的现代替代(C++11)
C++11 用 using 彻底干掉了老古董 typedef,让类型起名变得像给变量起名一样自然。
一、typedef 的"反人类"设计
在 C++98 年代,我们只能用 typedef 给类型起外号。但它的语法......怎么说呢,就像用脚设计键盘------能用,但难受。
1. 语法反直觉
c++
typedef unsigned int uint; // 还好,像赋值反过来
typedef int* IntPtr; // 还能忍
typedef void (*Func)(int, double); // 这啥玩意儿???
最后这个函数指针,读的时候得从中间往两边看:(*Func) 表示 Func 是一个指针,指向一个返回 void、参数是 (int, double) 的函数。正常人谁这么思考?
2. 不能直接用于模板
想给 std::vector 起个别名,比如 Vec 表示 std::vector,但 typedef 做不到模板别名。你只能曲线救国,套一层结构体:
c++
template<typename T>
struct Vec
{
typedef std::vector<T> type;
};
Vec<int>::type v; // 用起来想骂娘
每次还要写 ::type,烦不烦?
3. 数组别名也难受
c++
typedef int Array[10]; // Array 是 int[10] 的别名
Array arr; // arr 是长度为 10 的 int 数组
这种语法就像是把类型名藏在了中间,阅读性极差。
二、C++11 using 类型别名:终于像个人了!
C++11 引入了 using 关键字的新用法------类型别名。语法超级直观:
c++
using 新名字 = 原类型;
从左到右读,和变量声明一模一样!
对比一下:
c++
// typedef
typedef unsigned int uint;
typedef int* IntPtr;
typedef void (*Func)(int, double);
typedef int Array[10];
// using(舒服!)
using uint = unsigned int;
using IntPtr = int*;
using Func = void(*)(int, double);
using Array = int[10];
尤其是函数指针,using Func = void(*)(int, double); 一眼就能看出 Func 是一个类型,它代表一个函数指针,返回 void,参数是 int 和 double。清晰到爆!
三、模板别名:using 的杀手锏
这才是 using 真正的王炸!C++11 允许你直接定义模板别名,不用再套结构体了。
c++
template<typename T>
using Vec = std::vector<T>;
Vec<int> v; // 直接当类型用,爽!
甚至可以定义更复杂的别名模板:
c++
template<typename T>
using StringMap = std::map<std::string, T>;
StringMap<int> m; // std::map<std::string, int>
这在泛型编程中简直是神器。比如你想封装一个自定义分配器的容器:
c++
template<typename T>
using MyVec = std::vector<T, MyAllocator<T>>;
以后代码里直接用 MyVec,简洁又统一。
四、一些小建议
- 新代码一律用 using,别给后来人留 typedef 的坑。
- 模板别名是你的好朋友,能少写很多 typename XXX::type。
- 可以在头文件里使用 using 类型别名,它不引入名字,不会污染命名空间。
继承中的 using:提升基类成员
继承中的 using------一个能让你的派生类瞬间拥有"特权"的神器。解决名字隐藏、一键继承构造函数、甚至改变基类成员的访问权限,using 在继承里简直是个"权限管理大师"。
一、名字隐藏问题:为啥我调不了基类的函数?
先看个经典翻车现场:
c++
struct Base
{
void func(int) { std::cout << "Base::func(int)" << std::endl; }
};
struct Derived : Base
{
void func(double) { std::cout << "Derived::func(double)" << std::endl; }
};
int main()
{
Derived d;
d.func(10); // 猜猜调用哪个?
return 0;
}
输出是 Derived::func(double)!惊不惊喜?明明传的是 int,却调了 double 版本。
这就是 C++ 的名字隐藏规则:只要派生类里有一个同名函数(不管参数是否匹配),基类里所有同名函数都被隐藏了。
解决办法?用 using 声明把基类的 func 拉回来:
c++
struct Derived : Base
{
using Base::func; // 引入基类所有名为 func 的函数
void func(double) { std::cout << "Derived::func(double)" << std::endl; }
};
现在 d.func(10) 就会调用 Base::func(int),完美解决!
二、继承构造函数:再也不用手写转发构造函数了!
以前写派生类,如果基类有一堆构造函数,派生类想支持同样的构造,就得一个个手写转发:
c++
struct Base
{
Base(int) {}
Base(double, int) {}
// 还有几十个...
};
struct Derived : Base
{
Derived(int x) : Base(x) {} // 手动转发
Derived(double d, int i) : Base(d, i) {} // 累死
// ...
};
这简直是体力活!C++11 引入了继承构造函数,一个 using 搞定所有:
c++
struct Derived : Base
{
using Base::Base; // 继承基类所有构造函数
};
就这么简单!现在 Derived 自动拥有了和 Base 一样的构造函数(除了默认、拷贝、移动构造函数外)。你可以这样用:
c++
Derived d1(5); // 调用 Base(int)
Derived d2(3.14, 10); // 调用 Base(double, int)
注意事项:
- 继承的构造函数和派生类自己定义的构造函数可以共存,编译器会根据重载决议选择。
- 如果基类构造函数有默认参数,继承时会生成多个版本(一个带默认参数,一个不带)。
- 不能继承构造函数的初始化列表,派生类自己的成员需要在类内初始化或通过默认值。
三、改变访问权限:让基类成员"升级"或"降级"
using 在派生类中还可以改变基类成员的访问权限。比如基类某个成员是 protected,你想在派生类中把它公开给外界:
c++
class Base
{
protected:
void helper() { std::cout << "Base::helper" << std::endl; }
};
class Derived : public Base
{
public:
using Base::helper; // 将 protected 提升为 public
};
int main()
{
Derived d;
d.helper(); // 现在可以调了!
}
同样,你也可以把基类的 public 成员在派生类中变成 private(不过一般不这么干,除非你想刻意隐藏)。
原则: using 声明引入的名字的访问权限由它在派生类中声明的位置决定。
所以放在 public: 下,它就是 public;放在 private: 下,它就是 private。
应用场景:
- 你想暴露基类的某个 protected 成员给外部调用(比如测试代码)。
- 你想隐藏基类的某个 public 成员(比如接口收窄)。
四、综合案例:using 在继承中的大显身手
来看一个稍微复杂点的例子:
c++
class Base
{
public:
void foo(int) {}
protected:
void bar() {}
Base(int) {}
Base(double) {}
};
class Derived : public Base
{
public:
using Base::Base; // 继承构造函数
using Base::bar; // 把 protected 的 bar 提升为 public
void foo(double) {} // 自己的 foo(double)
private:
using Base::foo; // 把基类的 foo(int) 变成 private(奇怪但允许)
};
注意这里 using Base::foo 放在 private 下,会导致基类的 foo(int) 在派生类中变成 private。
但是因为我们在 public 里定义了自己的 foo(double),所以外部可以通过 Derived 对象调用 foo(double),但无法调用 foo(int)。这算是一种选择性暴露。
不过这种用法比较少见,通常我们更常用的是提升访问权限,而不是降低。
五、注意事项与最佳实践
- 名字隐藏时,记得 using
只要派生类定义了同名函数(无论参数是否相同),基类同名函数就被隐藏。如果想保留,就用 using Base::func。 - 继承构造函数时注意成员初始化
继承的构造函数不会初始化派生类自己的成员,这些成员需要用默认值或类内初始化。 - 改变访问权限要合理
尽量不要随意降低基类成员的访问权限,这会破坏接口的一致性。提升 protected 到 public 通常是用于工具类或测试。 - 不要过度使用
using 很强大,但滥用会导致代码难以理解。比如在多个基类中引入同名成员可能引发二义性。
C++17:using 声明支持逗号分隔多个名字
我们来聊一个"看起来很小,但用起来真香"的改进------C++17 允许 using 声明一次引入多个名字。以前写 using 就像挤牙膏,一次只能挤一点;现在终于可以"批量导入"了!
一、C++17 前的"牙膏式" using
在 C++17 之前,如果你想把同一个命名空间里的几个名字引入当前作用域,只能一个一个写:
c++
using std::cout;
using std::endl;
using std::string;
using std::vector;
这代码看着就累,手指都酸了。更要命的是,如果从 std::chrono 里引入一堆时间相关的名字:
c++
using std::chrono::duration;
using std::chrono::time_point;
using std::chrono::duration_cast;
using std::chrono::seconds;
using std::chrono::milliseconds;
// ... 还有一堆
这简直就是"重复劳动大赛"冠军!明明都是一个命名空间的东西,非要我写五六遍 using std::chrono::,手不累吗?
ps:这就像你点外卖,明明同一家店,却要一个一个菜下单,外卖小哥都得跑五趟!
二、C++17 的"批发式" using
C++17 终于听到了群众的呼声,允许在一条 using 声明中用逗号分隔多个名字:
c++
// C++17 的写法
using std::cout, std::endl, std::string, std::vector;
爽不爽?一行搞定!
对于前面那个 std::chrono 的例子:
c++
using std::chrono::duration, std::chrono::time_point,
std::chrono::duration_cast, std::chrono::seconds;
虽然还是得写 std::chrono::,但至少不用重复写 using 了。(能偷点懒也不错)
C++17 的逗号分隔 using 声明,虽然是个小改进,但写代码时能省不少手指运动。
C++20:using enum
using 专题终于聊到 C++20 了!这次要说的 using enum 简直是"枚举爱好者的福音"------以前写 enum class 觉得安全但啰嗦的朋友,这回可以解放双手了!
一、传统枚举访问的"痛":安全与啰嗦的博弈
C++11 引入了 enum class(作用域枚举),解决了传统枚举的两个大问题:名字污染和隐式转换 。但代价是------每次访问枚举值都得写前缀,手指头都累细了:
c++
enum class Color { Red, Green, Blue };
void printColor(Color c)
{
switch (c)
{
case Color::Red: // 每个 case 都要写 Color::
std::cout << "Red" << std::endl;
break;
case Color::Green:
std::cout << "Green" << std::endl;
break;
case Color::Blue:
std::cout << "Blue" << std::endl;
break;
}
}
如果枚举名字再长一点,比如 FileOpenMode、ThreadPoolPriority,那简直是"手腕终结者"。
更痛苦的是,如果在同一个函数里多次使用同一个枚举值,每次都要敲一遍前缀,代码读起来也像复读机 。
二、C++20 using enum 声明
C++20 引入了 using enum 声明,可以把枚举的所有成员"注入"到当前作用域,之后就能直接使用枚举值,不用再写前缀了:
c++
void printColor(Color c)
{
using enum Color; // 关键
switch (c)
{
case Red: // 直接写 Red,不用 Color::
std::cout << "Red" << std::endl;
break;
case Green:
std::cout << "Green" << std::endl;
break;
case Blue:
std::cout << "Blue" << std::endl;
break;
}
}
是不是瞬间清爽了?这就是 using enum 的核心价值------既保留了 enum class 的类型安全,又获得了传统枚举的便捷访问。
语法形式
using enum 有两种用法:
- 引入整个枚举:using enum Color;
- 选择性引入:using Color::Red, Color::Green;(结合 C++17 的逗号分隔)
c++
void foo()
{
using enum Color; // 引入所有
Color c1 = Green; // OK
using Color::Red; // 只引入 Red
Color c2 = Red; // OK
// Color c3 = Green; // 错误,Green 没引入
}
第二种方式更精确,可以避免不必要的名字冲突。
三、应用场景
1. switch 语句中屠榜
这是 using enum 最爽的应用------switch 里枚举值多的时候,再也不用写一堆前缀了:
c++
enum class Permission { Read, Write, Execute, Delete, Modify, ... };
void checkPerm(Permission p)
{
using enum Permission;
switch (p)
{
case Read: // 干净!
// ...
break;
case Write:
// ...
break;
// 几十个 case 都不怕
}
}
2. 函数内频繁使用枚举
如果一个函数里多次用到同一个枚举的多个值,using enum 能省不少键盘寿命:
c++
void processFile(FileMode mode)
{
using enum FileMode;
if (mode == Read) {
// ...
}
else if (mode == Write) {
// ...
}
else if (mode == Append) {
// ...
}
}
3. 类内部暴露枚举值
在类的内部,可以通过 using enum 把枚举值直接暴露给成员函数使用,甚至可以暴露给外部:
c++
class Widget
{
public:
enum class State { Active, Inactive, Pending };
using enum State; // 把枚举值引入类作用域
void setState(State s)
{
if (s == Active) // 直接写 Active,不用 State::
{
// ...
}
}
};
int main()
{
Widget w;
w.setState(Widget::Active); // 甚至可以直接用 Widget::Active
}
这个特性很强大------Widget::Active 直接就是枚举值,调用方甚至不需要知道它来自哪个枚举。
4. 命名空间级别暴露
我们还可以在命名空间里用 using enum,把某个枚举的值暴露到整个命名空间:
c++
namespace MyLib
{
enum class LogLevel { Debug, Info, Warning, Error };
using enum LogLevel; // 将枚举值引入 MyLib 命名空间
}
void foo()
{
// 直接 MyLib::Debug,而不是 MyLib::LogLevel::Debug
MyLib::LogLevel level = MyLib::Debug;
}
这特别适合库设计------既保持了 enum class 的类型安全,又提供了简洁的访问方式。
结尾
这一路写下来算是把 C++ 里的 using 关键字从里到外扒了个遍!从命名空间的"三兄弟"到类型别名的"现代革命",从继承里的"权限大师"到 C++17/20 的"语法糖暴击",是不是感觉 using 这玩意比想象的能打多了?
最后的最后,我们一起来整个"using 完全指南",把知识点串成串。
1. 命名空间相关
- 命名空间别名:namespace fs = std::filesystem; ------ 给长名字起外号,手不酸。
- using 声明:using std::cout; ------ 精准引入,只拿需要的,不污染环境(要做环保人士)。
- using 指令:using namespace std; ------ 整个大门敞开,爽但危险,头文件里千万别用!
2. 类型别名(C++11)
- 替代 typedef:using IntPtr = int*; ------ 从左到右读,符合直觉,告别"反人类"语法。
- 模板别名:template using Vec = std::vector; ------ 模板也能起别名,元编程神器。
3. 继承中的 using
- 解决名字隐藏:using Base::func; ------ 把基类被藏起来的函数拉回来。
- 继承构造函数:using Base::Base; ------ 一键继承所有构造,懒人福音。
- 改变访问权限:放在 public 下就把基类 protected 变 public,放在 private 下就降级。
4. C++17/20 新花样
- C++17 逗号分隔:using std::cout, std::endl, std::string; ------ 一次引入多个名字,告别牙膏式 using。
- C++20 using enum:using enum Color; ------ 让 enum class 的枚举值既安全又方便。