本篇目标:
认识enum class,static_assert,tuple及其使用
一.强类型枚举(enum class)
1.传统枚举
C++枚举存在以下主要问题:
<1>. 隐式转换为整型:枚举值会自动转换为整数,可能导致意外行为
<2>. 污染外围作用域:枚举值会泄漏到包含它的作用域中
<3>. 无法指定底层类型:不能明确控制枚举使用的存储大小
例如:
cpp
enum Color { Red, Green, Blue };
enum TrafficLight { Red, Yellow, Green };
int main()
{
//使⽤enum是不可以这样定义的,enum值是暴露到全局的,Red存在冲突
Color c1=Red;
int a=Color::Red; //C语言中的枚举即使加了::也会发生重定义
//隐式转换为整型:枚举值会⾃动转换为整数,可能导致意外⾏为
std::cout << a << std::endl;
return 0;
}
Color与TrafficLight的部分内容冲突了,所以在使用时,就会出错
2.强类型枚举
规则:
<1>.拥有**强作用域,**枚举值被限制在枚举类型的内部,访问时必须加上枚举类型名作为前缀和域操作符,例如:
cpp
enum class Color { Red, Green, Blue };
enum class TrafficLight { Red, Yellow, Green };
int main()
{
Color c = Color::Red; // 必须带上 Color:: 作用域
TrafficLight t = TrafficLight::Red;
return 0;
}
<2>.强类型, 不会隐式转换为 int 或其他枚举类型,如果要转换,必须明确告诉编译器你要这么做,例如:
cpp
#include<iostream>
using namespace std;
enum class Color { Red, Green, Blue };
enum class TrafficLight { Red, Yellow, Green };
int main()
{
Color c = Color::Red; // 必须带上 Color:: 作用域
TrafficLight t = TrafficLight::Red;
//int a=Color::Red; //报错
int a=static_cast<int>(Color::Red);
cout<<a<<endl;
return 0;
}
<3>.可以指定底层类型,默认情况下,enum class 的底层类型是 int。但你可以显式指定它的底层类型,例如:
cpp
enum class SmallEnum : uint8_t { Value1, Value2 }; // 8位存储
enum class BigEnum : uint32_t { Value1, Value2 }; // 32位存储
注意:enum class(强类型枚举)的内存对齐数取决于它的底层类型。
<4>.C++20后,可以引入强枚举到当前作域,例如:
cpp
using enum Color;
Color c = Red;
二.static_assert
概念: static_assert 是C++11引入的编译时断言机制 ,它允许开发者在编译期间检查条件是否满足,如 果条件不满足,则会导致编译错误。
使用规则:
cpp
static_assert(常量表达式, 错误消息);
注意:C++17后,错误信息可以不写
1.使用样例:
1.如果你的函数或变量是用 constexpr 修饰的(即在编译期就能算出结果),你可以用 static_assert 对其结果进行验证,例如:
cpp
// 在编译期就可以计算阶乘的函数
constexpr int factorial(int n)
{
return n <= 1 ? 1 : (n * factorial(n - 1));
}
int main()
{
// 可以在在编译期验证逻辑是否正确
static_assert(factorial(4) == 24, "计算错误!");
static_assert(factorial(5) == 120, "计算错误!");
return 0;
}
2.编写跨平台代码,或者你的底层逻辑对数据类型的大小有严格要求时,可以使用 static_assert 来保证编译环境符合预期,例如:
cpp
// 确保指针大小为 8 字节(限制代码只能在 64 位系统上编译)
static_assert(sizeof(void*) == 8, "This code requires 64-bit platform");
3.限制模板参数的类型,防止别人传入错误的类型导致报错,例如:
cpp
#include <iostream>
#include <type_traits>
template<typename T>
void process(T value)
{
static_assert(std::is_integral<T>::value, "T must be an integral type");
// 函数实现...
}
int main() {
std::cout << process(10) << std::endl;
// std::cout << process(3.14) << std::endl; //报错
// std::cout << process("C++") << std::endl; //报错
return 0;
}
2.static_assert与assert的区别
|------|------------------------------|-------------------------------------|
| 对比 | static_assert | assert |
| 生效时间 | 编译期 | 运行期 |
| 条件要求 | 必须是编译期常量表达式 | 可以是任意的变量 或表达式 |
| 影响 | 编译报错 | 运行终止 |
| 主要用途 | 类型检查、模板参数约束、内存对齐验证、跨平台环境假设验证 | 验证函数的传入参数、验证指针是否为空、验证业务逻辑的中间状态 |
| 报错表现 | 编译器报错,生成带有我们自定义信息的报错日志 | 程序运行时突然崩溃 (Abort),并打印出导致崩溃的代码行号和表达式 |
注意:is_integral是类型萃取的内容,可以用来检查T的类型是否为整形,后续会讲的
三.tuple的使用
概念:在 C++ 中,std::tuple(元组)是 C++11 引入的一个非常实用的标准库组件,定义在 <tuple> 头文件中,你可以把它看作是 std::pair 的泛型升级版 ,或者是**一个没有名字的结构体 (Struct),**允许你将任意数量、任意类型的数据打包成一个单一的对象。
1.创建tuple
例如:
cpp
// 创建⼀个包含3个元素的tuple : int, double, string
std::tuple<int, double, std::string> t1(1, 2.00003, "2134");
// 使⽤make_tuple⾃动推导类型
auto t2 = std::make_tuple(1, 2026, "123456");
// C++17起可以使⽤类模板参数推导
std::tuple t3("123456", 123, 12.0); //自动推导为string,int,double
std::tuple t4("123456", 123, std::vector<int>(1));
2.访问与修改元素
例如:
cpp
int a = std::get<0>(t1);
std::string str = std::get<2>(t2);
std::cout << a << std::endl << str << std::endl;
//修改元素 (std::get 返回的是引用)
std::get<2>(t3) = 15.0;
// C++14起可以通过类型访问(类型必须唯⼀)
std::cout << std::get<int>(t1) << std::endl;// 输出100
std::cout << std::get<double>(t1) << std::endl; // 输出3.14
3.解包tuple
例如:
cpp
int x; double y;
std::string z;
std::tie(x, y, z) = t1; //使用std::tie即可
std::cout << x << std::endl << y << std::endl << z << std::endl;
// C++17支持使用结构化绑定
auto [a1, b1, c1] = t2;
std::cout << a1 << std::endl << b1 << std::endl << c1 << std::endl;