本课程是上一门视频课程"面向对象程序设计"(即: C++面向对象高级编程(上))的续集, 将继续探讨一些未及讨论的主题:
- operator type() const
- explicit complex(...): initialization list { }
- pointer-like object
- function-like object
- namespace
- template specialization
- Standard library
- variadic template (C++ 11)
- auto (C++ 11)
- range-base for loop (C++ 11)
课程目标
- 在先前基础课程所培养的正规, 大器的编程素养上, 继续探讨更多技术.
- 泛型编程(Generic Programming)和面向对象编程(Object-Oriented Programming)虽然分属不同的思维, 但它们正是C++的技术主线, 所以本课程也讨论template(模板).
- 深入探索面向对象之继承关系(inheritance)所形成的对象模型(Object Model), 包括隐藏于底层的this指针, vptr(虚指针), vtbl(虚表), virutual mechanism(虚机制), 以及虚函数(virtual functions)造成的polymorphism(多态)效果.
推荐书目
- C++ Primer (5th Edition) by Stanley B. Lippman
- The C++ Programming Language (4th Edition) by Bjarne Stroustrup
- Effective C++: 55 Specific Ways to Improve Your Programs and Designs (3rd Edition) by Scott Meyers
- Effective Modern C++: 42 Specific Ways to Improve Your Use of C++11 and C++14 by Scott Meyers
- The C++ Standard Library - A Tutorial and Reference (2nd Edition) by Nicolai M. Josuttis
- STL源码剖析 by 侯捷
点评: 选择一本好的C++书籍(1和2两本书皆可), 然后以及尽量多的完成书籍的习题,可以帮助完成C++语法的学习; 熟悉C++的语法后,通过3和4学习如何正确高效地使用C++/C++11/C++14; 如果对STL的一些实现有兴趣,可参考书目5和6.
conversion operator, 转换函数
class Fraction {
public:
Fraction(int num, int den = 1)
: m_numerator(num), m_denominator(den) { }
operator double() const {
return (double) m_numerator / m_denominator;
}
private:
int m_numerator; // 分子
int m_denominator; // 分母
};
Fraction f(3, 5);
double d = 4 + f; // 调用operator double()将f转为0.6
摘自C++ Primer:
A conversion operator is a special kind of member function that converts a value of a class type to a value of some other type. A conversion function typically has the general form
operator type() const;
where type represents a type. Conversion operators can be defined for any type (other than void) that can be a function return type. Conversions to an array or a function type are not permitted. Conversions to pointer types—both data and function pointers—and to reference types are allowed.
Conversion operators have no explicitly stated return type and no parameters, and they must be defined as member functions. Conversion operations ordinarily should not change the object they are converting. As a result, conversion operators usually should be defined as const members.
non-explicite-one-argument ctor
class Fraction {
public:
Fraction(int num, int den = 1)
: m_numerator(num), m_denominator(den) { }
Fraction operator+(const Fraction &f) {
return Fraction(......);
}
private:
int m_numerator;
int m_denominator;
};
Fraction f(3, 5);
Fraction d2 = f + 4; // 调用non-explicit ctor将4转为Fraction(4,1)
// 然后调用operator+
Note (摘自C++ Primer):
A constructor that can be called with a single argument defines an implicit conversion from the constructor’s parameter type to the class type.
Why explicit?
class Fraction {
public:
Fraction(int num, int den = 1)
: m_numerator(num), m_denominator(den) { }
operator double() const {
return (double) m_numerator / m_denominator;
}
Fraction operator+(const Fraction &f) {
return Fraction(......);
}
private:
int m_numerator;
int m_denominator;
};
Fraction f(3, 5);
Fraction d2 = f + 4; // [Error] ambiguous
点评: 在这个例子中, conversion function负责将Fraction转化为double, 而non-explicit-one-argument ctor负责将double转化为Fraction. 然而当conversion function和non-explicit-one-argument ctor同时存在时, 程序出现了二义性问题. 对于编译器而言, Fraction d2 = f + 4
可以理解成4转为Fraction然后调用operator+,同时也可以理解成f转成double, 和4做加法后再转为Fraction, 所以程序是无法编译通过的.
新的explicit-one-argument ctor:
explicit Fraction(int num, int den = 1)
: m_numerator(num), m_denominator(den) { }
关于explicit(摘自C++ Primer):
We can prevent the use of a constructor in a context that requires an implicit conversion by declaring the constructor as explicit. The explicit keyword is meaningful only on constructors that can be called with a single argument. Constructors that require more arguments are not used to perform an implicit conversion, so there is no need to designate such constructors as explicit. The explicit keyword is used only on the constructor declaration inside the class. It is not repeated on a definition made outside the class body.
pointer-like classes, 关于智能指针
template <class T>
class shared_ptr {
public:
T& operator*() const { return *px; }
T* operator->() const { return px; }
shared_ptr(T* p): px(p) { }
private:
T *px;
long *pn;
};
关于pointer-like class和value-like class(摘自C++ Primer):
Classes that behave like values have their own state. When we copy a valuelike object, the copy and the original are independent of each other. Changes made to the copy have no effect on the original, and vice versa.
Classes that act like pointers share state. When we copy objects of such classes, the copy and the original use the same underlying data. Changes made to the copy also change the original, and vice versa.
Of the library classes we’ve used, the library containers and string class have valuelike behavior. Not surprisingly, the shared_ptr class provides pointerlike behavior, as does our StrBlob class (§ 12.1.1, p. 456). The IO types and unique_ptr do not allow copying or assignment, so they provide neither valuelike nor pointerlike behavior.
function-like classes, 所谓仿函数
template <class T>
struct identity {
const T& operator() (const T &x) const { return x; }
};
template <class Pair>
struct select1st {
const typename Pair::first_type&
operator() (const Pair &x) const
{ return x.first; }
};
template <class Pair>
struct select2st {
const typename Pair::second_type&
operator() (const Pair &x) const
{ return x.second; }
};
Function-like class/functor (摘自Numberical Recipes):
A functor is simply an object in which the operator () has been overloaded to play the role of returning a function value.
namespace
关于namespace的定义(摘自C++ Primer):
A namespace definition begins with the keyword namespace followed by the namespace name. Following the namespace name is a sequence of declarations and definitions delimited by curly braces. Any declaration that can appear at global scope can be put into a namespace: classes, variables (with their initializations), functions (with their definitions), templates, and other namespaces. As with any name, a namespace name must be unique within the scope in which the namespace is defined. Namespaces may be defined at global scope or inside another namespace. They may not be defined inside a function or a class.
specialization, 模板特化
template <class Key>
struct hash { ...... };
template <>
struct hash<char> {
size_t operator() (char x) const { return x; }
};
template <>
struct hash<int> {
size_t operator() (int x) const { return x; }
};
template <>
struct hash<long> {
size_t operator() (long x) const { return x; }
};
关于模板特化(摘自C++ Primer):
Redefinition of a class template, a member of a class template, or a function template, in which some (or all) of the template parameters are specified. A template specialization may not appear until after the base template that it specializes has been declared. A template specialization must appear before any use of the template with the specialized arguments. Each template parameter in a function template must be completely specialized.
partial specialization, 模板偏特化
- 个数的偏:
// original class template:
template <typename T, typename Alloc = ......>
class vector {
...
};
// partial specialization:
template <typename Alloc = ......>
class vector <bool, Alloc> {
...
};
- 范围的偏:
// original class template:
template <typename T>
class C
{
// ...
};
// partial specialization:
template <typename T>
class C<T*>
{
// ...
};
关于模板偏特化(摘自C++ Primer):
Differently from function templates, a class template specialization does not have to supply an argument for every template parameter. We can specify some, but not all, of the template parameters or some, but not all, aspects of the parameters. A class template partial specialization is itself a template. Users must supply arguments for those template parameters that are not fixed by the specialization.
关于C++标准库
点评: 标准库是C++重要的组成部分, 包括了容器, 迭代器, 算法和仿函数. 这是一个极其巨大的话题, 三言两语是无法解释的. 完成了C++基础语法的学习后, 应好好参考侯捷老师的"那本STL源码剖析".
variadic templates (since C++ 11)
void print()
{
}
template <typename T, typename... Types>
void print(const T &firstArg, const Types&... args) {
cout << firstArg <<endl;
print(args...);
}
print(7.5, "hello", bitset<16>(377), 42);
// OUTPUT:
// 7.5
// hello
// 0000000101111001
// 42
关于variadic templates(摘自 C++ Primer):
A variadic template is a template function or class that can take a varying number of parameters. The varying parameters are known as a parameter pack. There are two kinds of parameter packs: A template parameter pack represents zero or more template parameters, and a function parameter pack represents zero or more function parameters.
auto (since C++11)
list<string> c;
...
// before C++11
list<string>::iterator ite = find(c.begin(), c.end(), target);
// since C++11
auto ite2 = find(c.begin(). c.end(), target);
关于auto(摘自C++ Primer):
Under the new standard, we can let the compiler figure out the type for us by using the auto type specifier. Unlike type specifiers, such as double, that name a specific type, auto tells the compiler to deduce the type from the initializer. By implication, a variable that uses auto as its type specifier must have an initializer.
range-based for (since C++11)
for (int i : {2, 3, 5, 7, 9, 13, 17, 19}) {
cout << i << endl;
}
vector<double> vec;
...
// pass by value
for (auto elem : vec) {
cout << elem << endl;
}
// pass by reference
for (auto &elem : vec) {
elem*=3;
}
关于range-based for(摘自C++ Primer):
If we want to do something to every character in a string, by far the best approach is to use a statement introduced by the new standard: the range for statement. This statement iterates through the elements in a given sequence and performs some operation on each value in that sequence. The syntactic form is
for (declaration : expression) { statement }
where expression is an object of a type that represents a sequence, and declaration defines the variable that we’ll use to access the underlying elements in the sequence. On each iteration, the variable in declaration is initialized from the value of the next element in expression .