C++代码规范与优化——初始化列表&Range-Base for

󰃭 2016-05-06

C++代码规范与优化——初始化列表&Range-Base for


c++11为了使代码更为规范和简洁,增加了一些新的特性。今天在这里讲初始化列表和Range-Base for两点。

一致性初始化

先看一个例子。

complex *p = new complex(0.1, 0.2);
int a[] = {1, 2, 3};
int b = 3;

多样式的初始化方式使得程序猿在编程时的选择会比较混乱,c++11标准为此引入了**一致性初始化(uniform initialization)**的概念,使得对所有的初始化行为都采用相同的语法。

complex *p = new complex {0.1, 0.2};
int a[] {1, 2, 3};
int b {3};

一般来说,一致性初始化主要有以下几种语法:

T object {arg...};	//变量初始化,可嵌套
T {arg...};	//临时变量
new T {arg...};	//堆变量
return {arg...};	//返回值
function ({arg...});	//实参
object[{arg...}];	//运算符参数
T({arg...});	//构造参数
Class {T member = {arg...};}	//成员初始化
Class::Class() : member{arg...} {} //初始化列表
T object = {arg...};	//赋值构造

需要注意的是,初始化列表不支持某些隐式转换,称之为窄化(narrowing),适用以下几个规则:

  • 不支持从浮点型转为整型
  • 不支持从long doubledoublefloat的转换,不支持从doublefloat的转换,除非参数是一个返回值在目标范围内的常量表达式
  • 对于整型到浮点型的转换,满足参数是一个返回值在目标范围内的常量表达式
  • 整数或无作用域的枚举类型转换为整型可能损失精度,除非参数是一个返回值在目标范围内的常量表达式

另外,初始化表达式不是一个表达式,它没有类型,即不能够decltype({1})。正因为如此,在模板类型中它不能做类型推导,如template <class T> void f(T);不能通过表达式f({1, 2, 3})来进行调用。当我们用auto来对初始化列表进行推导时,编译器将对其以std::initializer_list对待。
一个嵌套的例子:

std::map<int, std::string> m = { // nested list-initialization
           {1, "a"},
           {2, {'a', 'b', 'c'} },
           {3, s1}
    };

Range-Base for

基于范围的for循环操作, 比传统的for语法更具可读性,可以迭代某个区间、数组或者集合内的每一个元素,在其它语言中称为foreach。语法如下:

for(range_declaration  : range_expression ) {
	loop_statement
}

在执行时,range_declaration会是遍历range_expression中解引用的结果,和传统的循环一样,作为临时变量时,range_declaration的生命周期为循环结束,但另一方面,range_declaration可以通过引用方式来实现,可以节约具有复杂构造函数类型的local copy。另外,基于范围的for依然可以通过break或者continue进行相应操作。

int a[] {1, 2, 3, 4, 5, 6, 7, 8, 9};
std::cout << "initializer list range-base for :" << std::endl;
for (auto i : {0, 1, 2, 3, 4, 5}) {
	std::cout << "a[" << i << "] = " << a[i] << ",";
}
std::cout << "\narray range-base for :" << std::endl;
for (auto element : a) {
	std::cout << "a[] = " << a[i] << ",";
}

实际上,使用Range-Base for要求满足以下两点:

  • 有成员函数begin()end()函数返回迭代器,或者满足全局函数std::begin()std::end()
  • 迭代器有操作符运算*,++,!=

数组类型就是满足全局的std::begin()std::end()操作,返回的迭代器为指针,具有*,++,!=操作。下面这个例子实现了自定义链表结构的遍历操作:

template <class T>
struct LinkNode {
	T data;
	LinkNode *next;
};

template <class T>
struct LinkIter {
	LinkNode<T> *ptr;
	LinkNode<T> *&operator *()  {
		return ptr;
	}
	LinkIter<T> &operator ++() {
		this->ptr = this->ptr->next;
		return *this;
	}
	bool operator !=(LinkIter<T> &var) {
		return ptr != var.ptr;
	}
};

template <class T>
class LinkList {
public:
	LinkList() : head(nullptr), tail(nullptr) {}

	LinkIter<T> begin() { 
    	return LinkIter<T>{head}; 
    }
	LinkIter<T> end() { 
    	return LinkIter<T>{nullptr}; 
    }
    
	void add(T p) {
		auto node = new LinkNode<T>{ p, nullptr };
		if (!head) {
			head = node;
			tail = node;
		}
		else if (!head->next) {
			head->next = node;
			tail = node;
		}
		else {
			tail->next = node;
			tail = node;
		}
	}

private:
	LinkNode<T> *head, *tail;
};


int main() {
	LinkList<int> ll;
	for (auto i :{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }) {
		ll.add(i);
	}
	for (auto ld : ll) {
		std::cout << ld->data << " ";
	}

	return 0;
}

以上就是关于初始化列表和基于范围的for的简单说明,c++的其它特性在之后的文章中介绍。