花了点时间学习了C++11的新特性,好多新知识,打算顺便make一下,做点记录什么的~~
类型别名
类型别名是C和C++一样存在的,以前的typedef的用法为typedef typeA typeB,把typeA定义为typeB的一个别名,这种用法,更像是宏定义的用法,并没有等号表达式直观;C++11中新增一种定义类型别名的方法, 该方法更符合我们平时的使用习惯:using typeA = typeB;这种方法在平时可能并无法表现多大的优越性,但如果用于函数指针时,却是很直观的。比如有如下函数 int add(int a,int b);如果定义指针,以前的做法是typedef int (add*)(int a,int b);定义一个函数指针与函数名完全无关,而只是与返回类型和参数类型有关(甚至可以没有参数名称:typedef int (add*)(int,int);),但无论怎么说,这种方法仍然是不太直观的,现在C++11中可以 这样用了。
using addptr = int (int,int);它很明确指出,addptr就是一个函数指针,它指向的函数类型为:返回值是int,并带有两个int形参的所有函数。
#include<bits/stdc++.h> using namespace std; typedef int(*add)(int a,int b); using add2=int(int,int); auto func(int a,int b) ->int { return a+b; } int main() { add t1=func; cout<<t1(2,3)<<endl; add2 *t2=func; cout<<t2(2,3)<<endl; return 0; }
auto类型
auto是C++11中新增加的特性,对于我们平时编写代码,我建议是少用应该尽量少用,只有在写代码时知道我每步会产生的结果时才会写出健壮的程序,仅 auto来为我们判断类型,事实与我们静态语言的原意相悖了;auto更多应用于泛型编程中,它能减少你编写特化模板的工作量。对于auto,我们只需要 记住以下几点:
1)auto是编译时得到的类型,也就是说,它是让编译器替我们分析表达式所属的类型的,所以有时候auto产生的值,如果对它没有足够的理解,你会被弄糊涂。
2)auto只能推断一种类型,比如当一条语句中声明多个变量时,如果变量类型不同,是会产生错误的:auto sz = 0,pi = 3.14;一个为int型,一个为double型,这时auto是不能正常工作的。
3) 当右值是一个引用类型时,auto的类型不是一个引用类型,而是引用类型的值。比如double &PI = pi;auto ref = PI;ref的类型不是double&而是跟pi的类型一样,为一个double类型。当我们确实需要一个引用类型时,我们可以写成这样auto &ref = PI;
4)说这点前,先说明下C++ Primer中提到的顶层const和底层const的概念。当一个const变量自身无法改变时,我们称为顶层const,当一个const变量本身可 改变,但关联的变量无法改变时我们称为底层const。也即是顶层也就是表面,底层也就是表面掩住的内容。如果:const double pi = 3.14;double *const ptr = π(注意,不要写成double const *ptr,这个是跟const double* ptr一样的), 这 2个都是顶层const,他们本身的值无法改变;const double *ptr2 = π;const double PI = π这2个都是底层const,事实上,指向常量的指针和声明引用的const都是底层const; auto会忽略掉顶层const的性质,但保留底层const;比如auto x = pi;这时x的类型为double而非const double,同样地,当auto x = ptr时,x的类型为double*;而auto x = ptr2,此时的x为const double*类型,它说明指向的值不可改变。如果我们想保留顶层const,只能手动写上符号,如auto const x = π
总的来说,auto能判断内置类型,能判断指针类型,但却无法正确判断出引用类型和顶层const类型,对于顶层const类型和引用类型(所有的const引用类型都是顶层const类型)只能通过手动增加的方法保留特性。
5)由编译器确定动态分配数据的类型,C++11的auto在new中也有新的定义,之前我们必须确实地指定new类型和指针类型才能正确地在堆上分配内存空间,现在,C++11允许由编译器推断分配类型,用法如下:
auto p1 = new auto(std::string); // p1为指向string类型的指针
#include<bits/stdc++.h> using namespace std; auto func(int a,int b) ->int { return a+b; } int main() { //auto b; 不允许这样定义 auto a=3.15; auto str2= {"adafs"}; auto str{"adafs"}; //新的赋值方式 vector<int >v{1,2,34}; for(auto &i:v) { cout<<i<<endl; } for(auto i=0; i<10; ++i); return 0; }
decltype
decltype实际上有点像auto的反函数,auto可以让你声明一个变量,而decltype则可以从一个变量或表达式中得到类型,有实例如下:
int x = 3; decltype(x) y = x;
decltype 和 auto 一起使用会更为有用,因为 auto 参数的类型只有编译器知道.然而 decltype对于那些大量运用运算符重载和类型特化来编码的表达式非常有用。
这两种形式的差距会随着你使用的容器的嵌套层次而增加, 这种情况下typedef也是一种减少代码的好方法!由decltype得出的类型可以和由auto推导出的类型不同:
#include<vector> int main() { const std::vector v(1); auto a = v[0]; // a 是 int 类型 decltype(v[1]) b = 1; // b 是 const int& 类型, 是std::vector::operator[](size_type) const // 的返回类型 auto c = 0; // c 是 int 类型 auto d = c; // d 是 int 类型 decltype(c) e; // e 是 int 类型, c变量的类型 decltype((c)) f = c; // f 是int&类型, 因为(c)是一个左值 decltype(0) g; // g 是 int 类型, 因为0是一个右值 }
nullptr
以前都是用0来表示空指针的,但由于0可以被隐式类型转换为整形,这就会存在一些问题。关键字nullptr是std::nullptr_t类型的值,用来指代空指针。nullptr和任何指针类型以及类成员指针类型的空值之间可以发生隐式类型转换,同样也可以隐式转换为bool型(取值为false)。但是不存在到整形的隐式类型转换。
void foo(int* p) {} void bar(std::shared_ptr<int> p) {} int* p1 = NULL; int* p2 = nullptr; if(p1 == p2) { } foo(nullptr); bar(nullptr); bool f = nullptr; int i = nullptr; // error: A native nullptr can only be converted to bool or, using reinterpret_cast, to an integral type
非成员begin()和end()
非成员begin()和end()函数。他们是新加入标准库的,除了能提高了代码一致性,还有助于更多地使用泛型编程。它们和所有的STL容器兼容。更重要的是,他们是可重载的。所以它们可以被扩展到支持任何类型。对C类型数组的重载已经包含在标准库中了。
我们还用上一个例子中的代码来说明,在这个例子中我打印了一个数组然后查找它的第一个偶数元素。如果std::vector被替换成C类型数组。代码可能看起来是这样的:
int arr[] = {1,2,3}; std::for_each(std::begin(arr), std::end(arr), [](int n) {std::cout << n << std::endl;}); auto is_odd = [](int n) {return n%2==1;}; auto pos = std::find_if(std::begin(arr), std::end(arr), is_odd); if(pos != std::end(arr)) std::cout << *pos << std::endl;
deleted 函数和 defaulted 函数
像以下形式的函数:
struct A{
A()=default; //C++11
virtual ~A()=default; //C++11
};
叫做 defaulted 函数,=default; 指示编译器生成该函数的默认实现。这有两个好处:一是让程序员轻松了,少敲键盘,二是有更好的性能。
与 defaulted 函数相对的就是 deleted 函数:
int func()=delete;
这货有一大用途就是实现 noncopyabe 防止对象拷贝,要想禁止拷贝,用 =deleted 声明一下两个关键的成员函数就可以了:
struct NoCopy { NoCopy & operator =( const NoCopy & ) = delete; NoCopy ( const NoCopy & ) = delete; }; NoCopy a; NoCopy b(a); //编译错误,拷贝构造函数是 deleted 函数
C++11 的标准库
除 TR1 包含的新容器(unordered_set, unordered_map, unordered_multiset, 和unordered_multimap),还有一些新的库,如正则表达式,tuple,函数对象封装器等。下面介绍一些 C++11 的标准库新特性:
线程库
从程序员的角度来看,C++11 最重要的特性就是并发了。C++11 提供了 thread 类,也提供了 promise 和 future 用以并发环境中的同步,用 async() 函数模板执行并发任务,和 thread_local 存储声明为特定线程独占的数据,这里(http://www.devx.com/SpecialReports/Article/38883)有一个简单的 C++11 线程库教程(英文)。
新的智能指针类
C++98 定义的唯一的智能指针类 auto_ptr 已经被弃用,C++11 引入了新的智能针对类 shared_ptr 和 unique_ptr。它们都是标准库的其它组件兼容,可以安全地把智能指针存入标准容器,也可以安全地用标准算法“倒腾”它们。
新的算法
主要是 all_of()、any_of() 和 none_of(),下面是例子:
#include <algorithm> //C++11 code //are all of the elements positive? all_of(first, first+n, ispositive()); //false //is there at least one positive element? any_of(first, first+n, ispositive());//true // are none of the elements positive? none_of(first, first+n, ispositive()); //false
还有一个新的 copy_n:
#include <algorithm> int source[5]={0,12,34,50,80}; int target[5]; //从 source 拷贝 5 个元素到 target copy_n(source,5,target);
iota() 算法可以用来创建递增序列,它先把初值赋值给 *first,然后用前置 ++ 操作符增长初值并赋值到给下一个迭代器指向的元素,如下:
#include <numeric> int a[5]={0}; char c[3]={0}; iota(a, a+5, 10); //changes a to {10,11,12,13,14} iota(c, c+3, 'a'); //{'a','b','c'}
是的,C++11 仍然缺少一些很有用的库如 XML API,socket,GUI、反射——以及自动垃圾收集。然而现有特性已经让 C++ 更安全、高效(是的,效率更高了,可以参见 Google 的 基准测试结果http://www.itproportal.com/2011/06/07/googles-rates-c-most-complex-highest-performing-language/)以及更加易于学习和使用。
如果觉得 C++ 变化太大了,不必惊恐,花点时间来学习就好了。可能在你融会贯通新特性以后,你会同意 Stroustrup 的观点:C++11 是一门新的语言——一个更好的 C++。
- 版权声明:本文基于《知识共享署名-相同方式共享 3.0 中国大陆许可协议》发布,转载请遵循本协议
- 文章链接:http://www.carlstedt.cn/archives/912 (转载时请注明本文出处及文章链接)
发表评论
快来吐槽一下吧!