Chapter9: Inline Function
1、C++中我们不会使用macro定义成员函数,因为macro没有权限访问数据成员;macro不属于任何类;对于类对象不能使用“.”运算符;且没有被封装,一但被定义就可以被任何类使用
相反,C++的inline function可以解决这个问题
2、Inline function
inline function确实是函数,且在调用时被扩展
在class里定义的函数默认为inline function,在class外定义的函数应在前面加上inline关键字,才为inline function
inline function常用于accessors和mutators
3、不能实现函数inline的情况
函数太复杂,具有循环或代码很长
函数的地址被引用
Recursive reference
class X{
int i;
public:
int f()const{ return g()+1; } //forward reference,inlining is ok
int g()const{ return i; }
};
class X{
int i;
public:
int f()const{ return g()+1; } //recursive reference,inlining is impossible
int g()const{ return f()+i; }
};
4、Hidden activities in constructors& destructors
class Member {
int i, j, k;
public:
Member(int x = 0) : i(x), j(x), k(x) { }
~Member() { cout << "~Member" << endl; }
};
class WithMembers {
Member q, r, s;
int i;
public:
WithMembers(int ii) : i(ii) { }
~WithMembers() { cout << "~WithMembers" << endl; }
};
此时构造WithMembers还需调用3次Member的构造函数,因此将WithMembers的constructor定义为inline反而降低了效率
Chapter10: Name Control
1、Static objects inside functions
函数第一次被调用时,static objects才调用constructor进行构造,且只构造一次
当程序调用exit()退出时,才调用destructor析构static objects
如果函数没有被调用,则static objects不会被构造更不会析构
构造顺序:global—>static 析构顺序:static—>global
程序的对象析构顺序总是与构造顺序相反的
2、Namespace:在一个大项目中避免名字冲突的有效方法
可以内嵌,且结尾处没有分号
一个名字空间的定义可以出现在几个头文件中,这不会造成重定义
可以运用别名:namespace Bob=BobsSuperDuperLibrary;
3、Unnamed namespace和static具有相同的作用
namespace {
class Arm { /* ... */ };
class Robot {
Arm arm[4];
...
} xanthan;
int i, j, k;
}
int main() { }
4、当声明某个函数f()为某一namespace中某一class的friend函数时,该函数f()会变为该名字空间的一员
5、Declare a namespace member:以cout为例
Scope resolution:
std::cout<<“num”<<num;
The using directive:
using namespace std;
cout<<“num”<<num;
The using declaration:
using std::cout;
cout<<“num”<<num;
6、当两个名字空间中具有相同名函数时,作用域内declare范围小的会被调用(范围scope resolution>the using directive>the using declaration),且与顺序无关
若同时出现两个declare范围相同,则会造成冲突出错
namespace U {
inline void f() { }
}
namespace V {
inline void f() { }
}
void h() {
using V::f;
using namespace U;
f(); // V::f() will be called.
U::f();
}
void g() {
using namespace V;
using namespace U;
!f(); //wrong!
U::f();
}
7、Using namespace
在头文件不要使用global using directive,在cpp文件使用global using directive
使用explicit qualification or declaration避免名字冲突
8、Static data members:a single piece of storage shared by all objects of a class
在类内进行声明,必须在类外部进行有且仅有一次的定义,可以更改
int x = 100;
class WithStatic {
static int x;
static int y;
};
int WithStatic::x = 1;
int WithStatic::y = x + 1; //x is WithStatic’s x
9、Static member functions:Essentially a global function which is scoped inside a class
static member function可以不通过类对象直接调用,即X::fun()
函数没有this指针,只能访问static data members
函数中不能调用non-static member function
10、Make a singleton:只有一个对象的类
class Egg {
int i;
static Egg singleton; //must be static
Egg(int ii) { i=ii; } //avoid calling constructor
Egg(const Egg &); //avoid copy-constructor
public:
static Egg * instance( ) {return & singleton; } //must be static to access singleton
void print() { cout << i << endl; }
};
Egg Egg::singleton(0); //define the only object
int main() {
Egg::instance( )->print( );
}
11、Static initialization dependency
// is added to the project file first
#include <fstream>
extern std::ofstream out;
class X{
public:
X() { out << "hello" << std::endl;} //wrong, out hasn’t been defined!
} x;
// is added to the project file later
#include <fstream>
std::ofstream out("data.txt");
Solution1:Use an additional class
#ifndef INIT_H
#define INT_H
#include <fstream>
extern std::ofstream * out;
class X{
public:
X() { *out << "hello" << std::endl;}
};
extern X *x;
class Initializer {
static int flag;
public:
Initializer() {
if (flag == 0) {
out = new std::ofstream("data.txt");
x= new X(); flag ++;
}
}
~Initializer() { if (flag != 0) {
delete x; delete out; flag =0;
}
}
};
static Initializer init;
#endif
Solution2:Use static objects inside functions
// first file in the project
#include <fstream>
std::ofstream & get_out();
class X{
public:
X() { get_out() << "hello" << std::endl;}
} x;
// second file in the project
#include <fstream>
std::ofstream & get_out() {
static std::ofstream out("data.txt");
return out;
}
Chapter11: References & the Copy-Constructor
1、References in C++
引用必须与一块内存绑定且不可改变,并在定义时完成绑定,不存在NULL reference
当访问一个引用对象时,事实上是在访问它所指的那块内存,故可以借此改变对象的值
2、References in functions
在函数对引用对象的任何改变都会引起函数外该参数的改变
当一个函数return a reference的时候,必须确保引用指向的object在函数结束后依旧存在,即该object不是局部变量和临时变量
int& h() {
int q;
!return q; //wrong!
}
int & k(){
static int x;
return x; //ok
}
3、Constant references
对于built-in types:意味着不改变形参的值
对于user-defined classes:意味着只调用const member function,且不改变public数据成员
形参以const Typename&形式模拟C中的按值传递参数,且由于没有复制过程,这种形式比按值传递效率高
4、Pointer references
void f( int * & p ) { p++; }
int * ip; f(ip);
使用引用使得语句更加简洁,易阅读
5、Copy-constructor:实现class的pass-by-value
class X{
public:
X(const X&);
};
可能会在“=”,“()”,按值传递函数参数或返回值被调用
int main() {
HowMany h("h");
HowMany h2 = f(h); //call copy-constructor twice
}
6、Default copy-constructor:
当class没有copy-constructor时,编译器会自动合成一个copy-constructor
对于普通类(no class composition and inheritance):default copy-constructor只是进行字节拷贝
对于classes with composition or inheritance:default copy-constructor会自动给每一个embedded object合成copy-constructor
7、Have your own copy-constructor
写自己的copy-constructor时,需要自己构建全部embedded object
default copy-constructor只能进行浅拷贝,自己写的copy-constructor才能实现深拷贝(针对class对象存在指针的情况)
如果不希望pass-by-value,则需将copy-constructor声明为private
8、Pointer to data members and member functions
Pointer to data members:
X d;
X *dp=&d;
int X::*p=&X::a;
dp—>*p=1; //the same as d.*p=1;
Pointer to member functions:
X d;
X *dp = &d;
void (X::*pmem)(int) const;
pmem = &X::h;
(dp—>*pmem)(1); //the same as (d.*pmem)(1);
Chapter12: Operator Overloading
1、Operator overloading只对用户定义的类有用
Operator overloading为当前类的运算符赋予新的意义,增加程序的可读性
2、Syntax
Global functions:
Unary operator —> one argument
Binary operator —>two arguments
Member functions:
Unary operator —> zero argument
Binary operator —> one argument
3、Constraints
不能通过结合两个已存在的运算符创造新的运算符
运算符的优先级和结合顺序不会改变
4、Function arguments and return value
Function arguments通常为const,+=等operator例外
Return value:return a non-const value时(a+b).func()合法;return a const value时(a+b).func()非法
5、iostream类operator(<<,>>)
friend ostream& operator<<(ostream& os,const Typename & T);
friend istream& operator>>(ostream& is,Typename & T);
6、Necessary behavior of operator=
simple class:拷贝所有必要的信息
class with pointers:
为指针所指对象分配新的内存;引用计数(reference counting)
7、Automatic type conversion
constructor conversion: X(int i);
函数存在于目标类中
不能够进行user_type—>built-in type的转换
加入explicit关键字后,不能自动进行转换
operator conversion: operator X()const { return X; }
函数存在于源类中
能够进行user_type—>built-in type的转换
Chapter14: Inheritance & Composition
1、Rules for access control
class B: public D{ //inheritance access control
public: //class access control
int a;
};
继承access control并不影响派生类成员函数对数据成员的访问,只对外部函数有影响
protected概念上是指在被派生类中允许被访问
最终在派生类中的访问权是access path上范围最小的
默认继承access control为private
class B {
public: int pub;
protected: int pro;
private: int pri;
};
B—public>D1—protected>D2 D2可以访问pub,pro
B—protected>D1—protected>D2 D2可以访问pub,pro
B—private>D1—protected>D2 pub、pro、pri D2均不可访问
2、The initialization list
class C : public B {
A a;
public:
C(int ii) : B(ii), a(ii) { }
};
当A或B存在default constructor,不需要在initialization list显式调用,即C(int ii){}
3、Order of constructor & destructors calls
constructor calls:
从继承树的根部开始构造;在每个水平上,基类的构造函数先被调用,然后根据声明顺序调用成员对象的构造函数
destructor calls:
与constructor的调用顺序相反
4、Name hiding
当在一个派生类中重定义一个基类的函数名时,基类中所有相同函数名的函数在派生类中都会丢失
class Base {
public:
int f( ) const {
cout << "Base::f()\n";
return 1;
}
int f(string) const { return 1; }
void g( ) {}
};
class Derived : public Base {
public:
void g() const {}
}; // both f are available, g() is unavailable
5、Special points
constructors、destructors和operator=不能继承
static member functions在继承上和non-static member function是相同的,但static member functions不能被声明为virtual函数
6、Choosing composition vs. inheritance
composition: has-a relationship
inheritance: Is-a relationship
private Inheritance可以用private composition代替
class A{
public:
void print(){}
};
class B{ class C: private A{
A a; public:
public: using A::print;
void print(){ a.print(); } };
};
以上B与C是等价的
7、Upcasting(Instrument—>Wind只发生在public继承和protected继承的派生类中)
由于Wind是一种Instrument,故Wind对象能调用Instrument的所有成员函数;任何以Instrument为argument的函数都可以接受一个Wind对象,当这个argument为指针或引用时,会导致upcasting;upcasted pointer or reference会丢失派生类的信息
enum note { middleC, Csharp, Cflat };
class Instrument {
public:
void play(note) const { }
};
class Wind : public Instrument { };
void tune(Instrument& i) {
// ...
i.play(middleC);
}
int main() {
Wind flute;
tune(flute); //call Instrument’s play()
}
upcasting对于派生类的copy-constructor的定义很有用
class Derived : public Base {
A a;
public:
Derived(const Derived& d): Base(d), a(d.a) {} //copy-constructor
};
Chapter15: Polymorphism & Virtual Functions:
(upcasting + virtual function—Late Binding>Polymorphism)
1、Early binding & Late binding
Early binding:compile binding (function name overloading)
编译器在编译阶段确定每个对象调用的函数的地址 (14.7例子为Early binding)
Late binding:dynamic binding,runtime binding
编译器在运行阶段根据每个对象的实际类型确定对象调用的函数的地址
enum note { middleC, Csharp, Cflat };
class Instrument {
public:
virtual void play(note) const { }
};
class Wind : public Instrument { };
void tune(Instrument& i) {
// ...
i.play(middleC); //Late binding
}
int main() {
Wind flute;
tune(flute); //call Wind’s play()
}
对于类对象,编译器只会使用Early binding;对于类对象的指针或引用,编译器才可能使用Late binding
2、Virtual function
只有成员函数能声明为virtual,函数定义不需要加virtual关键字
基类函数为virtual时,派生类中该函数都为virtual
virtual function是实现Late binding的前提
3、C++中是通过VTABLE和VPTR实现Late binding
对于每个包含virtual function的class,只有一个VTABLE会被构建
类对象并不包含VTABLE,但每个类对象用一个各自的VPTR
VPTR记录了类对象的类型信息,VPTR在constructor中初始化
函数的地址以相同的顺序保存在VTABLE中
4、Abstract base classes and pure virtual function
abstract base class:为派生类提供共同的接口;不能创建其类对象;至少含有一个pure virtual function
pure virtual function:形如virtual void print()=0;
类构建时,VTABLE中函数的顺序已经安排好,但没有任何pure virtual function的地址会被保存
基类pure virtual function在派送类中必须进行定义
派生类可以自主添加另外的virtual function或common function,派生类新定义的virtual function从当前派生类开始为virtual,原基类同名函数不为virtual
5、Object slicing
当将派生类对象按值传递给基类对象时,会得到一个真正的基类对象,此时会发生object silcing,VPTR会改变,故对于类对象,编译器只会使用Early binding
6、Name hiding for virtual functions
重载虚函数不能够改变返回值类型,但可以有不一样的参数
7、Constructor、destructor and virtual function
由于没有VPTR(VPTR在constructor中初始化),故不能够把constructor定义为virtual
应该将destructor定义为virtual,此时delelte an upcasted pointer才能正确析构
在任何constructor和destructor中,调用virtual function时为Early binding,这是由于派生类对象还未被构建或已被析构
8、Downcasting
Circle * pc = new Circle;
Shape *ps = pc;
pc =dynamic_cast<Circle*> ps;
只有当ps原本就指向一个Circle对象时,downcasting能成功
Chapter16: Introduction to Templates
1、Use template to construct general vector
template<class T> //parameterized type
class X{
...
};
X<int>, X<double> ...
2、Multiple parameters & Default parameters
template<class T, int size> //multiple parameters
class Stack{
T stack[size];
...
};
Stack<int, 100> s, Stack<string, 20> ...
template<class T, int size=100> //default parameters
3、Container:a data structure whose length can vary automatically at runtime
容器有自动调节容量大小的函数
4、Introduction of iterators:a smart pointer
iterator提供了遍历container和访问container中元素的接口
所有的iterator有共同的接口:begin(), end(), ++, --, *, —>
各容器的iterator通常作为其nested class在container中声明实现
template<class T, int size=100>
class Stack{
T stack[size];
int top;
public:
class iterator;
friend class iterator;
class iterator{
Stack& s;
int dex;
public:
iterator(Stack& st): s(st), index(0){}
iterator(Stack& st, bool): s(st), index(s.top){}
...
};
...
};
5、The typename keyword
two condition:
Used for a name which depends on a template argument
template<typename T>
class X{
...
};
The type is nested within the template argument
template<class T>
class X{
typename T::iterator itr;
...
};
6、Function templates
一种可以处理多种数据类型的函数;函数第一次调用之前模板类型不会确定
template <typename T>
T array_sum(T * p, int n){
T sum=0;
for (int i=0; i<n; i++)
sum += *p++;
return sum;
}
int main (){
int ia [3]={1, 2, 3};
double da [4]={1, 2, 3, 4};
cout<<array_sum<int>(ia,3);
cout<<array_sum<double>(da,4);
}
7、Automatic type deduction:calling arguments—>template type
type和value都可以进行推断,推断出的type或value需与其他调用的参数一致
class templates不能进行type deduction
template <typename Dest, typename Src>
Dest implicit_cast (Src s)
{
return s;
}
int main(){
int i;
!implicit_cast(i); //no have enough argument
implicit_cast<double>(i);
implicit_cast<char,double>(i);
implicit_cast<char*, int> (i); //int can’t be cast to char*
}
8、The moment of instantiation
对于function template,在调用函数时instantiated
对于class template,每个成员函数都在调用时才instantiated