首先栈,堆,和静态存储区static,都是开辟在内存上的空间,具体来说在c++中,创建一个对象如果不用new创建,那么这个对象的空间就被开辟在了栈上,这样的空间不需要我们在意,使用完程序会自动释放,也不用担心会发生内存泄漏的情况。
和这样的空间类似的空间是static静态空间,这样的空间也不用担心会发生内存泄漏的情况,静态空间是在程序运行前就开辟好的空间,
这里具体说一下static的两种用法,
第一种就是静态变量,
static int a =10 ;
其实这样的变量a可以当作是全局变量使用。
第二种便是局部的静态变量,
void a(int b){
static int c = 10;
c+=1;
}
这样的局部静态变量和普通的局部变量相比,他的生命周期更长了,他不会随着每次调用函数而重新初始化,他的值会在那个已经开辟好的空间上进行更新。
其次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
}
很容易明白,我们为新拷贝的对象开辟的新的堆空间,这样就避免了刚才发生的问题。
简单来说就这些吧。。会继续更新~