有没有专门做布料的网站,服装工厂做网站的好处,莘县网站定制,网站制作模板教案导论 c17新特性引入了许多新的语法#xff0c;这些语法特性更加清晰#xff0c;不像传统语法#xff0c;语义飘忽不定#xff0c;比如‘a’你根本不知道是宽字符还是UTF-8 字符。以及测试i i#xff0c;最后结果到底是多少。这种问题很大情况是根据编译器的优化进行猜测这些语法特性更加清晰不像传统语法语义飘忽不定比如‘a’你根本不知道是宽字符还是UTF-8 字符。以及测试i i最后结果到底是多少。这种问题很大情况是根据编译器的优化进行猜测不同环境得出不同结果。而c17对这些问题给出完美的解决方案
字符字面量
理论
官方给出的语法: 普通字符字面量: c-char UTF-8 字符字面量: u8c-char UTF-16 字符字面量: uc-char UTF-32 字符字面量: Uc-char 宽字符字面量: Lc-char 普通多字符字面量: c-char-sequence 宽字符多字符字面量: Lc-char-sequence
不太懂基础薄弱 普通字符字面量: 形式: c-char表示: 一个单个的普通字符字面量,通常为 8 位 ASCII 字符。类型: char UTF-8 字符字面量: 形式: u8c-char表示: 一个单个的 UTF-8 编码的字符字面量。类型: char8_t (C20 引入) UTF-16 字符字面量: 形式: uc-char表示: 一个单个的 UTF-16 编码的字符字面量。类型: char16_t UTF-32 字符字面量: 形式: Uc-char表示: 一个单个的 UTF-32 编码的字符字面量。类型: char32_t 宽字符字面量: 形式: Lc-char表示: 一个单个的宽字符字面量。宽字符的大小由实现定义,通常为 16 位或 32 位。类型: wchar_t 普通多字符字面量: 形式: c-char-sequence表示: 一个字符序列,由多个普通字符组成。类型: 数组类型 char[N]其中 N 为字符序列的长度。 宽字符多字符字面量: 形式: Lc-char-sequence表示: 一个字符序列,由多个宽字符组成。类型: 数组类型 wchar_t[N]其中 N 为字符序列的长度。
这些不同类型的字符字面量主要有以下区别: 字符编码: 普通字符为 8 位 ASCII 编码。UTF-8、UTF-16 和 UTF-32 字符分别采用 UTF-8、UTF-16 和 UTF-32 编码。宽字符的编码由实现定义,通常为 16 位或 32 位。 表示范围: UTF-8、UTF-16 和 UTF-32 字符可以表示更广泛的字符集,包括非 ASCII 字符。宽字符的表示范围由实现定义。 内存占用: 普通字符和 UTF-8 字符占用 1 个字节。UTF-16 字符占用 2 个字节。UTF-32 字符和宽字符占用 4 个字节。 选择合适的字符类型取决于您的具体需求。对于仅需要处理 ASCII 字符的场景,使用普通字符就足够了。如果需要支持更广泛的字符集,可以考虑使用 UTF-8、UTF-16 或 UTF-32 字符。如果需要与遗留代码兼容,或者需要处理宽字符的场景,则可以使用宽字符。 现在我们可以定义出适合的字符串类型写出最佳的程序。
实践
普通字符字面量:
char c1 a;
char c2 \n;
char c3 \x2a; // 等同于 *UTF-8 字符字面量:
char8_t c1 u8a;
// char8_t c2 u8¢; // 错误,¢无法用单个UTF-8码元表示
// char8_t c3 u8猫; // 错误,猫无法用单个UTF-8码元表示
// char8_t c4 u8; // 错误,无法用单个UTF-8码元表示UTF-16 字符字面量:
char16_t c1 ua;
char16_t c2 u¢;
char16_t c3 u猫;
// char16_t c4 u; // 错误,无法用单个UTF-16码元表示UTF-32 字符字面量:
char32_t c1 Ua;
char32_t c2 U¢;
char32_t c3 U猫;
char32_t c4 U;宽字符字面量:
wchar_t wc1 La;
wchar_t wc2 L¢;
wchar_t wc3 L猫;
wchar_t wc4 L; // 在Windows上,这可能是非法的普通多字符字面量:
int mc1 ab; // 实现定义的值
int mc2 abc; // 实现定义的值宽字符多字符字面量:
wchar_t wmc1 Lab; // 实现定义的值
有关变量的新语法
变量的评估顺序
表达式的求值顺序: C 标准没有规定表达式中各个子表达式的求值顺序,除了一些特定的情况。 通常编译器可以自由选择求值顺序,只要最终结果与按照左到右的顺序求值一致。 x (x, y); 传统cpp并没有标准的定义到底谁先执行都是由编译器优化这就会造成环境不同效果不同争的你死我活。现在c17是这样解决的。
C 标准库中的 std::evaluate 和 std::as_const 这两个工具函数: std::evaluate (C23): 功能: 强制求值一个表达式,确保其副作用按照预期顺序发生。 声明: templateclass T constexpr T evaluate(T t) noexcept; 使用: int x 0, y 0;
x (x, y); // 存在顺序未定义,可能 x 或 y 先增加
x std::evaluate((x, y)); // 确保 x 和 y 按从左到右的顺序增加 作用: 帮助开发者明确表达式的求值顺序,避免由于未定义行为导致的bug。 std::as_const (C17): 功能: 获取一个表达式的常量引用,避免意外修改。 声明: templateclass T constexpr const T as_const(T t) noexcept; 使用: std::string s hello;
std::string_view sv s; // 可能意外修改 s
std::string_view sv std::as_const(s); // 确保 sv 只能读取 s 的内容 作用: 帮助开发者强制使用只读引用,防止无意中修改原对象。 总之, std::evaluate 和 std::as_const 是 C 标准库提供的两个有用的工具函数,前者解决表达式求值顺序的问题,后者解决引用可能被意外修改的问题。开发者可以在需要时使用它们来编写更加健壮的代码。
ifswitch初始化
这种多说无益,直接看示例就明白了。
auto lambda [](int x) {if (int y x * x; y 100) {return y;} else {return 0;}
};int result lambda(11); // result 为 121auto lambda [](char c) {switch (int x c; x) {case a:return 1;case b:return 2;default:return 0;}
};int result lambda(b); // result 为 2 结构化绑定声明 简化代码。
可以看我往期文章在此简单举个例子
#include iostream
#include tupleint main() {std::tupleint, char, std::string t{42, a, hello};//变量的数量必须与复合类型的元素数量一致。auto [x, y, z] t;std::cout x , y , z \n; // 输出: 42, a, helloreturn 0;
} 左值引用和右值引用
可以看我往期文章就不再赘述这是重中之重。
constexpr consteval 编译时求值
constexpr 和 consteval 是 C11 和 C20 引入的两个关键字,它们都用于在编译时执行计算,但是它们之间有一些区别: constexpr: constexpr 表示该变量或函数在编译时就可以计算出它的值。constexpr 函数可以在编译时执行,也可以在运行时执行。如果 constexpr 函数在编译时无法计算出结果,编译器会尝试在运行时执行该函数。constexpr 可以用于变量、函数和类的成员函数。 consteval: consteval 是 C20 引入的,它比 constexpr 更加严格。consteval 函数必须在编译时就能计算出结果,不能在运行时执行。如果 consteval 函数在编译时无法计算出结果,编译器会报错。consteval 只能用于函数,不能用于变量或类的成员函数。
举例
constexpr 变量
constexpr int x 42;
constexpr std::arrayint, 3 arr {1, 2, 3};constexpr 构造函数
struct Point {constexpr Point(int x, int y) : x(x), y(y) {}int x, y;
};
constexpr Point p(1, 2); // 在编译时创建 p 对象consteval关键字
consteval int square(int x) {return x * x;
}
constexpr int y square(3); // 在编译时计算出 y 9
总的来说:
constexpr 是一种可能在编译时计算的函数或变量,而 consteval 是一种必须在编译时计算的函数。constexpr 提供了更大的灵活性,但 consteval 提供了更严格的编译时计算保证。开发者应该根据具体需求选择使用 constexpr 还是 consteval。如果一个函数在编译时就能计算出结果,使用 consteval 更合适;如果需要在运行时也能正常执行,使用 constexpr 更合适。
任意类型变量 any variant
variant 编译期
std::variant 是 C17 引入的一个非常有用的类型,它可以表示一个在多个类型之间进行选择的值。它提供了一种安全和高效的方式来处理可能出现的多种类型。
以下是 std::variant 的一些主要特点: 可以包含多种类型: std::variant 可以存储不同类型的值,这些类型由开发者在定义 std::variant 时指定。 类型安全: 与使用 void* 或者联合(union)相比, std::variant 提供了更好的类型安全性。它可以在编译时就检查访问是否合法,从而避免运行时错误。 访问安全: std::variant 提供了多种安全的访问方式,如 std::get、std::visit 等,可以确保在访问时不会出现未定义行为。 无需手动内存管理: std::variant 会自动管理其包含的值的生命周期,开发者不需要手动分配或释放内存。
any 运行期
std::any 是 C17 引入的一个很有用的类型,它可以用来存储和传递任意类型的值。它提供了一个安全和高效的方式来处理动态类型的数据。
以下是 std::any 的一些主要特点: 可以存储任意类型的值: std::any 可以存储任何类型的值,包括基本数据类型、自定义类型、数组、指针等。 类型安全: 与使用 void* 相比, std::any 提供了更好的类型安全性。它会在运行时检查访问是否合法,从而避免未定义的行为。 无需手动内存管理: std::any 会自动管理其包含的值的生命周期,开发者不需要手动分配或释放内存。 支持赋值和拷贝: std::any 支持赋值和拷贝操作,这使得它可以很方便地在代码中传递和存储值。
示例
#include iostream
#include any
#include variant
#include stringint main() {// 使用 std::anystd::any anyValue 42;std::cout std::any value: std::any_castint(anyValue) std::endl;anyValue std::string(hello);std::cout std::any value: std::any_caststd::string(anyValue) std::endl;// 使用 std::variantstd::variantint, std::string variantValue 42;std::cout std::variant value: std::getint(variantValue) std::endl;variantValue std::string(hello);std::cout std::variant value: std::getstd::string(variantValue) std::endl;return 0;
}
any 和 variant区别
std::any 和 std::variant 都是 C17 引入的非常有用的类型,但它们在功能和使用场景上有一些区别: 存储类型: std::any 可以存储任意类型的值,包括自定义类型、数组、指针等。 std::variant 只能存储在定义时指定的有限个类型中的一种。 类型安全: std::any 提供了运行时类型检查,通过 std::any_cast 访问时会检查类型是否匹配,不匹配则抛出 std::bad_any_cast 异常。 std::variant 则是在编译时就确定了可能存储的类型,通过 std::get 等函数访问时也可以在编译时检查类型是否匹配。 访问方式: std::any 通过 std::any_cast 进行访问,需要显式指定类型。 std::variant 可以使用 std::get、std::visit 等多种方式进行访问。 使用场景: std::any 适用于需要处理动态类型数据的场景,如插件系统、配置文件解析等。 std::variant 则更适用于在有限的几种类型之间进行选择的场景,如函数重载、状态机等。 隐藏转换
这个模块可谓是本文的重点坑点是最多的。
临时对象具体化 抛砖 在 C 中,临时对象的具体化是一个非常重要的概念。临时对象是在表达式求值过程中创建的短暂对象,它们通常会在表达式结束后被销毁。
std::string getStr() {return hello;
}
std::string s getStr(); // 临时对象被具体化并赋值给s
在这个例子中, getStr() 函数返回一个临时的 std::string 对象,该对象会在 main()
函数中被具体化并赋值给 s。int x 1, y 2;
int z x y; // 临时对象被具体化并赋值给z
在这个例子中,x y 表达式创建了一个临时的 int 对象,该对象会被具体化并赋值给 z。std::functionint(int) get_lambda() {return [](int x) { return x * x; };
}
auto square_lambda get_lambda();
int result square_lambda(5); // 结果是 25在这个例子中:
1. get_lambda() 函数返回一个 lambda 表达式。
2. 这个 lambda 表达式产生了一个临时的 lambda 对象。
3. 这个临时的 lambda 对象被用于初始化 square_lambda 变量。
这个临时的 lambda 对象就是通过临时对象具体化的过程产生的。编译器会确保这个临时对象的生命周期足够长,以满足初始化 square_lambda 的需求。
保证拷贝省略 引玉 保证拷贝省略(Guaranteed Copy Elision, GCE)并不是 C17 引入的新特性,它实际上是在 C11 中引入的。 C11中引入了以下几种情况下的拷贝省略: 返回值优化(RVO) 当函数返回一个局部对象时,编译器可以直接在调用方的位置构造该对象,而无需进行拷贝。 移动语义优化 当返回一个临时对象时,编译器可以使用移动构造函数而不是拷贝构造函数。 构造函数参数优化 当构造函数的参数是一个临时对象时,编译器可以直接在目标位置构造该对象,而无需进行拷贝。
C17进一步扩展了拷贝省略的范围,增加了以下几种情况: 无参数构造函数拷贝省略 当函数返回一个局部对象,且该对象没有参数的构造函数时,编译器可以直接在调用方的位置构造该对象,而无需进行拷贝。 聚合类型拷贝省略 当函数返回一个聚合类型(如数组或结构体)的局部对象时,编译器可以直接在调用方的位置构造该对象,而无需进行拷贝。
总的来说,C11和C17中的拷贝省略优化可以显著提高程序的性能,减少不必要的拷贝操作。编译器会自动进行这些优化,开发者无需手动干预。
性能优化巅峰
先举个例子
#include iostreamclass Noisy {
public:Noisy() { std::cout Noisy object constructed at this \n; }Noisy(const Noisy other) {std::cout Noisy object copy-constructed at this \n;}Noisy(Noisy other) noexcept {std::cout Noisy object move-constructed at this \n;}Noisy operator(const Noisy other) {if (this ! other) {std::cout Noisy object copy-assigned at this \n;}return *this;}Noisy operator(Noisy other) noexcept {if (this ! other) {std::cout Noisy object move-assigned at this \n;}return *this;}~Noisy() {std::cout Noisy object destructed at this \n;}
};Noisy f(){// c11传统调用 创建一个临时 Noisy 对象,然后将其拷贝构造或移动构造到 v 中 // c17 编译器可以直接在 v 上构造 Noisy 对象,省略掉不必要的拷贝/移动操作。 称为(since C17) 保证拷贝省略Noisy v Noisy(); //注意这里没有任何函数调用 它是怎么做到给返回值给变量v赋值的
/*
解答
1.编译器识别到 Noisy v Noisy(); 是一个直接初始化语句。
2.它会在 v 的位置直接构造一个 Noisy 对象Noisy v,而不是先创建一个临时对象,然后再拷贝或移动到 v 中。
3.这个构造过程会调用 Noisy 类的默认构造函数,输出 Noisy object constructed at 0x[地址]。
4.最终,v 就直接成为一个 Noisy 类型的对象,不需要经过任何拷贝或移动操作。
*/ return v;
}void g(Noisy arg)
{std::cout arg arg \n;
}int main()
{// c11 会执行一次拷贝或移动操作,将 v 的内容复制或移动到返回值中。//C17引入命名返回值优化 NRVO允许编译器直接在返回值位置构造对象不调用构造函数,避免不必要的拷贝或移动。Noisy v f(); //同理 f()返回值v 如何实现给v初始化的/*
如果 f() 返回一个临时 Noisy 对象或者返回一个命名的 Noisy 对象(例如一个局部变量),编译器会尝试应用 RVO 和 NRVO 等优化,
尽量避免不必要的拷贝和移动操作。
编译器可以直接在 v 的位置构造这个返回的 Noisy 对象,避免任何拷贝或移动。
*/ std::cout v v \n;g(f());// (since C17) 拷贝省略
}
输出 constructed at 0x7fff1d765096 解释代码行 Noisy v Noisy(); 的调用
v 0x7fff1d765096
constructed at 0x7fff1d765097 解释代码行 Noisy v Noisy(); 的调用
arg 0x7fff1d765097destructed at 0x7fff1d765097
destructed at 0x7fff1d765096C11 和 C17 在这些优化上的差异: 临时变量的初始化: C11 及更早的版本中,Noisy v Noisy(); 会先创建一个临时 Noisy 对象,然后将其拷贝或移动到 v 中。这会涉及一次构造和一次拷贝/移动操作。C17 引入了保证拷贝省略(Guaranteed Copy Elision, GCE)特性,编译器可以直接在 v 上构造 Noisy 对象,省略掉不必要的拷贝/移动操作。 函数返回值的优化: C11 及更早的版本中,return v; 会执行一次拷贝或移动操作,将 v 的内容复制或移动到返回值中。C17 引入的命名返回值优化(Named Return Value Optimization, NRVO)允许编译器直接在返回值位置构造对象,避免不必要的拷贝或移动。 函数参数的传递: C11 及更早的版本中,g(f()); 会先创建一个临时 Noisy 对象,然后将其拷贝或移动到 arg 中。C17 的拷贝省略(Copy Elision)特性允许编译器直接在 arg 上构造 Noisy 对象,避免不必要的拷贝或移动。 上列的例子应该能让读者更清楚的对c17”保证拷贝省略“进一步的理解最后我们在整理一下思路,
临时变量的初始化: Noisy v Noisy() - Nosiy v();
函数返回值的优化: Noisy v {Noisy v; return v;} - Nosiy v();
函数参数的传递:主函数 nrvo优化
Noisy v f(); -
Noisy v {Noisy v Noisy(); return v; }- //临时变量初始化的优化
Noisy v {Noisy v(); return v; } -
Noisy v {return v} - //函数返回值的优化
Noisy v Noisy() -
Noisy v;g(f())
g(Noisy arg {Noisy v Noisy(); return v;})
g(Noisy arg {Noisy v(); return v;})
g(Noisy arg )隐式转换 C 隐式转换的内容总结如下: 隐式转换的顺序: 标准转换序列 - 包括值转换和一些数值转换 用户定义转换 - 通过单参数构造函数或转换函数进行 可能的额外标准转换序列 值转换: 左值到右值 数组到指针 函数到指针 临时对象化 - 将右值转换为左值 整数提升: 将小整数类型提升为 int 或 unsigned int 浮点提升: float 转换为 double 数值转换: 整数间转换 浮点间转换 浮点和整数间转换 指针间转换 指针到成员间转换 布尔转换 限定符转换: 在相似类型间添加或删除 const/volatile 限定符 上下文转换: 在特定上下文中进行的隐式转换,如条件表达式、逻辑运算符等
举例
标准转换序列:
int x 3.14; // float 到 int 的隐式转换用户定义转换:
class Rational {
public:Rational(int numerator, int denominator 1) {// 用户定义的单参数构造函数}
};
Rational r 5; // int 到 Rational 的隐式转换 5-Rational 因为没加explicit值转换:
int* p new int[10];
int x p[0]; // 数组到指针的隐式转换然后左值到右值的隐式转换整数提升:
char c a;int i c; // char 提升到 int浮点提升:
float f 3.14f;
double d f; // float 提升到 double限定符转换:
const int* p new int;
int* q const_castint*(p); // 从 const int* 到 int* 的转换上下文转换:
if (Rational r 5) { // Rational 到 bool 的隐式转换// ...
}
总结 总的来说,这篇文章全面介绍了C17中的一些重要语法和概念变化,对于了解和掌握C17的新特性很有帮助。文章内容丰富,逻辑清晰,可以作为C17学习的良好参考资料。