C++ 基础概念速览与常见用法
C++ 兼容 C,这意味着可以在 C++ 环境里直接写很多 C 代码。本杰尼·斯特劳斯特卢普是 C++ 之父。
命名空间
C++ 里,标识符的查找遵循作用域规则:编译器先看当前最内层作用域,找到了就停,不会再往外层找。问题也就出在这里——不同模块一旦起了同名,冲突很容易出现。
命名冲突是怎么来的
先看一个局部变量遮住库函数名的例子:
#include<stdio.h>
#include<stdlib.h>
int main() {
int rand = 10;
printf("%d\n", rand); // 局部变量优先,代码正确执行
}
如果把 rand 放到全局,就不一样了:
#include<stdio.h>
#include<stdlib.h>
int rand = 10; // 与 stdlib.h 中的 rand 函数冲突
int main() {
printf("%d\n", rand); // 编译报错
}
这类问题用命名空间来隔开。
命名空间的写法
命名空间可以理解成一个带名字的容器,用来放变量、函数、类这些标识符。访问时通常写成 命名空间名::标识符。
namespace mycode {
int rand = 10;
}
int main() {
printf("%d\n", mycode::rand); // 正常打印 10
}
嵌套命名空间
同一个大命名空间里还可以继续分层。实际写代码时,这比硬把所有名字堆在一起更清楚。
#include<stdio.h>
namespace code {
namespace codeA {
int a = 10;
}
namespace codeB {
int a = 20;
}
}
int main() {
printf("%d\n", code::codeA::a); // 打印 10
printf("%d\n", code::codeB::a); // 打印 20
}
常见用法
除了直接写全名,还有两种方式:
- 只引入某个成员
using name::a; // 只引入 a
- 引入整个命名空间
using namespace name; // 引入所有成员
后者图省事,但污染也明显,尤其在头文件和公共代码里,我一般都会躲开。
C++ 允许同一个命名空间分散在多个文件里,编译器会把它们合并起来。标准库统一放在
std命名空间中。
C++ 的输入输出
C++ 的标准输入输出头文件是 <iostream>,它提供了 cin、cout 以及流操作符 <<、>>。
cin 和 cout
std::cin 用来从控制台读数据,std::cout 用来往控制台写数据。它们都在 std 命名空间里,所以通常会写 using namespace std;,或者直接加 std:: 前缀。
流操作符
<<配合cout,负责输出>>配合cin,负责输入
#include<iostream>
using namespace std;
int main() {
cout << "hello world" << endl;
cout << "我的刀盾喜欢奶龙" << endl << "咕咕嘎嘎" << endl;
int a, b;
cout << "请输入两个整数:";
cin >> a >> b;
cout << "两个数之和为:" << a + b << endl;
}
这里最方便的地方,是不用像 C 那样手动写格式占位符,编译器会按变量类型处理。endl 会换行,并顺手刷新缓冲区。
缺省参数
缺省参数就是给函数参数一个默认值。调用时不传,就用默认值;传了,就覆盖掉。
全缺省和半缺省
- 全缺省:所有参数都有默认值
- 半缺省:只有部分参数有默认值
C++ 对缺省参数的顺序要求很死:必须从右往左连续设置。也就是说,左边不能空着,右边却有默认值。
#include<iostream>
using namespace std;
// 正确:缺省值从右向左
void Print(int a, int b = 1, int c = 2) {
cout << a << "," << b << "," << c << endl;
}
int main() {
Print(0); // 打印 0,1,2
Print(6, 6); // 打印 6,6,2
Print(5, 5, 5);// 打印 5,5,5
// Print(); // 错误:没传够非缺省参数
}
函数声明和定义分开写时,缺省参数放在声明里更稳妥,别两边都写。
函数重载
同一个作用域里,C++ 允许出现多个同名函数,只要参数列表不同。区别可以是参数个数不同,也可以是类型或者顺序不同。
#include<iostream>
using namespace std;
// 参数个数不同
int Add(int a, int b, int c) { return a + b + c; }
int Add(int a, int b) { return a + b; }
// 参数类型不同
double Calc(int a, double b) { return a + b; }
double Calc(double a, int b) { return a - b; }
int main() {
cout << Add(1, 1, 1) << endl; // 调用三参数版本
cout << Add(1, 1) << endl; // 调用两参数版本
cout << Calc(2, 2.2) << endl; // 第一个是 int,第二个是 double
cout << Calc(2.2, 2) << endl; // 第一个是 double,第二个是 int
}
这种写法比起给函数起一堆相近名字,要顺手得多。
引用
引用可以看成已有变量的别名。它不复制数据,直接绑定到原变量上,改引用就是改原变量。
引用和指针
以前交换变量常写指针版本,现在引用版本更干净:
// 指针写法
void Swap(int* a, int* b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
// 引用写法
void Swap(int& a, int& b) {
int tmp = a;
a = b;
b = tmp;
}
引用不用写 * 解引用,读起来更像在操作普通变量。
引用的几个限制
- 定义时必须初始化,绑定之后不能再改绑到别的对象
- 一个变量可以有多个引用,改其中一个,原变量和其他引用都会跟着变
- 没有空引用这一说,写起来比指针少很多边界判断
const 引用
const 引用常用来传参。它的价值很实际:既避免拷贝,又不让函数内部乱改数据。
权限控制
- 原变量可读可写,
const引用把它限制成只读 - 原变量本身就是
const时,const引用也只能读
int main() {
int a = 3;
const int& ra = a; // 安全,ra 只能读
// ra = 3; // 错误:超出权限
}
绑定临时变量
普通引用不能绑临时变量,比如字面量,因为它既短命又不可写。const 引用可以接住它,还会顺手延长生命周期。
int main() {
const double& ra = 20; // 安全
// int& rra = 20; // 错误:权限放大
}
宏与内联函数
宏
宏发生在预处理阶段,本质上就是文本替换。它有无参和有参两种写法,快是快,但不受类型检查约束,优先级也容易出坑。
#define Add(a,b) ((a) + (b))
内联函数
内联函数会建议编译器把函数体展开到调用处,省掉一些函数调用开销。它保留了函数的类型检查,也比宏更好调。
| 特性 | 宏 | 内联函数 |
|---|---|---|
| 本质 | 文本替换 | 函数 |
| 调试 | 无法调试 | 可以调试 |
| 优先级 | 需手动加括号 | 无需处理 |
nullptr
C++11 引入了 nullptr,专门表示空指针,用来替代老的 NULL。
为什么要换成 nullptr
NULL 在 C++ 里通常就是 0。这在重载场景里很烦,因为它可能被当成整数,而不是空指针。
#include<iostream>
using namespace std;
void Print(int* x) { cout << "int*" << endl; }
void Print(int x) { cout << "int" << endl; }
int main() {
Print(NULL); // 实际调用 Print(int),因为 NULL 是 0
Print(nullptr); // 正确调用 Print(int*)
}
nullptr 的意思更明确,重载时也少歧义。这个改动不花哨,但很实用。


