c++类的构造函数拷贝函数和析构函数

一,类的概念及封装

1.什么是封装

  • 第一层含义:封装是面向对象程序设计最基本的特性。把数据(属性)和函数(方法)合成一个整体,这在计算机世界中是用类和对象实现的。(把属性和方法进行封装)
  • 第二层含义:把客观事物封装成抽象的类,并且类可以把自己的属性和方法只让可信的类或者对象操作,对不可信的进行信息的隐藏。(对属性和方法进行访问控制)

2.类的访问控制

  在C++中可以对类的属性和方法定义访问级别,public修饰的属性和方法,可以在类的内部访问,也可以在类的外部进行访问。private修饰的属性和方法,只能在类的内部进行访问。

3.类的意义

  • 类是把属性和方法进行封装,同时对类的属性和方法进行访问控制。
  • 类是由我们根据客观事物抽象而成,形成一类事物,然后用类去定义对象,形成这类事物的具体个体。
  • 类是一个数据类型,类是抽象的,而对象是一个具体的变量,是占用内存空间的。

4,圆类的抽象和封装

# include
using namespace std;

/* 抽象一个圆类,并对圆的属性和方法进行封装 */
class Circle
{
private:
    double r; /* 圆类的半径属性 */ 
public:
    void setR(double r) /* 设置圆的半径 */
    {
        this->r = r;
    }
    double getR() /* 获取圆的半径 */
    {
        return this->r;
    }
};

二,构造函数

1.前言

  当我们创建一个对象的时候,常常需要做某些初始化操作,例如属性进行赋初值。为了解决这个问题,C++编译器提供了构造函数来处理对象的初始化。构造函数是一种特殊的成员函数,与其他成员函数不同,不需要用户来调用它,而是在创建对象时自动执行。

2.构造函数的定义及调用方式

  • 定义:C++类中可以定义与类名相同的特殊成员函数,这种与类名相同的成员函数叫做构造函数。构造函数可以有参数列表,但是不能有函数返回值。
  • 调用:一般情况下C++编译器会自动调用构造函数,但在一些情况下则需要手工调用构造方法。

3.构造函数的分类

  • 无参构造函数:构造函数没有函数参数。
  • 有参构造函数:构造函数有函数参数。
  • 拷贝构造函数:构造函数的参数为const ClassName &vriable。

4.无参构造函数示例及调用

# include
using namespace std;

/* 抽象一个圆类,并对圆的属性和方法进行封装 */
class Circle
{
private:
    double r; /* 圆类的半径属性 */ 
public:
    Circle() /* 无参构造函数 */
    {
        cout << "无参构造函数被执行" << endl;
    }
    void setR(double r) /* 设置圆的半径 */
    {
        this->r = r;
    }
    double getR() /* 获取圆的半径 */
    {
        return this->r;
    }
};

int main()
{
    /* 无参构造函数的调用方式 */
    Circle circle1,circle2;
} 

5.有参构造函数示例及调用

# include
using namespace std;

/* 抽象一个圆类,并对圆的属性和方法进行封装 */
class Circle
{
private:
    double r; /* 圆类的半径属性 */ 
public:
    Circle(int a,int b) /* 有参构造函数 */
    {
        cout << "有参构造函数被调用" << endl;
    }
    void setR(double r) /* 设置圆的半径 */
    {
        this->r = r;
    }
    double getR() /* 获取圆的半径 */
    {
        return this->r;
    }
};

int main()
{
    /* 有参构造函数调用方式一 */
    Circle circle1(1, 2);
    /* 有参构造函数调用方式二:程序员手工直接调用构造方法 */
    Circle circle2 = Circle(1, 2);
}

 

6.拷贝构造函数示例及调用

1.拷贝构造函数的调用方式

  • 当用对象1初始化对象2的时候拷贝构造函数被调用。
  • 当用对象1括号方式初始化对象2的时候拷贝函数被调用。
  • 当用对象(此处是元素而而非指针或引用)做函数参数的时候,实参传递给形参的过程会调用拷贝构造函数。
  • 当用对象(此处是元素而而非指针或引用)做函数返回值的时候,若用同一类型来接收该返回值,则会执行拷贝构造函数(此时会涉及到匿名对象的去和留的问题,因为函数的返回对象是个匿名对象)。

2.拷贝构造函数调用方式示例

# include
using namespace std;

class Location
{
private:
    int x;
    int y;
public:
    Location()
    {
        cout << "无参构造函数被执行" << endl;
    }
    Location(int x,int y)
    {
        this->x = x;
        this->y = y;
        cout << "有参构造函数被执行" << endl;
    }
    Location(const Location& location)
    {
        this->x = location.x;
        this->y = location.y;
        cout << "拷贝构造函数被执行" << endl;
    }
    ~Location()
    {
        cout << "x = " << this->x << ",y = " << this->y << "析构函数被执行" << endl;
    }

};

/* 模拟拷贝构造函数调用方式三 */
void setLocation(Location location)
{
    cout << "setLocation()全局函数..." << endl;
}

/* 模拟拷贝构造函数调用方式四 */
Location getLocation()
{
    cout << "getLocation()全局函数..." << endl;
    Location location(100, 200);
    return location;
}

/* 研究拷贝构造函数的调用 */
int main()
{
    /* 拷贝构造函数调用方式一 */
    cout << "###############方式一###############" << endl;
    Location loc1(1, 2);
    Location loc2 = loc1;

    /* 拷贝构造函数调用方式二 */
    cout << "###############方式二###############" << endl;
    Location loc3(10, 20);
    Location loc4(loc3);

    /* 拷贝构造函数调用方式三 */
    cout << "###############方式三###############" << endl;
    Location loc5(5, 10);
    setLocation(loc5);

    /* 拷贝构造函数调用方式四 */
    cout << "###############方式四###############" << endl;
    /* getLocation()产生匿名对象赋值给loc6后,匿名对象执行析构函数,然后对象销毁 */
    Location loc6;
    loc6 = getLocation();
    /* getLocation()产生匿名对象赋值给loc7,匿名对象被扶正,直接转成新对象 */
    Location loc7 = getLocation();
}

7.构造函数小结 

  • 当类中没有定义任何构造函数的时候,C++编译器默认为我们定义一个无参构造函数,函数体为空。
  • 当在类中定义任意构造函数后(包括拷贝构造函数),C++编译器就不会为我们定义默认构造函数,也就是说我们定义了构造函数就必须要使用。
  • C++默认提供拷贝构造函数是指向的浅拷贝。只是进行了简单的成员变量的值的复制。
  • 拷贝构造函数只有当我们写了以后C++编译器才不会给我提供,如若不写则C++编译器会一直提供默认的拷贝构造函数。

三,析构函数

1.析构函数的定义及调用方式

  • 定义:C++类中可以定义一个特殊的成员函数来清理对象和释放我们在对象中分配的内存,这个特殊的方法叫做析构函数。
  • 调用:当对象生命周期临近结束时,析构函数由C++编译器自动调用。

2.析构函数的使用

  析构通常是在对象生命周期结束之前调用,我们会在此时去释放我们在对象中创建的堆内存,保证程序的合理性。

# define _CRT_SECURE_NO_WARNINGS
# include

using namespace std;

class Student
{
private:
    char * name;
    int age;
public:
    Student(char * name, int age)
    {
        /* 在构造函数中分配堆内存 */
        this->name = (char *)malloc(sizeof(name)+1);
        /* 初始化成员变量 */
        strcpy(this->name, name);
        this->age = age;
        cout << "构造函数被执行..." << endl;
    }
    ~Student()
    {
        if (this->name != NULL)
        {
            /* 在析构函数中释放堆内存 */
            free(this->name);
            this->name = NULL;
            this->age = 0;
        }
        cout << "析构函数被执行..." << endl;
    }
    void print()
    {
        cout << this->name << " = " << this->age << endl;
    }
    
};

int main()
{
    Student stu("王刚", 21);
    stu.print();
    return 0;
}

四,多个对象的构造函数和析构函数

1.对象初始化列表

  当我们有一个类成员,它本身是一个类,而且这个成员只有一个带参数的构造函数,没有默认构造函数,这个时候要对这个类成员进行初始化,就必须调用这个类成员的带参构造函数,这个时候可以在定义成员的时候采用该构造函数初始化对象,但是我们想要动态的初始化,则需要将该成员动态接受外界数据,因此出现了对象初始化列表这个概念。

2.类中成员为const修饰

  当我们有个类成员,它被const修饰,那么在初始化的时候可以直接在定义变量的时候初始化,也可以在使用对象初始化列表的时候进行初始化。

3.对象初始化示例

# include

using namespace std;

/* 定义A类 */
class A
{
private:
    int a;
    int b;
public:
    A(int a, int b)
    {
        this->a = a;
        this->b = b;
        cout << "A(" << this->a << "," << this->b << ")有参构造函数被执行" << endl;
    }
    ~A()
    {
        cout << "~A(" << this->a << ", " << this->b << ")析构函数被执行" << endl;
    }
};
/* 定义类B */
class B
{
private:
    A a1;
    A a2;
    const int n;
    const int m = 10;
public:
    /* 对象初始化列表:让成员变量从外界接收的方式来初始化 */
    B(int x,int y):a1(x,y),a2(10,20),n(100)
    {
        cout << "B(int x,int y)有参构造函数被执行" << endl;
    }
    ~B()
    {
        cout << "~B()析构函数被执行" << endl;
    }
};

int main()
{
    B b(1, 2);

    return 0;
}

 

4.构造函数和析构函数的执行顺序

  • 当类中有成员是其他类的对象时,首先调用成员变量的构造函数,调用顺序和成员变量的定义顺序一致。成员变量的构造函数统统执行完毕后,再调用该类的构造函数。
  • 析构函数的执行顺序是“倒关”的方式,即先与构造函数的执行顺序相反。

五,拷贝构造函数的深拷贝和浅拷贝

1.拷贝构造函数的执行原理

  拷贝构造函数有四种调用方式,当我们不写拷贝构造函数的时候,C++默认的帮我们写了拷贝构造函数,其函数体实现的是简单的成员变量的值的拷贝。这种默认的拷贝方式,我们称为浅拷贝,即只是简单的复制成员变量的值到另一个对象中。

2.深拷贝问题的抛出

  当我们的成员变量是动态分配的一组堆内存的时候,这个时候C++默认的为我们写的拷贝构造函数把成员变量的值进行了复制,那么副本也会指向原始对象所分配的堆内存,当我们在析构函数中释放堆内存的时候,就会发生两次析构现象(一次是原始对象释放堆内存,一次是副本对象释放堆内存)因此造成程序崩溃。

3.深拷贝代码示例

# define _CRT_SECURE_NO_WARNINGS
# include
using namespace std;

class Student
{
private:
    char * name;
    int age;
public:
    /* 无参构造函数 */
    Student()
    {
        cout << "无参构造函数被执行..." << endl;
    }
    /* 有参构造函数 */
    Student(char * name, int age)
    {
        /* 在构造函数中分配堆内存 */
        this->name = (char *)malloc(sizeof(name) + 1);
        /* 初始化成员变量 */
        strcpy(this->name, name);
        this->age = age;
        cout << "有参构造函数被执行..." << endl;
    }
    /* 拷贝构造函数 */
    Student(const Student &student)
    {
        /* 重新分配内存 */
        this->name = (char *)malloc(sizeof(student.name) + 1);
        /* 初始化成员变量 */
        strcpy(this->name, name);
        this->age = age;
        cout << "拷贝构造函数被执行..." << endl;
    }
    /* 析构函数 */
    ~Student()
    {
        if (this->name != NULL)
        {
            free(this->name);
            this->name = NULL;
            this->age = 0;
        }
        cout << "析构函数被执行..." << endl;
    }
    void print()
    {
        cout << this->name << " = " << this->age << endl;
    }

};

int main()
{
    Student stu1("王刚", 22);
    /* 此时拷贝构造函数执行,在创建副本的时候进行深拷贝,则不会出现析构问题 */
    Student stu2 = stu1;

    return 0;
}

你可能感兴趣的:(c++,c++,class,构造函数,拷贝函数,析构函数)