C++操作符与高级特性详解
立即解锁
发布时间: 2025-08-22 00:56:26 阅读量: 2 订阅数: 8 


C++编程语言精髓与实践
# C++ 操作符与高级特性详解
## 1. 各类操作符
### 1.1 逻辑操作符
逻辑操作符包括 `&&`(与)、`||`(或)和 `!`(非),它们接受算术和指针类型的操作数,将其转换为 `bool` 类型,并返回 `bool` 结果。`&&` 和 `||` 操作符仅在必要时计算第二个参数,可用于控制求值顺序。例如:
```cpp
while (p && !whitespace(*p)) ++p;
```
这里,如果 `p` 是 `nullptr`,则不会对其进行解引用。
### 1.2 位逻辑操作符
位逻辑操作符 `&`(与)、`|`(或)、`^`(异或)、`~`(取反)、`>>`(右移)和 `<<`(左移)应用于整数类型的对象。一个普通的枚举(而非枚举类)可以隐式转换为整数类型,并用作位逻辑操作的操作数。
位逻辑操作符常用于实现小集合(位向量)的概念。例如:
```cpp
enum ios_base::iostate {
goodbit = 0, eofbit = 1, failbit = 2, badbit = 4
};
state = goodbit;
if (state & (badbit | failbit)) {
// stream not good
}
```
位逻辑操作还可用于从一个字中提取位域,示例代码如下:
```cpp
constexpr unsigned short middle(int a) {
static_assert(sizeof(int) == 4, "unexpected int size");
static_assert(sizeof(short) == 2, "unexpected short size");
return (a >> 8) & 0xFFFF;
}
int x = 0xFF00FF00;
short y = middle(x); // y = 0x00FF
```
### 1.3 条件表达式
一些 `if` 语句可以方便地用条件表达式替代。例如:
```cpp
if (a <= b)
max = b;
else
max = a;
// 可替换为
max = (a <= b) ? b : a;
```
条件表达式可用于常量表达式。
### 1.4 递增和递减操作符
`++` 操作符用于直接表示递增,`--` 操作符用于直接表示递减。它们可以作为前缀和后缀操作符使用。例如:
```cpp
y = ++x; // 前缀递增,y 为 x 递增后的值
y = x++; // 后缀递增,y 为 x 递增前的值
```
`++` 和 `--` 操作符在循环中特别有用,例如复制以零结尾的 C 风格字符串:
```cpp
void cpy(char* p, const char* q) {
while (*p++ = *q++);
}
```
## 2. 自由存储区
自由存储区(也称为堆或动态内存)用于创建独立于创建它的作用域的对象。`new` 操作符用于创建对象,`delete` 操作符用于销毁对象。
### 2.1 内存管理
自由存储区的主要问题包括:
- 内存泄漏:使用 `new` 分配对象后忘记使用 `delete` 释放。
- 过早删除:删除一个对象后,仍然使用指向该对象的其他指针。
- 双重删除:一个对象被删除两次。
为避免这些问题,可以采用以下两种方法:
- 尽量不使用自由存储区,优先使用作用域内的变量。
- 使用资源管理对象(如 `string`、`vector`、`unique_ptr` 和 `shared_ptr`),遵循 RAII(资源获取即初始化)原则。
### 2.2 数组
可以使用 `new` 创建对象数组。例如:
```cpp
char* save_string(const char* p) {
char* s = new char[strlen(p) + 1];
strcpy(s, p);
return s;
}
```
使用 `delete[]` 释放数组内存。
### 2.3 获取内存空间
`new`、`delete`、`new[]` 和 `delete[]` 操作符通过 `<new>` 头文件中的函数实现。当 `new` 无法分配内存时,默认会抛出 `bad_alloc` 异常。
### 2.4 重载 new
可以通过提供额外参数的分配函数,将对象分配到其他位置。例如:
```cpp
void* operator new(size_t, void* p) { return p; }
void* buf = reinterpret_cast<void*>(0xF00F);
X* p2 = new(buf) X;
```
在程序中需要避免异常时,可以使用 `nothrow` 版本的 `new` 和 `delete`。
## 3. 列表
`{}` 列表可以作为表达式在许多地方使用,分为限定列表和非限定列表。
### 3.1 实现模型
`{}` 列表的实现模型分为三种情况:
- 作为构造函数参数使用时,实现方式与 `()` 列表相同。
- 用于初始化聚合体(数组或没有构造函数的类)的元素时,每个列表元素初始化聚合体的一个元素。
- 用于构造 `initializer_list` 对象时,每个列表元素用于初始化 `initializer_list` 底层数组的一个元素。
### 3.2 限定列表
如果可以使用 `T x {v};` 初始化变量 `x`,则可以使用 `T{v}` 或 `new T{v}` 创建具有相同值的对象。例如:
```cpp
struct S { int a, b; };
void f() {
S v {7, 8};
v = S{7, 8};
S* p = new S{7, 8};
}
```
### 3.3 非限定列表
非限定列表在预期类型明确的地方使用,可作为函数参数、返回值、赋值操作符的右操作数或下标。例如:
```cpp
int f(double d, Matrix& m) {
int v {7};
int v2 = {7};
int v3 = m[{2, 3}];
v = {8};
v += {88};
f({10.0});
return {11};
}
```
## 4. Lambda 表达式
Lambda 表达式是定义和使用匿名函数对象的简化表示法,常用于将操作作为参数传递给算法。
### 4.1 实现模型
可以将 Lambda 表达式视为定义和使用函数对象的简写。例如:
```cpp
void print_modulo(const vector<int>& v, ostream& os, int m) {
for_each(begin(v), end(v),
[&os, m](int x) { if (x % m == 0) os << x << '\n'; }
);
}
```
可以将其等效的函数对象定义为:
```cpp
class Modulo_print {
ostream& os;
int m;
public:
Modulo_print(ostream& s, int mm) : os(s), m(mm) {}
void operator()(int x) const {
if (x % m == 0) os << x << '\n';
}
};
```
### 4.2 替代方案
可以使用单独定义的类或 `for` 循环替代 Lambda 表达式。例如:
```cpp
void print_modulo(const vector<int>& v, ostream& os, int m) {
for (auto x : v)
if (x % m == 0) os << x << '\n';
}
```
### 4.3 捕获
Lambda 表达式的捕获列表用于指定可以在其主体中使用的定义环境中的名称,以及这些名称是通过值还是引用进行捕获。捕获列表有多种形式:
- `[]`:空捕获列表,表示不能使用周围上下文中的局部名称。
- `[&]`:隐式按引用捕获,所有局部名称都可以使用,所有局部变量通过引用访问。
- `[=]`:隐式按值捕获,所有局部名称都可以使用,所有名称引用调用 Lambda 表达式时局部变量的副本。
- `[capture-list]`:显式捕获,指定要捕获的局部变量的名称。
### 4.4 调用和返回
Lambda 表达式传递参数和返回结果的规则与函数相同,但有两个特殊之处:
- 如果 Lambda 表达式不接受任何参数,则可以省略参数列表。
- Lambda 表达式的返回类型可以从其主体推导得出。
### 4.5 Lambda 的类型
Lambda 表达式的类型未明确定义,但被定义为函数对象的类型。可以使用 `auto` 或 `std::function<R(AL)>` 来声明变量并使用 Lambda 表达式进行初始化。
## 5. 显式类型转换
C++ 提供了多种显式类型转换操作,包括构造、命名转换、C 风格转换和函数风格转换。
### 5.1 构造
使用 `{}` 表示法进行类型安全的构造。例如:
```cpp
auto d1 = double{2};
double d2 {double{2} / 4};
```
`{}` 构造仅执行“行为良好”的转换。
### 5.2 命名转换
包括 `const_cast`、`static_cast`、`reinterpret_cast` 和 `dynamic_cast`。例如:
```cpp
IO_device* d1 = reinterpret_cast<IO_device*>(0Xff00);
void* my_allocator(size_t);
int* p = static_cast<int*>(my_allocator(100));
```
### 5.3 C 风格转换
C 风格转换 `(T)e` 可以执行任何可以表示为 `static_cast`、`reinterpret_cast`、`const_cast` 组合的转换,但这种转换更危险。
### 5.4 函数风格转换
函数风格转换 `T(e)` 对于内置类型等价于 C 风格转换,不太安全。建议优先使用 `T{v}` 进行良好行为的构造,使用命名转换进行其他转换。
## 6. 建议
- 优先使用前缀 `++` 而不是后缀 `++`。
- 使用资源句柄避免内存泄漏、过早删除和双重删除。
- 尽量不使用自由存储区,优先使用作用域内的变量。
- 避免使用“裸 `new`”和“裸 `delete`”。
- 使用 RAII 原则。
- 如果操作需要注释或具有通用性,优先使用命名函数对象而不是 Lambda 表达式。
- 保持 Lambda 表达式简短。
- 谨慎使用按引用捕获。
- 让编译器推导 Lambda 表
0
0
复制全文
相关推荐









