三、对象的构造与析构

一、构造函数与析构函数

1.1 初识构造函数

构造函数主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用, 无需手动调用。类似于python中的 __init__
析构函数主要用于对象销毁前系统自动调用,执行一定的清理工作。 类似于python的 __del__

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

class Person
{
public:
    // 构造函数写法
    // 与类名相同,没有返回值, 不写 void,可以发生重载 (可以有参数)
    // 构造函数由编译器自动调用,而且只会调用一次
    Person()
    {
        cout << "自动调用构造无参数函数" << endl;
    }

    // 析构函数写法
    // 与类型相同, 类名前面加一个符号 "~",也没有返回值,不写 void, 不可以有参数(不能发生重载)
    // 自动调用, 也只会调用一次
    ~Person()
    {
        cout << "自动调用析构函数" << endl;
    }
};

void test()
{
    Person p1;  // 默认调用了构造和析构,是系统提供的两个空的函数, 一般自己写
}
int main()
{
    test();
    return EXIT_SUCCESS;
}

 

1.2 构造函数的分类

1. 按照参数进行分类,无参数构造函数(默认构造函数),有参构造函数
2. 按照类型进行分类, 普通构造函数, 拷贝构造函数

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

// 分类

// 1. 按照参数进行分类,无参数构造函数(默认构造函数),有参构造函数

// 2. 按照类型进行分类, 普通构造函数, 拷贝构造函数

class Person
{
public:  // 构造和析构必须在 public下才可以调用
    Person()  // 默认、无参构造函数
    {
        cout << "默认、无参构造函数" << endl;
    }
    Person(int a)  // 有参构造函数
    {
        cout << "有参构造函数" << endl;
    }

    Person(const  Person &p)   // 拷贝构造函数
    {
        age = p.age;
        cout << "拷贝构造函数" << endl;
    }

    int age;
};


void test01()
{
    // 构造函数调用
    // 括号法调用
    Person p0;   // 默认、无参构造函数, 不加括号。 Person p0(), 默认是函数声明
    Person p1(1);  // 有参构造函数
    p1.age = 30;
    Person p2(p1); // 拷贝构造函数
    cout << "p2.age = " << p2.age << endl;  // p2.age = 30;

    // 显示法调用
    Person p4 = Person(100);  // 有参构造函数
    Person p5 = Person(p4);   // 拷贝构造函数
    Person(100);  // 匿名对象,匿名对象特点:如果编译器发现了对象是匿名的,那么在这行代码结束后就释放这个对象
    // 不能用拷贝构造函数,初始化匿名对象
    // Person(p5);  报错, 这样写编译器认为写成了 Person p5对象的声明,p5重定义了
    Person p6 = 100; // 有参构造函数, 相当于 (Person p6(100)) == (Person p6 = Person(100)); 隐式类型转换
    Person p7 = p6;  // 拷贝构造函数, 相当于 Person p7 = Person(p6);
}

int main()
{
    test01();
    return EXIT_SUCCESS;
}

 

二、拷贝构造函数的调用时机

1. 用已经创建好的对象来初始化新的对象
2. 以值传递的方式给函数参数传值
3. 以值的方式返回局部对象

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

class Person
{
public:
    Person()
    {
        cout << "默认、无参构造函数" << endl;
    }
    Person(int a)
    {
        cout << "有参构造函数" << endl;
    }
    Person(const Person &p)  // 没有 & 就是值传递, 值传递调用的时候 Person p需要拷贝一份新的空间, 新的空间调用了类中此函数, 一直循环调用自身了 ,死循环了。
    {
        age = p.age;
        cout << "拷贝构造函数" << endl;
    }
    ~Person()
    {
        cout << "析构函数" << endl;
    }
    int age;
};

// 拷贝构造函数的调用时机
// 1. 用已经创建好的对象来初始化新的对象
void test()
{
    Person p1;
    p1.age = 20;
    Person p2(p1);
}
// 2. 以值传递的方式给函数参数传值
void doWord(Person p1) // 相当于 Person p1 = Person(p)
{

}

void test02()
{
    Person p;  // 默认、无参构造函数
    p.age = 20;
    doWord(p);  // 拷贝构造函数
}

// 3. 以值的方式返回局部对象
Person doWork2()
{
    Person p1;
    return p1;
}

void test03()
{
    Person p = doWork2();
    /*
    release下会优化
    Person p; // 此时还不调用默认构造
    doWorrk2(p);
    void doWork2(Person &p)
    {
        Person p1;  // 调用默认构造
    }
    */
}

int main()
{
    test();
    test02();
    test03();
    return EXIT_SUCCESS;
}

 

三、构造函数的调用规则

默认情况下, C++编译器至少为我们类提供三条函数:
  1. 默认构造函数 (无参,函数体为空)
  2. 默认析构函数 (无参,函数体为空)
  3. 拷贝构造函数 对类中非静态成员属性进行简单值拷贝

如果用户定义拷贝构造函数, C++ 不会在提供任何默认构造函数
如果用户定义了有参构造, C++不再提供无参构造,但是会提供拷贝构造函数

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

class MyClass
{
public:
    
    MyClass(int a)
    {
        cout << "有参构造函数" << endl;
    }

    int age;
    
};

// 系统默认给一个类提供默认构造函数, 拷贝构造函数, 析构函数

// 1. 当我们提供了有参构造函数,系统就不会在提供默认构造函数。但是系统还会提供默认拷贝构造函数,进行简单的值拷贝
void test01()
{
    MyClass c1(1);
    c1.age = 27;
    MyClass c2(c1);  
    /*
    如果先 MyClass c2(c1);  再c1.age = 27;那么c2.age就是随机数了,因为先值拷贝,此时 c1.age尚未赋值。
    */
    cout << "c2.age = " << c2.age << endl;  // c2.age = 27

}

// 2. 当我们提供了拷贝构造,系统就不会提供 其他构造了
class MyClass02
{
public:

    MyClass02(const MyClass02 &c)
    {
        cout << "拷贝构造函数" << endl;
    }
    int age;
};

void test02()
{
    // MyClass02 c1;  // 报错
    // MyClass02 c2(10);  // 报错
}


int main()
{
    test01();
    test02();
    return EXIT_SUCCESS;
}

 

 

四、深浅拷贝

系统提供的拷贝构造,会进行简单的值拷贝,如果过属性里面有指向堆区空间的数据,那么简单的浅拷贝会导致重复释放内存的异常,解决上述问题,需要我们自己提供拷贝构造函数们进行深拷贝

4.1 浅拷贝

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
#include<string>
using namespace std;

class Person
{
public:
    // 初始化属性
    Person(char *name, int age)
    {
        Name = (char *)malloc(strlen(name) + 1);
        strcpy(Name, name);
        Age = age;
    }

    // 拷贝构造, 系统会默认提供一个拷贝构造,而且是简单的值拷贝

    // 析构函数
    ~Person()
    {
        cout << "析构函数调用" << endl;
        if (Name != NULL)
        {
            free(Name);
            Name = NULL;
        }
    }


    char* Name;
    int Age;

};

void test01()
{
    Person p1("wangyong", 27);
    Person p2(p1);   
    // 此函数这样操作会直接崩溃。因为运行结束会调用析构函数, 析构中释放了 p1中Name所指向的地址
    // 但是在拷贝构造中, p2会直接值拷贝一份 p1的数据,将p1中的Name的地址也直接值拷贝了一份,释放完了
    // p1的Name指针以后, p2的Name指针指向的还是原来的地址,结束的时候也会释放所指向的空间,就报错了
    // 这样的值拷贝场景就成为浅拷贝。
}

int main()
{
    test01();
    return EXIT_SUCCESS;
}

4.2 深拷贝

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
#include<string>
using namespace std;

class Person
{
public:
    // 初始化属性
    Person(char *name, int age)
    {
        Name = (char *)malloc(strlen(name) + 1);
        strcpy(Name, name);
        Age = age;
    }

    // 拷贝构造, 系统会默认提供一个拷贝构造,而且是简单的值拷贝
    // 自己提供拷贝构造,原因简单的浅拷贝释放堆区空间两次,导致挂掉
    // 自己提供拷贝构造成为深拷贝
    Person(const Person &p)
    {
        Age = p.Age;
        Name = (char *)malloc(strlen(p.Name) + 1);
        strcpy(Name, p.Name);
    }

    // 析构函数
    ~Person()
    {
        cout << "析构函数调用" << endl;
        if (Name != NULL)
        {
            free(Name);
            Name = NULL;
        }
    }


    char* Name;
    int Age;

};

void test01()
{
    Person p1("wangyong", 27);
    Person p2(p1);
}

int main()
{
    test01();
    return EXIT_SUCCESS;
}

 

五、多个对象的构造与析构

5.1 常规的有参构造传参赋值

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
#include<string>
using namespace std;

class Person
{
public:
    // 有参构造初始化数据
    Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
    string Name;
    int Age;
};

void test01()
{
    Person p1("wangyong", 27);
    cout << "p1.Name = " << p1.Name << endl;
    cout << "p1.Age = " << p1.Age << endl;
}
int main()
{
    test01();
    return EXIT_SUCCESS;
}

 

5.2 利用初始化列表初始化数据

// 构造函数  + : 属性(参数)
#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
#include<string>
using namespace std;

class Person
{
public:
    Person() : Name("王勇"), Age(28)
    {
    }
    // 利用初始化列表初始化数据
    // 构造函数  + : 属性(参数)
    Person(string name, int age) :Name(name), Age(age)
    {
    }

    string Name;
    int Age;
};

void test01()
{
    Person p1("wangyong", 27);
    cout << "p1.Name = " << p1.Name << endl;  // p2.Name = "wangyong"
    cout << "p1.Age = " << p1.Age << endl;    // p2.Age = 27
    Person p2;
    cout << "p2.Name = " << p2.Name << endl;  // p2.Name = "王勇"
    cout << "p2.Age = " << p2.Age << endl;    // p2.Age = 28
}
int main()
{
    test01();
    return EXIT_SUCCESS;
}

 

六、类对象作为类成员

类对象作为类成员的时候,构造顺序现将类对象一一构造,然后构造自己,析构的顺序是相反的。

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
#include<string>
using namespace std;

class Phone
{
public:
    Phone()
    {
        cout << "Phone的默认构造函数" << endl;
    }
    Phone(string name)
    {
        PhoneName = name;
    }
    ~Phone()
    {
        cout << "Phone的析构函数" << endl;
    }
    string PhoneName;
};

class Game
{
public:
    Game()
    {
        cout << "Game的默认构造函数" << endl;
    }
    Game(string name)
    {
        GameName = name;
    }
    ~Game()
    {
        cout << "Game的析构函数" << endl;
    }
    string GameName;
};

class Person
{
public:
    Person()
    {
        cout << "Person的默认构造函数" << endl;
    }
    Person(string name)
    {
        PersonName = name;
    }
    ~Person()
    {
        cout << "Person的析构函数" << endl;
    }

    string PersonName;
    Phone phone;  // 手机
    Game game;    // 游戏   
};

void test01()
{
    Person p;
    p.PersonName = "王勇";
    p.phone.PhoneName = "华为";
    p.game.GameName = "王者荣耀";
}

int main()
{
    test01();
    return EXIT_SUCCESS;
}

 

 

 

七、explicit 关键字

//  explicit 关键字的作用就是防止构造函数中隐式类型转换
#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

class MySring
{
public:
    MySring(const char *str)
    {

    }
    explicit MySring(int a)
    {
        strSize = a;
    }
    char *mStr;
    int strSize;
};

void test01()
{
    MySring str1("123");
    // MySring str2 = 10;  // 做什么意图?str2字符串为"10"?字符串的长度是10?MySring理解为一个数据类型
    // 隐式类型转换 MySring str2 = MySring(10); 加了 explicit 就不能隐式类型转换,这一行就会报错了。
    // explicit 之后只能如下定义
    MySring str2("234");
}

int main()
{
    test01();
    return EXIT_SUCCESS;
}

 

 

 

八、new运算符

    当我们创建数组的时候,总是需要提前预定数组的长度,然后编译器分配预定长度的数组空间,在使用数组的时候,会产生这样的问题,数组也许空间太大了,浪费空间,也许空间不足,所以对于数组来讲,如果能根据需要来分配空间大小在合适不过。
    所以动态的意思就是意味着不确定性
    为了解决这个普遍存在的问题,在运行中可以创建和销毁对象是最基本的要求。当然 C 早就提供了动态内存分配,函数 malloc和free可以再运行中堆区分配存储单元。
    然后这些函数在C++中不能很好的运行,因为他不能帮我们完成对象的初始化工作。

8.1 对象的创建

当创建一个 c++ 对象时会发生两件事
  1. 为对象分配内存
  2. 调用构造函数来初始化那块内存

第一步我们能保证实现,需要我们确保第二步一定能发横。C++ 强迫我们这么做是因为使用为初始化的对象是程序出错的重要原因。

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
#include<string>
using namespace std;

class Person
{
public:
    Person()
    {
        Age = 20;
        Name = (char *)malloc(strlen("wangyong") + 1);
        strcpy(Name, "wangyong");
    }
    void Init()
    {
        Age = 20;
        Name = (char *)malloc(strlen("wangyong") + 1);
        strcpy(Name, "wangyong");
    }
    void Clean()
    {
        if (Name != NULL)
        {
            free(Name);
        }
    }
    int Age;
    char * Name;;
};

int main()
{
    // 内存分配
    Person *person = (Person *)malloc(sizeof(Person));
    if (person == NULL)
    {
        return 0;
    }
    // 初始化对象
    person->Init();
    // 清理对象
    person->Clean();
    // 释放person对象
    free(person);
    person = NULL;
    return EXIT_SUCCESS;
}

 

8.2 malloc动态内存分配问题

malloc 申请空间存在的问题:
  1. 程序员必须确定对象的长度
  2. malloc返回的一个 void*,C++不允许void*赋值给任何指针,必须强转。
  3. malloc可能申请内存失败,所以必须判断返回值来确保内存分配成功
  4. 用户使用对象之前必须记住对他的初始化,构造函数不能显示调用初始化(构造函数是编译器调用),用户有可能忘记调用初始化函数。

8.3 new运算符操作

C++ 在解决动态内存分配的方案是把创建一个对象所需要的操作都结合在一个称为 new 的运算符里。当用new创建一个对象时候,他就在的堆里面为对象分配内存并调用构造函数完成初始化。

Person person = new Person;
// 相当于完成
Person *person = (Person *)malloc(sizeof(Person));
if(person == NULL)
{
    return 0;
}
person->Init();
#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

class Person
{
public:
    Person()
    {
        cout << "Person默认构造函数" << endl;
    }
    Person(int a)
    {
        cout << "Person有参构造函数" << endl;
    }
    ~Person()
    {
        cout << "Person析构函数" << endl;
    }
};

void test01()
{
    Person p1;  // 栈区开辟
    cout << endl;

    Person *person = new Person;   // 堆区开辟,需要自己释放才行
    // 所有new出来的对象,都会返回该类型的指针。 malloc 返回的void*, 还需要强制转换数据类型
    // malloc不会调用构造函数, new会调用构造函数。 new 是一个运算符, malloc是一个函数
    // 释放堆区的空间, 需要用delete
    delete person;
    cout << endl;

    void *p = new Person;
    // 当用 void * 接收new 出来的指针,会出现释放的问题
    delete p;
    cout << endl;

    // 通过 new 来开辟数组, 一定会调用默认构造函数,所以一定要提供默认构造。
    Person * pArr = new Person[3];
    delete[] pArr;  // 释放数组用 delete []
    cout << endl;

    // 在栈上开辟数组,可以指定有参构造
    Person pArr2[2] = { Person(1), Person(2) };

}

int main()
{
    test01();
    return EXIT_SUCCESS;
}

 

九、静态成员变量和函数

    在一个类中,若将一个成员变量声明为 static,这种成员称为静态成员变量,与一般的数据成员吧冉,无论建立多少个对象,都只有一个静态数据的拷贝,静态成员变量属于某个类,所有对象共享。

 

    静态变量,是在编译阶段分配空间,对象还没有创建时,就已经分配空间

静态成员变量必须在类中声明,类外定义

静态数据域成员不属于某个对象,在为对象分配空间中不包括静态成员所占的空间

 

静态数据成员可以通过类名或者对象名来引用

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;

class Person
{
public:
    Person()
    {
        // A = 10; 代码不报错,但是不一般不这样用,因为直接用类名调用今天变量不会走构造函数
    }

    static void func()  // 静态成员函数, 不可以访问普通成员变量, 静态成员函数也是有作用域的
    {
        cout << "A = " << A << endl;
        // cout << "C = " << C << endl;  报错,不能访问普通成员变量, 因为不知道C是谁传过来的,不清楚值,A之所以可以访问,因为A是共享的,所有人都一样的。
        cout << "func的调用" << endl;
    }

    void myFUnc() // 普通函数可以访问静态成员变量和普通成员变量
    {
        A = 30;
        C = 40;
    }

    int C;         // 普通成员变量
    static int A;  //静态成员变量,会共享数据,在类内声明,在类外初始化
private:
    static int B;  // 私有权限,类外不能访问, 静态成员变量也是有权限的
};

int Person::A = 0;  // 类外初始化实现
int Person::B = 20; // 虽然写在类外,但是作用域还是类内

void test01()
{
    // 1. 通过对象来访问属性
    Person p1;
    p1.A = 10;
    Person p2;
    p2.A = 20;
    cout << "p1.A = " << p1.A << endl;  // p1.A = 20
    cout << "p2.A = " << p2.A << endl;  // p2.A = 20

    // 2. 通过类名访问属性
    cout << "通过类名访问属性: " << Person::A << endl;// 通过类名访问属性: 20

    p1.func();     
    p2.func();     
    Person::func();
}

int main()
{

    test01();
    return EXIT_SUCCESS;
}

 

 

十、单例模式

 

单例模式是一种常用的软件设计模式,在他的核心结构中只包含一个被称为单例的特殊类,通过单例模式可以保证系统中一个类只有一个实例热切该实例抑郁外界访问,从而方便对实例个数的控制并节约系统资源,如果希望某个类的对象只能存在一个,单例模式就是最好的选择。

 

Singleto(单例): 在单例类的内部实现只生成一个实例,同时他提供一个静态的 getInstance()工厂方法,在客户可以访问他的唯一实现,为了防止外部对其实例化,将其默认构造函数和拷贝构造函数设为私有,在单例内部定义了一个Singleton模型的静态对象,作为对外共享的唯一实现

1. 将默认构造函数和拷贝构造函数私有化

2. 内部维护一个对象指针

3. 私有化唯一指针

4. 对外提供 getInstance() 方法来访问这个指针

 

5. 保证类中只能实例化一个对象

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;

// 创建主席类
// 需求:单例模式,为了创建类中的对象,并且保证只有一个对象实例
class ChairMan
{
    
private:
    // 1. 默认构造函数私有化
    ChairMan()
    {
        cout << "创建国家领导人" << endl;
    }
    // 2. 拷贝构造私有化
    ChairMan(const ChairMan &c) 
    {
        cout << "创建国家领导人" << endl;
    }

    // 3. 设置属性私有化
    static ChairMan * singleMan;
public:
    // 4. 提供 getInstance 方法访问主席
    static ChairMan * getInstance()
    {
        return singleMan;
    }
};

// 5. 类外(本质上类内)实例化一个对象
ChairMan * ChairMan::singleMan = new ChairMan;  // 本质上还是在类内初始化创建对象,在编译阶段分配空间,早于main函数运行

void test01()
{
    /*ChairMan c1;
    ChairMan *c2 = new ChairMan;*/  // 不同通过普通创建对象的方式,因为这样就创建出来了好几个对象,所以要将构造函数私有化
    
    /*ChairMan *m1 = ChairMan::singleMan;
    ChairMan *m2 = ChairMan::singleMan;*/
    // ChairMan::singleMan = NULL; 主席不能够消灭掉,所以访问的 ChairMan::singleMan 只能设为私有化

    ChairMan * cm1 = ChairMan::getInstance();
    ChairMan * cm2 = ChairMan::getInstance();
    if (cm1 == cm2)
    {
        cout << "cm1与cm2相同" << endl;
    }
    else
    {
        cout << "cm1与cm2不相同" << endl;
    }

    //ChairMan *cm3 = new ChairMan(*cm2);  // 创建一个 cm2的拷贝构造,这样又创建了一个对象,所以要将拷贝构造私有化
    //if (cm3 == cm2)
    //{
    //    cout << "cm3与cm2相同" << endl;
    //}
    //else
    //{
    //    cout << "cm3与cm2不相同" << endl;
    //}
}

int main()
{
    cout << "main 调用" << endl;
    test01();
    return EXIT_SUCCESS;
}

// 用单例模式,模拟公司员工使用打印机的场景,打印机可以打印员工要输出的内筒,并且可以累积打印机的使用次数
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;

class Printer
{
private:
    Printer()
    {
        count = 0;
    }
    Printer(const Printer &p){}
     
    static Printer * singlePrinter;

    int count;
public:
    static Printer * getInstance()
    {
        return singlePrinter;
    }

    void printTest(string text)
    {
        cout << text << endl;
        count++;
        cout << "打印机使用了次数:" << count << endl;
    }
};

Printer * Printer::singlePrinter = new Printer;

void test01()
{
    Printer * p1 = Printer::getInstance();
    p1->printTest("离职报告");
    p1->printTest("入职报告");
}

int main()
{
    test01();
    return EXIT_SUCCESS;
}

 

Person person = new Person;// 相当于完成Person *person = (Person *)malloc(sizeof(Person));if(person == NULL){    return 0;}person->Init();

 

posted on 2022-03-14 20:25  软饭攻城狮  阅读(15)  评论(0编辑  收藏  举报

导航