类 & 对象
1.类的定义
类中的数据称为成员变量,函数称为成员函数。
类可以被看作是一种模板,可以用来创建具有相同属性和行为的多个对象。
定义一个类,本质上是定义一个数据类型的蓝图,它定义了类的对象包括了什么,以及可以在这个对象上执行哪些操作。
以下实例我们使用关键字 class 定义 Box 数据类型,包含了三个成员变量 length、breadth 和 height:
1 2 3 4 5 6 7 class Box { public : double length; double breadth; double height; };
关键字 public 确定了类成员的访问属性。在类对象作用域内,公共成员在类的外部是可访问的。您也可以指定类的成员为 private 或 protected ,这个我们稍后会进行讲解。
2.定义对象
类提供了对象的蓝图,所以基本上,对象是根据类来创建的。声明类的对象,就像声明基本类型的变量一样。下面的语句声明了类 Box 的两个对象:
对象 Box1 和 Box2 都有它们各自的数据成员。
下面程序定义了一个roommate类,记录了几个人的睡觉时间和起床时间:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class roommate { public : int name,getup,sleep; int average_sleeptime (void ) ; void Init (int _name,int _getup,int _sleep) { name=_name;getup=_getup;sleep=_sleep; } }; int roommate ::average_sleeptime (void ){ if (sleep<24 && sleep>12 )return getup+24 -sleep; else return getup-sleep; }
3.访问数据成员
用法1:对象名.成员名
1 2 3 CRectangle r1,r2; r1. w = 5 ; r2. Init (5 ,4 );
用法2:指针->成员名
1 2 3 4 5 CRectangle r1,r2; CRectangle * p1 = & r1; CRectangle * p2 = & r2; p1->w = 5 ; p2->Init (5 ,4 );
用法3:引用名.成员名
1 2 3 4 CRectangle r2; CRectangle & rr = r2; rr.w = 5 ; rr.Init (5 ,4 );
类的对象的公共数据成员可以使用直接成员访问运算符 . 来访问。
4.类成员的可访问范围
在类的定义中,用下列访问范围关键字来说明类成员可被访问的范围:
private: 私有成员,只能在成员函数内访问
public : 公有成员,可以在任何地方访问
protected: 保护成员
注:三种关键字出现的次数和先后次序都没有限制
如果某个成员前面没有上述关键字,则缺省地被认为是私有成员。
1 2 3 4 5 6 7 8 9 10 class Man { int nAge; char szName[20 ]; public : void SetName (char * szName) { strcpy ( Man::szName,szName); } };
在类的成员函数内部,能够访问:
当前对象的全部属性、函数;
同类其它对象的全部属性、函数。
注:用struct定义类和用"class"的唯一区别,就是未说明是公有还是私有的成员,就是公有
5.成员函数的重载和参数缺省
成员函数也可以重载 成员函数可以带缺省参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Location { private : int x, y; public : void init ( int x=0 , int y = 0 ) ; void valueX ( int val ) { x = val ;} int valueX () { return x; } }; void Location::init ( int X, int Y) { x = X ; y = Y; } int main () { Location A,B; A.init (5 ); A.valueX (5 ); cout << A.valueX (); return 0 ; }
6.构造函数
定义:成员函数的一种:
名字与类名相同 ,可以有参数,不能有返回值(void也不行)
作用是对对象进行初始化,如给成员变量赋初值。
如果定义类时没写构造函数,则编译器生成一个默认的无参数的构造函数。默认构造函数无参数,不做任何操作
如果定义了构造函数,则编译器不生成默认的无参数的构造函数。对象生成时构造函数自动被调用。对象一旦生成,就再也不能在其上执行构造函数
一个类可以有多个构造函数
系统自动生成的无参构造函数:
1 2 3 4 5 6 7 8 9 10 11 class Complex { private : double real, imag; public : void Set ( double r, double i) ; }; Complex c1; Complex * pc = new Complex;
手写的带参构造函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Complex { private : double real, imag; public : Complex ( double r, double i = 0 ); }; Complex::Complex ( double r, double i) { real = r; imag = i; } Complex c1; Complex * pc = new Complex; Complex c1 (2 ) ; Complex c1 (2 ,4 ) , c2 (3 ,5 ) ;Complex * pc = new Complex (3 ,4 );
带参构造函数的重载:
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 29 30 31 32 33 34 #include <iostream> using namespace std;class Complex { public : double real, imag; void Set (double r, double i) ; Complex (double r, double i); Complex (double r); Complex (Complex c1, Complex c2); }; Complex::Complex (double r, double i) { real = r; imag = i; } Complex::Complex (double r) { real = r; imag = 0 ; } Complex::Complex (Complex c1, Complex c2) { real = c1. real + c2. real; imag = c1. imag + c2. imag; } int main () { Complex c1 (3 ) , c2 (1 , 0 ) , c3 (c1, c2) ; cout<<c1. real<<endl; return 0 ; }
注:构造函数最好是public的,private构造函数不能直接用来初始化对象
1 2 3 4 5 6 7 8 class CSample { private : CSample () {} }; int main () {CSample Obj; return 0 ;}
7.构造函数在数组中的使用
构造一个对象,就调用一次构造函数:
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 29 30 31 32 33 34 35 36 #include <iostream> using namespace std;class CSample { int x; public : CSample () { cout << "Constructor 1 Called" << endl; } CSample (int n) { x = n; cout << "Constructor 2 Called" << endl; } }; int main () { CSample array1[2 ]; cout << "step1" << endl; CSample array2[2 ] = {4 , 5 }; cout << "step2" << endl; CSample array3[2 ] = {3 }; cout << "step3" << endl; CSample *array4 =new CSample[2 ]; delete [] array4; return 0 ; }
以上程序输出:
1 2 3 4 5 6 7 8 9 10 11 Constructor 1 Called Constructor 1 Called step1 Constructor 2 Called Constructor 2 Called step2 Constructor 2 Called Constructor 1 Called step3 Constructor 1 Called Constructor 1 Called
单对象多参数的构造:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Test { public : Test (int n) {} Test (int n, int m) {} Test () {} }; Test array1[3 ] = { 1 , Test (1 ,2 ) }; Test * pArray[3 ] = { new Test (4 ), new Test (1 ,2 ) };
拓展:使用初始化列表来初始化字段
使用初始化列表来初始化字段:
1 2 3 4 Line::Line ( double len): length (len) { cout << "Object is being created, length = " << len << endl; }
上面的语法等同于如下语法:
1 2 3 4 5 Line::Line ( double len) { length = len; cout << "Object is being created, length = " << len << endl; }
假设有一个类 C,具有多个字段 X、Y、Z 等需要进行初始化,同理地,您可以使用上面的语法,只需要在不同的字段使用逗号进行分隔,如下所示:
1 2 3 4 C::C ( double a, double b, double c): X (a), Y (b), Z (c) { .... }
8.复制构造函数(copy constructor)
只有一个参数,即对同类对象的引用。
形如 X::X( X& )或X::X(const X &), 二者选一 后者能以常量对象作为参数
如果没有定义复制构造函数,那么编译器生成默认复制构造函数。默认的复制构造函数完成复制功能
注意:
不允许有形如 X::X( X ) or X ( X ) 的构造函数 。
1 2 3 class CSample { CSample ( CSample c ) {} };
9.复制构造函数起作用的三种情况
1)当用一个对象去初始化同类的另一个对象时。
1 2 Complex c2 (c1) ;Complex c2 = c1;
2)如果某函数有一个参数是类 A 的对象,那么该函数被调用时,类A的复制构造函数将被调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class A { public : A () { }; A ( A & a) { cout << "Copy constructor called" <<endl; } }; void Func (A a1) { }int main () { A a2; Func (a2); return 0 ; }
1 2 程序输出结果为: Copy constructor called
3)如果函数的返回值是类A的对象时,则函数返回时,A的复制构造函数被调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> using namespace std;class A { public : int v; A (int n) { v = n; }; A (const A &a) { v = a.v; cout << "Copy constructor called" << endl; } }; A Func () { A b (4 ) ; return b; } int main () { cout << Func ().v << endl; return 0 ; }
这段程序理论上应该输出:
1 2 Copy constructor called 4
但实际上只输出了:
这是因为:
C++ 函数返回值为对象时调用复制构造函数的问题
知识点:
C++中调用复制构造函数的三种情况:
1.通过一个对象构造另一个对象
2.调用参数为对象的函数
3.调用返回值为对象的函数
– 上述知识点在各种书籍、博客都无不同,属于C++的标准
– 但是实际测试的时候,当调用返回值为对象的函数时,并未按预想地调用复制构造函数。
– 在查阅了很多博客资料后,原因如下:
当调用返回值为对象的函数时,系统消耗调用复制构造函数、调用构造函数,调用析构函数的代价,为了减少消耗,编译器使用了一项名为返回值优化的技术,使得调用函数时不需要调用复制构造函数
具体过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Person { string name; int age; Person (string name,int age){ this ->name = age; this ->age = age; } Person (const Person& p){ cout<<"调用复制构造函数" ; } }; Person create (string name,int age) { return new Person (name,age); } int main () { Person p = create ("张三" ,20 ); }
优化前:调用create函数时,先根据传入的参数生成一个临时对象 t1(栈中),然后拷贝生成临时对象 t2(栈外),函数执行完毕,返回 t2的地址,t1被回收;p根据返回的 t2 地址构造对象
优化后:调用create函数时,编译器偷偷地增加了一个参数,传入了p的地址,直接在函数内部构造了p对象
10.转换构造函数(Intconstructor)
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 #include <iostream> using namespace std;class Complex { public : double real, imag; explicit Complex (int i) { cout << "IntConstructor called" << endl; real = i; imag = 0 ; } Complex (double r, double i) { real = r; imag = i; } }; int main () { Complex c1 (7 , 8 ) ; Complex c2 = Complex (12 ); c1 = 9 ; c1 = Complex (9 ); cout<< c1. real << "," << c1. imag << endl; return 0 ; }
隐式类型转换构造函数:
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 #include <iostream> using namespace std;class Complex { public : double real, imag; Complex (int i) { cout << "IntConstructor called" << endl; real = i; imag = 0 ; } Complex (double r, double i) { real = r; imag = i; } }; int main () { Complex c1 (7 , 8 ) ; Complex c2 = 12 ; c1 = 9 ; cout << c1. real << "," << c1. imag << endl; return 0 ; }
11.析构函数(destructors)
名字与类名相同,在前面加 ~ , 没有参数和返回值,一个类最多只能有一个析构函数。
析构函数对象消亡时即自动被调用。可以定义析构函数来在对象消亡前做善后工作,比如释放分配的空间等。
如果定义类时没写析构函数,则编译器生成缺省析构函数。缺省析构函数什么也不做。
如果定义了析构函数,则编译器不生成缺省析构函数。
析构函数实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class String { private : char *p; public : String () { p = new char [10 ]; } ~String (); }; String ::~String () { delete [] p; }
析构函数被调用的几种情况:
1.对象数组生命期结束时,对象数组的每个元素的析构函数都会被调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <iostream> using namespace std;class Ctest { public : ~Ctest () { cout << "destructor called" << endl; } }; int main () { Ctest array[2 ]; cout << "End Main" << endl; return 0 ; }
2.delete 运算导致析构函数调用。
1 2 3 4 5 6 7 8 int main () { Ctest *pTest; pTest = new Ctest; delete pTest; pTest = new Ctest[3 ]; delete [] pTest; }
若new一个对象数组,那么用delete释放时应该写 []。否则只delete一个对象(调用一次析构函数)
3.析构函数在对象作为函数返回值返回后被调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> using namespace std;class CMyclass { public : ~CMyclass () { cout << "destructor" << endl; } }; CMyclass obj; CMyclass fun (CMyclass sobj) { return sobj; } int main () { obj = fun (obj); return 0 ; }
输出:
1 2 3 destructor destructor destructor