类与对象课程笔记



类与对象

基础知识

1.类的定义

类是面对对象程序设计实现信息封装的基础。类是一种用户定义类型,也称类类型,每个类包含数据说明(成员数据)和一组操作数据或传递消息的函数(成员函数)。

类是对具有相同属性和行为的一组对象的抽象与统一描述。是用户自定义的数据类型

2.1)类的说明语句一般形式为:

class <类名>

{

public:

共有段数据成员和成员函数;

Protected:

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

Private:

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

};

关键字private用于声明私有成员,私有成员只能在类中可见,不能在类外或派生类中使用。如果私有成员放在第一段,则可以忽略关键字private

Protected声明保护成员。保护成员在类和它的派生类中可见。

Public 声明共有成员。共有成员是类的接口,在类中和类外可见

2)注意事项、

类的成员可以是其他类的对象,但不能以类自身的对象作为本类的成员,而类自身的指针和引用可以作为类的成员。

 类定义必须以分号结束。

 类与结构体的区别:

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

3)数据成员和成员函数

class Date

{public:

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

int IsLeapYear();

void PrintDate();

private:

int year,month,day;

};

在这个类的定义中

定义了三个私有数据成员:year,month,day

成员函数在类外的定义使用作用域区分符进行说明,即以下的形式:

返回类型 类名::函数名(参数表)

则将以上的三个成员函数可以输入为:

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

{year=y;

month=m;

day=y;

}

int Date::IsLeapYear()

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

Void Date::PrintDate()

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

成员函数有两个作用:

一是操作数据成员,包括访问和修改数据成员;

二是用于协同不同的对象操作,称为传递消息

当然成员函数也可以在函数类内实现

class Coord{

    public:

       void setCoord(int a,int b)

       { x=a; y=b;}

       int getx()

       { return x;}

       int gety()

       { retrun y;}

  private:

       int x,y;

  };

在类内定义的函数被默认成内联函数,

内联函数用inline修饰,它的作用是减少频繁调用小子程序的运行的时间开销

重载函数

函数名相同,但参数不相同(类型不同,或者个数不同)的一组函数。

它的作用是用来处理不同数据类型的相似功能的任务

例如以下coding

#include<iostream>

using namespace std ;

int  abs ( int a ) ;

double  abs ( double f ) ;

int main ()

  {  cout<< abs ( -5 )  << endl;

      cout << abs ( -7.8 )<< endl ;

  }

int  abs ( int a )

  { return  a <  0 ?  -a  :  a;  }

double  abs ( double f )

  { return  f < 0  ? -f  :  f ;  }

这里定义的abs函数,用来处理了两种数据类型的(int型和double型)绝对值,这样就可以利用重载函数;

 

 

3.对象

对象是类的实例或实体。

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

定义的基本格式为:

类名 对象名1,对象名2,对象名3,,,,,

比如以下定义的点类:

class Point      

{

public:    

         voidInitPoint(float PointA_x, float PointA_y);

         voidMove(float New_x, float New_y);

         floatGetPointx();

         floatGetPointy();

private:

            float P1_x,P1_y;

};

int main()

{

     Point  p1,p2;

}

在主函数中就定义了两个Point类的对象p1p2

那么,对象该如何调用类中的成员函数?

有两种方法

法一:使用运算符“.” 来进行调用

例如:

#include<iostream>

using namespace std;

classTclass

{public:

Int x,y;

voidprint()

{cout<<x<<”,”<<y;}

};

Intmain()

{ Tclasstest;

test.x=100;       //访问共有数据成员

test.y=200;

test.print();        //共有段成员函数

}

法二:

用指针访问对象成员

#include<iostream>

usingnamespace std;

classTclass

{public:

Int x,y;

Voidprint()

{cout<<x<<”,”<<y<<endl;

};

intadd(Tclass *ptf)

{rutern(ptf->x+ptf->y);

}

Intmain()

{Tclasstest,*pt=&test;   //说明一个对象test和对象指针pt

pt->x=100;            //用对象指针访问数据成员

pt->y=200;

pt->print();            //用对象指针调用成员函数

test.x=150;

test.y=450;

test.print();

cout<<”x+y=”<<add(&test)<<endl;   //把对象地址传给指针参数

}

4.this指针

在调用成员函数的时候,我们会用到指针的方法,在C++中,同一类的各个对象都有自己的数据成员的存储空间,但系统不会为每个类的对象建立建立成员函数副本,类的成员函数可以被各个对象调用。例如,说明一个Tclass类的对象test,函数 调用:

test.print()

在对象test上操作。同样,若说明一个指向Tclass的指针:

  Tclass *p

则函数调用为:p->print()

*P上操作。

但从成员函数:

voidTclass::print();

的参数上看,并不知道它正在哪个对象上操作。其实C++为成员函数提供一个称为this的隐含指针参数,所以,我们常常称成员函数拥有this指针。

  当一个对象类调用类的成员函数时,对象的地址被传递给this指针,即this指针指向了该对象。This是一个隐含指针,不能被显示说明,但可以在成员函数中显示使用。

Tclass的成员函数print可以这样书写:

voidTclass::print()

{cout<<this->x<<”,”<<this->y<<endl;}

this指针的显示使用主要在运算符重载,自引用等场合。

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,b_date(2014,3,25);   //a_date是无参类型,b_date是带参的类型

    a_date.showDate();

    b_date.showDate();

    return 0;

}

则这个程序的输出为

0.0.0

2014.3.25

利用构造函数创建对象,有以下两种方式:

利用构造函数直接创建对象,其一般形式为

类名 对象名(实参表)

这里的类名与构造函数名相同

实参表是为构造函数提供的实际参数

比如 Date b_date(2014,3,25);

利用构造函数创建对象时,通过指针和new来实现,

语法为:

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

例如以下的程序:

intmain()

{Date*date 1;

date=newDate(1998,4,28);

cout<<”Date1 output 1:”<<endl;

date1->showDate();

deletedate 1;

return 0;

}

(2)构造函数初始化成员有两种方法

a.使用构造函数的函数体来进行初始化

b.使用构造函数的初始化列表进行初始化

a.coding

classDate

{intd,m,y;

public:

Date(intdd,int mm,int yy)

{d=dd;

m=mm;

y=yy;

}

Date(intdd,int mm)

{d=dd;

m=mm;

}

}

b.coding

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

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

初始化列表的形式:

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

ClassDate

{intd,m,y;

Public:

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

{}

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

{}

};

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

数据成员为常量

数据成员为引用类型

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

以下这个程序是数据成员为常量,数据成员为引用类型

#include<iostream>

usingnamespace std;

class A

{public:

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

{};

voiddisplay()

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

Private:

Intx,&rx;

constfloat pi;

};

intmain()

 {A aa(10);

aa.display();

return 0;

}

类成员初始化的顺序:也就是定义顺序

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

3)构造函数的重载

class Box

{public:Box();

Box(int,int,int);

Intvolume();

Private:

intheight,width,length;

};

Box::Box()

{height=10;

width=10;

length=10;

}

Box::Box(inth,int w,int l):height(h),width(w),length(l)

{}

Int Box::volume()

{returnwidth*length*height;}

intamin()

{Boxbox();

Cout<<”Thevolume is”<<box1.volume();

Boxbox2(12,30,25);

cout<<”Thevolume is”<<box2.volume();//如果出现这样的情况 Box box3(30,25)//这样的话 第三个数为10,缺哪个那个增添为10

return 0;

}

2)析构函数

析构函数是与构造函数相对应的,析构函数是在类名之前冠以一个波浪号“~”析构函数没有参数,也没有返回类型。析构函数在类对象作用域结束时自动调用。

 基本形式是

类名::~类名()

例如以下这个程序

#include<iostream>

usingnamespace std;

class test

{ private:

int num;

float f1;

public:

test();

test(int n,float f);

~test();

};

test::test()

{num,=0;

f1=0.0;

}

test::test(int n,float f)

{num=n;

f1=f;

}

test::~test()

{cout<<”destructor isactive”<<endl;

}

int main()

{text x;

Cout<<”Exiting main”<<end;

}

执行结果为:

Exiting main

destructor is active

则析构函数的调用发生在上面这个程序的最后一条输出语句之后,当对象的生存期结束时,程序调用析构函数,并回收占用内存。

#include <cstring>

#include <iostream>

 using namespace std;

class Student{

public:

         Student(intn, char * a_name, char s)  {

                   num=n;name=new char[strlen(a_name)+1];

                   strcpy(name,a_name);            sex=s;

                   cout<<"Constructorcalled."<<endl;

         }

         ~Student(){

                   cout<<"Destructorcalled."<<endl;

                   delete[]name;            //delete【】是释放连续空间的功能

         }

         voiddisplay()

         {cout<<name<<endl;cout<<num<<endl;cout<<sex<<endl;        }

private:

         intnum;          char *name; char sex;

};

6.复制构造函数

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

类名::类名(const 类名&引用名,);

为保证所引用对象不被修改,通常把引用参数说明为const参数例如:

class A

{public:

A(int);

A(consta&,int=1);

};

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

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

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

第一条语句创建对象a,调用一般的构造函数Aint)。第二条和第三条都调用了复制构造函数,复制对象a创建吧,复制对象b创建C,

复制构造函数的特点:

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

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

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

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

复制构造函数的调用,以下几种情况由系统自动调用:

声明语句中用类的一个已知对象初始化该类的另一个对象时。

当对象作为一个函数实参传递给函数的形参时,需要将实参对象去初始化形参对象时,需要调用复制构造函数。

当对象是函数的返回值时,由于需要生成一个临时对象作为函数返回结果,系统需要将临时对象的值初始化另一个对象,需要调用复制构造函数。

 

7.关于深复制和浅复制

1)浅复制

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

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

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

 

#include<iostream>

#include<string.h>

using namespacestd;

class Person

{

public:

       Person(char* name1,int a,double s);   

       voiddisplay(){cout<<name<<"\t"<<age<<"\t"<<salary<<endl;}

       ~Person(){delete name;}                      //析构函数的声明

Person::Person(char*name1,int a,double s)          

{

       name=new char[strlen(name1)+1];

       strcpy(name,name1);

       age=a;

       salary=s;

}

int main()

{

Person *P1=newPerson("WangWei ",8,3880); //调用构造函数创建对象P1

P1->display();

 Person P2(*P1);  //调用复制构造函数,用P1的数据初始化对象P2

        delete P1;

 P2.display();

return 0;

}

而输出的结果是

WangWei,8,3880

       8,3880

出现这种情况的原因是delete P1 “WangWei”的资源库删了,所以不会输出

那么就要考虑到深复制

2)深复制

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

定义:类名::类名(【const】类名&对象名);

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

代码:

Person::Person(const Person&Po)

{name=new char[strlen(Po.name)+1];

strcpy(name,Po.name);

age=Po.age;

salary=Po.salsry;

cout<<”ff”<<endl;

}

这样的话,程序的结果就变成了

WangWei 8 3880

ff

WangWei 8 3880

8.常成员(只读不写)

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

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

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

例如:

const int M;

void PrintStudent() const;

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 mian()

{const T_class t1(1,2);

T_class t2(3,4);

//t1.a=5;

//t2.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;

}

结果为 t1.a=1  t1.b=2

       T2.a=7  t2.b=8

常成员函数

格式:

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

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

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

9.静态成员

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

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

静态数据成员在定义或说明时前面加关键字static,如:

class A

{

     intn;

       static int s;

};

10.类的包含

在一个类中,使用另一个类的内容。但是遇到private时,没有访问权限。

包含的那个类是以对象的形式出现

包含类的初始化语法形式

构造函数(参数表):对象成员1(参数表),对象成员2(参数表)….

初始化顺序

先出示化包含的对象的成员,在初始化本身的成员

二,这一章的经典例题和心得总结

这一章到目前为止,就完整的写了Student这个系统

现在结合这个系统来总计我的心得

1.首先再写一个系统之前要知道自己要实现什么功能

需要哪些数据,也就是要考虑数据类,这是最基础的

比如,写Student类就要考虑 一个学生有哪些基本的信息

首先是姓名,用string类型的字符串即可解决

然后是学号,成绩,平均分,排名

好了,这就是数据类的全部成员了

那么先来定义这个类

 

class Student

{

    string name;

    int numb;

    int score[3];

    float average;

    int order;

    public:

    Student(int id,string na,intx,int y,int z):name(na),numb(id)

    {

        score[0]=x;

        score[1]=y;

        score[2]=z;

       average=(score[0]+score[1]+score[2])/3;

        order=1;

    }

    Student()

    {

        score[0]=score[1]=score[2]=0;

        average=0;

        order=1;

    }

    void display();

    int getNo(){return numb;}

    float getAverage(){returnaverage;}

    void setAverage(floatavg){average=avg;}

    void setOreder(int x){order=x;}

    string getName(){return name;}

    void setName(stringname){this->name=name;}

 

 

 

};

先抛开函数不看,那么就是定义了所分析的基本的数据成员,并且用列表的形式进行了初始化,这个过程中不要忘记了构造一个缺省的构造函数,因为有自定义的构造函数时,系统便不会给定义无参类型

下一步,就是要考虑要得到的对于数据成员进行的基本操作,也就是

得到数据 int getNo(){return numb;}

更改数据 void setName(string name){this->name=name;}

还有就是 要展示

void Student::display()

{

   cout<<numb<<"\t"<<name<<"\t"<<score[0]<<"\t"<<score[1]<<"\t"<<score[2]<<"\t"

   <<average<<"\t"<<order<<endl;

}

到此数据类的基本成员就已全部定义完毕

这时写一个主函数来一步步的调试

int main()

{

 

    Students(201718,"ff",89,89,78);

    //s.display();

    //cout<<s.getNo();

    //cout<<s.getAverage();

    //cout<<s.getName();

    //s.setName("分左右");

   // s.display();

   //s.setAverage(12.2);

   //s.display();

   //s.setOreder(5);

   //s.display();}

调试成功后即可

2.第二部就是要考虑操作类了

那么首先 对于学生有什么操作

应该有 输入学生的信息,查询学生的信息,更改学生的信息,还有把学生的信息进行一个排名打印

那么这是可以构造一个类来实现这些功能

class StudentOperation

{

    Student list[60];

    int n;

    public:

    StudentOperation():n(0){};

    void add();

    void sortlist();

    void deleteh();

    void display(int flag);

    int search(int no);

    void query();

    void change();

 

 

 

 

 

};

由上一步的数据类得到 student类,可以构造一个对象数组

那么先来考虑 如何增添学生的信息

void StudentOperation::add()

 {

     int no,i,x,y,z;

     string name;

     system("cls");

     cout<<"请按照学生学号,姓名,数学,英语,c++成绩的顺序输入学生信息,按-1结束"<<endl;

    while((cin>>no)&&no!=-1)

     {

        cin>>name>>x>>y>>z;

         Student s(no,name,x,y,z);//对于对象数组的初始化

         list[n++]=s;

         sortlist();//这一步的功能是及时的对学生的信息进行一个排名

     }

 }

 那么接着就是排名了,排名的方式有两种,一种是按照学号的方式,另一种是按照成绩的方式

bool cmp1(Student stu1,Student stu2)

{

   if(stu1.getAverage()-stu2.getAverage()>=1e-9) return 1;

    else return 0;

}

bool cmp2(Student stu1,Student stu2)

{

    returnstu1.getNo()>stu2.getNo();

}

void StudentOperation::sortlist()

{

    sort(list,list+n,cmp1);

    int i;

    for(i=0;i<n;i++)

    list[i].setOreder(i+1);

}

以下的是展示的功能

void StudentOperation::display(int flag)

{

    cout<<"学号"<<"\t"<<"姓名"<<"\t"<<"数学"<<"\t"<<"英语"<<"\t"<<"c++"<<"\t"<<"平均分"

   <<"\t"<<"排名"<<endl;

    if(flag)

    {

        sort(list,list+n,cmp2);

        int i;

        for(i=0;i<n;i++)

        list[i].display();

    }

    else

    {

        sort(list,list+n,cmp1);

        int i;

        for(i=0;i<n;i++)

        list[i].display();

    }

}

查询和更改的功能

int StudentOperation::search(int no)

{

    int i;

    for(i=0;i<n;i++)

    if(list[i].getNo()==no) returni;

     return -1;

}

void StudentOperation::query()

{

    int no,i;

    cout<<"请输入要查询学生的学号,按-1结束"<<endl;

   while((cin>>no)&&no!=-1)

    {

        i=search(no);

        if(i!=-1)

        {

            cout<<"学号"<<"\t"<<"姓名"<<"\t"<<"数学"<<"\t"<<"英语"<<"\t"<<"c++"

           <<"\t"<<"平均分"<<"\t"<<"排名"<<endl;

            list[i].display();

            cout<<"请输入要查询的学生学号,按-1结束"<<endl;

        }

        else cout<<"输入学号有误,请重输!"<<endl;

    }

}

 

void StudentOperation::change()

{

    int no,i;

    string name;

    cout<<"请输入要更改信息的学生的学号,按-1结束"<<endl;

   while(cin>>no&&no!=-1)

    {

        i=search(no);

        cout<<"学号"<<"\t"<<"姓名"<<"\t"<<"数学"<<"\t"<<"英语"<<"\t"<<"c++"<<"平均分"

        <<"\t"<<"排名"<<endl;

        list[i].display();

        cout<<"请输入要更改的名字"<<endl;

        cin>>name;

        list[i].setName(name);

        list[i].display();

        cout<<"请输入要更改信息的学生学号,按-1结束"<<endl;

    }

  cout<<"输入学生的学号有误,请重输!"<<endl;

}

最后加上主函数即可,那么完成一个系统的大致过程也即是如此,采用滚雪球的方法,一步步的考虑,即可完成

 

 

 

 

 

 

猜你喜欢

转载自blog.csdn.net/fzydlrb/article/details/80033254