那些年,我与c++的交友日记(初识二):(保姆级教学)三大重要默认函数与栈的实例(c++的受益者)

本文详细解释了C++中的构造函数、析构函数、拷贝构造函数等概念,强调了内存泄漏问题及其解决方案,以栈为例展示了它们的使用。
摘要由CSDN通过智能技术生成

引子:

如果一个类中什么成员都没有,简称为空类。

空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员 函数。 默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

提示:六大函数分别是默认构造函数,拷贝构造函数,移动构造函数,默认析构函数,赋值重载运算符,移动赋值运算符!

那本讲的重点是构造函数,析构函数,拷贝函数,以及栈的c++编写!

附录:关系图如下:

构造函数:

什么是构造函数?简单来说就是完成初始化的工作,你可以理解为类似init();

构造函数的水很深!

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

从中我们可以看出我们没有进行初始化,但是,我们可以打印出对应的值,在这里就用到了默认函数“构造函数!”

注意:有时你看看到的是0,0,0!不要认为是初始化为0的,注意:这只是有些编译器,不是C++的标准,c++没有规定,而且我们也要从最坏情况考虑!

在这里我就不得不讲一下构造函数的基本特征了!

其特征如下:

1. 函数名与类名相同。(什么意思呢?就是我们手写初始函数时是类名)

2. 无返回值。(也就是说是void类型)

3. 对象实例化时编译器自动调用对应的构造函数。(默认构造函数)(重点!)

4. 构造函数可以重载。(可以重载,但是注意可能会有编译器的调用歧义!)

ok,铁汁们,我们来重点讲一下第三点自动调用对应的构造函数啊!

C++把类型分成内置类型(基本类型)和自定义类型

内置类型就是语言提供的数据类 型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。

那什么是默认的构造函数?

无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。(否则会有歧义!) 注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。(注意:对于内置类型(基本类型)c++不做处理,初始化!而且编译器不报错误!)

其实也很好理解:

我们只要记住不用传参数就可以调用的函数就是默认构造函数!而且一般情况下我们都要写显示定义哦!,也就是要写初始化!(因为:这是一个嵌套原理,老铁,你可以想自定义类型最后还是内置类型) 

注意默认构造函数只能有一个。(否则会有歧义!)的理解:

如图(正解):(我们有二种方式进行构造函数!如下);

注意:在c++中nullptr才是空指针!(小知识!)

C++11的补丁:

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

析构函数:

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

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

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

2. 无参数无返回值类型。

3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载

4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

同构造函数一样:默认析构函数内置类型(基本类型)和自定义类型

1,对于内置类型(基本类型)c++不做处理,而且编译器不报错误!)

2,对于自定义类型,去调用它的自定义析构函数!

注意:如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如 Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

如图分析以下二张图就可以知道为什么栈的实例(c++的受益者):

对于析构函数的总结:

对于构造函数,析构函数的顺序,生命周期的理解:

注意:一个类只有一个构造函数与析构函数,任何说有二个构造函数或二个析构函数都是错的!

我总结以下几个原则!

1,构造顺序是按照语句的顺序进行构造,析构是按照构造的相反顺序进行析构!

2,对象析构要在生存作用域结束的时候才进行析构!

3:类的析构函数调用一般按照构造函数调用的相反顺序进行调用,但是要注意static对象的存在, 因为static改变了对象的生存作用域,需要等待程序结束时才会析构释放对象!

4,全局对象先于局部对象进行构造,局部对象按照出现的顺序进行构造,无论是否为static!

5,需注意static改变对象的生存作用域之后,会放在局部对象之后进行析构!

拷贝函数:

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰)(如比较大小时,我们不希望进行改变本身值的大小!所以用const!),在用已存 在的类类型对象创建新对象时由编译器自动调用。

其特征如下:

1. 拷贝构造函数是构造函数的一个重载形式。所以为

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

我们看以下二张图!

用引用!

没用引用:

这是为何?

其实是使用传值方式编译器直接报错, 因为会引发无穷递归调用了。

原理图:

拷贝也有二种形式!

以下是第二种:

3,若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按 字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。(此处要进行资源的考虑!若要进行资源管理则要自显示!)

对于第三点的原理图:

针对:构造函数,析构函数以及拷贝函数等等等的函数都要注意的一点就是:内存泄漏问题!

内存泄漏(Memory Leak)是指程序在分配内存后没有正确释放,导致这部分内存不再可用,并且无法通过常规的手段进行管理和重用!

在C++中,内存泄漏通常发生在以下几种情况:

  1. 使用new或malloc分配的堆内存没有使用delete或free进行释放。

  2. 指针在不再需要内存时没有被设置为nullptr,导致后续可能发生非法访问。

  3. 类的析构函数没有正确释放对象持有的资源。

  4. 长时间运行的应用中,内存泄漏累积到一定程度会导致内存耗尽。

解决方法:

  1. 使用智能指针(如std::unique_ptr, std::shared_ptr)管理资源,自动释放内存。

  2. 确保所有指针在不再使用后被正确地释放或置nullptr。

  3. 使用内存分析工具(如Valgrind, AddressSanitizer)检测内存泄漏。

  4. 定期审查和测试代码,确保所有内存分配有对应的释放操作。

  5. 在类的析构函数中正确释放所有需要释放的资源。

  6. 在开发周期中实施内存泄漏检测和防护策略。

栈的实例(c++的受益者)

代码:

typedef int STDataType;
class STack
{
public:
    STack()
    {
        _a = nullptr;
        _top = 0;
        _capacity = 0;
        cout << "调用了构造函数" << endl;
    }
    ~STack()
    {
    cout << "调用了析构函数~Stack()" << endl;
        if (_a)
        {
            free(_a);
            _a= nullptr;
            _capacity = 0;
            _top = 0;
        }
    }
    void StackPush(STDataType date)
    {
        //assert(_a);
        if (_capacity == _top)
        {
            int newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
            int* tmp = (int*)realloc(_a, sizeof(int) * newcapacity);
            if (tmp == NULL)
            {
                perror("realloc fail!");
                return;
            }
            _a = tmp;
            _capacity = newcapacity;
        }
        _a[_top] = date;
        _top++;
    }
    bool  Empty()
    {
        return _top == 0;
    }
    STDataType Top()
    {
        return _a[_top-1];
    }
    void print()
    {
        cout << _a << " " << _top << " " << _capacity <<" ";
    }
    void pop() 
    {
      _top--;
    }
private:
    STDataType* _a=nullptr;
    int _top=0;        // 栈顶
    int _capacity=0;  // 容量
};
int main()
{
    STack T1;
    T1.print();
    T1.StackPush(1);
    T1.StackPush(3);
    T1.StackPush(1);
    T1.StackPush(8);
    T1.StackPush(23);
    T1.StackPush(63);
    T1.StackPush(675);
    while (!T1.Empty())
    {
        cout << T1.Top() << " ";
        T1.pop();
    }
    cout << endl;
    T1.print();
    cout << endl;
    return 0;
}

结果:

谢幕:

今天就到这啦,大家多多给我一点支持!

下次,我将分享运算符重载!

  • 19
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值