OOP
概念
Program = Object1 + Object2 +…… + Objectn
Object: Data + Operation
Message: function call
Class
分类
面向对象(Object-Oriented)
基于对象(Objected-Based):Ada 语言,没有继承
评价标准
类
类包含成员变量和成员函数。
C++ 中还分为头文件和源文件,与 C++ 的编译有关。
头文件中只有函数签名。
头文件 a.h
:
1 2 3 4 5 6 7 8 class TDate { public : void SetDate (int y, int m, int d) ; int IsLeapYear () ; private : int year, month, day; };
源文件 a.cpp
:
1 2 3 4 5 6 7 8 void TDate::SetDate (int y, int m, int d) { year = y; month = m; day = d; }int TDate::IsLeapYear () { return (year % 4 == 0 && year % 100 != 0 ) || (year % 400 == 0 ); }
也可以像 java 一样,函数体和声明写在一起。这样的写法在建议编译器将函数调用编译成内联函数。
1 2 3 4 5 6 7 8 9 10 11 12 class TDate { public : void SetDate (int y, int m, int d) { year = y; month = m; day = d; } int IsLeapYear () { return (year % 4 == 0 && year % 100 != 0 ) || (year % 400 == 0 ); } private : int year, month, day; };
绝大多数情况下希望大家分为头文件和源文件写。
一般把成员变量声明为私有,成员函数声明为公有。
构造函数
描述
与类同名、无返回类型
自动调用,不可直接调用
可重载
默认构造函数无参数,当类中未提供默认构造函数时,编译系统提供。构造函数主要工作需要完成对象内存初始化。
可以声明为公有或私有。私有构造函数用于实现单例。
构造函数调用
自动调用
1 2 3 4 5 6 7 8 9 10 11 12 class A { public : A (); A (int i); A (char *p); }; A a1 = A (1 ); A a1 (a) ; A a1 = 1 ; A a2 = A (); A a2; A a3 = A ("abcd" ); A a[4 ]; A b[5 ] = {A (), A (1 ), A ("abcd" ), 2 , "xyz" };
成员初始化表
构造函数的补充。先于构造函数体执行,按类数据成员声明次序,而不是初始化表次序 。该机制有利于减轻编译器负担。
C++98 标准中,只有 static const
常量才能在类内部初始化。因此该标准中,对于 const
成员和引用成员,只能使用初始化表初始化,不能使用赋值语句初始化。
C++11 允许非静态成员在声明处初始化,类似 java。
1 2 3 4 5 6 7 8 class CString { char * p; int size; public : CString (int x) : size (x), p (new char [size]) {} };
1 2 3 4 5 6 7 8 9 class A { int x; const int y; int & z; public : A () : y (1 ), z (x), x (0 ) { x = 100 ; } };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class A { int m; public : A () { m = 0 ; } A (int m1) { m = m1; } };class B { int x; A a; public : B () { x = 0 ; } B (int x1) { x = x1; } B (int x1, int m1) : a (m1) { x = x1; } };void main () { B b1; B b2 (1 ) ; B b3 (1 , 2 ) ; }
在构造函数中尽量使用成员初始化表取代复制动作。
常量成员、引用成员、对象成员
效率高
数据成员太多时,不采用本条准则,这会降低可维护性。
析构函数
描述
析构函数声明为 ~<className>()
,对象消亡时自动调用。 栈上的对象所在作用域结束时消亡,堆上的对象需要手动清除。
Java 的 GC 机制存在效率障碍,不适用于实时性要求很高的程序(金融交易程序)。
C++ 使用 RAII(Resource Acquisition Is Initialization,资源获取即对象初始化)机制。C++ 获取的资源(如文件、连接和数据库)被封装在对象当中,获取资源即创建对象,对象消亡时资源自动被释放。
调用
析构函数声明为私有,强制自主控制对象存储分配,只能分配在堆上而不能分配在栈上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class A { public : A (); void destroy () { delete this ; } static void free (A *p) { delete p; } private : ~A (); }; A a; int main () { A aa; A* p = new A; p->destroy (); A::free (p); }
拷贝构造函数
描述
拷贝构造函数(Copy Constructor),在创建对象时,用同一类的对象对其初始化,自动调用。用于把对象的资源拷贝给另一对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 A a; A b = a; f (A a) { ... } A b;f (b); A f () { A a; return a; }f ();
声明为:
默认拷贝构造函数,逐个成员初始化(member-wise initialization),对于对象成员,该定义时递归的。
一般深拷贝需要构造函数。
调用
包含成员对象的类:
默认拷贝构造函数:调用成员对象的拷贝构造函数
自定义拷贝构造函数:调用成员对象的默认构造函数
该规则体现了 C++ 的理念:程序员不做的事情,编译器提供默认规则;程序员接管的事情,编译器不再干预。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class A { int x, y; public : A () { x = y = 0 ; } void inc () { x++; y++; } };class B { int z; A a; public : B () { z = 0 ; } B (const B& b) { z = b.z; } void inc () { z++; a.inc (); } };int main () { B b1; b1. inc (); B b2 (b1) ; }
移动构造函数
移动构造函数(move constructor),声明为 A(A&&)
。用于把对象的资源移动给另一个对象,原对象不再使用。
右值引用
1 2 3 int x = 5 ;int &y = x;const int &z = x;
右值引用可以绑定到右值上,能改变值,不能绑定到左值上
非 const
引用只能绑定到左值,不能改变值
const
引用可以绑定到左值和右值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class A { int val; void setVal (int v) { val = v; } };A getA () { return A (); }int main () { int a = 1 ; int &ra = a; const A &cra = getA (); A &&aa = getA (); A &ab = getA (); }
实例
1 2 3 string::string (string &&s): p (s.p) { s.p = nullptr ; }
默认移动构造函数
没有自定义拷贝构造函数和析构函数时,编译器生成默认移动构造函数。
五三原则
拷贝构造函数、移动构造函数、析构函数(和拷贝赋值函数、移动赋值函数)三个(五个)方法中,有一个自定义,则其他函数不会产生自定义版本。
动态内存
创建对象
1 2 3 4 5 6 7 8 9 int * a = new int ; A* p = new A; A* q = new A (1 ); int *p1 = new int [5 ]; int *p2 = new int [5 ](); int *p3 = new int [5 ]{0 ,1 ,2 ,3 ,4 };
对象删除
1 2 delete intPtr; intPtr = NULL ;
动态对象数组
1 2 3 A* p; p = new A[100 ]; delete []p;
元素个数在 p
前的一个 4 字节大小的空间内存储。不能显式初始化,相应的类必须有默认构造函数 。delete 中的[]不能省。
动态二维数组
略。
const 成员
const 成员变量
略。
const 成员函数
1 2 3 4 5 6 7 8 9 10 11 class A { public : void f () ; void show () const ; }int main () { const A a; a.f (); a.show (); }
1 2 3 4 void show (const A* const this ) ;
引用成员变量可以被 const 成员函数修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 class A { int a; int & indirect_int; public : A () : indirect_int (*new int ) { ... } ~A () { delete &indirect_int; } void f () const { indirect_int++; const_cast <A*> this -> a = 1 ; } };
mutable
关键字修饰的变量,可以在 const 成员函数中修改。
静态成员
描述
静态成员变量,由类对象共享,有唯一的拷贝,遵循类访问控制。
1 2 3 4 5 6 class A { int x, y; static int shared; }int A::shared = 0 ;
静态成员函数,只能存取静态成员变量,调用静态成员函数。
单例
原则:谁创建,谁归还。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class singleton { protected : singleton () {} singleton (const singleton&); public : static singleton* instance () { return m_instance == NULL ? m_instance = new singleton : m_instance; } static void destroy () { delete m_instance; m_instance = NULL ; } private : static singleton* m_instance; }; singleton* singleton::m_instance = NULL ;
友元
描述
对象外部不能访问该类的私有成员。需要通过私有方法,会降低对私有成员的访问效率,缺乏灵活性。
友元可以访问 private
和 protected
的成员。
友元函数、友元类、友元类成员函数。
一个全局函数是一个类的友元,如果在这之前没有声明也是可以进行声明友元函数。
1 2 3 4 5 6 7 8 9 10 void func () ;class B ;class C { void f () ; };class A { friend void func () ; friend class B ; friend void C::f () ; };
友元类函数在完整的类声明出现前不能声明友元函数。因为数据的一致性:避免对应类里面没有这个函数(也就是 C 的完整定义必须有)并且成员函数依赖于类
1 2 3 4 5 6 7 class Vector ; class Matrix { friend void multiply (Matrix& m, Vector& v, Vector& r) ; };class Vector { friend void multiply (Matrix& m, Vector& v, Vector& r) ; };
友元不具有传递性。
迪米特法则
迪米特(Demeter)法则:避免将 data member 放在公共接口中,同时努力让接口完满且最小化。
总结
1 2 3 4 5 6 7 8 9 class Empty { Empty (); Empty (const Empty&); ~Empty (); Empty& operator =(const Empty&); Empty *operator &(); const Empty *operator &() const ; };