文章目录
一、类及其实例化
1、定义类
类要先声明后使用;不能声明两个名字相同的类,类是具有唯一标识符的实体;在类中声明的任何成员不能使用extern、auto、register关键字进行修饰;类中声明的变量属于该类,在某些情况下,变量也可以被该类的不同实例所共享;类中有数据成员和成员函数,不有在类声明中对数据成员使用表达式进行初始化。
⑴ 声明类
声明类以class开始,其后跟类名,类所声明的内容用花括号括起来,右括号后的分号作为类声明结束的标志。
类成员具有访问权限,通过它前面的关键字来定义,关键字private后的成员叫私有成员、public后的成员叫公有成员、protected后的成员叫受保护的成员,访问权限用于控制对象的成员在程序中的可访问性。如果没有使用关键字,则所有成员默认声明为private权限。
class Point{//类名Point
private://声明为私有访问权限
int x,y;//私有的数据成员
public://声明为公有的访问权限
void setXY(int a,int b);
void move(int a,int b);
void display();
int getX();
int getY();
};//声明以分号为结尾
⑵ 定义成员函数
#include <iostream>
using namespace std;
class Point{//类名Point
private://声明为私有访问权限
int x,y;//私有的数据成员
public://声明为公有的访问权限
Point(){};//没有参数的构造函数
Point(int a,int b){//两个有参的构造函数
x = a;
y = b;
}
void setXY(int a,int b);//函数声明
void display();
inline int getX();//声明为内联函数
int getY(){//在类体中定义函数,如果不包含循环或switch语句则默认为内联函数
return y;
}
};//声明以分号为结尾
//在类体外定义函数
void Point :: setXY(int a,int b){
x = a;
y = b;
}
void Point :: display(){
cout << x << "," << y;
}
inline int Point :: getX(){//类体外定义内联函数
return x;
}
一般在类体中给出简单成员函数的定义,在类中定义的函数如果不包含循环或switch语句会被系统默认当作内联函数来处理,函数体内容较多的函数则在类外定义,系统并不会把它们默认为内联函数,如果想把它们指定为内联函数,则应该使用inline进行显示声明。如果在类体外定义内联函数,需要把函数声明和函数定义放在同个源文件中才能编译成功。
在类体中直接定义函数时,不需要在函数名前加上类名,只有在类体外定义时才需要。其中,“::”是作用域运算符,它用于表明其后的成员函数是属于这个特定的类 。
⑶ 数据成员的赋值
不能在类体内给数据成员赋值,数据成员的具体值是用来描述对象属性的,只有产生了一个具体的对象,这些数据值才有意义。如果产生对象时就使对象的数据成员具有指定值,则称为对象的初始化。注意,初始化和赋初值是两个不同的概念,初始化是使用与Point同名的构造函数来实现的,赋初值是在有了对象A之后,对象A调用自己的数据成员或成员函数实现赋值操作。
int main(){
//构造函数初始化
Point a(10,20);
//对象赋值
Point b;
b.setXY(10,20);
return 0;
}
2、使用类的对象
只有产生类的对象,才能使用这些数据和成员函数。类不仅可以声明为对象,还可以声明为对象的引用和对象的指针。
//定义print函数的重载,分类使用类指针和类对象作为参数
void print(Point *a){//类指针作为参数重载print函数
a -> display();
}
void print(Point &b){//类引用作为参数重载print函数
b.display();
}
int main(){
//构造函数初始化
Point a(10,20);
//对象赋值
Point b;
b.setXY(10,20);
Point *p1 = &a;//声明对象a的对象指针
Point &p2 = b;//声明对象b的对象引用
print(p1);//10,20
print(p2);//10,20
return 0;
}
总结:
① 类的成员函数可以直接使用类的私有成员;
② 类外的函数不能直接使用类的私有成员;
③ 类外的函数只能通过类的对象使用该类的公有成员函数;
④ 对象的成员函数代码都是一样的,对象的区别只是属性的取值;
⑤ 在程序运行时,通过为对象分配内存来创建对象,为了节省内存,在创建对象时,只分配用于保存数据的内存,代码为每个对象共享,类中定义的代码被放在计算机内的一个公共区中供该类的所有对象共享;
⑥ 对象和引用在访问对象的成员时,使用运算符“.”,而指针则使用“->”运算符。
3、数据封装
面向对象程序设计是通过为数据和代码建立分块的内存区域,以便提供对程序进行模块化的程序设计方法,这些模块可以被用作样板,在需要时再建立副本。而对象是计算机内存中的一块区域,通过将内存分块,每个对象在功能上保持相对独立。这些内存块中不但存储数据,也存储代码,只有对象中的代码才可以访问存储于这个对象中的数据,这将保护它自己不受未知外部事件的影响,从而使自己的数据和功能不会遭到破坏。
在面向对象的程序中,只有向对象发送消息才能引用对象的行为,所以面向对象是消息处理机制,对象之间只能通过成员函数相互调用来实现相互通信。这样,对象之间相互作用的方式是受控制的,一个对象外部的代码就没有机会通过直接修改对象的内存区域妨碍对象发挥其功能。
面向对象就是将世界看成是一组彼此相关并能相互通信的实体即对象组成的,程序中的对象映射现实世界中的对象。
C++对其对象的数据成员和成员函数的访问是通过访问控制权限来限制的,一般情况下将数据成员说明为私有的,以便隐藏数据,将部分成员函数说明为公有的,用于提供外界和这个类的对象相互作用的接口,从而使得其他函数也可以访问和处理该类的对象。
二、构造函数
1、默认构造函数
没有定义构造函数,却可以使用类直接产生对象,原因是当没有为一个类定义任何构造函数的情况下,C++编译器总要自动建立一个不带参数的构造函数。默认的构造函数函数名与类名相同,函数体是空的,没有参数,也没有返回值,如果它有返回值,编译器就必须知道如何处理返回值,这样会大大增加编译器的工作,降低了效率。如果我们在程序中定义了自己的构造函数,系统就不再提供默认的构造函数。如果我们需要使用到无参的构造函数,则需要在显式的声明并定义一个无参的构造函数。
2、定义构造函数
#include <iostream>
using namespace std;
class Point{
private:
int x,y;
public:
Point();//声明一个无参的构造函数
Point(int,int);//声明一个有两个参数的构造函数
};
Point :: Point(){//定义无参的构造函数
cout << "默认初始化对象" << endl;
}
Point :: Point(int a,int b):x(a),y(b){//定义两个参数的构造函数,x(a)相当于x = a,它跟下面的声明方式是等价的
cout << "初始化对象,属性x:" << a << ",属性y:" << b <<endl;
}
/*Point :: Point(int a,int b){
x = a;
y = b;
}*/
int main(){
Point a;//使用无参构造函数产生对象
Point b(10,20);//使用有参构造函数产生对象
Point c[2];//使用无参构造函数产生对象数组
Point d[2]={Point(15,25),Point(20,30)};//使用有参构造函数产生对象数组
/**
* 初始化对象,属性x:10,属性y:20
* 默认初始化对象
* 默认初始化对象
* 初始化对象,属性x:15,属性y:25
* 初始化对象,属性x:15,属性y:25
*/
}
3、构造函数和运算符new
运算符new用于建立生存期可控的对象,new返回这个对象的指针。当使用new建立一个动态的对象时,new将首先分配保证类的一个对象所需要的内存,然后自动调用构造函数来初始化这块内存,再返回这个动态对象的地址。
使用new建立的动态对象只能使用delete删除,以便释放所占空间。
Point *p1 = new Point();
Point *p2 = new Point(5,8);
delete p1;
delete p2;
/**
* 默认初始化对象
* 初始化对象,属性x:5,属性y:8
*/
4、构造函数的默认参数
class Point1{
private:
int x,y;
public:
Point1(int=0,int=0);//声明一个默认参数的构造函数,使用默认参数的构造函数就不能再声明无参的构造函数
};
Point1 :: Point1(int a,int b):x(a),y(b){//定义两个参数的构造函数
cout << "初始化对象,属性x:" << a << ",属性y:" << b <<endl;
}
int main(){
Point1 a;
Point1 b(10,25);
}
5、复制构造函数
引用在类中可以用在复制构造函数中,编译器建立一个默认复制构造函数,然后采用拷贝式的方法使用已有的对象来建立新对象。复制构造函数必须使用对象的引用为形式参数,为了安全起见,建议使用const限定符。
class Point2{
private:
int x,y;
public:
Point2();
Point2(const Point2&);//声明带const限定符的复制构造函数
};
Point2 :: Point2():x(12),y(20){
cout << "初始化对象,属性x:" << x << ",属性y:" << y <<endl;
}
Point2 :: Point2(const Point2 &p){
x = p.x;//一个类中定义的成员函数可以访问该类任何对象的私有成员
y = p.y;
cout << "初始化对象,属性x:" << x << ",属性y:" << y <<endl;
};
int main(){
Point2 a;
Point2 b(a);
/**
* 初始化对象,属性x:12,属性y:20
* 初始化对象,属性x:12,属性y:20
*/
}
三、析构函数
在对象消失时,应使用析构函数释放由构造函数分配的内存。构造函数、复制构造函数和析构函数是构造型成员函数的基本成员。
1、定义析构函数
析构函数的函数名称与类名一样,为了与构造函数进行区分,在析构函数的前面加一个“~”号。在定义析构函数时,不能指定任何返回类型,也不能指定任何参数,但是可以显式的声明参数为void,即A::~A(void),一个类只能定义一个析构函数。
void example3();
class Point3{
private:
int x,y;
public:
Point3(int,int);//声明两个参数的构造函数
~Point3();//声明析构函数
};
Point3 :: Point3(int a,int b):x(a),y(b){//定义两个参数的构造函数
cout << "Initializing" << endl;
}
Point3 :: ~Point3(){
cout << "Destructor is active" << endl;
}
int main(){
example3();
return 0;
}
void example3(){
Point3 a(10,20);//通过构造函数实例化一个对象
cout << "Exiting main function" << endl;
/**
* Initializing //创建对象时调用构造函数
* Exiting main function //在程序结束之前调用析构函数
* Destructor is active //程序自动调用构造函数
*/
}
当对象的生命周期结束时,程序为这个对象自动调用析构函数,然后回收这个对象占用的内存。全局对象和静态对象的析构函数在程序运行结束之前调用。类的对象数组的每个元素调用一次析构函数。全局对象的析构函数在程序结束之前被调用。
如果在定义类时没有定义析构函数,C++编译器也要为它产生一个函数体为空的默认析构函数。
2、析构函数和运算符delete
运算符delete与析构函数一起工作,当使用运算符delete删除一个动态对象时,它首先为这个动态对象调用析构函数,然后再释放这个动态对象占用的内存,这与使用new建立动态对象的过程刚好相反。
void example4();
class Point3{
private:
int x,y;
public:
Point3(int=0,int=0);//声明两个参数的构造函数
~Point3();//声明析构函数
};
Point3 :: Point3(int a,int b):x(a),y(b){//定义两个参数的构造函数
cout << "Initializing" << a << "," << b << endl;
}
Point3 :: ~Point3(){
cout << "Destructor is active" << endl;
}
int main(){
example4();
return 0;
}
void example4(){
Point3 *p = new Point3[2];//创建对象数组
delete [] p;//动态删除对象
/**
* Initializing0,0
* Initializing0,0
* Destructor is active
* Destructor is active
*/
}
当使用delete释放动态对象数组时,必须告诉它这个动态对象数组有几个元素对象,C++使用“[]”来实现 。然后,delete将为动态数组的每个对象调用一次析构函数,并释放内存。
当程序先后创建几个对象时,系统将按照先创建后析构的原则进行析构对象,当使用delete调用析构函数时,则按delete的顺序析构。
四、this指针
在定义Point类的对象a之后,当执行语句“a.getX()”时,计算是怎么知道获取哪个对象的值呢?其实成员函数getX()有一个隐藏参数,名为this指针,当源程序被编译后,getX()的实际形式如下:
int Point :: getX((Point*)this){
return this -> x;
}
五、一个类的对象作为另一个类的成员
void example9();
class Desk{
private:
int num;//数量
public:
void setNum(int a){
num = a;
}
int getNum(){
return num;
}
};
class Bed{
private:
int num;//数量
public:
void setNum(int a){
num = a;
}
int getNum(){
return num;
}
};
class House{
private:
Desk d;
Bed b;
public:
void setHouse(Desk &d,Bed &b){
this -> d = d;
this -> b = b;
}
int getTotal(){
return d.getNum() + b.getNum();
}
};
int main(){
example9();
return 0;
}
void example9(){
Bed b;
b.setNum(2);
Desk d;
d.setNum(5);
House h;
h.setHouse(d,b);
cout << "屋子里一共有" << h.getTotal() << "件家具!";
/**
* 屋子里一共有7件家具!
*/
}