c++类与对象三

C++类与对象三

上期我们介绍了类的实例化,大小计算,还有this指针。这期我们继续深入更高层次的用法

类的六个默认函数

如个一个类里面没有成员,就是空类,但是空类里面真的什么都没有吗,并不是,在编译器中,会默认生成六个默认成员函数,但是这只是隐式生成的,用户是看不见的。

默认成员函数:用户没有显示显现,编译器自动生成的成员函数称为默认成员函数

image-20241003115447369

VS2022自动生成的默认成员函数

image-20241011164552187

六个默认函数主要功能

构造函数

概念

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 _day;
	int _month;
	int _year;

};

int main() {
	Date d1;
	d1.Init(2011, 02, 05);
	d1.Print();

	Date d2;
	d2.Init(2024, 10, 10);
	d2.Print();
}

对于以上函数,我们对d1d2对象初始化时,都可以调用Init函数来初始化赋值,但是每次创建一个对象进行初始化,就必须调用一次Init函数,非常的不方便。有没有一种函数,在创建对象时,默认帮你完成初始化呢?

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

注意是**自动调用!!!**极大节省调用函数时的燃烧的卡路里

特性

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

1、构造函数与类名相同

2、无返回值

3、对象实例化时编译器自动调用对应的构造函数

4、构造函数可以重载

主要分为两种构造方式

1、无参构造

2、有参构造

class Date {
public:
    Date() {

    }//无参构造

    Date(int year, int month, int day) {
        _year = year;
        _month = month;
        _day = day;
    }//有参构造
    
    void Print() {
        cout << _year << '-' << _month << '-' << _day << endl;
    }
private:
    int _day;
    int _month;
    int _year;
};

int main() {
    //无参构造
    Date d1;

    //有参构造
    Date d2(2014,11,11);
    d2.Print();
    
    Date d3();
    // 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
    // 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
    // warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)
    
     return 0;
}

image-20241011173119480

无参和有参的区别

如果用户没有显示定义构造函数,编译器会自动生成一个无参的默认的构造函数,一旦用户显示定义编译器就不再生成。

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 _day;
    int _month;
    int _year;
};

int main() {
    //无参构造
    Date d1;

     return 0;
}

注释掉有参构造,可以识别到编译器默认的构造函数

放开有参构造,则编译器自动生成的构造函数会变成有参构造,因此系统无法识别无参调用

image-20241011193901798

注意事项

一、有关于系统的默认构造,有很多同学有疑惑:不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?d1对象调用了编译器生成的默认构造函数,但是d1对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用??

为了验证上面的看法,我们需要在不同环境的编译器下测试以下代码

VS2022

class Stack {
public:
	Stack(int capacity = 4)
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a==nullptr) {
			perror("malloc fail!!");
		}
		_capacity = capacity;
		_top = 0;
	}

private:
	int* _a;
	int _capacity;
	int _top;
};

class MyQueue {
	Stack _push;
	Stack _pop;
	int _size;
};

int main() {
	Stack t1;
	MyQueue m1;

	return 0;
}

image-20241011203801388

我们给Stack显示写上构造函数,而MyQueue这个类是没有显示写构造函数的,是编译器默认生成的构造函数。

在监视窗口下,t1对象是显示的构造函数,m1是编译器生成的。

可以看到m1对象没有显示的构造函数,但是_size的值却变成了0!!!!

首先,C++把类型分成内置类型和自定义类型。内置类型就是语言提供的数据类型,例如int、char、double.....,自定义类型就是struct/class/union等自己定义的类型。让我们看看下面的代码

class Time {
public:
	Time() {
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;
		_second = 0;
	}
private:
	int _hour;
	int _minute;
	int _second;
};

class Date {
private:
	int _year;
	int _month;
	int _day;
    //这三个都是内置类型

	Time _t1;//自定义类型
};

int main() {
	Date d1;

	return 0;
}

调试以上代码调试发现,C++对内置类型是不初始化的,在自定义类型中,有显示的构造函数。

image-20241012105101279

可以看见在Date中三个内置类型是随机值,而自定义类型中是用户自己写的显示构造函数。当我们注释掉Time类中的构造函数,使用系统生成的默认构造函数时

image-20241012105318196

发现**他们都成了乱码!!!**回顾前面内容

class MyQueue {
	Stack _push;
	Stack _pop;
	int _size;
};

为什么这段代码中int _size明明是内置类型,为什么又被初始化了!!!

其实最根本的原因还是VS2022的锅,这是新版本的优化!!!因此这是个巨坑。稍有不慎就会因为初始化的问题出现各种问题,当切换到VS2013调试MyQueue他就原形毕露(由于这里不方便展示,只能说出结论)

同学们到这里可能就乱了,首先我们要记住:C++对自定义类型是会初始化的,但是会调用他的默认构造函数,对内置类型,是不会初始化的!,因此VS2022这里就是一个妥妥的渣男,欺骗人感情。因此_size实际上是随机值的。

针对内置成员不初始化的问题,在C++11中,新增了一个补丁,在定义内置类型时,可以给缺省值

class Date {
private:
	int _year=1970;
	int _month=01;
	int _day=01;

	Time _t1;
};

那么这三个自定义类型是有内存的吗?答案是没有,因为这个是缺省值

二、无参的构造函数和全缺省的构造函数都被称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数全缺省构造函数、用户没有写编译器默认生成的构造函数,都是默认构造函数

class Date {
public:
	//无参构造
	Date() {
		_year = 1970;
		_month = 01;
		_day = 01;
	}
	//有参构造
	Date(int year=1970,int month=01,int day=01) {
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main() {
	Date d1;

	return 0;
}

image-20241012111510475

以上代码会报错,因为只能有一个默认构造函数

写法

根据上面对构造函数的了解,可以给出参考写法—全缺省构造

class Date {
public:
	//有参构造
	Date(int year=1970,int month=01,int day=01) {
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main() {
	Date d1;
	Date d2(2024, 10, 10);

	return 0;
}

image-20241012111836899

一举两得!

析构函数

概念

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

析构函数:与构造函数功能相反,析构函数不是完成对象本身的销毁,局部变量销毁是由编译器完成的,而对象在销毁时会自动调用析构函数,完成对本对象的资源清理

特性

1、析构函数名是在类名前加上字符~

2、无参数无返回值

3、一个类只能有一个析构函数,用户没有显示定义,则编译器会自动生成默认的析构函数

4、对象生命周期结束时,C++编译器自动调用

5、析构函数不能重载

class Stack {
public:
	Stack(int capacity = 4) {
		_array = (int*)malloc(sizeof(int) * capacity);
		if (_array == nullptr) {
			perror("malloc fail");
		}

		_capacity = capacity;
		_size = 0;
	}

	void Push(int x) {
		_array[_size++] = x;
	}

	~Stack() {
		if (_array) {
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}

private:
	int* _array;
	int _capacity;
	int _size; 

};

int main() {
	Stack s1;
	Stack s2;
	s1.Push(1);
	s2.Push(3);

	return 0;
}

当上面的对象生命周期结束时,会自动调用析构函数~Stack()清理内部资源

image-20241012171223489

生命周期结束资源清理

那么系统默认生成的析构函数又是怎么样的呢

class Time {
public:
	~Time() {
		cout << "~Time()" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};

class Date {
private:
	int _year = 1970;
	int _month = 01;
	int _day = 01;

	Time _t1;
};

int main() {
	Date d1;
	return 0;
}

运行结果是创建哪一个类就运行哪一个类的默认析构函数

这段代码一共运行了倆个析构函数,第一个是Time类显示定义的,第二个是Date系统默认生成的(注意:内置类型是由系统回收资源的,而自定义类型是需要析构函数去清理的)

因此,析构函数是非常重要的,可以在对象生命周期自动调用,清理不需要的资源,降低内存泄漏的风险

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吃椰子不吐壳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值