1、size_type
类型
这属于是基础没打好了。
size_type
是由string
和 vector
类型定义的类型,用于保存任意string
或 vector
对象的长度。
- 切记,
size_type
是unsigned
的。 size_type
的长度,会随机器改变而改变。
2、for_each
for_each(InputIterator first, InputIterator last, Function fn);
注意:for_each
是对first
和 last
里的每个【元素】,调用函数fn
。所以传入fn
的参数并不一定是前面的迭代器或指针类型。
3、
左值与右值的根本区别在于是否允许取地址&运算符获得对应的内存地址。
左值持久,右值短暂
4、
拷贝构造函数第一个参数必是该类型的一个左值引用,任何额外的参数都必须有默认实参。
5、noexpect
:承诺一个函数不抛出任何异常
noexcept
写在参数列表和初始化列表之间。- 生命和定义都要加
noexcept
。
6、一个普通的只有类型名修饰的参数即可以接受左值,也可以接受右值。
X& X::operator=(X x)
{}
X& X::operator=(X&& x)
{}
也就是说,如果类X
有这样两个重载的赋值运算符的话,用右值做赋值运算符右侧的运算单元就会报错。因为两个函数都能接收右值,产生二义性。
7、只有重载的函数调用运算符operator()
能有默认实参,其他重载运算符不能有默认实参。
8、
重载操作符不在保证操作数的求值顺序,所以一般不重载&&
||
,
9、"hello" == "world"
像这样直接比较两个char*
类型的C风格字符串的话,比较的是指针。
10、重载输入运算符要处理输入失败
X& operator>>(istream& is, X& x)
{
is >> 输入巴拉巴拉.....
if (!is) 这个检查是一次性检查,没有逐个检查每个读取操作
x = X(); 输入失败,赋予对象默认状态
else
某些成员需要由刚刚出入的数据计算出来,则在确保输入正常的情况下进行
return is;
}
发生输入错误可能是如下原因:
- 输入的数据类型与代码要求的不符
- 读取到达文件末尾,或遇到其他错误
不过输入也是有强转的,当对一个int
输入一个double
时,不会出错,int
只保留的输入的整数部分。
标示错误
11、算术运算符 和 复合赋值运算符
因为使用复合赋值运算符来实现算数运算符是很方便的。如下所示:
Sale_data operator+(const Sale_data& s1, const Sale_data& s2)
{
Sale_data sum = s1;
sum += s2; 直接使用复合赋值运算符就行了,而不用逐成员相加
return sum;
}
12、定义类的加法和减法运算符,实现指针的算术运算
在定义的时候要注意与内置版本保持一致,也就是返回一个结果的拷贝,而不是引用,因为进行指针算术运算我们是希望通过计算后的指针去访问值。所以在实现的过程中我们也不应改变源对象,而是拷贝一个副本,改变副本的值。如下面代码所示:
类的定义就不写了,意会吧,假设类名是X,他有个成员是指针
X operator+(int n)
{
X x = *this;
x.pointer += n;
return x;
}
X operator-(int n)
{
X x = *this;
x.pointor -= n;
return x;
}
13、replace_if
函数
replace_if
是一个标准库提供的泛型算法。
void replace_if(ForwardIterator first, ForwardIterator last,
UnaryPredicate pred, const T& new_value );
前两个参数指定范围。
第三个参数是一个谓词:接受范围内的元素作为参数,并返回bool
值,若为true
则用第四个参数替换他,反之不替换。
14、count_if
函数
count_if
是一个标准库他一共的泛型算法。
typename iterator_traits<InputIterator>::difference_type
count_if(InputIterator first, InputIterator last, UnaryPredicate pred);
上面那个是返回值,总的来说是difference_type
类型。
前两个参数指定范围。
第三个参数是一个谓词,返回值是bool
值,该函数能计算出范围内使谓词返回true
得元素数量。
该函数在这个题里面有妙用:
15、find_if
函数
这也是标准库的算法。
InputIterator find_if(InputIterator first, InputIterator last, UnaryPredicate pred);
前两个参数指明范围。
第三个参数是个谓词,返回bool
值。
该函数最终返回指向第一个满足谓词条件元素的迭代器。
16、bind1st
和 bind2nd
两个函数都能把二元函数 转换为 一元函数。元指的是参数的数量。
转换的原理就是bind(绑定),他们能固定二元函数其中一个参数具体的值是多少,从而把他变成了一元函数。
而,bind1st
和 bind2nd
的区别就是固定的是哪个参数。前者固定第一个参数,后者固定第二个参数。
下面举个例子:
vector<int< vec = { 1, 2 ,2, 3, 4, 5 };
auto iter1 = find_if( vec.begin(), vec.end(), bdin1st(less<int>(), 2) );
cout << *iter1 << endl;
auto iter2 = find_if( vec.begin(), vec.end9), bind2nd(less<int>(), 2) );
cout << *iter2 << endl;
输出为:
3
1
上面对find_if
的调用除了第三个参数bind1st
和 bind2nd
不一样以外,其他完全一致。但输出的结果不同。
bind1st
固定less
的第 一 个参数为2,所以找的是第一个满足2 < value
的数。bind2nd
固定less
的第 二 个参数为2,所以找的是第一个满足value < 2
的数。
bind1st
和 bind2nd
的意义
bind1st
和 bind2nd
为那些只涉及一个容器的泛型算法提供一个机会,让他们也能使用二元函数。
啥???没有他们就不能用二元函数了吗?
没错,确实不能。你想啊,二元函数须要传进去两个参数,那你只涉及一个容器怎么传两个参数进去?
所以只能由bind1st
或 bind2nd
帮助他们固定好一个参数,把二元函数转化成一元的,这下就能用了。
17、综合标准库函数对象,泛型算法,bind1st
bind2nd
可以用简洁的语句做到一些事情
17.1 统计大于1024的值有多少个
count_if( vec.begin(), vec.end(), bind2st(greater<int>(), 1024) );
17.2 找到第一个不等于"pooh"的字符串
find_if( vec.begin(), vec.end(), bind2nd(not_equal_to<string>(), "pooh") );
17.3 把所有值乘2
transform( vec.begin(), vec.end(), bind2nd(multiplies<int>(), 2) );
18、for_each
和transform
的区别
先明确这俩是干啥的。
for_each
:对前两个参数指定范围内的元素,进行第三个参数表示的操作。transform
:对前两个参数指定范围内的元素,进行第四个参数表示的操作,结果存储在第三个参数表示的容器里,不改变原容器的元素。
已经可以看出区别了,for_each
只涉及到一个容器,而且会修改原容器;transform
涉及多个容器,只是访问原容器,结果存放在其他容器。
好,接下来详细分析一下transform
的用法。
transform
:
第一个重载版本:
template <class InputIterator, class OutputIterator, class UnaryOperation>
OutputIterator transform(InputIterator first1, InputIterator last1,
OutputIterator result, UnaryOperation op);
图示:
- 前两个参数依旧指定范围。
- 第四个参数指定要执行的操作。
- 第三个参数指定存放结果迭代器的起始位置。
第二个重载版本:
template <class InputIterator1, class InputIterator2,
class OutputIterator, class BinaryOperation>;
OutputIterator transform(InputIterator1 first1, InputIterator1 last1,
InputIterator2 first2, OutputIterator result,
BinaryOperation binary_op);
图示:
- 第五个参数是一个二元算子(函数 或 谓词?总之是二元的)
- 前两个参数依旧指定范围,其中元素做二元算子的第 一 个参数。
- 第三个参数是指向另一个容器的迭代器,其中元素做二元算子的第 二 个参数。
- 第四个容器指定存放结果迭代器的起始位置。
下面举例同时说明上述两个重载版本:
#include <iostream> // std::cout
#include <algorithm> // std::transform
#include <vector> // std::vector
#include <functional> // std::plus
using namespace std;
int op_increase (int i) { return ++i; }
int main ()
{
vector<int> foo = { 10, 20, 30, 40, 50 };
vector<int> bar;
bar.resize(foo.size()); 分配空间
transform(foo.begin(), foo.end(), bar.begin(), op_increase);
bar: 11 21 31 41 51
// std::plus adds together its two arguments:
transform( foo.begin(), foo.end(), bar.begin(), foo.begin(), std::plus<int>() );
foo: 21 41 61 81 101
cout << "foo contains:";
for (auto it=foo.begin(); it!=foo.end(); ++it)
cout << ' ' << *it;
cout << '\n';
return 0;
}
输出:
foo contains: 21 41 61 81 101
19、return
语句中的string
加法表达式的求值顺序
string rep(const Query& lhs, const Query& rhs)
{
return "(" + lhs.rep() + " " + opSym + " " + rhs.rep() + ")";
}
上面的return
中,有多个string
相加,其求值是 从右往左执行 的,也就是rhs.rep()
先于lhs.rep()
被调用。
20、set_intersection
标准库算法
默认版本:
template <class InputIterator1, class InputIterator2, class OutputIterator>
OutputIterator set_intersection (InputIterator1 first1, InputIterator1 last1,
InputIterator2 first2, InputIterator2 last2,
OutputIterator result);
他接受5个参数,前四个迭代器表示两个输入序列,最后一个参数表示目的位置。
该算法把前两个输入序列中共同出现的元素写入到目的位置中。
使用示例:
set_intersection( left.begin(), left.end(),
right.begin(), right.end(),
inserter(*ret_set, ret_set->begin() );
上述调用中,传入的最后一个参数是一个插入迭代器,当set_insertsection
向该迭代器写入内容时,实际上是向ret_set
插入一个新元素。
扩展版本:
template <class InputIterator1, class InputIterator2,
class OutputIterator, class Compare>
OutputIterator set_intersection (InputIterator1 first1, InputIterator1 last1,
InputIterator2 first2, InputIterator2 last2,
OutputIterator result, Compare comp);
相比默认版本多了一个参数,这最后一个参数是个函数指针或任何可调用对象,接受两个参数,返回bool
值,用于自定义比较前两个序列中的元素。
21、智能指针的 删除器
21.1 shared_ptr
的删除器在运行时绑定
在一个shared_ptr
的生存期中,可以随时改变其删除器的类型,因此他的删除器是运行时绑定的。
正因此,shared_ptr
的删除器不属于shared_ptr
的成员,因为成员的类型在运行时是不可变的,所以不能直接保存删除器作为成员。
实际上,在调用shared_ptr
的删除器时,是跳转到某个成员保存的地址,并执行对应代码,完成删除操作。
运行时绑定,shared_ptr
使用户重载删除器更方便。
21.2 unique_ptr
的删除器在编译时绑定
unique_ptr
的删除器类型在编译时绑定,后面再也无法改变。
unique_ptr
有个构造函数专门用来绑定我们自定义的删除器,因此,unique_ptr
的删除器是unique_ptr
的一个成员。
编译时绑定,避免了间接调用删除器的运行时开销。
例题:
DebugDelete
是我们自定义的一个删除器。
22、打开命名空间
我们使用如下方法打开某个命名空间,并向其添加我们自己定义的内容:
namespace 命名空间的名字
{
// ..........
}
花括号之间的任何定义都将成为命名空间std
的一部分。
举个例子,当我们想为标准库hash
模板定义一个特例化版本时:
打开 std 命名空间,以便特例化 std::hash
namespace std
{
// ...........
}
23、
标准库算法都是函数模板
标准库容器都是类模板
24、类的static
成员
static
成员属于类,不是与对象,被类的所有对象共享,现有static
成员后有对象,所以static
成员要在类外初始化。
由于static
成员属于类,不属于对象,所以static
成员函数没有this
指针。this
指针是指向本对象的指针,正因为没有this
指针,所以static
成员函数不能访问非static
的成员。