• 类和对象(2)


    1.类的6个默认成员函数(天选之子)

    C语言中,可能中途return也可能最后return,destroy的地方很多,比较麻烦。
    以下为C++进行的改进,俗称”天选之子“。
    在这里插入图片描述

    • 构造函数和析构函数如果不写,编译器则会自动生成一个默认
    • 如果我们实现了任意一个,编译器就会自动生成了。
    • 对象在实例化的时候必须调用构造函数(包括自己写的和编译器自动生成的),应调用自己生成的那个。

    C++类型分为:内置类型(int,char,任意类型的指针)不处理,
    自定义类型(class,struct,union联合体)调用他的构造
    默认生成的构造函数对于内置类型的成员不做处理,对于自定义类型的成员,会去调用它的构造(不用传参的构造)
    析构函数相似

    class Date
    {
    public:
    	Date()
    	{
    		_year = 1;
    		_month= 1;
    		_day= 1;
    
    
    	}
    	Date(int year, int month, int day)//带参的初始化
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    	void Print()
    	{
    		cout << _year << "年" << _month << "月" << _day << "日" << endl;
    	}
    private:
    //基本类型或内置类型,因为带int
    	int _year;
    	int _month;
    	int _day;
    };
    int main()
    {
    	Date d1;
    	Date d2(2023, 9, 13);
        d1.Print();
    	d2.Print();
    	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

    在这里插入图片描述

    合并两个构造函数,用全缺省更好用

    	Date(int year=1, int month=1, int day=1)//全缺省
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    两个栈实现一个队列(push,pop,peek,empty)问题。
    在这里插入图片描述
    C

    typedef struct {
    	ST pushst;
    	ST popst;
    }MyQueue;
    bool myQueueEmpty(MyQueue* obj);
    MyQueue* myQueueCreate()
    {
    	MyQueue* pq = (MyQueue*)malloc(sizeof(MyQueue));
    	StackInit(&pq->pushst);
    	StackInit(&pq->popst);
    	return pq;
    }
    void myQueueFree(MyQueue* obj)
    {
    	assert(obj);
    	StackDestroy(&obj->pushst);
    	StackDestroy(&obj->popst);
    
    	return pq;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    C++

    class MyQueue {
    public:
    	//默认生成的构造函数,对自定义类型,会调用他的默认构造函数。
    	void push(int x)
    	{}
    	//...
    	Stack _pushST;
    	Stack _popST;
    };
    int main()
    {
    	MyQueue q;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2.构造函数

    实际上是在初始化对象特殊的成员函数。作用和Init相同。,主要任务不是开空间创造对象,而是初始化对象

    1. 函数名和类名相同。名字已经定好了
    2. 没有返回值,什么也不用写。
    3. 对象实例化时自动调用对应的构造函数需要调用显示的Init
    4. 可以重载(一个类可以有多个构造函数),有多个构造函数就有多种初始化方式
    5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,对于内置类型不做处理,对于自定义类型会去调用它的构造函数。一旦用户显式定义编译器再生成。
    6. 无参全缺省的构造函数都被称为默认构造函数,且仅有一个,同时调用时存在歧义。不写编译器自动生成的也是默认构造函数。不传参数就可以调用构造函数。一般建议每个类都提供一个默认构造函数。
    #include 
    using namespace std;
    class Stack
    {
    public:
    	//成员函数
    	void Init(int n=4)
    	{
    		_a = (int*)malloc(sizeof(int) * n);
    		if (nullptr == a)
    		{
    			perror("malloc申请空间失败");
    			return;
    		}
    		capacity = n;
    		size = 0;
    	}
    	void Push(int x)
    	{
    		//...
    		a[size++] = x;
    	}
    	void Destroy()
    	{
    		//...防止内存泄漏,可以把这件事交给编译器,让他自动搞起来。
    	}
    	//...
    private:
    	//成员变量
    	int* _a;
    	int _size;
    	int _capacity;
    };
    int main()
    {
    	//定义一个栈
    	Stack st;
    	//初始化
    	st.Init(4);
    	st.Push(1);
    	st.Push(1);
    	st.Push(1);
    	st.Push(1);
    	//进行销毁
    	st.Destroy();
    
    	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
    class Stack
    {
    public:
    	Stack()
    	{
    		_a = nullptr;
    		_size = _capacity = 0;
    	}
    	Stack(int n)//有了构造函数的类就不需要Init的类
    		//构成函数重载
    	{
    		_a = (int*)malloc(sizeof(int) * n);
    		if (nullptr == _a)
    		{
    			perror("malloc申请空间失败");
    			return;
    		}
    		_capacity = n;
    		_size = 0;
    	}
    	void Push(int x)
    			{
    				//...
    				_a[_size++] = x;
    			}
    	void Destroy()
    			{
    				//...防止内存泄漏,可以把这件事交给编译器,让他自动搞起来。
    			}
    private:
    		//成员变量
    		int* _a;
    		int _size;
    		int _capacity;
    };
    int main()
    {//构造函数的调用
    	//定义一个栈
    	//Stack st;//无参,不带(),否则会报错
    	//因为是声明函数还是调用对象分不清楚。有点混淆
    	
    	Stack st(4);//带参
    	//Stack st(4,3);
    
    	st.Push(1);
    	st.Push(1);
    	st.Push(1);
    	st.Push(1);
    	//进行销毁
    	st.Destroy();
    
    	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

    Stack st(4)不等同于st.Stack(4)
    构造函数不能用对象调用,st定义好了才向下走。

    3.析构函数

    destroy,和刚才构造函数的方法类似,定义一个函数让他自动调用。功能和构造函数相反,析构函数不是完成对象本身的销毁,析构函数进行清理资源在对象出了作用域,生命周期销毁后进行调用

    3.1特性

    1. 函数名是在类名前加==~==。C语言中代表按位取反。
    2. 参数返回值。
    3. 一个类只有一个析构函数,若未显式定义,系统则**自动生成默认的析构函数。**注意:析构函数不能重载。
    4. 生命周期结束时,C++编译系统自动调用析构函数
    ~Stack()
    {
    
       free(_a);
       _a=nullptr;
       _size=_capacity=0;
    }//只定义一个对象就释放一次,两个对象释放2次
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    4.拷贝构造

    1. 构造函数的一个重载,函数名和类名相同,没有返回值。
    2. 参数只有一个且必须是类类型对象的引用,使用传值编译器直接报错,因为会引发无穷递归调用
    Date(Date d)
    	{
    		_year = d._year;
    		_month = d._month;
    		_day = d._day;//拷贝构造
    	}//编译器不允许发生这种拷贝构造,会产生无穷递归,规定这个地方必须用引用
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    //普通整型,内置类型可以直接传递,但是自定义类型不可以

    //传值传参
    void Func1(Date d)
    {}//Func传参的时候是一个拷贝,把d1拷贝给d
    
    • 1
    • 2
    • 3
    //传引用传参
    void Func2(Date& d)
    {
    }//d是d1的别名
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    int main()
    {
    Func1(d1);
    return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 自定义类型不能编译器随便拷贝。内置类型编译器可以直接拷贝,自定义类型的拷贝需要调用拷贝构造。

    2. 字节拷贝:浅拷贝

    3. 对于栈来说,浅拷贝两个栈指定同一空间,会析构两次。

    4. C++规定自定义类型要调用一个拷贝构造,栈这样的类要调用一个深拷贝自己去开一块空间)的拷贝构造。

    5. 调用func1先传参,传参就是一个拷贝构造

    6. Date d2(d1);//对象实例化调用,先调用对应的构造函数,此时是拷贝构造Date (Date d);,调用拷贝构造之前要先传参d1传给d,传值传参又形成一个拷贝构造;拷贝构造要先传参,传值传参又是一个拷贝构造。。。。。递归下去。套娃
      解决方法:引用。Date (Date &d);//d是d1的别名

    7. 拷贝构造也可以这么写:Date d2=d1;
      拷贝构造一般加const

    8. Date (const Date &d);//权限缩小,权限可以缩小不可放大。

    9. 引用不需要调用函数。调用就是传参

    10. 指针也是内置类型自定义类型必须用拷贝构造完成,编译器驾驭不了。

    Date(const Date* d)
    {
            cout<<"Date(Date& d)"<<endl;
            _year = d->_year;
    		_month = d->_month;
    		_day = d->_day;//不是拷贝构造
    }//只是一个普通的构造函数
      Date d4(&d1);
      Dare d5=&d1;//复杂别扭
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    实现一个函数,实现多少天以后的日期。

    int GetMonthDay(int year, int month)
    	{//if,else/switch,case
    		//数组
    		assert(month > 0 && month < 13);
    
    		int monthArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
    		if (month == 2 && ((year % 4 == 0 && year % 100 == 0) || (year % 400 = 0)))
    		{
    			return 29;
    		}
    		else
    		{
    			return monthArray[month];
    		}
    	}
    	Date GetAfterXDay(int x)
    	{
    		//先用一个结果,先生成一个中间临时对象。拷贝d1,this是d1的地址
    		//Date tmp;
    		//tmp._year = _year;//传值返回不用tmp,因为tmp出了作用域后被销毁,返回tmp的拷贝。进行拷贝构造
    		//用同类型对象
    		//Date tmp(*this);拷贝构造
    	    Date tmp = *this;//拷贝构造
    	//不能用引用,用引用tmp是d1的别名。tmp的改变就是d1的改变。
     //不动自己的成员
    		//加法进位
    		tmp._day += x;
    		while (tmp._day > GetMonthDay(tmp._year, tmp._month))
    		{
    			//进位
    			tmp._day -= GetMonthDay(tmp._year, tmp._month);
    			++tmp._month;
    			if (tmp._month == 13)
    			{
    				tmp._year++;
    				tmp._month = 1;
    			}
    		}
    		//获取日期
    		//return* this;//返回自己,自己的年月日都在往后加,this是这个对象的指针,*this是这个对象//除了作用于还在
    		return tmp;//局部对象
    	}
    	void Print()
    	{
    		cout << _year << "年" << _month << "月" << _day << "日" << endl;
    	}
    	int main()
    	{
    		Date d1(2023, 9, 13);
    		Date d2 = d1.GetAfterXDay(100);
    		d1.Print();
    		d2.Print();//这时改变_year等会改变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
    • 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

    +(不改变)和+=(改变)返回值的区别。

  • 相关阅读:
    vivo 短视频用户访问体验优化实践
    大比拼:讯飞星火大模型将超越ChatGPT?
    艾美捷细胞失巢凋亡检测试剂盒测定原理&化验方案
    DSPE-PEG-Hydrazide DSPE-PEG-HZ 水溶性磷脂-聚乙二醇-酰肼
    Netty(11)序列化/反序列化、Netty参数
    js 随机生成大小写字母数字16位数
    七、自定义配置
    数论——扩展欧几里得算法
    Android-线程池
    docker下移除不使用的镜像、容器、卷、网络
  • 原文地址:https://blog.csdn.net/weixin_73236117/article/details/132841373