C++之构造函数-拷贝构造

一,类和对象

1,对象

万物皆对象,任何一种事物都可以看作是对象。

2,面向对象

2.1 如何描述对象

      通过对象的属性(名词、形容词、数量词)和行为(动词)表达对象。

冰箱属性:品牌、容量、颜色、功耗

冰箱行为:装东西、冷冻、冷藏..

2.2 面向对象的程序设计

    对自然界的观察引入到编程实践中的一种理念和方法

    这种方法得益于“数据抽象”,在描述对象时把细节的东西剥离出去,只考虑一般性,有规律性的,统一的东西

3 类

     类是将多个对象共性提取出来 定义的一种新的数据类型,是对 对象的属性和行为的抽象描述,对象是类的实例化。

       属性   行为

狗     犬种   进食

       犬龄   睡眠

       体重   玩耍

       毛色  

 

二, 类的定义与实例化

1 类的一般形式

class/struct 类名:继承方式 基类

{

访问控制限定符:

     类名(形参表):初始化表

    {     //构造函数

          //函数体

     };        

     ~类名(void)

   {

//析构函数

          //函数体

     }

     返回类型 函数名(形参表)

{//成员函数

           //函数体

     }

     数据类型  变量名;//成员变量

};

 

学生:

       属性      行为

       名字      吃饭

       年龄      睡眠

       学号      学习

 

2 访问控制限定符

public:公有成员,谁都可以访问该成员

private:私有成员,只有自己可以访问该成员

protected:保护成员。//后面讲

在C++类中class和struct没有本质的区别,唯一不同在于class的缺省访控属性为私有,而struct的访控属性为公有

 

三 ,类的定义与实例化

1 类的一般形式

class/struct 类名:继承方式 基类

{

访问控制限定符:

     类名(形参表):初始化表//构造函数

     {

              //函数体

     };        

     ~类名(void)//析构函数

     {

             //函数体

     }

     返回类型 函数名(形参表)//成员函数

     {

             //函数体

     }

     数据类型  变量名;//成员变量

};

 

2 访问控制限定符

public:公有成员,谁都可以访问该成员

private:私有成员,只有自己可以访问该成员

protected:保护成员。//后面讲

在C++类中class和struct没有本质的区别,唯一不同在于class的缺省访控属性为私有,而struct的访控属性为公有

 

3 构造函数(Constructors)

类名(构造形参表)[:初始化表]

{

     //构造函数体;

}

1)构造函数名与类名相同,且没有返回类型

2)构造函数在对象创建时自动被调用

3)构造函数负责确定对象初始化状态以及分配必要的内存

4)构造函数在每个对象的整个声明周期内,一定会被调用,且仅会被调用一次。

 

4 对象的创建和销毁

1)在栈中创建单个对象

类名 对象(构造实参表);

类名 对象 = 类名(构造实参表);

 

2)在栈中创建多个对象

类名 对象数组[元素个数] = {类名(构造实参表),...}

 

3) 在堆中创建/销毁单个对象

创建:

     类型* 对象指针 = new 类名(构造实参表);

销毁:

    delete 对象指针

4)在队中创建/销毁多个对象

创建:

    //这种初始化的写法,只在c++2011支持

    类名* 对象指针 = new 类名[元素个数]{类名(参数),...}

销毁:

    delete[]  对象指针

 

5 类的声明和定义可以分别放在不用的文件中

类声明部分放在xx.h

类实现部分放在xx.cpp

使用该类的代码通过在其它的XX.cpp中

例子:

#include <iostream>
#include <cstdio>

using namespace std;
class Clock
{
public:
    Clock(bool timer = false):m_hour(0),m_min(0),m_sec(0)
    {
        if(timer)
        {
            time_t t = time(NULL);
            tm* local = localtime(&t);
            m_hour = local->tm_hour;
            m_min = local->tm_min;
            m_sec = local->tm_sec;
        }
    }
    void run(void)
    {
        for(;;)
        {
            //显示时间
            show();
            //计时
            tick();
        }
    }
private:
    void show(void)
    {
        printf("\r%02d:%02d:%02d",m_hour,
                m_min,m_sec);
        fflush(stdout);
    }
    void tick(void)
    {
        sleep(1);
        if(++m_sec == 60)
        {
            m_sec = 0;
            if(++m_min == 60)
            {
                m_min = 0;
                if(++m_hour == 24)
                    m_hour = 0;
            }
        }
    }
    int m_hour;
    int m_sec;
    int m_min;
};
int main(void)
{
    //Clock clock(true);//时钟
    Clock clock;//计时器
    clock.run();
    return 0;
}

 

 

 

 

 

四, 构造函数和初始化表

1 构造函数重载

  构造函数通过参数表的差别化形成重载,创建对象通过构造实参选择匹配,表示不同对象的创建方式。

2 缺省构造函数

如果一个类没有定义构造函数,编译器会提供一个缺省构造函数(无参构造函数),使成员变量获得定义。

1)对于基本类型的成员变量不初始化

2)对类 类型的成员 变量,调用相应类型的无参构造函数。

3)如果定义了构造函数,无论是否有参数,编译器都不会再提供无参构造函数。

3 类型转换构造函数(单参构造函数)

class 目标类型

{

     目标类名(源类型  src){...}

};

使用explicit关键字修饰类型转换构造函数,可以强制这种转换必须显示地进行。

 

4 拷贝构造函数

类名 (const 类名& that){...}

1)用一个已经存在的对象构造同类型的副本对象时,会调用该类的拷贝构造函数

class A{..};

A a1;//无参方式构造

A a2=a1;//拷贝方式构造

2)如果一个类没有定义拷贝构造函数,那么编译器会为其提供一个缺省拷贝构造函数

 a,对基本类型的成员变量,按字节复制

 b,对类类型的成员变量(成员子对象),调用相应类型的拷贝构造函数。

3)如果自己定义拷贝构造函数,编译器将不再提供缺省拷贝构造函数,这时要实现成员复制有关操作,必须在自己

定义的拷贝构造函数中编码完成。

4)拷贝构造函数调用时机

 a.用已经定义对象作为同类型对象的构造实参

 b.以对象的形式向函数传递参数。

 c.从函数中返回对象(有时会被编译器优化掉)

5 构造函数初始化表

1)指明类中成员变量的初始化方式

  类名(形参表):成员变量1(参数1),成员变量2(常量)..

  {

      构造函数体;

  }

2)必须要使用初始化表的地方

a.如果有类 类型的成员变量,而该类又没有无参构造函数,则必须通过初始化初始化该成员变量。

b.类中包含"const"和引用"&"成员变量,必须在初始化表中显示的初始化。

c.成员变量初始化顺序由声明顺序决定,而与初始化表的顺序无关。

 

五 ,this指针与常函数

1.this指针

1)类的构造函数和成员函数中都隐藏一个该类类型的指针参数,名为this。

    a.对于普通的成员函数,this指针就是指向调用该函数的对象。

    b.对于构造函数,this指针就是指向这个正在被构造的对象。

 

2)this指针可以显示的使用,需要显示使用this指针的地方:

   a.区分作用域

   b.从成员函数中返回对象自身(自引用)

   c.从类的内部销毁对象自身(自销毁)

   d.作为函数的实参,从一个对象传递给另一个对象,实现对象间的交互。

 

2 常函数

1)在一个成员函数参数表后面加const,这个成员函数就称为常函数。

   返回类型  函数名(形参表) const{函数体}

2)常函数中this指针是一个常指针,不能在常函数中修改该成员变量的值。

3)被mutable关键字修饰的成员变量可以在常函数中被修改

4)非常对象可以调用常函数,可以调用非常函数,但是常对象只能调用常函数,不能调用非常函数。

5)函数名和形参表相同的成员函数,其常版本和非常版本可以构成重载关系,常对象调用常版本,非常对象调用非常版本

 

六 ,析构函数(Destructor)

1 析构是类的特殊的成员函数

 class 类名

 {

    ~类名(void){...}//析构函数

 };

 1)函数名必须“~类名”

 2)没有返回类型,也没有参数

 3)不能被重载,一个类只能有一个析构函数

 4)主要负责清理对象生命周期内动态产生的资源

 

2.当对象被销毁时,该对象的析构函数自动被执行

1)栈对象离开作用域时,其析构函数被作用域终止右花括号"}"调用;

2)堆对象的析构函数被delete运算符调用。

 

3 如果一个类没有显示定义析构函数,那么系统会为该类提供一个缺省析构函数:

1)对基类类型的成员变量,什么也不做;

2)对类 类型的成员变量,调用相应类的析构函数,析构成员子对象。

 

4 对象的创建和销毁的过程

1)对象的创建

   -->为对象分配内存

   -->按声明顺序依次调用成员子对象的构造函数

   -->执行构造函数体的代码

2)对象销毁

   -->执行析构函数的代码

   -->按声明逆序依次调用成员子对象的析构函数

   -->释放对象所占的内存空间

 

七 , 拷贝构造和拷贝赋值

1 浅拷贝和深拷贝

    如果一个类包含指针形式的成员变量,缺省拷贝构造函数只是赋值指针成员变量本身,而没有复制该指针所指向的内容,这种拷贝构造被称为浅拷贝。

    浅拷贝将导致不同对象间的数据共享,同时会在析构函数中引发"double free"异常,为此必须自己定义一个支持深拷贝的拷贝构造函数。

C++之构造函数-拷贝构造

C++之构造函数-拷贝构造

2 类的缺省拷贝赋值函数和缺省拷贝构造类似,是浅拷贝,为了得到深拷贝的赋值效果,就必须自己定义一个支持深拷贝赋值的运算符函数。

1)防止自赋值

2)释放旧资源

3)分配新资源

4)拷贝新数据

5)返回自引用

#include <iostream>
using namespace std;

class Integer
{
public:
    Integer(int data = 0):m_data(new int(data)){}
    /*缺省拷贝构造,浅拷贝
    Integer(const Integer& that):m_data(that.m_data){}*/
    /*自定义深拷贝*/
    Integer(const Integer& that):m_data(new int(*that.m_data)){}
    /*缺省拷贝赋值函数*/
    /*Integer& operator=(const Integer& that){
        m_data = that.m_data;
        return *this;
    }*/
    /*深拷贝赋值函数*/
    Integer& operator=(const Integer& that)
    {
        if(&that != this)
        {//防止自赋值
            delete m_data;//释放旧资源
            //分配新资源,拷贝新数据
            m_data = new int(*that.m_data);
        }
        return *this;//返回自引用
    }
    ~Integer(void){
        cout << "Integer::~Integer()" << endl;
        delete m_data;
        m_data = NULL;
    }
    int get(void){
        return *m_data;
    }
private:
    int* m_data;
};
int main(void)
{
    Integer i(100);
    cout << i.get() << endl;
    Integer i2(i);//拷贝构造
    cout << i2.get() << endl;
    Integer i3;
    i3 = i2;//拷贝赋值,i3.operator=(i2).
    cout << i3.get() << endl;
    return 0;
}