C++ 核心基础概念解析
C++ 兼容 C,这意味着我们可以在 C++ 环境中编写 C 代码。本杰尼·斯特劳斯特卢普是 C++ 之父。
一、命名空间
在 C++ 中,标识符(变量、函数、类、常量、命名空间)的查找遵循特定规则。编译器会从当前最内层的作用域开始查找,一旦找到就不会继续向外层查找。这导致了一个常见问题:同一作用域下不能存在同名的标识符。
1. 命名冲突的本质
看下面这个例子:
#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); // 编译报错
}
这就是命名冲突。为了解决这个问题,我们引入了命名空间。
2. 命名空间的概念与语法
命名空间就像一个自定义的容器,用来存放标识符,避免不同模块间的名字打架。访问时通常使用 命名空间名::标识符 的形式。
namespace mycode {
int rand = 10;
}
int main() {
printf("%d\n", mycode::rand); // 正常打印 10
}
3. 命名空间的嵌套
当同一个命名空间下仍有冲突时,可以使用嵌套命名空间。就像在父文件夹里再建子文件夹一样。
#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
}
4. 命名空间的使用技巧
除了显式指定,还有两种常用方式:
- 展开某个成员:只引入需要的部分。
using name::a; // 只引入 a - 展开整个命名空间:方便但可能带来污染。
using namespace name; // 引入所有成员
注意:C++ 允许同一个命名空间分散在不同文件中,编译器会自动合并它们。标准库就存放在
std命名空间中。
二、C++ 的输入输出
C++ 的标准 I/O 头文件是 <iostream>,它提供了 cin、cout 以及流操作符 <<、>>。
1. cin 和 cout
std::cin 用于控制台输入,std::cout 用于控制台输出。因为它们在 std 命名空间下,使用前通常需要 using namespace std; 或加上 std:: 前缀。
2. 流操作符
<<(流插入):配合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++ 能自动识别变量类型,不需要像 C 语言那样手动写占位符(如 %d)。endl 则表示换行并刷新缓冲区。
三、缺省参数
缺省参数允许我们在定义函数时给参数一个默认值。调用时如果不传该参数,就使用默认值;如果传了,则覆盖默认值。
1. 全缺省与半缺省
- 全缺省:所有参数都有默认值。
- 半缺省:只有部分参数有默认值。关键点: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
}
五、引用(Reference)
引用是给已有变量起的一个别名,它直接操作原变量的内存地址,而不是拷贝一份数据。
1. 引用 vs 指针
以前我们用指针交换变量,现在用引用更简洁:
// 指针写法
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;
}
引用写法不需要解引用符号 *,代码更直观。
2. 引用的要求
- 初始化:引用定义时必须绑定对象,且终身绑定,不能重新赋值指向其他对象。
- 多引用:一个变量可以有多个引用,修改其中一个,原变量和其他引用都会变。
- 安全性:引用不存在空指针问题,比指针更安全。
六、const 引用
被 const 修饰的引用称为 const 引用。它常用于函数参数传递,既能避免拷贝提高效率,又能防止函数内部意外修改数据。
1. 权限控制
- 原变量可读可写,const 引用后变为只读。
- 原变量本身就是 const,const 引用保持只读。
int main() {
int a = 3;
const int& ra = a; // 安全,ra 只能读
// ra = 3; // 错误:超出权限
}
2. 修饰临时变量
普通引用不能绑定临时变量(如字面量),因为临时变量生命周期短且不可写。但 const 引用 可以延长临时变量的生命周期,直到引用销毁为止。
int main() {
const double& ra = 20; // 安全
// int& rra = 20; // 错误:权限放大
}
七、宏与内联函数
1. 宏 (Macro)
宏是预处理阶段的文本替换,分为无参和有参宏。虽然简单,但缺乏类型检查,容易因优先级问题出错。
#define Add(a,b) ((a) + (b))
2. 内联函数 (Inline)
内联函数建议编译器将函数体直接展开到调用处,省去栈帧构建开销,效率接近宏,但有类型检查和调试支持。
| 特性 | 宏 | 内联函数 |
|---|---|---|
| 本质 | 文本替换 | 函数 |
| 调试 | 无法调试 | 可以调试 |
| 优先级 | 需手动加括号 | 无需处理 |
八、nullptr
C++11 引入了 nullptr 关键字来表示空指针,替代了旧的 NULL。
为什么需要 nullptr?
在 C++ 中,NULL 通常定义为 0(整型)。在函数重载时,NULL 可能被误判为整型参数,导致调用错误的函数。
#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 能明确表达意图,避免歧义。


