面向对象
定义基类和派生类
成员函数和继承
- C++中,基类需要将其两种成员函数区分开
- 基类希望其派生类进行覆盖的函数
- 基类希望派生类直接继承而不改变的函数
- 对于上述前者,基类通常将其定义为虚函数,用virtual关键字进行区分,当调用虚函数时,会使用动态绑定进行调用。
- 任何构造函数之外的非静态函数都可以是虚函数
- 成员函数如果没有被声明为虚函数,则其解析会发生在编译期间而非运行时。
- 尽管派生类含有从基类继承而来的成员,但是派生类并不能直接初始化这些成员,需要使用基类的初始化去初始化其基类部分。
- 在声明派生类时,不需要包含它的派生类列表,但是声明中不需要包含它的派生列表。
- 如果希望某个类作为基类,则它必须已经被定义而非仅仅声明。
- 如果希望一个类防止被继承,则可以通过关键字
final
来实现这个功能 code
class A { public: static void printInfo() { cout << "I am a static member func.\n"; } A(int val) : val_a(val) { } int val_a; }; // 类派生列表,表明自己的基类 class B final:public A { public: // 派生类构造函数 B(int a, int b) : A(a), val_b(b) { } int val_b; }; // 错误,B被声明为final的类了,无法被继承 //class C : public B //{}; void test() { A a(1); B b(1, 2); // 下面4种访问方式相同 a.printInfo(); b.printInfo(); A::printInfo(); B::printInfo(); }
类型转换与继承
- 不存在从基类向派生类的隐式类型转换,因为派生类中可能含有一些成员是基类没有的(自己尝试,用static_cast进行转换也不可以)
code
class A { public: A(int val) : val_a(val) { } int val_a; virtual void f1(int a) const { cout << __FUNCTION__ << ", current num : " << a << endl; }; virtual void f2(){ cout << __FUNCTION__ << endl; }; //void f3(); }; // 类派生列表,表明自己的基类 class B final:public A { public: // 派生类构造函数 B(int a, int b) : A(a), val_b(b) { } int val_b; void f1(int a) const override { cout << __FUNCTION__ << ", current num : " << a << endl; } // void f2(int) override; // 没有这样的虚函数 // void f3() override; // f3不是虚函数 void f2() override { cout << __FUNCTION__ << endl; } }; // 错误,B被声明为final的类了,无法被继承 //class C : public B //{}; void test() { A a1(1); a1.f1(1); a1.f2(); B b1(1, 2); b1.f1(2); b1.f2(); A a2 = b1; a2.f1(3); a2.f2(); // B b2 = a1; // 无法实现隐式转换 // 要想使得从派生类到基类转换成功,需要使用引用或者指针的方式 B* b3 = static_cast<B*>(&a1); if (b3 != NULL) { b3->f1(4); b3->f2(); } }
访问控制和继承
protected
- protected成员对于类的用户来说是不可访问的。
- 和公有成员类似,受保护的成员对于派生类的成员和友元来说是可访问的
- 派生类的成员或者友元只能通过派生类对象来访问基类的protected成员。派生类对于一个基类对象中的受保护成员没有任何访问特权。
code
class A { public : A(int a = 0) :prot_mem(a){} protected: int prot_mem; }; class B : public A { friend void clobber(A&); friend void clobber(B&); }; // 无法访问基类的受保护成员 void clobber(A& a) { //cout << __FUNCTION__ << " : " << a.prot_mem << endl; // 报错 cout << __FUNCTION__ << endl; } // 可以通过派生类去访问 void clobber(B& b) { cout << __FUNCTION__ << " : " << b.prot_mem << endl; } void test() { A a; clobber( a ); clobber( *(static_cast<B*>(&a)) ); }
继承中的类作用域
- 派生类也可以重定义在基类中的名字,此时基类中的名字会被隐藏
- 可以通过作用域运算来访问基类中被隐藏的成员
- 名字查找会优先于类型检查,即使基类和派生类的成员参数列表也不相同,派生类中的成员函数也会隐藏基类中的同名成员函数
- 基类和派生类的虚函数必须有相同的形参列表,如果形参不同,则无法通过基类的指针调用派生类的成员函数。
- 如果调用的函数是非虚函数,则不会进行动态绑定,即实际调用的函数由指针或者引用的静态类型决定。
code
class A { public: A(int a = 0) : mem(a) {} virtual void print() { cout << mem << endl; } void func() { cout << __FUNCTION__ << endl; } virtual void printNum(int a) { cout << __FUNCTION__ << " : " << a << endl; } protected: int mem; }; class B : public A { public: B(int a) : mem(a) { } void print() { cout << mem << endl; } void func() { cout << __FUNCTION__ << endl; } // 不是虚函数,覆盖了基类的成员函数,而且不会动态绑定 void printBase() { cout << A::mem << endl; } // 可以通过作用域运算来访问基类中被隐藏的成员 void printNum() { cout << __FUNCTION__ << endl; } protected: // 会将基类中的mem屏蔽掉 int mem; }; void test() { B b(2); b.print(); b.printBase(); A &a2 = b; a2.print(); // 执行的是b中的print,动态绑定 // a2.printNum( ); // 形参不同,无法通过基类的指针调用派生类的成员函数 b.printNum(); // b.printNum( 1 ); // 基类中的成员函数会被隐藏,这句话会报错 b.A::printNum( 2 ); // 按照这种方式调用基类中的同名成员函数 A &a3 = b; a3.func(); // 由于不是虚函数,因此在调用时不会执行动态绑定,调用的函数由指针或者引用的静态类型决定 b.func(); }
构造函数和拷贝控制
- 如果一个类没有定义拷贝控制操作,则编译器会为其合成一个版本,但是我们可以设置这个拷贝控制函数是delete的状态(即该类对象不可以被拷贝。)
- 如果要删除一个动态分配类型的对象,则我们需要在积累中将析构函数也设置为虚函数,这样才能确保执行正确的析构函数版本。
- 对于派生类的构造函数,基类的构造函数会首先执行,然后再执行派生类的构造函数;对于析构过程正好相反,派生类的析构函数首先执行,再执行基类的析构函数。
code
class A { public: A() {} virtual void func() {} virtual ~A() { cout << __FUNCTION__ << " is being deleted." << endl; } }; class B : public A { public: B() {} void func() {} ~B() { cout << __FUNCTION__ << " is being deleted." << endl; } }; void test() { A *a1 = new A(); delete a1; cout << endl; a1 = new B(); // 动态绑定,会析构B,这里由于是派生类,所以之后也会析构A delete a1; //B *b1 = new A(); // 无法将基类转换为派生类 // delete b1; }
容器与对象
- 如果要在容器中放入继承体系中的对象,通常必须采取间接存储的方式,因为不允许在容器中放入不同的对象,因此如果只是放置类对象的话,无法放入一个继承体系中的各种类。
- 可以在容器中放入
(智能)指针
而非类对象,这些指针的动态类型都是基类类型,从而实现多个继承类对象指针存储在一个容器中。 code
class A { public: A() {} virtual void func() { cout << __FUNCTION__ << endl; } }; class B : public A { public: B() {} void func() { cout << __FUNCTION__ << endl; } }; void test() { cout << " class object with container. " << endl; vector<A> vec; vec.push_back( A() ); vec.push_back( B() ); // 可以将派生类对象转化为基类,但是无法通过对象的方式将基类转化为派生类 vec[0].func(); vec[1].func(); // 由于容器中是对象,因此B被向上转化为A cout << " share_ptr with container. " << endl; vector<shared_ptr<A>> vec2; vec2.push_back( make_shared<A>() ); vec2.push_back(make_shared<B>()); vec2[0]->func(); vec2[1]->func(); //采用指针的方式,这里可以调用 }