首页 > 编程语言 >03-C++类和对象(上)

03-C++类和对象(上)

时间:2025-01-03 21:33:51浏览次数:3  
标签:03 调用 函数 对象 定义 C++ int 析构 构造函数

一、类的概述

1.类的引入

类的封装:将数据和方法封装在一起,加以权限区分,用户只能通过公共方法访问私有数据。

为什么要将数据和方法封装在一起呢,而且还要通过公共方法才能访问私有数据?

  • C语言中数据和方法分开可能产生的问题:
// 定义一个狗结构体
struct Dog
{
    char name[32];
};

// 定义一个人结构体
struct Person
{
    char name[32];
};

// 定义一个狗的方法
void dogRun(struct Dog *dog)
{
    printf("%s正在用四条腿跑\n", dog->name);
}

// 定义一个人的方法
void personRun(struct Person *per)
{
    printf("%s正在用两条腿跑\n", per->name);
}

void test01()
{
    struct Dog laifu = {"来福"};
    struct Person changwei = {"常威"};
    dogRun(&laifu);
    personRun(&changwei);
    dogRun((struct Dog *)&changwei);
}
  • 运行结果
来福正在用四条腿跑
常威正在用两条腿跑
常威正在用四条腿跑
  • 可以看到,如果将数据和方法分开的话,由于对函数和数据的使用不规范,可能造成非常尴尬的局面。

2.类的封装

  1. 定义类的关键字为:class;
  2. 对类的数据加以权限区分:public 公有数据 、protected 受保护数据 、private 私有数据;
  3. public 修饰的数据和方法,类外可以直接访问;
  4. private、protected 修饰的数据和方法,类外不可以访问;
  5. 没有涉及继承时,private 和 protected 没有任何区别;
  6. 权限限制只是针对类外访问而言的,类的内部没有权限的区分,因此可以通过类的成员函数访问类中的任意数据;
  7. 一般建议,数据为私有,方法为公有。
  • 代码演示
// 定义一个类
class Data
{
private:
    int a;
protected:
    int b;
public:
    int c;
    // 定义一个成员函数,用于初始化数据
    void initData(int a1, int b1, int c1)
    {
        a = a1;
        b = b1;
        c = c1;
    }
    // 定义一个成员函数,打印所有变量的值
    void showData()
    {
        printf("a = %d,b = %d, c = %d\n",a , b, c);
    }
};

void test02()
{
    // 实例化一个对象
    Data data1;
    data1.initData(11, 22, 33);

    // 访问data1中的数据
    // printf("a = %d\n", data1.a); // 报错,数据私有,不允许访问
    // printf("b = %d\n", data1.b); // 报错,数据受保护,不允许访问
    printf("c = %d\n", data1.c); // 访问成功,数据公有

    // 通过成员函数访问
    data1.showData();
}
  • 运行结果
c = 33               
a = 11,b = 22, c = 33
  • 说明:
    1. 定义类的时候和结构体一样,是不占用空间的,实例化对象的时候才会开辟空间;
    2. 可以看到,我们在类的外部访问私有和受保护数据的时候,访问失败,访问公共数据,访问成功;
    3. 但是无论公有私有还是公共数据,通过类的成员函数进行读写操作都能成功,这也证明了在类的内部没有权限区分;
    4. 因此以后,想要访问类私有属性,就需要通过公共方法来访问。

3.如何设计一个类

因为类是封装了数据和方法,因此在设计类前我们要想好需要有哪些数据,然后要对这些数据进行哪些操作。

3.1案例一

  • 案例1:定义一个 Person 类,包括人的姓名和年龄数据,包含初始化方法,对每个数据的读写操作,显示所有信息,同时限制年龄为合理范围。
// 定义一个 Person 类
class Person
{
private:
    char name[32];
    int age;
public:
    // 定义函数,初始化对象
    void initPerson(char *new_name, int new_age)
    {
        if (new_age >= 0 && new_age <= 120)
        {
            age = new_age;
        }
        else
        {
            cout << "请输入有效年龄" << endl;
            return;
        }
        strcpy(name, new_name);
    }
    // 定义函数,获取name的值
    char *getName(void)
    {
        return name;
    }
    // 定义函数,获取age的值
    int getAge(void)
    {
        return age;
    }
    // 定义函数,修改name
    void setName(char *new_name)
    {
        strcpy(name, new_name);
    }
    // 定义函数,修改age
    void setAge(int new_age)
    {
        if (new_age >= 0 && new_age <= 120)
        {
            age = new_age;
        }
        else
        {
            cout << "请输入有效年龄" << endl;
            return;
        }
    }
    // 定义函数,打印所有信息
    void showPerson()
    {
        cout << "name = " << name << ", age = " << age << endl;
    }
};

void test03()
{
    // 创建一个Person对象
    Person jack;
    jack.initPerson("jack", 20);
    cout << "name = " << jack.getName() << endl;
    cout << "age = " << jack.getAge() << endl;
    jack.setName("rose");
    jack.setAge(18);
    jack.showPerson();
}
  • 运行结果
name = jack          
age = 20             
name = rose, age = 18

3.2案例二

  • 案例2:设计立方体类(Cube),立方体的长宽高为 a b c ,求出立方体的面积和体积,分别用全局函数和成员函数判断两个立方体是否相等。
// 定义一个立方体类
class Cube
{
private:
    int a;
    int b;
    int c;
public:
    // 定义函数,初始化长宽高
    void initCube(int a1, int b1, int c1);

    // 定义函数,设置长宽高
    void setA(int a1);
    void setB(int b1);
    void setC(int c1);

    // 定义函数,获取长宽高
    int getA();
    int getB();
    int getC();

    // 定义函数,计算立方体的面积
    int getArea();
    // 定义函数,计算立方体的体积
    int getVolume();

    // 定义成员函数,判断两个立方体是否相等
    bool cmpCube(Cube &other_cube);
};

void Cube::initCube(int a1, int b1, int c1) {
    a = a1;
    b = b1;
    c = c1;
}

void Cube::setA(int a1) {
    a = a1;
}

void Cube::setB(int b1) {
    b = b1;
}

void Cube::setC(int c1) {
    c= c1;
}

int Cube::getA() {
    return a;
}

int Cube::getB() {
    return b;
}

int Cube::getC() {
    return c;
}

int Cube::getArea() {
    int area = (a * b + a * c + b * c) * 2;
    return area;
}

int Cube::getVolume() {
    int volume = a * b *c;
    return volume;
}

bool Cube::cmpCube(Cube &other_cube) {
    if ((a == other_cube.getA()) && (b == other_cube.getB()) && (c == other_cube.getC()))
        return true;
    return false;
}

// 定义全局函数,判断两个立方体是否相等
bool cmpCube1(Cube &cube1, Cube &cube2)
{
    if ((cube1.getA() == cube2.getA()) && (cube1.getB() == cube2.getB()) && (cube1.getC() == cube2.getC()))
        return true;
    return false;
}

void test04()
{
    // 创建一个立方体对象
    Cube cube1;
    cube1.initCube(2, 3, 5);
    cout << "立方体1面积:" << cube1.getArea() << ", 立方体1体积:" << cube1.getVolume() << endl;

    // 创建另一个立方体对象
    Cube cube2;
    cube1.initCube(1, 4, 8);
    cout << "立方体2面积:" << cube1.getArea() << ", 立方体2体积:" << cube1.getVolume() << endl;

    // 判断两个立方体是否相等
    // if (cmpCube1(cube1, cube2)) // 全局函数判断
    if (cube1.cmpCube(cube2)) // 成员函数判断
    {
        cout << "两个立方体完全一样" << endl;
    }
    else
    {
        cout << "两个立方体不一样" << endl;
    }
}
  • 运行结果
立方体1面积:62, 立方体1体积:30
立方体2面积:88, 立方体2体积:32
两个立方体不一样 
  • 说明:
    1. 上面定义类的时候,将类的成员函数在类中声明,类外定义。注意,类外定义的函数得加上作用域的限制,标明这个成员函数是属于哪一个类的;
    2. 这里在比较两个立方体是否相等的时候,返回值用到了 bool 类型,表示真假值,在C语言中没有用过,c++阶段学习;
    3. 当使用全局函数比较两个立方体时,需要传两个参数,但使用成员函数比较两个立方体大小的时候,只需要传另外一个立方体就行。

3.3案例三

  • 案例3:设计一个圆形类(Circle),和一个点类(Point),计算点和圆的关系。 假如圆心坐标为x0, y0, 半径为 r,点的坐标为 x1, y1,判断点在圆上、圆外还是园内。
// 定义一个点类
class Point
{
private:
    int x;
    int y;
public:
    // 初始化点坐标
    void initPoint(int x1, int y1)
    {
        x = x1;
        y = y1;
    }
    // 获取点的横纵坐标
    int getX()
    {
        return x;
    }
    int getY()
    {
        return y;
    }
};

// 定义一个圆类
class Circle
{
private:
    int r;
    Point p;
public:
    // 初始化圆
    void initCircle(int r0, int x0, int y0)
    {
        // 初始化圆半径
        r = r0;
        // 初始化圆心
        p.initPoint(x0, y0);
    }
    // 定义函数,判断点和圆的位置(外 上 内 对应 1 0 -1)
    int getRelation(Point &p1)
    {
        // 定义变量分别表示半径的平方,横坐标、纵坐标差值的平方
        int R = r*r;
        int X = (p1.getX() - p.getX()) * (p1.getX() - p.getX());
        int Y = (p1.getY() - p.getY()) * (p1.getY() - p.getY());

        // 判断位置
        if (R == X + Y)
            return 0;
        else if (R < X + Y)
            return 1;
        else
            return -1;
    }
};

void test05()
{
    // 创建一个点对象
    Point p;
    p.initPoint(3, 4);
    // 创建一个圆对象
    Circle c;
    c.initCircle(5, 0, 0);

    int ret = c.getRelation(p);
    switch(ret)
    {
        case 0:
            cout << "点在圆上" << endl;
            break;
        case 1:
            cout << "点在圆外" << endl;
            break;
        case -1:
            cout << "点在圆内" << endl;
            break;
        default:
            cout << "未得出结果" << endl;
            break;
    }
}
  • 运行结果
点在圆上
  • 说明:上面的案例中用到了对象的嵌套,圆对象里面又嵌套了一个点对象。

二、构造析构

1.构造函数

1.1构造函数的概述

我们前面定义类的时候,都会设置一个给对象初始化的函数,创建对象以后再手动调用,其实 c++ 类里有可以主动调用的初始化函数,叫做构造函数。

构造函数:类实例化对象的时候自动调用。构造函数的本质功能就是初始化对象中的数据成员。

1.2构造函数的定义

构造函数名和类名称相同,没有返回值类型,连 void 都不可以,可以有参数,可以重载,因为创建对象是在类外创建,构造函数在创建对象的时候调用,相当于要在类外调用构造函数,因此构造函数权限为 public。

  • 代码演示
// 定义一个类
class Data
{
private:
    int a;
    int b;
public:
    Data()
    {
        a = 0;
        b = 0;
        cout << "无参的构造函数被调用" << endl;
    }
    Data(int a1 ,int b1)
    {
        a = a1;
        b = b1;
        cout << "两个参数的构造函数被调用" << endl;
    }
};

void test06()
{
    // Data obj; // error: no matching function for call to 'Data::Data()'
    Data obj;
    Data obj1(1, 2);
}
  • 运行结果
无参的构造函数被调用
两个参数的构造函数被调用
  • 说明:
    1. 我们前面没有学习构造函数的时候,可以直接Data obj来实例化对象,而这里,我们定义了两个参数的构造函数以后,没有定义无参的构造函数,再去用Data obj实例化对象会报错:error: no matching function for call to 'Data::Data(),可以看到错误提示信息是 Data 类中没有 Data() 函数;
    2. 上面问题的原因是,如果不提供任何构造函数,编译器会为类提供一个默认的无参的构造函数,因此不定义任何构造函数的时候,通过Data obj实例化对象,其实是调用了默认的无参构造;
    3. 但定义了构造函数以后,Data obj创建对象失败是因为,用户提供任何一个构造函数都会屏蔽默认无参的构造函数;
    4. 因此,我们在定义构造函数的时候,最好定义一个无参构造。

1.3构造函数的调用

构造函数的调用形式,本质上也就是我们创建对象的形式,因为创建对象时主动触发构造函数调用。

  • 代码演示
// 定义一个类
class Data
{
private:
    int a;
    int b;
public:
    Data()
    {
        a = 0;
        b = 0;
        cout << "无参的构造函数被调用" << "a = " << a << " b = " << b << endl;
    }
    Data(int a1)
    {
        a = a1;
        b = 0;
        cout << "一个参数的构造函数被调用" << "a = " << a << " b = " << b << endl;
    }

    Data(int a1 ,int b1)
    {
        a = a1;
        b = b1;
        cout << "两个参数的构造函数被调用" << "a = " << a << " b = " << b  << endl;
    }
};

void test06()
{
    // 隐式调用
    cout << "--------------------隐式调用--------------------" << endl;
    Data obj;
    Data obj1(1);
    Data obj2(2, 3);

    // 显示调用
    cout << "--------------------显式调用--------------------" << endl;
    Data obj3 = Data();
    Data obj4 = Data(4);
    Data obj5 = Data(5, 6);

    // 为什么有参的隐式调用都加(),无参的不加
    cout << "--------------------无参加()--------------------" << endl;
    Data obj6();

    cout << "--------------------隐式转换--------------------" << endl;
    Data obj7 = 7;

    cout << "--------------------匿名对象--------------------" << endl;
    Data();
    Data(8);
    Data(9, 10);
}
  • 运行结果
--------------------隐式调用--------------------
无参的构造函数被调用a = 0 b = 0                 
一个参数的构造函数被调用a = 1 b = 0             
两个参数的构造函数被调用a = 2 b = 3             
--------------------显式调用--------------------
无参的构造函数被调用a = 0 b = 0                 
一个参数的构造函数被调用a = 4 b = 0             
两个参数的构造函数被调用a = 5 b = 6             
--------------------无参加()--------------------
--------------------隐式转换--------------------
一个参数的构造函数被调用a = 7 b = 0             
--------------------匿名对象--------------------
无参的构造函数被调用a = 0 b = 0                 
一个参数的构造函数被调用a = 8 b = 0             
两个参数的构造函数被调用a = 9 b = 10
  • 说明:
    1. 上面的演示,构造函数的调用有两种方式:隐式调用、显示调用,推荐使用隐式调用;
    2. 无参的构造在隐式调用的时候是不加 () 的,因为加了会产生冲突,如:Data obj6(),加了 () 以后就不是创建对象了,而是声明了一个名为 obj6 的函数,且函数的返回值是一个 Data 类型的对象;
    3. 当类中有一个参数的构造函数的时候,Data obj7 = 7这种写法会触发一个参数构造函数的隐式转换,不推荐这种写法;
    4. 创建对象的时候,可以定义一个变量名来接收创建的对象,当没有定义变量接收的时候,这里创建的对象叫匿名对象,匿名对象没有变量保存,也没有被使用的话,创建完就会释放。

2.析构函数

2.1析构函数的概述

析构函数:当对象生命结束的时候,系统自动调用析构函数,完成对象的清理工作。

构造函数:先为对象开辟空间,然后调用构造函数完成初始化。

析构函数:先调用析构函数,然后释放对象自身的空间。

  • 析构函数注意点:
    1. 如果不提供析构函数,系统会自动提供一个空的析构函数;
    2. 析构函数并不是清理对象自身的空间(由系统自动释放),而是清理指针成员指向的堆区空间(避免内存泄漏);
    3. 如果类中有指针成员且指向堆区,必须实现析构函数手动释放堆区空间。

2.2析构函数的定义

析构函数:~类名 称为析构函数名,析构函数没有返回值类型,连 void 都不可以,不能有参数,因此不能被重载。

  • 代码演示:还是上面的代码,加一个析构函数,看现象
// 定义一个类
class Data
{
private:
    int a;
    int b;
public:
    // 定义构造函数
    Data()
    {
        a = 0;
        b = 0;
        cout << "无参的构造函数被调用" << "a = " << a << " b = " << b << endl;
    }
    Data(int a1)
    {
        a = a1;
        b = 0;
        cout << "一个参数的构造函数被调用" << "a = " << a << " b = " << b << endl;
    }

    Data(int a1 ,int b1)
    {
        a = a1;
        b = b1;
        cout << "两个参数的构造函数被调用" << "a = " << a << " b = " << b  << endl;
    }
    // 定义析构函数
    ~Data()
    {
        cout << "调用析构函数" << "a = " << a << " b = " << b  << endl;
    }
};

void test06()
{
    // 隐式调用
    cout << "--------------------隐式调用--------------------" << endl;
    Data obj;
    Data obj1(1);
    Data obj2(2, 3);

    // 显示调用
    cout << "--------------------显式调用--------------------" << endl;
    Data obj3 = Data();
    Data obj4 = Data(4);
    Data obj5 = Data(5, 6);

    // 为什么有参的隐式调用都加(),无参的不加
    cout << "--------------------无参加()--------------------" << endl;
    Data obj6();

    cout << "--------------------隐式转换--------------------" << endl;
    Data obj7 = 7;

    cout << "--------------------匿名对象--------------------" << endl;
    Data();
    Data(8);
    Data(9, 10);
    cout << "--------------------析构函数--------------------" << endl;
}
  • 运行结果
--------------------隐式调用--------------------
无参的构造函数被调用a = 0 b = 0                 
一个参数的构造函数被调用a = 1 b = 0             
两个参数的构造函数被调用a = 2 b = 3             
--------------------显式调用--------------------
无参的构造函数被调用a = 0 b = 0                 
一个参数的构造函数被调用a = 4 b = 0             
两个参数的构造函数被调用a = 5 b = 6             
--------------------无参加()--------------------
--------------------隐式转换--------------------
一个参数的构造函数被调用a = 7 b = 0             
--------------------匿名对象--------------------
无参的构造函数被调用a = 0 b = 0                 
调用析构函数a = 0 b = 0                         
一个参数的构造函数被调用a = 8 b = 0             
调用析构函数a = 8 b = 0                         
两个参数的构造函数被调用a = 9 b = 10            
调用析构函数a = 9 b = 10                        
--------------------析构函数--------------------
调用析构函数a = 7 b = 0                         
调用析构函数a = 5 b = 6                         
调用析构函数a = 4 b = 0                         
调用析构函数a = 0 b = 0                         
调用析构函数a = 2 b = 3                         
调用析构函数a = 1 b = 0                         
调用析构函数a = 0 b = 0
  • 说明:
    1. 根据上面代码运行的结果可以看出,匿名对象,在定义以后没有变量保存其值,匿名对象也没有被使用,定义完立马就释放了;
    2. 而非匿名对象,这里的对象是在同一作用域定义的前提下,函数调用(当前复合语句)结束,调用析构函数,且满足先定义的后释放,后定义的先释放。

2.3析构顺序

前面的案例中,同级别的对象,先创建的后释放,当对象在不同作用域定义的时候呢?

  • 代码演示
// 定义一个类
class Data
{
private:
    int a;
public:
    Data()
    {
        a = 0;
        cout << "无参的构造函数被调用" << "a = " << a << endl;
    }
    Data(int a1)
    {
        a = a1;
        cout << "有参的构造函数被调用" << "a = " << a << endl;
    }
    ~Data()
    {
        cout << "析构函数被调用" << "a = " << a << endl;
    }
};

// 在全局创建对象
Data obj1(1);
Data obj2(2);
void test07()
{
    Data obj3(3);
    {
        Data obj4(4);
        Data obj5(5);
    }
    Data obj6(6);
}
  • 运行结果
有参的构造函数被调用a = 1
有参的构造函数被调用a = 2
有参的构造函数被调用a = 3
有参的构造函数被调用a = 4
有参的构造函数被调用a = 5
析构函数被调用a = 5      
析构函数被调用a = 4      
有参的构造函数被调用a = 6
析构函数被调用a = 6      
析构函数被调用a = 3      
析构函数被调用a = 2      
析构函数被调用a = 1
  • 说明:
    1. 上面的演示可以看出:对象的创建是按照代码执行的顺序依次执行的;
    2. 但是析构函数,会先遵循作用域的变量释放顺序,和前面学习的普通变量的释放顺序一样,即当前复合语句结束,变量就释放了。最先结束的符合语句里的对象最先释放,全局的对象最后释放。满足上述对象释放顺序的前提下,还满足同一作用域,先创建的后释放。

3.拷贝构造

3.1拷贝构造函数的定义

  • 代码演示
// 定义一个类
class Data
{
private:
    int a;
    int b;
public:
    // 定义构造函数
    Data()
    {
        a = 0;
        b = 0;
        cout << "无参的构造函数被调用" << endl;
    }
    Data(int a1, int b1)
    {
        a = a1;
        b = b1;
        cout << "有参的构造函数被调用" << endl;
    }
    // 定义拷贝构造函数
    Data(const Data &obj)
    {
        a = obj.a;
        b = obj.b;
        cout << "拷贝构造函数被调用" << endl;
    }
    // 显示对象信息的函数
    void showData()
    {
        cout << "a = " << a << " b = " << b << endl;
    }
};

void test08()
{
    // 先创建一个对象
    Data obj1(11, 22);
    // 再创建一个对象,并将旧对象赋值给它
    Data obj2 = obj1;
    obj2.showData();
}
  • 运行结果
有参的构造函数被调用
拷贝构造函数被调用
a = 11 b = 22
  • 说明:
    1. 如果用户没有提供拷贝构造函数,系统会提供默认拷贝构造函数,默认拷贝构造是浅拷贝,对应成员为普变量的话,浅拷贝足够了。但如果类中有指针成员且指向堆区, 必须实现拷贝构造函数,深拷贝;
    2. 拷贝构造函数定义和普通构造函数一样,类名作为函数名,只是参数不一样,参数const Data &obj中,obj 为形参名,加 & 表示引用传递,因为实参是一个对象,对象占用空间一般很大,引用传参节约空间。同时,参数要加 const 修饰,不允许函数内部修改传入的对象的数据。

3.2拷贝构造函数的调用

拷贝构造函数和构造函数一样,不需要手动调用,而是在特定的情况下自动触发调用。

拷贝构造函数是:创建一个新对象的时候,通过旧对象给新对象初始化,会自动调用拷贝构造函数,会自动将旧对象的引用作为实参传入拷贝构造函数,将旧对象的数据拷贝给新对象。

3.2.1普通对象作为函数的参数

普通对象作为函数的参数的时候,会调用拷贝构造。

  • 代码演示
// 定义一个类
class Data {
private:
    int a;
    int b;
public:
    // 定义构造函数
    Data() {
        a = 0;
        b = 0;
        cout << "无参的构造函数被调用" << endl;
    }

    Data(int a1, int b1) {
        a = a1;
        b = b1;
        cout << "有参的构造函数被调用" << endl;
    }

    // 定义拷贝构造函数
    Data(const Data &obj) {
        a = obj.a;
        b = obj.b;
        cout << "拷贝构造函数被调用" << endl;
    }
};

void func(Data obj){}
// void func(Data &obj){}

void test09() {
    // 创建一个对象
    Data obj1(11, 22);
    // 普通对象作为函数的参数
    func(obj1);
}
  • 运行结果
有参的构造函数被调用
拷贝构造函数被调用
  • 说明:
    1. 可以看到,上面的代码中,我们并没有像前面一样创建一个新对象,然后将旧对象的值赋给新对象,却发送了拷贝构造。其实这里也发生了旧对象赋值给新对象的动作,只是比较隐晦;
    2. 前面学习函数的时候知道,函数在定义的时候不占用空间,在调用的时候才占用空间,因为函数调用的时候会为形参开辟空间,然后定义一个形参指定类型的变量,将实参赋值给形参变量,而这里的形参就是一个新创建的 Data 类型的对象,实参是一个旧的对象,那么调用函数传参的过程就是一个旧对象给新对象赋值的过程,因此会调用拷贝构造;
    3. 为了防止拷贝构造被调用,一般将形参设置为引用传递,引用传递只是给旧对象起了个别名,没有旧对象给新对象赋值的过程,不会调用拷贝构造。
3.2.2普通对象作为函数的返回值

普通对象作为函数的返回值,外部通过一个新对象去接收,按照我们前面的理解,是将一个旧对象返回,赋值给新对象,理论上会发生拷贝构造,但事实并不一定如此,不同的高级编译器会有不同的结果。在 visual studio 里面会发生拷贝构造,但是在 Qt 或者 CLion 里面不会,具体可以在自己习惯使用的编译器里验证。

  • 这里演示 CLion 的
// 定义一个类
class Data {
private:
    int a;
    int b;
public:
    // 定义构造函数
    Data() {
        a = 0;
        b = 0;
        cout << "无参的构造函数被调用" << endl;
    }

    Data(int a1, int b1) {
        a = a1;
        b = b1;
        cout << "有参的构造函数被调用" << endl;
    }

    // 定义拷贝构造函数
    Data(const Data &obj) {
        a = obj.a;
        b = obj.b;
        cout << "拷贝构造函数被调用" << endl;
    }

    void showData()
    {
        cout << "a = " << a << " b = " << b << endl;
    }

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

Data func()
{
    Data obj1(11, 22);
    cout << &obj1 << endl;
    return obj1;
}

void test09() {
    Data obj2 = func();
    obj2.showData();
    cout << &obj2 << endl;
}
  • 运行结果
有参的构造函数被调用
0xc718fffd88  
a = 11 b = 22 
0xc718fffd88  
析构函数被调用
  • 说明
    1. 根据上面的运行结果,可以看到,只调用了一次构造函数,没有调用拷贝构造,而且明明创建了两个对象,但是结果却只析构了一次;
    2. 打印两个对象的内存地址,发现内存地址相同,结合上面的分析,可以知道,这两个变量本质上是指向同一个空间;
    3. 因此,可以得出结论, func 函数调用完成以后,局部变量 obj1 被删除了,但它指向的空间并没有被释放,而是由 obj2 指向了同一个空间,等到 obj2 释放的时候,空间才会释放。
3.2.3无参、有参、拷贝构造总结
  1. 如果用户提供了有参构造或拷贝构造会屏蔽默认的无参构造;
  2. 但用户提供有参构造或无参构造不会屏蔽默认拷贝构造,只有用户提供拷贝构造,才会屏蔽默认拷贝构造;
  3. 如果类中有指针成员且指向堆区,必须实现析构函数和拷贝构造函数(深拷贝);
  4. 构造函数实例化对象自动调用,先开辟空间,后调用构造函数,可以重载;
  5. 析构函数,对象结束的时候自动调用,先调用析构函数,后释放对象自身空间,不能重载。

标签:03,调用,函数,对象,定义,C++,int,析构,构造函数
From: https://blog.csdn.net/qq_63958145/article/details/144871366

相关文章

  • 高级java每日一道面试题-2025年01月03日-并发篇-什么是Callable和Future?
    如果有遗漏,评论区告诉我进行补充面试官:什么是Callable和Future?我回答:Callable定义与功能:Callable是Java5引入的一个接口,用于定义可并发执行的任务。它类似于Runnable接口,但提供了更多的功能。Callable可以在执行完成后返回结果,而Runnable无法返回任何结果。Call......
  • 高级java每日一道面试题-2025年01月03日-并发篇-索引是什么?
    如果有遗漏,评论区告诉我进行补充面试官:索引是什么?我回答:在Java高级面试中,“索引”这个概念可以涉及到多个方面,包括但不限于数据库中的索引、Java集合框架中的索引(如List接口)、以及某些数据结构或算法中的索引。为了提供一个详尽的解释,我们将从不同角度来探讨“......
  • 01.03 CW 模拟赛 T2. game
    思路先把赛时的思路搬一下你发现确定两个人的起始点,其实是可以确定\(\rm{Alice}\)的选点可能的,考虑写个代码验证一下具体的,就是分成两个弧,\(\rm{Alice}\)可以选择一个弧的优势(过半),然后其他的劣势感觉现在是猜结论,全靠感性,我也不知道怎么解释这个问题那么......
  • 25.01.03
    喜欢我\(O(n^2\log^2n)\)过\(2e5\)吗......
  • C++程序运行的三种方式
    一、例程编一个程序,计算机随机产生一个整数(1至5),自己输入一个整数,若两数相同,则输出“恭喜你,中奖了!奖金10元”,否则输出“没中奖,请付费2元”;同时公布中奖号码。#include<iostream>#include<cstdlib>//要用到定义在其中的rand()、srand()函数#include<ctime>//......
  • C++期末总复习last day 20250103
    内容主体来自于QJH先生,向其表示敬意。C++高级程序设计题目类型简述题(5题、25分)什么是数据抽象与封装?相比于过程抽象与封装,数据抽象与封装有什么好处?……(不用死记硬背)程序分析题(5题、40分)指出下面程序的错误和错误原因写出下面程序的运行结果。写出下面程序的运行......
  • CH32V203F6P6-TSSOP20测试之03---三种烧录方式
      CH32V203F6P6-TSSOP20支持三种下载方式:USB下载、串口下载(用串口2即8脚PA2为TX2接下载的RX,9脚PA3为RX2接下载的TX)和SWD两线下载。  CH32V203F6P6-TSSOP20的BOOT1内置接GND,而BOOT0外露,用户可以选择两种启动模式,因而支持USB下载和串口下载。接法可以选择下面两种方法的其......
  • C++课程设计,c++餐厅管理系统
    本系统主要只用于c++课程设计、毕业设计。其功能如图:(每个功能执行后都可返回初始页面继续选择执行功能)1.信息查询:餐厅信息、服务员信息、菜单信息、查看评价(1)查询餐厅信息:(2)查询服务员信息:(3)查询菜单:(4)查看评价:2.信息录入:(1)添加菜品信息:包括菜品的编号、名称、价格......
  • C++中的数组与指针
    在大多数C++书籍或教程中,数组和指针的知识总是放在一起让大家学习,这是为什么,它们之间有什么联系呢?在C++中,数组与指针有着紧密的联系,主要体现在下面几个方面:1、数组名即指针:本质联系:在大多数情况下,数组名会被隐式转换为指向数组第一个元素的指针。例如,对于一个数组intarr[5];......
  • 01.03 CW 模拟赛 T1. math
    前言赛场上\(\rm{while}\)打成\(\rm{if}\)痛失\(40\rm{pts}\)不过下来看是贪心的话也没什么好做的了,一般都不会对了这是题目题目下载\(\rm{sol}\)方法\(1\):逐位计算思路显然的是你需要把数字从大到小填入,使得高位的数尽量大,这个显然由上面的结论可以知道......