类和对象:类,对象,构造函数,析构函数和重载函数

本文详细介绍了C++中类和对象的基础概念,包括访问权限、实例化、构造函数(普通和拷贝)、析构函数的作用以及运算符重载,特别是对复制构造函数和流操作符的特殊处理。

一丶类和对象

1.引入

         C++相较于C语言引入了类的概念,类和结构体看起来很像,但实际应用大不相同。

class A{   //class + 类名    
   //...
   //类体
};

       以上我们创建了一个A类型,其中包含三种访问权限:public,共有的,可以被外部访问;protect,受保护的,不能被外部访问,在继承那块内容使用较多;private,私有的,不能被外部访问。这样的A类型就是一个类,接下来我会用一个Date类来讲解有关内容。

2.实例

class Date
{
public:
	// 获取某年某月的天数
	int GetMonthDay(int year, int month); //可以在类的内部定义 也可以在外部定义
   /* 
      Date()    //普通构造函数
      {
          //...
      }  
   */

	// 全缺省的构造函数
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

(1).实例化

        创建对象与结构体很像,但C++不再依赖typedef 和 struct创建一个对象,可以直接Date d;

(2).访问

        访问一个对象的成员,如果是实例对象,就用“ . ” 访问,比如 d._year (私有不可直接访问,这里举个例子),如果是指针,就用“ -> ”访问,Date *ptr = &d, ptr->_year,如果是引用也用“ . ”。

(3).大小

       结构体用sizeof可以计算出它的大小,同样我们用sizeof(Date)可以计算出Date类的大小,如果是像我上面那样的话,结果应该是12,这里就有疑问了,因为Date类不是还有函数吗?函数

难道不占空间吗?实际上一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐,

然后在一个公共代码区存放成员函数。

(4).对齐规则

     ①. 第一个成员在与结构体偏移量为0的地址处。
     ②. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
     注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
     VS中默认的对齐数为8
     ③. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
     ④. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

(5).this指针

void Init(int year, int month, int day)   //两个都是类成员函数
 {
 _year = year;
 _month = month;
 _day = day;
 }
 void Print()
 {
 cout <<_year<< "-" <<_month << "-"<< _day <<endl;
 }

       在对象d1 和 d2 都调用 Init 和 Print两个函数时,根据函数实现的内容来看,函数体中没有关于不同对象的区分,但能准确初始化和输出d1和d2。是因为每个对象都有一个隐藏的this指针,这个指针只能在自己的对象内使用,指针指向的就是这个对象的地址。C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。

二丶构造函数和析构函数

1.构造函数

        构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。在下面代码中,我展示了两个构造函数,是可以同时存在的,这属于构造函数的重载,后面的内容会讲解,如果不写构造函数,编译器会自动调用默认构造函数,默认构造函数什么也不会做。如果我们 Date d1;这样创建一个对象,就会调用第一个构造函数,如果 Date d2(2024,1,31);编译器会调用第二个构造函数。

class Date
{
public:
	// 获取某年某月的天数
	int GetMonthDay(int year, int month); //可以在类的内部定义 也可以在外部定义
    Date()    //普通构造函数
    {
          _year = 1900;
          _month = 1;
          _day = 1;
    }  
    Date(int year, int month, int day)
    {
           _year = year;
          _month = month;
          _day = day;
    }
private:
	int _year;
	int _month;
	int _day;
};

2.拷贝构造函数

class Date
{
public:
	// 获取某年某月的天数
	int GetMonthDay(int year, int month); //可以在类的内部定义 也可以在外部定义
    Date()    //普通构造函数
    {
          _year = 1900;
          _month = 1;
          _day = 1;
    }  
    Date(int year, int month, int day)
    {
           _year = year;
          _month = month;
          _day = day;
    } 
    Date(const Date &d)
    {
         _year = d._year;
         _month = d._month;
         _day = d._day;
    }
private:
	int _year;
	int _month;
	int _day;
};
//...
    Date d1;
    Date d2(d1);     //拷贝构造函数
    Date d3 = d2;    //拷贝构造函数



       上述代码就实现了拷贝构造函数,是一种特殊的构造函数,用基于同一类的一个对象构造和初始化另一个对象。其中d2 和 d3 实际上是不存在的,都是用已有的对象来初始化,这个过程终究是调用的构造函数来初始化对象。当然这里有一个疑点就是最后一行代码,待会再说。

3.析构函数

       析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。形式如下。析构函数只能有一个,无参数无返回类型,不能重载。申请了空间最好还是写析构函数。

~Date()
{
    cout<<"析构函数"<<endl; //这里要做什么随意
}

三丶重载函数

      上面已经提到了构造函数的重载,接下来我们重点讲解运算符重载。我们知道两个整型变量a 和 b可以直接相加,但如果我想Date 类型加上一个整型改变时间,可以写这样一个函数,但每次调用略显麻烦,于是我们有了另外一种方法。

void AddDate(Date &d, int day)
{
     //...这里有部分代码省略了,假如_day 是公有的
     d._day += day;
}

       第二种办法,这样就可以 d1 + 10,天数加10天,这样实际是  d1.operator+(10),也是调用了一个函数,但要简单太多了,除了 + ,还有其他运算符都可以重载。

其中不能被重载的运算符有:
1.作用域操作符: ::
2.条件操作符: ?:
3.点操作符: .
4.指向成员操作的指针操作符:->* , . *
5.预处理符号:#

class Date
{
public:
	// 获取某年某月的天数
	int GetMonthDay(int year, int month); //可以在类的内部定义 也可以在外部定义
    Date()    //普通构造函数
    {
          _year = 1900;
          _month = 1;
          _day = 1;
    }  
    Date(int year, int month, int day)
    {
           _year = year;
          _month = month;
          _day = day;
    } 
    Date(const Date &d)
    {
         _year = d._year;
         _month = d._month;
         _day = d._day;
    }

    //日期加天数
    Date operator+(int day);
    //可以在类内定义


private:
	int _year;
	int _month;
	int _day;
};
//类外定义 :: 前面提到的域作用符
//   返回类型 类名::函数名()
Date Date::operator+(int day)
{
	//...
}

       大多数运算符重载都是如此,有两个目前来说是特殊的就是流提取 >> 和 流插入 <<,以及++和--运算符。这里我就直接以代码的形式展现了。

class Date
{
public:
	// 获取某年某月的天数
	int GetMonthDay(int year, int month); //可以在类的内部定义 也可以在外部定义
    Date()    //普通构造函数
    {
          _year = 1900;
          _month = 1;
          _day = 1;
    }  
    Date(int year, int month, int day)
    {
           _year = year;
          _month = month;
          _day = day;
    } 
    Date(const Date &d)
    {
         _year = d._year;
         _month = d._month;
         _day = d._day;
    }

    //日期加天数
    Date operator+(int day);
    //可以在类内定义



	// 前置++
	Date& operator++() 
    {
	    *this += 1;  //假设 +=以及完成重载
	    return *this;
    }
	// 后置++
	Date operator++(int)  //这个int没用但是必须写,为了区分,别问为什么
    {
	    Date tmp = *this;
	    *this += 1;
	    return tmp;
    }
    friend ostream& operator<<(ostream& out,const Date d)  //这是友元类
    {
	    out << d._year << ":" << d._month << ":" << d._day << endl;
	    return out;
    }
    friend istream& operator>>(ostream& in,const Date d);  

private:
	int _year;
	int _month;
	int _day;
};
//类外定义 :: 前面提到的域作用符
//   返回类型 类名::函数名()
Date Date::operator+(int day)
{
	//...
}

       由于 << 和 >> 是二元运算符,它们需要两个操作数:一个左操作数和一个右操作数。这意味着它们的重载版本需要两个参数:一个表示流对象(如 std::ostream 或 std::istream),另一个表示我们想要插入或提取的自定义对象。如果我们将它们定义为类的成员函数,那么它们将只有一个隐式的 this 参数来表示类的实例,而缺少另一个参数来表示流对象。因此,为了使 << 和 >> 运算符能够与自定义类型一起使用,我们需要将它们定义为类的友元函数。这样,它们就可以访问类的私有和保护成员,并且可以接受两个参数:一个流对象和一个自定义对象。

       为了能够连续输入或者输出,我们在函数定义的返回的是ostream 或 istream 类型的对象,

比如(cout << d1 )<< d2,这里cout << d1 是 ostrea( cout , d1) 然后返回cout ,然后就是 cout << d2。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值