命名空间
名字冲突的痛苦
C 和 C++ 里,变量、函数和类名默认都堆在全局作用域里,冲突是家常便饭。一个叫做 rand 的整型变量就可能跟标准库里的 rand() 过不去。
#include <stdio.h>
#include <stdlib.h>
int rand = 6;
int main() {
printf("%d", rand);
}
命名空间就是为了把名字圈进独立的小隔间,互不干扰。C++ 的做法:
#include <stdio.h>
namespace m {
int rand = 6;
}
int main() {
printf("%d", m::rand);
}
定义与隔离
用 namespace 关键字划出一个域,里面放变量、函数或类型,跟全局隔开。不同域里同名是完全合法的。编译器查找名字时只看你所在的域(局部、全局、命名空间或类)。局部域和全局域还影响生命周期,命名空间域和类域则不影响。
namespace m {
int a = 6;
int Add(int a, int b) {
return a + b;
}
struct Node {
struct Node* next;
int val;
};
}
支持嵌套,同一个命名空间可以散在不同文件里自动合并。标准库的东西基本都关在 std 里。
namespace m {
namespace n {
int a = 0;
int b = 9;
}
int c = 6;
}
使用方式
默认查找只在局部和全局进行,所以要用命名空间成员有三种路子:
- 每次访问时带上命名空间前缀,比如
std::cout—— 最推荐。 - 用
using把单个名字拉进当前作用域:using m::a;。无冲突时很方便。 - 一把梭
using namespace std;,小练习可以,工程里容易污染全局,慎用。
输入输出
C++ 的 IO 通过流库实现,std::cin 和 std::cout 分别是 istream 和 ostream 的对象,<< 是流插入,>> 是流提取。
#include <iostream>
int main() {
int a = 0;
double b = 0;
std::cin >> a >> b;
std::cout << a << " " << b << std::endl;
return 0;
}
好处是不用手动指定类型格式,编译器自己识别。如果对 IO 性能要求极高(比如算法竞赛),可以关掉同步流提速:
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
缺省参数
函数声明或定义时给参数一个默认值,调用时不传就用它。全缺省和半缺省都可以。
#include <iostream>
using namespace std;
int Add(int a = 1, int b = 2) {
return a + b;
}
int main() {
cout << Add(4, 3) << endl; // 7
cout << Add() << endl; // 3
return 0;
}
半缺省参数必须从右往左连续给出,调用时从左往右依次传实参。声明和定义分离时,缺省值只能出现在一处,通常留在声明里。
函数重载
同一作用域内允许函数同名,只要参数不同(个数或类型)。C 没这能力。
按参数类型区分
void Add(int a, int b) { cout << a + b << endl; }
void Add(double a, double b) { cout << a + b << endl; }
按参数个数区分
void fuc() { cout << "haha" << endl; }
void fuc(int a) { cout << "hehe" << endl; }
按参数类型顺序区分
void fuc(int a, char b) { cout << "haha" << endl; }
void fuc(char b, int a) { cout << "hehe" << endl; }
容易踩的坑
- 返回值类型不同不算重载,调用时挑不出来。
- 缺省参数可能造成歧义,比如
f()和f(int a = 10),编译器不知道你叫谁。可以用命名空间隔离:
namespace m {
void f1(int a = 10) { cout << "f(int a)" << endl; }
}
// 调用时 f1() 或 m::f1() 清清楚楚
引用
别名而已
引用是另一个名字,不占额外内存。
int a = 0;
int& b = a; // b 就是 a
int& c = b; // c 也是 a
特性
- 必须初始化,定义时就绑死。
- 绑了谁就永远是它,赋值只是修改值,不会换绑。
常见用处
传参和返回时避免拷贝,或者直接修改原对象。比如写个栈,用引用传进去:
#include <iostream>
#include <cstdlib>
using namespace std;
typedef int STDataType;
typedef struct Stack {
STDataType* a;
int top;
int capacity;
} ST;
void STInit(ST& rs, int n = 4) {
rs.a = (STDataType*)malloc(n * sizeof(STDataType));
rs.top = 0;
rs.capacity = n;
}
void STPush(ST& rs, STDataType x) {
if (rs.capacity == rs.top) {
int newcapacity = (rs.capacity == 0 ? 4 : rs.capacity * 2);
STDataType* ptr = (STDataType*)realloc(rs.a, sizeof(STDataType) * newcapacity);
if (ptr == NULL) {
perror("realloc");
return;
}
rs.a = ptr;
rs.capacity = newcapacity;
}
rs.a[rs.top++] = x;
}
int& STTop(ST& rs) {
return rs.a[rs.top - 1];
}
void STDestroy(ST& rs) {
if (rs.a == NULL) return;
free(rs.a);
rs.a = nullptr;
rs.capacity = rs.top = 0;
}
int main() {
ST rs;
STInit(rs);
STPush(rs, 1);
STPush(rs, 2);
cout << STTop(rs) << endl;
STTop(rs) = 10; // 通过引用直接改栈顶
cout << STTop(rs) << endl;
STDestroy(rs);
return 0;
}
const 引用
- 只能拿 const 引用去引用 const 对象。
- 隐式转换产生的临时对象具有常性,只能用 const 引用接住,顺便防止意外修改。
double a = 1.23;
const int& b = a; // 必须 const
指针和引用对比
| 特性 | 引用 | 指针 |
|---|---|---|
| 内存 | 别名,不额外占空间 | 存放地址,占空间 |
| 初始化 | 必须初始化 | 建议初始化,非强制 |
| 指向可变 | 不可变 | 可变 |
| 访问 | 直接 | 需解引用 |
| sizeof | 引用类型大小 | 地址大小 |
| 安全性 | 较高,少野引用 | 容易出现空/野指针 |
inline 内联
在调用处展开函数体,省去栈帧开销,适合短小且频繁调用的函数。递归或复杂的函数通常会被编译器无视。
inline void Swap(int& a, int& b) {
int tmp = a;
a = b;
b = tmp;
}
比起 C 的宏,inline 不丢类型检查,调试也舒服。声明和定义最好放一起,分开放不同文件可能导致链接找不到。
nullptr
问题
用 NULL 调重载函数时,它可能是个 0,结果跑进了参数为整型的版本,而不是指针版本。
原因
C++ 里 NULL 可能是字面常量 0 或 (void*)0,类型推断时容易搞偏。
解决
C++11 搞出 nullptr,只认指针类型,不能隐式转成整数,彻底消除歧义。
void fuc(int* a) { cout << "pointer" << endl; }
void fuc(int a) { cout << "int" << endl; }
int main() {
fuc(nullptr); // 明明白白走指针版本
return 0;
}


