本課程是上一門視頻課程"面向?qū)ο蟪绦蛟O(shè)計(jì)"(即: C++面向?qū)ο蟾呒?jí)編程(上))的續(xù)集, 將繼續(xù)探討一些未及討論的主題:
- 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)
課程目標(biāo)
- 在先前基礎(chǔ)課程所培養(yǎng)的正規(guī), 大器的編程素養(yǎng)上, 繼續(xù)探討更多技術(shù).
- 泛型編程(Generic Programming)和面向?qū)ο缶幊?Object-Oriented Programming)雖然分屬不同的思維, 但它們正是C++的技術(shù)主線, 所以本課程也討論template(模板).
- 深入探索面向?qū)ο笾^承關(guān)系(inheritance)所形成的對象模型(Object Model), 包括隱藏于底層的this指針, vptr(虛指針), vtbl(虛表), virutual mechanism(虛機(jī)制), 以及虛函數(shù)(virtual functions)造成的polymorphism(多態(tài))效果.
推薦書目
- 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 侯捷
點(diǎn)評(píng): 選擇一本好的C++書籍(1和2兩本書皆可), 然后以及盡量多的完成書籍的習(xí)題,可以幫助完成C++語法的學(xué)習(xí); 熟悉C++的語法后,通過3和4學(xué)習(xí)如何正確高效地使用C++/C++11/C++14; 如果對STL的一些實(shí)現(xiàn)有興趣嗦哆,可參考書目5和6.
conversion operator, 轉(zhuǎn)換函數(shù)
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; // 調(diào)用operator double()將f轉(zhuǎn)為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; // 調(diào)用non-explicit ctor將4轉(zhuǎn)為Fraction(4,1)
// 然后調(diào)用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
點(diǎn)評(píng): 在這個(gè)例子中, conversion function負(fù)責(zé)將Fraction轉(zhuǎn)化為double, 而non-explicit-one-argument ctor負(fù)責(zé)將double轉(zhuǎn)化為Fraction. 然而當(dāng)conversion function和non-explicit-one-argument ctor同時(shí)存在時(shí), 程序出現(xiàn)了二義性問題. 對于編譯器而言, Fraction d2 = f + 4
可以理解成4轉(zhuǎn)為Fraction然后調(diào)用operator+,同時(shí)也可以理解成f轉(zhuǎn)成double, 和4做加法后再轉(zhuǎn)為Fraction, 所以程序是無法編譯通過的.
新的explicit-one-argument ctor:
explicit Fraction(int num, int den = 1)
: m_numerator(num), m_denominator(den) { }
關(guān)于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, 關(guān)于智能指針
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;
};
關(guān)于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, 所謂仿函數(shù)
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
關(guān)于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; }
};
關(guān)于模板特化(摘自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, 模板偏特化
- 個(gè)數(shù)的偏:
// 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*>
{
// ...
};
關(guān)于模板偏特化(摘自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.
關(guān)于C++標(biāo)準(zhǔn)庫
點(diǎn)評(píng): 標(biāo)準(zhǔn)庫是C++重要的組成部分, 包括了容器, 迭代器, 算法和仿函數(shù). 這是一個(gè)極其巨大的話題, 三言兩語是無法解釋的. 完成了C++基礎(chǔ)語法的學(xué)習(xí)后, 應(yīng)好好參考侯捷老師的"那本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
關(guān)于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);
關(guān)于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;
}
關(guān)于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 .