深入浅析C++的new和delete

new和delete的内部实现

C++中如果要在堆内存中创建和销毁对象需要借助关键字new和delete来完成。比如下面的代码

class CA
{
 public:
  CA()m_a(0){}
  CA(int a):m_a(a){}
  virtual void foo(){ cout<<m_a<<endl;}
  int m_a;
};
void main()
{
  CA *p1 = new CA;
  CA *p2 = new CA(10);
  CA *p3 = new CA[20];
  delete p1;
  delete p2;
  delete[] p3;
}

new和delete既是C++中的关键字也是一种特殊的运算符。

void* operator new(size_t size);
 void* operator new[](size_t size);
 void operator delete(void *p);
 void operator delete[](void *p);

new和delete不仅承载着内存分配的功能还承载着对象构造函数的调用功能,因此上面的对象创建代码其实在编译时会转化为如下的实现:

CA *p1 = operator new(sizeof(CA)); //分配堆内存
  CA::CA(p1); //调用构造函数
  CA *p2 = operator new(sizeof(CA)); //分配堆内存
  CA::CA(p2, 10); //调用构造函数
  CA *p3 = operator new[](20 * sizeof(CA));
  CA *pt = p3;
  for (int i = 0; i < 20; i++)
  {
   CA::CA(pt);
   pt += 1;
  }
  CA::~CA(p1);
  operator delete(p1);
  CA::~CA(p2);
  operator delete(p2);
  CA *pt = p3;
  for (int i = 0; i < 20; i++)
  {
   CA::~CA(pt);
   pt += 1;
  }
  operator delete[](p3);

看到上面的代码也许你会感到疑惑,怎么在编译时怎么会在源代码的基础上插入这么多的代码。这也是很多C程序员吐槽C++语言的原因:C++编译器会偷偷插入很多未知的代码或者对源代码进行修改和处理,而这些插入和修改动作对于程序员来说是完全不可知的!

言归正传,我们还能从上面的代码中看出new和delete操作其实是分别进行了2步操作:1.内存的分配,2.构造函数的调用;3.析构函数的调用,4.内存的销毁。所以当对象是从堆内存分配时,构造函数执前内存就已经完成分配,同样当析构函数执行完成后内存才会被销毁。

这里面一个有意思的问题就是当我们分配或者销毁的是数组对象时,系统又是如何知道应该调用多少次构造函数以及调用多少次析构函数的呢?答案就是在内存分配里面。当我们调用operator new[]来分配数组对象时,编译器时系统内部会增加4或者8字节的分配空间用来保存所分配的数组对象的数量。当对数组对象调用构造和析构函数时就可以根据这个数量值来进行循环处理了。因此上面对数组对象的分配和销毁的真实代码其实是按如下方式处理的:

 // CA *p3 = new CA[20]; 这句代码在编译时其实会转化为如下的代码片段
  unsigned long *p = operator new[](20 * sizeof(CA) + sizeof(unsigned long)); //64位系统多分配8字节
  *p = 20; //这里保存分配的对象的数量。
  CA *p3 = (CA*)(p + 1);
  CA *pt = p3;
  for (int i = 0; i < *p; i++)
  {
   CA::CA(pt);
   pt += 1;
  }
 // delete[] p3; 这句代码在编译时其实会转化为如下的代码片段
  unsigned long *p = ((unsigned long*)p3) - 1;
  CA *pt = p3;
  for (int i = 0; i < *p; i++)
  {
   CA::~CA(pt);
   pt += 1;
  }
  operator delete[](p);

可见C++中为我们隐藏了多少细节啊!既然new和delete操作默认是从堆中进行内存分配,而且new和delete又是一个普通的运算符函数,那么他内部是如何实现呢?其实也很简单。我们知道C语言中堆内存分配和销毁的函数是malloc/free。因此C++中对系统默认的new和delete运算符函数就可以按如下的方法实现:

void * operator new(size_t size)
{
  return malloc(size);
}
void * operator new[](size_t size)
{
  return malloc(size);
}
void operator delete(void *p)
{
  free(p);
}
void operator delete[](void *p)
{
 free(p);
}

这里需要注意的是你在代码里面使用new关键字和使用operator new操作符所产生的效果是不一样的。如果你在代码里面使用的是new关键字那么系统内部除了会调用operator new操作符来分配内存还会调用构造函数,而如果你直接使用operator new时则只会进行内存分配而不会执行任何构造就比如下面的代码:

 CA *p1 = new CA; //这里会分配内存和执行构造函数
 CA *p2 = operator new(sizeof(CA)); //这里只是执行了普通的堆内存分配而不会调用构造函数

上述的伪代码都是在运行时通过查看汇编语言而得出的结论,我是在XCODE编译器上查看运行的结果,有可能不同的编译器会有一些实现的差异,但是不管如何要想真实的了解内部实现原理还是要懂一些汇编的知识为最好。

placement技术

系统默认的new关键字除了分配堆内存外还进行构造函数的调用。而实际中我们可能有一些已经预先分配好的内存区域,我们想在这些已经分配好的内存中来构建一个对象。还有一种情况是不希望进行频繁的堆内存分配和释放而只是对同一块内存进行重复的对象构建和销毁。就如下面的代码:

char buf1[100];
CA *p1 = (CA*)buf1;
CA::CA(p1);
p1->foo();
p1->m_a = 10;
char *buf2 = new char[sizeof(CA)];
CA *p2 = (CA*)buf2;
CA::CA(p2);
p2->foo();
p2->m_a = 20;
p1->~CA();
p2->~CA();
delete[] buf2;

可以看出代码中buf1是栈内存而buf2是堆内存,这两块内存区域都是已经分配好了的内存,现在我们想把这些内存来当做CA类的对象来使用,因此我们需要对内存调用类的构造函数CA::CA()才可以,构造函数的内部实现会为内存区域填充虚表指针,这样对象才可以调用诸如foo虚函数。但是这样写代码不够优雅,那么有没有比较优雅的方法来实现在一块已经存在的内存上来构建对象呢? 答案就是 placement技术。 C++中的仍然是使用new和delete来实现这种技术。new和delete除了实现默认的操作符外还重载实现了如下的操作符函数:

void* operator new(size_t size, void *p)
{
 return p;
}
void* operator new[](size_t size, void *p)
{
 return p;
}
void operator delete(void *p1, void *p2)
{
 // do nothing..
}
void operator delete[](void *p1, void *p2)
{
 // do nothing..
}

我们称这四个运算符为 placement new 和 placement delete  。通过这几个运算符我们就可以优雅的实现上述的功能:

char buf1[100];
CA *p1 = new(buf1) CA(10); //调用 operator new(size_t, void*)
p1->foo();
char *buf2 = new char[sizeof(CA)];
CA *p2 = new(buf2) CA(20);  //调用 operator new(size_t, void*)
p2->foo();
p1->~CA();
operator delete(p1, buf1); //调用 operator delete(void*, void*)
p2->~CA();
operator delete(p2, buf2); //调用 operator delete(void*, void*)
delete[] buf2;

上面的例子里面发现通过placement new可以很优雅的在现有的内存中构建对象,而析构时不能直接调用delete p1, delete p2来销毁对象,必须人为的调用析构函数以及placement delete 函数。并且从上面的placement delete的实现来看里面并没有任何代码,既然如此为什么还要定义一个placement   delete呢? 答案就是C++中的规定对new和delete的运算符重载必须是要成对实现的。而且前面曾经说过对delete的使用如果带了operator前缀时就只是一个普通的函数调用。因此为了完成析构以及和new操作符的匹配,就必须要人为的调用对象的析构函数以及placement delete函数。
除了上面举的例子外placement技术的使用还可以减少内存的频繁分配以及提升系统的性能。

void main()
{
  for (int i = 0; i < 10000; i++)
  {
   CA *p = new CA(i);
   p->foo();
   delete p;
  }
}

例子里面循环10000次,每次循环都创建一个堆内存对象,然后调用虚函数foo后再进行销毁。最终的结果是程序运行时会进行10000次的频繁的堆内存分配和销毁。很明显这是有可能会影响系统性能的而且还有可能发生堆内存分配失败的情况。而如果我们借助placement 技术就可以很简单的解决这些问题。

void main()
{
  char *buf = new[](sizeof(CA));
  for (int i = 0; i < 10000; i++)
  {
   CA *p = new(buf) CA(i);
   p->foo();
   p->~CA();
   operator delete(p, buf);
  }
  delete[] buf;
}

上面的例子里面只进行了一次堆内存分配,在循环里面都是借助已经存在的内存来构建对象,不会再分配内存了。这样对内存的重复利用就使得程序的性能得到非常大的提升。

new和delete运算符重载

发现一个很有意思的事情就是越高级的语言就越会将一些系统底层的东西进行封装并形成一个语言级别的关键字来使用。比如C++中的new和delete是用于构建和释放堆内存对象的关键字,又比如go语言中chan关键字是用于进行同步或者异步的队列数据传输通道。

C++语言内置默认实现了一套全局new和delete的运算符函数以及placement new/delete运算符函数。不管是类还是内置类型都可以通过new/delete来进行堆内存对象的分配和释放的。对于一个类来说,当我们使用new来进行构建对象时,首先会检查这个类是否重载了new运算符,如果这个类重载了new运算符那么就会调用类提供的new运算符来进行内存分配,而如果没有提供new运算符时就使用系统提供的全局new运算符来进行内存分配。内置类型则总是使用系统提供的全局new运算符来进行内存的分配。对象的内存销毁流程也是和分配一致的。

new和delete运算符既支持全局的重载又支持类级别的函数重载。下面是这种运算符的定义的格式:

//全局运算符定义格式
void * operator new(size_t size [, param1, param2,....]);
void operator delete(void *p [, param1, param2, ...]);
//类内运算符定义格式
class CA
{
 void * operator new(size_t size [, param1, param2,....]);
 void operator delete(void *p [, param1, param2, ...]);
};

对于new/delete运算符重载我们总有如何下规则:

new和delete运算符重载必须成对出现

new运算符的第一个参数必须是size_t类型的,也就是指定分配内存的size尺寸;delete运算符的第一个参数必须是要销毁释放的内存对象。其他参数可以任意定义。

系统默认实现了new/delete、new[]/delete[]、 placement new / delete 6个运算符函数。它们都有特定的意义。
你可以重写默认实现的全局运算符,比如你想对内存的分配策略进行自定义管理或者你想监测堆内存的分配情况或者你想做堆内存的内存泄露监控等。但是你重写的全局运算符一定要满足默认的规则定义。

如果你想对某个类的堆内存分配的对象做特殊处理,那么你可以重载这个类的new/delete运算符。当重载这两个运算符时虽然没有带static属性,但是不管如何对类的new/delete运算符的重载总是被认为是静态成员函数。

当delete运算符的参数>=2个时,就需要自己负责对象析构函数的调用,并且以运算符函数的形式来调用delete运算符。

一般情况下你不需要对new/delete运算符进行重载,除非你的整个应用或者某个类有特殊的需求时才会如此。下面的例子你可以看到我的各种运算符的重载方法以及使用方法:

//CA.h
class CA
{
public:
 //类成员函数
 void * operator new(size_t size);
 void * operator new[](size_t size);
 void * operator new(size_t size, void *p);
 void * operator new(size_t size, int a, int b);
 void operator delete(void *p);
 void operator delete[](void *p);
 void operator delete(void *p, void *p1);
 void operator delete(void *p, int a, int b);
};
class CB
{
public:
 CB(){}
};
//全局运算符函数,请谨慎重写覆盖全局运算符函数。
void * operator new(size_t size);
void * operator new[](size_t size);
void * operator new(size_t size, void *p) noexcept;
void * operator new(size_t size, int a, int b);
void operator delete(void *p);
void operator delete[](void *p);
void operator delete(void *p, void *p1);
void operator delete(void *p, int a, int b);
.......................................................
//CA.cpp
void * CA::operator new(size_t size)
{
 return malloc(size);
}
void * CA::operator new[](size_t size)
{
 return malloc(size);
}
void * CA::operator new(size_t size, void *p)
{
 return p;
}
void* CA::operator new(size_t size, int a, int b)
{
 return malloc(size);
}
void CA::operator delete(void *p)
{
 free(p);
}
void CA::operator delete[](void *p)
{
 free(p);
}
void CA::operator delete(void *p, void *p1)
{
}
void CA::operator delete(void *p, int a, int b)
{
 free(p);
}
void * operator new(size_t size)
{
 return malloc(size);
}
void * operator new[](size_t size)
{
 return malloc(size);
}
void * operator new(size_t size, void *p) noexcept
{
 return p;
}
void* operator new(size_t size, int a, int b)
{
 return malloc(size);
}
void operator delete(void *p)
{
 free(p);
}
void operator delete[](void *p)
{
 free(p);
}
void operator delete(void *p, void *p1)
{
}
void operator delete(void *p, int a, int b)
{
 free(p);
}
..................................
//main.cpp
int main(int argc, const char * argv[]) {
 char buf[100];
 CA *a1 = new CA(); //调用void * CA::operator new(size_t size)
 CA *a2 = new CA[10]; //调用void * CA::operator new[](size_t size)
 CA *a3 = new(buf)CA(); //调用void * CA::operator new(size_t size, void *p)
 CA *a4 = new(10, 20)CA(); //调用void* CA::operator new(size_t size, int a, int b)
 delete a1; //调用void CA::operator delete(void *p)
 delete[] a2; //调用void CA::operator delete[](void *p)
 //a3用的是placement new的方式分配,因此需要自己调用对象的析构函数。
 a3->~CA();
 CA::operator delete(a3, buf); //调用void CA::operator delete(void *p, void *p1),记得要带上类命名空间。
 //a4的运算符参数大于等于2个所以需要自己调用对象的析构函数。
 a4->~CA();
 CA::operator delete(a4, 10, 20); //调用void CA::operator delete(void *p, int a, int b)
 //CB类没有重载运算符,因此使用的是全局重载的运算符。
 CB *b1 = new CB(); //调用void * operator new(size_t size)
 CB *b2 = new CB[10]; //调用void * operator new[](size_t size)
 //这里你可以看到同一块内存可以用来构建CA类的对象也可以用来构建CB类的对象
 CB *b3 = new(buf)CB(); //调用void * operator new(size_t size, void *p)
 CB *b4 = new(10, 20)CB(); //调用void* operator new(size_t size, int a, int b)
 delete b1; //调用void operator delete(void *p)
 delete[] b2; //调用void operator delete[](void *p)
 //b3用的是placement new的方式分配,因此需要自己调用对象的析构函数。
 b3->~CB();
 ::operator delete(b3, buf); //调用void operator delete(void *p, void *p1)
 //b4的运算符参数大于等于2个所以需要自己调用对象的析构函数。
 b4->~CB();
 ::operator delete(b4, 10, 20); //调用void operator delete(void *p, int a, int b)
 return 0;
}

我是在XCODE上测试上面的代码的,因为重写了全局的new/delete运算符,并且内部是通过malloc来实现堆内存分配的, malloc函数申明了不能返回NULL的返回结果检测:

void *malloc(size_t __size) __result_use_check __alloc_size(1);

因此有可能你在测试时会发生崩溃的问题。如果出现这个问题你可以尝试着注释掉对全局new/delete重写的代码,再运行查看结果。 可见如果你尝试着覆盖重写全局的new/delete时是有可能产生风险的。

对象的自动删除技术

一般来说系统对new/delete的默认实现就能满足我们的需求,我们不需要再去重载这两个运算符。那为什么C++还提供对这两个运算符的重载支持呢?答案还是在运算符本身具有的缺陷所致。我们知道用new关键字来创建堆内存对象是分为了2步:1.是堆内存分配,2.是对象构造函数的调用。而这两步中的任何一步都有可能会产生异常。如果说是在第一步出现了问题导致内存分配失败则不会调用构造函数,这是没有问题的。如果说是在第二步构造函数执行过程中出现了异常而导致无法正常构造完成,那么就应该要将第一步中所分配的堆内存进行销毁。C++中规定如果一个对象无法完全构造那么这个对象将是一个无效对象,也不会调用析构函数。为了保证对象的完整性,当通过new分配的堆内存对象在构造函数执行过程中出现异常时就会停止构造函数的执行并且自动调用对应的delete运算符来对已经分配的堆内存执行销毁处理,这就是所谓的对象的自动删除技术。正是因为有了对象的自动删除技术才能解决对象构造不完整时会造成内存泄露的问题。

当对象构造过程中抛出异常时,C++的异常处理机制会在特定的地方插入代码来实现对对象的delete运算符的调用,如果想要具体了解情况请参考C++对异常处理实现的相关知识点。

全局delete运算符函数所支持的对象的自动删除技术虽然能解决对象本身的内存泄露问题,但是却不能解决对象构造函数内部的数据成员的内存分配泄露问题,我们来看下面的代码:

class CA
{
 public:
 CA()
 {
   m_pa = new int;
   throw 1;
 }
 ~CA()
 {
   delete m_pa;
   m_pa = NULL;
 }
 private:
  int *m_pa;
};
void main()
{
  try
  {
   CA *p = new CA();
   delete p; //这句代码永远不会执行
  }
  catch(int)
 {
   cout << "oops!" << endl;
 }
}

上面的代码中可以看到类CA中的对象在构造函数内部抛出了异常,虽然系统会对p对象执行自动删除技术来销毁分配好的内存,但是对于其内部的数据成员m_pa来说,因为构造不完整就不会调用析构函数来销毁分配的堆内存,这样就导致了m_pa这块内存出现了泄露。怎么解决这类问题呢? 答案你是否想到了? 那就是重载CA类的new/delete运算符。我们来看通过对CA重载运算符解决问题的代码:

class CA
{
public:
 CA(){
  m_pa = new int;
  throw 1;
 }
 //因为对象构造未完成所以析构函数永远不会被调用
 ~CA()
 {
  delete m_pa;
  m_pa = NULL;
 }
 void * operator new(size_t size)
 {
  return malloc(size);
 }
 //重载delete运算符,把已经分配的内存销毁掉。
 void operator delete(void *p)
 {
  CA *pb = (CA*)p;
  if (pb->m_pa != NULL)
   delete pb->m_pa;
  free(p);
 }
private:
 int *m_pa;
};

因为C++对自动删除技术的支持,当CA对象在构造过程中发生异常时,我们就可以通过重载delete运算符来解决那些在构造函数中分配的数据成员内存但又不会调用析构函数来销毁的数据成员的内存问题。这我想就是为什么C++中要支持对new/delete运算符在类中重载的原因吧。

下面看下:new和delete使用注意事项

1. new 和delete都是内建的操作符,语言本身所固定了,无法重新定制,想要定制new和delete的行为,徒劳无功的行为。

2. 动态分配失败,则返回一个空指针(NULL),表示发生了异常,堆资源不足,分配失败。

3. 指针删除与堆空间释放。删除一个指针p(delete p;)实际意思是删除了p所指的目标(变量或对象等),释放了它所占的堆空间,而不是删除p本身(指针p本身并没有撤销,它自己仍然存在,该指针所占内存空间并未释放),释放堆空间后,p成了空指针。

4. 内存泄漏(memory leak)和重复释放。new与delete 是配对使用的, delete只能释放堆空间。如果new返回的指针值丢失,则所分配的堆空间无法回收,称内存泄漏,同一空间重复释放也是危险的,因为该空间可能已另分配,所以必须妥善保存new返回的指针,以保证不发生内存泄漏,也必须保证不会重复释放堆内存空间。

5. 动态分配的变量或对象的生命期。我们也称堆空间为自由空间(free store),但必须记住释放该对象所占堆空间,并只能释放一次,在函数内建立,而在函数外释放,往往会出错。

6. 要访问new所开辟的结构体空间,无法直接通过变量名进行,只能通过赋值的指针进行访问。

用new和delete可以动态开辟和撤销地址空间。在编程序时,若用完一个变量(一般是暂时存储的数据),下次需要再用,但却又想省去重新初始化的功夫,可以在每次开始使用时开辟一个空间,在用完后撤销它。

总结

以上所述是小编给大家介绍的C++的new和delete,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

您可能感兴趣的文章:

  • C++动态内存分配(new/new[]和delete/delete[])详解
  • C++ new、delete(new[]、delete[])操作符重载需要注意的问题
  • c++中new和delete操作符用法
  • 浅析c++中new和delete的用法
  • C++ new/delete相关知识点详细解析
(0)

相关推荐

  • 浅析c++中new和delete的用法

    new和delete运算符用于动态分配和撤销内存的运算符 new用法: 1.开辟单变量地址空间1)new int;  //开辟一个存放数组的存储空间,返回一个指向该存储空间的地址.int *a = new int 即为将一个int类型的地址赋值给整型指针a. 2)int *a = new int(5) 作用同上,但是同时将整数赋值为5 2. 开辟数组空间一维: int *a = new int[100];开辟一个大小为100的整型数组空间二维: int **a = new int[5][6]三维

  • c++中new和delete操作符用法

    "new"是C++的一个关键字,同时也是操作符.当我们使用关键字new在堆上动态创建一个对象时,它实际上做了三件事:获得一块内存空间.调用构造函数.返回正确的指针.当然,如果我们创建的是简单类型的变量,第二步就会被省略. new用法: 1. 开辟单变量地址空间 1)new int; 开辟一个存放数组的存储空间,返回一个指向该存储空间的地址.int *a = new int 即为将一个int类型的地址赋值给整型指针a. 2)int *a = new int(5) 作用同上,但是同时将整数

  • C++动态内存分配(new/new[]和delete/delete[])详解

    C++动态内存分配(new/new[]和delete/delete[])详解 为了解决这个普通的编程问题,在运行时能创建和销毁对象是基本的要求.当然,C已提供了动态内存分配函数malloc( )和free( ),以及malloc( )的变种(realloc:改变分配内存的大小,calloc:指针指向内存前初始化),这些函数在运行时从堆中(也称自由内存)分配存储单元,但是运用这些库函数需要计算需要开辟内存的大小,容易出现错误. 那么通常我们在C语言中我们开辟内存的方式如下: (void*)mall

  • C++ new、delete(new[]、delete[])操作符重载需要注意的问题

    new.delete(new[].delete[])操作符的重载需要注意: 1.重载的 new.delete(或者 new[].delete[])操作符必须是类的静态成员函数(为什么必须是静态成员函数,这很好理解,因为 new 操作符被调用的时候,对象还未构建)或者是全局函数,函数的原型如下: 复制代码 代码如下: void* operator new(size_t size) throw(std::bad_alloc); // 这里的 size 为分配的内存的总大小 void* operato

  • C++ new/delete相关知识点详细解析

    每个程序在执行时都占用一块可用的内存空间,用于存放动态分配的对象,此内存空间称为程序的自由存储区(free store)或堆(heap).C语言用一堆标准库函数malloc和free在自由存储区中分配存储空间,而C++则用new和delete表达式实现相同的功能. 一.new和delete创建和释放动态数组:数组类型的变量有三个重要的限制:数组长度固定,在编译时必须知道其长度,数组只在定义它的语句内存在.动态数组:长度固定,编译时不必知道其长度,通常是运行时确定:一直存在,直到程序显示释放它.

  • 深入浅析C++的new和delete

    new和delete的内部实现 C++中如果要在堆内存中创建和销毁对象需要借助关键字new和delete来完成.比如下面的代码 class CA { public: CA()m_a(0){} CA(int a):m_a(a){} virtual void foo(){ cout<<m_a<<endl;} int m_a; }; void main() { CA *p1 = new CA; CA *p2 = new CA(10); CA *p3 = new CA[20]; delet

  • 浅析drop user与delete from mysql.user的区别

    drop  user 会将该用户的信息全部删掉,而 delete  只会清除user表,其他的比如db表中的信息还是存在.如果delete 后,再创建一个最小权限的用户,那么他会重用以前的权限.grant all on test.* to 'test'@'%' identified by 'test';delete  from user  where  user =test;差看db 表mysql> select * from db;+------+---------+------+------

  • 浅析删除表的几种方法(delete、drop、truncate)

    delete from 表名: 删除所有记录,表结构还在,写日志,可以恢复的,速度慢 drop talbe 表名: 删除表的结构和数据 truncate talbe 表名 删除表中的所有记录,表结构还在,不写日志,无法找回删除的记录,速度快

  • 浅析JavaScript中的delete运算符

    delete运算符将删除运算数所指定的对象的属性.数组元素或变量.如果删除操作成功,它将返回true,如果运算数不能被删除, 它将返回false.并非所有的属性和变量都是可以删除的,某些内部的核心属性和客户端属性不能删除,用var语句声明的用户 定义变量也不能被删除.如果delete使用的运算数是一个不存在的属性,它将返回true(ECMAScript标准规定,当delete运算的 运算数不是属性.数组元素或变量时,它将返回true). var o = {x:1, y: 2};    //定义一

  • JavaScript中闭包之浅析解读(必看篇)

    JavaScript中的闭包真心是一个老生常谈的问题了,最近面试也是一直问到,我自己的表述能力又不能完全支撑起来,真是抓狂.在回来的路上,我突然想到了一个很简单的事情,其实我们在做项目时候,其实就经常用到闭包的,可是面试问的时候,回答又往往是我们经常搜到的答案,唉 不管是应付面试 还是真的想学点东西 ,我也用自己的理解跟大家分享一下,书面化就避免不了了的. 1.闭包是什么? 红宝书中曰:"是指有权访问另外一个函数作用域中的变量的函数." 简单的说,JavaScript允许使用内部函数-

  • 浅析mybatis和spring整合的实现过程

    根据官方的说法,在ibatis3,也就是Mybatis3问世之前,Spring3的开发工作就已经完成了,所以Spring3中还是没有对Mybatis3的支持.因此由Mybatis社区自己开发了一个Mybatis-Spring用来满足Mybatis用户整合Spring的需求.下面就将通过Mybatis-Spring来整合Mybatis跟Spring的用法做一个简单的介绍. MapperFactoryBean 首先,我们需要从Mybatis官网上下载Mybatis-Spring的jar包添加到我们项

  • 浅析JSONP技术原理及实现

    跨域问题一直是前端中常见的问题,每当说到跨域,第一浮现的技术必然就是JSONP JSONP在我的理解,它并不是ajax,它是在文档中插入一个script标签,创建_callback方法,通过服务器配合执行_callback方法,并传入一些参数 JSONP的局限就在于,因为是通过插入script标签,所以参数只能通过url传入,因此只能满足get请求,特别jQuery的ajax方法时,即使设置type: 'POST',但是只要设置了dataType: 'jsonp',在请求时,都会自动使用GET请

  • 浅析Yii2中GridView常见操作

    本文是小编给大家收集整理些有关网络上GridView出现的大部分问题,本文做一个总结特此分享到我们平台供大家参考. 如果下面有没说到的GridView常见问题,下方留言,我会进行补充. 下拉搜索 日期格式化并实现日期可搜索 根据参数进行是否显示 链接可点击跳转 显示图片 html渲染 自定义按钮 设定宽度等样式 自定义字段 自定义行样式 增加按钮调用js操作 yii2 GridView 下拉搜索实现案例教程 yii2 GridView 日期格式化并实现日期可搜索 案例 是否显示某列案例 我们举一

  • 浅析在javascript中创建对象的各种模式

    最近在看<javascript高级程序设计>(第二版) javascript中对象的创建 •工厂模式 •构造函数模式 •原型模式 •结合构造函数和原型模式 •原型动态模式 面向对象的语言大都有一个类的概念,通过类可以创建多个具有相同方法和属性的对象.虽然从技术上讲,javascript是一门面向对象的语言,但是javascript没有类的概念,一切都是对象.任意一个对象都是某种引用类型的实例,都是通过已有的引用类型创建:引用类型可以是原生的,也可以是自定义的.原生的引用类型有:Object.A

随机推荐