容器
调用empty而不是检查size是否为0
对任一容器C,下面代码
1 | if(c.size()==0) |
本质上与
1 | if(c.empty()) |
等价的。但是为什么使用emtpy,这是因为emtpy对所有的标准容器都是常数操作时间,而对于一些list的实现,size耗费线性时间。
这段代码只是把容器从头到尾数一遍才能知道有多少个元素,所以size()是线性的。
区间成员函数优化于之对应单元素成员函数
给定V1和V2两个矢量,使得V1的内容与V2的后半部分相同的最简单的操作是什么?
若不使用区间成员函数来解决此问题,就得要写一个显式的for循环,我们有代码
1 | vector<Widget>v1,v2; |
我们可以通过assign函数来解决此问题。若使用copy函数,通过实例化之后,与显式循环的速度相同。
若使用insert函数,则有
1 | int data[numValues]; |
而若通过显式地循环调用insert,则可能如下
1 | vector<int>::iteator insertLoc(v.begin()); |
我们必须要把insert的返回值进行记录,否则会遇到两个问题,第一、第一次迭代后的所有循环迭代都将导致不可预料的行为,因为每次调用insert都会使insertloc无效,其次,即使insertLoc仍然有效,插入总是发生在vector的最前面,结果这个数组被以相反的顺序拷贝到v当中。
若是把循环替换对copy的调用,则有以下代码
1 | copy(data,data+numValues,insert(v,v.begin())) |
当copy模板被实例化之后,基于copy的代码和显式循环使用的代码基本相同。
然而采用单元素成员函数,会存在以下疸
- 第一次影响不必要的函数调用把numValues个元素逐个插入到v中导致了对insert的numValues次调用。而使用区间形式的insert,则只做了一次函数调用,节省了numValues-1次。当然,使用内联(inlining)可能会避免这样的影响,但是,实际中不见得会使用内联。只有一点是肯定的:使用区间形式的insert,肯定不会有这样的影响。
- 第二种影响是即把v中已有的元素频繁地移动到插入后它们所处的位置。每次调用insert把新元素插入到v中时,插入点后的每个元素都要向后移动一个位置,以便为新元素腾出空间。所以,位置p的元素必须被移动到位置/7+7在通常情况下,把numValues个元素逐个插入到含有n个元素的vector
的前端将会有/inumValues次函数调用的代价:(n~l)numValues次调用Widget的赋值操作符和numValues次调用Widget的拷贝构造函数。即使这些调用是内联的,仍需要移动n次。
别一方面参数inputIterator表示任何类型的输入迭代器都是可以接受的。
当心C++编译器的分析机制
我们有代码
1 | ifstream dataFile("ints.bat"); |
然而上面这串代码可能 不会如你所愿。这段代码可以通过编译,但是在运行时,它什么也不会做。
如有以下代码,这行代码声明了一个带double参数并返回int的函数
1 | int f(double d); |
下面这行做了同样的事情,参数d两边的括号是多余的
1 | int f(double d); |
这行声明了同样的函数,只是它省略了参数名称
1 | int f(double); |
我们再来看三个函数声明,第一个声明了一个函数g,它的参数是一个指向不带任何参数的函数指针,该函数返回double值。
1 | int g(double(*pf)()); //g以指向函数的指针为参数 |
另一种方式也可表明同样的意思
1 | int g(double pf()); |
参数名称亦可省略
1 | int g(double()); |
我们再来研究代码
1 | ifstream dataFile("ints.bat"); |
- 第一个参数名称是dataFile,它的类型是istream_iterator
,dataFile两边的括号是多余的会被忽略。 - 第二个参数没有名称,它的类型是不带参数的函数指针,该函数指针返回istream_iterator
。
我们有以下常见的错误
1 | class Widget(...); //假定Widget有默认构造函数 |
它没有声明名为w的Widget,而是声明了一个名为w的函数,该函数不带任何参数,并返回一个Widget。
慎重选择删除元素的方法
若有一个连续内存的容器(vector,deque或string)那么最好的办法是earse-remove用法
1 | c.erase(remove(c.begin(),c.end(),1936),c.end()) |
1 | template <class ForwardIterator, class T> |
remove函数是通过迭代器不断向前移动来达到删除元素的目的。
对于list,则是list的成员函数更加有效.
1 | c.remove(1963) |
而对于关联窗口而言,则应该调用erase函数
1 | c.erase(193) |
若我们不再从c中删除所有等于特定值的元素,而是删除使下面的判别式返回true的每一个对象
1 | bool badValue(int);//返回x是否是坏值 |
而对于标准关联容器而言,我们有两种解决办法,一种易于编码,一种效率更高