第六章 类与对象

第六章 类与对象

一、类与对象的定义与访问

面向对象程序设计:程序=对象(数据允许的操作组成的封装体)+消息

6.1.1、类和对象的定义

具有相同性质和功能的东西构成的集合,通常归为“一类”这就是类的通俗定义,例如:“人”是类的概念,为了描述人的特点,有姓名、性别、年龄、身高、体重等特征,称为“属性”。人还有各种生活技能和工作技能,称为“方法”。类是抽象的,对象是类的能动实体。

类的说明语句:

Class  类名

{

访问限制:

返回值类型 函数名(){}......

访问限制:

返回值类型 函数名(){}.....

访问限制:

数据类型 变量......

.........

 };

Class<类名>

{

Public:

公有段数据成员和成员函数

Protected:

保护段数据成员和成员函数

Private:

私有段数据成员和成员函数

};(分号在类定义结束后一定要有)

PS

(1)class是关键词,表示后续的语句是一类。

(2)访问限制方式:3个关键词:publicprotectedprivate

(3)public的访问限制:允许从类外边进行访问。比如“类名::成员函数()”

“类对象.变量”“类对象指针->变量”这样的访问

(4)private 不许从类外边进行访问,通过public成员函数还是可以访问到的。

(5)protected:不允许从类外边进行访问,主要用于继承。

(6)类与结构体的区别:

  没有明确指定类成员的访问权限时,C++结构体的成员是公有的,而类的成员是私有的。

(7)对象和类的关系:

●对象是类的实例或实体。

●类与对象的关系,如同C++基本数据类型和该类型的变量之间的关系。

Class Data

{

Public:

Void SetDate(int y,int m,int d);

int IsLeap();

Void PrintDate();

Private:

Int Year,Month,Day;

};

Void Date::SetDate(int y,int m,int d)

{

Year=y;

Month=m;

Day=d;

}

Int Date::IsLeapYear()

{return(Year%4==0&&Year%100!=0)||(Year%400==0);}

Void Date::PrintDate()

{cout<<Year<<”.”<<month<<”.”<<day;}

对象数组

Date date[100]   每一个数组成员就是一个对象

调用方式  date[i].成员函数

6.1.2访问等于对象成员

使用对象包括访问对象的数据成员和调用成员函数。类中的成员函数可以是用自身不同性质的数据成员和调用成员函数。公有成员是提供给外部的接口,即,只有公有成员在类体系外可见。对象成员的访问形式与访问结构的形式相同,运算符.”和“->”用与访问对象成员。

1

#include <iotream>

Using namespace std;

 Class Tclass

{

 Public:

Int x,y;

    Void print()

{

Cout<<x<<”,”<<y;

}

};

Int main()

{

   Tclass test;

   test.x=100;

   Test.y=200;

test.print(); //圆点访问形式

}

2

#include<iostream.h>

class  ptr_access   {

public:                         

 void setvalue(float a, float b) { x=a; y=b; }

 float Getx() {return x;}

 float Gety() {return y;}

 void print()

{

 cout<<"x="<<x<<endl;

 cout<<"y="<<y<<endl;

 }

private:                          //私有数据成员

 float x,y;

};

int main()

{

 float a1,a2;

 ptr_access *ptr=new ptr_access;

       ptr->setvalue(2,8);      

      //通过指针访问公有成员函数

 ptr->print();

 a1=(*ptr).Getx();     

      //通过公有成员函数访问私有数据成员

 a2=(*ptr).Gety();

 cout<<"a1="<<a1<<endl;

        cout<<"a2="<<a2<<endl;

        return 0;

}

这个例子就将两种对象访问形式展现了出来。

6.1.3  this指针

一个对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。

this指针是类的一个自动生成、自动隐藏的私有成员,它存在于类的非静态成员函数中,指向被调用函数所在的对象。全局仅有一个this指针,当一个对象被创建时,this指针就存放指向对象数据的首地址。

根据以下程序来说明this指针

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

#include<iostream>

using namespace std;

class Point

{

  private:

  int x,y;

  public:

  Point(int a,int b)

  {

    x=a;

    y=b;

  }

  void MovePoint(int a,int b)

  {

    x+=a;

    y+=b;

  }

  void print()

  {

    cout<<"x="<<x<<"y="<<y<<endl;

  }  

};

int main()

{

  Point point1(10,10);

  point1.MovePoint(2,2);

  point1.print();

  return 0;

}

当对象point1调用MovePoint(2,2)函数时,即将point1对象的地址传递给了this指针。

MovePoint函数的原型应该是 void MovePoint( Point *this, int a, int b);第一个参数是指向该类对象的一个指针,我们在定义成员函数时没看见是因为这个参数在类中是隐含的。这样point1的地址传递给了this,所以在MovePoint函数中便显式的写成:

void MovePoint(int a, int b) { this->x +=a; this-> y+= b;}

即可以知道,point1调用该函数后,也就是point1数据成员被调用并更新了值。

4. 关于this指针的一个经典回答:

当你进入一个房子后,

你可以看见桌子、椅子、地板等,

但是房子你是看不到全貌了。

对于一个类的实例来说,

你可以看到它的成员函数、成员变量

但是实例本身呢?

this是一个指针,它时时刻刻指向你这个实例本身

5.使用this指针要注意的事项

相信大家对指针的用法已经很熟了,这里也不多说些定义性的东西了,只说一下指针使用中的注意事项吧。

.在定义指针的时候注意连续声明多个指针时容易犯的错误,例如int * a,b;这种声明是声明了一个指向int类型变量的指针a和一个int型的变量b,这时候要清醒的记着,而不要混淆成是声明了两个int型指针。

.要避免使用未初始化的指针。很多运行时错误都是由未初始化的指针导致的,而且这种错误又不能被编译器检查所以很难被发现。这时的解决办法就是尽量在使用指针的时候定义它,如果早定义的话一定要记得初始化,当然初始化时可以直接使用cstdlib中定义的NULL也可以直接赋值为0,这是很好的编程习惯。

.指针赋值时一定要保证类型匹配,由于指针类型确定指针所指向对象的类型,因此初始化或赋值时必须保证类型匹配,这样才能在指针上执行相应的操作。

This指针的两种使用方式:

一种情况就是,在类的非静态成员函数中返回类对象本身的时候,直接使用 return *this;另外一种情况是当参数与成员变量名相同时使用this指针,如this->n = n (不能写成n = n)。

6.2.1构造函数和析构函数

<>构造函数

1、构造函数是用于创建对象的特殊成员函数。

     当创建对象时,系统自动调用构造函数。

没有用户定义的构造函数时,系统提供缺省版本的构造函数。

2、构造函数的作用是:

   为对象分配空间;对数据成员赋初值;请求其他资源。

3、构造函数名与类名相同:类名。

4、构造函数可以重载。

5、构造函数可以有任意类型的参数,但没有返回类型。

<>析构函数

1、析构函数是用于取消对象的成员函数.

     当一个对象作用域结束时,系统自动调用析构函数。

2 、析构函数的作用是进行对象消亡时的清理工作。

3、没有用户定义析构函数时,系统提供缺省版本的析构函数。

4、析构函数名为: ~ 类名。

5、析构函数没有参数,也没有返回类型。

创建对象的两种:

1)、 如果类中没有定义构造函数,系统将自动生成一个默认形式的构造函数,用于创建对象,默认构造函数形式:

类名::类名(){}

默认构造函数是一个空函数。

例如:

为类Date建立一个构造函数。

#include <iostream.h>

class Date {

    public:

    Date();     // 无参构造函数

    Date(int y,int m,int d);

    void showDate();

    private:

            int year, month, day;

    };

Date::Date()    // 构造函数的实现

{  year=0; month=0; day=0; }

Date::Date(int y,int m,int d)

{ year=y; month=m; day=d; }

inline void Date::showDate()

{  cout<<year<<"."<<month<<"."<<day<<endl; }

int main()

{

     Date a_date,bDate(2014,3,25);  //初始化对象的值 即用到了构造函数。

     a_date.showDate();

     b_date.showDate();

     return 0;

}

2)、通常,利用构造函数创建对象有以下两种方法:

       (1)  利用构造函数直接创建对象.其一般形式为:

             类名  对象名[(实参表)];

          这里的“类名”与构造函数名相同,“实参表”是为构造函数提供的实际参数。

int main()

{

    Date *date1;

            date1=new Date(1998,4,28);     

//  以上两条语句可合写成:Date *date1=new Date(1998,4,28);

    cout<<"Date1 output1:"<<endl;

    date1->showDate();          

    delete date1;

             return 0;

}

(2) 利用构造函数创建对象时,通过指针和new来实现。其一般语法形式为:

      类名 *指针变量 = new 类名[(实参表)];

 例如:

      Date *date1=new Date(1998,4,28);

就创建了对象(*date1)

 构造函数的初始化列表-数据成员的初始化

1、使用构造函数的函数体进行初始化

class Date

{

int d, m, y;

public:

Date(int dd, int mm, int yy)

{

d=dd;

m=mm;

y=yy;

}

Date(int dd, int mm)

{

d=dd;

m=mm;

}

}

2、使用构造函数的初始化列表进行初始化

格式:

funname(参数列表):初始化列表

{  函数体,可以是空函数体  }

初始化列表的形式:

成员名1(形参名1),成员名2(形参名2),成员名n(形参名n)

class Date

{

int d, m, y;

public:

Date(int dd, int mm, int yy)d(dd),m(mm),y(yy)

{ }

Date(int dd, int mm): d(dd),m(mm)

{                }

}

必须使用参数初始化列表对数据成员进行初始化的几种情况:

1、数据成员为常量

2、数据成员为引用类型

#include <iostream>

using namespace std;

class A{

public:

A(int i):x(i),rx(x),pi(3.14)

{}

void display()

{cout<<"x="<<x<<"rx="<<rx<<"pi="<<pi<<endl;}

private:

int x,&rx;

const float pi;

};

int main(){

A aa(10);

aa.display();

return 0;

}

3、数据成员为没有无参构造函数的类的对象

#include<iostream>

using namespace std ;

class A

{ public :

    A ( int x ) : a ( x ) { }

    int a ;

} ;

class B

{ public :

    B( int x, int y ) : aa( x ),  b( y ) { }

    void out()

      { cout << "aa = " << aa.a << endl << "b = " << b << endl ; }

  private :

      int b ;

      A aa ;

} ;

int main ()

  { B  objB ( 3, 5 ) ;

     objB . out ( ) ;

  }

类成员的初始化的顺序:按照数据成员在类中的声明顺序进行初始化,与初始化成员列表中出现的顺序无关。

#include <iostream>

using namespace std;

class CMyClass{

public:

CMyClass(int x, int y):m_y(x),m_x(m_y)

{

cout<<"m_x="<<m_x<<endl;

cout<<"m_y="<<m_y<<endl;

}

private:

int m_x,m_y;

};

int main()

{

CMyClass mc(15,10);

return 0;

}

6.2.3 重载构造函数

构造函数与普通函数一样,允许重载。如果Date类具有多个构造函数,创建对象是,将更具参数匹配调用其中的一个。

像所有的C++hanshu一样,构造函数可以具有默认参数。但是,定义默认参数构造函数时注意调用时有可能产生的二义性。例如:

Class X

{

Public

X()

X(int i=0);

.....

};

Void f()

{

X one(10); //正确

X two;  //错误

}

上述程序再说明X类对象one时,调用构造函数X::X(int i=0);但在说明two的时候,系统无法判断调用的是哪一个?

因此会出现匹配二义性。

6.2.4复制构造函数

1、创建对象是,有时希望用一个已有的同类型对象的数据对他进行初始化。

这就叫复制构造函数

格式:

类名::类名(const 类名 & 引用名,........

Class A

{

Public:

A(int);

A(const A&,int=1); //复制构造函数

}

A a(1);   //创建对象a,调用 A(int);

A b(a,0);  //创建对象b,调用A(const A&,int=1);

A c=b;   //创建对象c,调用A(const A&,int=1);

复制构造函数的特点;

1)、复制构造函数名与类名相同,并且也没有返回值类型。

2)、复制构造函数可写在类中,也可以写在类外。

3)、复制构造函数要求有一个类类型的引用参数。

4)、如果没有显式定义复制构造函数,系统自动生成一个默认形式的复制构造函数。

2、浅复制与深复制

关于浅复制:

●在用一个对象初始化另一个对象时,只复制了数据成员,而没有复制资源,使两个对象同时指向了同一资源的复制方式称为浅复制。

即:对于复杂类型的数据成员只复制了存储地址而没有复制存储内容

●默认复制构造函数所进行的是简单数据复制,即浅复制

关于深复制:

●通过一个对象初始化另一个对象时,不仅复制了数据成员,也复制了资源的复制方式称为深复制。

●自定义复制构造函数所进行的复制是浅复制。

定义支持深复制的复制构造函数

1)、深复制构造函数必须显式定义

2)、深复制构造函数的特点

定义:类名::类名([const] 类名 &对象名);

成员变量的处理:对复杂类型的成员变量,使用new操作符进行空间的申请,然后进行相关的复制操作

6.3类的其他成员

6.3.1常成员

 常数据成员是指数据成员在实例化被初始化后,其值不能改变。

在类的成员函数说明后面可以加const关键字,则该成员函数成为常量成员函数

1. 常数据成员

       使用const说明的数据成员称为常数据成员。

        如果在一个类中说明了常数据成员,那么构造函数就只能通过初始化列表对该数据成员进行初始化,而任何其他函数都不能对该成员赋值。

#include<iostream>

using namespace std;

class Mclass

{ public :

       int k;

       const int M;

       Mclass() : M(5) { }

       void testFun()

        { //M++;

            k++;

        }

 } ;

int main()

{ Mclass t1, t2;

   t1.k=100;

   //t1.M=123; //错误,不能在类外修改常数据成员

   cout<<"t1.k="<<t1.k<<'\t'<<"t1.M="<<t1.M<<endl;

   t2.k=200;   t2.testFun();

   cout<<"&t1.M="<<&t1.M<<endl;

   cout<<"t2.k="<<t2.k<<'\t'<<"t2.M="<<t2.M<<endl;

   cout<<"&t2.M="<<&t2.M<<endl;

}

2、常对象

  如果在说明对象时用const修饰,则被说明的对象为常对象。

    常对象的说明形式如下:

      类名 const 对象名[(参数表)];

  或者

      const  类名 对象名[(参数表)];

  在定义常对象时必须进行初始化,而且不能被更新。

    说明:

   1C++不允许直接或间接更改常对象的数据成员。

   2C++规定常对象只能调用它的常成员函数、静态成员函数、构造函数(具有公有访问权限)

#include<iostream>

using namespace std;

class T_class

{ public:

       int a, b;

  T_class( int i, int j )  {

      a=i; b=j;

   }

} ;

int main()

{ const T_class t1( 1, 2 );

  T_class t2( 3, 4 );

  //t1.a=5;

  //t1.b=6;

 t2.b=8;

 t2.a=7;

cout<<"t1.a="<<t1.a<<'\t'<<"t1.b="<<t1.b<<endl;

  cout<<"t2.a="<<t2.a<<'\t'<<"t2.b="<<t2.b<<endl;

}

3、常成员函数

   在类中使用关键字const说明的函数为常成员函数,常成员函数的说明格式如下:

       类型说明符 函数名(参数表) const;

      const是函数类型的一个组成部分,因此在函数的实现部分也要带关键字const

     常成员函数不能更新对象的数据,也不能调用非const修饰的成员函数(静态成员函数、构造函数除外)

#include<iostream>

using namespace std ;

class  Simple

{   int  x, y ;

  public :

     void setXY ( int a, int b) { x = a ;  y = b ; }

     void printXY() { cout << x << "," << y << endl ; }

     void  constFun ( ) const

         { x ++ ; y ++ ; }//非法

}

 

6.3.2静态成员

 类成员冠以static声明时,称为静态成员。

 静态数据成员为同类对象共享。

 静态成员函数与静态数据成员协同操作。  

静态数据成员的初始化:

在类外进行静态数据成员的声明

类型 类名::静态数据成员[=初始化值];   //必须进行声明

不能在成员初始化列表中进行初始化

如果未进行初始化,则编译器自动赋初值(默认值是0

初始化时不能使用访问权限

静态成员函数

静态成员不属于某一个单独的对象,而是为类的所有对象所共有

静态成员函数的作用不是为了对象之间的沟通,而是为了能处理静态数据成员: 保证在不依赖于某个对象的情况下,访问静态数据成。

除静态数据成员以外,一个类还可以有静态成员函数。

静态函数仅可以访问静态成员,

或是静态成员函数或是静态数据成员。

静态成员函数和静态数据成员一样,它们都属于类的静态成员,它们都不是对象成员。因此,对静态成员的引用不需要用对象名。

静态成员函数没有this指针,只能对静态数据操作

定义静态成员函数的格式如下:

    static 返回类型 静态成员函数名(参数表);

    与静态数据成员类似,调用公有静态成员函数的一般格式有如下几种:

   类名::静态成员函数名(实参表)

   对象. 静态成员函数名(实参表)

   对象指针->静态成员函数名(实参表)

#include <iostream>

using namespace std;

class Goods

{

int weight;

static int total_weight;

public:

Goods(int x):weight(x){}

void in() {total_weight=total_weight+weight; }

void out(){total_weight=total_weight-weight; }

static void display_store(){ cout<<total_weight<<endl; }

};

int Goods::total_weight=0;

int main(){

Goods g1(10)g2(20);

g1.in(); g2.in();

Goods::display_store(); //g1.display_store(); g2.display_store();

g1.out();  Goods::display_store();

return 0;

}

Ps

1)静态成员函数在类外定义时不用static前缀。

2)静态成员函数主要用来访问同一类中的静态数据成员。

  (3)   私有静态成员函数不能在类外部或用对象访问。

4)可以在建立对象之前处理静态数据成员。

5)编译系统将静态成员函数限定为内部连接(在其他文件中不可见)

6)静态成员函数中是没有this指针的。

7)静态成员函数不访问类中的非静态数据成员。如有需要,只能通过对象名(或指向对象的指针)访问该对象的非静态成员。

 

静态成员函数来访问非静态数据成员。

#include<iostream.h>

class small_cat{

public:

     small_cat(double w){weight=w;total_weight+=w;total_number++;}

     static void display(small_cat &w)

     {cout<<"The small_cat weights "<<w.weight<<"kg\n";}

     static void total_disp()

     {cout<<total_number<<"small_cat total weight ";

       cout<<total_weight<<" kg "<<endl;}

private:

     double weight;  

     static double total_weight; static double total_number;

};

double small_cat::total_weight=0;double small_cat::total_number=0;

int main()

{    small_cat w1(0.9),w2(0.8),w3(0.7);

      small_cat::display(w1);small_cat::display(w2);

      small_cat::display(w3);small_cat::total_disp();

      return 0;}

6.3.3友元

 

友元:可以访问类的所有成员,包括私有成员。友元可以是一个普通函数、成员函数或者另一个类。

友元关系是非对称的,非传递的。除非特别声明,否则:

FA的友元,但A不是F的友元;

B是A的友元,C是B的友元,但C不是A的友元。

1、友元函数

如果在本类(类A)以外的其他地方定义了一个函数(函数B

这个函数可以是不属于任何类的非成员函数,

也可以是其他类的成员函数,

在类体中用friend对其(函数B)进行声明,此函数就称为本类(类A)的友元函数。

友元函数(函数B)可以访问这个类(类A)中的私有成员

class  A

   { private:

          int  i ;

          friend void FriendFun(A * , int) ;

      public:

         void MemberFun(int) ;

   } ;

    …

void FriendFun( A * ptr , int x  )

     { ptr -> i = x ; } ;  

void A:: MemberFun( int x )

     { i = x ; } ;

2、友元类

F类是A的友元类。则F类的所有成员函数都是A类的友元函数,F类中能访问A中的所有数据。在程序中,友元类通常设计为一种对数据操作或类之间传递消息的辅助类。

6.4类的包含

 类的包含是程序设计中一种软件重用技术。即定义一个新的类时,通过编译器把另一个类 “抄”进来。

 当一个类中含有已经定义的类类型成员,带参数的构造函数对数据成员初始化,须使用初始化语法形式。

构造函数 ( 形参表 ) : 对象成员1(形参表 ) , , 对象成员n (形参表 )

 例如:

用类包含求计算两点之间的距离

#include<cmath>
#include<iostream>
using namespace std;
class  Point 
{ public:
       Point( int xi=0, int yi=0 ) { x = xi; y = yi; }
       int GetX()  {  return x;  }
       int GetY()  {  return y;  }
  private:  int x;  int y;

};

class Distance 
{ public:
      Distance( Point xp1, Point xp2 );
      double GetDis()  {  return dist;  }
  private:
     Point p1, p2;
     double dist;

};

#include<iostream>
using namespace std;
class A{ 
public:
A( ){ cout<<"This is A."<<endl; }      //缺省构造函数
A(int i) { cout<<"This is A, and it's value = "<<i<<endl; }
};
class B{  
public:
B( ) {  cout<<"This is B"<<endl;   } //缺省构造函数
B(int i):a2(i) { cout<<"Hello B!"<<endl;m} //采用成员初始化列表的方式,成员对象a1的形参未初始化,将成员对象a2的形参初始化为i

猜你喜欢

转载自blog.csdn.net/qq_40893490/article/details/80028091