• 类和对象(中上)


    1.类的6个默认成员函数

    如果一个类中什么成员都没有,简称为空类。
    空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。

    默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数

    class Date {};
    
    • 1

    在这里插入图片描述
     
     

    2. 构造函数

    大部分的类都需要自己写构造函数
    只有像myqueue这样的类不需要写显示构造函数
    每个类最好都要提供默认构造函数

    class Date
    {
    public:
    	void Init(int year, int month, int day)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    	void Print()
    	{
    		cout << _year << "-" << _month << "-" << _day << endl;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    int main()
    {
    	Date d1;
    	d1.Init(2022, 7, 5);
    	d1.Print();
    
    	Date d2;
    	d2.Init(2022, 7, 6);
    	d2.Print();
    
    	return 0;
    }
    //运行时奔溃了,是因为没有初始化
    //C语言调用Init初始化可能会忘记,导致奔溃或出现随机值
    //C++ 构造函数 ——保证初始化
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

     
     

    2.1概念

    构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次

     

    2.2特征

    构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开
    空间创建对象,而是初始化对象。

    1. 函数名与类名相同。
    2. 无返回值。(也不用写void)
    3. 对象实例化时编译器自动调用对应的构造函数。
    4. 构造函数可以重载。

    class Date
    {
    public:
    	// 1.无参构造函数
    	Date()
    	{}
    	// 2.带参构造函数
    	Date(int year, int month, int day)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    void TestDate()
    {
    	Date d1; // 调用无参构造函数
    	Date d2(2015, 1, 1); // 调用带参的构造函数
    	// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
    	// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
    	// warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)
    	Date d3();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

     
     
    练习:栈的构造函数

    typedef int DataType;
    class Stack
    {
    public:
    	Stack(int capacity)//构造函数
    	{
    		cout << "Stack(int capacity = 4)" << endl;
    		_array = (DataType*)malloc(sizeof(DataType)*capacity);
    		if (NULL == _array)
    		{
    			perror("malloc申请空间失败!!!");
    			return;
    		}
    
    		_size = 0;
    		_capacity = capacity;
    	}
    
    	void Push(DataType data)
    	{
    		// CheckCapacity();
    		_array[_size] = data;
    		_size++;
    	}
    private:
    	DataType* _array;
    	int _capacity;
    	int _size;
    };
    
    int main()
    {
        Stack st;
        st.Push(0);
        st.Push(1);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

     
    5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成
     

    class Date
    {
    public:
    /*
    // 如果用户显式定义了构造函数,编译器将不再生成
    Date(int year, int month, int day)
    {
    _year = year;
    _month = month;
    _day = day;
    }
    */
    void Print()
    {
    cout << _year << "-" << _month << "-" << _day << endl;
    }
    private:
    int _year;
    int _month;
    int _day;
    };
    int main()
    {
    // 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
    // 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生// 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
    Date d1;
    return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

     
     

    6.构造函数并不开空间
    C++将类型分为两种:
    内置类型(基本原生类型):int double char 指针
    自动类型:struct class

    默认生成构造函数:
    内置类型成员不做处理
    自定义类型成员回去调用他(自定义类型成员变量)的默认构造函数

     

    class Date
    {
    public:
    	// 不写 默认生成构造函数
    private:
    	int _year = 1;
    	int _month = 1;
    	int _day = 1;
    
    	//Time* _t;  // 指针都是内置类型
    	Time _t;
    };
    
    int main()
    {
    	Date d1;//调用构造函数初始化,只能调用默认构造函数
    
    	return 0;
    }
    
    对于这样的类才有价值
    class MyQueue
    {
        private:
            Stack _st1;
            Stack _st2;
    };
    
    int main()
    {
        MyQueue q;
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    这是c++早期设计的一个缺陷,默认生成构造函数,本来应该和内置类型也一并处理

    C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值

    总结:
    一般的类都不会让编译器默认生成构造函数,都是自己写。写一个全缺省,非常好用

    特殊情况下才会默认生成

    class MyQueue {
    public:
    	void push(int x) {}
    	//...
    private:
    	size_t _size = 0;
    	Stack _st1;
    	Stack _st2;
    };
    
    int main()
    {
    	Date d1;
    	// MyQueue 用默认生成构造函数就挺好,不需要显示写
    	MyQueue q;
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    7. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。

    注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数

    默认构造函数有三类:特点——不传参数就可以调用

    • 我们不写,编译器自动生成
    • 我们自己写的,全缺省构造函数
    • 我们自己写的,无参构造函数
    using namespace std;
    
    class Date
    {
    public:
    	// 1.无参构造函数
    	Date()
    	{}
    
    	// 2.带参构造函数(最好的方式——提供全缺省)
    	Date(int year = 1, int month = 1, int day = 1)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    	//这两个函数可以同时存在,不过调用的时候会产生歧义——二义性
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    
    int main()
    {
    	//Date d1; // 调用存在歧义/二义性
    	Date d2(2022, 7, 23);
    
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

     
     

    3.析构函数

    一些类需要显示写析构函数,比如:stack,queue
    一些类不需要显示写析构函数。a.date这类,没有资源需要清理 b.myqueue也可以不写,默认生成的就可以

     

    3.1 概念

    通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?

    析构函数:
    与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。

    而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
    生命周期,函数结束就会自动销毁了

     

    3.2 特性

    析构函数是特殊的成员函数,其特征如下:

    1. 析构函数名是在类名前加上字符 ~。
    2. 无参数无返回值类型。
    3. 一个类只能有一个析构函数(没有参数就不会构成重载了)。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重

    4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
    5. 默认生成的析构函数特点:

    • 跟构造函数类似,内置函数不处理,
    • 自定义类型成员回去调用他的析构函数
    class Date
    {
    public:
    	Date(int year = 1, int month = 1, int day = 1)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    
    	// 不需要写,默认生成就够用。但是默认生成的也没做什么事情
    	//~Date()//~按位取反
    	//{
    	//	//~Date()没有什么需要清理
    	//	cout << "~Date()" << endl;
    	//}
    
    	
    private:
    	int _year = 1;
    	int _month = 1;
    	int _day = 1;
    };
    
    typedef int DataType;
    class Stack
    {
    public:
    	Stack(size_t capacity = 3)
    	{
    		_array = (DataType*)malloc(sizeof(DataType)* capacity);
    		if (NULL == _array)
    		{
    			perror("malloc申请空间失败!!!");
    			return;
    		}
    
    		_capacity = capacity;
    		_size = 0;
    	}
    
    	void Push(DataType data)
    	{
    		// CheckCapacity();
    		_array[_size] = data;
    		_size++;
    	}
    
    	// 需要显示写,手动明释放资源,不释放会内存泄漏
    	//析构函数是特殊函数,写了之后会自动调用
    	~Stack()
    	{
    		cout << "~Stack()->" << _array << endl;
    		free(_array);
    		_capacity = _size = 0;
    		_array = nullptr;
    	}
    private:
    	DataType* _array;
    	int _capacity;
    	int _size;
    };
    
    class MyQueue {
    public:
    	void push(int x) {}
    	//...
    
    	// 不需要写,默认生成就够用。但是默认生成对于Stack自定义成员,调用Stack析构函数
    private:
    	size_t _size = 0;
    	Stack _st1;
    	Stack _st2;
    };
    
    void func()
    {
    	Date d;
    	Stack st;
    	MyQueue q;
    }
    
    int main()
    {
    	func();
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88

     

    6.分为三类

    • 不需要写 date日期类
    • 需要写 stack queue List SeqList binarytree
    • 需要写但可以默认生成 myqueue(两个栈实现队列)
    1. 析构的顺序
    class A
    {
    public:
    	A(int a = 0)
    	{
          _a = a;
    		cout << "A(int a = 0)->" <<_a<< endl;
    	}
    
    	~A()
    	{
    		cout << "~A()->" <<_a<<endl;
    	}
    private:
    	int _a;
    };
    
    
    
    
    int main()
    {
    	A aa1(1);
    	A aa2(2);
    
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    在这里插入图片描述

    先定义的先构造 后定义的先析构

    栈帧和栈里面的对象都要符合:后进先出

     A aa3(3);
    int main()
    {
        static A aa0(0);
    
    	A aa1(1);
    	A aa2(2);
     static A aa4(4);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述
    全局的最先初始化,局部和静态是第一次运行进入main函数的时候初始化所以是按顺序来的

    1 2 是在栈帧里的
    0 4 在局部静态区
    3是在去全局
    析构大方向是先栈帧然后局部静态最后全局 遵循后进先出
    每个部分里面也是遵循后进先出

     
     

    A aa3(3);
    
    void f()
    {
    	static int i = 0;
    	static A aa0(0);
    	A aa1(1);
    	A aa2(2);
    	static A aa4(4);
    }
    
    // 构造顺序:3 0 1 2 4 1 2
    // 析构顺序:~2 ~1 ~2 ~1 ~4 ~0 ~3
    
    int main()
    {
    	f();
    	f();//两次调用
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在这里插入图片描述
    第二次调用的时候静态就不会再构造了
    而且第一次调用之后静态和全局也不会析构,直到第二次调完才会析构

     
     

    4. 拷贝构造函数

    一些类需要显示写拷贝额赋值,比如stack,queue
    一些类不需要显示写拷贝和赋值。a.date这样的类,默认生成就会完成值拷贝/浅拷贝 b.比如myqueue这样的类,默认生成就会调用他的自定义类型成员stack的拷贝和赋值

    4.1 概念

    拷贝构造函数:
    只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型
    对象创建新对象时由编译器自动调用。

     

    4.2 特征

    拷贝构造函数也是特殊的成员函数,其特征如下:

    1. 拷贝构造函数是构造函数的一个重载形式。
    2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发
    无穷递归调用

    传值传参和传引用传参

    • 传值传参:d是d1的拷贝,形参是实参的拷贝 会在get1函数中开辟一个新的空间吧d1拿过去
    • 传引用传参:d是d1的别名 公用一块空间,只不过取了不同的名字 是为自定义类型准备的
    void get1(Date d)
    {}
    
    void get2(Date& d)
    {}
    
    void func()
    {
        get1(d1);
        get2(d1);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    传参就是一个拷贝构造

    传值返回和传引用返回

    A func3()
    {
    	static A aa(3);
    	return aa;//返回aa的拷贝
    }
    
    A& func4()
    {
    	static A aa(4);
    	return aa;//返回的是aa的别名
    }
    
    int main()
    {
    
    	func3();//析构的也是拷贝的对象
    	cout << endl << endl;
    	func4();
    
    	
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述
    需要注意的是,如果aa出来func4的作用域就销毁了,那么引用返回是有问题的

     
     

    class Date
    {
    public:
    	Date(int year = 1, int month = 1, int day = 1)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    
    	
    	//1.引用传参
    	Date(const Date& d)
    	{
    		cout << "Date(Date& d)" << endl;
    		_year = d._year;
    		_month = d._month;
    		_day = d._day;
      
       //写反了——用const避免这种错误发生
    
    		/*d._year = _year;
    		d._month = _month;
    		d._day = _day;*/
    	}
    
    	//2.指针——可以但是达不到想要的效果
    	Date(Date* d)
    	{
    		cout << "Date(Date& d)" << endl;
    		_year = d->_year;
    		_month = d->_month;
    		_day = d->_day;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    
    
    
    void func()
    {
    	Date d0;
    	Date d1(2022, 7, 23);
    
    
    	Date d2(d1);
    	Date d3 = d1;
    
    	Date d4(&d1);
    	Date d5 = &d1;
    // 内置类型
    	int i = 0;
    	int j = i;
     
     
     //拷贝场景:
     	// 1 3 2 6 7 打印一下路径值  下标路径
    	Stack path;
    	Stack copy(path);
    	while (!copy.Empty())
    	{
    		cout << copy.Top() << endl;
    		copy.Pop();
    	}
     }
    
    
    
    int main()
    {
    	func();
    
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

     

    1. 若未显式定义,编译器会生成默认的拷贝构造函数。
      默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝memcpy

    注意
    在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的
    自定义类型是调用其拷贝构造函数完成拷贝的
     

    4**.浅拷贝**:
    一个对象修改会影响另一个对象会析构两次,程序会崩溃
    解决方式:自己实现深拷贝

    注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
     

    1. 拷贝构造函数典型调用场景
    • 使用已存在对象创建新对象
    • 函数参数类型为类类型对象
    • 函数返回值类型为类类型对象
  • 相关阅读:
    Visual Studio实用的搜索、查找、替换技巧
    力扣 字母异位词分组 哈表 集合
    (服务器&客户端)网络通信是怎么实现的?7000字爆肝----原来java网络编程技术功不可没(多线程,URL,InetAddressm,TCP,UDP)集结
    openGauss学习笔记-102 openGauss 数据库管理-管理数据库安全-客户端接入之查看数据库连接数
    基于javaweb+mysql的投票管理系统
    java小游戏-java小游戏-飞机大战
    Grid-Based Continuous Normal Representation for Anomaly Detection 论文阅读
    封装比较好的登录页面
    计算机毕业设计Java高校会议室预约系统(源码+系统+mysql数据库+lw文档)
    SpringMVC项目Rest风格
  • 原文地址:https://blog.csdn.net/Ll_R_lL/article/details/126128072