new的三种用法
1.运算符
1.内置类型使用new
int * ip1 = new int[5]{1,2,3,4,5};
编译器执行的步骤
1.先计算大小,为int类型,有5个则为20个字节
2.从内存中开辟20个字节空间大小
3.把这个空间初始化为1,2,3,4,5
4.把这个空间的地址赋值给ip1
2.自己设计的类型使用new
using namespace std;
class Complex {
private:
int real;
int image;
public:
Complex() :real{ 0 }, image{ 0 }{cout << "create default " << this << endl; }
Complex(int Areal, int Aimage) :real{ Areal }, image{ Aimage } {cout << "create " << this << endl; }
~Complex() { cout << "destory " << this << endl; }
void Print() const {
cout << "const " << endl;
}
void show() {
cout << "show" << endl;
Print();
}
};
int main() {
Complex* ap = new Complex[5]{ {1,2},{3,4},{4,5} };
delete[]ap;
}
此时我们可以看出,在上越界标志下面记录了对象的数量,
而只有 关键字new的这种用法在内存中才会有对象的数量
因此我们可以这样来
计算创建创建对象的数量
int n = * ((int * )ap - 1);
让我们来运行下面的代码验证一下
class Complex {
private:
int real;
int image;
public:
Complex() :real{ 0 }, image{ 0 }{cout << "create default " << this << endl; }
Complex(int Areal, int Aimage) :real{ Areal }, image{ Aimage } {cout << "create " << this << endl; }
~Complex() { cout << "destory " << this << endl; }
void Print() {
cout << real << " " << image << endl;
}
};
int main() {
Complex* ap = new(nothrow) Complex[]{ {1,2},{2,3} };
int n = *((int*)ap - 1);
for (int i = 0; i < n; ++i) {
ap[i].Print();
}
delete[]ap;
}
结果如下
当我们使用new的时候,new和delete的使用一定要对应
new -->delete
new [] --> delete []
举个例子:
当这样创建一个对象时
int main() {
Complex* ap = new Complex(1, 2);
}
就要这样释放 delete ap
千万不能写成这样 delete [] ap;
这样编译器会把上越界标志当成要析构的对象的数量,会析构cdcdcdcd个对象
代码运行结果如下
当这样创建一个对象时
int main() {
Complex* ap = new Complex[5]{ {1,2},{2,3} };
}
释放的时候就要写成这样,delete [] ap;
千万不能写成这样delete ap,
这样的话编译器会只以为只析构一个对象,变会从上越界标志那里开始释放,这样就会造成异常
3.new分配失败之后便会抛出异常, 我们如果不想让它抛出异常的话,在new后面加上(nothrow)就可以了
Complex* ap = new(nothrow) Complex[5]{ {1,2},{2,3} };
delete []ap;
2.函数的用法
与new 不同的点
1.需要手动计算字节大小
2.没有办法初始化
3.返回的地址为void * 类型,需要自己手动强转
1.内置类型使用函数方法
2.自己设计的类型使用函数方法
class Complex {
private:
int real;
int image;
public:
Complex() :real{ 0 }, image{ 0 }{cout << "create default " << this << endl; }
Complex(int Areal, int Aimage) :real{ Areal }, image{ Aimage } {cout << "create " << this << endl; }
~Complex() { cout << "destory " << this << endl; }
void Print() {
cout << real << " " << image << endl;
}
};
int main() {
Complex* ap = (Complex*) :: operator new(sizeof(Complex) * 5);
for (int i = 0; i < 5; ++i) {
new (&ap[i]) Complex(1, 2);
}
for (int i = 0; i < 5; ++i) {
ap[i].Print();
}
for (int i = 0; i < 5; ++i) {
ap[i].~Complex();
}
::operator delete (ap);
ap = nullptr;
}
与new一样,它在分配失败时也会抛出异常
就上面的例子,我们不想让它抛出异常的话,则在后面加上 nothrow就可以了
Complex* ap = (Complex*) :: operator new(sizeof(Complex) * 5,nothrow);
3.定位new
1内置类型使用定位new
我们对开辟的空间重新定位,存储了3个int数据
2.自己设计的类型使用定位new
1.重定位对象
class Complex {
private:
int real;
int image;
public:
Complex() :real{ 0 }, image{ 0 }{cout << "create default " << this << endl; }
Complex(int Areal, int Aimage) :real{ Areal }, image{ Aimage } {cout << "create " << this << endl; }
~Complex() { cout << "destory " << this << endl; }
void Print() {
cout << real << " " << image << endl;
}
};
int main() {
Complex * a =new Complex(1, 2);
a->Print();
new (a) Complex(2, 3);
a->Print();
}
运行结果如下
我们可以看出,这个对象被重新构造了一次。
2.重定位空间
我们要记住一句话“有空间,不一定有对象,有对象,不一定有空间”。
class A {
private:
int value;
public:
A() {}
A(int Avalue) :value{ Avalue } {
cout << "create " << value << " " << this << endl;
}
~A() {
cout << "Destory " << value << " " << this << endl;
}
void Print() {
cout << "Print " <<hex<< value << endl;
}
};
int main() {
A* a = (A*)malloc(sizeof(A));
if (a == nullptr) {
return 1;
}
a->Print();
}
让我们先运行一下观察一下它的结果。
发现打印的值为cdcdcdcd,为什么会这样呐?
这个是a->value的地址,因为在堆中开辟的空间,而堆中的空间默认都拿cdcdcdcd填充,而在调用Print函数的时候,编译器把a->value放到这块地址空间,并打印了出来
但是,我们要清楚,创建一个对象,必须要调用它的构造函数,此时它只是开辟了空间,始终没有创建对象,这样的话, 这个对象没有它的虚表和虚表指针,
在我们给Print函数前面加了virtual之后,它就会报错。
而此时,我们可以直接拿重定位new重新构建这个对象,这样就好了。