类和对象

类

通过前面的结构体的学习。我们接下来要学习另外一个和结构有点类似的东西。这个东西是面向对象的核心。现在以后很多语言都有它。比如 java、python、c#。

这就是类。

所以大家学好类,那么对你以后学习编程有非常大的作用。

其实什么是类。其实语言的设计者为什么需要类和对象。其实他是模拟现实世界。

就是让我们的编程和现实世界更接近。

比如我们现实世界,要去北京。那么要买辆车。然后开车去北京。那么这里的车就是一个对象。

比如我们现在去饭店吃饭。我们不需要自己做饭。我们可以给钱厨师,然后厨师买菜、洗菜、做菜。这里厨师、菜都是一个对象。

我们现实世界就是一个充满了对象的世界。比如汽车、飞机、火车、人、学校。

我们编程也是一样,可以创建模拟一个个的对象。然后这些对象不是孤立的,它们和现实世界都是有关系的。

比如上面的吃饭,我要完成吃饭,需要钱、有厨师、菜。

C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。同时 C++ 也保留了面向过程,就是你可以不用类,也可以编程。

类是 C++ 的核心特性,通常被称为用户定义的类型。类其实就是类型,也就是用户自己定义的数据类型。我们上面定义 book 结构体也是一种数据类型,但是它不是类。类是使用 class 关键字。

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

C++ 类定义

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

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

class Box
{
   public:
      double length;   // 盒子的长度
      double breadth;  // 盒子的宽度
      double height;   // 盒子的高度
};Code language: PHP (php)

关键字 public 确定了类成员的访问属性。在类对象作用域内,公共成员在类的外部是可访问的。您也可以指定类的成员为 private 或 protected,这个我们稍后会进行讲解。

定义 C++ 对象

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

Box Box1;          // 声明 Box1,类型为 Box
Box Box2;          // 声明 Box2,类型为 BoxCode language: JavaScript (javascript)

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

#include <iostream>
#include <cstring>

using namespace std;
//类就是一个模板 通过这个模板
//可以创建很多对象 就是好比 人类是一个类   然后 小明是一个对象
class Box
{
   public:
      double length;   // 盒子的长度
      double breadth;  // 盒子的宽度
      double height;   // 盒子的高度
};

int main() {
  Box box1;
  Box box2;
  return 0;
}Code language: PHP (php)

类:是一个模板,是一个抽象的东西。是一个对这个世界的同一个类的东西抽象成一个概念、一个模板。

比如所有的人可以抽象成一个人类。所有的汽车可以抽象成一个汽车类。

对象:通过上面的类的这个模板,然后创建出具体的对象。比如小明、小强。黄老师的汽车。

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

类是一个抽象的概念,是不能具体看到的。比如你不能拿出一个人类,但是你可以拿出小明。你不能说人类的身高是多少,你只能说小明的身高。

抽象是提炼出来,形成一个概念。而对象是具体的。但是对象和类是有关系的,对象是通过类来得到。就好比类是一个模板,通过这个模板生产出一个个的对象出来。

#include <iostream>
#include <cstring>

using namespace std;
//类就是一个模板 通过这个模板
//可以创建很多对象 就是好比 人类是一个类   然后 小明是一个对象
class Box
{
   public:
      double length;   // 盒子的长度
      double breadth;  // 盒子的宽度
      double height;   // 盒子的高度
};

int main() {
  Box box1;
  Box box2;

  box1.breadth = 3.3;
  box1.height = 3.5;
  box1.length = 5.6;

  box2.breadth = 5.3;
  box2.height = 3.7;
  box2.length = 9.6;

  cout << "box1的体积:" << box1.breadth * box1.height * box1.length << endl;
  cout << "box2的体积:" << box2.breadth * box2.height * box2.length << endl;

  //cout << "Box的体积:" << Box.breadth * Box.height * Box.length << endl;

  return 0;
}Code language: PHP (php)

需要注意的是,私有的成员和受保护的成员不能使用直接成员访问运算符 (.) 来直接访问。我们将在后续的教程中学习如何访问私有成员和受保护的成员。

类成员函数

类的成员函数是指那些把定义和原型写在类定义内部的函数,就像类定义中的其他变量一样。

类成员函数是类的一个成员,它可以操作类的任意对象,可以访问对象中的所有成员。

属于类的里面的函数,跟我们以前的函数不一样。以前的函数是不属于任何的类的,现在定义的是属于类里面的函数。

其实这个也很好理解。比如一个人,小明,人是属于类,小明是属于对象。那么小明有自己的特征属性,比如小明的身高、体重、国籍这些,通常我们可以用数据成员,比如长宽高。

但是小明除了有自己的特征属性,还有他的能力。小明可以走路、可以唱歌、可以吃饭。这些是动作来的。我们可以用函数来表示小明的这些能力,而不能使用变量来表示。

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

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

成员函数可以定义在类定义内部,或者单独使用范围解析运算符 :: 来定义。

在类定义中定义的成员函数把函数声明为内联的,即便没有使用 inline 标识符。

对于一些比较小、不复杂的函数,我们可以使用内联函数。内联函数在调用的时候,编译器会把函数复制到调用的地方,这样性能上面会快一些。

所以您可以按照如下方式定义 getVolume() 函数:

class Box
{
   public:
      double length;      // 长度
      double breadth;     // 宽度
      double height;      // 高度

      double getVolume(void)
      {
         return length * breadth * height;
      }
};Code language: JavaScript (javascript)

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

double Box::getVolume(void)
{
    return length * breadth * height;
}Code language: JavaScript (javascript)

在这里,需要强调一点,在 :: 运算符之前必须使用类名。调用成员函数是在对象上使用点运算符(.),这样它就能操作与该对象相关的数据,如下所示:

Box myBox;          // 创建一个对象
myBox.getVolume();  // 调用该对象的成员函数Code language: JavaScript (javascript)

看下面实例:

#include <iostream>

using namespace std;

class Box
{
   public:
      double length;         // 长度
      double breadth;        // 宽度
      double height;         // 高度

      // 成员函数声明
      double getVolume(void);
      void setLength( double len );
      void setBreadth( double bre );
      void setHeight( double hei );
};

// 成员函数定义
double Box::getVolume(void)
{
    return length * breadth * height;
}

void Box::setLength( double len )
{
    length = len;
}

void Box::setBreadth( double bre )
{
    breadth = bre;
}

void Box::setHeight( double hei )
{
    height = hei;
}

// 程序的主函数
int main( )
{
   Box Box1;                // 声明 Box1,类型为 Box
   Box Box2;                // 声明 Box2,类型为 Box
   double volume = 0.0;     // 用于存储体积

   // box 1 详述
   Box1.setLength(6.0);
   Box1.setBreadth(7.0);
   Box1.setHeight(5.0);

   // box 2 详述
   Box2.setLength(12.0);
   Box2.setBreadth(13.0);
   Box2.setHeight(10.0);

   // box 1 的体积
   volume = Box1.getVolume();
   cout << "Box1 的体积:" << volume <<endl;

   // box 2 的体积
   volume = Box2.getVolume();
   cout << "Box2 的体积:" << volume <<endl;
   return 0;
}Code language: PHP (php)

我们的例子:

#include <iostream>
#include <cstring>

using namespace std;
//类就是一个模板 通过这个模板
//可以创建很多对象 就是好比 人类是一个类   然后 小明是一个对象
class Box
{
   public:
      double length;   // 盒子的长度
      double breadth;  // 盒子的宽度
      double height;   // 盒子的高度
      //inline double getVolumn(void) //显示盒子的体积
      //{
      //    return length*breadth*height;
      //}
      double getVolumn(void);
};

int main() {
  Box box1;
  Box box2;

  box1.breadth = 3.3;
  box1.height = 3.5;
  box1.length = 5.6;

  box2.breadth = 5.3;
  box2.height = 3.7;
  box2.length = 9.6;

  cout << "box1的体积:" << box1.getVolumn() << endl;
  cout << "box2的体积:" << box2.getVolumn() << endl;

  return 0;
}

double Box::getVolumn(void)
{
    return length*breadth*height;
}Code language: PHP (php)

类访问修饰符

数据封装是面向对象编程的一个重要特点,它防止函数直接访问类类型的内部成员。

成员的访问限制是通过在类主体内部对各个区域标记 public、private、protected 来指定的。

关键字 public、private、protected 称为访问修饰符。

一个类可以有多个 public、protected 或 private 标记区域。

每个标记区域在下一个标记区域开始之前或者在遇到类主体结束右括号之前都是有效的。成员和类的默认访问修饰符是 private。

class Base {

   public:

  // 公有成员

   protected:

  // 受保护成员

   private:

  // 私有成员

};Code language: PHP (php)
公有(public)成员

公有成员在程序中类的外部是可访问的。您可以不使用任何成员函数来设置和获取公有变量的值,如下所示:

#include <iostream>

using namespace std;

class Line
{
   public:
      double length;
      void setLength(double len);
      double getLength(void);
};

// 成员函数定义
double Line::getLength(void)
{
    return length ;
}

void Line::setLength(double len)
{
    length = len;
}

// 程序的主函数
int main( )
{
   Line line;

   // 设置长度
   line.setLength(6.0);
   cout << "Length of line : " << line.getLength() <<endl;

   // 不使用成员函数设置长度
   line.length = 10.0; // OK: 因为 length 是公有的
   cout << "Length of line : " << line.length <<endl;
   return 0;
}Code language: PHP (php)

我们的代码:

#include <iostream>
#include <cstring>

using namespace std;

class Line
{
    public:
        double length;
        void SetLength(double len);
        double GetLength(void);
        void ShowLength(void);
        void SetPoint(double p);
    private:
        double pointX;
        double pointY;
};

int main()
{
    Line line;
    line.SetLength(10.3);
    cout << line.GetLength() << endl;

    line.length = 12.33;
    cout << line.length << endl;
    return 0;
}

void Line::SetLength(double len)
{
    length = len;
}

double Line::GetLength(void)
{
    return length;
}

//类名:: 这种写法 是属于类的内部的 不是外部 虽然是在外部编写
// 因为有 Line:: 所以 这里就相当于类的飞地 飞地也是属于内部
// 就好比河北的廊坊 它和河北没有接壤 被北京和天津包裹 但是它也是属于河北内部
void Line::ShowLength(void)
{
    cout << length << endl;//可以随便访问length 不管length是什么修饰符
}

void Line::SetPoint(double p)
{
    pointX = p;
}Code language: PHP (php)

public 的意思是类的外面是公开,不是类内部。在类的内部,不管什么修饰符,它是可以访问任意的自己的成员。

在 main 函数或者其他的函数,那么就属于类的外部了。

哪里是类的外部,哪里是类的内部:

类的大括号是类的内部,使用类名:: 这些类飞地也是属于类的内部。

除了这两种,其他的都是类的外部,比如 main 函数等非成员函数。属于某个类的函数就是成员函数。还有另外一个类也是属于类的外部。

class A{}
class B{}Code language: JavaScript (javascript)

A 对于 B 来说也是属于它的外部。

我们就好比代码的上帝视角。我们可以通过编程模拟现实世界,比如创造一个个对象,让一个个对象帮我们干活。

比如我们要开发一款游戏,我们可以创造游戏的世界,比如士兵、坦克、炸弹。

私有(private)成员

私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。

默认情况下,类的所有成员都是私有的。例如在下面的类中,width 是一个私有成员,这意味着,如果您没有使用任何访问修饰符,类的成员将被假定为私有成员:

class Box
{
   double width;
   public:
      double length;
      void setWidth( double wid );
      double getWidth( void );
};Code language: JavaScript (javascript)

我们的代码:

#include <iostream>
#include <cstring>

using namespace std;

class Line
{
    double dir; // 它也是属于private私有的 也是不写就是私有private
    public:
        double length;
        void SetLength(double len);
        double GetLength(void);
        void ShowLength(void);
        void SetPoint(double p);
    private:
        double pointX;
        double pointY;
    public:
        double abc;//可以多个public: private: protected:
};

int main()
{
    Line line;
    line.SetLength(10.3);
    cout << line.GetLength() << endl;

    line.length = 12.33;
    cout << line.length << endl;
    return 0;
}

void Line::SetLength(double len)
{
    length = len;
}

double Line::GetLength(void)
{
    return length;
}

//类名:: 这种写法 是属于类的内部的 不是外部 虽然是在外部编写
// 因为有 Line:: 所以 这里就相当于类的飞地 飞地也是属于内部
void Line::ShowLength(void)
{
    cout << length << endl;//可以随便访问length 不管length是什么修饰符
}

void Line::SetPoint(double p)
{
    pointX = p;
}Code language: PHP (php)

实际操作中,我们一般会在私有区域定义数据,在公有区域定义相关的函数,以便在类的外部也可以调用这些函数,如下所示:

#include <iostream>

using namespace std;

class Box
{
   public:
      double length;
      void setWidth( double wid );
      double getWidth( void );

   private:
      double width;
};

// 成员函数定义
double Box::getWidth(void)
{
    return width ;
}

void Box::setWidth( double wid )
{
    width = wid;
}

// 程序的主函数
int main( )
{
   Box box;

   // 不使用成员函数设置长度
   box.length = 10.0; // OK: 因为 length 是公有的
   cout << "Length of box : " << box.length <<endl;

   // 不使用成员函数设置宽度
   // box.width = 10.0; // Error: 因为 width 是私有的
   box.setWidth(10.0);  // 使用成员函数设置宽度
   cout << "Width of box : " << box.getWidth() <<endl;

   return 0;
}Code language: PHP (php)

我们的代码:

#include <iostream>
#include <cstring>

using namespace std;

class Line
{
    private:
        double length;
    public:
        void SetLength(double len);
        double GetLength(void);
};

int main()
{
    Line line;
    //私有的成员 通常通过公有方法取修改和获取 而不是任意修改
    //这样做的目的是保证代码安全 不是谁都可以访问 而是通过唯一的一些入口来操作
    //保证安全
    line.SetLength(10.3);
    cout << line.GetLength() << endl;

    //line.length = 111111111.22; //私有 是不允许 在类的外面访问
    //cout << line.length << endl;
    line.SetLength(111111111.22);
    cout << line.GetLength() << endl;

    return 0;
}

// 这些方法 最好是老员工写的 老员工知道业务  他就可以做一些约束了
void Line::SetLength(double len)
{
    //我们来了保证数据的完整性 增加一些约束
    if(len<100 && len >0)
    {
        length = len;
    }
}

double Line::GetLength(void)
{
    return length;
}Code language: PHP (php)
保护(protected)成员

保护成员变量或函数与私有成员十分相似,但有一点不同,保护成员在派生类(即子类)中是可访问的。

在下一个章节中,您将学习到派生类和继承的知识。

现在您可以看到下面的实例中,我们从父类 Box 派生了一个子类 smallBox。

下面的实例与前面的实例类似,在这里 width 成员可被派生类 smallBox 的任何成员函数访问。

#include <iostream>
#include <cstring>

using namespace std;

class Box
{
    protected:
        double width;
};

class SmallBox:Box
{
    public:
        void SetWidth(double w);
        double GetWidth(void);
};

int main()
{
    SmallBox smallBox;
    smallBox.SetWidth(11.2);
    cout << smallBox.GetWidth() << endl;

    return 0;
}

void SmallBox::SetWidth(double w)
{
    width = w;
}

double SmallBox::GetWidth(void)
{
    return width;
}

protected 是继承了父类的属性。这也是模拟现实世界的继承,可以让我们少写代码。

有 public、protected、private 三种继承方式,它们相应地改变了基类成员的访问属性。

  1. public 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:public、protected、private(子类不能访问父类的 private)
  2. protected 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:protected、protected、private(子类不能访问父类的 private)
  3. private 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:private、private、private(子类不能访问父类的 private)

但无论哪种继承方式,上面两点都没有改变:

  1. private 成员只能被本类成员(类内)和友元访问,不能被派生类访问;
  2. protected 成员可以被派生类访问。
父类成员 \ 继承方式public 继承protected 继承private 继承
public 成员public 成员protected 成员private 成员
protected 成员protected 成员protected 成员private 成员
private 成员不能继承不能访问不能继承不能访问不能继承不能访问

对于 protected 和 private 成员,在类的外部是不能访问。

对于 protected,在子类里面可以访问。

不管哪种修饰,在自己类内部都可以访问。

public 继承

#include <iostream>
#include <cstring>

using namespace std;

class A
{
    public:
        int a1;
    protected:
        int a2;
    private:
        int a3;
    public:
        int a;

        //构造函数
        A()
        {
            a1 = 1;
            a2 = 2;
            a3 = 3;
            a = 4;
        }

        void fun()
        {
            //类内部 可以随意访问任何修饰符的成员
            cout << a << endl;    //正确
            cout << a1 << endl;   //正确
            cout << a2 << endl;   //正确
            cout << a3 << endl;   //正确
        }
};

// public A 继承 那么 A的public成员 到了B也是public
// A里面是protected 那么到了B也是protected
// A里面的private 那么B里面 不能访问 继承不过来的  就算是儿子也不能访问父类的private
class B:public A //public方式继承A 那么A的成员 修饰符
{
    public:
        int a;

        B(int i){
            A();
            a = i;
        }

        void fun()
        {
            cout << a << endl;       //正确,public成员
            cout << a1 << endl;       //正确,基类的public成员,在派生类中仍是public成员。
            cout << a2 << endl;       //正确,基类的protected成员,在派生类中仍是protected可以被派生类访问。
            //cout << a3 << endl;       //错误,基类的private成员不能被派生类访问。
        }
};

int main()
{
    B b(10);
    cout << b.a << endl; //输出10 也就是输出B里面的a 而不是A里面的a
    cout << b.a1 << endl;   //正确 a1是从A里面继承 A的a1是public 并且B以public继承
                        // 所以a1 在B里面 也是public 所以在类的外部 main函数可以访问
    //cout << b.a2 << endl;   //错误,类外不能访问protected成员
                        // A里面的a2是protected 并且以public继承 所以在B里面a2也是protected
                        // protected成员 只能类内部或者它的子类内部
    //cout << b.a3 << endl;   //错误,类外不能访问private成员

    return 0;
}

protected 继承

#include <iostream>
#include <cstring>

using namespace std;

class A
{
    public:
        int a1;
    protected:
        int a2;
    private:
        int a3;
    public:
        int a;

        //构造函数
        A()
        {
            a1 = 1;
            a2 = 2;
            a3 = 3;
            a = 4;
        }

        void fun()
        {
            //类内部 可以随意访问任何修饰符的成员
            cout << a << endl;    //正确
            cout << a1 << endl;   //正确
            cout << a2 << endl;   //正确
            cout << a3 << endl;   //正确
        }
};

// protected A 继承
class B:protected A
{
    public:
        int a;

        B(int i){
            A();
            a = i;
        }

        void fun()
        {
            cout << a << endl;       //正确,public成员
            cout << a1 << endl;       //正确,基类的public成员,子类变成protected成员
            cout << a2 << endl;       //正确,基类的protected成员,在派生类中仍是protected可以被派生类访问。
            //cout << a3 << endl;       //错误,基类的private成员不能被派生类访问。
        }
};

int main()
{
    B b(10);
    cout << b.a << endl; //输出10 也就是输出B里面的a 而不是A里面的a
    //cout << b.a1 << endl;   //a1 虽然父类是public 但是由于是protected继承 则也变成了protected
    //cout << b.a2 << endl;   //错误,类外不能访问protected成员
    //cout << b.a3 << endl;   //错误,类外不能访问private成员

    return 0;
}

private 继承

#include <iostream>
#include <cstring>

using namespace std;

class A
{
    public:
        int a1;
    protected:
        int a2;
    private:
        int a3;
    public:
        A()
        {
            a1 = 1;
            a2 = 2;
            a3 = 3;
        }
};

class B:private A
{
    public:
        B(){
            A();
        }

        void fun()
        {
            cout << a1 << endl;       //正确,基类的public成员,子类变成private成员
            cout << a2 << endl;       //正确,基类的protected成员,子类变成private成员
            //cout << a3 << endl;       //错误,基类的private成员不能被派生类访问。
        }
};

int main()
{
    B b;
    //cout << b.a1 << endl;   //a1 虽然父类是public 但是由于是private继承 则也变成了private
    //cout << b.a2 << endl;   //错误,类外不能访问private成员
    //cout << b.a3 << endl;   //错误,类外不能访问private成员 连B的内部都不行

    return 0;
}

类的构造函数

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

构造函数的名称与类名是完全相同的,并且不会返回任何类型,也不会返回 void。

构造函数可用于为某些成员变量设置初始值。

#include <iostream>
#include <cstring>

using namespace std;

class Line
{
    public:
        Line();
    private:
        double length;
};

int main()
{
    Line line1;
    Line line2;
    Line line3;

    return 0;
}

Line::Line()
{
    cout << "构造函数被调用了" << endl;
}Code language: PHP (php)
带参数的构造函数

默认的构造函数没有任何参数,但如果需要,构造函数也可以带有参数。这样在创建对象时就会给对象赋初始值,如下面的例子所示:

#include <iostream>
#include <cstring>

using namespace std;

class Line
{
    public:
        Line(double len);
        void ShowLineLength();
    private:
        double length;
};

int main()
{
    Line line1(42.3);
    Line line2(23.2);
    Line line3(344.4);

    line1.ShowLineLength();
    line2.ShowLineLength();
    line3.ShowLineLength();

    return 0;
}

Line::Line(double len)
{
    length = len;
}

void Line::ShowLineLength()
{
    cout << length << endl;
}Code language: PHP (php)

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

Line::Line( double len): length(len)
{

}Code language: CSS (css)

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

Line::Line( double len)
{
    length = len;
}Code language: PHP (php)

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

C::C(double a,double b,double c): X(a), Y(b), Z(c)
{
  ....
}Code language: PHP (php)

类的析构函数

我们知道构造函数是用来创建对象的,用来初始化。那么有创建就有销毁。

那么我们这节讲的析构函数就是用来销毁操作的。

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

析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。

析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。

#include <iostream>
#include <cstring>

using namespace std;

class Line
{
    public:
        Line(double len);
        ~Line();
        void ShowLineLength();
    private:
        double length;
};

int main()
{
    Line line1(42.3);
    Line line2(23.2);
    Line line3(344.4);

    line1.ShowLineLength();
    line2.ShowLineLength();
    line3.ShowLineLength();

    return 0;
}

Line::Line(double len): length(len)
{
    cout << "构造函数被调用" << endl;
}

Line::~Line()
{
    cout << "析构函数被调用" << endl;
}

void Line::ShowLineLength()
{
    cout << length << endl;
}Code language: PHP (php)

拷贝构造函数

拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。

以旧的对象来初始化新的对象。

拷贝构造函数通常用于:

  • 通过使用另一个同类型的对象来初始化新创建的对象。
  • 复制对象把它作为参数传递给函数。
  • 复制对象,并从函数返回这个对象。

如果在类中没有定义拷贝构造函数,编译器会自行定义一个。

如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。

拷贝构造函数的最常见形式如下:

classname (const classname &obj) {
   // 构造函数的主体
}Code language: JavaScript (javascript)

在这里,obj 是一个对象引用,该对象是用于初始化另一个对象的。

#include <iostream>

using namespace std;

class Line
{
   public:
      int getLength( void );
      Line( int len );             // 简单的构造函数
      Line( const Line &obj);      // 拷贝构造函数
      ~Line();                     // 析构函数

   private:
      int *ptr;
};

// 成员函数定义,包括构造函数
Line::Line(int len)
{
    cout << "调用构造函数" << endl;
    // 为指针分配内存
    ptr = new int;
    *ptr = len;
}

Line::Line(const Line &obj)
{
    cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
    ptr = new int;
    *ptr = *obj.ptr; // 拷贝值
}

Line::~Line(void)
{
    cout << "释放内存" << endl;
    delete ptr;
}
int Line::getLength( void )
{
    return *ptr;
}

void display(Line obj)
{
   cout << "line 大小 : " << obj.getLength() <<endl;
}

// 程序的主函数
int main( )
{
   Line line(10);

   display(line);

   return 0;
}Code language: PHP (php)

下面的实例对上面的实例稍作修改,通过使用已有的同类型的对象来初始化新创建的对象:

#include <iostream>

using namespace std;

class Line
{
   public:
      int getLength( void );
      Line( int len );             // 简单的构造函数
      Line( const Line &obj);      // 拷贝构造函数
      ~Line();                     // 析构函数

   private:
      int *ptr;
};

// 成员函数定义,包括构造函数
Line::Line(int len)
{
    cout << "调用构造函数" << endl;
    // 为指针分配内存
    ptr = new int;
    *ptr = len;
}

Line::Line(const Line &obj)
{
    cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
    ptr = new int;
    *ptr = *obj.ptr; // 拷贝值
}

Line::~Line(void)
{
    cout << "释放内存" << endl;
    delete ptr;
}
int Line::getLength( void )
{
    return *ptr;
}

void display(Line obj)
{
   cout << "line 大小 : " << obj.getLength() <<endl;
}

// 程序的主函数
int main( )
{
   Line line1(10);

   Line line2 = line1; // 这里也调用了拷贝构造函数

   display(line1);
   display(line2);

   return 0;
}Code language: PHP (php)

拷贝构造函数补充

什么是拷贝构造函数:

首先对于普通类型的对象来说,它们之间的复制是很简单的,例如:

int a=100;
int b=a;

而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量。

下面看一个类对象拷贝的简单例子。

#include<iostream>
using namespace std;
class CExample
{
    private:
        int a;
    public:
        //构造函数
        CExample(int b)
        {
            a=b;
            printf("constructor is called\n");
        }

        //拷贝构造函数
        CExample(const CExample & c)//这是一个引用
        {
            a=c.a;
            printf("copy constructor is called\n");
        }

        //析构函数
        ~CExample()
        {
            cout<<"destructor is called\n";
        }

        void Show()
        {
            cout<<a<<endl;
        }
};

int main()
{
    CExample A(100);
    CExample B=A;//此处会调用拷贝构造函数 在拷贝构造函数里面 会把A的a付给B的a
    B.Show();
    return 0;
}Code language: PHP (php)

浅拷贝与深拷贝

所谓浅拷贝,指的是在对象复制时,只对对象中的数据成员进行简单的赋值,默认拷贝构造函数执行的也是浅拷贝。大多情况下”浅拷贝”已经能很好地工作了,但是一旦对象存在了动态成员,那么浅拷贝就会出问题了。

出现这些问题最根本就在于在复制对象时,计数器没有递增。如果类带有指针变量,并有动态内存分配,浅拷贝会导致两个指针指向同一个地址,当对象结束时,会调用两次析构函数,而导致指针悬挂现象。所以这时必须采用深拷贝。

深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,从而也就解决了指针悬挂的问题。简而言之,当数据成员中有指针时,必须要用深拷贝。

防止默认拷贝发生

通过对对象复制的分析,我们发现对象的复制大多在进行”值传递”时发生,这里有一个小技巧可以防止按值传递——声明一个私有拷贝构造函数,甚至不必去定义这个拷贝构造函数。这样因为拷贝构造函数是私有的,如果用户试图按值传递或函数返回该类对象,将得到一个编译错误,从而可以避免按值传递或返回对象。

拷贝构造函数里能调用 private 成员变量吗?​

解答:拷贝构造函数是一个特殊的构造函数,操作的还是自己类的成员变量,所以不受 private 的限制。

类的友元函数

类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。

尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。

友元可以是一个函数,该函数被称为友元函数;

友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。

如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend,如下所示:

class Box
{
   double width;
public:
   double length;
   friend void printWidth( Box box );
   void setWidth( double wid );
};Code language: JavaScript (javascript)

声明类 ClassTwo 的所有成员函数作为类 ClassOne 的友元,需要在类 ClassOne 的定义中放置如下声明:

friend class ClassTwo;Code language: JavaScript (javascript)

实例:

#include <iostream>

using namespace std;

class Box
{
   double width;
public:
    friend void printWidth(Box box);
   void setWidth(double wid);
};

// 成员函数定义
void Box::setWidth( double wid )
{
    width = wid;
}

// 请注意:printWidth() 不是任何类的成员函数 前面是没有Box::这个 所以它不是成员函数
void printWidth(Box box)
{
   /* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
   cout << "Width of box : " << box.width <<endl;
}

// 程序的主函数
int main( )
{
   Box box;
   //cout << box.width<<endl; 类外面是不能访问私有成员的
   // 使用成员函数设置宽度
   box.setWidth(10.0);

   // 使用友元函数输出宽度
   printWidth(box);

   return 0;
}Code language: PHP (php)

内联函数

C++ 内联函数是通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。

对于其他的函数:当我们调用的时候,会调到这个函数里面去,所以函数只有一份。

但是对于内联函数,那么在编译的时候,就会把函数的副本复制到调用的地方。

对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。

如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline,在调用函数之前需要对函数进行定义。如果已定义的函数多于一行,编译器会忽略 inline 限定符。

在类定义中的定义的函数都是内联函数,即使没有使用 inline 说明符。

inline 的使用比较简单,只需要在声明或者定义函数时在头部加上 inline 关键字即可,格式如下:

inline 返回值类型  函数名(函数参数){
    //此处定义函数体
}Code language: JavaScript (javascript)

一般来说,inline 适用的函数有两种:一种是在类内定义的成员函数,另一种是在类内声明、类外定义的成员函数。

(1)类内定义成员函数:这种情况下,我们可以不用在函数头部加 inline 关键字,因为编译器会自动将类内定义的函数声明为内联函数。

(2)类内声明函数,在类外定义函数:这种情况下如果想将该函数设置为内联函数,则可以在类内声明时不加 inline 关键字,而在类外定义函数时加上 inline 关键字。

我们的代码:

#include <iostream>

using namespace std;

class Temp{
  public:
     int  amount;

      //构造函数
       Temp(int amount){
          this->amount = amount;
       }

      //普通成员函数,在类内定义时前面可以不加inline
      void print_amount();
};

inline void Temp::print_amount()
{
    cout << this-> amount;
}

// 程序的主函数
int main( )
{
   Temp temp(10);
   temp.print_amount();

   return 0;
}Code language: PHP (php)

Next:

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注