三.类专题总结

不知不觉已经将近把类学完,最近几天在整理笔记的时候才发现学了很多东西。

很喜欢一句话:你可以很短时间内把知识点记熟,却不能很短时间内提升代码能力。

在类的学习中,这句话更是体现的淋漓尽致。类的知识点是琐碎的,而且很多,当然也要把知识点记熟,然而运用知识点写出具体程序才是最好记熟知识点的方式。

类中通常包含数据类,而数据类可以有多个,而操作类目前写的程序一个就够了。操作类和数据类分开写条理分明,便于理解。

在写多个类时,应该一个一个写,而且写完要在主函数内调试一下再写下一个类。这样可以保证错误尽少地出现,而且便于程序的调试。

当一个类包含另一个类时,应该注意其包含方式及初始化方式。

只有多写程序,多练才可以把面向对象编程学好。


一.类的定义

类要用关键词class,类似与结构体的struct。

//类的定义格式

class student(类名)

{

public://一般情况下public内为函数,是对外界的接口,外界调用函数来对数据进行操作;

......;

private://类中数据成员私有化,为了保证程序封装性,以及不被外界操作(外界通过函数对数据操作),数据通常定义为私有;

......;

protected:

......//内容通常为受保护的函数和数据成员;

};//类的最后一定要加分号结束!

//类的定义举例

class point

{

private:

int x,y;//不能直接int x=0,y=0;

public:

void setX(int a){x=a;};//分号结束

void setY(int b){y=b;};

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

};//分号一定要有

二.数据成员初始化

 需要注意,不可直接对数据成员初始化。后面的构造函数和析构函数会作进一步补充

例如


正确的初始化方式:

class point 

{

private:

int x,int y;

public:

point(int a,int b):x(a),y(b){}//这样把形参a传给x,把形参b传给y;达到赋值的目的;

//再定义一个无参类型

point()

{x=0;

y=0;

}//同样达到对成员赋予初值的目的;///这时的函数名和类名相同;

三.类中函数的写法;

1.

class point

{

private:

int x,y;//不能直接int x=0,y=0;

public:

void setX(int a);//先在类中声明 格式为     返回值类型+函数名+(形参)+分号//然后在类外进行函数原型写入

void setY(int b);

void show();

};//分号一定要有

inline void point::setX(int a)//在类外,函数的写法为   返回值类型+类名+::+函数名+(形参)

x=a;

}

void point::setY(int b)//返回值类型+类名+::+函数名+(形参)

{

y=b;

}

void point::show()

{

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

}

2.内联函数

格式:inline+函数原型

作用:提高效率

但仅适用与小程序,不可递归调用;

例如:

//内联函数写法   inline+返回值类型+类名+::+函数名+(形参)

//一个小程序总结一下

#include<bits/stdc++.h>
using namespace std;
class rect//定义类
{
    private:
    int x,y;
    public:
    int set(int a,int b){x=a;y=b;};
    void area();//函数声明
    void perimeter(){cout<<2*(x+y)<<endl;};
};
inline void rect::area()//inline 函数  类外写函数原型
{
    cout<<x*y<<endl;
}
int main()
{
    rect rect1;
    rect1.set(5,5);
    rect1.area();

}

3.重载函数


四.对象生成

类是包含一类问题,对象是一个具体的东西;创建类后,需要定义具体对象;

对象的定义方法为


类名+对象名+分号

例如:rect    rectssssss;

定义完对象后,就要考虑怎样操作对象中的函数成员,调用函数成员常见的方式有

1.

对象名+.+函数名+(形参)

例如 :rect1.set(5,5);

2.指针方法

int main()
{
    rect *plus=new rect;//new运算符,开辟一个空间,把空间地址初始值赋予给指针变量plus;
    plus->set(5,5);
    plus->area();//指针变量+      ->     +函数名();//通过这种方式调用函数
}

或者

(*plus).set(6,6);

    (*plus).perimeter();

与上个指针方法调用函数效果相同;

格式为(*指针变量名)+.+函数名(形参)

五.构造函数和析构函数

1.构造函数

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

构造函数为对象分配空间,为数据成员赋予初值;

构造函数与类名相同;

若没定义构造函数,系统自动生成一个默认形式的构造函数   类名::类名(){}

例如:

#include<bits/stdc++.h>
using namespace std;
class date
{
    int year,month,day;
    public:
    date();//无参构造函数
    date(int a,int b,int c);//有参构造函数
    void show()
    {
        cout<<year<<endl;
    }
};
date::date()//类名+::+类名+()
{
    year=0;
    month=0;
    day=0;
}
date::date(int a,int b,int c)//括号内为形参
{
    year=a;
    month=b;
    day=c;//具体在花括号内赋值
}
int main()
{
    date d1;
    d1.show();//自动调用无参函数
    date d2(5,5,5);
    d2.show();

}

2.利用构造函数创建对象

第一种方式:

类名+对象名+(实参名)

例如上面程序的  date d2(5,5,5)

第二种方式 :

利用指针方式

date *p;

    p=new date(5,5,5);//指针变量名=new 类名 实参表

//date *p=new date(5,5,5);与上述方法一样

    p->show();//或者(*p).show();

3.初始化列表方式

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

(在类外)date::date(int years,int months,int days)
{
    year=years;
    month=months;
    day=days;

}

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

(在类内)date(int years,int months,int days):year(years),month(months),day(days){此处内容看情况具体确定}

4.必须使用初始化列表进行数据成员初始化的情况

~1.数据成员为常量或引用数据

~2.数据对象为   没有无参构造函数的类的对象

例如:

#include<bits/stdc++.h>
using namespace std;
class A
{
    int s;
    public:
    A(int x):s(x){}
    void put(){cout<<s<<endl;};


};
class B
{
    A a;//对象
    int x,&y;//引用数据
    const float pi;//常数据成员
    public:
    B(int c):a(c),x(c),y(x),pi(3.14){}
    void show()
    {
        cout<<x<<endl<<y<<endl<<pi<<endl;
        a.put();
    }
};
int main()
{
    B b(1);
    b.show();
    return 0;

}

5.利用初始化列表初始化顺序

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;先初始化的是前者,而前者依靠m_y初始化,此时m_y还没有值,所以m_x是随机数,m_y后来是x的值;

6.构造函数的重载

#include<bits/stdc++.h>
using namespace std;
class rec
{
    private:
    int l,h,s;
    public:
    rec(int a,int b):l(a),h(b){cout<<l*h<<endl;}//有参构造函数
    rec()//无参构造函数
    {
        l=0;
        h=0;
        cout<<l*h<<endl;
    }
};
int main()
{
    rec s;//调用无参构造函数
    rec s1(6,6);//调用有参构造函数
    return 0;

}

7.析构函数

对象生存期结束时,需要做清理工作,比如:释放成员(指针)所占有的存储空间

析构函数可以完成上述工作。

析构函数不能重载,无返回值,不能有参数。

在类外实现析构函数:

class rec
{
    private:
    int l,h,s;
    public:
    ~rec();//类内声明析构函数///
    rec(int a,int b):l(a),h(b){cout<<l*h<<endl;}
    rec()
    {
        l=0;
        h=0;
        cout<<l*h<<endl;
    }
};
rec::~rec()//类外实现析构函数
{
   cout<<"aaaaa"<<endl;
}
int main()
{
    rec s;
    rec s1(6,6);
    return 0;

}

8.this指针

~1.在类的非静态成员函数中返回类对象本身或对象的引用的时候,直接使用 return *this,返回本对象的地址时,return this。

例如:public:
Person(string n, int a) {
name = n;    //这里的 name 等价于this->name
age = a;      //这里的 age 等价于this->age
}
int get_age(void) const{  return age;  }
Person& add_age(int i) {age += i;     return *this;   }//引用类型add_age(int i)
private:
string name;
int age;

主函数内
Person Li("Li", 20);//定义对象Li
cout<<"Li age = "<< Li.get_age()<<endl;//输出20
cout<<"Li add age = "<< Li.add_age(1).get_age()<<endl;  //输出20+1结果
//增加1岁的同时,可以对新的年龄直接输出;

~2.参数与成员变量名相同时,如this->x = x,不能写成x = x。

例如上面程序

Person(string name, int age) {
this->name = name;    //this->name
this->age = age;      //this->age

}

~3.避免对同一对象进行赋值操作,判断两个对象是否相同时,使用this指针

public:
     void init(int x,int y) { X =x; Y = y;};
     void assign(Location& pointer);//引用类型
     int GetX(){ return X; }
     int GetY(){ return Y; }
};
void Location::assign(Location& pointer)
{
    if(&pointer!=this) //同一对象之间的赋值没有意义,所以要保证pointer不等于this,保证新的对象成员X,Y赋值为pointer对象里的X,Y;
    {        X=pointer.X;        Y=pointer.Y;    }

}

主函数

Location x;
    x.init(5,4);
    Location y;
    y.assign(x);
    cout<<"x.X = "<< x.GetX()<<" x.Y = "<<x.GetY();

    cout<<"y.X = "<< y.GetX()<<" y.Y = "<<y.GetY();

输出5,4;

六.复制构造函数

1.复制构造函数用一个已有同类对象创建新对象进行数据初始化

格式为

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

const保护实参对象只读

如果不定义一个这样的函数,系统默认生成一个

复制构造函数名与类名相同。

例如:

Box(int =10, int =10, int =10);
Box(const Box & b)//用对象b 初始化新对象
{height=2*b.height; width=2*b.width;length=2*b.length;}
int volume();
private:

int length, height, width;

主函数:

Box box1(1,2,3),box2(box1),box3=box2;//结果box2,box3都被box1初始化;

2.深复制与浅复制

浅复制对于数据类型复杂的成员只复制了地址而没有复制内容;

默认复制构造函数只是简单的浅复制;

例如

class Person
{
public:
Person(char* name1,int a,double s);
void display(){cout<<name<<"\t"<<age<<"\t"<<salary<<endl;}
~Person(){delete name;}
private:
char* name;
int age;
double salary;
};
Person::Person(char* name1,int a,double s)
{
name=new char[strlen(name1)+1];//name接受其首地址,申请空间
strcpy(name,name1);//把 数组name1复制给name;
age=a;
salary=s;
}
int main()
{
Person *P1=new Person("WangWei ",8,3880); //调用构造函数创建对象P1
P1->display();
 Person P2(*P1);  //调用复制构造函数,用P1的数据初始化对象P2
delete P1;
 P2.display();
return 0;

}

但结果p2输出为乱码

因为删除p1地址后,原本俩对象公用的某些地址被删除;

同时复制地址和内容的为深复制///

格式为

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

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

例如上面程序可改为

class Person
{
public:
Person(char* name1,int a,double s);
Person(const Person& po);
void display(){cout<<name<<"\t"<<age<<"\t"<<salary<<endl;}
~Person(){delete name;}
private:
char* name;
int age;
double salary;
};
Person::Person(char* name1,int a,double s)
{
    name=new char[strlen(name1)+1];//name=new char [strlen(name1)+1];
strcpy(name,name1);
age=a;
salary=s;
}
Person::Person(const Person& P0) //复制构造函数的实现
{
    name=new char[strlen(P0.name)+1];
strcpy(name,P0.name);
age=P0.age;
salary=P0.salary;
cout<<"ff"<<endl;
}


int main()
{
Person *P1=new Person("WangWei ",8,3880); //调用构造函数创建对象P1
P1->display();
 Person P2(*P1);  //调用复制构造函数,用P1的数据初始化对象P2
delete P1;
 P2.display();
return 0;

}

3.常数据成员静态成员常对象常函数

长数据成员///

class Mclass

{ public :
      int k;
      const int M; //说明常数据成员
      Mclass() : M(5) { } //用初始式对常数据成员赋值,长数据成员只能用初始化列表方式初始化
      void testFun()
       { //M++; //错误,不能在成员函数中修改常数据成员
           k++; //可以修改一般数据成员
       }

 } ;

常对象//

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

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

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

而且  不允许直接或间接更改常对象的数据成员。

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

//常成员函数

类型说明符 函数名(参数表) const;
      const是函数类型的一个组成部分,因此在函数的实现部分也要带关键字const。 

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

例如:

 void  constFun ( ) const

         { x ++ ; y ++ ; }//非法,常成员函数不能更新数据成员值

4.静态成员

static声明静态成员,静态成员与静态成员函数协同操作,为同类对象共享

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

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

如果未进行初始化,则编译器自动赋初值(默认值是0)
初始化时不能使用访问权限

例如:

class  counter
{     static  int  num ;
  public :
      void  setnum ( int i ) { num = i ; }
      void  shownum() { cout << num << '\t' ; }

} ;

int  counter :: num = 0 ;//在类外声明值     返回值类型+类名+::+静态成员名+=值

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

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

对静态成员的引用不需要用对象名

5.静态成员函数

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

调用格式:

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

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

七.友元函数

如果在本类(类A)以外的其他地方定义了一个函数(函数B)
这个函数可以是不属于任何类的非成员函数,
也可以是其他类的成员函数,
在类体中用friend对其(函数B)进行声明,此函数就称为本类(类A)的友元函数。

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

例如:用友元函数计算点距离

#include<iostream>
using namespace std ;
#include<math.h>
class Point
{ public:
      Point(double xi, double yi) { X = xi ; Y = yi ;}
      double GetX() { return X ; }
      double GetY() { return Y ; }
      friend double Distance ( Point & a, Point & b ) ;
  private:  double X, Y ;

} ;

double Distance(Point & a, Point & b )
  { double dx = a.X - b.X ;
     double dy = a.Y - b.Y ;
     return sqrt ( dx * dx + dy * dy ) ;
  }
int main()
{ Point  p1( 3.0, 5.0 ) ,  p2( 4.0, 6.0 ) ;
  double  d = Distance ( p1, p2 ) ;
  cout << "This distance is " << d << endl ;

八.类的包含

类的包含在写软件时很好用,而且形式简单。

例如用类的包含写点的距离函数

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;

};

//定义距离类

再如,用一个类初始化另一个类

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;}//此处把i值传递给a2//

//private:
A a1,a2;  //声明两个A类对象a1和a2
};
int main( )
{
B b1, b2(9);   
return 0;

}

再例如学生类可以与成绩类有包含关系

class Student
{
private:
    string name;
    string stu_no;
    Score score1;
public:
    Student(string name1, string stu_no1, float s1, float s2, float s3):score1(s1,s2,s3){//score中的数据成员用s1,s2,s3初始化!!!!!!!!这样就比较方便
        name=name1;
         stu_no=stu_no1;

}

又如atm机中记录类可以与时间类包含




九.对象数组

格式:

类名 数组名[下标表达式];

例如:

int main(){
Point p1[4];//与单个元素不同之处
             p1[0]=Point(1,1);

}

例如统计多个学生成绩,在学生类中应该定义对象 student student1[100];

这样比较便于管理学生各种信息;

代码如下:

#include<bits/stdc++.h>
using namespace std;
class score
{
    float math,English,Chinese;
    float ave;
public:
    score(float a,float b,float c)
    {
        math=a;
        English=b;
        Chinese=c;
        ave=(a+b+c)/3;
    }
    score()
    {
        math=0;English=0;Chinese=0,ave=0;
    }
    void show();
};
void score::show()
{
    cout<<ave<<endl;
}
int main()
{
    score scoress[100];
    float a,b,c;
    for(int i=0;i<=2;i++)
    {
        cin>>a>>b>>c;
        scoress[i]={a,b,c};//赋值形式;
     scoress[i].show();
    }
    return 0;

}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值