C++ 内存区
C++ 内存通常分为 4 个区域:
- 全局数据区(data area);
- 代码区(code area);
- 堆区(自由存储区)(heap area);
- 栈区(stack area);
在 C 语言中,我们通过 malloc 或者 calloc 申请的空间即为堆区的空间,使用完成后用 free 归还申请的内存;而在 C++ 中我们用 new 申请堆区内存,delete 释放内存。操作堆内存时,有借有还,分配了堆内存就要记得对其进行回收,当然,这在 C++ 中是一件很麻烦的事情。
new 和 delete
C++ 是面向对象编程语言,类和对象变得尤为重要,也是 C++ 与 C 语言的主要区分标志,在 C++ 中我们不能再依赖 C 语言中的 malloc ()等函数申请内存,其中一个原因是,它不能在分配空间时调用类中的构造函数,然而类对象的建立正是由构造函数来完成的。
而 free()函数也不会调用类中的析构函数,关于构造函数及析构函数详见文章。
通过 malloc 申请到的对象空间无非就是一个含有随机数据的类对象空间而已,值不确定,毫无意义,要使得对象有意义我们还需要在后续自行调用构造函数,十分不便,故 C++ 用 new 代替 malloc ()那是必然的。
使用 new 分配堆对象
C++ 的 new 和 deleta 机制简单易懂,以下程序片段演示了堆对象空间的申请
class student //student类
{
public:
//..
private:
//..
};
void func(){
student* p;
p=new student;//申请堆对象,p指向该对象地址,此时C++自动调用构造函数student()
//..
delete p;//释放堆对象的空间,此时C++自动调用析构函数~student()
}
如果需要调用有参构造函数,参考以下程序片段
class Tdate{
public:
Tdate(int m,int d,int y);//有参构造函数
Tdate(){};//无参构造函数
protected:
int month;
int day;
int year;
};
Tdate::Tdate(int m,int d,int y)
{
//..
}
void fun(){
Tdate* p;
//p=new Tdate();调用无参构造函数的方法
p=new Tdate(1,1,2021);//调用有参构造函数
//..
delete p;
}
delete 与 delete[]的使用
delete 的用法如以上内容,在其后跟上指向需要释放的空间的指针即可;而 delete[]我们通过后面的“[]”就知道这是用于释放数组空间的,如果我们申请的是如下的堆空间
void fun(){
Tdate* p;
p=new Tdate[5];//分配5个对象数组空间,此时只能调用默认的无参构造函数
//..
delete[] p;//释放这5个对象数组空间
}
delete[]即是告诉 C++ 该指针指向的是一个数组,[]不需要写上数组长度,如果有,C++ 编译器也会将其忽略,但绝不能忘记写[]。
拷贝构造函数
拷贝构造函数,顾名思义,用于拷贝一个对象然后去构造另一个对象的函数,即用一个对象的值去初始化一个新构造的对象,如以下代码片段
student s1("Henry");
student s2=s1;//拷贝对象s1至s2,此时会调用拷贝构造函数
将对象作为函数参数传递时,也涉及对象的拷贝,因为函数调用涉及实参到形参的传递,也就是将实参对象拷贝到形参对象,对象的类型多种多样,很多对象中的数据并不像基本的 int、double 等能够简单的赋值拷贝,比如对象里可能存在用 new 申请的堆空间?那么显然就不能进行简单的赋值拷贝,这也就引出了拷贝构造函数的意义。
以下程序片段演示了如何编写我们需要的拷贝构造函数
class student{
private:
string name;
int id;
public:
student(string nName="null",int nId=0)//构造函数
{
this->name=nName;
this->id=nId;
cout<<"Constructing new student "<<nName<<endl;
}
student(student& s){//拷贝构造函数,student&为student类对象的引用,引用的内容参考前一篇文章
cout<<"Constructing copy of "<<s.name<<endl;
this->name="copy of "+s.name;
this->id=s.id;
}
~student(){
cout<<"delete "<<this->name<<endl;
}
};
void fun(student s){
cout<<"In function fun()"<<endl;
}
int main()
{
student henry("Henry",21);
cout<<"Calling fun()"<<endl;
fun(henry);
cout<<"Returned from fun()"<<endl;
return 0;
}
运行结果
Constructing new student Henry
Calling fun()
Constructing copy of Henry
In function fun()
delete copy of Henry
Returned from fun()
delete Henry
默认拷贝构造函数
与构造函数类似,当开发者没有定义自己的拷贝构造函数时,C++ 将提供一个默认拷贝构造函数。
其工作方法为:完成一个成员一个成员的简单拷贝。
浅拷贝与深拷贝
浅拷贝即是像默认拷贝构造函数那样对数据成员进行简单的复制,那么如果对象中存在分配的资源(如堆内存)我们就不能在进行简单的浅拷贝,那样会使多个对象拥有同一块内存资源,如果其中一个对象遭到释放,那么其他对象将面临严重的内存堆栈错误,并且,在对象进行析构时,也会多次释放同一块资源,程序崩溃。
以下程序片段演示了深拷贝
class student{
public:
student(char* nName);
student(student& s);
~student();
protected:
char* name;
};
student::student(cahr* nName){//构造函数
this->name=new char[strlen(nName)+1];//申请新内存
if(nName)
strcpy(this->name,nName);
}
student::student(student& s){//深拷贝构造函数
this->name=new char[strlen(s.name)+1];//申请新内存
if(s.name)
strcpy(this->name,s.name);
}
student::~student(){
name[0]="\0";
delete name;
}
无名对象
仅简述无名对象的三种用法
//此程序片段中student为一个类
student& refs=student("Henry");//初始化引用
student s=student("Henry");//初始化对象
fun(student("Henry"));//作为函数参数
【补充】this 指针
this 指针,其实我们看名字可以知道,这个指针肯定是指向与自己相关的,或正在处理的内存空间。
的确如此,一个类中所有对象调用的成员函数都处于同一个代码段,成员函数为了区分数据成员属于哪一个对象,故出现了 this 指针。
如在对象 s 调用成员函数 set()时:s.set(1,1,2021),成员函数 set 除了接受了 3 个实参外,还接受了一个对象的地址(对象 s 的地址),这个地址被隐含的形参 this 指针所获取即 this 相当于&s,所有都数据成员的访问都隐含地被加上了 this->,在本文前面的代码片段中,我特意加上了 this->,方便读者理解。
//以下三种数据成员访问方法等价
month=m;
this->month=m;
s.month=m;
我们在一个成员函数需要返回当前处理的对象或对象的地址时,this 指针就成为了必要,如以下程序片段
student student::fun(){
//..
return *this;//返回当前对象
//return this;//返回指向当前对象的指针
}
编辑:Henry 2021-03-04 未授权禁止转载
版权属于:字节星球/肥柴之家 (转载请联系作者授权)
原文链接:https://www.bytecho.net/archives/1710.html
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
【浅拷贝与深拷贝】里,为什么说:那么如果对象中存在分配的资源(如堆内存)我们就不能在进行简单的浅拷贝,那样会使多个对象拥有同一块内存资源。不太理解为什么会拥有同一块内存。
你好,因为浅拷贝对于字符数组等仅会复制其内存地址,也就使得你复制出来的新变量依然指向你复制的那个变量的地址。这也是为什么字符数组需要用strcpy复制的原因。其他需要深拷贝的案例也可以这样理解。
我知道了,谢谢博主OωO
欸,那是不是类的成员变量里没有指针类型的变量,使用浅拷贝就不会发生指向同一块内存的问题了?
其实,只要理解“拷贝”究竟拷贝的是什么就可以了。
@(OK)