C++基础复习·三

1.构造函数与析构函数

问题:构造函数是什么原理呢?在Test t1(10, 20);中,t1是一个类对象,不是一个函数名, 怎么会和函数调用一样直接双括号加实参呢?他的实现原理是什么呢? 同样Test t1 = Test(10, 20);该构造函数又没有返回值,怎么会作为右值呢?

    class  Test
    {
    public:
        Test(int x, int y)
        {
            m_x = x;
            m_y = y;
        }
    private:
        int m_x;
        int m_y; 
    };
    main::
    Test t1(10, 20);
    Test t1 = Test(10, 20);/*产生一个匿名类对象,将该匿名类对象转正为t1变量,未调用等号操作符进行赋值操作*/

2.默认构造函数和默认析构函数

如果显示定义一个构造函数和析构函数,那么就会覆盖掉默认的构造函数和析构函数

类中会有一个默认的无参构造函数,一个默认的拷贝构造函数,一个默认的等号操作符
1.当没有任何显示的构造函数(显式的无参构造函数,显式的有参构造函数,显式的拷贝构造函数),默认无参构造函数都会被调用
2.当没有**显示拷贝构造函数**,默认的拷贝构造函数就会调用
3.当没有**显示的析构函数**,默认的析构函数就会被调用

3.拷贝构造函数

编译器会有一个默认的拷贝构造函数
单纯的将另一个对象的成员变量拷贝给自己

    void operator=(const Test& another)
    {
        m_x = another.m_x;
        m_y = another.m_y;
    }
    Test(const Test& another)
    {
        m_x = another.m_x;
        m_y = another.m_y;
    }
    main::
    //调用有参数的构造函数
    Test t2(10, 20);
    t2.printTest();
    //如果不写Test(const Test& another),系统会自动加上这种构造函数,其实默认的拷贝构造函数
    //做的就是将另一个对象的成员变量全部拷贝给自己的成员变量
    Test t3(t2);
    t3.printTest();

    //两种调用拷贝构造函数的方法是等价的
    Test t4 = t2;
    t4.printTest();
    //----------------分隔符------------
    //这个是定义t5时调用无参的构造函数
    Test t5;
    //等号操作符重载,这是一个赋值操作  
    t5 = t1;

    //注意,拷贝构造函数会引出深拷贝和浅拷贝问题

4.构造函数的调用顺序(六种场景)(重点)

void test1()
{
    //构造函数:谁先创建,先调用谁
    Test t1(10, 20);
    Test t2(t1);//Test t2 = t1;
    //析构函数:谁先创建,后调用谁
    //原理就是栈的入栈出栈,对象定义后进行压栈,释放时进行出栈
}
void func(Test t)//Test t = t1; //Test t 的拷贝构造函数
{
    cout << "func begin..." << endl;
    t.printT();
    cout << "func end..." << endl;
}

void test3()
{
    cout << "test3 begin..." << endl;
    Test t1(10, 20);

    func(t1);

    cout << "test3 end..." << endl;
}

输出:
test3 begin…
Test(int x, int y)…
Test(const Test &)… //调用fun时,相当于 Test t = t1,调用t的拷贝构造函数
func begin…
x = 10, m_y = 20 //调用t.printT();
func end… //cout << “func end…” << endl;
~Test()… //调用t的析构函数
test3 end… //cout << “test3 end…” << endl;
~Test()… //调用t1的析构函数


Test func2(){
    cout << "func2 begin..." << endl;
    Test temp(10, 20);
    temp.printT();

    cout << "func2 end..." << endl;

    return temp;//这一步调用拷贝构造函数,匿名的对象 = temp  匿名对象.拷贝构造(temp)
}//↑  这个临时变量temp到这个位置时才会调用析构函数

void test4(){
    cout << "test4 being.. " << endl;
    func2();/*返回一个匿名对象。 当一个函数返回一个匿名对象的时候,函数外部没有任何变量去接收它, 这个匿名对象将不会再被使用,(找不到), 编译会直接将个这个匿名对象*/
        //回收掉,而不是等待整改函数执行完毕再回收.
        //匿名对象就被回收。

    cout << "test4 end" << endl;
}

输出:
test4 being..
func2 begin…
Test(int x, int y)…
x = 10, m_y = 20
func2 end…
Test(const Test &)…
~Test()…//先将temp析构
~Test()…//返回的临时变量因为没有变量承接,func2()调用完毕后编译器就就把这个临时变量析构
test4 end


Test& func2(){
    cout << "func2 begin..." << endl;
    Test temp(10, 20);
    temp.printT();
    cout << "func2 end..." << endl;
    return temp;
}
void test4(){
    cout << "test4 being.. " << endl;
    Test t1 = func2();//返回一个temp的引用
    t1.printT();
    cout << "test4 end" << endl;
}

输出:
test4 being..
func2 begin…
Test(int x, int y)…
x = 10, m_y = 20
func2 end…
~Test()… //将temp析构掉
Test(const Test &)… //拷贝构造,Test t1 = func2();会返回一个temp的引用
x = -858993460, m_y = -858993460
test4 end
~Test()…


Test func2(){
    cout << "func2 begin..." << endl;
    Test temp(10, 20);
    temp.printT();
    cout << "func2 end..." << endl;
    return temp;
}//匿名的对象 = temp  匿名对象.拷贝构造(temp)

void test5(){
    cout << "test 5begin.. " << endl;
    Test t1 = func2(); 
    //会不会触发t1拷贝构造来   t1.拷贝(匿名)?
    //并不会触发t1拷贝,而是 将匿名对象转正 t1,
    //把这个匿名对象 起了名字就叫t1.
    cout << "test 5 end.." << endl;
}

输出:
test 5begin..
func2 begin…
Test(int x, int y)…
x = 10, m_y = 20
func2 end…
Test(const Test &)…
~Test()…
test 5 end..
~Test()…


Test func2(){
    cout << "func2 begin..." << endl;
    Test temp(10, 20);
    temp.printT();
    return temp;
}
void test6(){
    cout << "test6 begin..." << endl;
    Test t1; //t1已经被初始化了。
    t1 = func2();
    t1.printT();
    cout << "test6 end.." << endl;
}

输出:
test6 begin…
Test()…
func2 begin…
Test(int x, int y)…
x = 10, m_y = 20
func2 end…
Test(const Test &)…
~Test()…
Test(const Test &)…
~Test()… //匿名对象在完成赋值后就会被释放掉
x = 10, m_y = 20
test6 end..
~Test()…


5.深拷贝和浅拷贝

触发时机:调用了类中默认的拷贝构造函数或者默认的等号操作符

浅拷贝示例

class A
{
private:
    int _ma;
    int _mb;
    char *_mch;
public:
    A(int a, int b, char *ch)
    {
        cout << "A(int a, int b, char *ch)" << endl;
        _ma = a;
        _mb = b;
        int len = strlen(ch);
        _mch = (char *)malloc(sizeof(char)*(len + 1));
        strcpy(_mch, ch);
    }
    //A(A &_newa)
    //{
    //}
    ~A()
    {
        cout << "~A()" << endl;
        if (_mch != NULL)
        {
            free(_mch);
            _mch = NULL;
        }
    }
};
void fun1()
{
    A a1(10, 20, "lily");
    A a2(a1);
}
//定义a1时调用有参数的构造函数,在堆区分配了块内存空间,将文字常量区的内容拷贝到该区域
//定义a2时调用默认的拷贝构造函数,默认的拷贝构造函数的主要逻辑就是将a1内存中的所有内容拷贝到a2中
//这种做法虽然对普通形参没有太大的影响,但是对于指针变量而言,隐藏了一个错误

//默认的拷贝构造函数将a1中的内容全部拷贝到a2中后,a2._mch也会指向a1._mch所指向的内存区域
//在fun1函数结束时,a2先调用析构函数,将a2._mch所指向的内存区域释放掉
//接着a1调用析构函数将a1._mch所指向的内存区域再释放一遍,这样对同一块内存区域释放两次,程序会报错
//解决这个问题,可以通过一个深拷贝来完成,在类中定义一个拷贝构造函数,再在堆中创建一个内存区域

A(A &_newa)
{
    _ma = _newa._ma;
    _mb = _newa._mb;
    int len = strlen(_newa._mch);
    _mch = (char *)malloc(sizeof(char)*(len + 1));
    strcpy(_mch, _newa._mch);
}
//这样两个对象的两个指针分别指向两块不同的内存区域

6.构造函数初始化列表

1).类对象中包含另一个类的对象
2).如果类对象中包含两个或多个类的对象,在构造函数的初始化列表中的成员对象的初始化顺序
class A
{
public:
    A(int a)
    {
        cout << "A()..." << a << endl;
        m_a = a;
    }
    ~A() 
    {
        cout << "~A()" << endl;
    }
    void printA() 
    {
        cout << "a = " << m_a << endl;
    }
private:
    int m_a;
};
class B
{
public:
    B(A &a1, A &a2, int b) :m_a1(a1), m_a2(a2)//调用默认的拷贝构造函数
    {
        //如果没有构造函数形参列表
        //这样定义一个类B中包含有两个类A的对象,在创建B的对象时,初始化成员变量m_a1,m_a2就出现了问题
        //1.不能直接m_a1 = a1,这是调用类A的等号操作符进行赋值
        //2.也不能直接 m_a1(a1);这样是吧m_a1当成函数来看待,当然这样是错误的
        //所以将两个成员变量m_a1,m_a2放在形参列表中初始化

        cout << "B(A &a1, A &a2, int b)" << endl;
        m_b = b;
    }
//构造对象成员的顺序跟初始化列表的顺序无关
//跟成员变量的定义顺序有关
//定义在前的先被初始化
    B(int a1, int a2, int b) : m_a1(a1), m_a2(a2)
    //  B(int a1, int a2, int b) : m_a2(a2), m_a1(a1)
    {
        cout << "B(int, int, int)..." << endl;

        m_b = b;
    }
    void printB() 
    {
        cout << "b = " << m_b << endl;
        m_a1.printA();
        m_a2.printA();
    }
    ~B()
    {
        cout << "~B().." << endl;
    }
private:
    int m_b;
    A m_a1;
    A m_a2;
};
void test1()
{
    A a1(10), a2(100);
    B b(a1, a2, 1000);

    b.printB();
}

7.new和delete关键字

new和delete关键字同malloc和free的相同点

new和malloc一样都是在堆中分配一块内存,然后返回该内存在堆中的地址.
delete和free一样,都是将堆中的指定内存释放掉

用new开辟的空间可以用free来释放掉
用malloc开辟的空间可以用delete来释放掉

new和delete关键字同malloc和free的不同点

A).malloc和free是标准库stdlib.h中定义的函数,既然是函数,那在调用时就存在入栈出栈操作.而new和delete关键字是C++的操作符,根C中的sizeof一样,不存在函数调用时的入栈出栈操作
B).用6)中的类A来说
    A *temp = (A*)malloc(sizeof(A));
    //此时malloc不会将成员变量初始化,如果这时候调用temp.printA()输出的则是乱码

    A* temp = new A(10);
    //new关键字可以在堆中创建一个A空间,同时调用A的构造函数将该空间进行了初始化操作,返回该空间的地址
C).new和delete会触发类的构造函数和析构函数
class Dog
{
public:
    Dog(int id,char *name)
    {
        cout << "Dog(int id)" << endl;
        m_id = id;
        int len = strlen(name);
        m_name = (char *)malloc(sizeof(char)*(len+1));
        strcpy(m_name,name);
    }
    ~Dog()
    {
        cout << "~Dog()" << endl;
        if(m_name != NULL)
        {
            free(m_name);
            m_name = NULL;
        }
    }
    void showDog()
    {
        cout << "showDog()" << endl;
    }
private:
    int m_id;
    char *m_name;
};
void fun1()
{
    Dog *d1 = new Dog(10,"SUNNY");
    d1->showDog();

    if (d1 != NULL)
    {
        delete d1;
    }

}
/*
    Dog(int id)
    showDog()
    ~Dog()
*/

//在堆中创建了一个类的对象空间,里面有int,和char*两个成员---->空间A
//在构造函数中另外创建了一个m_name空间,用来存储Dog的名字---->空间B
//调用 delete d1;时delete负责释放的是“空间A”,它触发了类的析构函数,而析构函数负责释放“空间B”     
//析构函数不是释放对象本身,而是负责释放对象在堆上额外开辟的内存空间

8.static成员变量

初始化方法

    static成员变量必须在类的外部进行初始化
    类型 类名::静态成员变量 = 值
class Box
{
private:
    int len;
    int width;          
public:
    static int high;
    Box(int l, int w) 
    {
        len = l;
        width = w;
    }
    void volume()
    {
        cout << "high = " << high << endl;
        cout << "volume = " << len*width*high << endl;
    }           
};

    int Box::high = 10;//static成员变量初始化一定要在类外进行

在public区

Box b1(10, 20);
b1.volume();

Box::high = 20;//static成员变量放在public区,可以在类外进行修改访问
b1.volume();

在private区

class Box2
{
private:
    int len;
    int width;
    static int high;
public:
    Box2(int l, int w)
    {
        len = l;
        width = w;
    }
    void volume()
    {
        cout << "high = " << high << endl;
        cout << "volume = " << len*width*high << endl;
    }
    static void setHigh(int h)//如果static成员变量放在private区域,进行修改时必须通过静态成员函数进行修改
    {
        high = h;
    }
};

Box2 b2(10, 20);
b2.volume();
Box2::setHigh(20);//因为是静态的,可以通过类名加::进行访问
b2.volume();

static 成员类外存储,求类大小,并不包含在内

cout << "sizeof(Goods) = " << sizeof(Goods) << endl;
//8个字节,int len; int width;
//static int high;在data区,不包含在类内
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值