博客
关于我
C++类与对象详解
阅读量:163 次
发布时间:2019-02-28

本文共 10885 字,大约阅读时间需要 36 分钟。

什么是类和对象

类和对象的概念

类是对象的抽象,对象是对客观事物的抽象。

用通俗的话来说:

类是类别的意思,是数据类型。

对象是类别下的具体事物。

也就是说:

类是数据类型,对象是变量。

比如:

水果是类。(水果不是真实存在的)

一个苹果是具体的对象。(一个苹果是真实存在的,它有大小,有颜色)

类的定义

定义一个类:盒子Box

class Box{      public:      double length;	// 盒子的长度      double breadth;	// 盒子的宽度      double height;	// 盒子的高度};

解释1:

class是一个专门用于定义类的关键字。

Box为类名
public下面会讲到,这里不必在意。
double length是类的数据成员,这里一共定义了三个数据成员。分别代表Box的长宽高。
将类的成员使用大括号{}包裹,记得最后使用分号结束

解释2:

盒子是有自己的属性的,比如说盒子的长宽高,盒子的颜色和材质(上面的代码没有定义)。

我们称这些属性为类的数据成员

尽管有数据成员,类是没有存储空间的,只是一段代码存储在内存中。(因为类是抽象的,不是真实存在的)

只有实例化为对象,才会创建内存空间供数据成员存储。
也就是说,每一个盒子都会有自己的大小。
比如,int是没有存储空间的,但是int i=0;就会为变量i申请4字节的内存空间。

对象的创建
class Box{      public:      double length;	// 盒子的长度      double breadth;	// 盒子的宽度      double height;	// 盒子的高度};Box box1;               //使用类创建对象box1box1.breadth = 20;      //将对象box1的数据成员breadth的值修改为20box1.height = 20;box1.length = 20;//得到box1的数据成员的值,再得到盒子box1的体积cout << "盒子的体积为" << box1.breadth* box1.height*box1.length << endl;

解释:

Box为类名,是数据类型。

box1为变量名,是对象。它是通过类Box实例化的一个对象。
可以看到类就像是一个模板,对象是根据这个模板创建的。
box1.breadth表示访问对象中的数据成员breadth。
访问数据成员的语法:对象名.成员名。

思考:如果我们使用动态内存分配来创建对象,那么如果访问数据成员。

分析:我们一般是通过【对象名.成员名】。如果使用动态内存分配,那么我们就没有对象名了,只有指针名。

class Box{      public:      double length;	// 盒子的长度      double breadth;	// 盒子的宽度      double height;	// 盒子的高度};Box *p = new Box;       //使用动态内存分配的方式创建对象p->breadth = 20;        //使用指针修改对象的数据成员p->height = 20;p->length = 20;//使用指针得到对象的数据成员的值,在得到盒子的体积cout << "盒子的体积为" << p->breadth * p->height * p->length << endl

成员函数

成员函数的定义
class Box{      public:      double length;	// 长度      double breadth;	// 宽度      double height;	// 高度         double getVolume(){      //成员函数         return length * breadth * height;      }};

解释:

成员函数定义在类中,可以访问对象中的所有成员(包括数据成员和成员函数)。

(有些数据成员是不能被随便访问的,public的作用就是限制访问的,后面会讲到)

成员函数没有内存空间:

数据成员在每个对象中都有内存空间,但是成员函数在存在类中,在内存中只是一段代码,需要被对象使用时,则被调用。

成员函数的使用
class Box{      public:      double length;	// 长度      double breadth;	// 宽度      double height;	// 高度      double getVolume(){            return length * breadth * height;      }};Box box1;box1.breadth = 20;box1.height = 20;box1.length = 20;//调用成员函数得到对象的体积cout << "盒子的体积为" << box1.getVolume() << endl;

解释:

成员函数的调用方法与数据成员一致,通过【对象名.函数名(参数)】调用。

函数可访问此对象的所有成员。

成员函数声明

普通函数中,可以先声明再调用最后定义。

在类的成员函数中,同样可以进行函数声明。

class Box{      public:      double length;	// 长度      double breadth;	// 宽度      double height;	// 高度      double getVolume();   //成员函数声明};double Box::getVolume(){       //在类的外面进行函数定义    return length * breadth * height;}

解释:

使用【类名::】来修饰函数,表示此函数是Box的成员函数。(其他类可能也有同名的函数)

在调用时,是使用【对象名.函数名(参数)】调用的。

为什么要这么做?

因为函数具有封装性,我们只要关心函数的使用,不必关心函数是如何实现的。


类的访问修饰符

数据封装是面向对象编程的一个重要特点,类的访问修饰符防止直接访问类的内部成员。

类成员的访问限制是通过标记public、private、protected来指定的。
关键字public、private、protected称为访问修饰符

比如:

class Base {      public:  // 这里是公有成员    protected:  // 这里是受保护成员    private:  // 这里是私有成员};
1.公有成员-public

公有成员在程序中类的外部是可访问的。也就是说,谁都可以访问这些公有成员,没有任何限制。

class Box{      public:        double length;	// 盒子的长度        double breadth;	// 盒子的宽度        double height;	// 盒子的高度};void main(){           Box box1;        box1.breadth = 20;}

我们发现:可以直接通过【对象名.成员名】访问对象的成员。

相对的,我们可以看一下private和protected是都能通过这种方式直接访问对象的成员。

2.私有成员-private

私有成员在类的外部是不可访问的,甚至是不可查看的。

也就是说,在类的外部是不可以通过【对象名.成员名】访问对象的成员的。
类的私有成员只能在类的内部被直接调用。

class Box{   private:   double length;	// 盒子的长度   double breadth;	// 盒子的宽度   double height;	// 盒子的高度};void main(){      Box box1;// box1.breadth = 20;	//这行代码会报错!}

解释:

在以上的代码中,由于breadth是私有成员,所以直接通过【对象名.数据成员】的形式访问会报错。

类的私有成员是默认的成员,如果没有写访问修饰符,则默认为private。

比如:

//下面的数据成员都是privateclass Box{      double length;   double breadth;   double height;};

通过函数间接访问private:

如果我们在类的外部需要访问私有成员,我们可以通过函数间接访问

原理:

1.private不能被直接访问.

2.public可以被直接访问。
3.类的内部可以随意调用任意成员。
得到结论:通过public成员间接调用private成员。

xclass Box{   private:   double length;	// 盒子的长度   double breadth;	// 盒子的宽度   double height;	// 盒子的高度public:   void setBreadth(double B){               Breadth = B;   }   double getBreadth(){               return breadth;   }};void main(){      Box box1;   box1.setBreadth(12.5);   cout <<"盒子的宽度为: "<< box1.getBreadth() << endl;}

解释:

length、breadth和height都是私有成员。

setBreadth(double B)getBreadth()是公有成员。
我们通过这两个set和get函数修改和得到breadth的值。
通过这样的方式,我们就可以访问私有成员了。

3.受保护成员-protected

保护成员访问控制与私有成员十分相似。在类的外部不可被直接访问。

但有一点不同,保护成员在派生类(即子类)中是可访问(以后再说)。

UTOOLS1576408252981.png

构造函数和析构函数

1.构造函数

什么是构造函数?

类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。

而且这个构造函数时自动执行的,无需手动调用。

构造函数有什么作用?

构造函数可用于初始化,例如:为某些成员变量设置初始值。

如何定义构造函数?

  1. 构造函数的名称与类的名称是完全相同。
  2. 不会返回任何类型,也不会返回 void。
class Box{   private:   double length;	// 盒子的长度   double breadth;	// 盒子的宽度   double height;	// 盒子的高度public:   Box(double L,double B,double H){   	//构造函数      length = L;  breadth = B;  height = H;   }   double getBreadth(){         return breadth;   }};void main(){      Box box1 = Box(12, 12, 12);	//定义的时候初始化   cout << "盒子的宽度为: " << box1.getBreadth() << endl;}
2.默认构造函数
  1. 类中带有默认构造函数,若没有定义构造函数,则会采用默认构造函数创建对象。
  2. 默认构造函数是没有参数的,不会初始化数据成员。(就是空的函数,什么事也不做)。
  3. 若自定义了构造函数,则C++不会为我们创建默认构造函数。
    我们可以进行构造函数重载,使得初始化多样化。

例1:

Box(){         //默认构造函数,由程序自动创建}//如果我们自定义了其他函数,则程序不会为我们自动创建构造函数

例2:

#include
using namespace std;class Box{ private: double length; // 盒子的长度 double breadth; // 盒子的宽度 double height; // 盒子的高度};void main(){ Box box1=Box(); //此处不会报错,因为自动创建了构造函数Box(){}}

例3:

#include
using namespace std;class Box{ private: double length; // 盒子的长度 double breadth; // 盒子的宽度 double height; // 盒子的高度public: Box(double L, double B, double H){ //构造函数 length = L; breadth = B; height = H; }};void main(){ Box box1; //报错!!!!不存在默认构造函数}/*解释: 因为我们自己创建了一个构造函数,所以程序不会为我们创建默认的构造函数Box(){} 如果想要有默认的构造函数:可以使用函数重载,自己创建Box(){}*/

例4:

#include
using namespace std;class Box{ private: double length; // 盒子的长度 double breadth; // 盒子的宽度 double height; // 盒子的高度public: Box(double L, double B, double H){ //构造函数 length = L; breadth = B; height = H; } Box(){ //构造函数 }};void main(){ Box box1 = Box(11,12,14); Box box2; //相当于Box box2=Box();}
3.析构函数

什么是析构函数?

类的析构函数是类的一种特殊的成员函数,它会在每次删除对象时执行。

如何定义析构函数?

1.析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀。

2.它不会返回任何值,也不能带有任何参数。

析构函数有什么作用?

析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。

比如:在构造函数中使用了动态内存分配,那么系统并不会自动回收这些内存。所以需要析构函数。

如何调用析构函数?

无需手动调用,在程序运行完后自动调用析构函数,完成内存释放和文件关闭等功能。(所以只要关心如何定义即可)

class Arr{   public:   int *p;   Arr(){   		//构造函数      p = new int[5];   }   ~Arr(){   	//析构函数      delete[] p;   }};/*解释:   析构函数作用其中之一就是回收动态内存分配的空间。   new和delete要成对出现。   将delete写在析构函数中,就可以在销毁对象时自动回收内存了。   析构函数还有其他的作用:比如关闭文件。*/
4.拷贝构造函数

什么是拷贝构造函数?

拷贝构造函数是一种特殊的构造函数,它在创建对象时,可以用对象给对象赋值。

拷贝构造函数有什么作用?

如果对象中有指针,那么使用对象给对象赋值后,它们的指针将会指向同一块内存。这样它们就会产生关联,而不是两个不同的对象。

如何定义拷贝构造函数?

类名 (const 类名 &obj) {      //省略构造函数的主体}

例1:

/*这个例子没有使用拷贝构造函数,但是运行结果是正常的。例子中:使用了对象给对象赋值。对象给对象赋值的过程中,会将所有成员变量的值复制一遍。*/#include
using namespace std;class Box{ public: double length; // 盒子的长度 double breadth; // 盒子的宽度 double height; // 盒子的高度 Box(double L, double B, double H){ //构造函数 length = L; breadth = B; height = H; }};void main(){ Box box1 = Box(1, 1, 1); Box box2 = box1; cout << box2.length << endl; //输出1}/*解释: 这个例子中:将box1的值复制一份给box2。*/

例2:

#include
using namespace std;class Arr{ public: int *p; Arr(){ //构造函数 p = new int[5]; //动态内存分配5个int的空间 } ~Arr(){ //析构函数 delete[] p; //回收动态内存分配的空间 }};void main(){ Arr a; a.p[1] = 1; //将a中的p[1]修改为1 Arr b = a; b.p[1] = 2; //将b中的p[1]修改为2 cout << a.p[1] << endl; //输出2!!! cout << b.p[1] << endl; //输出2!!! getchar(); getchar(); getchar(); getchar(); getchar();}/*现象: 我们将b中的p[1]修改为2,但是a中的p[1]也被修改为了2. 我们并没有修改a中的p[1],为什么也变成了2?解释: Arr b = a;将a的值赋值给了b。 那么,a的值就和b一样了,即他们的数据成员p指向的内存空间是同一个。 所以,修改b的值,a的值也会跟着变化。*/
UTOOLS1576414358568.png

产生的问题:

我们想要创建的是两个不同的对象,但是上面这个例子:两个对象相互关联了。

如何解决:

拷贝构造函数

#include
using namespace std;class Arr{ public: int *p; Arr(){ p = new int[5]; } ~Arr(){ delete[] p; } Arr(const Arr&obj){ //拷贝构造函数 p = new int[5]; for (int i = 0; i < 5; i++) p[i] = obj.p[i]; }};void main(){ Arr a; a.p[1] = 1; Arr b = a; b.p[1] = 2; cout << a.p[1] << endl; //输出1 cout << b.p[1] << endl; //输出2 getchar(); getchar(); getchar(); getchar(); getchar();}/*解释: 有了拷贝构造函数,对象给对象赋值的时候就不会只复制地址了,而是复制地址所在的内存空间的值。*/

友元

类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。

尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。

友元可以是一个函数,该函数被称为友元函数

友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。

使用关键字friend来定义友元类和友元函数

友元函数举例
class Box{      double width;public:   friend void printWidth(Box box);   void setWidth(double wid){         width = wid;   }};void printWidth(Box box){      cout << box.width << endl;}void main(){      Box b;   b.setWidth(10);   printWidth(b);	//外部函数访问private成员}
友元类举例
class Box{      double width;public:   friend class friendBox;      void setWidth(double wid){         width = wid;   }};class friendBox{   public:   void testFunc(Box box){         cout << "box.width = " << box.width << endl;   }};void main(){      Box b;   b.setWidth(10);   friendBox f;   f.testFunc(b);		//友元类中的所有函数都是友元函数}

内联函数

背景:

函数在调用的时候会发生转移,会在栈中保存现场(比如原来的变量值),这样的操作会花费一定的内存空间和时间。

内联函数:

在调用时不发生控制转移,而是在编译时将函数体嵌入在每一个调用处。

适用于功能简单,规模较小又使用频繁的函数。
好处:加快程序运行效率

注意:

  1. 递归函数无法内联处理,内联函数不能有循环体,switch语句,不能进行异常接口声明。
  2. 即便将函数声明为内联函数,程序在编译时若发现不适合作为内敛函数,则按普通函数处理。
  3. 类中的所有成员函数都是内联函数。
inline int Max(int x, int y){   	//将函数声明为内联函数   return (x > y)? x : y;}int main(){      cout << "Max (20,10): " << Max(20,10) << endl;   return 0;}

this指针

每一个对象都有自己的地址,this指向自己。

可以通过this来访问本对象的成员。

class Box{   public:   double length; 	// 长度   double breadth;     // 宽度   double height;    	// 高度   double setLength(double length){         this->length=length;	//通过this指针访问自身的数据成员   }};int main(){      Box box1;   box1.setLength(10);	//长度被修改为10   cout<
<

解释:

细心发现,函数double setLength(double length)中的length和数据成员中的额length冲突了,函数参数length比数据成员length的作用域更小,所以默认访问的是函数参数length。

那么我们如何去访问数据成员length呢?
因为this指针永远指向自己(本对象),所以可以通过this->length来访问。

静态成员

静态成员在类的所有对象中是共享的。无论创建多少对象,静态成员始终只有一个。

我们使用static关键字将成员声明为静态。
静态成员分为静态数据成员静态成员函数

1.静态数据成员

静态数据成员需要在类中定义,在类外初始化。

访问方式:

类名::变量名(推荐)对象名.变量名。

例1-静态成员是共享的:

class A{   public:   static int i;  //静态数据成员定义};int A::i=0;       //静态数据成员初始化int main(){      A a1,a2;   a1.i=1;   cout<
<

解释:

我们将a1的i修改为1,可以发现a2的i也变成了1,所以静态成员是共享的。

例2-静态数据成员的作用举例:

class A{   public:    static int ObjectCount;         //静态成员    A(){           ObjectCount++;	//每创建一个对象,此变量加1(用于记录对象的数量)    }   ~A(){         ObjectCount--;	//每回收一个对象,此变量减1   }};int A::ObjectCount=0;               //静态成员初始化void main(){      A a1;   A a2;   cout<
<

解释:

静态数据成员是共享的。在构造函数中,将此变量+1;在析构函数中,将此变量-1。

那么,这个成员就是用来记录对象的数量。(当前存在多少个A的对象)

1.静态数据成员

静态成员函数只能访问静态成员(包括数据成员和静态成员函数)和类外部其他函数

静态成员函数没有this指针。

解释:

因为静态成员函数只有一份,那么如果他要访问对象中的数据成员,该访问哪一个对象呢?所以,不能让静态成员函数访问对象中的非静态成员。

this指针是每个对象都有的,指向本对象。静态成员是类拥有的,而不是对象,所以类没有this指针,静态成员函数也就不能使用this指针了。

访问方式:

类名::静态函数名(推荐)对象名.静态函数名

举例:

class A{   public:    static int ObjectCount;         //静态成员   A(){         ObjectCount++;   }   ~A(){         ObjectCount--;	//每回收一个对象,此变量减1   }   static void print(){              //静态成员函数      cout<
<

转载地址:http://hfbj.baihongyu.com/

你可能感兴趣的文章
MySQL Binlog 日志监听与 Spring 集成实战
查看>>
MySQL binlog三种模式
查看>>
multi-angle cosine and sines
查看>>
Mysql Can't connect to MySQL server
查看>>
mysql case when 乱码_Mysql CASE WHEN 用法
查看>>
Multicast1
查看>>
mysql client library_MySQL数据库之zabbix3.x安装出现“configure: error: Not found mysqlclient library”的解决办法...
查看>>
MySQL Cluster 7.0.36 发布
查看>>
Multimodal Unsupervised Image-to-Image Translation多通道无监督图像翻译
查看>>
MySQL Cluster与MGR集群实战
查看>>
multipart/form-data与application/octet-stream的区别、application/x-www-form-urlencoded
查看>>
mysql cmake 报错,MySQL云服务器应用及cmake报错解决办法
查看>>
Multiple websites on single instance of IIS
查看>>
mysql CONCAT()函数拼接有NULL
查看>>
multiprocessing.Manager 嵌套共享对象不适用于队列
查看>>
multiprocessing.pool.map 和带有两个参数的函数
查看>>
MYSQL CONCAT函数
查看>>
multiprocessing.Pool:map_async 和 imap 有什么区别?
查看>>
MySQL Connector/Net 句柄泄露
查看>>
multiprocessor(中)
查看>>