C++的默认成员以及函数详解(上篇)

前言

我相信大家都是从C语言跨入到C++的,C语言时一个面向过程的一门高级语言,而C++则是面向对象的一门高级语言。
可是面向对象这个词,一个一个字都懂,但是组合在一起是不是就不认了呢,来,
让我们看看是怎么样去面对的对象

面向过程和面向对象初步认识

C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。

在这里插入图片描述
在这里插入图片描述
是不是对面向过程了解了些,来看C++

C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
用上述的洗衣服来讲:
在这里插入图片描述
在这里插入图片描述
人具有的属性是负责把衣服和洗衣服倒入洗衣机,而洗衣服的属性就是用来清洗衣服,洗衣机的属性则是负责洗衣服,这四个对象互相交互,这就是C++面向对象。

在C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数,但是在C++中更喜欢用class来代替。

类的定义

class className //空类
{
	// 类体:由成员函数和成员变量组成
}; 	// 一定要注意后面的分号

class为定义类的关键字,className为类的名字,{}中为类的主体

  • 注意,跟C语言的struct的定义一样后面要加分号,不能省略

类中的内容称为类的成员: 类中的变量称为类的属性成员变量;
类中的函数称为类的方法或者成员函数

类的6个默认成员函数

如果一个类中什么成员都没有,那么就叫空类。

既然空类都称"空"了,就真的什么都没有了吗?并不是,任何类在什么都不写的时候,编译器会自动生产以下6个默认成员函数
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
在这里插入图片描述

构造函数

通过下面的日期类,可以更好的给大家讲解

#include <iostream>
 
class Date {
public:
    void DateInit(int year, int month, int day) {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print() {
        printf("%d-%d-%d\n", _year, _month, _day);
    }
 
private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d1;
    d1.DateInit(2022, 2, 16);
    d1.Print();
 
    Date d2;
    d2.DateInit(2023, 2, 17);
    d2.Print();
 
    return 0;
}

对于Date类,通过上面中的DateInit初始化Date的成员变量。
那要是没有写这个DateInit函数,是不是Date 就不能初始化嘛?
实践出真知,我们将DateInit函数注释掉,再来运行一遍

在这里插入图片描述
怎么是随机值呢?
原来是编译器自动会生成初始化函数,而这个函数就叫默认构造函数
但是,这都初始化的什么鬼?哈哈,先把这个问题放放。

概念

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

特性

  1. 构造函数的函数名和类名是相同的

  2. 构造函数无返回值

  3. 构造函数可以重载

  4. 会在对象实例化时自动调用对象定义出来。

来看下面的代码:

#include <iostream>
 
class Date {
public:
    /* 无参构造函数 */
    Date() {
        _year = 1970;
        _month = 1;
        _day = 1;
    }
    /* 带参构造函数 */
    Date(int year, int month, int day) {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print() {
        printf("%d-%d-%d\n", _year, _month, _day);
    }
 
private:
    int _year;
    int _month;
    int _day;
};
 
int main(void)
{
    Date d1;   // 对象实例化,此时触发构造,调用无参构造函数
    d1.Print();
 
    Date d2(2023, 2, 16);   // 对象实例化,此时触发构造,调用带参构造函数
    d2.Print();
 
    return 0;
}

运行结果:在这里插入图片描述
上面的中有两种构造函数,一种是带参的构造函数,另一种是无参的构造函数,无参时让类实例化会自动调用无参的构造函数,若传参数时,则会调用带参的构造函数

  • 1.注 意: 这里需要注意传无参的时候,Date实例化时,出现Date d3(),这时编译报错,这是因为编译器以为是函数声明,则是声明了d3函数,该函数无参,返回一个日期类型的对象
    在这里插入图片描述
  • 2.注意:这里要调用带参的构造函数,需要传三个参数,若是设了缺省值,也要遵循缺省参数从右往左依次缺省

默认构造函数

如果你没有自己定义构造函数(类中未显式定义),C++ 编译器会自动生成一个无参的默认构造函数。当然,如果你自己定义了,编译器就不会帮你生成了。

无参构造函数、全缺省构造函数都被称为默认构造函数,并且默认构造函数只能有一个!
所以你要是都定义了,语法上他们两个可以同时存在,但是如果有对象定义去调用就会报错。
在这里插入图片描述

默认构造函数对内置类型和自定义类型差别

还记得前面的这个随机值嘛在这里插入图片描述
是不是感觉这里编译器生成的默认构造函数好像并没有什么卵用
在讲清楚之前,还得先搞懂内置类型和自定义类型。
内置类型就是语法已经定义好的类型:如 int / char…,
自定义类型就是我们使用 class / struct / union / 自己定义的类型。

接下来,再定义一个类Time就能讲清楚这个问题了

#include<iostream>

using std::cout;
using std::endl;

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


class Date {
public:
    /* 无参构造函数 */
    Date() {
        _year = 1970;
        _month = 1;
        _day = 1;
    }
    
    void Print() {
        printf("%d-%d-%d\n", _year, _month, _day);
    }

private:
    int _year;
    int _month;
    int _day;
	
	Time _t;
};

int main()
{
    Date d1;// 对象实例化,此时触发构造,调用无参构造函数
    
    return 0;
}

运行结果:

在这里插入图片描述
在这里插入图片描述
结果:对自定义类型处理,会自动调用自己的默认构造函数

在这里发现没有对内置类型和自定义类型统一处理,不处理内置类型成员变量,只处理自定义类型成员变量。

析构函数

构造函数的出现,仍让一个对象初始化,而析构函数的出现就是让一个对象销毁。

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

特性

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

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值类型。
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

现在用一个Stack类来给大家讲述
都知道栈创建完后,需要主动去释放并清理内存空间的

析构函数的用法:

#include<iostream>
#include<stdlib.h>
using namespace std;
 
typedef int StackDataType;
class Stack {
public:
    // 构造函数 默认给4(利用缺省参数)
    Stack(int capacity = 4) 
    {  
        _array = (StackDataType*)malloc(sizeof(StackDateType) * capacity);
        if (_array == NULL) {
            cout << "Malloc Failed!" << endl;
            exit(-1);
        }
        _top = 0;
        _capacity = capacity;
    }
 
    // 析构函数
    ~Stack() 
    {   // 这里就用的上析构函数了,我们需要清理开辟的内存空间(防止内存泄漏)
        free(_array);
        _array = nullptr;
        _top = _capacity = 0;
    }
 
private:
    int* _array;
    size_t _top;
    size_t _capacity;
};
 
int main(void)
{
    Stack s1;
    Stack s2(10);
      
    return 0;
}

保证了栈被定义出来就一定被初始化,用完后会自动销毁。以后就不会有忘记调用 destroy 而导致内存泄露的惨案了,这里的析构函数就可以充当销毁的作用。

  • 在这里定义了s1和s2,那是先析构 s1 还是先析构 s2?
    其实是先析构s2后析构s1,根据栈的原理嘛,先进后出,后进先出
  • 现在将自己写的析构函数注释后,编译器生成的析构函数会帮我们destory嘛
    答案是不会!结果和构造函数才不多
    对于自定义类型的成员变量不作处理,对于自定义类型的成员变量会去调用它的析构函数。

默认析构函数

自动产生的析构函数就真的一点没用了嘛?
其实在一些情况下还是有点用的,有的时候内置类型不需要去销毁且定义了自定义类型,这个时候就不用自己去定义析构函数

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值