https://en.cppreference.com/w/cpp/language/new
https://zh.cppreference.com/w/cpp/language/new
new表达式
创建并且初始化动态存储的对象,对象的声明周期并不受创建作用域的限制。这里很明确的说明了new是创建并且初始化,并且是对象。那么new的考点就是:
-
new与malloc的不同
- malloc是申请一块内存。malloc更简洁,仅仅是分配对应大小的内存,分配好之后给我们使用,具体如何使用由我们自己决定,比如初始化,或者当作数组
- new是创建对象。有了更详细的区分,对不不同类型的对象,比如常见变量类型int,数组或者类都有着细微的区别,更方便我们的使用
-
new的初始化。对不同对象的初始化写法以及结果
-
new[]。申请数组的方法和初始化方法
上面只是大概的考点,还有其他的一些细节,在下面会一一介绍。
语法
::(optional) new (placement_params)(optional) ( type ) initializer(optional)
::(optional) new (placement_params)(optional) type initializer(optional)
new有两种语法,两个的唯一区别就是type是否有括号,之所以有这个区别是因为
- 动态数组只能用第二种无括号的创建
- 由于符号优先级以及聚合的原因,有些类型只能用第一种方式,用括号把类型组合在一起
括号的用法
//这个是错误的,由于优先级组合的原因,导致编译的时候把new int作为一个整体,那么就是申请一个int类型的空间,而后面的(*[10])()是不符合语法的,无法解析,所以会报错
new int(*[10])(); // error: parsed as (new int) (*[10]) ()
//这样就很明确了,括号内int(*[10])()是一个整体,看作一个类型,这个类型是保存函数指针的10个数组元素
new (int (*[10])()); // okay: allocates an array of 10 pointers to functions
上面的int (*[10])()如何解析,请看数组、函数与指针
无括号的优先级组合规则
如果无括号,new的规则是尽可能的与后面有意义的符号组合在一起,如下
//正确,申请一个int,并且把这个指针加一,虽然这个没有意义,本身是有问题的,但是从语法上说是正确的
new int + 1; // okay: parsed as (new int) + 1, increments a pointer returned by new int
//错误,由于new的贪婪的组合规则,导致会把new int *看作一个整体,那么后面的1就无法解析
//就算是写成(new int)*1也是错误的,因为指针没有乘除法,只有加减法
//如果按照默认组合规则如何才能正确呢,需要改成new int*((int*)(1)),这就相当于申请了一个int*的类型,初始化为1。因为1是整数,所以默认情况会提示类型不匹配,强制转换就匹配了,虽然这是无意义的
new int * 1; // error: parsed as (new int*) (1)
必须存在的初始化器
对于最后的一个参数initializer是可选的,但是有些情况是必须的:
- 未知边界的数组
- 使用了占位符
- auto (C++11 起)
- decltype(auto) (C++14 起)
- auto or decltype(auto),附带类型约束 (C++20 起)
- 使用了类模板,且该模板有实参需要被推导
上面三种情况必须要明确初始化,因为后面两种如果不初始化,就无法得知创建的类型,第一种如果不初始化,就无法得知空间大小
//创建一个double的数组,数组的个数没有给,由后面的初始化表达式{1,2,3}确定
double* p = new double[]{1,2,3}; // creates an array of type double[3]
//创建一个auto类型的对象,类型不知,由auto('c')推导出是char,初始化为c
auto p = new auto('c'); // creates a single object of type char. p is a char*
//可以通过auto(1)推导出是int
auto q = new std::integral auto(1); // OK: q is a int*
//前面是floating_pint,而后面是auto(true)是一个布尔类型,所以不匹配,是错误的
auto q = new std::floating_point auto(true) // ERROR: type constraint not satisfied
//可以有(1,true)得到pair的类型是<int, bool>
auto r = new std::pair(1, true); // OK: r is a std::pair<int, bool>*
//这个是错误的,因为无法知道vector的类型,所以就不知道创建多少空间
//可以改写成auto r = new std::vecotr<int>; 这样就是创建一个int类型的vector
//也可以写成auto r = new vector<int>(3); 这样就是创建一个int类型的vector,有3个元素
//auto r1 = new vector<int>[3]; 创建一个vector<int>的数组,数组有3个元素
//auto r2 = new vector<int>{ 1,2,3 }; 创建一个int类型的vector,初始化为1 2 3,有三个元素
auto r = new std::vector; // ERROR: element type can't be deduced
数组
除了第一维是未知的以外,其余维度必须是正的常量整数。因为数组元素不能是未知边界数组,所以未知边界数组只有第一维度是可扩展的。因为数组会把除了第一维度的其他维度看作第一维度数组的元素,所以只能第一维度是未知。
int n = 42;
//直接创建是不允许的,所有维度必须都是常量
double a[n][5]; // error
//可以
auto p1 = new double[n][5]; // OK
//不可以,第二维度不能是未知的
auto p2 = new double[5][n]; // error: only the first dimension may be non-constant
//在visual studio 2017最新版本上测试是可以,可能编译器对括号这种方式做了优化
auto p3 = new (double[n][5]); // error: syntax (1) cannot be used for dynamic arrays
注意事项
- 如果第一维变量是负数,C++11之前行为未知,C++11之后会抛出异常std::bad_array_new_length,如果没有捕获异常会导致程序崩溃,如果不想捕获异常,而是通过空指针判断需要增加nothrow,用法如下
auto p1 = new (nothrow) double[n][5];
- 如果所有维度都是常量可以确定数组大小,再使用{}进行初始化的时候,如果初始化的元素个数大于申请的数组元素,会编译报错
auto p = new int[2]{1,2,3};//报错
- 如果第一维度是未知的,程序不会有任何问题,但是就会导致数组越界,产生更严重并且很难查询的问题
int n = 2;
auto p = new int [n]{1,2,3};
- 如果维度是0,这个是允许的,也是正常的,调用delete[]也不会有问题,但是这个仅仅相当于定义了一个指针,不能对其赋值,因为是0空间,所以没有申请内存,对其赋值操作会导致覆盖了其他正常的元素
构造规则
T不是数组
-
new T 创建一个T的对象,用默认初始化
-
new T(n) 创建一个T的对象,初始化为n,这里如果n是模板或者类,调用的是其对应的构造函数,比如
auto p = new vector<int>(4);
这里并不是创建了一个vector
- new T{n1,n2,...} 创建一个T对象,初始化为n1,n2,...,这个用法可以用作类,结构体(有多个成员),容器(vector)和常见的基本类型
struct test
{
int a;
char b;
};
void main(void)
{
auto p1 = new test{ 1,'c' };
auto p2 = new vector<int>{ 1,2,3 };
auto p3 = new int{ 1 };
}
T是数组
-
new T[n] 创建T的数组,默认初始化
-
new Tn 创建T的数组,并且对每一个元素初始化。这里注意是空括号,括号内并没有参数,初始化的规则基本上是数字是0,字符是0,字符串是空,下面这些是合法的
struct test
{
int a;
char b;
};
void main(void)
{
auto p1 = new test[2]();
auto p2 = new vector<int>[2]();
auto p3 = new int[3]();
auto p4 = new char[5]();
}
- new T[n]{a1, a2, a3...} 创建T的数组,并且按照顺序聚合初始化元素
struct test
{
int a;
char b;
};
void main(void)
{
//这个测试下来是无效的,并没有按照我们写的初始化,但是没有报错
auto p1 = new test[2]{ {1, 'a'}, {2, 'c'} };
auto p2 = new vector<int>[2]{ {1},{2} };
auto p3 = new int[3]{ 1,2,3 };
auto p4 = new char[5]{'a', 'b', 'c'};
}