类的定义:
C++中使用关键字 class 来定义类, 其基本形式如下:
1 class 类名 2 { 3 public: 4 //公共的行为或属性 5 6 protected: 7 //受保护的行为或属性 8 9 private: 10 //私有的行为或属性 11 };
示例:
1 class Point 2 { 3 public: 4 void setPoint(int x, int y); 5 void printPoint(); 6 7 private: 8 int xPos; 9 int yPos; 10 };
类的实现:
(1)在类定义时定义成员函数:
1 #include <iostream> 2 3 using namespace std; 4 5 class Point 6 { 7 public: 8 void setPoint(int x, int y) //实现setPoint函数 9 { 10 xPos = x; 11 yPos = y; 12 } 13 14 void printPoint() //实现printPoint函数 15 { 16 cout<< "x = " << xPos << endl; 17 cout<< "y = " << yPos << endl; 18 } 19 20 private: 21 int xPos; 22 int yPos; 23 }; 24 25 int main() 26 { 27 Point M;//用定义好的类创建一个对象 点M 28 M.setPoint(10, 20); //设置 M点 的x,y值 29 M.printPoint(); //输出 M点 的信息 30 31 return 0; 32 }
(2)在类外定义成员函数:
格式:
1 返回类型 类名::成员函数名(参数列表) 2 { 3 //函数体 4 }
示例:
1 #include <iostream> 2 3 using namespace std; 4 5 class Point 6 { 7 public: 8 void setPoint(int x, int y); //在类内对成员函数进行声明 9 void printPoint(); 10 11 private: 12 int xPos; 13 int yPos; 14 }; 15 16 void Point::setPoint(int x, int y) //通过作用域操作符 '::' 实现setPoint函数 17 { 18 xPos = x; 19 yPos = y; 20 } 21 22 void Point::printPoint() //实现printPoint函数 23 { 24 cout<< "x = " << xPos << endl; 25 cout<< "y = " << yPos << endl; 26 } 27 28 int main() 29 { 30 Point M;//用定义好的类创建一个对象 点M 31 M.setPoint(10, 20); //设置 M点 的x,y值 32 M.printPoint(); //输出 M点 的信息 33 34 return 0; 35 }
构造函数:
构造函数是以类名作为函数名,无返回类型的函数,该类对象被创建时,编译系统对象分配内存空间,并自动调用该构造函数,由构造函数完成成员的初始化工作。
种类:
1 class Complex 2 { 3 4 private : 5 double m_real; 6 double m_imag; 7 8 public: 9 10 // 无参数构造函数 11 // 如果创建一个类你没有写任何构造函数,则系统会自动生成默认的无参构造函数,函数为空,什么都不做 12 // 只要你写了一个下面的某一种构造函数,系统就不会再自动生成这样一个默认的构造函数,如果希望有一个这样的无参构造函数,则需要自己显示地写出来 13 Complex(void) 14 { 15 m_real = 0.0; 16 m_imag = 0.0; 17 } 18 19 // 一般构造函数(也称重载构造函数) 20 // 一般构造函数可以有各种参数形式,一个类可以有多个一般构造函数,前提是参数的个数或者类型不同(基于c++的重载函数原理) 21 // 例如:你还可以写一个 Complex( int num)的构造函数出来 22 // 创建对象时根据传入的参数不同调用不同的构造函数 23 Complex(double real, double imag) 24 { 25 m_real = real; 26 m_imag = imag; 27 } 28 29 // 复制构造函数(也称为拷贝构造函数) 30 // 复制构造函数参数为类对象本身的引用,用于根据一个已存在的对象复制出一个新的该类的对象,一般在函数中会将已存在对象的数据成员的值复制一份到新创建的对象中 31 // 若没有显示的写复制构造函数,则系统会默认创建一个复制构造函数,但当类中有指针成员时,由系统默认创建该复制构造函数会存在风险,具体原因请查询 有关 “浅拷贝” 、“深拷贝”的文章论述 32 Complex(const Complex & c) 33 { 34 // 将对象c中的数据成员值复制过来 35 m_real = c.m_real; 36 m_img = c.m_img; 37 } 38 39 // 类型转换构造函数,根据一个指定的类型的对象创建一个本类的对象 40 // 例如:下面将根据一个double类型的对象创建了一个Complex对象 41 Complex::Complex(double r) 42 { 43 m_real = r; 44 m_imag = 0.0; 45 } 46 47 // 等号运算符重载 48 // 注意,这个类似复制构造函数,将=右边的本类对象的值复制给等号左边的对象,它不属于构造函数,等号左右两边的对象必须已经被创建 49 // 若没有显示的写=运算符重载,则系统也会创建一个默认的=运算符重载,只做一些基本的拷贝工作 50 Complex &operator=( const Complex &rhs ) 51 { 52 // 首先检测等号右边的是否就是左边的对象本,若是本对象本身,则直接返回 53 if ( this == &rhs ) 54 { 55 return *this; 56 } 57 58 // 复制等号右边的成员到左边的对象中 59 this->m_real = rhs.m_real; 60 this->m_imag = rhs.m_imag; 61 62 // 把等号左边的对象再次传出 63 // 目的是为了支持连等 eg: a=b=c 系统首先运行 b=c 64 // 然后运行 a= ( b=c的返回值,这里应该是复制c值后的b对象) 65 return *this; 66 } 67 68 }; 69 70 //下面使用上面定义的类对象来说明各个构造函数的用法: 71 72 void main() 73 { 74 // 调用了无参构造函数,数据成员初值被赋为0.0 75 Complex c1,c2; 76 77 // 调用一般构造函数,数据成员初值被赋为指定值 78 Complex c3(1.0,2.5); 79 // 也可以使用下面的形式 80 Complex c3 = Complex(1.0,2.5); 81 82 // 把c3的数据成员的值赋值给c1 83 // 由于c1已经事先被创建,故此处不会调用任何构造函数 84 // 只会调用 = 号运算符重载函数 85 c1 = c3; 86 87 // 调用类型转换构造函数 88 // 系统首先调用类型转换构造函数,将5.2创建为一个本类的临时对象,然后调用等号运算符重载,将该临时对象赋值给c1 89 c2 = 5.2; 90 91 // 调用拷贝构造函数( 有下面两种调用方式) 92 Complex c5(c2); 93 Complex c4 = c2; // 注意和 = 运算符重载区分,这里等号左边的对象不是事先已经创建,故需要调用拷贝构造函数,参数为c2 94 95 96 97 }
浅拷贝与深拷贝:
上面提到,如果没有自定义复制构造函数,则系统会创建默认的复制构造函数,但系统创建的默认复制构造函数只会执行“浅拷贝”,即将被拷贝对象的数据成员的值一一赋值给新创建的对象,若该类的数据成员中有指针成员,则会使得新的对象的指针所指向的地址与被拷贝对象的指针所指向的地址相同,delete该指针时则会导致两次重复delete而出错。下面是示例:
1 #include <iostream.h> 2 #include <string.h> 3 class Person 4 { 5 public : 6 7 // 构造函数 8 Person(char * pN) 9 { 10 cout << "一般构造函数被调用 ! "; 11 m_pName = new char[strlen(pN) + 1]; 12 //在堆中开辟一个内存块存放pN所指的字符串 13 if(m_pName != NULL) 14 { 15 //如果m_pName不是空指针,则把形参指针pN所指的字符串复制给它 16 strcpy(m_pName ,pN); 17 } 18 } 19 20 // 系统创建的默认复制构造函数,只做位模式拷贝 21 Person(Person & p) 22 { 23 //使两个字符串指针指向同一地址位置 24 m_pName = p.m_pName; 25 } 26 27 ~Person( ) 28 { 29 delete m_pName; 30 } 31 32 private : 33 34 char * m_pName; 35 }; 36 37 void main( ) 38 { 39 Person man("lujun"); 40 Person woman(man); 41 42 // 结果导致 man 和 woman 的指针都指向了同一个地址 43 44 // 函数结束析构时 45 // 同一个地址被delete两次 46 } 47 48 49 // 下面自己设计复制构造函数,实现“深拷贝”,即不让指针指向同一地址,而是重新申请一块内存给新的对象的指针数据成员 50 Person(Person & chs); 51 { 52 // 用运算符new为新对象的指针数据成员分配空间 53 m_pName=new char[strlen(p.m_pName)+ 1]; 54 55 if(m_pName) 56 { 57 // 复制内容 58 strcpy(m_pName ,chs.m_pName); 59 } 60 61 // 则新创建的对象的m_pName与原对象chs的m_pName不再指向同一地址了 62 }
析构函数:
析构函数也是一种特殊的成员函数,它的作用是在对象消失时执行一系列的清理任务,例如可以释放内存等。
- 析构函数与类名相同,在函数名前要加~符号
- 析构函数没有参数,也不能重载,一个类中只能定义一个析构函数
- 不能指定返回类型
- 析构函数自动调用,不需要去显示调用
示例:
1 Class Point 2 { 3 Public : Int x,y 4 Public 5 Point();//构造函数 6 ~Point(); 7 } 8 Point::Point()//构造函数是实现 9 { 10 X=0; 11 Y=0; 12 } 13 Point::~point() 14 { 15 Cout<<”(“<<x<<”,”<<y<<”)”<<endl; 16 }
析构函数在下边3种情况时被调用:
- 对象生命周期结束,被销毁时;
- delete指向对象的指针时,或delete指向对象的基类类型指针,而其基类虚构函数是虚函数时;
- 对象i是对象o的成员,o的析构函数被调用时,对象i的析构函数也被调用。
this指针:
一个对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。也就是说,即使你没有写上this指针,编译器在编译的时候也是加上this的,它作为非静态成员函数的隐含形参,对各成员的访问均通过this进行。
使用情况:
- 在类的非静态成员函数中返回类对象本身的时候,直接使用 return *this;
- 另外一种情况是当参数与成员变量名相同时,如this->n = n (不能写成n = n)。
类的this指针有以下特点:
- this只能在成员函数中使用。
- this在成员函数的开始前构造,在成员函数的结束后清除。