好用的c++11语言特性
欢迎来到博主的专栏——c++杂谈
博主ID:代码小豪
文章目录
- auto
- initializer_list(初始化列表)
- 范围for
auto
c++11标准新增了一个auto关键字,允许变量或者对象完成类型的自动推导。
auto num1 = 44;//int
cout << typeid(num1).name() << endl;
auto num2 = 3.14;//double
cout << typeid(num2).name() << endl;
实际上auto是C语言时期就存在的关键字,但是作用完全不同,在C语言中,auto和static都是修饰变量的生命周期。auto修饰的变量具有局部生命周期,但是我们几乎不用auto,因为在C语言当中,非static的变量(非全局变量)都会默认是auto修饰的。
由于auto需要根据初值来判断变量或对象的类型,因此必须为auto修饰的变量进行初始化
auto i;//error,编译器无法判断i的类型
还可以在auto前面加上其他关键字修饰。
const auto num3 = 44;//const int
//num3 = 11;//error,num3是常量
static const auto num4 = 3.14;//static const double
//num4是一个静态的常量
当然了,这种程度的类型推导我们并不需要交给编译器工作,auto真正好用的点在于可以推导较为复杂的类型(比如迭代器),这样可以省去我们推导的时间。
vector<int> v1;vector<int>::iterator it1 = v1.begin();auto it2 = v1.begin();//auto=vector<int>::iterator
不过使用auto肯定会有缺点的,最大的问题在于可读性变低了。比如程序当中有一个变量用错了,如果这个程序的源代码很不幸的全使用了auto关键字,那么在查bug的时候估计很麻烦。总而言之,auto不能滥用,否则省下来的效率会在未来的某一天如数奉还。
initializer_list(初始化列表)
实际上这个列表我们在认识它之前就已经用过了。不信?那你看看下面的代码。
int a[10] = { 1,2,3,4,5,6 };
int* a = new int[10]{ 1,2,3,4,5,6 };
这个大括号({})就是初始化列表,但请注意:不是所有的大括号都是初始化列表!,大括号有时候还能表示构造函数的参数,匿名对象,隐式转换等。只有可以任意控制参数数量的才是初始化列表。
class test
{
public:test(int a, int b){}void print(const test& t1){cout <<"void print(const test& t1)";}
};
int main()
{test t1{ 10,20 };//调用构造函数初始化t1对象test{ 10,20 };//匿名对象t1.print({ 10,20 });//隐式类型转换
}
c++11直接将初始化列表写成了一个模板类。我们可以在c++手册当中找到initializer_list的使用说明。initializer_list成为了一个模板类、并且为这个类设计了几个接口
template<class T> class initializer_list;
我们可以用这个模板实例化出一个初始化列表对象
initializer_list<int>il1 {1, 2, 3, 4, 5, 6, 7};
initializer_list<int>il2 = { 1, 2, 3, 4, 5, 6, 7 };
但这并不是c++11将initializer_list写成模板类的真正目的,initializer_list最强大的功能是可以作为函数的参数(一个类的对象当然可以作为参数了)。
void print(initializer_list<int> lt1)
{for (auto i = lt1.begin(); i != lt1.end(); ++i){cout << *i << ' ';}
}int main()
{print({ 1,2,3,4,5,6,7,8,9 });
}
这里的i,其类型是initializer_list<int>::iterator。说明auto还是蛮好用的(hh)。
c++11标准新增了initializer_list模板类后。STL的许多容器也支持使用initializer_list来构造容器,比如vector,list。让容器初始化变得简单了。(关于STL有什么容器支持initializer_list构造,可以去c++手册当中查看)
vector (initializer_list<value_type> il, const allocator_type& alloc = allocator_type());
//c++11新增的vector构造函数
vector<int> v1{ 1,2,3,4,5,6,7,8 };//v1构造
list<int> l1{ 1,2,3,4,5,6,7 };//l1构造
范围for
c++11新增了一个全新的for循环形式,可以自动的遍历给定的区间,主要是数组,容器,或者一些元素的集合。范围for的标准格式如下:
for(element:object)
{
statement
}
其中element要声明为object的元素的类型(主要根据迭代器的iterator *的返回值判断object的元素类型)。返回for会自动的遍历整个集合,并且执行循环体内的操作(statement),在遍历的过程中,object的每个元素的值都会传递给element。
比如我们用范围for循环整型数组
int a[] = { 1,2,3,4,5,6 };
for (int e : a)//e的类型与a中的每个元素相同,即int
{cout << e;//打印123456
}
如果你不想判断集合的每个元素都是什么类型,只需要将element声明为auto就行了。(实际上博主在用范围for的时候经常用auto类型的element)。
vector<string> v1{ "hello","world","hello","xiaohao" };
for (auto e : v1)
{cout << e<<' ';//hello world hello xiaohao
}
我们可以将element声明为引用类型,这样就可以通过引用传递的形式,迭代的修改集合中的元素。
int b[] = { 1,2,3,4,5,6 };
for (auto& e : b)
{e += 10;//b中的所有元素加了10.
}
将element声明为引用除了能修改集合的元素这一作用外,还有一个很重要的特性。那就是当集合内的元素是一个对象时,pass by value的方式会导致频繁的调用拷贝构造函数,而将element声明成reference则不会。如果你不想让element修改集合内的元素,就将其声明为const reference。
vector<string> v1{ "hello","world","hello","xiaohao" };
for (const auto& e : v1)
{cout << e << ' ';//这种方式的效率远高于pass by value
}
如果你对迭代器存在一定的了解,那么一定知道如下的迭代方式。
vector<string> v2{ "hello","world","hello","xiaohao" };
vector<string>::iterator it1 = v2.begin();
while (it1 != v2.end())
{cout << *it1 << ' ';it1++;
}
这种迭代方式和范围for的迭代方式是一样的,而且不止方式一样,甚至范围for的底层原理就是这种迭代方式。
如果我们为object提供成员函数begin()和end(),那么我们就可以用范围for遍历这个object集合内的元素(前提是你为object写了一个逻辑正确的迭代器。否则会因为缺乏iterator::operator++()而导致编译器报错)。
class myclass
{
public:myclass(initializer_list<T> lt){size = lt.size();array = new T[lt.size()];int i = 0;for (auto& e : lt){array[i] = e;i++;}}typedef T* iterator;//设计迭代器iterator begin() { return array; }//返回迭代器的beginiterator end() { return array + size; }//返回迭代器的end
private:T* array;size_t size;
};int main()
{myclass<string> m1{ "hello","world","hello","xiaohao" };for (const auto& e : m1)//返回for可以遍历自定义的对象。{cout << e << ' ';}return 0;
}
如果将begin()和end()去掉或者写错了函数名字(比如写大写的B),都不能让范围for正常工作,所以请注意这些问题