类与对象

C++学习笔记(二)

Posted by Chen Jinhao on July 13, 2021

类与对象

1.类的声明

以时钟类为例

class Clock
{
    int Hour,Minute,Second;
    void SetTime(int h,int m,int s);
    void ShowTime();
};

实现类的访问控制

数据封装的目的是信息隐蔽,通过设置成员的访问控制属性来实现对类成员的访问控制,这些控制属性有:public(公有成员)、protected(保护成员)、private(私有成员),所以可以对之前的程序做出以下修改:

class Clock
{
    private:
    int Hour,Minute,Second;
    public:
    void SetTime(int h,int m,int s);
    void ShowTime();
};
2.类的实现
class Clock
{
    private:
    int Hour,Minute,Second;
    public:
    void SetTime(int h,int m,int s)
    {
        Hour=h;
        Minute=m;
        Second=s;
	}
    void ShowTime()
    {
        cout<<"Current Time:";
        cout<<Hour<<":"<<Minute<<":"<<Second<<endl;
	}
};//即将函数声明写在类里面
/*当然,也可以不改变原来的结构,将函数声明写在类的外部*/
class Clock
{
    private:
    int Hour,Minute,Second;
    public:
    void SetTime(int h,int m,int s);
    void ShowTime();
};
void Clock::SetTime(int h,int m,int s)
    {
        Hour=h;
        Minute=m;
        Second=s;
	}
void Clock::ShowTime()
    {
        cout<<"Current Time:";
        cout<<Hour<<":"<<Minute<<":"<<Second<<endl;
	}
3.实例分析

数组类的实现:赋值,求最大最小值;

#include <bits/stdc++.h>
using namespace std;
class IntArray
{
    int *data;
    int size=10;
public:
    void setArray(int len);
    int getSize();
    int SetVal(int pos, int val);
    int getMaxVal();
    int getMinVal();
};
void IntArray::setArray(int len)
{
    size=len;
    data=new int[size];
}
int IntArray::getSize()
{
    return size;
}
int IntArray::SetVal(int pos, int val)
{
    if ((pos <= 0) || (pos > size))
        return -1;
    data[pos] = val;
    return 0;
}
int IntArray::getMaxVal()
{
    int temp = data[1];
    for (int i = 1; i <= size; i++)
    {
        if (data[i] > temp)
            temp = data[i];
    }
    return temp;
}
int IntArray::getMinVal()
{
    int temp = data[1];
    for (int i = 1; i <= size; i++)
    {
        if (data[i] < temp)
            temp = data[i];
    }
    return temp;
}
int main()
{
    IntArray array;
    int size,value,max,min;
    size=array.getSize();
    cout<<"please input "<<size<<" members"<<endl;
    array.setArray(10);
    for(int i=1;i<=size;i++)
    {
        cin>>value;
        array.SetVal(i,value);
    }
    max=array.getMaxVal();
    min=array.getMinVal();
    cout<<max<<endl;
    cout<<min<<endl;
    return 0;
}
4.对象引用

形式:

CLock C1(8,20,20);
Clock &Cr=C1;
Cr.showTime();

对象引用通常用作函数的参数,它不仅有对象指针的优点,而且比对象指针更简洁,更方便,更直观。

5.构造函数
  • 构造函数是类的一种特殊的成员函数,本质上也是类的成员函数;
  • 函数名和类名相同,可以有参数,但没有返回类型;
  • 当创建类的一个新对象时,构造函数被自动调用,完成对象的初始化工作;

实现构造函数的方式:1.赋值语句的方式

class Clock
{
    int Hour,Minute,Second;
    public:
    Clock(int h,int m,int s)
    {
        Hour=h;
        Minute=m;
        Second=s;
	}
};

2.表达式表的方式

class Clock
{
    int Hour,Minute,Second;
    public:
    Clock(int h,int m,int s):Hour(h),Minute(m),Second(s)
    {
	}
};

重载构造函数

  • 一个类可以提供多个构造函数,即构造函数的重载;

  • 重载的目的是为了满足不同的初始化需求

    class Clock
    {
    	private:int Hour,Minute,Second;
        public:
        Clock(int j,int m,int s);
        Clock();
        Clock(char *timestr);
    };
    void main()
    {
    	Clock clock1(23,12,0);
        Clock clock2();
        Clock clock3("14:45:32");
    }
    
6.析构函数
  • 与构造函数相对的是析构函数,C++通过析构函数来处理对象被销毁时的清理工作;
  • 析构函数没有返回类型,没有参数,函数名是在类名前加“~”,析构函数会在对象的生存期结束后被自动调用;
class CString
{
    private: int len;
    char *buf;
    public:CString(int n);
    ~CString();
    void copy(char *src);
};
CString::CString(int n)
{
    len=n;
    buf=new char[n];
}
void CString::copy(char *src)
{
    strcpy(buf,src);
}
CString::~CString()
{
    delete []buf;
}
viod func()
{
    CString obj(64);
    obj.copy("helloworld");
}
void main()
{
    func();
}

动态内存管理容易出错

  1. 删除动态内存失败

  2. 读写已删除的对象

  3. 对同一个内存空间使用两次delete:(1)2个及以上的指针指向同一个动态分配的对象;

    ​ (2)一个指针,多次删除

7.拷贝构造函数

如果将与自己同类的对象的引用作为参数进行构造函数的初始化时,该构造函数就称为拷贝构造函数,它将一个已经创建好的对象作为参数,根据需要将该对象中的数据成员逐一对应地赋值给新对象,示例如下:

class Point
{
    private:
    float x,y;
    public:
    Point(float a,float b)
    {
        x=a;y=b;
	}
    Point(Point &obj)
    {
        x=obj.x;
        y=obj.y;
	}
}
void main()
{
    Point obj1(5,15);
    Point obj2(obj1);
    Point obj3=obj2;//两种调用拷贝构造函数的方式
}

缺省的拷贝构造函数使用位拷贝的方法来完成对象到对象的复制(即完全相同)

使用缺省拷贝构造函数的缺点:如果使用指针,拷贝后两个类的指针指向同一个空间,就会出现析构时同一块内存空间delete两次的错误

故:通过自定义拷贝构造函数可以准确地复制数据,以免发生错误

8.静态数据成员的定义和初始化

定义静态数据成员

class ABCD//定义静态数据成员
{
    int value;
    public:
    static int s_value;//在类内声明静态数据成员
};
int ABCD::s_value=6;//在类外定义静态数据成员,在定义时给初值

静态数据成员属于类而不属于对象,所以无论建立多少个该类的对象,静态数据成员只有一份拷贝

静态成员函数类似

静态成员的应用:如果有一个类MyClass,利用静态成员可以实现程序运行过程中只能有一个实例;

class MyClass
{
    private:
    static MyClass *instance;
    MyClass(){}//将构造函数声明为非public属性,可以控制对象的生成
    public:
    static MyClass *getInstance()
    {
        if(instance==NULL)
        {
            instance=new MyClass();
		}
        return instance;
	}
};
MyClass *MyClass::instance=NULL;
void main()//设计模式——单件模式
{
    MyClass *obj1,*obj2;
    obj1=MyClass::getInstance();
    obj2=MyClass::getInstance();//两次调用,只有一个实例
}
9.友元

(1)友元函数

  • 友元函数可以直接访问私有成员
  • 友元函数的声明可以放在类内的任意位置,且和普通函数一样可以直接调用友元函数
  • 友元函数不属于任何类,因此没有this指针
#include <bits/stdc++.h>
using namespace std;
class Point
{
    private:
    int x,y;
    public:
    Point(float a,float b):x(a),y(b){}
    friend float Distance(Point a,Point b);//类内定义:前面加上friend
};
float Distance(Point a,Point b)//若在类外声明,声明方式与普通函数相同
{
    float dx,dy;
    dx=a.x-b.x;//在友元函数内可以直接访问类的私有成员
    dy=a.y-b.y;
    return sqrt(dx*dx+dy*dy);
}
int main()
{
    Point p1(3.0,5.0);
    Point p2(4.0,6.0);
    float d=Distance(p1,p2);//调用方式与普通函数相同
    cout<<d<<endl;
    return 0;
}

(2)友元类:

1.除了将一个普通函数声明为一个类的友元函数外,也可以将一个类Y声明为另一个类X的友元类;

2.友元类的特点:类Y中的所有成员函数都成为类X的友元函数,都能直接访问类X中的所有成员。

#include <bits/stdc++.h>
using namespace std;
class Y;//向前说明
class X
{
    int x;
    friend class Y;
    public:
    void show()
    {
        cout<<x;
    }
};
class Y
{
    public:
    void SetX(X &obj,int v)//Y中的成员函数可以自由访问X中的所有成员
    {
        obj.x=v;
    }
};

int main()
{
    X xobj;
    Y yobj;
    yobj.SetX(xobj,5);
    xobj.show();//输出5
    return 0;
}

(3)将成员函数说明为另一个类的友元函数

class X;//向前说明
class Y
{
	public:
    void SetX(X &obj,int v);
    void func(X &obj){};
};
Class X
{
    friend void Y::SetX(X &obj,int v);
};
void Y::SetX(X &obj,int v)
{
    obj.x=v;
}
10.类的组合

实例:Circle的实现

#include <bits/stdc++.h>
using namespace std;
class Point
{
    float x,y;
    public:
    Point(float xx,float yy)
    {
        x=xx;y=yy;
    }
    float GetX(){return x;}
    float GetY(){return y;}
    void moveto(float xx,float yy)
    {
        x=xx;
        y=yy;
    }
};
class Circle
{
    Point center;
    float radius;
    public:
    Circle(float x,float y,float r):center(x,y)//下面详细说明
    {
        radius=r;
    }
    void moveto(float xx,float yy)
    {
        center.moveto(xx,yy);
    }
};
int main()
{
    Circle acircle(0,0,5);
    acircle.moveto(5,8);
    return 0;
}
//注释处:若子对象对应的类的构造函数有参数,那么包含该子对象的类必须使用表达式的方式先初始化子对象