一、 关于无符号与有符号数
无符号不能为负数
循环如果使用无符号计数,以>=为条件将导致死循环
二、 C++ 11的初始化方法
三、 extern的使用
对于希望分离成多个文件独立编译的变量,使用extern公开出去
声明一个变量并extern出去,但注意不要声明+定义一起,否则失去了extern作用了
extern变更的声明可以在多个文件,但定义只能在一个文件
四、复合类型:引用与指针
引用类型一旦初始化就与初始对象绑定在一起,不可以更换绑定。且必须在定义时赋值。
引用类型不可以与值绑定
指针可以不在声明时初始化,指针是一个对象
指针赋值之间类型必须相同
C++ 11的标准提供了nullptr为指针初始化为空。 引用cstdlib标准库中,还可以用NULL为指针初始化
任何非0的指针都为true.
void*指针,可用于存放任意对象的地址,但不能直接操作指针所指的对象,可理解为操作内存空间
五、理解复合类型
对于一个变量,要理解其类型,最简单的办法从右向左阅读变量的定义,离变量名最近的对变量有最直接的影响。
int i = 42 ;int *p;int *&r = p; r = &i; *r = 0 ;
六、const
const 限定符, 声明了const后就不能改变其值,但可以使用非常量来初始化const值
const 在多文件中的使用:在一个文件中用extern声明同时定义,其他文件中仅extern声明即可。
const声明的任何类型都不可以改值!
const指针, 即指针指向的地址是不能改变的
int *errNumb = 0 ;int *const curErr = &errNumb;const double pi = 3.14 ;const double *const pip = π
顶层const与底层const: 顶层const描述一个变量或者指针本身是一个const, 即可以认为最上层是const, 而底层const描述多用于指针所向的对象自身是常量
拷贝的操作必须两对象都具有相同的底层const
一般来说非常量可转换为常量。
七、常量表达式 constexpr
具有以下两个条件的可称为常量表达式:
表达式左侧的变量需定义为常量
表达式右侧的值是不用到运行时就能确定的。
C++ 11的标准规定,可以使用constexpr 让编译器验证是否为常量表达式,也可定义函数为constexpr 这样就可以在常量表达式中使用。
constexpr int mf =20 ;constexpr int limit = mf+1 ;constexpr int sz = size ();
constexpr 定义指针,表达此指针为常量指针,因此根据常量指针的特点,必须进行初始化。
注意constexpr定义后的指针就为常量指针,即此const为指针对象的顶层const
const int *p = nullptr ;constexpr int *q = nullptr ;
八、类型别名
使用typedef 为类型起一个别名:
C++ 11 新标准可使用using语法替换typedef
特别注意当有const在最左侧声明时,带指针的类型别名解释不能直接替换后来翻译,如:
typedef char *pstring; const pstring a;
九、Auto类型说明符 (C++11)
不需要强制为某个变量指定类型的声明方式
注意: auto一般会忽略掉顶层的const,当auto引用时!才会保留const
auto多变量声明类型必须一样
int i = 0 ;const int ci = i;auto &n = i, *p = &ci;
十、decltype类型说明符(C++11)
使用decltype可取出表达式或变量的类型,以此类型再声明变量。
注意:与auto不同的是decltype的值与其内的变量或表达式密切相关,同时也可使用到顶层const和引用。
注意:带括号的表达式或变量,使用decltype时,将必定!返回对应结果的引用类型。而且普通无多个括号时,只有表达式或变量是引用类型才为引用。
十一、关于结构体
C++11新标准规定可以为结构体内的变量设置初始值
十二、关于using的用法
using std:cin;using namespace std;
十三、string
string的size()返回的size_type,不确定具体类型,但一定是无符号的,所以比对时一定注意。
十四、 for : (C++ 11)
类似于foreach,for 与: 结合取出对象。
十五、 vector
vector<int > ivec; vector<int > ivec2 (ivec) ;vector<int > ivec3 = ivec; vector<int > ivec4 = {5 ,6 ,7 } vector<int > vec (10 ,-1 ); vector<int > vec2 (10 ) ;
区别初始化vector时,花号与普通括号内的值的意义。
curly braces还可以智能识别,如果其内的第一位与vector不符,且为数字,则此第一位可以表示为初始化的长度。第二位必须与类型相符。
添加元素: push_back. 注意不可用下标添加元素
#include <iostream> #include <vector> using namespace std;int main () { vector<string> myVec; myVec.push_back ("a" ); myVec.push_back ("b" ); cout<< myVec[1 ] << endl; auto &v = myVec[1 ]; v = "c" ; cout<< myVec[1 ]<<endl; return 0 ; }
十六、 迭代器的使用
string与vector都有迭代器。但返回的类型是未知的。因此经常使用auto来接收。
vector<vector<string>> myVec; vector<string> iVec; iVec.push_back ("a" ); myVec.push_back (iVec); if (myVec.begin () != myVec.end ()){ cout<< "非空vector" <<endl; auto firstVec = myVec.begin (); auto &v = (*firstVec)[0 ]; cout<< "第一个值:" << v; v = "b" ; cout<< "改:第一个值:" << (*firstVec)[0 ]; }
C++中推荐尽量使用迭代器加!= 来做遍历。因为标准库的容器都定义了== !=。大部分没有定义<比较。
vector<int > myVec = {5 ,9 ,7 ,5 ,8 ,2 }; cout << myVec[1 ]<<endl; for (auto it = myVec.begin (); it != myVec.end () ; ++it){ cout<< *it << endl; }
迭代器的表示:
除了用auto外,还可以使用::iterator,或::const_iterator表示常量vector或string。
vector<int > a; a.push_back (6 ); vector<int >::iterator it = a.begin (); cout<< *it << endl;
c++ 11 中引入 cbegin和cend用于表示vector的常量迭代器,返回永远是const_iterator.
访问迭代器
使用C++的箭头, 将解引用与.运算符合在一起了。
特别注意,迭代器的循环不能添加元素,否则将破坏迭代器。
迭代器之间的运算:
+、- 一个数,表示移位置
+=、-=也适用
相减得到之间的距离:得到的类型为difference_type。为有符号类型。
大小比较根据位置而定
十七、 数组的使用
与vector区别,数组是定长的
不存在存引用的数组
int arr[10 ];int * parr[10 ];
特殊情况的初始化:字符数组初始化必须加一个’\0‘结尾字符
重要:存对象的数组不需要由另一个数组拷贝,也不需要赋值!
可以定义引用或指针数组来指向一个普通的数组:
int a[] = {1 ,5 ,3 ,6 ,8 };int (*b)[5 ] = &a;int (&c)[5 ] = a;cout << a[0 ]<< endl; cout << *b[0 ]<<endl; cout << c[0 ]<<endl;
string nums = {"a" ,"b" ,"c" }; string *p2 = nums;
对数组使用decltype关键字返回的是数组还不是指针。使用auto返回的是指针!
int a[] = {1 ,5 ,3 ,6 ,8 };auto b (a) ;decltype (a) c = {5 ,9 ,8 ,7 };
指针其实也是一个迭代器,拥有之前迭代器的操作。
如用指针做迭代器输出:
for (int *i = a; i != &a[5 ]; i++){ cout << *i << endl; }
C++ 11引入 begin和end函数用于取出数组的头尾指针位置。
for (int *i = begin (a); i != end (a); i++){ cout << *i << endl; }
begin与end相减返回的类型为ptrdiff_t。与迭代器之差不同。但类型类似是符号数
指针当成数组用:数组下标与vector和string不同,不是无符号类型!。可为负数。
int *p = &ia[2]; int j = p[1];//与*(p+1)等价 int k = p[-2];//返回ia[0]的元素
十八、 C风格字符串
C风格的字符串,操作的都是指针,而没有string对象的操作。
C++中的cstring就是string.h的C++版本。
使用C风格字符串,必须保证字符数组以’\0’结尾,否则就会发生严重错误!
常用函数
strlen (p)strcmp ()strcat (p1,p2)strcpy (p1,p2)
使用C的字符串在估算数组长度时会充满风险,建议使用标准库string.
十九、 与旧代码兼容
以下 C字符串意为: 以空字符结尾的字符数组
允许使用C字符串初始化 string,或为其赋值
string的加法运算,允许有一个C字符串
string的复合赋值,右侧可以为一个C字符串
此三个专为string标准库设计,反之如果是C字符则不行。但可以使用string.cstr()返回一个C字符串。如:
string s ("Hello" ) ;char * str = s;const char *str = s.cstr ();
int a[] = {1 ,5 ,3 ,6 ,8 };vector<int > ivec (begin(a),end(a)) ;cout << ivec[0 ] << endl;
二十、 多维数组
本身C++是没有多维数组的,可以用一个指针嵌套的思想理解。从左往右的理解一个多维数组。
如3,4的数组表示数组有三个元素,每个元素又是一个有四个元素的数组
定义,二维数组可理解为行和列,平铺式的定义是以一行一行的定义来:
int a[3 ][4 ] = { {1 ,2 ,3 ,4 }, {5 ,6 ,7 ,8 }, {9 ,10 ,11 ,12 } }; int b[3 ][4 ] = {1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 ,11 ,12 };
遍历查值,除了用下标查找外,可以使用C++ 11的for
int b[3 ][4 ] = {1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 ,11 ,12 }; for (auto &row:b){ for (auto &i:row){ cout << i <<endl; } }
for (auto p = b;p != b+3 ;p++){ for (auto q = *p; q != (*p)+4 ; q++){ cout << *q <<endl; } }
using int_array = int [4 ];
二十一、 运算符组合使用
注意++ 运算符优先级高于解引用优先级
int b[3 ][4 ] = {1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 ,11 ,12 }; auto p = begin (b[0 ]); cout << *p++ << endl; cout << *p << endl; cout << *++p << endl;
二十二、 位运算符
* 优先级: 算术运算符 > 移位运算符 >条件运算符
二十三、 函数
函数的形参会自动忽略顶层const。
尽量将不改动的对象形参设为常量形参。以阻止调用时出错。
二十四、 内联函数inline
使用Inline定义内联函数
当函数被声明为内联函数之后, 编译器会将其内联展开, 而不是按通常的函数调用机制进行调用.
在使用内联函数时要留神:
1.在内联函数内不允许使用循环语句和开关语句;
2.内联函数的定义必须出现在内联函数第一次调用之前;
3.类结构中所在的类说明内部定义的函数是内联函数。
Tip: 只有当函数只有 10 行甚至更少时才将其定义为内联函数.
优点: 当函数体比较小的时候, 内联该函数可以令目标代码更加高效. 对于存取函数以及其它函数体比较短, 性能关键的函数, 鼓励使用内联.
缺点: 滥用内联将导致程序变慢. 内联可能使目标代码量或增或减, 这取决于内联函数的大小. 内联非常短小的存取函数通常会减少代码大小, 但内联一个相当大的函数将戏剧性的增加代码大小. 现代处理器由于更好的利用了指令缓存, 小巧的代码往往执行更快。
结论: 一个较为合理的经验准则是, 不要内联超过 10 行的函数. 谨慎对待析构函数, 析构函数往往比其表面看起来要更长, 因为有隐含的成员和基类析构函数被调用!
二十五、类成员
C++11 标准中,将默认值声明成一个类内初始值:
std::vector<Screen> screens{Screen (24 , 80 , '')};
Screen &Screen::set (pos r) { contents[r] = 10 ; return *this ; }
如果一个const成员函数以引用形式返回 * this, 则其返回类型是常量引用。因此如下代码无法执行
myScreen.display (cout).set ('s' );
二十六、 析构函数
参考文章:https://blog.csdn.net/yangkunqiankun/article/details/74885784
对于非指针类型的对象,离开作用域会主动调用析构
另外由于vector的扩容机制问题,也会导致对象的复制拷贝过程,从而对于对象会导致其析构
void initShaders () { ShaderInclude::ShaderSource vecShaderSource = ShaderInclude::load (shaderDir + "vec.glsl" ); ShaderInclude::ShaderSource fragShaderSource = ShaderInclude::load (shaderDir + "frag.glsl" ); auto vertShader = Shader (vecShaderSource, GL_VERTEX_SHADER); auto fragShader = Shader (fragShaderSource, GL_FRAGMENT_SHADER); auto fragShader2 = Shader (ShaderInclude::load (shaderDir + "frag2.glsl" ), GL_FRAGMENT_SHADER); std::vector<Shader> shaders1{ vertShader, fragShader }; std::vector<Shader> shaders2{ vertShader, fragShader2 }; printf ("Dummy 1\n" ); shaderProgram = new ShaderProgram (shaders1); shaderProgram2 = new ShaderProgram (shaders2); }
二十七、 C++ noexcept
表明此函数没有异常
C++11后的新特性
使用后,本来的throw将不再抛到上层,会直接调用 terminate终止
double &Vector::operator [](int i) noexcept { if (i < 0 || i > size ()){ throw std::out_of_range{"Vector out of range" }; } return elem[i]; } Vector user (int sz) { Vector v (sz) ; try { v[102 ] = 38 ; cout<< v[102 ] <<endl; }catch (out_of_range &err) { cerr<<err.what ()<<endl; } }
在删除noexcept时,将正常捕获异常
二十八、 类的不变式invariant
假设某事为真的声明为类不变式
类的不变式是构造函数的任务
Vector v (23 ) ;Vector::Vector (int s){ if (s < 0 ){ throw length_error ("negative size" ); } elem = new double [s]; sz = s; }
二十九、 使用assert, static_assert 或 合约
C* assert断言机制,如果内容发生异常,则会直接terminate
static_assert 使用常量定义的检测
三十、 C++ 拷贝控制
30.1 分类
在类通过定义五类特殊的成员函数:
拷贝构造函数
拷贝赋值运算符
移动构造函数
移动赋值运算符
析构函数
30.2 拷贝构造函数
一个构造函数的第一个参数是自身引用,但任何额外参数都有默认值,由此为拷贝构造函数
Foo(const Foo&)
这两种都会走拷贝构造函数
string s = “abc”;
string s(“abc”);
不仅出现在定义变量时,也出现在以下几种情况:
实参传到非引用的参数
返回值为非引用
以花号初始化数组或一个聚合类成员
如果类中未定义,则默认编译器会生成一个
如果定义了explicit,则不能使用拷贝函数的行为
30.3 拷贝赋值
Foo& operator=(const Foo&)
通常这种走拷贝赋值函数
f2 = f;
直接初始化时,编译器会主动找匹配的函数
Foo f2(200);
Foo f3 = f2; //这样走的是赋值构造函数
如果类中未定义,则默认编译器会生成一个合成拷贝赋值运算符
30.4 析构函数
不可被重载,且有唯一性,释放对象非static数据成员。
析构的部分是隐式的
需要理解析构并不是执行销毁的过程,而只是成员销毁步骤的另一个部分
由于内置类型没析构函数,所以销毁内置类型时什么也不会做,比如隐式销毁一个内置指针类型成员,不会销毁其指向的对象
一个引用,或者指针离开作用域并不会调用其析构函数,所以需要手动去释放
{ Foo *f = new Foo (); auto p = make_shared <Foo>(); delete f; }
30.5 三五原则
三法则: 如果一个类需要自定义析构函数,那基本肯定的是也需要有自己的拷贝构造和拷贝赋值函数
如果有拷贝赋值,必然要有拷贝构造, 相反也成立。但不一定要有析构
扩展, 五法则,增加两种特殊函数,移动构造函数,移动赋值函数
30.6 =default
当希望编译器用合成构造或合成赋值函数时,在后面接上=default即可。
在定义里使用=default表示使用内联的方式,要想使用非内联,可以在类定义外的地方使用=default
Foo& Foo::operator =(Foo &) = default ;
=default只能对编译器带有合成功能的函数使用
30.7 阻断合成 =delete
当不希望编译器为我们合成拷贝构造函数,赋值运算符函数时,必须在定义时使用=delete阻断
=delete可对任意函数使用
析构函数一般不能被定义为=delete,否则将无法删除对象
如果一个类的某个成员,不具有拷贝, 复制,删除的功能,则对应到类的合成拷贝,赋值,删除函数将被默认=delete掉。 这个规则同样适用于有引用成员的类。
30.8 使用private来阻断
使用private阻止了一般用户的访问,但友元函数和成员函数均可以访问,如果未定义,则会报链接错误。因此不推荐此种方法。
30.9 类值的行为类
每一个拷贝后的东西都是自己独立的个体,而不会影响到原对象。
设计示例:
#include "ValueAssignmentTemple.h" ValueAssignmentTemple& ValueAssignmentTemple::operator =(ValueAssignmentTemple &s) { auto ns = new string (*s.ps); delete this ->ps; this ->ps = ns; this ->i = s.i; return *this ; } ValueAssignmentTemple& ValueAssignmentTemple::operator =(string &s) { *this ->ps = s; return *this ; } ValueAssignmentTemple::~ValueAssignmentTemple (){ delete this ->ps; } string &ValueAssignmentTemple::operator *() { return *this ->ps; } ValueAssignmentTemple vat ("God1" ) ; cout << *vat << endl; ValueAssignmentTemple vat2 (vat) ; cout << *vat << "," << *vat2 <<endl; cout << vat.ps << "," << vat2.ps << endl; God1 get copyed God1,God1 0x7fddf5c05a90 ,0x7fddf5c05ab0
赋值运算符函数的注意:
要保证参数是自身的,即自己赋值给自己的情况
先拷贝再删除自己对象的中的成员的指针。
30.10 类指针的行为类
与类值的行为类对比,就是把一个成员的本身传过去,而不是拷贝。因此,为了不影响原对象,需要在析构进行判断,这里就需要加入一个引数计数的实现方法:
引用计数的意义: 当自己生成时,表示计数占一,当自己换成其他时,自己的计数要减一
例:
class PtrAssignmentTemple {public : string *ps; int i; int *use; PtrAssignmentTemple (const PtrAssignmentTemple& s):ps (s.ps), i (s.i), use (s.use){ ++*use; }; PtrAssignmentTemple (const string &s = string ()):ps (new string (s)),i (0 ), use (new int (1 )){}; PtrAssignmentTemple& operator =(PtrAssignmentTemple &); PtrAssignmentTemple& operator =(const string &); string& operator *(); ~PtrAssignmentTemple (); }; PtrAssignmentTemple& PtrAssignmentTemple::operator =(PtrAssignmentTemple &s) { ++*s.use; if (--*this ->use == 0 ) { delete this ->ps; delete this ->use; } this ->ps = s.ps; this ->use = s.use; return *this ; } PtrAssignmentTemple& PtrAssignmentTemple::operator =(const string &s) { *this ->ps = s; return *this ; } PtrAssignmentTemple::~PtrAssignmentTemple (){ if (--*this ->use == 0 ) { delete this ->ps; delete this ->use; } } string &PtrAssignmentTemple::operator *() { return *this ->ps; } PtrAssignmentTemple vat ("God1" ) ; cout << *vat << endl; PtrAssignmentTemple vat2 (vat) ; cout << *vat << "," << *vat2 <<endl; cout << vat.ps << "," << vat2.ps << endl;
30.11 对象移动
右值引用: 使用&& 表示, 只能绑定到将要销毁的对象上。表达式等。短暂。
左值引用:常规引用,持久
特别的,可以 将一个带const的引用绑定到右值引用上
int i = 2 ;const int &r3 = i * 42 ;
使用move,可以将一个左值显式转换为对应的右值引用
int &&r1 = 42 ;int &&r2 = std::move (r1);
30.12 移动构造函数和移动赋值运算符
class StrVec {public : StrVec (StrVec&&) noexcept ; StrVec &StrVec::operator =(StrVec &&rhs) noexcept ; }
场景: 如果不想用拷贝,而只是想支持移动操作
注意: 任何额外的参数都必须有默认实参。 原因是移后源对象会被销毁,如果不在给定对象中未删除,新对象中的指针就会失效。所以应该要这样在移动构造函数中写:
s.elements = s.first_free = s.cap = nullptr ;
告诉标准库 noexcept的重要性,在移动操作过程中的发生的异常是不可处理的,标准库会为拷贝操作做异常处理,为了让标准库不做拷贝操作,这里需要指明noexcept
首先检测自身是否与参数的结果相同,如果一样,则按移动的处理是先free()自己,再将参数的引用传过来,最后再将参数置none使其进入析构。
30.13 合成移动构造函数,移动赋值运算符
生成条件
条件1: 如果没有定义拷贝构造函数和拷贝赋值运算符和析构函数,编译器才会为其生成移动构造函数和移动赋值运算符。反之,如果定义了任一个,就不会生成,会默认使用拷贝操作。
条件2: 如果成员是类类型,则需要保证此类也有对应的移动操作才行
只有条件1和条件2都存在时,才会可能生成合成移动的函数和运算符。
与拷贝操作不同,移动操作永远不会被隐式定义为删除的函数,但如果强制用=default指定时,有可能被定义为删除的函数,条件如下:
有成员不能合成移动构造函数或未定义时
有类成员的移动构造函数被定义为删除时
类的析构被定义为不可访问时
类成员是const或引用时
定义了移动构造函数和移动运算符的类,也必须定义拷贝构造函数等,不然拷贝的相关函数会被定义为删除的
30.14 同时都有的情况
移动右值,拷贝左值
StrVec v1, v2; v1 = v2; StrVec getVec (istream &) ;v2 = getVec (cin);
如果没有定义移动或移动是删除的情况下,即使用了move函数,也会使用拷贝操作代替移动
30.15 移动迭代器
改变迭代器解引用运算符行为来适配移动,生成移动迭代器。
此解引用运算符生成的将是一个右值引用,意味着构造函数将是一个移动构造函数。
30.16 引用限定符
使用引用限定符,强制返回变为一个左值或右值,使用&表示 返回是一左值,&& 是右值
Foo anotherMem () const & ;
支持重载:
Foo Foo::sorted () && ; Foo Foo::sorted () const & ;
如果用了const标识,在加了引用限定符,在重载时必须指明引用限定符
三十一、C++ 重载运算与类型转换
30.1 概念
通过参数个数分为:一元运算符,二元运算符。 除operator()外,其他不能有默认实参。
运算符函数是成员函数时,第一个左侧运算对象绑定到隐式的this指针上
不能重载已有的内置运算符
(+、-、、& ) 既是一元运算符,又是二元运算符。
运算符重载后优先级不变
作用域运算符不可重载
显示调用: data1.operator+=(data2) 等价于 data1 +=data2
一般不重载点运算符和取引用运算符
除了对称性的运算符,双与,算术,相等性,关系,一般都是成员函数
成员函数时,左侧运算对象表示为第一个所属类。
struct node { int a; string name; operator +(node &b){ a += b.a; name += b.name; } };
30.2 输入和输出运算符
“>>” 输入运算符, “<<” 输出运算符
重载输出运算符: 需要第一个参数为非常量的引用类型ostream, 因为需要改变其状态 ,且不能复制,所以用引用。
ostream &operator <<(ostream &os, const Sales_data &item) { os<<item.isbn () << " " << item.units_sold; return os; }
必须是非成员函数,因为返回不能是this指针,而必须是osstream或isstream.
重载输入运算符:第一个参数为非常量的引用类型,第二参数必须为非常量,因为需要改变其状态
istream &operator >>(isstream &is, Sales_data &item) { double price; is >> item.book_name >> item.units_sold >> price; if (is) item.revenue = item.units_sold * price; else item = Sales_Data (); return is; }
对于输入运算符重载,需要设定标识符,如failbit读取失败符, eofbit表示文件结束符
30.3 算术和关系运算符
一般为非成员函数以允许左侧与右侧的运算对象进行互换。但一般需要实现复合赋值运算符! 如下例中需要先定义+=运算符。在都定义的情况下,通常情况下应该使用复合赋值来实现算术运算符。
Sales_data &operator +(const Sales_data &lhs,const Sales_data &rhs) { Sales_data sum = lhs; sum += rhs; return sum; }
相等与不相等运算符,一般都是成对出现在非成员函数中。
30.4 下标运算符
必须是成员函数
返回的是元素的引用
可以用常量标识返回的结果是否可以改变
class StrVec {public : std::string &operator [](std::size_t n) { return elementes[n];} const std::string &operator [](std::size_t n) const { return elementes[n];} private : std::string *elements; }
30.5 递增递减运算符
建议为成员函数,不强制
需要定义前置和后置的两个版本,但由于同时存在时重载会有问题,所以会添加一个无用的int实参
class StrBlobPtr {public : StrBlobPtr &operator ++(); StrBlobPtr &operator --(); StrBlobPtr operator ++(int ); StrBlobPtr operator --(int ); private : std::string *elements; } StrBlobPtr &StrBlobPtr::operator ++() { check (curr, 'increment past end!' ); curr++; return *this ; } StrBlobPtr &StrBlobPtr::operator ++() { curr--; check (curr, 'increment past begin!' ); return *this ; } StrBlobPtr StrBlobPtr::operator ++(int ) { StrBlobPtr ret = *this ; ++*this ; return ret; } StrBlobPtr StrBlobPtr::operator ++() { StrBlobPtr ret = *this ; --*this ; return ret; } StrBlobPtr p (a1) ;p.operator ++(0 ); p.operator ++();
30.6 成员访问运算符
解引用运算符,箭头运算符
class StrBlobPtr {public : std::string &operator *() const { auto p = check (curr, "past end" ); return (*p)[curr]; } std::string *operator ->() const { return &this ->operator *(); } }
箭头运算符不能改变其获取成员的事实
30.7 函数调用运算符
如果一个类定义函数调用运算符,则该类的对象称作 函数对象
struct absInt { int operator () (int val) const { return val < 0 ? -val; val; } }
含状态的函数对象
class PrintString {public : PrintString (ostream &o = cout, char c = ' ' ): os (o), sep (c){} void operator () (const string &s) const {os << s << sep;} private : ostream &os; char sep; }; PrintString printer; printer (s);PrintString errors (cerr, '\n' ) ;errors (s);
for_each(vs.begin (), vs.end (), PrintString (cerr, '\n' ));
stable_sort (words.begin (), words.end (), [](const string &a, const string &b){return a.size () < b.size ();})其类似于: class ShorterString {public : bool operator () (const string &s1, const string &s2) { return s1.size () < s2.size (); } } stable_sort (words.begin (), words.end (), ShorterString ());
标准库定义的函数对象
如加法plus, 二元的%取模 modulus, equal_to等执行==。 如例:
plus<int > intAdd; negate<int > intNegate; int sum = intAdd (10 , 20 );sum = intNegate (intAdd (10 , 20 ));
作用:对于算法中的调用传递
sort (svec.bein (), svec.end (), great <string>());
特别的,标准库的函数对象对于指针同样适用。
sort (nameTable.begin (), nameTable.end (), less <string *>());
可调用对象
函数,函数指针,lambda表达式,bind创建的对象等
不同的可调用对象也有各自的类型,如:
int add (int i, int j) {return i+j;}auto mod = [](int i, int j){return i % j}map<string, int (*)(int ,int )> binops; binops.insert ({"+" , add});
标准库function类型
使用function解决可调用对象的问题,其本质是一个模板。
function<int (int , int )> f1 = add; function<int (int , int )> f2 = divide (); function<int (int , int )> f3 = [](int i, int j){return i * j;}; map<string, function<int (int , int )>> binops = { {"+" , add}, {"-" , std::minus <int >()}, {"/" , divide ()}, {"*" , [](int i, int j){return i * j;}}, {"mod" , mod} };
30.8 类型转换运算符
特殊的成员函数,负责将一个类类型的值转换为其他类型,如:
必须为成员函数,不能有返回类型,形参列表必须为空,const
class SmallInt {public : SmallInt (int i = 0 ): val (i) { if (i < 0 || i > 255 ) throw std::out_of_range ("Bad small int value" ); } operator int () const {return val;} private : std::size_t val; } SmallInt si = 3.14 ; si + 3.14 ;
explicit operator int () const {return val;}SmallInt si = 3 ; si +3 ; static_cast <int >(si) + 3 ;
特别的,显式的类型转换会被隐式调用,在if、while、for、条件运算符
operator const int () ; operator int () const ;
避免二义性
使用显式的调用来解决
在成员函数中避免定义多个以内置算术类型为结果的运算符