1 C++11中针对vector<pair<int,string> >两个尖括号间的空格去除,也就是说C++11中可以写成:vector<pair<int,string>>,编译器这里不会将>>认为是右移符号。
2 auto: C++中每个变量使用前必须定义从而被视为静态语言,而不像一些脚本语言变量可以这样用x=1称为动态语言。 静态语言和动态语言主要区别:静态语言类型检查发生在编译阶段,动态语言类型检查发生在运行阶段。C++11中实现了auto和decltype从静态语言向动态语言拉近了一点。注意的是auto不再是存储类型的标识符,还记得曾几何C语言中大讲atuo、static等等变量的区别,现在auto在C++11中是个类型指示符,原来C++98的存储类型标识符语义被遗弃。auto告诉编译器所声明的变量类型在编译时期推导而得,相当于atuo是个占位符,等编译时确定变量的类型。如:atuo x=1; 编译时x的类型自动推断为int,其等价于int x=1;
一个实用之处就是std::vector<int>::iterator it=vec.begin();现在可以写为: auto it=vec.begin()
atuo增加了类型的灵活性,比如:
class PI {
public:
double operator* (float v) {
return (double)val * v; // 这里精度被扩展了
}
const float val = 3.1415927f;
};
int main() {
float radius = 1.7e10;
PI pi;
auto circumference = 2 * (pi * radius);//atuo的一个好处就是,如果将来PI中的operator* 返回值类型改变了,main中不需要跟着修改
}
atuo不能解决溢出问题,如:
unsigned int a=4294967295;//最大无符号整数
unsigned int b=1;
auto c=a+b;//c=0
atuo为跨平台带来了一定好处,如:
auto var=strlen("hello world")//strlen在32位下返回4字节,64位下返回8字节整型,实用auto完全屏蔽了该细节
auto提高了模板的灵活性:
template<typename T1, typename T2>
double Sum(T1 & t1, T2 & t2) {//遗憾的是这里double不能用auto了,编译器报错
auto s = t1 + t2; // s的类型会在模板实例化时被推导出来,不过此处已经增加了灵活性
return s;
}
int main() {
int a = 3;
long b = 5;
float c = 1.0f, d = 2.3f;
auto e = Sum(a, b); // s的类型被推导为long
auto f = Sum(c, d); // s的类型被推导为float
}
auto用于宏的一个惊艳例子:
#define Max1(a, b) ((a) > (b)) ? (a) : (b)//遇见一个宏替换a或b就要计算一次a或b
#define Max2(a, b) ({ \
auto _a = (a); \ //这里将宏替换a的计算结果先保存至_a中,这样减少了计算开销
auto _b = (b); \
(_a > _b) ? _a: _b; })
int main() {
int m1 = Max1(1*2*3*4, 5+6+7+8);
int m2 = Max2(1*2*3*4, 5+6+7+8);//计算开销减小
}
atuo与指针和引用的关系,如:
int x;
int * y = &x;
double foo();
int & bar();
auto * a = &x; // int*,这里auto* 和auto一样的
auto & b = x; // int&,这里必须是atuo&
auto c = y; // int*
auto * d = y; // int*
auto * e = &foo(); // 编译失败, 指针不能指向一个临时变量
auto & f = foo(); // 编译失败, nonconst的左值引用不能和一个临时变量绑定
auto g = bar(); // int
auto & h = bar(); // int&
CV限制符:auto, volatile, const 分别表示类型标示符,易失的,常量的。
double foo();
float * bar();
const auto a = foo(); // a: const double
const auto & b = foo(); // b: const double&
volatile auto * c = bar(); // c: volatile float*
auto d = a; // d: double, 简单的auto并不能带走右边的cv特性
auto & e = a; // e: const double &,但是auto&能带走右边的cv特性
auto f = c; // f: float *
volatile auto & g = c; // g: volatile float * &
auto可以和初始化列表和new一起使用:
#include <initializer_list>
auto x = 1;
auto x1(1);
auto y {1}; // 使用初始化列表的auto
auto z = new auto(1); // 可以用于new
auto在同一个语句中推断多个变量,第一个atuo的类型就是后面变量的了性,从左至右推导,如:
auto a=1,b=2;//等价于int a=1; int b=2;但是建议不要这样使用auto,还是每个auto推断单独占一行
一些auto不适用的地方:
#include <vector>
using namespace std;
void fun(auto x =1){} // 1: auto函数参数,无法通过编译
struct str{
auto var = 10; // 2: auto非静态成员变量,无法通过编译
};
int main() {
char x[3];
auto y = x;
auto z[3] = x; // 3: auto数组,无法通过编译
// 4: auto模板参数(实例化时),无法通过编译
vector<auto> x = {1};
}
3 decltype:类型推导
typeid(类名).name()可以返回类型的名字,typeid(类名).hash_code()返回类型对应的唯一hash值,该hash值可以比较,如:typeid(类1).hash_code()==typeid(类名2).hash_code()。is_same<类名1,类名2>::value也可以判断两个类是否相同。
int i;
decltype(i) j = 0;
cout << typeid(j).name() << endl; // 打印出"i", g++表示integer
float a;
double b;
decltype(a + b) c;
cout << typeid(c).name() << endl; // 打印出"d", g++表示double
decltype不像auto那样从变量声明的初始化表达式获得变量的类型,decltype总是以一个普通的表达式为参数,返回该表达式的类型。decltype可以获得的类型来定义另外一个变量,与auto相同的是,decltype也是在编译是进行的。
decltype和using结合的例子:
using size_t=decltype(sizeof(0));//size_t就可以跨平台使用了
decltype可以像auto it=vec.begin()一样用于标准容器:
vector<int> vec;
typedef decltype(vec.begin()) vectype;
vectype i; // 这是auto无法做到的
for (i = vec.begin(); i < vec.end(); i++) {
// 做一些事情
}
for (decltype(vec)::iterator i = vec.begin(); i < vec.end(); i++) {
// 做一些事情
}
decltype可以重用匿名类型:
enum class{K1, K2, K3}anon_e; // 匿名的强类型枚举
union {
decltype(anon_e) key;
char* name;
}anon_u; // 匿名的union
struct {
int d;
decltype(anon_u) id;
}anon_s[100]; // 匿名的struct数组
int main() {
decltype(anon_s) as;//重用匿名类型的类型
as[0].id.key = decltype(anon_e)::K1; // 引用匿名强类型枚举中的值
}//通过匿名的变量名anon_e等加上decltype可以推断这些匿名类型的类型并进行重用,不过这个似乎违背了匿名类型的设计初衷
decltype重塑前面“auto提高了模板的灵活性”的例子:
// s的类型被声明为decltype(t1 + t2)
template<typename T1, typename T2>
void Sum(T1 & t1, T2 & t2, decltype(t1 + t2) & s) {//可以通过参数引用s返回这个不确定类型的返回值
s = t1 + t2;
}
int main() {
int a = 3;
long b = 5;
float c = 1.0f, d = 2.3f;
long e;//这里仍然需要定义e的类型,传给Sum,还不是完全智能的,后面的函数返回值推导可以解决这个问题
float f;
Sum(a, b, e); // s的类型被推导为long
Sum(c, d, f); // s的类型被推导为float
}
decltype用于实例化模板:
int hash(char*);//一个hash函数
//map<char*, decltype(hash)> dict_key; // 无法通过编译,hash是个函数名,hash(nullptr)才是表达式满足decltype
map<char*, decltype(hash(nullptr))> dict_key;
模板类
result_of实现就是基于decltype的,用于推断函数的返回值类型,位于头文件<type_traits>:
typedef double (*func)();
result_of<func()>::type f; // 由func()推导其结果类型,double
decltype(e)推断的四个规则:
1) 当e是一个没有带括号的标记符表达式(除去关键字、字面量等编译器需要使用的标记之外的程序员自定义的标记)或者类成员访问表达式,那么decltype(e)就是e所命名的实体的类型。如果e是一个被重载的函数名,则会编译错误。
2) 否则,假设e类型是T,若e是一个将亡值(如函数非引用返回值),那么decltype(e)为T&&
3) 否则,若e是一个左值,decltype(e)为T&,这是一个左值规则
4) 否则,decltype(e)为T
例子如下:
int i = 4;
int arr[5] = {0};
int *ptr = arr;
struct S { double d; } s;
void Overloaded(int);
void Overloaded(char); // 重载的函数
int && RvalRef();
const bool Func(int);
// 规则1: 单个标记符表达式以及访问类成员,推导为本类型
decltype(arr) var1; // int[5], 标记符表达式
decltype(ptr) var2; // int*, 标记符表达式
decltype(s.d) var4; // double, 成员访问表达式
decltype(Overloaded) var5; // 无法通过编译,是个重载的函数
// 规则2: 将亡值,推导为类型的右值引用
decltype(RvalRef()) var6 = 1; // int&&
// 规则3: 左值,推导为类型的引用
decltype(true ? i : i) var7 = i; // int&, 三元运算符,这里返回一个i的左值
decltype((i)) var8 = i; // int&, 带圆括号的左值
decltype(++i) var9 = i; // int&, ++i返回i的左值
decltype(arr[3]) var10 = i; // int& []操作返回左值
decltype(*ptr) var11 = i; // int& *操作返回左值
decltype("lval") var12 = "lval"; // const char(&)[9], 字符串字面常量为左值
// 规则4:以上都不是,推导为本类型
decltype(1) var13; // int, 除字符串外字面常量为右值
decltype(i++) var14; // int, i++返回右值
decltype((Func(1))) var15; // const bool, 圆括号可以忽略
规则3左值规则,确实比较难以判断,可以通过一定技术检查,先声明decltype定义的变量,再在其它语句对其初始化,若是左值必须声明即初始化,那么编译器会报错。也可以借助is_lvalue_reference等模板类判断变量引用类型:
#include <type_traits>
#include <iostream>
using namespace std;
int i = 4;
int arr[5] = {0};
int *ptr = arr;
int && RvalRef();
int main(){
cout << is_rvalue_reference<decltype(RvalRef())>::value << endl; // 1
cout << is_lvalue_reference<decltype(true ? i : i)>::value << endl; // 1
cout << is_lvalue_reference<decltype((i))>::value << endl; // 1
cout << is_lvalue_reference<decltype(++i)>::value << endl; // 1
cout << is_lvalue_reference<decltype(arr[3])>::value << endl; // 1
cout << is_lvalue_reference<decltype(*ptr)>::value << endl; // 1
cout << is_lvalue_reference<decltype("lval")>::value << endl; // 1
cout << is_lvalue_reference<decltype(i++)>::value << endl; // 0
cout << is_rvalue_reference<decltype(i++)>::value << endl; // 0
}
decltype可以带来冗余的符号,如decltype(T) & a中decltype本身可能就推断出是个引用,后面由出现一个&则出现冗余的&。auto不能带走初始化表达式的cv限制符,但是decltype却能带走其表达式的cv限制符。
#include <type_traits>
#include <iostream>
using namespace std;
const int ic = 0;
volatile int iv;
struct S { int i; };
const S a = {0};
volatile S b;
volatile S* p = &b;
int main() {
cout << is_const<decltype(ic)>::value << endl; // 1
cout << is_volatile<decltype(iv)>::value << endl; // 1
cout << is_const<decltype(a)>::value << endl; // 1
cout << is_volatile<decltype(b)>::value << endl; // 1
cout << is_const<decltype(a.i)>::value << endl; // 0, 成员不是const //成员变量失去了其所属类型的cv属性
cout << is_volatile<decltype(p->i)>::value << endl; // 0, 成员不是volatile
}
decltype最后的推断结果包含了一些冗余符号cv限制符和引用&则可以忽略多余的符号
#include <type_traits>
#include <iostream>
using namespace std;
int i = 1;
int & j = i;
int * p = &i;
const int k = 1;
int main() {
decltype(i) & var1 = i;
decltype(j) & var2 = i; // 冗余的&, 被忽略
cout << is_lvalue_reference<decltype(var1)>::value << endl; // 1, 是左值引用
cout << is_rvalue_reference<decltype(var2)>::value << endl; // 0, 不是右值引用
cout << is_lvalue_reference<decltype(var2)>::value << endl; // 1, 只是左值引用
//decltype(p)* var3 = &i; // 无法通过编译 //auto*和auto相同,但是decltype的*不能省略,这里推断结果int** var3和右边的&i不符
decltype(p)* var3 = &p; // var3的类型是int**
auto* v3 = p; // v3的类型是int*
v3 = &i;
const decltype(k) var4 = 1; // 冗余的const,被忽略
}
4 追踪返回类型
先看一个实例:
#include <iostream>
using namespace std;
template<typename T1, typename T2>
auto Sum(const T1 & t1, const T2 & t2) -> decltype(t1 + t2)
{//当模板的返回值类型是随着模板实例化才能确定时,通过auto和decltype追踪返回值类型,auto相当于占位符,最后模板实例化从左至右,最后decltype推导出返回值类型填写回auto处即可
return t1 + t2;
}
template <typename T1, typename T2>
auto Mul(const T1 & t1, const T2 & t2) -> decltype(t1 * t2)
{
return t1 * t2;
}
int main()
{
auto a = 3;
auto b = 4L;
auto pi = 3.14;
auto c = Mul(Sum(a, b), pi);//完全根据模板实例化自动推导参数
cout << c << endl; // 21.98
}
追踪返回类型用于简化函数定义:
#include <type_traits>
#include <iostream>
using namespace std;
// 有的时候,你会发现这是面试题
int (*(*pf())())() {//这个看起来真的比较蛋疼
return nullptr;
}
// auto (*)() -> int(*) () 一个返回函数指针的函数(假设为a函数)
// auto pf1() -> auto (*)() -> int (*)() 一个返回a函数的指针的函数
auto pf1() -> auto (*)() -> int (*)() {//从右向左解析
return nullptr;
}
int main() {
cout << is_same<decltype(pf), decltype(pf1)>::value << endl; // 1
}
追踪返回值类型用于转发函数中:
#include <iostream>
using namespace std;
double foo(int a) {
return (double)a + 0.1;
}
int foo(double b) {
return (int)b;
}
template <class T>
auto Forward(T t) -> decltype(foo(t)){//转发函数
return foo(t);
}
int main(){
cout << Forward(2) << endl; // 2.1
cout << Forward(0.5) << endl; // 0
}
5 for循环
标准库已经有了for_each
for_each( InputIt first, InputIt last, UnaryFunction f );
c++11有了新的for循环方式:
#include <vector>
#include <iostream>
using namespace std;
int main() {
vector<int> v = {1, 2, 3, 4, 5};
for (auto i = v.begin(); i != v.end(); ++i)
cout << *i << endl; // i是迭代器对象
for (auto e: v)//冒号分开,前面部分是范围内用于迭代的变量,后半部分是迭代的范围,只能用于明确已知迭代范围
cout << e << endl; // e是解引用后的对象
}
对于迭代范围不确定不能使用新的for形式,如下:
#include <iostream>
using namespace std;
int func(int a[]) {
for (auto e: a) // 编译失败,迭代范围不确定
cout << e;
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
func(arr);
}