《深入浅出c++11之3新手易学,老兵易用》

3 新手易学,老兵易用

3.1 右尖括号 > 的改进

在C++ 98中,如果实例化模版的时候出现了连续的两个右尖括号>,需要一个空格来进行分隔,否则被解析为右移。C++ 11中取消了这种限制。

3.2 auto类型推导

auto声明的变量必须被初始化,以使编译器能够从其初始化表达式中推导其类型。
auto使用细则:

int x;
int * y = &x;
double foo();
int &bar();

auto *a = &x; //int*
auto &b = x; //int&
auto c = y; //int *
auto *d = y; //int *
auto *e = &foo(); //编译失败,指针不能指向一个临时变量
auto & f = foo(); //编译失败,nonconst的左值引用不能和一个临时变量绑定
auto g = bar(); //int
auto &h = bar(); //int&
注意:要使auto声明的变量使另一个变量的引用,必须使用auto&,如,b和h。

volatile和const代表变量的两种不同的属性:易失的和常量的。称为cv限制符。
注意:声明为auto的变量并不能从其初始化表达式中“带走”cv限制符,但是声明为引用或指针的变量可以带走“cv限制符”。

double foo();
float *bar();

const auto a = foo(); //const double
const auto &b = foo(); //const double&
volatile auto* c = bar(); //volatile float*

auto d = a; //double
auto &e = a; //const double&
auto f = c; //float*
volatile auto & g = c; //volatile float * &

//auto可以声明为多个变量,但是这些变量的类型必须相同
auto x = 1, y =2;

//这里m的类型为const int*, n的类型为int,auto是一个推导出的类型占位符,这里auto就是int,所以编译正确
const auto *m = &x, n = 1;

auto使用的限制:

void fun(auto x = 1) {} //auto函数参数,编译错误
struct str {
	auto var = 10; // auto非静态成员变量,编译错误
};

int main() {
	char x[3];
	auto y = x;
	auto z[3] = x; //auto数组,编译错误
	
	vector<auto> v = {1}; //auto模版参数(实例化时),编译错误
}

auto不能推导的情况:

  1. 对于函数fun来说,auto不能是形参类型。
  2. 对于结构体,非静态成员变量的类型不能是auto
  3. 对于数组,不能auto声明
  4. 不能在实例化模版的时候,使用auto

3.3 decltype

C++ 98对动态类型支持就是RTTI(运行时类型识别)。
decltype总是以普通的表达式为参数,返回该表达式的类型。

typeid返回变量相应的type_info数据,c++11中增加了hash_code()成员函数,返回该类型唯一的哈希值。
typeid(a).name()
typeid(a).hash_code()
int i;
decltype(i) a; //int
decltype((i)) b; //int & 无法通过编译, i是左值。

decltype(e)推导规则:

  1. 如果e是一个没有带括号的标记表达式或者类成员访问表达式(单个标记符,如果有多个标记符就不符合),那么decltype(e)就是e所命名的实体的类型。如果e是一个被重载的函数,则会导致编译时错误。
  2. 否则,假设e的类型是T,如果e是一个将亡值,那么decltype(e)为T&&
  3. 否则,假设e的类型是T,如果e是一个左值,则decltype(e)为T&
  4. 否则,假设e的类型是T,则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.3.1 cv限制符的继承与冗余的符号

decltype是能够“带走”表达式的cv限制符的。不过如果结构体对象的定义中有const或volatile限制符,使用decltype进行推导时,其成员是不会继承const或volatile限制符。

const int ic = 0;
struct S {int i;}
const S a = {0};
decltype(ic) //const int

decltype(a) //const 
decltype(a.i) //非const

decltype从表达式推导出类型后,进行类型定义时,会允许一些冗余的符号,如cv限制符以及引用符号&,而*指针是不能冗余忽略的。

3.4 追踪返回类型

//编译器只能从左往右地读入符号,因此这里推导返回值要放在最右边,不然t1和t2都没有定义
template <typename T1, typename T2>
auto Sum(T1 &t1, T2 &t2) -> decltype(t1 + t2) {
	return t1 + t2;
}

int func(char* a, int b); 等价于:
auto func(char* a, int b) -> int;

int (*(*pf())())() {
	return nullptr;
}
等价于:
//auto (*)() -> int(*)() 一个返回函数指针的函数(假设为a函数)
//auto pf1() -> auto(*)()->int(*)() 一个返回a函数的指针的函数
auto pf1() -> auto(*)() -> int(*)() {
	return nullptr;
}

auto (*fp)() ->int 等价于:
int (*fp)();

3.5 基于范围的for循环

是否能够基于范围的for循环,必须依赖一些条件:

  1. for循环的迭代的范围是可确定的。如果该类有begin和end函数,范围就是begin和end之间就是for循环迭代的范围。对于数组而言,就是数组的第一个和最后一个元素间的范围。
  2. 需要迭代的对象实现++和==等操作符。
int arr[5] = {1, 2, 3, 4, 5};
//如果迭代变量在循环中不需要修改,完全可以不用引用的方式来做迭代。
for (auto &e: arr) {
	e *= 2;
}

int func(int a[]) {
	for (auto e: a) {
		cout << e;
	}
}

func(arr); //编译错误,因为arr传入func后,arr退化为指针,数组的范围是不确定的。

猜你喜欢

转载自blog.csdn.net/kaydxh/article/details/106303000
今日推荐