Hi!这里是山幺幺的c++ primer系列。写这个系列的初衷是,虽然在学校学习了c++,但总觉得对这门语言了解不深,因此我想来啃啃著名的c++ primer,并在这里同步记录我的学习笔记。由于我啃的是英文版,所以笔记是中英文夹杂的那种。另外由于已有一定的编程基础,所以这个系列不会含有太基础的知识,想入门的朋友最好自己啃书嘻嘻~
基础概念
overloaded operator
- overloaded operator function的参数个数
- 若不是成员函数:与operator的operand数相同
- 若是成员函数:=operator的operand数 - 1,因为左操作数bound to this指针
- 除了 overloaded function-call operator(即operator()),an overloaded operator may not have default arguments
- an operator function must either be a member of a class or have at least one parameter of class type
// error: cannot redefine the built-in operator for ints
int operator+(int, int);
- 能/不能被重载的操作符一览
- 只能重载已存在的操作符,不能发明新的操作符
- 重载不改变操作符的优先级和结合性
- 调用重载操作符的两种方式
data1 + data2; // 调用operator+的法一
operator+(data1, data2); // 调用operator+的法二
data1 += data2; // 调用operator+=的法一
data1.operator+=(data2); // 调用operator+=的法二
- 最好不要重载的
- 最好不要重载&&、||、,:它们本来 guarantee the order in which operands are evaluated 和 short -circuit evaluation properties,重载后则不再保证这些(因为重载操作符相当于函数调用),所以最好不要重载它们
- 最好不要重载&、,:the language defines what the comma and address-of operators mean when applied to objects of class type;because these operators have built-in meaning, they ordinarily should not be overloaded
- 重载的设计建议
- 必须重载为成员函数的:assignment (=)、subscript ([])、call (())、member access arrow (->)
- 建议重载为成员函数的:compound-assignment、increment、decrement、dereference
- 建议重载为非成员函数的:symmetric operators(即左右操作数类型可以互换)(比如arithmetic、equality、relational、bitwise operators)
string s = "world"; string t = s + "!"; // ok: we can add a const char* to a string string u = "hi" + s; // would be an error if + were a member of string
Input and Output Operators
重载 <<
- 原本的<<:左操作数是一个 reference to a nonconst ostream object(是引用是因为we cannot copy an ostream object);返回its ostream parameter
- 栗子
ostream &operator<<(ostream &os, const Sales_data &item) {
os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price();
return os;
}
- 设计的注意事项:
- 一般而言output operators should print the contents of the object, with minimal formatting(比如should not print a newline),从而让user可以设计details of their output
- I/O operators必须是非成员函数:因为第一个参数是i/ostream类型
- I/O operators usually must be declared as friends:因为通常要访问nonpublic成员
重载 >>
- 原本的>>:左操作数是一个 reference to a stream;返回a reference to its given stream
- 栗子
istream &operator>>(istream &is, Sales_data &item) {
double price; // no need to initialize; we'll read into price before we use it
is >> item.bookNo >> item.units_sold >> price;
if (is) // check that the inputs succeeded
item.revenue = item.units_sold * price;
else
item = Sales_data(); // input failed: give the object the default state
return is;
}
- input过程中可能发生的错误
- the stream contains data of an incorrect type
- hit end-of -file or some other error on the input stream
- 设计的注意事项:
- input operators should decide what, if anything, to do about error recovery
- usually an input operator should set only the failbit,表示读入的数据格式不正确(不能set badbit,因为badbit would indicate that the stream was corrupted;不能set eofbit,因为eofbit would imply that the file was exhausted;这两个bit都应该交给IO library itself to indicate)
- I/O operators必须是非成员函数:因为第一个参数是i/ostream类型
- I/O operators usually must be declared as friends:因为通常要访问nonpublic成员
Arithmetic and Relational Operators
arithmetic operators
- classes that define an arithmetic operator generally define the corresponding compound assignment operator as well,因为重载arithmetic operator的函数体中通常会使用重载的compound assignment operator
// arithmetic operator
Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs) {
Sales_data sum = lhs; // copy data members from lhs into sum
sum += rhs; // add rhs into sum
return sum;
}
// compound assignment operator
Sales_data& Sales_data::operator+=(const Sales_data &rhs) {
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
equality operators
bool operator==(const Sales_data &lhs, const Sales_data &rhs) {
return lhs.isbn() == rhs.isbn() &&
lhs.units_sold == rhs.units_sold &&
lhs.revenue == rhs.revenue;
}
bool operator!=(const Sales_data &lhs, const Sales_data &rhs) {
return !(lhs == rhs);
}
relational operators
- 设计原则:if the class also has ==, define < only if the definitions of
< and == yield consistent results(即:不相等的两个对象,必然有一个<另一个)
Assignment Operators
普通assignment operators
- 作用:allow other types as the right-hand operand(copy- and move-assignment operators要求左右两边都是该类)
- 特点:assignment operators, regardless of parameter type, must be defined as member function;should return a reference to its left -hand operand
- 栗子:这个assignment可以不用check for self-assignment,因为左右是不同的类型,不可能用于self-assignment
StrVec &StrVec::operator=(initializer_list<string> il) {
// alloc_n_copy allocates space and copies elements from the given range
auto data = alloc_n_copy(il.begin(), il.end());
free(); // destroy the elements in this object and free the space
elements = data.first; // update data members to point to the new space
first_free = cap = data.second;
return *this;
}
compound-assignment operators
- 特点:compound assignment operators are not required to be members(但建议设计为成员);should return a reference to their left-hand operand
Subscript Operator
- 特点:subscript operator must be a member function;通常returns a reference to the element that is fetched(这样subscript就可以出现在赋值语句的任意一边了);通常define both const and nonconst versions of subscript
- 栗子
class StrVec {
public:
std::string& operator[](std::size_t n) { return elements[n]; }
const std::string& operator[](std::size_t n) const { return elements[n]; }
private:
std::string *elements; // pointer to the first element in the array
};
Increment and Decrement Operators
特点
- 建议重载为成员,因为它们change the state of the object on which they operate
- should define both the prefix and postfix versions
prefix increment/decrement operators
- 特点:return a reference to the incremented or decremented object
- 栗子
// prefix: return a reference to the incremented/decremented object
StrBlobPtr& StrBlobPtr::operator++() {
// if curr already points past the end of the container, can't increment it
check(curr, "increment past end of StrBlobPtr");
++curr; // advance the current state
return *this;
}
StrBlobPtr& StrBlobPtr::operator--() {
// if curr is zero, decrementing it will yield an invalid subscript
--curr; // move the current state back one element
check(-1, "decrement past begin of StrBlobPtr");
return *this;
}
post increment/decrement operators
- 特点:为与prefix区分,postfix versions take an extra (unused) parameter of type int,使用它时,编译器supplies 0 as the argument for this parameter;return the old (unincremented or undecremented) value(是value不是reference)
- 栗子
StrBlobPtr StrBlobPtr::operator++(int) {
// no check needed here; the call to prefix increment will do the check
StrBlobPtr ret = *this; // save the current value
++*this; // advance one element; prefix ++ checks the increment
return ret; // return the saved state
}
StrBlobPtr StrBlobPtr::operator--(int) {
// no check needed here; the call to prefix decrement will do the check
StrBlobPtr ret = *this; // save the current value
--*this; // move backward one element; prefix -- checks the decrement
return ret; // return the saved state
}
StrBlobPtr p(a1); // p points to the vector inside a1
p.operator++(0); // call postfix operator++
p.operator++(); // call prefix operator++
Member Access Operators
操作符
- ->
- *
特点
- operator arrow must be a member
- dereference operator is not required to be a member but usually should be a member
栗子
class StrBlobPtr {
public:
std::string& operator*() const {
auto p = check(curr, "dereference past end");
return (*p)[curr]; // (*p) is the vector to which this object points
}
std::string* operator->() const { // delegate the real work to the dereference operator
return & this->operator*();
}
};
operator arrow的特殊性质
- 其他操作符都可以被重载来做任何事,比如+可以被重载为-,但we cannot change the fact that arrow fetches a member,arrow operator never loses its fundamental meaning of member access
- overloaded arrow operator must return either a pointer to a class type or an object of a class type that defines its own operator arrow
- point->mem的执行步骤
- 若point是一个指针,则apply built-in arrow operator:相当于执行 (*point).mem; (step 1)
- 若point是an object of a class that defines operator->,则相当于执行point.operator()->mem; :the result of point.operator->() is used to fetch mem. If that result is a pointer, then step 1 is executed on that pointer. If the result is an object that itself has an overloaded operator->(), then this step is repeated on that object. (step 2)
Function-Call Operator
作用
- classes that overload the call operator allow objects of its type to be used as if they were a function
栗子
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; // stream on which to write
char sep; // character to print after each output
};
PrintString printer; // uses the defaults; prints to cout
printer(s); // prints s followed by a space on cout
PrintString errors(cerr, '\n');
errors(s); // prints s followed by a newline on cerr
for_each(vs.begin(), vs.end(), PrintString(cerr, '\n')); // print each element in vs to cerr followed by a newline
特点
- function-call operator must be a member function
lambda本质上是function object
- When we write a lambda, the compiler translates that expression into an unnamed object of an unnamed class
- lambda没有capture时
上面代码实际上等价于stable_sort(words.begin(), words.end(), [](const string &a, const string &b) { return a.size() < b.size();});
PS:因为lambda默认不能change their captured variables,所以是const函数;If the lambda is declared as mutable, then the call operator is not constclass ShorterString { public: bool operator()(const string &s1, const string &s2) const { return s1.size() < s2.size(); } }; stable_sort(words.begin(), words.end(), ShorterString());
- lambda capture by value时:因为variables that are captured by value are copied into the lambda,所以classes generated from lambdas that capture variables by value have data members corresponding to each such variable,且会用捕获的变量值对它们进行初始化
等价于// get an iterator to the first element whose size() is >= sz auto wc = find_if(words.begin(), words.end(), [sz](const string &a)
class SizeComp { SizeComp(size_t n): sz(n) { } // parameter for each captured variable // call operator with the same return type, parameters, and body as the lambda bool operator()(const string &s) const { return s.size() >= sz; } private: size_t sz; // a data member for each variable captured by value }; auto wc = find_if(words.begin(), words.end(), SizeComp(sz));
- lambda capture by reference时:the compiler is permitted to use the reference directly without storing that reference as a data member in the generated class
- classes generated from a lambda expression have a deleted default constructor, deleted assignment operators, and a default destructor
library-defined function objects
- standard library defines a set of classes,包括
- 用法
plus<int> intAdd; // function object that can add two int values
int sum = intAdd(10, 20); // equivalent to sum = 30
// passes a temporary function object that applies the > operator to two strings
sort(svec.begin(), svec.end(), greater<string>());
- 它们对指针有效
vector<string *> nameTable; // vector of pointers
// error: the pointers in nameTable are unrelated, so < is undefined
sort(nameTable.begin(), nameTable.end(), [](string *a, string *b) { return a < b; });
// ok: library guarantees that less on pointer types is well defined
sort(nameTable.begin(), nameTable.end(), less<string*>());
- associative containers use less<key_type> to order their elements
Callable Objects
分类
- 函数
- 指向函数的指针
- lambda
- objects created by bind
- classes that overload the function-call operator
特点
- 每个callable object都有其类型,比如each lambda has its own unique (unnamed) class type
- 不同类型的callable objects可能有相同的call signature(即the type returned by a call to the object and the argument type(s) that must be passed in the call;栗子:int(int, int))
- 即使call signature相同,因为类型不同,以下写法的最后一行也是不正确的
// ordinary function
int add(int i, int j) { return i + j; }
// lambda, which generates an unnamed function-object class
auto mod = [](int i, int j) { return i % j; };
// function-object class
struct div {
int operator()(int denominator, int divisor) {
return denominator / divisor;
}
};
// maps an operator to a pointer to a function taking two ints and returning an int
map<string, int(*)(int,int)> binops;
binops.insert({"+", add}); // ok: add is a pointer to function of the appropriate type
binops.insert({"%", mod}); // error: mod is not a pointer to function
function类
作用
- 解决上面那段代码的问题,把call signature相同的callable object集成到一个map中
操作一览
栗子
function<int(int, int)> f1 = add; // function pointer
function<int(int, int)> f2 = div(); // object of a function-object class
function<int(int, int)> f3 = [](int i, int j) { return i * j; }; // lambda
cout << f1(4,2) << endl; // prints 6
cout << f2(4,2) << endl; // prints 2
cout << f3(4,2) << endl; // prints 8
解决上上段代码问题的办法
map<string, function<int(int, int)>> binops = {
{"+", add}, // function pointer
{"-", std::minus<int>()}, // library function object
{"/", div()}, // user-defined function object
{"*", [](int i, int j) { return i * j; }}, // unnamed lambda
{"%", mod} // named lambda object
};
binops["+"](10, 5); // calls add(10, 5)
binops["-"](10, 5); // uses the call operator of the minus<int> object
binops["/"](10, 5); // uses the call operator of the div object
binops["*"](10, 5); // calls the lambda function object
binops["%"](10, 5); // calls the lambda function object
注意
- 不能直接store the name of an overloaded function in an object of type function
int add(int i, int j) { return i + j; }
Sales_data add(const Sales_data&, const Sales_data&);
map<string, function<int(int, int)>> binops;
binops.insert( {"+", add} ); // error: which add?
- 解决方法一:使用指向函数的指针
int (*fp)(int,int) = add; // pointer to the version of add that takes two ints
binops.insert( {"+", fp} ); // ok: fp points to the right version of add
- 解决方法二:使用lambda
// ok: use a lambda to disambiguate which version of add we want to use
binops.insert( {"+", [](int a, int b) {return add(a, b);} } );
Class-type Conversions(即user-defined conversions,由class类型转为其他类型)
conversion operator
- 作用:a special kind of member function that converts a value of a class type to a value of some other type
- 格式:operator type() const;
PS:这里的type可以是任何可作为函数返回类型的类型(void除外);type不能是an array or a function type - 要求
- 必须是成员函数
- 不能有返回类型(返回值必须是type类型的)
- 形参list必须为空(因为通常是被隐式调用的)
- 通常是常函数
- 栗子
class SmallInt {
public:
SmallInt(int i = 0): val(i) {
if (i < 0 || i > 255) throw std::out_of_range("Bad SmallInt value");
}
operator int() const { return val; }
private:
std::size_t val;
};
SmallInt si;
si = 4; // implicitly converts 4 to SmallInt then calls SmallInt::operator=
si + 3; // implicitly converts si to int followed by integer addition
// the double argument is converted to int using the built-in conversion
SmallInt si = 3.14; // calls the SmallInt(int) constructor
// the SmallInt conversion operator converts si to int;
si + 3.14; // that int is converted to double using the built-in conversion
explicit conversion operators
- 一个危险的栗子:if istream had a conversion to bool,那么下面这段代码就是合法的:首先用bool conversion operator 把 cin 转为 bool,随后the resulting bool value would then be promoted to int,最后the promoted bool value (either 1 or 0) would be shifted left 42 positions
int i = 42;
cin << i; // this code would be legal if the conversion to bool were not explicit!
- 作用:避免上面那个危险的栗子发生
- 用法
class SmallInt {
public:
// the compiler won't automatically apply this conversion
explicit operator int() const { return val; }
// other members as before
};
SmallInt si = 3; // ok: the SmallInt constructor is not explicit
si + 3; // error: implicit is conversion required, but operator int is explicit
static_cast<int>(si) + 3; // ok: explicitly request the conversion
- 一个例外:标了explicit但被用作如下用途的表达式会隐式转换
- the condition of an if, while, or do statement
while (std::cin >> value) // cin is implicitly converted to bool by the istream operator bool conversion function
- the condition expression in a for statement header
- operand to the logical NOT (!), OR (||), or AND (&&) operators
- the condition expression in a conditional (?:) operator
- 使用建议:operator bool ordinarily should be defined as explicit
避免模糊转换
- 情形一
// usually a bad idea to have mutual conversions between two class types
struct B;
struct A {
A() = default;
A(const B&); // converts a B to an A
};
struct B {
operator A() const; // also converts a B to an A
};
A f(const A&);
B b;
// 错误写法
A a = f(b); // error ambiguous: f(B::operator A()) or f(A::A(const B&))
// 正确写法
A a1 = f(b.operator A()); // ok: use B's conversion operator
A a2 = f(A(b)); // ok: use A's constructor
PS:注意we can’t resolve the ambiguity by using a cast,因为cast也会面临ambiguity的问题
- 情形二
struct A {
A(int = 0); // usually a bad idea to have two
A(double); // conversions from arithmetic types
operator int() const; // usually a bad idea to have two
operator double() const; // conversions to arithmetic types
};
void f2(long double);
A a;
f2(a); // error ambiguous: f(A::operator int()) or f(A::operator double())
long lg;
A a2(lg); // error ambiguous: A::A(int) or A::A(double)
// 这样是可以的
short s = 42;
// promoting short to int is better than converting short to double
A a3(s); // uses A::A(int)
- 情形三
struct C {
C(int);
};
struct D {
D(int);
};
void manip(const C&);
void manip(const D&);
manip(10); // error ambiguous: manip(C(10)) or manip(D(10))
- 情形四:只要有two different user-defined conversions could be used,就是模糊的,哪怕这两个转换的代价有差异,即:the rank of an additional standard conversion (if any) matters only if the viable functions require the same userdefined conversion
struct E {
E(double);
};
void manip2(const C&);
void manip2(const E&);
// error ambiguous: two different user-defined conversions could be used
manip2(10); // manip2(C(10) or manip2(E(double(10)))
- 情形五
class SmallInt {
friend SmallInt operator+(const SmallInt&, const SmallInt&);
public:
SmallInt(int = 0); // conversion from int
operator int() const { return val; } // conversion to int
private:
std::size_t val;
};
SmallInt s1, s2;
SmallInt s3 = s1 + s2; // uses overloaded operator+
int i = s3 + 0; // error: ambiguous,可以convert 0 to a SmallInt and use the SmallInt version of +或者convert s3 to int and use the built-in addition