深圳网站设计兴田德润i优惠吗,南京网站排名,金融保险网站模板,中国造价工程建设管理协会网站目录
1、统一列表初始化
1.1 C98 的初始化方式
1.2 C11 后的统一列表初始化
1.3 C11中的initializer_list
1.3.1 初始化
1.3.2 访问元素
1.3.3 应用
2、变量类型推导
2.1 auto
2.1.1 auto 的使用
2.1.2 注意事项
2.2 decltype
2.2.1 语法形式
2.2.2 函数返回值类…目录1、统一列表初始化1.1 C98 的初始化方式1.2 C11 后的统一列表初始化1.3 C11中的initializer_list1.3.1 初始化1.3.2 访问元素1.3.3 应用2、变量类型推导2.1 auto2.1.1 auto 的使用2.1.2 注意事项2.2 decltype2.2.1 语法形式2.2.2 函数返回值类型的推导2.2.3 配合 typedef 或 using2.2.4 注意实现2.2.5 与auto的区别2.3 nullptr3、范围 fo4、STL 的变化4.1 新增的容器4.2 字符串转换函数# C11 是 C 的第二个主要版本并且是从 C98 起的最重要更新。它引入了大量更改标准化了既有实践并改进了对 C 程序员可用的抽象。在它最终由 ISO 在 2011 年 8 月12 日采纳前人们曾使用名称 “C0x”因为它曾被期待在 2010 年之前发布。C03 与 C11 期间花了 8 年时间故而这是迄今为止最长的版本间隔。从那时起C 有规律地每 3 年更新⼀次。1、统一列表初始化1.1 C98 的初始化方式在 C11 之前初始化不同类型的对象往往有着不同的语法规则例如对于普通内置类型我们可以像这样初始化int a 5; // 传统的赋值初始化方式对于数组可以使用如下方式int arr[3] {1, 2, 3};而对于类对象如果有合适的构造函数可能会通过构造函数来初始化像class MyClass { public: MyClass(int x, int y) : m_x(x), m_y(y) {} private: int m_x; int m_y; }; MyClass obj(10, 20); // 通过构造函数初始化对象# 这种多样化的初始化语法在复杂的代码环境中容易造成混淆并且当涉及到更复杂的类型比如 STL 容器等时初始化的方式可能不够直观。1.2 C11 后的统一列表初始化# C11 的统一列表初始化旨在解决这些问题提供一种通用的、能适用于多种类型的初始化语法。# 统一列表初始化使用花括号 { } 来进行初始化操作以下是一些常见的示例展示其语法应用内置类型初始化int num { 10 }; // 使用统一列表初始化语法初始化整型变量 double d { 3.14 }; // 初始化双精度浮点型变量# 这里不再局限于传统的 赋值初始化方式通过花括号可以更直观地表示这是一个初始化操作。使用初始化列表时可添加等号()也可不添加。数组初始化int arr[] { 1, 2, 3 }; // 省略数组大小编译器会根据初始化列表中的元素个数自动推断数组大小 int anotherArr[5] { 1, 2 }; // 部分初始化未指定的元素会被初始化为 0对于基本数据类型而言 int* pa new int[4] { 0 };//new表达式也可同时初始化# 与之前的数组初始化语法相比更加简洁明了尤其是省略数组大小的情况让代码更具灵活性。类对象初始化默认构造函数情况如果类有默认构造函数我们可以这样初始化对象class SimpleClass { public: SimpleClass() {} }; SimpleClass sc {}; // 使用统一列表初始化调用默认构造函数带参数构造函数情况# 对于带有参数的构造函数的类可以直接传递参数列表在花括号内进行初始化class Point { public: Point(int x, int y) : m_x(x), m_y(y) {} private: int m_x; int m_y; }; Point p { 3, 5}; // 通过统一列表初始化调用带两个参数的构造函数1.3 C11中的initializer_list# initializer_list 是 C11 一个标准库中的类模板。它的主要作用是用于表示一个特定类型的元素列表旨在方便地处理那些能够以列表形式进行初始化的情况比如在类的构造函数中接收一组初始化值或者用于函数参数传递一组同类型的数据等。1.3.1 初始化# 要使用 initializer_list 首先需要包含对应的头文件 initializer_list 然后就可以像声明其他模板类型一样来声明它。例如如果我们想创建一个用于存放整型数据的 initializer_list可以这样写#include initializer_list #include iostream int main() { std::initializer_listint my_list {1, 2, 3}; return 0; }# 在上述代码中std::initializer_list int 声明了一个能够容纳整型元素的 initializer_list 类型的对象 my_list并通过花括号 { } 初始化它使其包含了1、2、3这三个整型元素。1.3.2 访问元素# initializer_list 提供了类似于容器的访问方式不过它相对来说比较简单主要有两个常用的成员函数begin()和end()。这两个函数返回的是指向列表中第一个元素和最后一个元素之后位置的迭代器遵循 STL 容器迭代器的通用规则我们可以利用这两个迭代器来遍历整个列表中的元素。示例如下#include initializer_list #include iostream int main() { std::initializer_listint my_list {4, 5, 6}; for (auto it my_list.begin(); it! my_list.end(); it) { std::cout *it ; } return 0; }# 在这个示例中通过迭代器遍历 initializer_list 中的元素并将每个元素输出到控制台最终会打印出4 5 6。# 此外由于 initializer_list 支持基于范围的 for 循环所以我们也可以更简洁地遍历它如下所示#include initializer_list #include iostream int main() { std::initializer_listint my_list {7, 8, 9}; for (int element : my_list) { std::cout element ; } return 0; }# 同样这段代码也能正确地将7、8、9依次打印出来基于范围的 for 循环在内部其实也是利用了begin()和end()这两个迭代器来实现元素的遍历。# 需要注意的是initializer_list 所表示的元素列表是不可变的也就是说一旦创建了 initializer_list 对象就不能修改其内部的元素了。例如下面这样尝试修改元素的代码是无法通过编译的#include initializer_list #include iostream int main() { std::initializer_listint my_list {10, 11, 12}; // 以下代码会编译出错因为不能修改 initializer_list 中的元素 *(my_list.begin()) 13; return 0; }# 这种不可变的特性符合它通常用于初始化操作的设计初衷确保传递进来的初始化数据在初始化过程中不会被意外更改。1.3.3 应用# 当一个类存在多个构造函数时包含接收 initializer_list 的构造函数会参与到构造函数的重载决议中。通常情况下如果调用构造函数时传递的参数形式符合 initializer_list 的初始化列表形式那么编译器会优先调用对应的接收 initializer_list 的构造函数。例如class MyClass { public: MyClass(int value) { std::cout Constructor with single int parameter called. std::endl; } MyClass(std::initializer_listint list) { std::cout Constructor with initializer_list parameter called. std::endl; } }; int main() { MyClass obj1(5); // 调用单个整型参数的构造函数 MyClass obj2({5}); // 调用接收 initializer_list 的构造函数 MyClass obj3{5}; // 同样调用接收 initializer_list 的构造函数 return 0; }# 在这个例子中obj1 的初始化调用了普通的带单个整型参数的构造函数而 obj2 和 obj3 的初始化由于传递参数的形式是花括号包裹的单个元素符合 initializer_list 的语法特征所以编译器会调用接收 initializer_list 的构造函数。# 所以说 initializer_list 为一些不定参数的类提供了一种更加方便的初始化方式。# 特别是对于标准模板库STL中的容器如 vector、list、map 等这一点得到了很好的应用。vectorint v { 1,2,3,4 }; listint lt { 1,2 }; // 这里{sort, 排序}会先初始化构造一个pair对象 mapstring, string dict { {sort, 排序}, {insert, 插入} }; // 使用大括号对容器赋值 v {10, 20, 30};# 之所以能够这样使用的原因就是 STL 容器在实现时提供了对应的 initializer_list 构造对应对象的构造函数与赋值重载。比如说以下是 list 的构造函数2、变量类型推导2.1 auto2.1.1 auto 的使用# 在传统的 C 编程中声明变量时需要明确指定变量的类型例如int num 10; double pi 3.14; std::string str Hello, World!;# 然而随着 C 语言的发展以及模板编程、STL标准模板库等复杂特性的广泛应用类型的书写有时候会变得非常冗长和繁琐尤其是涉及到一些复杂的模板类型或者函数返回值类型难以直接明确表述时。auto 关键字就是为了解决这个问题而诞生的它能够让编译器根据变量的初始值自动推导出变量的类型。# 简单来说使用 auto 关键字我们可以在声明变量时省略具体的类型声明部分让编译器帮我们去确定这个变量实际应该是什么类型代码的书写也就变得更加简洁。例如auto num 10; // 编译器自动推导出 num 为 int 类型 auto pi 3.14; // 编译器自动推导出 pi 为 double 类型 auto str std::string(Hello, World!); // 编译器自动推导出 str 为 std::string 类型2.1.2 注意事项# 如果初始化表达式带有顶层 const即修饰变量本身表示变量的值不能被修改使用 auto 推导时顶层 const 会被忽略变量本身不会成为 const 类型。例如const int num 10; auto var num; // var 的类型为 int顶层 const 被忽略var 可以被重新赋值# 但是如果想要保留顶层 const 属性可以使用 const auto 的形式来声明变量如下所示const int num 10; const auto var num; // var 的类型为 const int不能被重新赋值# 对于底层 const即修饰指针或引用所指向的对象表示不能通过该指针或引用修改所指向的对象auto 关键字会正确地推导并保留底层 const 属性。例如const int num 10; const int* ptr num; auto new_ptr ptr; // new_ptr 的类型为 const int*保留了底层 const不能通过 new_ptr 修改所指向的对象2.2 decltype# decltype 的主要作用是用于查询表达式的类型编译器会根据给定表达式的实际类型来推导出对应的类型然后可以用这个推导出的类型去定义变量、作为函数返回值类型等。# 简单来说就是让编译器帮你自动确定某个表达式的类型是什么而不需要你显式地去指定类型。2.2.1 语法形式# decltype表达式就是其基本语法结构例如int num 10; decltype(num) anotherNum; // anotherNum被推导为int类型因为num是int类型# 这样当你已经有了一个确定类型的变量又想定义同类型的其他变量时decltype 就很方便不需要重复写具体的类型尤其是对于复杂类型比如自定义结构体指针类型等情况使用decltype 能避免手动书写复杂类型名时出错。2.2.2 函数返回值类型的推导# 在 C11 之前函数返回值类型需要在函数声明时就明确写出来但是有些复杂情况很难提前确定具体类型这时 decltype 就可以帮忙了。例如templatetypename T1, typename T2 auto add(T1 a, T2 b) - decltype(a b) { // 根据ab表达式的类型来确定函数返回值类型 return a b; }# 上述代码定义了一个函数模板 add它可以接受不同类型的参数( T1 和 T2 类型然后通过decltypeab来让编译器自动推导 ab 运算结果的类型并将这个类型作为函数 add 的返回值类型。这样函数就能灵活地适应各种支持 运算的不同类型参数组合了比如 int 和 double 相加、两个自定义类对象如果重载了 运算符相加等情况。2.2.3 配合 typedef 或 usingint arr[5]; using ArrayType decltype(arr); // 使用decltype推导arr的类型在这里是int[5]即包含5个元素的int数组类型并定义别名ArrayType ArrayType anotherArr; // 相当于定义了int[5]类型的anotherArr数组# 这种方式可以方便地给一些复杂的、通过表达式才能确定的类型创建一个更简洁易懂的别名便于后续代码中使用该类型进行变量定义等操作。2.2.4 注意实现# decltype 在推导类型时括号的存在与否可能会导致不同的结果。例如int num 10; decltype((num)) refToNum num; // 这里推导的是int类型是num的引用因为(num)是一个左值表达式 decltype(num) anotherNum num; // 这里推导的是int类型anotherNum是一个新的int变量# 在使用时要特别留意如果想推导得到引用类型通常可以使用带括号的表达式形式前提是该表达式本身对应的是一个左值。2.2.5 与auto的区别# 虽然 auto 和 decltype 都和类型推导相关但它们有明显不同auto 是根据变量初始化表达式来推断变量的类型并且它推断出来的几乎都是值类型非引用类型除非明确使用或修饰比如 auto x 5x 就是 int 类型值类型。decltype 是根据给定的表达式本身的类型来推导不管这个表达式是左值、右值等情况而且它能准确推导出引用类型等就像前面例子中展示的那样decltype((num)) 推导出的就是引用类型。2.3 nullptr# 在 C 早期版本中通常使用 NULL 来表示空指针。然而NULL 实际上是一个宏定义在 C 语言中它一般被定义为 void*0但在 C 中为了保持类型安全 ( 因为 C 中不能直接将 void* 类型隐式转换为其他指针类型)它被定义为整数类型的 0。/* Define NULL pointer value */ #ifndef NULL #ifdef __cplusplus #define NULL 0 #else /* __cplusplus */ #define NULL ((void *)0) #endif /* __cplusplus */ #endif /* NULL */# 这就可能导致一些混淆和潜在的类型不匹配问题特别是在函数重载等场景下。例如void func(int i) { std::cout Called func(int) std::endl; } void func(void *p) { std::cout Called func(void *) std::endl; } int main() { func(NULL); // 在C中会调用func(int)而不是期望中的func(void *)因为NULL被当作整数0处理了 return 0; }# 为了解决这类问题C11 引入了 nullptr 关键字来明确地表示空指针它具有更好的类型安全性。类型特性nullptr 的类型是 std::nullptr_t这是一种特殊的类型。所有的指针类型都可以隐式转换为 std::nullptr_t 类型反过来std::nullptr_t 类型也可以隐式转换为所有的指针类型这使得它能很自然地在各种涉及指针的场景中表示空指针的情况。例如int *ptr1 nullptr; // 正确将nullptr赋值给int指针类型 double *ptr2 nullptr; // 同样正确用于double指针类型语法使用在代码中当你需要将一个指针初始化为空指针状态或者将一个指针赋值为空指针时就可以直接使用 nullptr。比如class MyClass { // 类的定义相关内容 }; MyClass *objPtr nullptr; // 初始化一个指向MyClass对象的空指针 if (objPtr nullptr) { // 执行相关逻辑判断指针是否为空 }3、范围 for# C11 中的范围 for 循环也称作基于范围的 for 循环Range-based for loop是一种语法糖它提供了一种简洁且方便的方式来遍历容器如数组、vector、list 等或其他可迭代对象中的元素。以下是关于它的详细介绍# 范围 for 循环的基本语法格式如下for ( decltype(容器)::value_type 元素变量名 : 容器对象 ) { // 循环体在这里可以对元素变量名所代表的元素进行操作 }不过在实际使用中更常见、更简洁的写法是编译器会自动进行合适的类型推导等处理for ( auto 元素变量名 : 容器对象 ) { // 循环体对元素进行操作 }# 例如下面分别是使用传统 for 循环和范围 for 循环遍历 vector 容器的示例对比# 传统 for 循环遍历 vector#include iostream #include vector int main() { std::vectorint vec {1, 2, 3, 4, 5}; for (size_t i 0; i vec.size(); i) { std::cout vec[i] ; } std::cout std::endl; return 0; }# 使用范围 for 循环遍历 vector#include iostream #include vector int main() { std::vectorint vec {1, 2, 3, 4, 5}; for (auto element : vec) { std::cout element ; } std::cout std::endl; return 0; }# 可以看到范围 for 循环的代码更加简洁直观无需手动去管理索引、判断边界等操作。# 当使用范围 for 循环时编译器实际上会将其展开为类似传统 for 循环的代码形式它会自动获取容器的开始迭代器和结束迭代器通过调用容器的begin()和end()方法如果容器不支持这两个方法则无法使用范围 for 循环进行遍历然后按照迭代器的顺序依次访问容器中的每个元素每次将当前元素绑定到循环中定义的变量上使得在循环体中可以方便地对元素进行操作。4、STL 的变化4.1 新增的容器# 首先 C11 中新增了四个容器分别是 arrayforward_list、unordered_map 和 unordered_set。其中 forward_list、unordered_map 与 unordered_set 在前面我们已经介绍过了接下来我们来简要介绍一下 array。# array 是 C11 引入的一个固定大小的数组容器它位于 array 头文件中。它封装了 C 中内置的普通数组提供了更符合现代 C 编程风格的接口以及一些额外的便利性和安全性保障在行为表现上更像是一个“智能数组”。特点及优势固定大小与普通的C风格数组一样在定义时就确定了其大小后续不能动态改变。例如std::arrayint, 5 myArray 就定义了一个能容纳 5 个 int 类型元素的 std::array这个大小在编译时就固定下来了。安全性提升它避免了普通数组常见的一些越界访问等问题因为它重载了 [ ] 算符等操作并且会在编译阶段对一些非法访问进行检查。例如当你试图访问超出其大小范围的元素时编译器会报错而不像普通数组那样可能在运行时产生难以察觉的错误比如访问了未初始化的内存区域等情况。# 例如#include iostream #include array int main() { std::arrayint, 4 arr {1, 2, 3, 4}; for (size_t i 0; i arr.size(); i) { std::cout arr[i] ; } std::cout std::endl; return 0; }4.2 字符串转换函数# C11 也提供了各种内置类型与 string 之间相互转换的函数比如 to_string、stoi、stol、stod 等函数。# 除此之外 C11 也为每个容器都增加了一些新方法比如提供了一个以 initializer_list 作为参数的构造函数用于支持列表初始化。提供了 cbegin 和 cend 方法用于返回 const 迭代器。提供了 emplace 系列方法并在容器原有插入方法的基础上重载了一个右值引用版本的插入函数用于提高向容器中插入元素的效率。