C++深度解析教程笔记8

news2024/11/23 11:43:59

C++深度解析教程笔记8

  • 第17课 - 对象的构造(上)
    • 类定义中成员变量i和j的初始值?
    • 实验-成员变量的初始值
    • 对象初始化解决方案1
    • 实验-手动调用函数初始化对象
    • 对象初始化解决方案2:构造函数
    • 实验-构造函数
    • 小结
  • 第18课 - 对象的构造(中)
    • 带参数的构造函数
    • 对象定义和对象声明的区别
    • 实验-带参数的构造函数
    • 构造函数的调用
    • 实验-创建对象数组的步骤
    • 实验-数组类
    • 小结
  • 第19课 - 对象的构造(下)
    • 实验-默认的拷贝构造函数
    • 实验-深拷贝与浅拷贝
    • 实验-数组类的改进:增加拷贝构造函数
    • 小结
  • 第20课 - 初始化列表的使用
    • 实验
    • 实验-初始化列表
    • 实验-初始化const变量并修改值
    • 小结
  • 第21课 - 对象的构造顺序
    • 实验
    • 实验
    • 实验
    • linux结果
    • windows结果
    • 小结
  • 第22课 - 对象的销毁
    • 实验-销毁对象自动调用析构函数
    • 小结
  • 第23课 - 神秘的临时对象
    • 实验-临时对象
      • 代码优化
    • 实验-编译器对临时对象的优化
    • 小结
  • 第24课 - 经典问题解析二
    • 实验-多对象析构函数顺序
    • 实验-const成员函数
    • 实验-this的使用

本文学习自狄泰软件学院 唐佐林老师的 C++深度解析教程,图片全部来源于课程PPT,仅用于个人学习记录

第17课 - 对象的构造(上)

类定义中成员变量i和j的初始值?

class Test
{
private:
    int i;
    int j;
public:
    int getI() { return i; }
    int getJ() { return j; }
};

实验-成员变量的初始值

#include <stdio.h>

class Test
{
private:
    int i;
    int j;
public:
    int getI() { return i; }
    int getJ() { return j; }
};

Test gt;

int main()
{
    printf("gt.i = %d\n", gt.getI());//0
    printf("gt.j = %d\n", gt.getJ());//0
    
    Test t1;
    
    printf("t1.i = %d\n", t1.getI());//随机
    printf("t1.j = %d\n", t1.getJ());//随机
    
    Test* pt = new Test;
    
    printf("pt->i = %d\n", pt->getI());//随机
    printf("pt->j = %d\n", pt->getJ());//随机
    
    delete pt;
    
    return 0;
}
/*
E:\test>g++ 17-1.cpp

E:\test>a
gt.i = 0
gt.j = 0
t1.i = 14380624
t1.j = 0
pt->i = 39267488
pt->j = 0

E:\test>a
gt.i = 0
gt.j = 0
t1.i = 1928784
t1.j = 0
pt->i = 6499488
pt->j = 0

E:\test>a
gt.i = 0
gt.j = 0
t1.i = 13921872
t1.j = 0
pt->i = 38808736
pt->j = 0


*/

对象的本质上就是变量
在栈和堆上创建对象时,成员变量的初始值是随机的
在静态存储区创建对象时,成员变量初始值为0

对象初始化解决方案1

在类中提供public的initialize函数,对象创建后手动调用此函数进行初始化

#include <stdio.h>

class Test
{
private:
    int i;
    int j;
public:
    int getI() { return i; }
    int getJ() { return j; }
    void initialize()
    {
        i = 0;
        j = 0;
    }
};

实验-手动调用函数初始化对象

#include <stdio.h>

class Test
{
private:
    int i;
    int j;
public:
    int getI() { return i; }
    int getJ() { return j; }
    void initialize()
    {
        i = 1;
        j = 2;
    }
};

Test gt;

int main()
{
    gt.initialize();
    
    printf("gt.i = %d\n", gt.getI());//1
    printf("gt.j = %d\n", gt.getJ());//2
    
    Test t1;
    
    //t1.initialize();
    
    printf("t1.i = %d\n", t1.getI());//随机值
    printf("t1.j = %d\n", t1.getJ());//随机值
    
    t1.initialize();
    
    Test* pt = new Test;
    
    pt->initialize();
    
    printf("pt->i = %d\n", pt->getI());//1
    printf("pt->j = %d\n", pt->getJ());//2
    
    delete pt;
    
    return 0;
}

对象初始化解决方案2:构造函数

initialize初始化的问题:普通函数,必须显示调用;若未调用,运行结果不确定
C++中与类名相同的成员函数叫构造函数
特点:没有任何返回类型的声明;构造函数在对象定义时自动被调用

实验-构造函数

#include <stdio.h>

class Test
{
private:
    int i;
    int j;
public:
    int getI() { return i; }
    int getJ() { return j; }
    Test()//构造函数
    {
        printf("Test() Begin\n");
        
        i = 1;
        j = 2;
        
        printf("Test() End\n");
    }
};

Test gt;

int main()
{
    printf("gt.i = %d\n", gt.getI());
    printf("gt.j = %d\n", gt.getJ());
    
    Test t1;
    
    printf("t1.i = %d\n", t1.getI());
    printf("t1.j = %d\n", t1.getJ());
    
    Test* pt = new Test;
    
    printf("pt->i = %d\n", pt->getI());
    printf("pt->j = %d\n", pt->getJ());
    
    delete pt;
    
    return 0;
}
/*
Test() Begin
Test() End
gt.i = 1
gt.j = 2
Test() Begin
Test() End
t1.i = 1
t1.j = 2
Test() Begin
Test() End
pt->i = 1
pt->j = 2
*/

小结

每个对象在使用之前都应该初始化
类的构造函数用于对象的初始化
构造函数与类同名且无返回值
构造函数在对象定义时自动被调用

第18课 - 对象的构造(中)

带参数的构造函数

构造函数可以根据需要定义参数
一个类中可以存在多个重载的构造函数
构造函数的重载遵循C++重载的规则

class Test
{
public:
    Test() 
    { 
        printf("Test()\n");
    }
    Test(int v) //带参数的构造函数
    { 
        printf("Test(int v), v = %d\n", v);
    }
};

对象定义和对象声明的区别

对象定义:申请对象的空间并调用构造函数
对象声明:告诉编译器存在这样一个对象

实验-带参数的构造函数

#include <stdio.h>

class Test
{
public:
    Test() 
    { 
        printf("Test()\n");
    }
    Test(int v) //带参数的构造函数
    { 
        printf("Test(int v), v = %d\n", v);
    }
};

int main()
{
    Test t;      // 调用 Test()
    Test t1(1);  // 调用 Test(int v)
    Test t2 = 2; // 调用 Test(int v)
    
    int i(100);//<------->int i=100;
    
    printf("i = %d\n", i);
    
    return 0;
}
/*
E:\test>a
Test()
Test(int v), v = 1
Test(int v), v = 2
i = 100
*/

构造函数的调用

一般情况下,构造函数在对象定义时被自动调用
特殊情况下,需要手工调用构造函数

实验-创建对象数组的步骤

#include <stdio.h>

class Test
{
private:
    int m_value;
public:
    Test() 
    { 
        printf("Test()\n");
        
        m_value = 0;
    }
    Test(int v) 
    { 
        printf("Test(int v), v = %d\n", v);
        
        m_value = v;
    }
    int getValue()
    {
        return m_value;
    }
};

int main()
{
    Test ta[3] = {Test(), Test(1), Test(2)};      
    
    for(int i=0; i<3; i++)
    {
        printf("ta[%d].getValue() = %d\n", i , ta[i].getValue());
    }
    
    Test t = Test(100);
    
    printf("t.getValue() = %d\n", t.getValue());
    
    return 0;
}
/*
Test()
Test(int v), v = 1
Test(int v), v = 2
ta[0].getValue() = 0
ta[1].getValue() = 1
ta[2].getValue() = 2
Test(int v), v = 100
t.getValue() = 100

*/

示例
开发一个数组类解决原生数组的安全性问题
提供函数 获取数组长度、获取数组元素、设置数组元素
类的私有成员:长度、头指针
类的公有成员:构造函数、返回长度的函数、获取和设置元素的函数、free()函数

实验-数组类

//头文件
#ifndef _INTARRAY_H
#define _INTARRAY_H
class IntArray
{
private:
    int m_length;
    int * m_pointer;
public:
    IntArray(int len);
    int length();
    bool getvalue(int index,int& value);
    bool setvalue(int index,int value);
    void free();


};//;
#endif

//.cpp
#include "IntArray.h"
IntArray::IntArray(int len)
{
    m_length=len;
    m_pointer=new int[len];//(int*)malloc(sizeof(int)*len);
    for(int i=0;i<len;i++)
    {
        m_pointer[i]=0;
    }



}
int IntArray::length()
{
    return m_length;

}

bool IntArray::getvalue(int index,int& value)//int & value
{
    bool res=(index>=0)&&(index<m_length);
    if(res)
    {

        value=m_pointer[index];
    }
    return res;
}
bool IntArray::setvalue(int index,int value)
{
    bool res=(index>=0)&&(index<m_length);
    if(res)
    {

        m_pointer[index]=value;
    }
    return res;

}
void IntArray::free(){
    delete[] m_pointer;


}

//main
#include <stdio.h>
#include "IntArray.h"

int main()
{
    IntArray a(5);
    a.setvalue(2,2);
//    for(int i=0;i<a.length();i++)
//    {
//
//        printf("a[%d]=%d\n",i,a[i]);
//    }
//    for(int i=0; i<a.length(); i++)
//    {
//        a.setvalue(i,i + 3);
//    }

    for(int i=0; i<a.length(); i++)
    {
        int value = 0;

        if( a.getvalue(i, value) )
        {
            printf("a[%d] = %d\n", i, value);
        }
    }
    return 0;
}



/*
a[0] = 0
a[1] = 0
a[2] = 2
a[3] = 0
a[4] = 0
*/

小结

构造函数可以根据需要定义参数
构造函数之间可以存在重载关系
构造函数遵循C++中重载函数的规则
对象定义时会触发构造函数的调用
在一些情况下可以手动调用构造函数

第19课 - 对象的构造(下)

两种特殊的构造函数
无参构造函数 没有参数的构造函数
当类中未定义构造函数时,编译器默认提供一个无参构造函数,并且其函数体为空
拷贝构造函数 参数为const class_name&
当类中没有定义拷贝构造函数时,编译器默认提供一个拷贝构造函数,进行变量值的赋值

实验-默认的拷贝构造函数

#include <stdio.h>

class Test
{
private:
    int i;
    int j;
public:
    int getI()
    {
        return i;
    }
    int getJ()
    {
        return j;
    }
    Test(const Test& t)
    {
        i = t.i;
        j = t.j;
    }
    Test()
    {
    }/**/
};

int main()
{
    Test t1;
    Test t2 = t1;
    
    printf("t1.i = %d, t1.j = %d\n", t1.getI(), t1.getJ());
    printf("t2.i = %d, t2.j = %d\n", t2.getI(), t2.getJ());
    
    return 0;
}
/*
无拷贝构造函数、构造函数代码 结果
t1.i = 2, t1.j = 0
t2.i = 2, t2.j = 0
有拷贝构造函数、构造函数代码 结果
t1.i = 2, t1.j = 0
t2.i = 2, t2.j = 0
*/

浅拷贝深拷贝区别
拷贝后的对象 物理状态相同 逻辑状态相同
编译器提供的拷贝构造函数只进行浅拷贝

拷贝构造函数的意义 兼容c语言的初始化方式
初始化行为符合预期的逻辑

实验-深拷贝与浅拷贝

#include <stdio.h>

class Test
{
private:
    int i;
    int j;
    int* p;
public:
    int getI()
    {
        return i;
    }
    int getJ()
    {
        return j;
    }
    int* getP()
    {
        return p;
    }
    //case1
    /*
    Test(const Test& t)
    {
        i = t.i;
        j = t.j;
        p = new int;
        
        *p = *t.p;
    }*/
    Test(int v)
    {
        i = 1;
        j = 2;
        p = new int;
        
        *p = v;
    }
    void free()
    {
        delete p;
    }
};

int main()
{
    //Test t1;
    Test t1(3);
    Test t2=t1;
    
    //printf("t1.i = %d, t1.j = %d, *t1.p = %d\n", t1.getI(), t1.getJ(), *t1.getP());
    //printf("t2.i = %d, t2.j = %d, *t2.p = %d\n", t2.getI(), t2.getJ(), *t2.getP());
     printf("t1.i = %d, t1.j = %d, t1.p = %p\n", t1.getI(), t1.getJ(), t1.getP());
    printf("t2.i = %d, t2.j = %d, t2.p = %p\n", t2.getI(), t2.getJ(), t2.getP());
    
    t1.free();
    t2.free();
    
    return 0;
}

//case1下侧注释掉   浅拷贝
cyz@cyz-virtual-machine:~/桌面$ g++ test.cpp
cyz@cyz-virtual-machine:~/桌面$ ./a.out
t1.i = 1, t1.j = 2, t1.p = 0x61c63c0bbeb0
t2.i = 1, t2.j = 2, t2.p = 0x61c63c0bbeb0
free(): double free detected in tcache 2
已中止 (核心已转储)                          //报错,free2次p
//case1下侧不注释掉   深拷贝
cyz@cyz-virtual-machine:~/桌面$ g++ test.cpp
cyz@cyz-virtual-machine:~/桌面$ ./a.out
t1.i = 1, t1.j = 2, *t1.p = 3
t2.i = 1, t2.j = 2, *t2.p = 3
t1.i = 1, t1.j = 2, t1.p = 0x5aa51679ceb0    //b0
t2.i = 1, t2.j = 2, t2.p = 0x5aa51679ced0    //d0

手动定义拷贝构造函数后,各个对象的指针p的地址不一样了


注意:自定义拷贝构造函数,必然需要实现深拷贝

3
t1.m_pointer
t2.m_pointer

t1.free();// 0x61c63c0bbeb0
t2.free();// 0x61c63c0bbeb0
对象中有成员指代了系统中的资源,需要深拷贝

  • 成员指向了动态内存空间
  • 成员打开了外存中的文件
  • 成员使用了系统的网络端口

实验-数组类的改进:增加拷贝构造函数

//IntArray.h
#ifndef _INTARRAY_H
#define _INTARRAY_H
class IntArray
{
private:
    int m_length;
    int * m_pointer;
public:
    IntArray(int len);
    //IntArray(const IntArray& obj);//深拷贝
    int length();
    int* getpointer();
    bool getvalue(int index,int& value);
    bool setvalue(int index,int value);
    void free();


};//;
#endif


//IntArray.cpp
#include "IntArray.h"
IntArray::IntArray(int len)
{
    m_length=len;
    m_pointer=new int[len];//(int*)malloc(sizeof(int)*len);
    for(int i=0;i<len;i++)
    {
        m_pointer[i]=0;
    }



}
/*拷贝构造函数
IntArray::IntArray(const IntArray& obj)
{
    m_length=obj.m_length;
    m_pointer=new int[m_length];//(int*)malloc(sizeof(int)*len);
    for(int i=0;i<m_length;i++)
    {
        m_pointer[i]=obj.m_pointer[i];
    }

}*/
int IntArray::length()
{
    return m_length;

}
int* IntArray::getpointer()
    {
        return m_pointer;
    }

bool IntArray::getvalue(int index,int& value)//int & value
{
    bool res=(index>=0)&&(index<m_length);
    if(res)
    {

        value=m_pointer[index];
    }
    return res;
}
bool IntArray::setvalue(int index,int value)
{
    bool res=(index>=0)&&(index<m_length);
    if(res)
    {

        m_pointer[index]=value;
    }
    return res;

}
void IntArray::free(){
    delete[] m_pointer;


}



//main.cpp
#include "IntArray.h"

int main()
{
    IntArray a(5);
    IntArray b=a;
    a.setvalue(2,2);

    printf("apointer = %p\n", a.getpointer());

    for(int i=0; i<a.length(); i++)
    {
        int value = 0;

        if( a.getvalue(i, value) )
        {
            printf("a[%d] = %d\n", i, value);
        }
    }
    
    a.free();
    printf("bpointer = %p\n", b.getpointer());
    for(int i=0; i<b.length(); i++)
    {
        int value = 0;

        if( b.getvalue(i, value) )
        {
            printf("b[%d] = %d\n", i, value);
        }
    }


    b.free();

    return 0;
}



/*
默认拷贝构造函数
apointer = 001F1650
a[0] = 0
a[1] = 0
a[2] = 2
a[3] = 0
a[4] = 0
bpointer = 001F1650
b[0] = 2031808
b[1] = 2047592
b[2] = 2
b[3] = 0
b[4] = 0
//增加拷贝构造函数之后
apointer = 00761650
a[0] = 0
a[1] = 0
a[2] = 2
a[3] = 0
a[4] = 0
bpointer = 00761670
b[0] = 0
b[1] = 0
b[2] = 0
b[3] = 0
b[4] = 0

*/

linux下运行对比

//浅拷贝
cyz@cyz-virtual-machine:~/桌面$ g++ main.cpp IntArray.cpp
cyz@cyz-virtual-machine:~/桌面$ ./a.out
apointer = 0x5c9e21c70eb0
a[0] = 0
a[1] = 0
a[2] = 2
a[3] = 0
a[4] = 0
bpointer = 0x5c9e21c70eb0
b[0] = -907928464
b[1] = 5
b[2] = -348087256
b[3] = 1712401683
b[4] = 0
free(): double free detected in tcache 2
已中止 (核心已转储)
//深拷贝

apointer = 0x5844f6a32eb0
a[0] = 0
a[1] = 0
a[2] = 2
a[3] = 0
a[4] = 0
bpointer = 0x5844f6a32ed0
b[0] = 0
b[1] = 0
b[2] = 0
b[3] = 0
b[4] = 0


小结

C++编译器会默认提供构造函数
无参构造函数用于定义对象的默认初始状态
拷贝构造函数在创建对象时拷贝对象的状态
对象的拷贝方式区别:浅拷贝 深拷贝(对象使用了系统资源,手动编写拷贝构造函数)

第20课 - 初始化列表的使用

类中定义const成员

实验

#include <stdio.h>

class Test
{
private:
    const int ci;
public:
    Test()
    {
        ci = 10;
    }
    int getCI() 
    { 
        return ci; 
    }
};


int main()
{
    Test t;
    
    printf("t.ci = %d\n", t.getCI());
    
    return 0;
}
E:\test>g++ 20-1.cpp
20-1.cpp: In constructor 'Test::Test()':
20-1.cpp:8:5: error: uninitialized const member in 'const int' [-fpermissive]
     Test()
     ^~~~
20-1.cpp:6:15: note: 'const int Test::ci' should be initialized
     const int ci;
               ^~
20-1.cpp:10:14: error: assignment of read-only member 'Test::ci'
         ci = 10;

在这里插入图片描述
在这里插入图片描述
类成员的初始化
C++中提供了初始化列表对成员变量进行初始化
语法:

ClassName::ClassName:
m1(v1),m2(v1,v2),m3(v3)
{
	//初始化
}

实验-初始化列表

#include <stdio.h>

class Value
{
private:
    int mi;
public:
    Value(int i)
    {
        printf("i = %d\n", i);
        mi = i;
    }
    int getI()
    {
        return mi;
    }
};

class Test
{
private:
    Value m2;
    Value m3;
    Value m1;
public:
    Test() : m1(1), m2(2), m3(3)
    {
        printf("Test::Test()\n");
    }
};


int main()
{
    Test t;
    
    return 0;
}
/*
i = 2
i = 3
i = 1
Test::Test()
顺序跟成员声明顺序一致
*/

在这里插入图片描述

实验-初始化const变量并修改值

#include <stdio.h>

class Value
{
private:
    int mi;
public:
    Value(int i)
    {
        printf("i = %d\n", i);
        mi = i;
    }
    int getI()
    {
        return mi;
    }
};

class Test
{
private:
    const int ci;
    Value m2;
    Value m3;
    Value m1;
public:
    Test() : m1(1), m2(2), m3(3), ci(100)
    {
        printf("Test::Test()\n");
    }
    int getCI()
    {
        return ci;
    }
    void setCI(int v)
    {
        int* p = const_cast<int*>(&ci);
        
        *p = v;
    }
};


int main()
{
    Test t;
    
    printf("t.ci = %d\n", t.getCI());
    
    t.setCI(10);
    
    printf("t.ci = %d\n", t.getCI());
    
    return 0;
}
/*
i = 2
i = 3
i = 1
Test::Test()
t.ci = 100
t.ci = 10

*/

初始化:对正在创建的对象设置初始值
赋值:对已创建的对象设置值

小结

在这里插入图片描述

第21课 - 对象的构造顺序

定义多个对象,对象的构造顺序
在这里插入图片描述
在这里插入图片描述

实验

#include <stdio.h>

class Test
{
private:
    int mi;
public:
    Test(int i)
    {
        mi = i;
        printf("Test(int i): %d\n", mi);
    }
    Test(const Test& obj)
    {
        mi = obj.mi;
        printf("Test(const Test& obj): %d\n", mi);
    }
};

int main()
{
    int i = 0;
    Test a1 = i;
        
    while( i < 3 )
    {
        Test a2 = ++i;
    }
        
    if( i < 4 )
    {
        Test a = a1;
    }
    else
    {
        Test a(100);
    }

    return 0;
}
/*
Test(int i): 0
Test(int i): 1
Test(int i): 2
Test(int i): 3
Test(const Test& obj): 0

*/

在这里插入图片描述
在这里插入图片描述

实验

#include <stdio.h>

class Test
{
private:
    int mi;
public:
    Test(int i)
    {
        mi = i;
        printf("Test(int i): %d\n", mi);
    }
    Test(const Test& obj)
    {
        mi = obj.mi;
        printf("Test(const Test& obj): %d\n", mi);
    }
    int getMi()
    {
        return mi;
    }
};

int main()
{
    int i = 0;
    Test* a1 = new Test(i); // Test(int i): 0
        
    while( ++i < 10 )
        if( i % 2 )
            new Test(i); // Test(int i): 1, 3, 5, 7, 9
        
    if( i < 4 )
        new Test(*a1);
    else
        new Test(100); // Test(int i): 100
        
    return 0;
}
/*

Test(int i): 0
Test(int i): 1
Test(int i): 3
Test(int i): 5
Test(int i): 7
Test(int i): 9
Test(int i): 100

*/

在这里插入图片描述

实验

//test.h
#ifndef _TEST_H_
#define _TEST_H_

#include <stdio.h>

class Test
{
public:
    Test(const char* s)
    {
        printf("%s\n", s);
    }
};

#endif

//t1.cpp
#include "test.h"
Test t1("t1");

//t2.cpp
#include "test.h"
Test t2("t2");

//t3.cpp
#include "test.h"
Test t3("t3");

//21-3.cpp
#include "test.h"
Test t4("t4");
int main()
{
    Test t5("t5");
}


linux结果

cyz@cyz-virtual-machine:~/桌面$ g++ 21-3.cpp t2.cpp t1.cpp t3.cpp -o test.out
cyz@cyz-virtual-machine:~/桌面$ ./test.out
t4
t2
t1
t3
t5

windows结果

E:\test>g++ 21-3.cpp t2.cpp t1.cpp t3.cpp -o test.out
E:\test>test.out
t3
t1
t2
t4
t5

全局对象的构造顺序是不定的

小结

局部对象的构造顺序依赖于程序执行流
堆对象的构造顺序依赖于new的使用顺序
全局对象的构造顺序是不确定的

第22课 - 对象的销毁

清理要销毁的对象

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

实验-销毁对象自动调用析构函数

#include <stdio.h>

class Test
{
    int mi;
public:
    Test(int i)
    {
        mi = i;
        printf("Test(): %d\n", mi);
    }
    ~Test()
    {
        printf("~Test(): %d\n", mi);
    }
};

int main()
{
    Test t(1);
    
    Test* pt = new Test(2);
    
    delete pt;
    
    return 0;
}
/*
Test(): 1
Test(): 2
~Test(): 2
~Test(): 1

*/

在这里插入图片描述

小结

析构函数是对象销毁时进行清理的特殊函数
析构函数在对象销毁时自动被调用
析构函数是对象释放系统资源的保障

第23课 - 神秘的临时对象

实验-临时对象

#include <stdio.h>

class Test {
    int mi;
    //优化1位置
public:
    Test(int i) {
        mi = i;
    }
    Test() {
    //优化2位置
	       //case0
        //Test(0);//case1
        //Test(2);//case2
    }
    void print() {
        printf("mi = %d\n", mi);
    }
};


int main()
{
    Test t;
    
    t.print();

    return 0;
}
/*case0:
mi = 0

case1:
mi = 0

case2:
mi = 0
*/

代码优化

为避免使用临时对象,可在private内添加初始化函数
;在public的无参构造函数内调用

//优化1:
 void init(int i)
    {
        mi = i;
    }
//优化2:
// init(0);//output:mi = 0
 init(1);//output:mi = 1

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

实验-编译器对临时对象的优化

#include <stdio.h>

class Test
{
    int mi;
public:
    Test(int i)//带参数构造函数
    {
        printf("Test(int i) : %d\n", i);
        mi = i;
    }
    Test(const Test& t)//拷贝构造函数
    {
        printf("Test(const Test& t) : %d\n", t.mi);
        mi = t.mi;
    }
    Test()//无参构造函数
    {
        printf("Test()\n");
        mi = 0;
    }
    void print()
    {
        printf("mi = %d\n", mi);
    }
    ~Test()
    {
        printf("~Test()\n");
    }
};

Test func()
{
    return Test(20);
}

int main()
{
    Test t = Test(10); // ==> Test t = 10;
   //理论上:先定义了临时对象,再调用拷贝构造函数赋值给t,
   //实际:被编译器优化了
    Test tt = func();  // ==> Test tt = Test(20); ==> Test tt = 20;
    
    t.print();
    tt.print();
    
    return 0;
}
/*
Test(int i) : 10
Test(int i) : 20
mi = 10
mi = 20
~Test()
~Test()
*/

小结

直接调用构造函数将产生一个临时对象
临时对象是性能的瓶颈,也是bug来源
C++编译器会尽力避开临时对象
实际开发中要人为避开临时对象

第24课 - 经典问题解析二

多个对象析构时,析构顺序与构造顺序相反

在这里插入图片描述

实验-多对象析构函数顺序

#include <stdio.h>

class Member
{
    const char* ms;
public:
    Member(const char* s)
    {
        printf("Member(const char* s): %s\n", s);
        
        ms = s;
    }
    ~Member()
    {
        printf("~Member(): %s\n", ms);
    }
};

class Test
{
    Member mA;
    Member mB;
public:
    Test() : mB("mB"), mA("mA")//初始化列表
    {
        printf("Test()\n");
    }
    ~Test()
    {
        printf("~Test()\n");
    }
};

Member gA("gA");//全局对象

int main()
{
    Test t;
    
    return 0;
}
/*
Member(const char* s): gA
Member(const char* s): mA
Member(const char* s): mB
Test()
~Test()
~Member(): mB
~Member(): mA
~Member(): gA

先全局对象,再初始化列表,后进入构造函数
*/
```c

#include <stdio.h>

class Member
{
    const char* ms;
public:
    Member(const char* s)
    {
        printf("Member(const char* s): %s\n", s);
        
        ms = s;
    }
    ~Member()
    {
        printf("~Member(): %s\n", ms);
    }
};

class Test
{
    Member mA;
    Member mB;
public:
    Test() : mB("mB"), mA("mA")//初始化列表
    {
        printf("Test()\n");
    }
    ~Test()
    {
        printf("~Test()\n");
    }
};

Member gA("gA");//全局对象

int main()
{
    Test t;
    
    return 0;
}
/*
Member(const char* s): gA
Member(const char* s): mA
Member(const char* s): mB
Test()
~Test()
~Member(): mB
~Member(): mA
~Member(): gA

先全局对象,再初始化列表,后进入构造函数
*/

对于栈对象和全局对象,类似于入栈和出栈的顺序,最后构造的对象被最先析构。
堆对象的析构发生在使用delete的时候,与delete的使用顺序相关。

const修饰对象的特性
const修饰的对象为只读对象,其成员不允许被改变
只读对象是编译阶段的概念,运行时无效

C++中的const成员函数

  • const对象只能调用const的成员函数
  • const成员函数中只能调用const成员函数
  • const成员函数中不能直接改写成员变量的值
    在这里插入图片描述

实验-const成员函数

#include <stdio.h>

class Test
{
    int mi;
public:
int mj;
    Test(int i);
    Test(const Test& t);
   /* int getMi();*/
     /**/int getMi()const;
};

Test::Test(int i)
{
    mi = i;
}

Test::Test(const Test& t)
{
//调用普通函数会报错,只能调用const成员函数  t为const变量
mi=t.getMi();//mi=t.mi;
    
}
/**/
int Test::getMi()const//const成员函数
{
//mi=2;//试试改值   error: assignment of member 'Test::mi' in read-only object
return mi;

}
/*
 int Test::getMi()   //普通函数
{
    return mi;
}*/

int main()
{
     const Test t(1);
      //printf("t.getMi()=%d\n",t.getMi());// (调用普通函数)error: passing 'const Test' as 'this' argument discards qualifiers [-fpermissive]
	printf("t.getMi()=%d\n",t.getMi());//t.getMi()=1(调用const函数)
	//t.mj=2;// error: assignment of member 'Test::mj' in read-only object
 
    
    return 0;
}

成员函数和成员变量都是隶属于具体对象的吗

从面向对象的角度
对象由属性(成员变量)和方法()构成
从程序运行的角度
对象由数据和函数构成
数据可以位于栈,堆和全局数据区
函数只能位于代码段

在这里插入图片描述

实验-this的使用

#include <stdio.h>

class Test
{
    int mi;
public:
    int mj;
    Test(int i);
    Test(const Test& t);
    int getMi();
    void print();
};

Test::Test(int i)
{
    mi = i;
}

Test::Test(const Test& t)
{
    mi = t.mi;
}
    
int Test::getMi()
{
    return mi;
}

void Test::print()
{
    printf("this = %p\n", this);
}

int main()
{
    Test t1(1);
    Test t2(2);
    Test t3(3);
    
    printf("t1.getMi() = %d\n", t1.getMi());
    printf("&t1 = %p\n", &t1);
    t1.print();
    
    printf("t2.getMi() = %d\n", t2.getMi());
    printf("&t2 = %p\n", &t2);
    t2.print();
    
    printf("t3.getMi() = %d\n", t3.getMi());
    printf("&t3 = %p\n", &t3);
    t3.print();
    
    return 0;
}
/*
t1.getMi() = 1
&t1 = 000000000061FE18
this = 000000000061FE18
t2.getMi() = 2
&t2 = 000000000061FE10
this = 000000000061FE10
t3.getMi() = 3
&t3 = 000000000061FE08
this = 000000000061FE08

*/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1673300.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

乡村振兴的乡村人才振兴计划:培养乡村人才,吸引人才回流,激发乡村发展活力,为美丽乡村建设提供人才保障

目录 一、引言 二、乡村人才振兴计划的必要性 &#xff08;一&#xff09;乡村发展面临的人才困境 &#xff08;二&#xff09;人才振兴对乡村振兴的推动作用 三、乡村人才振兴计划的实施路径 &#xff08;一&#xff09;加强乡村教育体系建设 &#xff08;二&#xff0…

AD软件针对分散的元器件归一排列

先框选 然后快捷键TOL 绿色的十字箭头选框选位置 完成

Golang | Leetcode Golang题解之第87题扰乱字符串

题目&#xff1a; 题解&#xff1a; func isScramble(s1, s2 string) bool {n : len(s1)dp : make([][][]int8, n)for i : range dp {dp[i] make([][]int8, n)for j : range dp[i] {dp[i][j] make([]int8, n1)for k : range dp[i][j] {dp[i][j][k] -1}}}// 第一个字符串从 …

deveco studio 打开官方案例,不显示运行按钮。

就拿官方的search举例好了 git 地址 https://gitee.com/harmonyos/samples/tree/master/ETSUI/Search 使用deveco studio打开Search项目&#xff0c;打开Tools->Device-Manager中的Local Emulator本地模拟器&#xff0c; 此时会发现&#xff0c;运行按钮是灰色的&#xff0…

【前端性能优化】深入解析重绘和回流,构建高性能Web界面

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 &#x1f3af; 引言&#xff1a;探索Web性能的基石&#x1f3d7;️ 基础概念&#xff1a;什么是重绘和回流&#xff1f;&#x1f4cc; 回流&#xff08;Reflow&#xff09;&#x1f4cc; 重绘&#xff08;Repaint&#xff0…

阿赵UE引擎C++编程学习笔记——解决中文乱码问题

大家好&#xff0c;我是阿赵。   在UE编写C的时候&#xff0c;可能有些朋友发现&#xff0c;在C里面如果打印输出或者赋值一些中文的字符串的时候&#xff0c;会出现各种的报错&#xff0c;要么乱码&#xff0c;要么直接编译不过。   这个问题&#xff0c;其实和UE本身没什…

数字化信息协同助力智能巡查,基于YOLOv5全系列【n/s/m/l/x】参数模型开发构建无人机数字侦查场景下智能靶标检测识别系统

无人机的快速发展与普及&#xff0c;使得其进入千家万户各行各业&#xff0c;发挥着越来越重要的作用。随着科技的飞速发展&#xff0c;未来的数字信息化战场正逐渐展现出其独特的作战形态。在这个以数据和信息为主导的新战场上&#xff0c;无人机侦查手段与人工智能目标智能检…

vue3编译优化之“静态提升”

前言 在上一篇 vue3早已具备抛弃虚拟DOM的能力了文章中讲了对于动态节点&#xff0c;vue做的优化是将这些动态节点收集起来&#xff0c;然后当响应式变量修改后进行靶向更新。那么vue对静态节点有没有做什么优化呢&#xff1f;答案是&#xff1a;当然有&#xff0c;对于静态节…

AI论文写作生成器-AI自动生成论文-10分钟/万字论文

在当今这个高速发展的信息时代&#xff0c;科技的进步无疑给我们的工作、学习乃至生活带来了翻天覆地的变化。学术研究领域也不例外&#xff0c;近期一款新型的论文自动写作神器诞生了&#xff0c;它凭借人工智能技术的优势&#xff0c;为学者和研究人员撰写学术论文提供了极大…

每日一题11:Pandas:数据重塑-透视

一、每日一题 解答&#xff1a; import pandas as pddef pivotTable(weather: pd.DataFrame) -> pd.DataFrame:df_pivot weather.pivot(indexmonth, columnscity, valuestemperature)return df_pivot 题源&#xff1a;力扣 二、总结 Pandas 是一个强大的 Python 数据分析…

ANSYS Workbench中如何从面选择创建节点集合named selection?

点击选中一个面&#xff0c;右键named selection&#xff1a;

windows下安装redis

正常生产我们会在Linux下安装redis&#xff0c;windows下安装redis只做依赖环境的快速搭建、项目的快速验证。 1、下载地址 Releases microsoftarchive/redis GitHub 下载 Redis-x64-3.0.504.zip 2、解压文件夹 解压到本地某个文件夹下&#xff0c;比如 D:\redis-3.0.504 3…

STL---排序模板参数

map 对map进行排序 首先一定要注意map模板类的第三个模板参数&#xff0c;这个参数决定元素按键值升序或者降序在map中的存储方式&#xff1a; 默认&#xff1a;less<key>升序----- < -----第一个小于取第一个 可设置&#xff1a;greater<key>降序-------…

机器人系统ros2-开发学习实践11-从零开始构建视觉机器人模型(urdf)(02)

接上一个教程继续完善&#xff0c; 我们需要对机器人身体的蓝色&#xff0c;我们定义了一种名为“蓝色”的新材质&#xff0c;其中红色、绿色、蓝色和 alpha 通道分别定义为 0、0、0.8 和 1。所有值都可以在 [0,1] 范围内。然后该材料由 base_link 的视觉元素引用。白色材料的…

移动 App 入侵与逆向破解技术-iOS 篇

如果您有耐心看完这篇文章&#xff0c;您将懂得如何着手进行app的分析、追踪、注入等实用的破解技术&#xff0c;另外&#xff0c;通过“入侵”&#xff0c;将帮助您理解如何规避常见的安全漏洞&#xff0c;文章大纲&#xff1a; 简单介绍ios二进制文件结构与入侵的原理介绍入…

【C++】可变参数模板简单介绍

前言 可变参数模板是C11中的新特性&#xff0c;它能够让我们创建可以接收可变参数的函数模板和类模板&#xff0c;相比C98/03&#xff0c;类模版和函数模版中只能含固定数量的模版参数&#xff0c;可变模版参数是一个巨大的改进&#xff0c;通过系统系统推演数据的类型&#xf…

【论文阅读笔记】HermesSim(Code is not Natural Language) (Security 24)

个人博客地址 HermesSim [Security 24] 论文&#xff1a;《Code is not Natural Language: Unlock the Power of Semantics-Oriented Graph Representation for Binary Code Similarity Detection》 仓库&#xff1a;https://github.com/NSSL-SJTU/HermesSim 提出的问题 二…

OpenAI 刚刚宣布了 “GPT-4o“ 免费用户开放、通过 API 可用

OpenAI 刚刚宣布了 “GPT-4o”。它可以通过语音、视觉和文本进行推理。 该模型速度提高了 2 倍&#xff0c;价格降低了 50%&#xff0c;比 GPT-4 Turbo 的速率限制高出了 5 倍。 它将对免费用户开放、通过 API 可用。 与 GPT-4 相比&#xff0c;GPT-4o 的速度和额外的编码能力…

网站客服系统免费版_服务器版_永久免费用的在线客服系统_OctIM

OctIM是一款可以永久免费用的在线客服系统&#xff0c;对于广大初创型的企业来说&#xff0c;是一个很好的助力。初创公司因为前期的业务量以及访问流量不是很高&#xff0c;一般初步只需要满足访客的基本聊天咨询对话就可以了&#xff0c;所以&#xff0c;对于免费的可以永久使…