class中成员函数定义时的"::"符号叫做scope resolution operator,它前面的类名通常叫做type qualifier。定义成员函数时,可以使用该类的data member和function member而不必使用"."
"::"用于类,"."用于对象;“==”符号不能用于object或structure;“=”却可以对object/structure进行互相赋值
private member不能被该类成员函数的定义部分之外的程序访问。关键词public和private可以在类定义中多次出现,如果在类定义的开头没有添加public和private,则这些开始部分的member会被定义为private类型
允许获得private成员变量的函数叫做accessor function,允许更改private成员变量的函数叫mutator function
constructor是声明该类的某个object时被自动调用的成员函数。constructor必须与class同名,不能有任何返回类型(也不能有void),并且应该属于public。constructor不能被以与其他成员函数相同的方式被调用(以object.function()的方式)。constructor可以被overload
class bankAccount { public: bankAccount(int dollar, double rate); bankAccount(); ... }; bankAccount account1(100, 1.0); bankAccount account2(); // 错误!!但是可能不会产生错误信息,编译器可能会认为这是一个函数声明
可以通过initialization section进行constructor定义,它由":"和由","分开的成员变量列表组成,如
bankAccount::bankAccount(): dollar(0), rate(1.0) bankAccount::bankAccount() { { // could be empty == dollar = 0; } rate = 1.0; }调用constructor可以有两种方式,它既可以在object被声明时自动调用,也可以再之后再次调用。调用constructor会生成一个匿名object,而它可以被赋给一个命名了的object。constructor就像一个返回该类object的函数
bankAccount account1(100, 1.0); account1 = bankAccount(100, 1.0); account1 = bankAccount(); // 注意!此时一定要加()
C++不会总是产生默认constructor,如果给一个class定义了一个constructor,C++编译器将不会再产生其他constructor。所以应该总是定义一个默认constructor,因为很可能会不用任何constructor参数而声明一个object。默认constructor的body可以为空。当创建一个class的数组时,默认的constructor会被调用,所以包含一个默认constructor是十分重要的。
copy constructor是只有一个参数的constructor,并且这个参数的类型与class要相同。这个参数必须是call by reference的,并且通常前面有const。当被初始化的object要成为一个完全独立的,它的参数的复制品时,需要定义copy constructor。比如当一个class的private部分有new的dynamic variable时,如果使用普通的constructor,则新定义的object会和被复制的object指向同一个位置;当其中的任意一个的destructor被调用时,dynamic variable就会被释放,然而此时另一个object还指向这个地方。所以当class的定义包含指针和使用new动态分配的内存时,需要添加copy constructor。copy constructor通常会被在以下三种情况下调用:
1)当一个object被声明并且被同类型的另一个object初始化时
2)当一个函数的返回一个class时
3)当一个class类型的参数被plug-in到一个call by value的参数位置时
一个类只能有一个destructor,它不能被重载,在object离开有效scope的时候会被调用,可以用来释放dynamic variable的空间等
copy constructor,赋值运算符"="和destructor被称为Big Three,因为如果要定义其中的任意一个,就必须定义其他两个,否则compiler会自动生成,但可能不会按照所期望方式工作
友元函数(friend function)并不是class的member function,它只是一个普通的函数,但是它能够访问一个class的private member。
class Date { public: Date(); friend bool equal(Date d1, Date d2); friend bool operator ==(const Date &d1, const Date &d2); // overloading operator "==" ... private: int month; int day; } bool equal(Date d1, Date d2) { // 注意这里不需要加"Date::" return d1.month == d2.month && d1.day == d2.day; } bool operator ==(const Date &d1, const Date &d2) { return d1.month == d2.month && d1.day == d2.day; }
将一个函数声明为友元函数唯一的原因就是这样能让函数的声明变得更简单和高效,例如当函数进行的操作可能涉及多个object时
对变量类型是class的函数来说,使用call by reference可以提高程序的效率。如果成员函数不改变所调用object的值(比如现在有一个object叫test,如果test的某个成员函数foo()不应该改变test的值,则这个foo()应该在最后加上const),则该成员函数可以声明为const,这里const要在函数声明之后,分号之前,并且必须在声明和定义时都使用const
const关键词的使用是all-or-nothing的,如果对某个类型的一个参数使用const,则必须对其他所有不会被函数调用改变值的该类型的变量使用const;如果变量类型是一个class,则还必须要对每个不改变所调用object的成员函数使用const。这样的原因是因为一个函数可能会调用其他函数。下例中,如果不将get_value声明为const,则guarantee在大多数编译器上会产生错误信息。尽管get_value并不改变所调用object的值,但因为没有const,编译器会默认它会改变所调用的object的值。所以,如果对Money类型使用了const,那么应该对所有不改变调用object值的Money的所有成员函数使用const(注意这里的gurantee函数并不是成员函数,也不是友类函数)
void guarantee(const Money& price) { cout << "Double your money if not satisfied:" << (2 * price.get_value()) << endl; }
重载运算符的一些规则:
1. 对运算符进行重载时,至少要有一个参数是class类型或枚举类型
2. 一个被重载的运算符可以是某个class的friend,可以是某个class的member function,也可以都不是,仅仅是一个普通的函数
3. 只能对已有的运算符进行重载,不能创建新的运算符;并且不能改变运算符进行操作的参数个数
4. 运算符的运算优先级不能更改
5. 以下运算符不能重载: . :: .* ?:
6. 赋值运算符“=”的重载需要用不同的方式(必须称为class的成员,而不能声明为友元);某些运算符,如"[]"和"->"等的重载方式也是不同的
7. "++"、"--"作为前缀和后缀的重载方式是不同的(参考:http://www.tutorialspoint.com/cplusplus/increment_decrement_operators_overloading.htm)
8. ">>"和"<<"最好被overload为friend,这样可以保证以类似cout << some_class的形式输出(并且注意它的返回类型是ostream&)。理论上讲,"<<"可以被overload为member function,但是无法以 cout << some_class的形式输出,因为"<<"被定义为member function时,第一个参数只能是当前的object(隐藏的*this)
class Money { public: Money(int dollar, int cent); Money(int value); void show(ostream& outs) const; double getValue(); friend Money operator +(const Money& m1, const Money& m2); friend bool operator ==(const Money& m1, const Money& m2); double operator-(const Money& m2); // overload "-" as member function friend ostream& operator <<(ostream& outs, const Money& m); // the return type must be a reference of stream private: double amount; } void Money::show(ostream &outs) const { outs << "the amount is: " << amount << endl; } ostream& operator <<(ostream& outs, const Money& m) { outs << "the value is: " << m.getValue() << endl; return outs; } Money base_amount(100, 60), full_amount; full_amount = base_amount + 25; // valid double value = full_amount - base_amount; value = full_amount.operator-(base_amount); // valid full_amount.show(cout); cout << full_amount;在上面的程序中,其实并没有重载操作对象是Money和int的"+",之所以能够成功执行,是因为定义了参数为一个int型变量的constructor,所以在调用"+"时自动用25生成了一个Money的object。如果使用25.00,因为没有对应的double型的constructor,就会报错
当一个class的变量中有指针时,普通的赋值运算符"="会令两个object的指针指向同一个位置,而这不是我们想要的,所以此时必须重载"="。当重载"="时,与重载其他运算符不同的是,它必须成为class的member而不能是friend。对于有dynamic array的class,需要先将原来的dynamic array释放再重新申请空间。但是这样有一个问题,就是当object1 = object1,即自己给自己赋值时原来的dynamic array会被销毁。一个解决的方法是检查"="左边的dynamic array是否有足够的空间,如果需要额外的空间再delete它的dynamic array;另一个方式是检查参数地址和this是否相等(重载"="的时候一定要检查自己给自己赋值的情况)
void Student::operator =(const Student& var) { ... }
继承和多态
class Person { public: Person() {} Person(string s) : name(s) {} setName(string s); void showInfo(void); private: string name; }; class Student : public Person { // Student is derived from Person public: Student(): Person() {} Student(string n, int num): Person(n), id(num) {} // use constructor from base class void setId(int num); showInfo(void); // only list the declaration of an inherited member function when you want to redefine private: int id; };
derived class并不会自动继承base class的constructor,但是可以在derived class的constructor的定义中调用base class的constructor,这个constructor会初始化从base class继承来的数据。应该总是在derived class的constructor中的初始化部分包含base class的constructor,否则没有参数的default constructor会被自动调用
destructor也不会被自动继承,但当derived class的destructor被调用时,它会自动调用base class的destructor,所以没有必要在derived class的destructor中显式调用base class的destructor。如果class C继承自class B,class B继承class A,则class C的object离开作用域时,class C的destructor会先被调用,然后是class B的最后是class A的
copy constructor不会被自动继承,如果在derived class中没有定义copy constructor系统会自动产生一个copy constructor,但只会复制member variable的内容,对在member variable中有指针或dynamic data的class不能正常工作,所以如果class member有pointer,dynamic array或其他dynamic data,则无论这个类是不是derived class,都需要定义copy constructor
赋值号"="也不会被继承,如果base class中重载了"=",derived class只会有系统默认的"="而没有被重载过的"="
Student& Student::operator =(const Student& var) { // called copy assignment, "Student&" means return a reference to the object Person::operator =(var); ... return *this; // to allow operator chaining like a = b = c }
因为Student是Person的派生类,所以所有Person object可以使用的地方,Student object也可以使用。但是不能将一个Person object赋给Student object
从base class继承来的private member不能被derived class访问;base class中的private variable只能在base class的member function中被直接访问;而对于base class中的private function则是完全无法访问,它就像没有被继承一样。如果要在derived class中访问base class的private member,只能用base class的public member function
protected的class member,对除derived class之外的所有函数或class的效果与private是相同的;但对于derived class,base class中的projected成员可以被直接访问。在derived class中,base class中的protected成员依然是protected的
只有当derived class想重定义base class中的某个member function时才需要重新声明,否则不要在derived class中重新声明base class的member function。对于在derived class中被重定义的函数来说,它在base class中的定义并没有在derived class中完全消失。如果想要使用base class中的原函数,需要显式指明,如:
Student s1("Tom", 123); s1.Person::showInfo();
多重继承时,derived class对base class的constructor的调用顺序是由class声明继承时的顺序决定的,而不取决于derived class的constructor对base class的constructor的调用顺序
class A { public: A() { cout << "A"; } }; class B { public: B() { cout << "B"; } }; class C : public A, public B { public: C(): B(), A(){} }; C c; // result is "AB"
多态指通过一种名为late binding的特殊机制令一个函数名具有多重含义
virtual function指在某种程度上可以在它被定义之前就可以使用的函数。当一个函数是virtual的时候,相当于告诉compiler“我并不知道这个函数是怎么实现的,等到它在程序中被使用时再通过object确定它的具体实现”。这种等到运行时再确定具体实现的方法叫late biding或dynamic binding
使用virtual function时有以下一些细节:
1)如果一个函数在base class中是virtual的,那么它在derived class中也会自动变成virtual的,尽管在derived class中可以省略关键词"virtual",最好加上
2)"virtual"关键词只出现在函数声明中,不能出现在函数的定义中
之所以不将所有的member function都声明成virtual的是因为这样效率比较低。对于virtual function,compiler和run-time environment需要做很多工作,如果将不需要声明成virtual的函数也声明成了virtual,程序的效率会降低
对于普通的函数,在derived class中改变它的定义这种行为叫做redefine;而对virtual function来说,这种行为叫做override
可以将一个derived class的object赋给一个base class的object,但是不能反过来。同时,虽然这样的赋值是合法的,但是derived class的object中的某些内容会丢失,这叫做slicing problem。C++提供了能让Dog被当作Pet并且不会发生slicing problem的方法,就是使用指针和dynamic object
class Pet { public: virtual void print(int x = 1) { cout << "value is " << x << endl; } string name; }; class Dog: public Pet { public: virtual void print(int x = 2) { cout << "value is " << x << endl; } string breed; }; Pet vpet; Dog vdog; // Dog is derived from Pet vpet = vdog; // slicing problem Pet *ppet; Dog *pdog; pdog = new Dog; ppet = pdog; // no slicing problem ppet->print(); // valid, Dog::print() is called, result is "value is 1" cout << "name: " << ppet->name << "breed: " << ppet->breed << endl; // invalid
上面代码的最后一行之所以是错误的,是因为*ppet的类型是指向Pet的指针,而Pet并没有成员breed。之所以ppet->print()可以正常工作是因为print()被声明为virtual,当compiler看到ppet->print()时,它会检查Pet和Dog的virtual table,然后发现ppet指向了Dog类型的object,所以它调用了Dog::print(),而不是Pet::print()。总结一下,当p_base = p_derived时不会发生slicing problem,但是需要virtual member去访问derived类的dynamic变量的member
在一个class的member function被定义为virtual但还没具体实现时,编译会产生错误。即使没有derived class并且只有一个virtual member,如果这个virtual member没被定义也会产生错误,但是产生的错误信息可能很难理解(可能会有指出undefined reference to default constructor这种错误信息,即使这些constructor已经被定义了)
class Base { public: virtual ~A() { f(); } virtual void f() { cout << "A::f" << endl; } }; class Derived : public Base { public: ~B() {} virtual void f() { cout << "B::f" << endl; } }; Base *p = new B; delete a; // result is "A::f"
最好总是将destructor声明成virtual的。说明destructor如何作用于virtual function机制的最简单的方式就是,所有的destructor都被当作具有相同名字的destructor来处理(尽管他们其实有不同的名字)。例如:
Base *pBase = new Derived; ... delete pBase;当执行delete时,因为base class的destructor被声明为virtual并且被指向的object是Derived类型的,class Derived的destructor会被调用(它会自动调用class Base的destructor)。如果class Base的destructor没有被声明为virtual类型,那么只有class Base的destructor会被调用(这样对于dynamic variable就会产生问题)
并且注意,当一个destructor被声明为virtual时,所有它derive的class的destructor不管有没有显式声明virtual都会被自动声明为virtual,并且和之前一样,所有的destructor都会被当作具有同样的名字(尽管他们的名字其实不同)