C++ 起步:从命名冲突到命名空间,cin/cout 与缺省参数
我的github:(https://github.com/xcx55/ubuntu-linux-project)
感谢各位大佬参观我的github!!
学完 Linux 系统编程回头看 C++,最大的感受是:C++ 的每个新语法都不是凭空发明的,都是在替 C 语言填坑。这篇从第一个坑写起------命名冲突。
一、C 语言的痛:库和你,撞名了
C 语言里有个经典死局:
c
#include <stdio.h>
#include <stdlib.h> // 库里有 rand 函数
int rand = 10; // 你定义了一个叫 rand 的变量
int main()
{
printf("%d\n", rand); // error C2365: "rand": 重定义;以前的定义是"函数"
return 0;
}
问题不在于你写错了什么------你只是想定义一个叫 rand 的变量,但库早就把这个名字占了。C 语言只有一个全局域,你的变量和库的函数都住在这个域里,名字一撞就是重定义,而且这仗你打不赢:库你改不了,变量名你还非起不可。
项目越大,这个问题越致命。多人协作、多个库互相包含,谁也不能保证自己的名字和别人的不撞。
C++ 的解法:提出"域"的概念------不同域里可以命名相同的名字。namespace 就是单独命名出来的一个域。
二、namespace:只是改变了查找规则
先看域操作符 :::
c
int x = 0;
int main()
{
int x = 1;
printf("%d\n", x); // 1,就近找局部域
printf("%d\n", ::x); // 0,::前面为空 = 全局域
return 0;
}
编译器寻找一个名字,默认只在局部域 → 全域 这条链上找,不会主动去命名空间里找。想让它去 namespace 里找,就得显式指定:namespace名字::变量。
这里有个我很喜欢抠的细节:namespace 并没有改变变量的存储位置 ------命名空间域里定义的东西,本质上还是全局的。namespace 解决的是"全局域里的重命名冲突",它影响的仅仅是编译器的查找规则,而不是变量的性质。这一点想通了,后面很多疑惑就没了。
C++ 里域一共四种:全局域、局部域、类域、命名空间域。命名空间还可以嵌套:
c
namespace bit
{
namespace p1
{
int x = 0;
}
}
// 访问:bit::p1::x
嵌套解决了另一个问题:同一个命名空间内部也可能撞名,那就再套一层,按"域1::域2::变量"的路径去找。
三、三种使用方式,项目里用哪一种?
多文件工程里(.h / .cpp 分开写),只要 namespace 名字相同,多个文件里的同名命名空间会自动合并------所以工程上习惯用一个统一的命名空间装项目自己的东西。
使用命名空间有三种姿势:
- 指定访问 :
bit::x,每次都写全路径。麻烦,但最安全------项目中推荐这种方式; - 部分展开 :
using bit::x;只放行某一个成员,对经常访问、确定不冲突的成员用这个; - 全部展开 :
using namespace bit;图省事,把整个命名空间摊平到全局。项目里不推荐,冲突风险很大,日常小练习为了方便才用。
std 这个标准库命名空间也一样:日常练习可以 using namespace std,实际项目里别这么干。
四、cin / cout:重新设计的流
C++ 重新设计了一套输入输出------iostream。cout 是流插入、cin 是流提取(<< >> 这两个符号,C 语言还在拿它们做左移右移位运算,靠的是运算符重载,这是后话)。
它比 printf/scanf 好在哪?自动识别类型,不用占位符:
c
int a = 10;
double d = 1.1;
cout << a << " " << d << endl; // 不用写 %d %lf
printf 要人肉指定格式,%d 对应 int、%f 对应 float,类型一多就是查表游戏;cout 直接把对象丢进去就行。这个"自动识别"本质是函数重载实现的------同一名字的不同版本函数各接一种类型,编译器帮你选。更重要的是,C++ 的流能更好地支持自定义类型对象的输入输出 :你自己写的类,重载一下 << 就能直接打印,printf 永远做不到这件事(它的格式串只认识内置类型)。
细节:cout/cin 都在 std 命名空间里,所以用它们绕不开命名空间;cin 的工作是"从缓冲区读字符,转化成你的类型",cout 是"把内存里的东西转成字符流出去"------方向永远以流为中心。另外 cout/cin 的效率其实不如 printf/scanf,日常够用,追求性能时有专门手段(sync_with_stdio)去调。
顺带还个旧账:之前在文件操作章欠下的"流"的理解,到这里算是正式补上了------文件流和网络编程里读写的本质也是流,一脉相承。
五、缺省参数:没传参就吃默认值
c
void func(int a = 10)
{
cout << a << endl;
}
func(); // 10,没传就用缺省值
func(20); // 20,传了就用你的
规则一句话:传了参就用参,没传就用缺省参数。全缺省、半缺省(从右往左连续给默认值)都支持。它和函数重载的恩怨下一篇细说------这两者组合起来会有调用歧义的坑,C++ 是有意这么设计的。
写 C 的时候,名字冲突这件事只能靠"人肉加前缀"来防( strutcpy、my_strcpy......);C++ 用一个 namespace 把它变成了语法层面的解。学习路线也是这个逻辑:先看见痛点,再看语言怎么收。下一篇写函数重载和引用------后者号称"指针的语法糖",但糖衣底下藏着 C++ 最重要的一条规则:临时变量具有常性。