c++浅拷贝,深拷贝,以及栈,堆,和静态存储区static,理解与心得。

首先栈,堆,和静态存储区static,都是开辟在内存上的空间,具体来说在c++中,创建一个对象如果不用new创建,那么这个对象的空间就被开辟在了栈上,这样的空间不需要我们在意,使用完程序会自动释放,也不用担心会发生内存泄漏的情况。

和这样的空间类似的空间是static静态空间,这样的空间也不用担心会发生内存泄漏的情况,静态空间是在程序运行前就开辟好的空间,

这里具体说一下static的两种用法,

第一种就是静态变量,

static int a =10 ;

其实这样的变量a可以当作是全局变量使用。

第二种便是局部的静态变量,

void a(int b){

static int c = 10;

c+=1;

}

扫描二维码关注公众号,回复: 17221275 查看本文章

这样的局部静态变量和普通的局部变量相比,他的生命周期更长了,他不会随着每次调用函数而重新初始化,他的值会在那个已经开辟好的空间上进行更新。

其次static还可以修饰成员函数,比如

cpp
class Math {
public:
    static int add(int a, int b) {  // 静态函数
        return a + b;
    }
};

int main() { 
    int sum = Math::add(1, 2);  // 直接调用
}

这样的静态成员函数的特点一目了然,他不需要创建对象就可以调用。

当然还有静态的成员变量,他的特点便是类的所有对象都可以贡献这个静态成员变量。(其实静态成员方法也类似)。

我个人的理解所有被静态修饰的函数或者变量,他们在程序运行前就已经开辟好空间了,这个空间不受任何其他的因素干扰,他们不会再次被任何情况初始化,他们会永远更新并保留自己的值。不会被其他因素再干扰了,而且大家都可以方便调用这个空间或值,这里有一些类似全局变量。

然后,再说一下堆,堆和栈类似,刚才说了,栈是创建对象时没有用new开辟的空间,而堆则是在创建对象时使用new关键词开辟的空间,在c++中使用new字符创建的空间是开辟到堆上的,这样的空间不归程序管,是程序员自己手动控制的,所以new开辟的空间也是很容易发生内存泄漏的一种情况,所以这样的开辟空间方式需要我们手动delete,有两种方式:

cpp
Person::~Person() {
    delete this;   // 手动释放内存
}





cpp
Person* p = new Person();
// ...
delete p;  // 主动调用delete释放内存

上面的是使用析构函数释放内存,下面的是手动删除内存。

其实这里有一个疑问,c++不是说创建类的时候会自动生成默认的析构函数吗,那我为啥还要手动写一个析构函数来释放内存嘞??

其实还是上面说的原理,具体来说,我们使用new关键字开辟了一块堆上的空间,这个空间是包括两部分的,一部分是我们对象要占用的空间,另一部分是一些非静态成员变量所占用的空间,而系统默认的析构函数只会自动释放这些非静态成员变量,而剩下那部分对象所占用的空间,需要我们手动去释放,所以我们需要手动去写析构函数了。(仔细想一下,如果堆空间也可以被系统默认的析构函数释放,那可能我们都见不到析构函数了。。)

接下来说一下浅拷贝和深拷贝。

浅拷贝和深拷贝是发生在拷贝构造函数的时候,简单说一下拷贝构造函数吧,我的理解就是使用取地址符来将对象的地址复制给新的对象。

具体代码:

class Foo {
    int m_p;
public:
    Foo() {
        m_p = new int(10);
    }
    
    Foo(const Foo& other) {
        m_p = other.m_p;  
    }
};
红色部分就是拷贝构造函数,在传变量时可以正常复制传值,但是如果是指针的话,则传递的就是指针了而不是值了。

代码如下:

cpp
class Foo {
    int* m_p;
public:
    Foo() {
        m_p = new int(10);
    }
    
    Foo(const Foo& other) {
        m_p = other.m_p;  // 拷贝指针,共享同一块内存
    }
};

int main() {
    Foo f1;
    Foo f2 = f1;  // 调用浅拷贝的拷贝构造函数
    
    delete f1.m_p;  // f1释放内存
    
    // f2.m_p指向已释放的内存,产生undefined behavior
}

仔细分析可以看到,我们创建的对象f1,在创建的时候自动调用构造函数在堆上开辟了10个空间给到指针m_p,然后我们又创建了第二个对象f2,让f2=f1,也就是调用我们的这个拷贝构造函数,那么我们看这个拷贝构造函数里是不是写的:m_p=other.m_p;这句话的意思是把这个f1的m_p的地址传给f2的m_p,对吧?那么也就是说现在f1对象的m_p成员变量的地址和f2对象的m_p成员变量的地址是一个地址了。这就是浅拷贝,那么这样会发生啥问题呢?第一种我们知道手动释放堆空间内存,如上代码可以看到我们使用delete释放掉了f1的堆内存,但是f2的m_p变量也指向这个地址呀,给他释放掉了,f2指谁呢?所以这样就会造成叫做悬空指针的情况(叫法不一。。)。这样程序会报错的。

第二种就是我们忘记delete我们开辟的堆空间了,那么直接会造成内存泄漏。

那么怎么解决嘞?

既然是公用一个堆空间的问题,那么我们再给开辟一个新的堆空间给f2好了。

没错,这样的方法就是深拷贝。

具体代码:

cpp
class Foo {
    int* m_p;
public:
    Foo() {
        m_p = new int(10);
    }
    
    Foo(const Foo& other) {
       // m_p = other.m_p;  // 拷贝指针,共享同一块内存
        m_p = new int(*other.m_p);
    }


};

~Foo(){

delete m_p;
}
int main() {
    Foo f1;
    Foo f2 = f1;  // 调用浅拷贝的拷贝构造函数
    
    delete f1.m_p;  // f1释放内存
    
    // f2.m_p指向已释放的内存,产生undefined behavior
}

很容易明白,我们为新拷贝的对象开辟的新的堆空间,这样就避免了刚才发生的问题。

简单来说就这些吧。。会继续更新~

猜你喜欢

转载自blog.csdn.net/qq_40962125/article/details/130769181