[类和对象]2 构造和析构 1.构造和析构 2.赋值构造函数的4种应用场景 3.二个系统提供的特殊构造函数 4.深拷贝和浅拷贝 5.多个对象的构造和析构:对象初始化列表 6.[专题]匿名对象 7.对象的动态建立和释放

 

有关构造函数 

1构造函数定义及调用

  1C++中的类可以定义与类名相同的特殊成员函数,这种与类名相同的成员函数叫做构造函数;

  2)构造函数在定义时可以有参数;

  3)没有任何返回类型的声明。

2构造函数的调用 

  自动调用:一般情况下C++编译器会自动调用构造函数

  手动调用:在一些情况下则需要手工调用构造函数

有关析构函数 

析构函数定义及调用

  1C++中的类可以定义一个特殊的成员函数清理对象,这个特殊的成员函数叫做析构函数

    语法:~ClassName()

  2)析构函数没有参数也没有任何返回类型的声明  

  3析构函数在对象销毁自动被调用

  4)析构函数调用机制

    C++编译器自动调用     

          先创建的对象 后释放

Q对象销毁是发生在什么时候?

A对象的析构函数在的对象销毁前被调用,对象何时销毁也与其作用域有关。

例如,全局对象是在程序运行结束时销毁,自动对象是在离开其搜索作用域时销毁,而动态对象则是在使用delete运算符时销毁。


无参构造函数

有参构造函数[c++编译器默认调用有参构造函数] 

赋值构造函数 

#include <iostream>
using namespace std;

class Test2
{
public:
//无参数构造函数    
    Test2() 
    {
        m_a = 0;
        m_b = 0;
        cout<<"无参数构造函数"<<endl;
    }

//有参数构造函数  
    Test2(int a)
    {
        m_a = a;
        m_b = 0;
    }

    Test2(int a, int b) 
    {
        m_a = a;
        m_b = b;
        cout<<"有参数构造函数"<<endl;
    }

//赋值构造函数 (copy构造函数) 
    Test2(const Test2& obj)
    {
        cout<<"我也是构造函数 " <<endl;
    }

public:
    void printT()
    {
        cout<<"普通成员函数"<<endl;
    }
    
private:
    int m_a;
    int m_b;
};

int main01()
{    
    Test2 t1;  //调用无参数构造函数
    cout<<"hello..."<<endl;

    return 0;
}



//调用 调用有参数构造函数 3
int main(void)
{
    //1括号法 
    Test2 t1(1, 2);  //调用参数构造函数  c++编译器自动的调用构造函数
    t1.printT();

    // 2 =号法[但这是一个很鸡肋的用法]
    Test2 t2 = (3, 4); // = c++对等号符 功能增强  c++编译器自动的调用构造函数
    
  

    //对象的初始化 和 对象的赋值 是两个不同的概念 
    //3 对象的初始化
    Test2 t4 = Test2(1, 2);  //匿名对象
    t1 =  t4;  //把t4 copy给 t1  //赋值操作
//4 赋值构造函数 (copy构造函数) Test2 t5 = Test2(t1); //等价于Test2 t5(t1); 等价于 Test2 t5 = t1;

//☆ Test2 t5 = Test2(t1);是程序员手工的调用构造函数

cout
<<"hello..."<<endl; return 0; } /*============================================== END File ==============================================*/

有参数构造函数
普通成员函数
有参数构造函数
我也是构造函数
hello...


2.赋值构造函数的4种应用场景

这是初始化的两种; Test t2 = t1; Test t2(t1);


第三种是功能函数的形参是一个对象的时候{非指针,非引用},这个时候实参给形参的时候必然调用拷贝构造


第四种是你说的返回,返回一个对象的时候,但这个时候返回的是匿名对象,

  如果是初始化,是直接会转换为要初始化的那个对象的,而且不会被析构掉
  如果是赋值操作,这个时候确实会调用copy构造,但是这个对象的生命周期只有那么一行,用完立即被析构

 2.1 copy构造第一, 二种

#include <iostream>

using namespace std;

class Test4
{
public:
    Test4()  //无参数构造函数
    {
        m_a = 0;
        m_b = 0;
        cout<<"无参数构造函数"<<endl;
    }

    Test4(int a)
    {
        m_a = a;
        m_b = 0;
    }

    Test4(int a, int b) //有参数构造函数  
    {
        m_a = a;
        m_b = b;
        cout<<"有参数构造函数"<<endl;
    }

    Test4(const Test4& obj )//赋值构造函数 (copy构造函数) 
    {
        cout<<"赋值构造函数 " <<endl;
        m_b = obj.m_b + 100;
        m_a = obj.m_a + 100;
    }                                                                

public:
    void printT()
    {
        cout<<"普通成员函数"<<endl;
        cout<<"m_a"<<m_a<<" m_b"<<m_b<<endl;
    }
private:
    int m_a;
    int m_b;
};


//1  赋值构造函数 用1个对象去初始化另外一个对象  
int main(void)
{
    Test4 t0(1, 2);    
    Test4 t1(1, 2);

    /*赋值操作 和 初始化是两个不同的概念 
        =的赋值操作   不会调用构造函数
        =的初始化操作   会调用构造函数 */ 
    //operator=()//抛砖
    t0 = t1; //用t1 给 t0赋值  

    //第1种调用方法
    Test4 t2 = t1; //用t1来初始化 t2     【one】 
    t2.printT();
    
    //第2种调用方法
    Test4 t3(t1);  //用t1对象 初始化 t2对象   【two】 
    t2.printT();
    
    cout<<"hello..."<<endl;

    return 0;
}

2.2 copy构造第三种

//[Three]
#include <iostream> using namespace std; class Location { public: Location( int xx = 0 , int yy = 0 ) { X = xx ; Y = yy ; cout << "None Param Constructor Func. " ; } //copy构造函数 完成对象的初始化 Location(const Location & obj) //copy构造函数 { cout << "Copy Constructor func. " ; X = obj.X; Y = obj.Y; } ~Location() { cout << X << "," << Y << " Object destroyed." << endl ; } int GetX () { return X ; } int GetY () { return Y ; } private: int X , Y ; }; //业务函数 形参是一个元素 void f(Location p) { cout<<"业务函数"<<endl; cout<<p.GetX()<<endl; } void playobj() { Location a(1, 2); Location b = a; cout<<"b对象已经初始化完毕"<<endl; f(b); //实参b传给形参p会调用copy构造函数 } int main() { playobj(); cout<<"hello..."<<endl; return 0; }

None Param Constructor Func.
Copy Constructor func.
b对象已经初始化完毕
Copy Constructor func.
业务函数
1
1,2 Object destroyed.
1,2 Object destroyed.
1,2 Object destroyed.
hello...

2.3 copy构造第四种

//[Four]
#include <iostream> #include <cstdio> using namespace std; class Location { public: Location( int xx = 0 , int yy = 0 ) { X = xx ; Y = yy ; cout << "Constructor Object. " ; } //copy构造函数 完成对象的初始化 Location(const Location & obj) //copy构造函数 { X = obj.X; Y = obj.Y; } ~Location() { cout << X << "," << Y << " Object destroyed." << endl ; } int GetX () { return X ; } int GetY () { return Y ; } private : int X , Y ; } ; /******************************************************************************************************************* g函数 返回一个元素 结论1 : 函数的返回值是一个元素 (复杂类型的), 返回的是一个新的匿名对象(所以会调用匿名对象类的copy构造函数) 结论2: 有关 匿名对象的去和留 如果用匿名对象 初始化 另外一个同类型的对象, 匿名对象 转成有名对象 如果用匿名对象 赋值给 另外一个同类型的对象, 匿名对象 被析构 你这么写代码,设计编译器的大牛们: 我就给你返回一个新对象(没有名字 匿名对象) *******************************************************************************************************************/ Location g() { Location A(1, 2); return A; } // void objplay2() { g(); } // void objplay3() { //用匿名对象初始化m 此时c++编译器 直接把匿名对转成m;(扶正) 从匿名转成有名字了m Location m = g(); printf(" 匿名对象,被扶正,不会析构掉 "); cout<<m.GetX()<<endl; } void objplay4() { //用匿名对象 赋值给 m2后, 匿名对象被析构 Location m2(1, 2); m2 = g(); printf(" 因为用匿名对象=给m2, 匿名对象,被析构 "); cout<<m2.GetX()<<endl;; } int main(void) { objplay2(); // objplay3(); // objplay4(); cout<<"hello..."<<endl; return 0; }

3.二个系统提供的特殊构造函数

1) 默认无参构造函数

    当类中没有定义构造函数时,编译器默认提供一个无参构造函数,并且其函数体为空

2) 默认拷贝构造函数

    当类中没有定义拷贝构造函数时,编译器默认提供一个默认拷贝构造函数,简单的进行成员变量的值复制

1)当类中没有定义任何一个构造函数时, c++编译器会提供默认无参构造函数和默认拷贝构造函数
2)当类中定义了拷贝构造函数时, c++编译器不会提供无参数构造函数
3)当类中定义了任意的非拷贝构造函数(即:当类中提供了有参构造函数或无参构造函数), c++编译器不会提供默认无参构造函数
4 )默认拷贝构造函数成员变量简单赋值
总结:只要你写了构造函数,那么你必须用。

构造析构阶段性总结
1)构造函数是C++中用于初始化对象状态特殊函数
2)构造函数在对象创建时自动被调用, 在对象离开其作用域时候自动销毁
3)构造函数和普通成员函数都遵循重载规则
4)拷贝构造函数是对象正确初始化的重要保证
5) 必要的时候,必须手工编写拷贝构造函数  eg:深拷贝和浅拷贝

4.深拷贝和浅拷贝

 默认拷贝构造函数可以完成对象的数据成员值简单的赋值
 对象的数据资源是由指针指示的堆时,默认复制构造函数仅作指针值复制

[类和对象]2 构造和析构
1.构造和析构
2.赋值构造函数的4种应用场景
3.二个系统提供的特殊构造函数
4.深拷贝和浅拷贝
5.多个对象的构造和析构:对象初始化列表
6.[专题]匿名对象
7.对象的动态建立和释放

[类和对象]2 构造和析构
1.构造和析构
2.赋值构造函数的4种应用场景
3.二个系统提供的特殊构造函数
4.深拷贝和浅拷贝
5.多个对象的构造和析构:对象初始化列表
6.[专题]匿名对象
7.对象的动态建立和释放

上图分析:
1. Name obj1("abcdefg");
2. Name obj2 = obj1; //C++编译器提供的 默认的copy构造函数 【浅拷贝】
因为类Name里没有copy构造函数,所以要使用默认的copy构造函数,
但这种拷贝只是浅拷贝,只会拷贝p 和 len 的数值,并不会对obj2进行开辟内存
所以,当析构的时候,先析构obj2,但在析构obj1的时候,发生内存错误
解决方案:Name(const Name& obj1)

下图分析:
3. Name obj4("obj4");
4. obj4 = obj1; // C++编译器提供的 等号操作 也属 浅拷贝
同理
解决方案: void operator=(Name &obj4)

#define  _CRT_SECURE_NO_WARNINGS 
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>

using namespace std;

class  Name
{
public:
    Name(const char *myp)
    {
        cout<<"普通构造函数"<<endl;
        m_len = strlen(myp);//strlen只关心字符串长度[不含字符串结束标志' ']
        m_p =(char *) malloc(m_len + 1); // +1 是因为 ' ',否则会内存越界  
        strcpy(m_p, myp);
    }

    
    //解决方案: 手工的编写拷贝构造函数 使用 【深拷贝】  本质上为构造一块内存区域,而不是浅拷贝里简单的数值复制 
    Name(const Name& obj1)   //Name obj2 = obj1;
    {
        cout<<"手工的编写拷贝构造函数"<<endl;
        m_len = obj1.m_len;
        m_p = (char *)malloc(m_len + 1);
        strcpy(m_p, obj1.m_p);
    }

    ~Name()
    {
        cout<<"析构了..."<<endl;
        if (m_p != NULL)
        {
            free(m_p);
            m_p = NULL;
            m_len = 0;
        }
    }
    
    void operator=(Name &obj4)
    {
        if (m_p != NULL)
        {
            free(m_p);
            m_p = NULL;
            m_len = 0;
        }
        cout<<"等号操作符重载"<<endl;

        //用obj4来=自己
        m_p = (char *)malloc(obj4.m_len + 1);
        strcpy(m_p, obj4.m_p);
        m_len = obj4.m_len;
    }  
    
protected:
private:
    char *m_p ;
    int m_len; 
};

//对象析构的时候 出现coredump
void objplaymain()
{

    Name obj1("abcdefg"); //普通构造函数

    Name obj2 = obj1;//用手工编写的copy构造函数进行初始化

    Name obj3("obj4"); //普通构造函数
    obj3 = obj1;//等号操作符重载

    Name obj4(obj1); //用手工编写的copy构造函数进行初始化

}

int main(void)
{
    objplaymain();
    cout<<"hello..."<<endl;

    return 0;
}

5.多个对象的构造和析构:对象初始化列表

1)对象初始化列表出现原因
  1.必须这样做:
      如果我们有一个类成员,它本身是一个类或者是一个结构而且这个成员它只有一个带参数的构造函数,没有默认构造函数。

      这时要对这个类成员进行初始化,就必须调用这个类成员的带参数的构造函数,如果没有初始化列表,那么他将无法完成第一步,就会报错
    

  2、类成员中若有const修饰,必须在对象初始化的时候,给const int m 赋值
      当类成员中含有一个const对象时,或者是一个引用时,他们也必须要通过成员初始化列表进行初始化,
      因为这两种对象要在声明后马上初始化,而在构造函数中,做的是对他们的赋值,这样是不被允许的。


2)C++中提供初始化列表对成员变量进行初始化
  语法规则
  Constructor::Contructor() : m1(v1), m2(v1,v2), m3(v3)
  {
    // some other assignment operation
  }[B(int _b1, int _b2) : a1(1), a2(2), c(0)]


3)注意概念
  初始化:被初始化的对象正在创建
  赋值:被赋值的对象已经存在

#include <iostream>
using namespace std;

class A
{
public:
    A(int _a)
    {
        a = _a;
        cout << "Class A 构造函数" << "a:" << a << endl;
    }

    ~A()
    {
        cout << "Class A 析构函数" << "a:" << a << endl;
    }

protected:
private:
    int a;
};

//0 类里如果复合了别的类成员,是没有默认的构造函数的,需要自己手动编写 格式就是对象初始化列表的格式

//1 构造函数的初始化列表  解决: 在B类中 组合了一个 A类对象 (A类设计了构造函数)
//根据构造函数的调用规则 设计A的构造函数, 必须要用;没有机会初始化A
//新的语法  Constructor::Constructor() : m1(v1), m2(v1,v2), m3(v3)

//类成员中若有const修饰,必须在对象初始化的时候,给const int m 赋值 
//当类成员中含有一个const对象时,或者是一个引用时,他们也必须要通过成员初始化列表进行初始化

//2 先执行 被组合对象的构造函数 
//如果组合对象有多个,按照定义(声明)的顺序, 而不是按照初始化列表的顺序

//3析构函数 : 和构造函数的调用顺序相反

//4 初始化列表 用来 给const 属性赋值 
class B
{
public:
    //B ojbB2(1, 2, 3, 4); 
    B(int _b1, int _b2, int m, int n, int k) : a1(m), a2(n), c(k) //构造函数始化列表 [无关] 
    {
        b1 = _b1;
        b2 = _b2;
        cout <<"B的构造函数"<<endl;
    }
    ~B()
    {
        cout<<"B的析构函数" <<endl;
    }

protected:
    
private://声明的顺序[有关]
    int b1;
    int b2;
    A a2;    
    A a1;
    const int c;
};


void obj10play()
{
    
    A a1(10);

    //1参数传递 
    B ojbB2(1, 2, 3, 4, 0);

    //2 调用顺序
    
    return ;
}

int main(void)
{
    obj10play();
    return 0;
}

Class A 构造函数a:10
Class A 构造函数a:4
Class A 构造函数a:3
B的构造函数
B的析构函数
Class A 析构函数a:3
Class A 析构函数a:4
Class A 析构函数a:10


6.[专题]匿名对象

ABCD(400, 500, 600); //临时[匿名]对象的生命周期,如果没有对象来接,只有这么一句话,如果有,则不用析构

7.对象的动态建立和释放

7.1 newdelete基本语法

1在软件开发过程中,常常需要动态地分配和撤销内存空间,例如对动态链表中结点的插入与删除。在C语言中是利用库函数mallocfree来分配和撤销内存空间的。

C++提供了较简便而功能较强的运算符newdelete来取代mallocfree函数。

注意: newdelete是运算符,不是函数,因此执行效率高。

2虽然为了与C语言兼容,C++仍保留mallocfree函数,但建议用户不用mallocfree函数,而用newdelete运算符。

new运算符的例子:

new int;       //开辟一个存放整数的存储空间,返回一个指向该存储空间的地址(即指针)
new int(100);  //开辟一个存放整数的空间,并指定该整数的初值为100,返回一个指向该存储空间的地址
new char[10];  //开辟一个存放字符数组(包括10个元素)的空间,返回首元素的地址
new int[5][4];  //开辟一个存放二维整型数组(大小为5*4)的空间,返回首元素的地址
float *p = new float (3.14159);  //开辟一个存放单精度数的空间,并指定该实数的初值为3.14159,将返回的该空间的地址赋给指针变量p

    3new和delete运算符使用的一般格式为:

[类和对象]2 构造和析构
1.构造和析构
2.赋值构造函数的4种应用场景
3.二个系统提供的特殊构造函数
4.深拷贝和浅拷贝
5.多个对象的构造和析构:对象初始化列表
6.[专题]匿名对象
7.对象的动态建立和释放

new分配数组空间时不能指定初值。

如果由于内存不足等原因而无法正常分配空间,则new会返回一个空指针NULL,用户可以根据该指针的值判断分配空间是否成功。

4)

[类和对象]2 构造和析构
1.构造和析构
2.赋值构造函数的4种应用场景
3.二个系统提供的特殊构造函数
4.深拷贝和浅拷贝
5.多个对象的构造和析构:对象初始化列表
6.[专题]匿名对象
7.对象的动态建立和释放

7.2 类对象的动态建立和释放

使用类名定义的对象都是静态的,在程序运行过程中,对象所占的空间是不能随时释放的。

但有时人们希望在需要用到对象时才建立对象,在不需要用该对象时就撤销它,释放它所占的内存空间以供别的数据使用。这样可提高内存空间的利用率。[类似于C中的动态内存分配]

C++中,可以用new运算符动态建立对象,用delete运算符撤销对象
比如:

Box *pt;  //定义一个指向Box类对象的指针变量pt
pt=new Box;  //在pt中存放了新建对象的起始地址

在程序中就可以通过pt访问这个新建的对象


cout<<pt->height;  //输出该对象的height成员
cout<<pt->volume();  //调用该对象的volume函数,计算并输出体积

Box *pt=new Box(12,15,18);

这种写法是把上面两个语句(定义指针变量和用new建立新对象)合并为一个语句,并指定初值。这样更精炼

新对象中的height,width和length分别获得初值12,15,18。调用对象既可以通过对象名,也可以通过指针。


在执行new运算时,如果内存量不足,无法开辟所需的内存空间,目前大多数C++编译系统都使new返回一个0指针值。只要检测返回值是否为0,就可判断分配内存是否成功。

在不再需要使用由new建立的对象时,可以用delete运算符予以释放。如

delete pt; //释放pt指向的内存空间


这就撤销了pt指向的对象。此后程序不能再使用该对象。
如果用一个指针变量pt先后指向不同的动态对象,应注意指针变量的当前指向,以免删错了对象。

 

7.3 基础用法

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>

using namespace std;

class Test
{
public:
    Test(int _a)
    {
        a = _a;
        cout<<"构造函数执行" <<endl;
    }

    ~Test()
    {
        cout<<"析构函数执行" <<endl;
    }

protected:
private:
    int a;
};

//1
//        malloc      free          c语言的函数
//        new        delete         c++的运算符


int main(void)
 {
/****************************
 * 分配基础类型
 ****************************/
    //c语言分配内存 
    int *p = (int *)malloc(sizeof(int));
    *p = 10;
    free(p);

    //c++语言分配内存
    int *p3 = new int(30);
    printf("*p3:%d 
", *p3);//30
    delete p3;

/****************************
 * 分配数组变量
 ****************************/
    //c语言分配数组
    int *ptr = (int *)malloc(sizeof(int) * 10);  //int array[10];
    ptr[0] = 1;
    free(ptr);

    //c++分配数组 
    int *pArray = new int[10] ;
    pArray[1] = 2;
    delete [] pArray; //数组不要把[] 忘记

    char *pArray2 = new char[25] ; //char buf[25]
    delete [] pArray2;
    
    
/****************************
 * 分配对象
 * new能执行类的构造函数   delete能执行类的析构函数
 ****************************/
    //c 
    Test *pT1 = (Test *)malloc(sizeof(Test));
    free(pT1);

    //c++
    Test *pT2 = new Test(10);//输出  构造函数执行 并初始化Test的变量为10
    delete pT2;//输出 析构函数执行

    
    cout<<"hello..."<<endl;
    return 0;
    
}

*p3:30
构造函数执行
析构函数执行
hello...