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区,不包含在类内

你可能感兴趣的:(C++基础)