面向对象的程序设计方法
抽象:将某类客观事物共同特点(属性)归纳出来,形成一个数据结构(可以用多个变量描述事物的属性);将这类事物所能进行的行为也归纳出来,形成一个个函数,这些函数可以用来操作数据结构。
封装:通过某种语法形式,将数据结构和操作该数据结构的函数“捆绑”在一起,形成一个“ 类”,从而使得数据结构和操作该数据结构的算法呈现出显而易见的紧密关系。
从客观事物抽象出类
写一个程序,输入矩形的长和宽,输出面积和周长。
比如对于“矩形”这种东西,要用一个类来表示,该如何做“抽象”呢?
矩形的属性就是长和宽。因此需要两个变量,分别代表长和宽。
一个矩形,可以有哪些行为呢(或可以对矩形进行哪些操作)?
矩形可以有设置长和宽,算面积,和算周长这三种行为(当然也可以有其他行为)。这三种行为,可以各用一个函数来实现,他们都需要用到长和宽这两个变量。
将长、宽变量和设置长,宽,求面积,以及求周长的三个函数“封装”在一起,就能形成一个“矩形类”。
长、宽变量成为该“矩形类”的“成员变量” ,三个函数成为该类的“成员函数” 。 成员变量和成员函数统称为类的成员。
下面就是一个最简单的类
1 class CRectangle 2 { 3 public: 4 int w, h; 5 6 int Area() 7 { 8 return w * h; 9 } 10 11 int Perimeter() 12 { 13 return 2 * ( w + h); 14 } 15 16 void Init( int w_,int h_ ) 17 { 18 w = w_; h = h_; 19 } 20 };
类成员的可访问范围
在类的成员函数内部,能够访问:
(1)当前对象的全部属性、 函数;
(2)同类其它对象的全部属性、 函数。
在类的成员函数以外的地方,只能够访问该类对象的公有成员。
1 class CEmployee 2 { 3 private: 4 char szName[30]; //名字 5 public : 6 int salary; //工资 7 void setName(char * name); 8 void getName(char * name); 9 void averageSalary(CEmployee e1,CEmployee e2); 10 }; 11 12 void CEmployee::setName( char * name) 13 { 14 strcpy( szName, name); //ok 15 } 16 17 void CEmployee::getName( char * name) 18 { 19 strcpy( name,szName); //ok 20 } 21 22 void CEmployee::averageSalary(CEmployee e1, CEmployee e2) 23 { 24 cout << e1.szName; //ok,访问同类其他对象私有成员 25 salary = (e1.salary + e2.salary ) / 2; 26 } 27 28 int main() 29 { 30 CEmployee e; 31 32 strcpy(e.szName,"Tom1234567889"); //编译错,不能访问私有成员 33 e.setName( "Tom"); // ok 34 e.salary = 5000; //ok 35 36 return 0; 37 }
构造函数
成员函数的一种,名字与类名相同,可以有参数,不能有返回值(void也不行),作用是对对象进行初始化,如给成员变量赋初值。
(1)如果定义类时没写构造函数,则编译器生成一个默认的无参数的构造函数,默认构造函数无参数,不做任何操作
(2)如果定义了构造函数,则编译器不生成默认的无参数的构造函数
(3)对象生成时构造函数自动被调用。对象一旦生成,就再也不能在其上执行构造函数
(4)一个类可以有多个构造函数
(5)构造函数最好是public的, private构造函数不能直接用来初始化对象
下面看2个例子:
1 class Complex 2 { 3 private : 4 double real, imag; 5 public: 6 void Set( double r, double i); 7 }; //编译器自动生成默认构造函数 8 9 Complex c1; //默认构造函数被调用 10 Complex * pc = new Complex; //默认构造函数被调用
1 class Complex 2 { 3 private : 4 double real, imag; 5 public: 6 Complex( double r, double i = 0); 7 }; 8 9 Complex::Complex( double r, double i) 10 { 11 real = r; imag = i; 12 } 13 14 Complex c1; // error, 缺少构造函数的参数 15 Complex * pc = new Complex; // error, 没有参数 16 Complex c1(2); // OK 17 Complex c1(2,4), c2(3,5); 18 Complex * pc = new Complex(3,4);
构造函数在数组中的使用
1 class Test 2 { 3 public: 4 Test( int n) { } //(1) 5 Test( int n, int m) { } //(2) 6 Test() { } //(3) 7 }; 8 9 // 三个元素分别用(1),(2),(3)初始化 10 Test array1[3] = { 1, Test(1,2) }; 11 // 三个元素分别用(2),(2),(1)初始化 12 Test array2[3] = { Test(2,3), Test(1,2) , 1}; 13 //两个元素分别用(1),(2) 初始化 14 Test * pArray[3] = { new Test(4), new Test(1,2) };
复制构造函数
只有一个参数,即对同类对象的引用。形如 X::X( X& )或X::X(const X &), 二者选一,后者能以常量对象作为参数
(1)如果没有定义复制构造函数,那么编译器生成默认复制构造函数。默认的复制构造函数完成复制功能。
1 class Complex 2 { 3 private : 4 double real,imag; 5 }; 6 7 Complex c1; //调用缺省无参构造函数 8 Complex c2(c1);//调用缺省的复制构造函数,将 c2 初始化成和c1一样
(2)如果定义的自己的复制构造函数,则默认的复制构造函数不存在。
1 class Complex 2 { 3 public : 4 double real,imag; 5 6 Complex(){ } 7 Complex( const Complex & c ) 8 { 9 real = c.real; 10 imag = c.imag; 11 cout << “Copy Constructor called”; 12 } 13 }; 14 15 Complex c1; 16 Complex c2(c1);//调用自己定义的复制构造函数,输出 Copy Constructor called
(3)当用一个对象去初始化同类的另一个对象时会调用复制构造函数
1 Complex c2 = c1; //初始化语句,非赋值语句 2 3 CMyclass c1,c2; 4 c2 = c1; //赋值语句
对象间赋值并不导致复制构造函数被调用
(4)如果某函数有一个参数是类 A 的对象,那么该函数被调用时,类A的复制构造函数将被调用
(5)如果函数的返回值是类A的对象时,则函数返回时,A的复制构造函数被调用
类型转换构造函数
定义转换构造函数的目的是实现类型的自动转换。
只有一个参数,而且不是复制构造函数的构造函数,一般就可以看作是转换构造函数。当需要的时候,编译系统会自动调用转换构造函数,建立一个无名的临时对象(或临时变量)。
1 class Complex { 2 public: 3 double real, imag; 4 5 //类型转换构造函数 6 Complex( int i) 7 { 8 cout << "IntConstructor called" << endl; 9 real = i; imag = 0; 10 } 11 12 Complex(double r,double i) {real = r; imag = i; } 13 }; 14 15 int main () 16 { 17 Complex c1(7,8); 18 Complex c2 = 12; 19 c1 = 9; // 9被自动转换成一个临时Complex对象 20 cout << c1.real << "," << c1.imag << endl; 21 return 0; 22 }
析构函数
(1)名字与类名相同,在前面加‘ ~’ , 没有参数和返回值,一个类最多只能有一个析构函数。
(2)析构函数对象消亡时即自动被调用。可以定义析构函数来在对象消亡前做善后工作,比如释放分配的空间等。
(3)如果定义类时没写析构函数,则编译器生成缺省析构函数。缺省析构函数什么也不做。
(4)如果定义了析构函数,则编译器不生成缺省析构函数。
(5)析构函数在对象作为函数返回值返回后被调用。
下面看个析构函数的例子:
1 #include <iostream> 2 3 using namespace std; 4 5 class CMyclass 6 { 7 public: 8 CMyclass() {} 9 CMyclass(const CMyclass & sobj) { cout << "copy constructor" << endl; } 10 ~CMyclass() { cout << "destructor" << endl; } 11 }; 12 13 CMyclass obj; 14 15 //参数对象消亡也会导致析构函数被调用 16 CMyclass fun(CMyclass sobj ) 17 { 18 cout << "here_2" << endl; 19 20 return sobj; //函数调用返回时生成临时对象返回 21 } 22 23 int main() 24 { 25 cout << "here_1" << endl; 26 27 //函数调用的返回值(临时对象)被用过后,该临时对象析构函数被调用 28 obj = fun(obj); 29 30 cout << "here_3" << endl; 31 32 return 0; 33 }
运行结果:
here_1:main函数最开始
copy constructor:调用函数fun,传入的参数是一个对象
here_2:进入了函数fun中
copy constructor:返回的是一个对象
destructor:退出函数fun,参数被析构
destructor:函数fun的返回值是个临时对象,被使用后,也被析构
here_3:没什么好说的
destructor:析构全局的obj对象
再来看一个综合的例子:
1 #include <iostream> 2 3 using namespace std; 4 5 class Demo { 6 int id; 7 public: 8 Demo(int i) 9 { 10 id = i; 11 cout << "id=" << id << " constructed" << endl; 12 } 13 ~Demo() 14 { 15 cout << "id=" << id << " destructed" << endl; 16 } 17 }; 18 19 Demo d1(1); 20 21 void Func() 22 { 23 static Demo d2(2); 24 Demo d3(3); 25 cout << "func" << endl; 26 } 27 28 int main () 29 { 30 Demo d4(4); 31 d4 = 6; 32 cout << "main" << endl; 33 { 34 Demo d5(5); 35 } 36 Func(); 37 Func(); 38 cout << "main ends" << endl; 39 return 0; 40 }
运行结果:
就解释一下id=6 constructed和id=6 destructed,这是因为调用了类型转换构造函数,生成了临时的对象
this指针
其作用就是指向成员函数所作用的对象
(1)非静态成员函数中可以直接使用this来代表指向该函数作用的对象的指针。
(2)静态成员函数中不能使用 this 指针!因为静态成员函数并不具体作用与某个对象!因此,静态成员函数的真实的参数的个数,就是程序中写出的参数个数!
1 #include <iostream> 2 3 using namespace std; 4 5 class A 6 { 7 int i; 8 public: 9 //void Hello(A * this ) { cout << "hello" << endl; } 10 void Hello() { cout << "hello" << endl; } 11 }; 12 13 int main() 14 { 15 A * p = NULL; 16 17 //Hello(p); 18 p->Hello(); 19 20 return 0; 21 }
运行结果:
1 #include <iostream> 2 3 using namespace std; 4 5 class A 6 { 7 int i; 8 public: 9 //void Hello(A * this ) { cout << this->i << "hello" << endl; } 10 void Hello() { cout << i << "hello" << endl; } 11 }; 12 13 int main() 14 { 15 A * p = NULL; 16 17 //Hello(p); 18 p->Hello(); 19 20 return 0; 21 }
运行结果:
静态成员变量
静态成员:在定义前面加了static关键字的成员。
(1)普通成员变量每个对象有各自的一份,而静态成员变量一共就一份,为所有对象共享。
(2)普通成员函数必须具体作用于某个对象,而静态成员函数并不具体作用于某个对象。
(3)sizeof 运算符不会计算静态成员变量。
(4)基于(1)和(2)的缘故,静态成员不需要通过对象就能访问。
(5)静态成员变量本质上是全局变量,哪怕一个对象都不存在,类的静态成员变量也存在。静态成员函数本质上是全局函数。
(6)设置静态成员这种机制的目的是将和某些类紧密相关的全局变量和函数写到类里面,看上去像一个整体,易于维护和理解。
(7)必须在定义类的文件中对静态成员变量进行一次说明或初始化。否则编译能通过,链接不能通过。
1 #include <iostream> 2 3 using namespace std; 4 5 class CRectangle 6 { 7 private: 8 int w, h; 9 static int nTotalArea; 10 static int nTotalNumber; 11 public: 12 CRectangle(int w_,int h_); 13 ~CRectangle(); 14 static void PrintTotal(); 15 }; 16 17 CRectangle::CRectangle(int w_,int h_) 18 { 19 w = w_; 20 h = h_; 21 nTotalNumber ++; 22 nTotalArea += w * h; 23 } 24 CRectangle::~CRectangle() 25 { 26 nTotalNumber --; 27 nTotalArea -= w * h; 28 } 29 void CRectangle::PrintTotal() 30 { 31 cout << nTotalNumber << "," << nTotalArea << endl; 32 } 33 34 35 int main() 36 { 37 CRectangle r1(3,3), r2(2,2); 38 //cout << CRectangle::nTotalNumber; // Wrong , 私有 39 CRectangle::PrintTotal(); 40 r1.PrintTotal(); 41 return 0; 42 }
编译结果:
需要改为如下:
1 int CRectangle::nTotalNumber = 0; 2 int CRectangle::nTotalArea = 0;
即使这样编译通过了,上面的例子还有有缺陷的。
在使用CRectangle类时,有时会调用复制构造函数生成临时的隐藏的CRectangle对象(调用一个以CRectangle类对象作为参数的函数时或者调用一个以CRectangle类对象作为返回值的函数时);临时对象在消亡时会调用析构函数,减少nTotalNumber 和nTotalArea的值,可是这些临时对象在生成时却没有增加nTotalNumber 和 nTotalArea的值
这时我们需要再自己写一个复制构造函数
1 CRectangle::CRectangle(CRectangle & r ) 2 { 3 w = r.w; h = r.h; 4 nTotalNumber ++; 5 nTotalArea += w * h; 6 }
(8)在静态成员函数中,不能访问非静态成员变量,也不能调用非静态成员函数。