C++11带来的优雅语法
作者:网络转载 发布时间:[ 2016/3/29 10:47:59 ] 推荐标签:.NET 测试开发技术
去除右尖括号的蹩脚语法 right angle brackets
在C++98标准中,如果写一个含有其他模板类型的模板:
vector<vector > vector_of_int_vectors;
你必须在结束的两个’>‘之间添加空格。这不仅烦人,而且当你写成>>而没有空格时,你将得到困惑和误导的编译错误信息。产生这种行为的原因是C++词法分析的大匹配原则(maximal munch rule)。一个好消息是从今往后,你再也不用担心了:
vector<vector> vector_of_int_vectors;
在C++98中,这是一个语法错误,因为两个右角括号(‘>’)之间没有空格(译注:因此,编译器会将它分析为”>>”操作符)。C++0x可以正确地分辨出这是两个右角括号(‘>’),是两个模板参数列表的结尾。
为什么之前这会是一个问题呢?一般地,一个编译器前端会按照“分析/阶段”模型进行组织。简要描述如下:
词法分析(从字符中构造token)
语法分析(检查语法)
类型检查(确定名称和表达式的类型)
这些阶段在理论上,甚至在某些实际应用中,都是严格独立的。所以,词法分析器会认为”>>”是一个完整的token(通常意味着右移操作符或是输入),而无法理解它的实际意义(译注:即在具体的上下文环境下,某一个符号的具体意义)。特别地,它无法理解模板或内置模板参数列表。然而,为了使上述示例“正确”,这三个阶段必须进行某种形式的交互、配合。解决这个问题的关键的点在于,每一个C++ 编译器已完整理解整个问题(译注:对整个问题进行了全部的词法分析、符号分析及类型检测,然后分析各个阶段的正确性),从而给出令人满意的错误消息。
lambda表达式的引入
对于为标准库算法写函数/函数对象(function object)这个事儿大家已经抱怨很久了(例如Cmp)。特别是在C++98标准中,这会令人更加痛苦,因为无法定义一个局部的函数对象。
首先,我们需要在我们实现的逻辑作用域(一般是函数或类)外部定义比较用的函数或函数对象,然后,才能使用:
bool myfunction (int i,int j) { return (i<j); }
struct myclass {
bool operator() (int i,int j) { return (i<j);}
} myobject;
int main()
{
int myints[] = {32,71,12,45,26,80,53,33};
std::vector myvector (myints, myints+8);
// using function as comp
std::sort (myvector.begin(), myvector.end(), myfunction);
// using function object as comp
std::sort (myvector.begin(), myvector.end(), myobject);
}
不过现在好多了,lambda表达式允许用”inline”的方式来写函数了:
sort(myvector.begin(), myvector.end(), [](int i, int j) { return i< j; });
真是亲切!lambda的引入应该会增加大家对STL算法的使用频率;
原生字符串 Raw string literals
比如,你用标准regex库来写一个正则表达式,但正则表达式中的反斜杠’’其实却是一个“转义(escape)”操作符(用于特殊字符),这相当令人讨厌。考虑如何去写“由反斜杠隔开的两个词语”这样一个模式(ww):
string s = "\w\\\w";
// 不直观、且容易出错
请注意,在正则表达式和普通C++字符串中,各自都需要使用连续两个反斜杠来表示反斜杠本身。然而,假如使用C++11的原生字符串,反斜杠本身仅需一个反斜杠可以表示。因而,上述的例子简化为:
string s = R"(w\w)";
// ok
非成员begin()和end()
非成员begin()和end()函数。他们是新加入标准库的,除了能提高了代码一致性,还有助于更多地使用泛型编程。它们和所有的STL容器兼容。更重要的是,他们是可重载的。所以它们可以被扩展到支持任何类型。对C类型数组的重载已经包含在标准库中了。
在这个例子中我打印了一个数组然后查找它的第一个偶数元素。如果std::vector被替换成C类型数组。代码可能看起来是这样的:
int arr[] = {1,2,3};
std::for_each(&arr[0], &arr[0]+sizeof(arr)/sizeof(arr[0]), [](int n) {std::cout << n << std::endl;});
auto is_odd = [](int n) {return n%2==1;};
auto begin = &arr[0];
auto end = &arr[0]+sizeof(arr)/sizeof(arr[0]);
auto pos = std::find_if(begin, end, is_odd);
if(pos != end)
std::cout << *pos << std::endl;
如果使用非成员的begin()和end()来实现,会是以下这样的:
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;
这基本上和使用std::vecto的代码是完全一样的。这意味着我们可以写一个泛型函数处理所有支持begin()和end()的类型。
初始化列表及统一初始化方法 Initializer lists
在C++98中,对vector的多个初始化,我们需要这样:
int myints[] = { 10, 20, 30, 30, 20, 10, 10, 20 };
std::vector myvector (myints, myints+8);
现在,我们可以这样:
std::vector second ={10, 20, 30, 30, 20, 10, 10, 20};
初始化表有时可以像参数那样方便的使用。看下边这个例子(x,y,z是string变量,Nocase是一个大小写不敏感的比较函数):
auto x = max({x,y,z},Nocase());
初始化列表不再于数组。对于常见的map、string等,我们可以使用以下语法来进行初始化:
int arr[3]{1, 2, 3};
vector iv{1, 2, 3};
map<int, string> m{{1, "a"}, {2, "b"}};
string str{"Hello World"};
可以接受一个“{}列表”对变量进行初始化的机制实际上是通过一个可以接受参数类型为std::initializer_list的函数(通常为构造函数)来实现的。例如:
void f(initializer_list);
f({1,2});
f({23,345,4567,56789});
f({});
// 以空列表为参数调用f()
f{1,2};
// 错误:缺少函数调用符号( )
years.insert({{"Bjarne","Stroustrup"},{1950, 1975, 1985}});
初始化列表可以是任意长度,但必须是同质的(所有的元素必须属于某一模板类型T, 或可转化至T类型的)。
容器可以用如下方式来实现“初始化列表构造函数”:
template class vector {
public:
// 初始化列表构造函数
vector (std::initializer_list s)
{
// 预留出合适的容量
reserve(s.size());
//
// 初始化所有元素
uninitialized_copy(s.begin(), s.end(), elem);
sz = s.size();
// 设置容器的size
}
// ... 其他部分保持不变 ...
};
使用“{}初始化”时,直接构造与拷贝构造之间仍有细微差异,但不再像以前那样明显。例如,std::vector拥有一个参数类型为int的显式构造函数及一个带有初始化列表的构造函数:
vector v1(7);
// OK: v1有7个元素
v1 = 9;
// Err: 无法将int转换为vector
vector v2 = 9;
// Err: 无法将int转换为vector
void f(const vector&);
f(9);
// Err: 无法将int转换为vector
vector v1{7};
// OK: v1有一个元素,其值为7.0
v1 = {9};
// OK: v1有一个元素,其值为9.0
vector v2 = {9};
// OK: v2有一个元素,其值为9.0
f({9});
// OK: f函数将以列表{9}为参数被调用
vector<vector> vs = {
vector(10),
// OK, 显式构造(10个元素,都是默认值0.0)
vector{10},
// OK:显式构造(1个元素,值为10.0)
10
// Err :vector的构造函数是显式的
};
函数可以将initializer_list作为一个不可变的序列进行读取。例如:
void f(initializer_list args)
{
for (auto p=args.begin(); p!=args.end(); ++p)
cout << *p << " ";
}
仅具有一个std::initializer_list的单参数构造函数被称为初始化列表构造函数。
标准库容器,string类型及正则表达式均具有初始化列表构造函数,以及(初始化列表)赋值函数等。初始化列表亦可作为一种“序列”以供“序列化for语句”使用。
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系SPASVO小编(021-61079698-8054),我们将立即处理,马上删除。
相关推荐
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11热门文章
常见的移动App Bug??崩溃的测试用例设计如何用Jmeter做压力测试QC使用说明APP压力测试入门教程移动app测试中的主要问题jenkins+testng+ant+webdriver持续集成测试使用JMeter进行HTTP负载测试Selenium 2.0 WebDriver 使用指南