C++ 的 new 关键字深入理解

C++基础 — new的使用 new的使用 阅读详情


new”是C++的一个关键字,同时也是操作符。关于new的话题非常多,因为它确实比较复杂,也非常神秘,下面我将把我了解到的与new有关的内容做一个总结。

new的过程

当我们使用关键字new在堆上动态创建一个对象时,它实际上做了三件事:获得一块内存空间、调用构造函数、返回正确的指针。当然,如果我们创建的是简单类型的变量,那么第二步会被省略。假如我们定义了如下一个类A:

1
2
3
4
5
6
7
8
9
class A
{
int i;
public:
A(int _i) :i(_i*_i) {}
void Say() { printf("i=%d\n", i); }
};
//调用new:
A* pa = new A(3);

那么上述动态创建一个对象的过程大致相当于以下三句话(只是大致上):

1
2
3
A* pa = (A*)malloc(sizeof(A));
pa->A::A(3);
return pa;

虽然从效果上看,这三句话也得到了一个有效的指向堆上的A对象的指针pa,但区别在于,当malloc失败时,它不会调用分配内存失败处理程序new_handler,而使用new的话会的。因此我们还是要尽可能的使用new,除非有一些特殊的需求。

new 的三种形态

到目前为止,本文所提到的new都是指的“new operator”或称为“new expression”,但事实上在C++中一提到new,至少可能代表以下三种含义:new operator、operator new、placement new。

new operator就是我们平时所使用的new,其行为就是前面所说的三个步骤,我们不能更改它。但具体到某一步骤中的行为,如果它不满足我们的具体要求时,我们是有可能更改它的。三个步骤中最后一步只是简单的做一个指针的类型转换,没什么可说的,并且在编译出的代码中也并不需要这种转换,只是人为的认识罢了。但前两步就有些内容了。

new operator的第一步分配内存实际上是通过调用operator new来完成的,这里的new实际上是像加减乘除一样的操作符,因此也是可以重载的。operator new默认情况下首先调用分配内存的代码,尝试得到一段堆上的空间,如果成功就返回,如果失败,则转而去调用一个new_hander,然后继续重复前面过程。如果我们对这个过程不满意,就可以重载operator new,来设置我们希望的行为。例如:

1
2
3
4
5
6
7
8
9
class A
{
public:
void* operator new(size_t size)
{
printf("operator new called\n");
return ::operator new(size);
}
};
1
A* a = new A();

这里通过::operator new调用了原有的全局的new,实现了在分配内存之前输出一句话。全局的operator new也是可以重载的,但这样一来就不能再递归的使用new来分配内存,而只能使用malloc了:

1
2
3
4
5
void* operator new(size_t size)
{
printf("global new\n");
return malloc(size);
}

相应的,delete也有delete operator和operator delete之分,后者也是可以重载的。并且,如果重载了operator new,就应该也相应的重载operator delete,这是良好的编程习惯。

new的第三种形态——placement new是用来实现定位构造的,因此可以实现new operator三步操作中的第二步,也就是在取得了一块可以容纳指定类型对象的内存后,在这块内存上构造一个对象,这有点类似于前面代码中的“p->A::A(3);”这句话,但这并不是一个标准的写法,正确的写法是使用

1
2
3
4
5
6
7
8
9
10
placement 
new:
#include
void main()
{
char s[sizeof(A)];
A* p = (A*)s;
new(p) A(3); //p->A::A(3);
p->Say();
}

对头文件或的引用是必须的,这样才可以使用placement new。这里“new(p) A(3)”这种奇怪的写法便是placement new了,它实现了在指定内存地址上用指定类型的构造函数来构造一个对象的功能,后面A(3)就是对构造函数的显式调用。这里不难发现,这块指定的地址既可以是栈,又可以是堆,placement对此不加区分。但是,除非特别必要,不要直接使用placement new ,这毕竟不是用来构造对象的正式写法,只不过是new operator的一个步骤而已。使用new operator地编译器会自动生成对placement new的调用的代码,因此也会相应的生成使用delete时调用析构函数的代码。如果是像上面那样在栈上使用了placement new,则必须手工调用析构函数,这也是显式调用析构函数的唯一情况:

1
p->~A();

当我们觉得默认的new operator对内存的管理不能满足我们的需要,而希望自己手工的管理内存时,placement new就有用了。STL中的allocator就使用了这种方式,借助placement new来实现更灵活有效的内存管理。

处理内存分配异常

正如前面所说,operator new的默认行为是请求分配内存,如果成功则返回此内存地址,如果失败则调用一个new_handler,然后再重复此过程。于是,想要从operator new的执行过程中返回,则必然需要满足下列条件之一:

  • 分配内存成功

  • new_handler中抛出bad_alloc异常

  • new_handler中调用exit()或类似的函数,使程序结束

于是,我们可以假设默认情况下operator new的行为是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void* operator new(size_t size)
{
void* p = null
while(!(p = malloc(size)))
{
if(null == new_handler)
throw bad_alloc();
try
{
new_handler();
}
catch(bad_alloc e)
{
throw e;
}
catch(…)
{}
}
return p;
}

在默认情况下,new_handler的行为是抛出一个bad_alloc异常,因此上述循环只会执行一次。但如果我们不希望使用默认行为,可以自定义一个new_handler,并使用std::set_new_handler函数使其生效。在自定义的new_handler中,我们可以抛出异常,可以结束程序,也可以运行一些代码使得有可能有内存被空闲出来,从而下一次分配时也许会成功,也可以通过set_new_handler来安装另一个可能更有效的new_handler。例如:

1
2
3
4
5
6
void MyNewHandler()
{
printf(“New handler called!\n”);
throw std::bad_alloc();
}
std::set_new_handler(MyNewHandler);

这里new_handler程序在抛出异常之前会输出一句话。应该注意,在new_handler的代码里应该注意避免再嵌套有对new的调用,因为如果这里调用new再失败的话,可能会再导致对new_handler的调用,从而导致无限递归调用。——这是我猜的,并没有尝试过。

在编程时我们应该注意到对new的调用是有可能有异常被抛出的,因此在new的代码周围应该注意保持其事务性,即不能因为调用new失败抛出异常来导致不正确的程序逻辑或数据结构的出现。例如:

1
2
3
4
5
6
7
8
9
10
11
class SomeClass
{
static int count;
SomeClass() {}
public:
static SomeClass* GetNewInstance()
{
count++;
return new SomeClass();
}
};

静态变量count用于记录此类型生成的实例的个数,在上述代码中,如果因new分配内存失败而抛出异常,那么其实例个数并没有增加,但count变量的值却已经多了一个,从而数据结构被破坏。正确的写法是:

1
2
3
4
5
6
static SomeClass* GetNewInstance()
{
SomeClass* p = new SomeClass();
count++;
return p;
}

这样一来,如果new失败则直接抛出异常,count的值不会增加。类似的,在处理线程同步时,也要注意类似的问题:

1
2
3
4
5
6
7
void SomeFunc()
{
lock(someMutex); //加一个锁
delete p;
p = new SomeClass();
unlock(someMutex);
}

此时,如果new失败,unlock将不会被执行,于是不仅造成了一个指向不正确地址的指针p的存在,还将导致someMutex永远不会被解锁。这种情况是要注意避免的。(参考:C++箴言:争取异常安全的代码)

STL 的内存分配与 traits 技巧

在《STL原码剖析》一书中详细分析了SGI STL的内存分配器的行为。与直接使用new operator不同的是,SGI STL并不依赖C++默认的内存分配方式,而是使用一套自行实现的方案。首先SGI STL将可用内存整块的分配,使之成为当前进程可用的内存,当程序中确实需要分配内存时,先从这些已请求好的大内存块中尝试取得内存,如果失败的话再尝试整块的分配大内存。这种做法有效的避免了大量内存碎片的出现,提高了内存管理效率。

为了实现这种方式,STL使用了placement new,通过在自己管理的内存空间上使用placement new来构造对象,以达到原有new operator所具有的功能。

1
2
3
4
5
template
inline void construct(T1* p, const T2& value)
{
new(p) T1(value);
}

此函数接收一个已构造的对象,通过拷贝构造的方式在给定的内存地址p上构造一个新对象,代码中后半截T1(value)便是placement new语法中调用构造函数的写法,如果传入的对象value正是所要求的类型T1,那么这里就相当于调用拷贝构造函数。类似的,因使用了placement new,编译器不会自动产生调用析构函数的代码,需要手工的实现:

1
2
3
4
5
template
inline void destory(T* pointer)
{
pointer->~T();
}

与此同时,STL中还有一个接收两个迭代器的destory版本,可将某容器上指定范围内的对象全部销毁。典型的实现方式就是通过一个循环来对此范围内的对象逐一调用析构函数。如果所传入的对象是非简单类型,这样做是必要的,但如果传入的是简单类型,或者根本没有必要调用析构函数的自定义类型(例如只包含数个int成员的结构体),那么再逐一调用析构函数是没有必要的,也浪费了时间。为此,STL使用了一种称为“type traits”的技巧,在编译器就判断出所传入的类型是否需要调用析构函数:

1
2
3
4
5
template
inline void destory(ForwardIterator first, ForwardIterator last)
{
__destory(first, last, value_type(first));
}

其中value_type()用于取出迭代器所指向的对象的类型信息,于是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template
inline void __destory(ForwardIterator first, ForwardIterator last, T*)
{
typedef typename __type_traits<T>::has_trivial_destructor 
trivial_destructor;
__destory_aux(first, last, trivial_destructor());
}
//如果需要调用析构函数:
template
inline void __destory_aux(ForwardIterator first, ForwardIterator last, 
__false_type)
{
for(; first < last; ++first)
destory(&*first); //因first是迭代器,*first取出其真正内容,然后再用&取地址
}
//如果不需要,就什么也不做:
tempalte
inline void __destory_aux(ForwardIterator first, ForwardIterator last, 
__true_type)
{}

因上述函数全都是inline的,所以多层的函数调用并不会对性能造成影响,最终编译的结果根据具体的类型就只是一个for循环或者什么都没有。这里的关键在于__type_traits这个模板类上,它根据不同的T类型定义出不同的has_trivial_destructor的结果,如果T是简单类型,就定义为__true_type类型,否则就定义为__false_type类型。其中__true_type、__false_type只不过是两个没有任何内容的类,对程序的执行结果没有什么意义,但在编译器看来它对模板如何特化就具有非常重要的指导意义了,正如上面代码所示的那样。__type_traits也是特化了的一系列模板类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct __true_type {};
struct __false_type {};
template
struct __type_traits
{
public:
typedef __false _type has_trivial_destructor;
……
};
template<> //模板特化
struct __type_traits<int>//int的特化版本
{
public:
typedef __true_type has_trivial_destructor;
……
};
…… //其他简单类型的特化版本

如果要把一个自定义的类型MyClass也定义为不调用析构函数,只需要相应的定义__type_traits的一个特化版本即可:

1
2
3
4
5
6
7
template<>
struct __type_traits<MyClass>
{
public:
typedef __true_type has_trivial_destructor;
……
};

模板是比较高级的C++编程技巧,模板特化、模板偏特化就更是技巧性很强的东西,STL中的type_traits充分借助模板特化的功能,实现了在程序编译期通过编译器来决定为每一处调用使用哪个特化版本,于是在不增加编程复杂性的前提下大大提高了程序的运行效率。更详细的内容可参考《STL源码剖析》第二、三章中的相关内容。带有“[]”的new和delete

我们经常会通过new来动态创建一个数组,例如:

1
2
3
char* s = new char[100];
……
delete s;

严格的说,上述代码是不正确的,因为我们在分配内存时使用的是new[],而并不是简单的new,但释放内存时却用的是delete。正确的写法是使用delete[]:

1
delete[] s;

但是,上述错误的代码似乎也能编译执行,并不会带来什么错误。事实上,new与new[]、delete与delete[]是有区别的,特别是当用来操作复杂类型时。假如针对一个我们自定义的类MyClass使用new[]:

1
MyClass* p = new MyClass[10];

上述代码的结果是在堆上分配了10个连续的MyClass实例,并且已经对它们依次调用了构造函数,于是我们得到了10个可用的对象,这一点与Java、C#有区别的,Java、C#中这样的结果只是得到了10个null。换句话说,使用这种写法时MyClass必须拥有不带参数的构造函数,否则会发现编译期错误,因为编译器无法调用有参数的构造函数。

当这样构造成功后,我们可以再将其释放,释放时使用delete[]:

1
delete[] p;

当我们对动态分配的数组调用delete[]时,其行为根据所申请的变量类型会有所不同。如果p指向简单类型,如int、char等,其结果只不过是这块内存被回收,此时使用delete[]与delete没有区别,但如果p指向的是复杂类型,delete[]会针对动态分配得到的每个对象调用析构函数,然后再释放内存。因此,如果我们对上述分配得到的p指针直接使用delete来回收,虽然编译期不报什么错误(因为编译器根本看不出来这个指针p是如何分配的),但在运行时(DEBUG情况下)会给出一个Debug assertion failed提示。

到这里,我们很容易提出一个问题——delete[]是如何知道要为多少个对象调用析构函数的?要回答这个问题,我们可以首先看一看new[]的重载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MyClass
{
int a;
public:
MyClass() { printf("ctor\n"); }
~MyClass() { printf("dtor\n"); }
};
void* operator new[](size_t size)
{
void* p = operator new(size);
printf("calling new[] with size=%d address=%p\n", size, p);
return p;
}
// 主函数
MyClass* mc = new MyClass[3];
printf("address of mc=%p\n", mc);
delete[] mc;

运行此段代码,得到的结果为:(VC2005)

1
2
3
4
5
6
7
8
calling new[] with size=16 address=003A5A58
ctor
ctor
ctor
address of mc=003A5A5C
dtor
dtor
dtor

虽然对构造函数和析构函数的调用结果都在预料之中,但所申请的内存空间大小以及地址的数值却出现了问题。我们的类MyClass的大小显然是4个字节,并且申请的数组中有3个元素,那么应该一共申请12个字节才对,但事实上系统却为我们申请了16字节,并且在operator new[]返后我们得到的内存地址是实际申请得到的内存地址值加4的结果。也就是说,当为复杂类型动态分配数组时,系统自动在最终得到的内存地址前空出了4个字节,我们有理由相信这4个字节的内容与动态分配数组的长度有关。通过单步跟踪,很容易发现这4个字节对应的int值为0×00000003,也就是说记录的是我们分配的对象的个数。改变一下分配的个数然后再次观察的结果证实了我的想法。于是,我们也有理由认为new[] operator的行为相当于下面的伪代码:

1
2
3
4
5
6
7
8
9
10
11
template
T* New[](int count)
{
int size = sizeof(T) * count + 4;
void* p = T::operator new[](size);
*(int*)p = count;
T* pt = (T*)((int)p + 1); //原文为(int)p+4,有误
for(int i = 0; i < count; i++)
new(&pt[i]) T();
return pt;
}

上述示意性的代码省略了异常处理的部分,只是展示当我们对一个复杂类型使用new[]来动态分配数组时其真正的行为是什么,从中可以看到它分配了比预期多4个字节的内存并用它来保存对象的个数,然后对于后面每一块空间使用placement new来调用无参构造函数,这也就解释了为什么这种情况下类必须有无参构造函数,最后再将首地址返回。类似的,我们很容易写出相应的delete[]的实现代码:

1
2
3
4
5
6
7
8
9
template
void Delete[](T* pt)
{
int count = ((int*)pt)[-1];
for(int i = 0; i < count; i++)
pt[i].~T();
void* p = (void*)((int)pt – 1); //原文为(int)pt-4,有误
T::operator delete[](p);
}

由此可见,在默认情况下operator new[]与operator new的行为是相同的,operator delete[]与operator delete也是,不同的是new operator与new[] operator、delete operator与delete[] operator。当然,我们可以根据不同的需要来选择重载带有和不带有“[]”的operator new和delete,以满足不同的具体需求。

把前面类MyClass的代码稍做修改——注释掉析构函数,然后再来看看程序的输出:

1
2
3
4
5
calling new[] with size=12 address=003A5A58
ctor
ctor
ctor
address of mc=003A5A58

这一次,new[]老老实实的申请了12个字节的内存,并且申请的结果与new[] operator返回的结果也是相同的,看来,是否在前面添加4个字节,只取决于这个类有没有析构函数,当然,这么说并不确切,正确的说法是这个类是否需要调用析构函数,因为如下两种情况下虽然这个类没声明析构函数,但还是多申请了4个字节:一是这个类中拥有需要调用析构函数的成员,二是这个类继承自需要调用析构函数的类。于是,我们可以递归的定义“需要调用析构函数的类”为以下三种情况之一:

  1. 显式的声明了析构函数的

  2. 拥有需要调用析构函数的类的成员的

  3. 继承自需要调用析构函数的类的

类似的,动态申请简单类型的数组时,也不会多申请4个字节。于是在这两种情况下,释放内存时使用delete或delete[]都可以,但为养成良好的习惯,我们还是应该注意只要是动态分配的数组,释放时就使用delete[]。

释放内存时如何知道长度

但这同时又带来了新问题,既然申请无需调用析构函数的类或简单类型的数组时并没有记录个数信息,那么operator delete,或更直接的说free()是如何来回收这块内存的呢?这就要研究malloc()返回的内存的结构了。与new[]类似的是,实际上在malloc()申请内存时也多申请了数个字节的内容,只不过这与所申请的变量的类型没有任何关系,我们从调用malloc时所传入的参数也可以理解这一点——它只接收了要申请的内存的长度,并不关系这块内存用来保存什么类型。下面运行这样一段代码做个实验:

1
2
3
4
5
6
7
char *p = 0;
for(int i = 0; i < 40; i += 4)
{
char* s = new char[i];
printf("alloc %2d bytes, address=%p distance=%d\n", i, s, s - p);
p = s;
}

我们直接来看VC2005下Release版本的运行结果,DEBUG版因包含了较多的调试信息,这里就不分析了:

1
2
3
4
5
6
7
8
9
10
alloc 0 bytes, address=003A36F0 distance=3815152
alloc 4 bytes, address=003A3700 distance=16
alloc 8 bytes, address=003A3710 distance=16
alloc 12 bytes, address=003A3720 distance=16
alloc 16 bytes, address=003A3738 distance=24
alloc 20 bytes, address=003A84C0 distance=19848
alloc 24 bytes, address=003A84E0 distance=32
alloc 28 bytes, address=003A8500 distance=32
alloc 32 bytes, address=003A8528 distance=40
alloc 36 bytes, address=003A8550 distance=40

每一次分配的字节数都比上一次多4,distance值记录着与上一次分配的差值,第一个差值没有实际意义,中间有一个较大的差值,可能是这块内存已经被分配了,于是也忽略它。结果中最小的差值为16字节,直到我们申请16字节时,这个差值变成了24,后面也有类似的规律,那么我们可以认为申请所得的内存结构是如下这样的:

从图中不难看出,当我们要分配一段内存时,所得的内存地址和上一次的尾地址至少要相距8个字节(在DEBUG版中还要更多),那么我们可以猜想,这8个字节中应该记录着与这段所分配的内存有关的信息。观察这8个节内的内容,得到结果如下:

图中右边为每次分配所得的地址之前8个字节的内容的16进制表示,从图中红线所表示可以看到,这8个字节中的第一个字节乘以8即得到相临两次分配时的距离,经过试验一次性分配更大的长度可知,第二个字节也是这个意义,并且代表高8位,也就说前面空的这8个字节中的前两个字节记录了一次分配内存的长度信息,后面的六个字节可能与空闲内存链表的信息有关,在翻译内存时用来提供必要的信息。这就解答了前面提出的问题,原来C/C++在分配内存时已经记录了足够充分的信息用于回收内存,只不过我们平常不关心它罢了。


转自:http://www.devstore.cn/essay/essayInfo/7712.html


备注:本文所说 delete 可以用于删除数组,编译可以通过,但有时运行会core dump,有时却不会。

【C++】几种类型的new介绍 现在的C++中,New有三种典型的使用方法,对应于下面三种格式。 1.new : 普通的new操作,一旦内存分配失败,直接抛出一个异常,需要用catch来处理这种异常信息。 2. new(nothrow) : 不抛出异常的new操作, 一旦内存分配失败,禁止抛出异常,而是返回一个NULL,可以通过判断指针是不是NULL来处理这种异常。 3. placement new : 直接复用已经分配好... 阅读详情

相关推荐

C++ new的使用

C++ new的使用1、使用new 构建动态内存空间2、使用new在自定义动态内存空间创建对象,及是否对象方法3、使用new在构造函数中创建对象 1、使用new 构建动态内存空间 int iCount = 0; cin >> iCount; char* cVolue = new char[iCount];//根据变量的值创建对应大小的内存空间 2、使用new在自定义动态内存空间创建对象...

YiWei2019的博客 2583

【C++】C++中的new关键字用法详解

C++new用法详解

qq_35902025的博客 5万+

C++复习笔记——C++ 关键字

文章目录关键词总览索引关键词详解asmelsenewthisautoenumoperatorthrowboolexplicitprivatetruebreakexportprotectedtrycaseexternpublictypedefcatchfalseregistertypeidcharfloatreinterpret_casttypenameclassforreturnunioncons...

Congying-Wang的博客 9491

C++ new关键字详解

一.new的基本用法 1.c++通过new关键字进行动态分配内存。 2.new开辟的空间存储在堆上,而我们定义的变量存储在栈上。 3.new分配的空间使用delete释放,new[] 使用 delete[]。 4. int* pi = new int(5);//表示动态分配一个int ,初始化为 5 int* pa = new int[5];//表示动态分配一个数组,数组大小为5 ...

希望能帮助大家,希望大家多多支持,你们的支持是我前进的动力。 2万+

C++八股文

1 变量的声明和定义有什么区别 变量的定义为变量分配地址和存储空间, 变量的声明不分配地址。一个变量可以在多个地方声明, 但是只在一个地方定义。 加入extern 修饰的是变量的声明,说明此变量将在文件以外或在文件后面部分定义。 说明:很多时候一个变量,只是声明不分配内存空间,直到具体使用时才初始化,分配内存空间, 如外部变量。 int main() { extern int A; //这是个声明而不是定义,声明A是一个已经定义了的外部变量 //注意:声明外部变量时可以把变量类型去掉如:extern A;

weixin_43519366的博客 1万+

银河飞腾FT-M6678 DSP开发初体验:从仿真器连接到第一个网络程序(CCS5.5+FT-EMU560实战)

本文详细介绍了银河飞腾FT-M6678 DSP开发环境的搭建过程,包括CCS5.5开发环境的配置、FT-EMU560仿真器的连接与设置,以及关键模块如网络驱动的国产化替换。通过实战案例,帮助开发者快速掌握国产DSP的开发流程,实现网络通信功能。

weixin_30919429的博客 556

C++ new关键字深入理解

“new”是C++的一个关键字,同时也是操作符。关于new的话题非常多,因为它确实比较复杂,也非常神秘,下面我将把我了解到的与new有关的内容做一个总结。 new的过程 当我们使用关键字new在堆上动态创建一个对象时,它实际上做了三件事:获得一块内存空间、调用构造函数、返回正确的指针。当然,如果我们创建的是简单类型的变量,那么第二步会被省略。假如我们定义了如下一个类A: class A {

bbs375的博客 2万+

centos-stream-9上安装nvidia驱动和cuda-toolkit

centos-stream-9上安装nvidia驱动和cuda-toolkit

qq_19072921的博客 3022

C++ 中关键字深入理解(一) —— new

文章转载自http://www.codeceo.com/article/cpp-new-keyword.html “new”是C++的一个关键字,同时也是操作符。关于new的话题非常多,因为它确实比较复杂,也非常神秘,下面我将把我了解到的与new有关的内容做一个总结。 new的过程 当我们使用关键字new在堆上动态创建一个对象时,它实际上做了三件事:(1)获得一块内存空间、(2)调用构造函数、(...

weixin_36323170的博客 656

龙头复盘神器/复盘于导出工具最新版5.6

龙头复盘神器,复盘于导出工具

初入python踩过的坑 8254

线性回归做糖尿病分析(diabetes数据集),并分析单个特征值与病情的关系

1.diabetes数据集 共442个样本,每个样本有十个特征,分别是 [‘age’, ‘sex’, ‘bmi’, ‘bp’, ‘s1’, ‘s2’, ‘s3’, ‘s4’, ‘s5’, ‘s6’],对应年龄、性别、体质指数、平均血压、S1~S6一年后疾病级数指标。 Targets为一年后患疾病的定量指标,值在25到346之间。 ...

t18438605018的博客 1万+

c语言new关键字的作用,深入理解c++中virtual关键字

1.virtual关键字主要是什么作用?c++中的函数调用默认不适用动态绑定。要触发动态绑定,必须满足两个条件:第一,指定为虚函数;第二,通过基类类型的引用或指针调用。由此可见,virtual主要主要是实现动态绑定。2.那些情况下可以使用virtual关键字?virtual可用来定义类函数和应用到虚继承。友元函数 构造函数 static静态函数 不能用virtual关键字修饰;普通成员函数 和析构...

weixin_39831493的博客 215

大疆C板使用C610电调驱动M2006电机(hal库)

大疆C板使用C610电调驱动M2006电机,以及遇到代码没问题,但是电机死活不转的处理

qq_46210601的博客 6025

C++基础学习:深入理解类中的构造函数、析构函数、this指针与new关键字

在C++面向对象编程中,类是构建复杂程序的基本单元。今天,我们将深入探讨类中的几个核心概念:构造函数、析构函数、this指针以及new关键字。这些概念对于理解C++对象生命周期和内存管理至关重要。构造函数、析构函数、this指针和new关键字是C++面向对象编程中的核心概念。构造函数用于初始化对象,析构函数用于清理对象,this指针用于在类内部访问对象成员,而new关键字用于在堆上动态分配内存。理解这些概念对于编写高效、可靠的C++程序至关重要。

坏柠的博客 431

Cursor生成JAVA相关的关键词提示规则

你是 Java 编程、Spring Boot、Spring Framework、Maven、JUnit 及相关 Java 技术的专家。在项目根目录创建一个.curstorrules文件(注意有个小数点),之后在该文件内填入下面内容。始终遵循 SOLID 原则,保持高内聚低耦合的 Spring Boot 应用设计。Java 和 Spring Boot 使用规范。Spring Boot 具体规范。

kitonGao的博客 2118

echarts 饼图(加载进度条动画效果)

1、外侧动画细线效果运动“custom”自定义系列中“renderItem”属性来实现,动态修改起始角度和终止角度。2、圆环使用" pie "类型添加两个data值来画百分比圆环,动态修改data中的值。

God's asylum的博客 1633

深入理解C++中的new操作符

今天在学习STL源码时,发现其中关于内存配置器allocate不是很懂,于是在网上找了写资料去了解,发现其还是基于new操作符,不过,必须深入理解new,然后在利用new,进行改造,使之更加高效管理内存。以下是在网上找到的关于new的一些理解: “new”是C++的一个关键字,同时也是操作符。关于new的话题非常多,因为它确实比较复杂,也非常神秘,下面我将把我了解到的与new有关的内容做一个

rave..juine 1226

uniapp - 详解使用高德地图在地图上实现绘制边界/点聚合/行政区域高亮等功能,Uniapp高德地图点聚合标记及绘制点线面示例代码(全端兼容H5网页、小程序、安卓app、苹果app、nvue)

uni-app,h5移动端网页,微信小程序,支付宝小程序,Android安卓,苹果ios,app,nvue,高德地图,uniAPP高德地图实现点聚合,地图区域描边、点标记、点信息详情弹窗,实现绘制边界区域,uniapp H5使用高德地图实现点聚合、行政区域高亮,实现高德地图的marker聚合功能,高德地图进行marker标点,点信息展示,点击标记点弹出对话框,点击聚合点后显示信息模态框,打标点,【Uniapp】高德地图的接入自定义标点与信息窗体使用,绘制行政区域边界,绘制多边形,电子围栏,高德行政区划边界,

✅ 精选推荐文章 1万+

c++中new的三种用法

测试类: struct test { test(int size = 1) { ary = new int[size]; } ~test() { delete[] ary; ary = nullptr; } int *ary; }; 用法1——当作关键字用,会做2步:1.先开辟空间。2.后调用构造函数。 test *a = new test(10); 用法2——当函数用,相当于malloc,单纯的开辟空间。 test *b = (test *)operator new(size

qq_45735519的博客 3493

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

c++关键字

auto auto(自动,automatic)是存储类型标识符,表明变量"自动"具有本地范围,块范围的变量声明(如for循环体内的变量声明)默认为auto存储类型 bool bool(布尔)类型,C++ 中的基本数据结构,其值可选为 true(真)或者 false(假)。C++ 中的 bool 类型可以和 int 混用,具体来说就是 0 代表 false,非 0 代表 true。bool 类型常用于条件判断和函数返回值。 explicit explicit(显式的)的作用是"禁止单参数构造函数"被用于自动型

weixin_43117602的博客 1191

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

工控产品安全等级(SL)评测认证实战:从GB/T 42456到CODESYS EtherCAT环境搭建

工业控制系统信息安全已成刚需,安全等级(SL)是评估工控产品防护能力的核心尺度。GB/T 42456参照IEC 62443思想,将产品抵御攻击的能力分为SL0~SL4,涵盖身份鉴别、数据保护、协议健壮性、供应链安全等维度。理解SL等级不是简单打分,而是从开发过程到运行防护的全生命周期安全能力验证。在PLC、运动控制器等设备送测前,差距分析与自测能显著降低整改成本。以CODESYS Control RTE SL为宿主,搭建EtherCAT主站测试环境,需注意XML版本、PDO映射、任务周期及网络拓扑,才能形成

weixin_34295316的博客 339

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

C++新的关键字_6

一。动态内存分配   1.C++中的动态内存分配     -C++中通过 new 关键字进行动态内存申请     -C++中的动态内存申请是基于类型进行的     -delete关键字用于内存释放   变量申请:     Type* pointer = new Type     delete pointer;   数组申请:     Type* pointer = new Typ...

weixin_34032621的博客 115

new 的运算符重载新用法

博客地址:http://www.52iocp.com 本文地址:http://blog.youkuaiyun.com/hzdiy/article/details/8707924 #include #include using namespace std; class A { public: A() { m_a = 1; m_b = 2; } A(int a, int b) {

游戏开发者 1310

C语言面试常考关键字,C/C++面试常考关键字总结

C/C++面试常考关键字总结C语言对于学习编程的小伙伴来说,是一种必须学习的基础。下面是小编为大家整理出来的有关于C/C++面试常考关键字总结,希望可以帮助到大家!1.static关键字的作用?1)、static 申明的变量为静态全局变量,该变量存储在静态数据区,当整个程序结束时该变量才会被释放,只是生命周期延长,作用域不受影响。2)、static申明的变量定义时如果不初始化默认初始化为03)、被...

weixin_33308507的博客 375

cpp链表和指针引用

【【C++】new用法详解 - 优快云 App】http://t.csdnimg.cn/CEZ73。

weixin_43861764的博客 88
上一篇: 《深度探索C++对象模型》读书笔记第五章:构造、解构、拷贝语意学
下一篇: 动态内存管理详解:malloc/free/new/delete/brk/mmap
FreeeLinux
博客等级 码龄11年 421粉丝 356原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值