闵行区做网站公司山东最新消息今天
web/
2025/10/6 1:51:25/
文章来源:
闵行区做网站公司,山东最新消息今天,网站建设和实现论文,网站的整体结构C11新特性选讲 语言部分 侯捷
本课程分为两个部分#xff1a;语言的部分和标准库的部分。只谈新特性#xff0c;并且是选讲。
本文为语言部分笔记。
语言 Variadic Templatesmove semanticsautoRange-based for loopInitializer listLambdas… 标准库 type_traitsunodered…C11新特性选讲 语言部分 侯捷
本课程分为两个部分语言的部分和标准库的部分。只谈新特性并且是选讲。
本文为语言部分笔记。
语言 Variadic Templatesmove semanticsautoRange-based for loopInitializer listLambdas… 标准库 type_traitsunodered containersforward_listarraytupleconcurrencyRegEx…
关于头文件
C11的新特性包含语言和标准库两部分后者以头文件 header files 的形式呈现。
关于C的头文件有以下几点
C标准库的头文件均不带 .h如 #include iostream在C中旧式的C的头文件带有 .h依然可用如 #include stdio.h建议在C中使用新式的C头文件与旧式的关系xxx.h - cxxx如 #include cstdio
Variatic Templates
函数变参模板
假如我想设计一个函数 print它能够接收任意数量的参数并且这个参数的类型也是任意的就可以利用 Variatic Templates 来递归地实现
#include iostream
void print() {} // 1templatetypename T, typename... Types // 2
void print(const T firstArg, const Types... args) { // 3std::cout firstArg std::endl;print(args...); // 4
}int main() {print(dfafda, s, 123);
}注意这里的 ... 可不是我们口语中的省略号而是实实在在的C11新语法的一部分可以将它理解为一个 pack 包具体是什么 “包”则取决于它出现在哪里。在本例中... 共出现了三次
用于 template parameters就是 template parameters pack”模板参数包“如2处用于 function parameters types就是 function parameters types pack”函数参数类型包“如3处用于 function parameters就是 function parameters pack“函数参数包”如4处
在变参模板中如果我们想要知道可变参数的个数可通过sizeof...(args)。
注意除了2处 我们在1处定义了一个空参数列表的 print 函数的重载版本它在 print 函数地参数列表中的参数被递归地打印完之后被调用其实就是相当于我们 print 函数的递归退出的条件。
思考以下这个 print 函数的重载版本能够与上面的 print 函数并存吗如果可以谁比较泛化谁比较特化呢我们知道两个版本均可的情况下较为特化的版本会被优先调用
template typename... Types
void print(const Types... args) {/* --- */
}变参模板的花式应用
万能的hash function多种函数重载 递归 函数变参模板 —花式调用 tuple类变参模板 继承 类变参模板
一些”小“的新特性
Spaces in Template Expressions
在C11之前如果有模板嵌套右侧的两个尖括号不能靠在一起中间须有空格否则编译器会认为那是个右移运算符在C11之后编译器变聪明了不再需要这个空格。
vectorlistint ; // OK in each C version
vectorlistint; // OK since C 11nullptr and std::nullptr_t
在C11之后我们可以使用 nullptr 而非之前的 NULL 或者 0来表示空指针。注意 NULL 就是一个宏其值为0而 nullptr 确实是个指针其类型为 std::nullptr_t。下面的例子可以验证
#include iostreamvoid f(int) {std::cout call f(int) std::endl;
}void f(void*) {std::cout call f(void*) std::endl;
}int main() {f(0); // calls f(int)f(NULL); // calls f(int) if NULL is 0; ambiguous otherwisef(nullptr); // calls f(void*)
}auto
自动类型推导 auto在C11之后可以用 auto 来定义变量的类型编译器会自动进行类型推导。
auto i 42; // i是int类型
double f();
auto d f(); // d是double类型注意不建议在任何时候都使用 auto 而是推荐在这个变量的类型名称实在是很长或者很复杂实在是懒得打那么多字时使用但是我们要知道变量应该是什么类型如
vectorstring v;
auto pos v.begin(); // 过长auto f [](int x) - bool { // 过于复杂// ...
}程序员要做到对自己的变量类型心中有数。
Uniform Initialization
在C11之前许多程序员会疑惑一个变量或者对象的初始化可能会发生于小括号大括号赋值运算符。如
vectorint vec(3, 5);
vectorint vec {1, 2 ,3};
int a 1;C11引入一致初始化使用大括号在变量后面直接跟大括号大括号中可以有任意多个元素个数设置初值进行初始化如
int values[] {1, 2, 3};
vectorint v {2, 3, 4};
complexdouble {4.0, 3.0};当然之前的语法也是可用的。
实际上编译器看到 {} 就会作出一个 initializer_listT它关联至一个 arrayT, n。调用函数如ctor时该 array 的元素被编译器分解逐一传给函数。需要注意的是若某个函数参数就是个 initializer_listT调用者不能传递数个 T 参数然后以为它们会被自动转换为一个 intializer_listT 传入即需要自己手动将数个参数转换为 initializer_listT 再进行传值。
比如
vectorstring cities {Berlin, New York, London};这形成一个 initializer_liststring 背后有个 arraystring, 3。调用 vectorstring 的 ctors构造函数中的接收 initialize_liststring 的版本标准库中所有容器都有接收这个 initializer_listT 的构造函数。
但是对于我们自己的类可能没有接收 intializer_listT 这种参数的构造函数此时这个初始化列表逐一分解拆成一个一个的参数传给函数再去找与多个单个参数相匹配的构造函数。
initializer_list
初始化列表是支持上面提到的大括号形式的一致性初始化的背后方法。
为了支持用户自定义的类的 initializer_list。C11提供了一个类模板std::initializer_listT。他可以用用于使用一包参数值来进行初始化或者用来其他你想要处理一包参数的地方。如使用initalizer_list传参
#include iostreamvoid print(std::initializer_listint vals) {for (auto ite vals.begin(); ite!vals.end(); ite) {std::cout *ite \n;}
}int main() {print( {1,2,3,4} ); // 使用initalizer_list传参
}即 {} 即可形成一个 initializer_list 不同于前面的 variadic template这里的 initializer_list 需要的是固定类型 T 的任意多个参数。也可以看做是一种容器。 initializer_list背后由array构建。 intializer_list如果被拷贝会是浅拷贝引用语义
在C11之后的标准库中initializers_list 有许多应用最常见的肯定是上面提到过的各个容器的构造函数中可以使用其作为参数。另外在一些算法中也有应用比如 min/max 函数在C11之前它们只能支持两个元素的比较
std::min(1, 2);在C11之后借助 initializer_list 它可以支持多个元素的比较
std::min( {1, 2, 3, 4} );range-based for loop
在C11之后
for (decl : coll) {statement;
}如
std::vectorint vec {1, 2, 3, 4};
for (int i : vec) {std::cout i std::endl;
}也可以用引用
std::vectordouble vec;
for (auto elem : vec) {elem * 3; // 因为是引用所以会改变原vector
}类似python的for loop
for i in range(10):print(i)实际上这种for loop的背后实现就是将该容器的迭代器取出来并遍历一遍并将遍历过程中的每个元素赋值到左侧声明出来的变量。
这种for loop赋值时可能会做隐式类型转换。
default, delete
如果你自行定义了一个 ctor那么编译器就不会再给你一个 default ctor但是如果你强制加上 default 可以空格就可以重新获得并使用默认的 default ctor。而如果加上 delete则是禁用该成员函数的使用。
class Zoo {
public:Zoo(int i1, int i2) : d1(i1), d2(i2) {} // 构造函数Zoo(const Zoo) delete; // 拷贝构造Zoo(Zoo) default; // 移动构造Zoo operator(const Zoo) default; // 拷贝赋值Zoo operator(const Zoo) delete; // 移动赋值virtual ~Zoo() {} // 析构函数
private:int d1, d2;
}default
每当我们声明一个有参构造函数时编译器就不会创建默认构造函数。在这种情况下我们可以使用 default 说明符来创建默认的构造函数。以下代码演示了如何创建
// use of defaulted functions
#include iostream
using namespace std;class A {
public:// A user-definedA(int x){cout This is a parameterized constructor;}// Using the default specifier to instruct// the compiler to create the default implementation of the constructor.A() default;
};int main(){A a; //call A()A x(1); //call A(int x)coutendl;return 0;
} delete
在C 11之前操作符delete 只有一个目的即释放已动态分配的内存。而C 11标准引入了此操作符的另一种用法即禁用成员函数的使用。这是通过附加 delete 来完成的; 说明符到该函数声明的结尾。
使用 delete 说明符禁用其使用的任何成员函数称为explicitly deleted函数。
虽然不限于它们但这通常是针对隐式函数。以下示例展示了此功能派上用场的一些任务
禁用拷贝构造函数
// copy-constructor using delete operator
#include iostream
using namespace std; class A {
public: A(int x): m(x) { } // Delete the copy constructor A(const A) delete; // Delete the copy assignment operator A operator(const A) delete; int m;
}; int main() { A a1(1), a2(2), a3(3); // Error, the usage of the copy assignment operator is disabled a1 a2; // Error, the usage of the copy constructor is disabled a3 A(a2); return 0;
} 禁用不需要的类型转换
// type conversion using delete operator
#include iostream
using namespace std;
class A {
public: A(int) {} // Declare the conversion constructor as a deleted function. Without this step, // even though A(double) isnt defined, the A(int) would accept any double value// for its argumentand convert it to an int A(double) delete;
};
int main() { A A1(1); // Error, conversion from double to class A is disabled. A A2(100.1); return 0;
} 请注意删除的函数是隐式内联的这一点非常重要。删除的函数定义必须是函数的第一个声明。换句话说以下方法是将函数声明为已删除的正确方法
class C {
public:C(C a) delete;
};但是以下尝试声明删除函数的方法会产生错误
// incorrect syntax of declaring a member function as deleted
class C {
public: C();
}; // Error, the deleted definition of function C must be the first declaration of the function.
C::C() delete;
最后明确删除函数有什么好处
删除特殊成员函数提供了一种更简洁的方法来防止编译器生成我们不想要的特殊成员函数。如“禁用拷贝构造函数”示例中所示。
删除正常成员函数或非成员函数可防止有问题的类型导致调用非预期函数如“禁用不需要的参数转换”示例中所示。
Big Five指每个类的拷贝控制即构造函数、拷贝构造函数、移动构造函数、拷贝赋值函数、移动赋值函数、析构函数。它们默认是 public 且 inline 的。
default 不能用于 Big Five 之外的常规函数编译会报错因为其他函数并没有默认的版本。delete 可以用于任何函数身上但好像没什么意义不需要某个函数不写就是了为什么要写了再delete呢注意类似的 0 只能用于虚函数这样会使得该虚函数称为纯虚函数强迫子类重写该函数。
alias template (template typedef)
带参数的别名模板。
template typename T
using Vec std::vectorT, MyAllocT;注意这里的 using 关键字并不是 C11 的新东西但是 using 关键字的这种使用方法是C11之后的新的用来做 alias template 的方法。
在经过了上面的定义之后以下两种写法是等价的
Vecint coll;
// 等价于
std::vectorint, MyAllocint coll;如此我们可以方便地使用我们自己的分配器 MyAlloc 创建一个类型可选的 vector 对象。
注意大家注意到这种用法和我们的宏定义和 typedef 好像有些类似但是实际上使用 macro 宏定义或 typedef 均无法实现上面的效果。 若使用宏定义 #define VecT templatetypename T std::vectorT, MyAllocT;我们知道宏定义就是机械地字符替换所以在使用时 Vecint coll;
// 等价于
templatetypename int std::vectorint, MyAllocint;完全不是我们想要的意思。 若使用 typedef 也不行因为 typedef 是不接收参数的。 至多写成 typedef std::vectorint, MyAllocint Vec;这当然也不是我们想要的没办法指定变量的类型。
注意 alias template 不能做偏特化或全特化。
type alias (similar to typedef)
using value_type T;
// 等价于
typedef T value_type;与上面的 alias template 类似这里的 using 关键字的这种使用方法是C11之后的新的用来做 type alias 的方法。
using func void(*)(int, int);
// 等价于
typedef void (*func)(int, int);// 使用func作为函数指针类
void example(int, int) {}
func fn example;后面这个例子中的 func 被定义为一种类型它是一个函数指针类型。
using关键字总结 using-directives如 using namespace std; using-declarations for namespace members如 using std::cout; using-declarations for class members如 using _Base::_M_allocate; type alias (since C11)如 template typename T
using Vec std::vectorT, MyAllocT;alias template (since C11)如 using func void(*)(int, int);
noexcept
void foo() noexcept {// ...
}程序员保证 foo() 函数不会抛出异常让别人/编译器“放心地”调用该函数。
实际上 noexcept 关键字还可以加参数来表示在…条件下函数不会抛出异常上面的 void foo() noexcept ; 就等价于 void foo() noexcept(true); 即相当于无条件保证。
而下面
void swap(Type x, Type y) noexcept(noexcept(x.swap(y))) {x.swap(y);
}则意为在 x.swap(y) 不抛出异常的条件下本函数不会抛出异常。
补充一下异常是这样的如果 A 调用 BB 调用 C而在 C 执行的过程中出现了异常则先看 C 有没有写明异常处理程序如果有则处理如果没有则异常传递给 B然后看 B 有没有对应的异常处理程序如果有则处理如果也没有则继续传递给 A。即按照调用顺序一层一层地向上传递直到有对应的异常处理程序。如果用户一直没有异常处理程序则执行 std::terminate() 进而执行 std::abort() 程序退出。
override
override 关键字标明重写应用于虚函数身上。
考虑下面这种情况
struct Base {virtual void vfunc(float) { }
};struct Derived: Base {// virtual void vfunc(int) { }virtual void vfunc(int) override { }
}子类 Derived 在继承父类 Base 之后想要重写父类的 void vfunc(float) 方法但是我们知道要重写父类方法需要函数签名完全一致这里可能由于疏忽大意将参数类型写为了 int。这就导致子类的这个函数定义了一个新函数而非是期望中的对于父类函数的重写了。而编译器肯定是不知道你其实是想重写父类方法的因为你函数签名的不一致就按照一个新方法来处理了。
在 C11 之后引入了 override 关键字在你确实想要重写的函数之后加上这个关键字编译器会在你在想重写但是函数签名写错的时候提醒你这个被标记为重写函数的函数实际上并未进行重写。
final 修饰类使得该类不能被继承 struct Base final {};struct Derived: Base {}; // ErrorBase 类被 final 关键字修饰使得其不能被继承下面的 Derived 试图继承它会报错。 修饰虚函数使得该虚函数不能被重写 struct Base {virtual void func() final;
}struct Derived : Base {void func(); // Error
}Base 类本身没有被 final 修饰所以可以被继承。但是其虚函数 func() 被 final 关键字修饰故 func() 不能被重写。下面的 Derive 类试图重写它会报错。
decltype
获取一个变量/一个对象的类型 即 tpyeof(a) 是非常常见的需求但是在 C11 之前并没有直接提供这样的关键字仅有 typeid 等。 decltype 可以满足这一需求方便地获得变量 / 对象的类型。
decltype 用来定义一种类型该类型等同于一个类型表达式的结果。如 decltype(xy) 定义了 xy 这个表达式的返回类型。
mapstring, float coll;
decltype(coll)::value_type elem;在C11之前只能
mapstring, float::value_type elem;这当然不能让我们在未知变量 / 对象的类型的条件下知道其类型。
decltype 的三种应用场景
1-用来声明返回值类型
有时候函数返回值的类型取决于参数的表达式的执行结果。然而在C11之前没有 decltype 之前以下语句是不可能成立的
templatetypename T1, typename T2
decltype(xy) add(T1 x, T2 y);因为上面的返回值的类型使用了尚未引入且不再作用域内的对象。
但是在C11之后我们可以通过在函数的参数列表之后声明一个返回值类型来实现
templatetypename T1, typename T2
auto add(T1 x, T2 y) - decltype(xy);这与 lambda 表达式声明返回值的语法相同 [...](...)mutableoptthrowSpecopt−retTypeopt{...}[...](...)\ mutable_{opt}\ throwSpec_{opt}-retType_{opt}\{...\} [...](...) mutableopt throwSpecopt−retTypeopt{...} 2-元编程
元编程是对模板编程的运用。
举例
typdef typename decltype(obj)::iterator iType;
// 类似 typedef typename T::iterator iType;
decltype(obj) anotherObj(obj);3-传递lambda的类型
面对lambda我们手上往往只有对象没有类型要获得其类型就得借助于 decltype 。
如
auto cmp [] (const Person p1, const Person p2) {return /* 给出Person类比大小的准则 */
}//...
std::setPerson, decltype(cmp) collcmp;我们知道由于 set 是有序容器所以在将自定义的类构成一个 set 的时候需要给出该类比大小的准则谓词通常是函数、仿函数或者 lambda 表达式。但是这里我们同样需要指定类型这就可以用 decltype 来指定。
lambdas
C11 引入了 lambdas 允许定义一个单行的函数可以用作是参数或者局部对象。Lambdas 的引入改变了C标准库的使用方式比如原来的一些仿函数谓词现在可直接用。
基本用法
最简单的 lambda 函数不接收参数并做一些简单的事情比如这里的打印一句话
[] { std::cout Hello Lambda std::endl; }我们可以直接调用它就像调用普通函数和函数对象那样用 ()
[] { std::cout Hello Lambda std::endl; }();虽然可以这样直接低啊用但是这样其实没什么意义因为你想要打印直接打印就好了没必要再绕个圈子我们通常将 lambda 函数赋值给一个变量这样就能像调用普通函数那样多次调用它
auto l [] { std::cout Hello Lambda std::endl; };
l();
l();
l();这里 lambda 对象的类型很复杂通常也没有必要显式地写出来我们正好用前面介绍过的 C11 中的 auto 来简化我们的代码。如果一定要拿到 lambda 函数对象的类型参考上面的 decltype 的用法三。
完整形式
lambda 表达式的完整形式 [...](...)mutableoptthrowSpecopt−retTypeopt{...}[...]\ (...)\ mutable_{opt}\ throwSpec_{opt}-retType_{opt}\{...\} [...] (...) mutableopt throwSpecopt−retTypeopt{...}
lambda 函数除了少数几处细节如没有默认构造函数、需要加mutable几乎完全等同于一个对应的函数对象[] 称为 lambda introducer 其中存放要捕获的外部变量表外部变量要注意区分值传递和引用传递。如果里面放一个等号[, y] 表示接收以值传递的形式接收所有的外界变量不太建议用要把自己用到的变量写清楚。() 中存放 lambda 函数的参数列表{} 是 lambda 函数的函数体中间的三项标明 opt 的都是看情况可有可无的但是一旦三个中有一个是出现的那么小括号 () 就必须有而若三个可选项都没有则 () 也是可有可无的。mutable 指明参数是否可被改变throwSpec 指明是否可能会抛出异常retType 指明返回值的类型lambda 函数默认是内联的
举例
#include iostreamint main() {int id 0;auto f [id] () mutable {std::cout id: id std::endl;id;};id 42;f();f();f();std::cout id std::endl;
}输出
id: 0
id: 1
id: 2
42注意
在定义 lambda 函数 f() 时就已经把 id 以值传递的形式传给函数因此后面 id 的改变不会影响函数真正被调用时的 id 值不加 mutable 关键字会报 id 是只读变量不能修改。
varidic template 变参模板详解
原视频这里花了很大篇幅来讲解变参模板及其应用这个极其重要的新特性但是考虑到在新手日常编程中的使用并不是太多而多是出现在大型模板库的设计中这里暂时略过以后再回来补。
Ref
https://blog.csdn.net/weixin_38339025/article/details/89161324
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/87673.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!