1、【C++】类&对象/构造函数/拷贝构造函数/操作符重载/析构函数

一、C++类 & 对象

    C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核心特性,通常被称为用户定义的类型。

    类用于指定对象的形式,它包含了数据表示法和用于处理数据的方法。类中的数据和方法称为类的成员。函数在一个类中被称为类的成员。

1、定义C++ 类

    定义一个类,本质上是定义一个数据类型的蓝图这实际上并没有定义任何数据,但它定义了类的名称意味着什么,也就是说,它定义了类的对象包括了什么,以及可以在这个对象上执行哪些操作。

    类定义是以关键字 class 开头,后跟类的名称。类的主体是包含在一对花括号中。类定义后必须跟着一个分号或一个声明列表。例如,我们使用关键字 class 定义 Box 数据类型,如下所示:

class Box
{
   public:
      double length;   // 盒子的长度
      double breadth;  // 盒子的宽度
      double height;   // 盒子的高度
};

    关键字 public 确定了类成员的访问属性。在类对象作用域内,公共成员在类的外部是可访问的。您也可以指定类的成员为 private 或 protected。

2、定义C++对象

    类提供了对象的蓝图,所以基本上,对象是根据类来创建的。声明类的对象,就像声明基本类型的变量一样。下面的语句声明了类 Box 的两个对象:

 Box Box1;          // 声明 Box1,类型为 Box
 Box Box2;          // 声明 Box2,类型为 Box

    对象 Box1 和 Box2 都有它们各自的数据成员。

3、访问数据成员

    类的对象的公共数据成员可以使用直接成员访问运算符 (.) 来访问。为了更好地理解这些概念,让我们尝试一下下面的实例:

#include <iostream>
 
using namespace std;
 
class Box
{
   public:
      double length;   // 长度
      double breadth;  // 宽度
      double height;   // 高度
};
 
int main( )
{
   Box Box1;        // 声明 Box1,类型为 Box
   Box Box2;        // 声明 Box2,类型为 Box
   double volume = 0.0;     // 用于存储体积
 
   // box 1 详述
   Box1.height = 5.0; 
   Box1.length = 6.0; 
   Box1.breadth = 7.0;
 
   // box 2 详述
   Box2.height = 10.0;
   Box2.length = 12.0;
   Box2.breadth = 13.0;
 
   // box 1 的体积
   volume = Box1.height * Box1.length * Box1.breadth;
   cout << "Box1 的体积:" << volume <<endl;
 
   // box 2 的体积
   volume = Box2.height * Box2.length * Box2.breadth;
   cout << "Box2 的体积:" << volume <<endl;
   return 0;
}

    需要注意的是,私有的成员和受保护的成员不能使用直接成员访问运算符 (.) 来直接访问。

4、类的成员函数

    类的成员函数是指那些把定义和原型写在类定义内部的函数,就像类定义中的其他变量一样。类的成员函数是类的一个成员,它可以操作类的任意对象,可以访问对象中的所有成员。

    让我们看看之前定义的类 Box,现在我们要使用成员函数来访问类的成员,而不是直接访问这些类的成员:

class Box
{
   public:
      double length;         // 长度
      double breadth;        // 宽度
      double height;         // 高度
      double getVolume(void);// 返回体积
};

    成员函数可以定义在类定义内部,或者单独使用范围解析运算符 :: 来定义在类定义中定义的成员函数把函数声明为内联的,即便没有使用 inline 标识符。所以可以按照如下方式定义 Volume() 函数:

class Box
{
   public:
      double length;      // 长度
      double breadth;     // 宽度
      double height;      // 高度
  	//这里的成员函数getVolume()是一个内联函数
      double getVolume(void)
      {
         return length * breadth * height;
      }
};

也可以在类的外部使用范围解析运算符 :: 定义该函数,如下所示:

double Box::getVolume(void)
{
    return length * breadth * height;
}

    在C++中建立一个类,这个类中肯定会包括构造函数析构函数拷贝构造函数重载赋值操作

二、类的构造函数

    类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。

    构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。

构造函数包括默认构造函数带参构造函数

1、默认构造函数

    默认构造函数没有返回值,也没有任何参数,下面是一个默认构造函数的实例:

class Line
{
   public:
      void setLength( double len );
      double getLength( void );
      Line();  // 这是构造函数
 
   private:
      double length;
};
 
// 构造函数定义
Line::Line(void)
{
    cout << "Object is being created" << endl;
}
2、带参构造函数

    带参构造函数可以理解为有参数的默认构造函数,这样在创建对象时就会给对象赋初始值,如下面的例子所示:

class Line
{
   public:
      void setLength( double len );
      double getLength( void );
      Line(double len);  // 这是带参构造函数
 
   private:
      double length;
};
 
// 带参构造函数定义
Line::Line( double len)
{
    cout << "Object is being created, length = " << len << endl;
    length = len;
}
3、使用初始化列表来初始化字段

使用初始化列表来初始化字段:

Line::Line( double len): length(len)
{
    cout << "Object is being created, length = " << len << endl;
}

上面的语法等同于如下语法:

Line::Line( double len)
{
    length = len;
    cout << "Object is being created, length = " << len << endl;
}

    假设有一个类 C,具有多个字段 X、Y、Z 等需要进行初始化,同理地,您可以使用上面的语法,只需要在不同的字段使用逗号进行分隔,如下所示:

C::C( double a, double b, double c): X(a), Y(b), Z(c)
{
  ....
}

初始化列表初始化和构造函数初始化的区别:

    (1)初始化列表初始化,是对类的成员进行显式的初始化,而构造函数初始化是对累的成员进行赋值;

    (2)对于内置类型的成员变量,使用初始化列表进行初始化和使用构造函数初始化,在性能和结果上都是一样的

    (3)对与非内置类型的成员变量,因为类类型的数据成员的数据成员对象,在进入函数体之前就已经构造完成,也就是说在成员初始化列表处进行构造对象的工作,调用构造函数,在进入函数体之后,进行的是对已经构造好的类对象的赋值,又**调用一个赋值操作符才能完成(**如果并未提供,则使用编译器提供的默认成员赋值行为)。为了避免两次构造,推荐使用类构造函数初始化列表。

    但有很多场合必须使用带有初始化列表的构造函数。例如,成员类型是没有默认构造函数的类,若没有提供显示初始化时,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试调用默认构造函数将会失败。再例如const成员或者引用类型的成员,因为const对象或引用类型只能初始化,不能对它们进行赋值。

    C++基本内置类型包括算术类型空类型算数类型包括整型(整数、字符、布尔)和浮点型。空类型指void类型

三、类的拷贝构造函数

1、拷贝构造函数定义

    拷贝构造函数是一种特殊的构造函数,其作用也是为类的成员初始化以及为对象的构造分配存储空间。函数的名称必须和类名称一致,无返回类型它的唯一的一个参数是本类型的一个引用变量,该参数是const类型,不可变。

拷贝构造函数原型如下:

	class_name(const class_name &src);

对于一个类X, 如果一个构造函数的第一个参数是下列之一:

   & X;
   const & X;
   volatile & X;
   const volatile & X;

且没有其他参数或其他参数都有默认值那么这个函数是拷贝构造函数,如下:

    X::X(const & X);   
    X::X(& X, int=1); 
    X::X(& X, int a=1, int b=2);
2、拷贝构造函数实现
class Date 
{ 
public: 
    Date(int year = 1995,int month = 12,int day = 8) // 带参构造函数 
        :_year(year) , _month(month) , _day(day) //初始化列表初始化
    { 
        cout << "date()构造函数"<< this << endl; 
    } 
    Date(const Date& d)// 拷贝构造函数 
        :_year(d._year) , _month(d._month) , _day(d._day) 
    { 
        cout << "date(&d)" << this << endl; 
    }
private: 
    int _year; 
    int _month; 
    int _day; 
};

四、类的赋值运算符重载

1、重载赋值运算符的定义

    重载赋值操作符是一个特别的赋值运算符,通常是用来把已存在的对象赋值给其它相同类型的对象。

重载赋值操作符的原型如下:

class_name& operator=(const class_name &src);
2、重载赋值运算符的实现
Date& Date::operator=(const Date& d) // 赋值运算符重载 
{
	cout << "operator= 赋值运算符重载 " << this << endl;
	if (this != &d) // 判断是否自己对自己赋值
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	return *this;
}

注意:重载赋值运算符的返回值是一个引用。

3、拷贝构造函数和重载赋值运算符的调用

    当类的对象需要拷贝时,复制构造函数将会被调用。以下情况都会调用复制构造函数:
  一个对象以值传递的方式传入函数体;
  一个对象以值传递的方式从函数返回;
  一个对象需要通过另外一个对象进行初始化。

    如果对象在声明的同时将另一个已存在的对象赋给它,就会调用拷贝构造函数;如果对象已经存在了,然后再将另一个已存在的对象赋给它,调用的就是重载赋值运算符

     //假设CTest是一个用户自定义类
     CTest obj;
     CTest obj1(obj); // 调用复制构造函数
     obj1 = obj; // 调用重载赋值操作符
4、深拷贝(deepcopy)与浅拷贝(shallowcopy)

    如果在类中没有显式地声明,那么编译器会自动生成默认的复制构造函数和重载赋值操作符。默认的复制构造函数和赋值运算符进行的都是“shallow copy”,只是简单地复制字段,把值一一赋给要拷贝的值。因此如果对象中含有动态分配的内存,就需要我们自己重写复制构造函数和重载赋值操作符来实现“deep copy”,确保数据的完整性和安全性。

    例如:类内成员变量需要动态开辟堆内存,如果实行浅拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。

    深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源(堆,或者是其它系统资源),当这个类的对象发生复制过程的时候,资源重新分配,使对象拥有不同的资源,但资源的内容是一样的,这个过程就是深拷贝;反之,没有重新分配资源,两个对象就有用共同的资源,同时对资源可以访问,就是浅拷贝。
    浅拷贝,只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。

五、类的析构函数

    类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。
    析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
    下面的实例有助于更好地理解析构函数的概念:

class Line
{
   public:
      void setLength( double len );
      double getLength( void );
      Line();   // 构造函数声明
      ~Line();  // 析构函数声明
 
   private:
      double length;
};
 
//构造函数定义
Line::Line(void)
{
    cout << "Object is being created" << endl;
}
//析构函数定义
Line::~Line(void)
{
    cout << "Object is being deleted" << endl;
}

    三法则假如类型有明显地定义下列其中一个成员函数,那么程序员必须连其他二个成员函数也一同编写至类型内,亦即下列三个成员函数缺一不可:

    (1)析构函数(Destructor)

    (2)拷贝构造函数(copy constructor)

    (3)重载赋值运算符(copy assignment operator)

猜你喜欢

转载自blog.csdn.net/sinat_33924041/article/details/83621017