caffe代码阅读4:LayerRegistry的介绍与实现_caffe layerregistry-程序员宅基地

技术标签: caffe  

一、LayerRegistry的作用简介

LayerResistry的功能很简单,就是将类和对应的字符串类型放入到一个map当中去,以便灵活调用。主要就是注册类的功能

二、LayerRegistry类的详细介绍


1)构造函数和析构函数

构造函数 
[cpp]  view plain   copy
  在CODE上查看代码片 派生到我的代码片
  1. // 禁止实例化,因为该类都是静态函数,所以是私有的  
  2.   LayerRegistry() {}  

2)类型定义

[cpp]  view plain   copy
  在CODE上查看代码片 派生到我的代码片
  1.  // 函数指针Creator,返回的是Layer<Dtype>类型的指针  
  2. typedef shared_ptr<Layer<Dtype> > (*Creator)(const LayerParameter&);  
  3. // CreatorRegistry是字符串与对应的Creator的映射  
  4. typedef std::map<string, Creator> CreatorRegistry;  

3)成员函数

3-1 加入一个Creator到注册表

[cpp]  view plain   copy
  在CODE上查看代码片 派生到我的代码片
  1. // 给定类型,以及函数指针,加入到注册表  
  2. static void AddCreator(const string& type, Creator creator) {  
  3.   CreatorRegistry& registry = Registry();  
  4.   CHECK_EQ(registry.count(type), 0)  
  5.       << "Layer type " << type << " already registered.";  
  6.   registry[type] = creator;  
  7. }  

3-2 给定层的类型,创建层

这个创建层在net.cpp中会用到,在初始化整个网络的时候会根据参数文件中的层的类型去创建该层的实例
[cpp]  view plain   copy
  在CODE上查看代码片 派生到我的代码片
  1. static shared_ptr<Layer<Dtype> > CreateLayer(const LayerParameter& param) {  
  2.   if (Caffe::root_solver()) {  
  3.     LOG(INFO) << "Creating layer " << param.name();  
  4.   }  
  5.   // 从参数中获得类型字符串  
  6.   const string& type = param.type();  
  7.   // 获得注册表指针  
  8.   CreatorRegistry& registry = Registry();  
  9.   // 测试是否查找到给定type的Creator  
  10.   CHECK_EQ(registry.count(type), 1) << "Unknown layer type: " << type  
  11.       << " (known types: " << LayerTypeListString() << ")";  
  12.   // 调用对应的层的Creator函数  
  13.   return registry[type](param);  
  14. }  

3-3 返回层的类型列表

[cpp]  view plain   copy
  在CODE上查看代码片 派生到我的代码片
  1. static vector<string> LayerTypeList() {  
  2.   // 获得注册表  
  3.   CreatorRegistry& registry = Registry();  
  4.   vector<string> layer_types;  
  5.   // 遍历注册表压入layer_types字符串容器  
  6.   for (typename CreatorRegistry::iterator iter = registry.begin();  
  7.        iter != registry.end(); ++iter) {  
  8.     layer_types.push_back(iter->first);  
  9.   }  
  10.   return layer_types;  
  11. }  

3-4 返回一个string,就是把所有的类型都拼起来用逗号分隔形成一个字符串

[cpp]  view plain   copy
  在CODE上查看代码片 派生到我的代码片
  1.   static string LayerTypeListString() {  
  2.     vector<string> layer_types = LayerTypeList();  
  3.     string layer_types_str;  
  4.     for (vector<string>::iterator iter = layer_types.begin();  
  5.          iter != layer_types.end(); ++iter) {  
  6.       if (iter != layer_types.begin()) {  
  7.         layer_types_str += ", ";  
  8.       }  
  9.       layer_types_str += *iter;  
  10.     }  
  11.     return layer_types_str;  
  12.   }  
  13. };  

3-5 获取注册表(静态的,第一次的时候才new,以后都是直接return的)

[cpp]  view plain   copy
  在CODE上查看代码片 派生到我的代码片
  1. // 产生一个CreatorRegistry映射的的实例赋值给g_registry_  
  2.   // 表示内部的注册表  
  3.   // 静态函数,第一次的时候会new然后return,其余时间都是return  
  4.   static CreatorRegistry& Registry() {  
  5.     static CreatorRegistry* g_registry_ = new CreatorRegistry();  
  6.     return *g_registry_;  
  7.   }  

3-6此外还定义了一个层注册器

[cpp]  view plain   copy
  在CODE上查看代码片 派生到我的代码片
  1. // LayerRegisterer  
  2. // 自己定义层的注册器  
  3. // 以供后面的宏进行使用  
  4. template <typename Dtype>  
  5. class LayerRegisterer {  
  6.  public:  
  7.   // 层的注册器的构造函数  
  8.   LayerRegisterer(const string& type,  
  9.                   shared_ptr<Layer<Dtype> > (*creator)(const LayerParameter&)) {  
  10.     // LOG(INFO) << "Registering layer type: " << type;  
  11.     // 还是调用的层注册表中的加入Creator函数加入注册表  
  12.     LayerRegistry<Dtype>::AddCreator(type, creator);  
  13.   }  
  14. };  

三、其他:

为了方便作者还弄了个宏便于注册自己写的层类

[cpp]  view plain   copy
  在CODE上查看代码片 派生到我的代码片
  1. #define REGISTER_LAYER_CREATOR(type, creator)                                  \  
  2.   static LayerRegisterer<float> g_creator_f_##type(#type, creator<float>);     \  
  3.   static LayerRegisterer<double> g_creator_d_##type(#type, creator<double>)    \  
  4. #define REGISTER_LAYER_CLASS(type)                                             \  
  5.   template <typename Dtype>                                                    \  
  6.   shared_ptr<Layer<Dtype> > Creator_##type##Layer(const LayerParameter& param) \  
  7.   {                                                                            \  
  8.     return shared_ptr<Layer<Dtype> >(new type##Layer<Dtype>(param));           \  
  9.   }                                                                            \  
  10.   REGISTER_LAYER_CREATOR(type, Creator_##type##Layer)  
下面对该宏进行详细解释:
[cpp]  view plain   copy
  在CODE上查看代码片 派生到我的代码片
  1. // 生成g_creator_f_type(type, creator<Dtype>)的两个函数 (double和float类型)  
  2. #define REGISTER_LAYER_CREATOR(type, creator)                                  \  
  3.   static LayerRegisterer<float> g_creator_f_##type(#type, creator<float>);     \  
  4.   static LayerRegisterer<double> g_creator_d_##type(#type, creator<double>)    \  
  5.   
  6. // 注册自己定义的类,类名为type,  
  7. // 假设比如type=bias,那么生成如下的代码  
  8. // 下面的函数直接调用你自己的类的构造函数生成一个类的实例并返回  
  9. // CreatorbiasLayer(const LayerParameter& param)  
  10. // 下面的语句是为你自己的类定义了LayerRegisterer<float>类型的静态变量g_creator_f_biasLayer(float类型,实际上就是把你自己的类的字符串类型和类的实例绑定到注册表)  
  11. // static LayerRegisterer<float> g_creator_f_biasLayer(bias, CreatorbiasLayer)  
  12. // 下面的语句为你自己的类定义了LayerRegisterer<double>类型的静态变量g_creator_d_biasLayer(double类型,实际上就是把你自己的类的字符串类型和类的实例绑定到注册表)  
  13. // static LayerRegisterer<double> g_creator_d_biasLayer(bias, CreatorbiasLayer)  
  14. #define REGISTER_LAYER_CLASS(type)                                             \  
  15.   template <typename Dtype>                                                    \  
  16.   shared_ptr<Layer<Dtype> > Creator_##type##Layer(const LayerParameter& param) \  
  17.   {                                                                            \  
  18.     return shared_ptr<Layer<Dtype> >(new type##Layer<Dtype>(param));           \  
  19.   }                                                                            \  
  20.   REGISTER_LAYER_CREATOR(type, Creator_##type##Layer)  

四、Layer_factory.cpp中的实现

首先给出卷积层的参数
[cpp]  view plain   copy
  在CODE上查看代码片 派生到我的代码片
  1. message ConvolutionParameter {  
  2.   optional uint32 num_output = 1; // The number of outputs for the layer  
  3.   optional bool bias_term = 2 [default = true]; // whether to have bias terms  
  4.   
  5.   // Pad, kernel size, and stride are all given as a single value for equal  
  6.   // dimensions in all spatial dimensions, or once per spatial dimension.  
  7.   repeated uint32 pad = 3; // The padding size; defaults to 0  
  8.   repeated uint32 kernel_size = 4; // The kernel size  
  9.   repeated uint32 stride = 6; // The stride; defaults to 1  
  10.   
  11.   // For 2D convolution only, the *_h and *_w versions may also be used to  
  12.   // specify both spatial dimensions.  
  13.   optional uint32 pad_h = 9 [default = 0]; // The padding height (2D only)  
  14.   optional uint32 pad_w = 10 [default = 0]; // The padding width (2D only)  
  15.   optional uint32 kernel_h = 11; // The kernel height (2D only)  
  16.   optional uint32 kernel_w = 12; // The kernel width (2D only)  
  17.   optional uint32 stride_h = 13; // The stride height (2D only)  
  18.   optional uint32 stride_w = 14; // The stride width (2D only)  
  19.   
  20.   optional uint32 group = 5 [default = 1]; // The group size for group conv  
  21.   
  22.   optional FillerParameter weight_filler = 7; // The filler for the weight  
  23.   optional FillerParameter bias_filler = 8; // The filler for the bias  
  24.   enum Engine {  
  25.     DEFAULT = 0;  
  26.     CAFFE = 1;  
  27.     CUDNN = 2;  
  28.   }  
  29.   optional Engine engine = 15 [default = DEFAULT];  
  30.   
  31.   // The axis to interpret as "channels" when performing convolution.  
  32.   // Preceding dimensions are treated as independent inputs;  
  33.   // succeeding dimensions are treated as "spatial".  
  34.   // With (N, C, H, W) inputs, and axis == 1 (the default), we perform  
  35.   // N independent 2D convolutions, sliding C-channel (or (C/g)-channels, for  
  36.   // groups g>1) filters across the spatial axes (H, W) of the input.  
  37.   // With (N, C, D, H, W) inputs, and axis == 1, we perform  
  38.   // N independent 3D convolutions, sliding (C/g)-channels  
  39.   // filters across the spatial axes (D, H, W) of the input.  
  40.   optional int32 axis = 16 [default = 1];  
  41.   
  42.   // Whether to force use of the general ND convolution, even if a specific  
  43.   // implementation for blobs of the appropriate number of spatial dimensions  
  44.   // is available. (Currently, there is only a 2D-specific convolution  
  45.   // implementation; for input blobs with num_axes != 2, this option is  
  46.   // ignored and the ND implementation will be used.)  
  47.   optional bool force_nd_im2col = 17 [default = false];  
  48. }  
注册卷积层、注册池化层、注册ReLU层注册Tanh层,注册python层(如果开始python绑定的话)
代码如下:
[cpp]  view plain   copy
  在CODE上查看代码片 派生到我的代码片
  1. // Make sure we include Python.h before any system header  
  2. // to avoid _POSIX_C_SOURCE redefinition  
  3. #ifdef WITH_PYTHON_LAYER  
  4. #include <boost/python.hpp>  
  5. #endif  
  6. #include <string>  
  7.   
  8. #include "caffe/layer.hpp"  
  9. #include "caffe/layer_factory.hpp"  
  10. #include "caffe/proto/caffe.pb.h"  
  11. #include "caffe/vision_layers.hpp"  
  12.   
  13. #ifdef WITH_PYTHON_LAYER  
  14. #include "caffe/python_layer.hpp"  
  15. #endif  
  16.   
  17. namespace caffe {  
  18.   
  19. // 写一个获取卷积层实例的函数  
  20. // Get convolution layer according to engine.  
  21. template <typename Dtype>  
  22. shared_ptr<Layer<Dtype> > GetConvolutionLayer(  
  23.     const LayerParameter& param) {  
  24.    // 从参数中获取是使用什么引擎进行计算CUDNN还是CAFFE还是DEFAULT  
  25.    // engine可从caffe.proto中看出是枚举类型的  
  26.   ConvolutionParameter_Engine engine = param.convolution_param().engine();  
  27.   if (engine == ConvolutionParameter_Engine_DEFAULT) {  
  28.     engine = ConvolutionParameter_Engine_CAFFE;  
  29. #ifdef USE_CUDNN  
  30.     engine = ConvolutionParameter_Engine_CUDNN;  
  31. #endif  
  32.   }  
  33.   if (engine == ConvolutionParameter_Engine_CAFFE) {  
  34.     //  直接初始化Caffe的卷积层  
  35.     return shared_ptr<Layer<Dtype> >(new ConvolutionLayer<Dtype>(param));  
  36. #ifdef USE_CUDNN  
  37.   } else if (engine == ConvolutionParameter_Engine_CUDNN) {  
  38.     // 初始化CUDNN的卷积层  
  39.     return shared_ptr<Layer<Dtype> >(new CuDNNConvolutionLayer<Dtype>(param));  
  40. #endif  
  41.   } else { // 否则就是出错了  
  42.     LOG(FATAL) << "Layer " << param.name() << " has unknown engine.";  
  43.   }  
  44. }  
  45. // 注册该卷积层,类型名为Convolution,获取卷积层的实例为GetConvolutionLayer函数  
  46. REGISTER_LAYER_CREATOR(Convolution, GetConvolutionLayer);  
  47.   
  48. // 获取池化层的实例,同卷积层的逻辑  
  49. // Get pooling layer according to engine.  
  50. template <typename Dtype>  
  51. shared_ptr<Layer<Dtype> > GetPoolingLayer(const LayerParameter& param) {  
  52.   PoolingParameter_Engine engine = param.pooling_param().engine();  
  53.   if (engine == PoolingParameter_Engine_DEFAULT) {  
  54.     engine = PoolingParameter_Engine_CAFFE;  
  55. #ifdef USE_CUDNN  
  56.     engine = PoolingParameter_Engine_CUDNN;  
  57. #endif  
  58.   }  
  59.   if (engine == PoolingParameter_Engine_CAFFE) {  
  60.     return shared_ptr<Layer<Dtype> >(new PoolingLayer<Dtype>(param));  
  61. #ifdef USE_CUDNN  
  62.   } else if (engine == PoolingParameter_Engine_CUDNN) {  
  63.     PoolingParameter p_param = param.pooling_param();  
  64.     if (p_param.pad() || p_param.pad_h() || p_param.pad_w() ||  
  65.         param.top_size() > 1) {  
  66.       LOG(INFO) << "CUDNN does not support padding or multiple tops. "  
  67.                 << "Using Caffe's own pooling layer.";  
  68.       return shared_ptr<Layer<Dtype> >(new PoolingLayer<Dtype>(param));  
  69.     }  
  70.     return shared_ptr<Layer<Dtype> >(new CuDNNPoolingLayer<Dtype>(param));  
  71. #endif  
  72.   } else {  
  73.     LOG(FATAL) << "Layer " << param.name() << " has unknown engine.";  
  74.   }  
  75. }  
  76.   
  77. // 注册池化层  
  78. REGISTER_LAYER_CREATOR(Pooling, GetPoolingLayer);  
  79.   
  80. // 注册ReLU层  
  81. // Get relu layer according to engine.  
  82. template <typename Dtype>  
  83. shared_ptr<Layer<Dtype> > GetReLULayer(const LayerParameter& param) {  
  84.   ReLUParameter_Engine engine = param.relu_param().engine();  
  85.   if (engine == ReLUParameter_Engine_DEFAULT) {  
  86.     engine = ReLUParameter_Engine_CAFFE;  
  87. #ifdef USE_CUDNN  
  88.     engine = ReLUParameter_Engine_CUDNN;  
  89. #endif  
  90.   }  
  91.   if (engine == ReLUParameter_Engine_CAFFE) {  
  92.     return shared_ptr<Layer<Dtype> >(new ReLULayer<Dtype>(param));  
  93. #ifdef USE_CUDNN  
  94.   } else if (engine == ReLUParameter_Engine_CUDNN) {  
  95.     return shared_ptr<Layer<Dtype> >(new CuDNNReLULayer<Dtype>(param));  
  96. #endif  
  97.   } else {  
  98.     LOG(FATAL) << "Layer " << param.name() << " has unknown engine.";  
  99.   }  
  100. }  
  101.   
  102. REGISTER_LAYER_CREATOR(ReLU, GetReLULayer);  
  103.   
  104. // 注册sigmoid层  
  105. // Get sigmoid layer according to engine.  
  106. template <typename Dtype>  
  107. shared_ptr<Layer<Dtype> > GetSigmoidLayer(const LayerParameter& param) {  
  108.   SigmoidParameter_Engine engine = param.sigmoid_param().engine();  
  109.   if (engine == SigmoidParameter_Engine_DEFAULT) {  
  110.     engine = SigmoidParameter_Engine_CAFFE;  
  111. #ifdef USE_CUDNN  
  112.     engine = SigmoidParameter_Engine_CUDNN;  
  113. #endif  
  114.   }  
  115.   if (engine == SigmoidParameter_Engine_CAFFE) {  
  116.     return shared_ptr<Layer<Dtype> >(new SigmoidLayer<Dtype>(param));  
  117. #ifdef USE_CUDNN  
  118.   } else if (engine == SigmoidParameter_Engine_CUDNN) {  
  119.     return shared_ptr<Layer<Dtype> >(new CuDNNSigmoidLayer<Dtype>(param));  
  120. #endif  
  121.   } else {  
  122.     LOG(FATAL) << "Layer " << param.name() << " has unknown engine.";  
  123.   }  
  124. }  
  125.   
  126. REGISTER_LAYER_CREATOR(Sigmoid, GetSigmoidLayer);  
  127.   
  128. // 注册softmax层  
  129. // Get softmax layer according to engine.  
  130. template <typename Dtype>  
  131. shared_ptr<Layer<Dtype> > GetSoftmaxLayer(const LayerParameter& param) {  
  132.   SoftmaxParameter_Engine engine = param.softmax_param().engine();  
  133.   if (engine == SoftmaxParameter_Engine_DEFAULT) {  
  134.     engine = SoftmaxParameter_Engine_CAFFE;  
  135. #ifdef USE_CUDNN  
  136.     engine = SoftmaxParameter_Engine_CUDNN;  
  137. #endif  
  138.   }  
  139.   if (engine == SoftmaxParameter_Engine_CAFFE) {  
  140.     return shared_ptr<Layer<Dtype> >(new SoftmaxLayer<Dtype>(param));  
  141. #ifdef USE_CUDNN  
  142.   } else if (engine == SoftmaxParameter_Engine_CUDNN) {  
  143.     return shared_ptr<Layer<Dtype> >(new CuDNNSoftmaxLayer<Dtype>(param));  
  144. #endif  
  145.   } else {  
  146.     LOG(FATAL) << "Layer " << param.name() << " has unknown engine.";  
  147.   }  
  148. }  
  149.   
  150. REGISTER_LAYER_CREATOR(Softmax, GetSoftmaxLayer);  
  151.   
  152. // 注册tanh层  
  153. // Get tanh layer according to engine.  
  154. template <typename Dtype>  
  155. shared_ptr<Layer<Dtype> > GetTanHLayer(const LayerParameter& param) {  
  156.   TanHParameter_Engine engine = param.tanh_param().engine();  
  157.   if (engine == TanHParameter_Engine_DEFAULT) {  
  158.     engine = TanHParameter_Engine_CAFFE;  
  159. #ifdef USE_CUDNN  
  160.     engine = TanHParameter_Engine_CUDNN;  
  161. #endif  
  162.   }  
  163.   if (engine == TanHParameter_Engine_CAFFE) {  
  164.     return shared_ptr<Layer<Dtype> >(new TanHLayer<Dtype>(param));  
  165. #ifdef USE_CUDNN  
  166.   } else if (engine == TanHParameter_Engine_CUDNN) {  
  167.     return shared_ptr<Layer<Dtype> >(new CuDNNTanHLayer<Dtype>(param));  
  168. #endif  
  169.   } else {  
  170.     LOG(FATAL) << "Layer " << param.name() << " has unknown engine.";  
  171.   }  
  172. }  
  173.   
  174. REGISTER_LAYER_CREATOR(TanH, GetTanHLayer);  
  175.   
  176. // 注册PYTHON层  
  177. #ifdef WITH_PYTHON_LAYER  
  178. template <typename Dtype>  
  179. shared_ptr<Layer<Dtype> > GetPythonLayer(const LayerParameter& param) {  
  180.   Py_Initialize();  
  181.   try {  
  182.     bp::object module = bp::import(param.python_param().module().c_str());  
  183.     bp::object layer = module.attr(param.python_param().layer().c_str())(param);  
  184.     return bp::extract<shared_ptr<PythonLayer<Dtype> > >(layer)();  
  185.   } catch (bp::error_already_set) {  
  186.     PyErr_Print();  
  187.     throw;  
  188.   }  
  189. }  
  190.   
  191. REGISTER_LAYER_CREATOR(Python, GetPythonLayer);  
  192. #endif  
  193.   
  194. // Layers that use their constructor as their default creator should be  
  195. // registered in their corresponding cpp files. Do not register them here.  
  196. }  // namespace caffe  

五、总结

作者还真是煞费苦心,弄了个宏,一下子就注册了类。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/boon_228/article/details/54909561

智能推荐

c# 调用c++ lib静态库_c#调用lib-程序员宅基地

文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib

deepin/ubuntu安装苹方字体-程序员宅基地

文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang

html表单常见操作汇总_html表单的处理程序有那些-程序员宅基地

文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些

PHP设置谷歌验证器(Google Authenticator)实现操作二步验证_php otp 验证器-程序员宅基地

文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器

【Python】matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距-程序员宅基地

文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距

docker — 容器存储_docker 保存容器-程序员宅基地

文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器

随便推点

网络拓扑结构_网络拓扑csdn-程序员宅基地

文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn

JS重写Date函数,兼容IOS系统_date.prototype 将所有 ios-程序员宅基地

文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios

如何将EXCEL表导入plsql数据库中-程序员宅基地

文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql

Git常用命令速查手册-程序员宅基地

文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...

分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120-程序员宅基地

文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120

【C++缺省函数】 空类默认产生的6个类成员函数_空类默认产生哪些类成员函数-程序员宅基地

文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数

推荐文章

热门文章

相关标签