我的编程空间,编程开发者的网络收藏夹
学习永远不晚

详解Java中自定义注解的使用

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

详解Java中自定义注解的使用

什么是注解

在早期的工作的时候 ,自定义注解写的比较多,可大多都只是因为 这样看起来 不会存在一堆代码耦合在一起的情况,所以使用了自定义注解,这样看起来清晰些,

Annontation是Java5开始引入的新特征,中文名称叫注解。

它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观、更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。

Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。

一般我们自定义一个注解的操作是这样的:

public @interface MyAnnotation {
}

如果说我们需要给他加上参数,那么大概是这样的

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface MyAnnotation {
    public int age() default 18;
    String name() ;
    String [] books();
}

我们可以关注到上面有些我们不曾见过的注解,而这类注解,统称为元注解 ,我们可以大概来看一下

@Document

是被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中。

@Target

是专门用来限定某个自定义注解能够被应用在哪些Java元素上面的,不定义说明可以放在任何元素上。

上面这个 Target这玩意有个枚举,可以清晰的看出来,他的 属性

使用枚举类ElementType来定义

public enum ElementType {
    
    TYPE,
    
    FIELD,
    
    METHOD,
    
    PARAMETER,
    
    CONSTRUCTOR,
    
    LOCAL_VARIABLE,
    
    ANNOTATION_TYPE,
    
    PACKAGE
}

@Retention

即用来修饰自定义注解的生命周期。

使用了RetentionPolicy枚举类型定义了三个阶段

public enum RetentionPolicy {
    
    SOURCE,

    
    CLASS,

    
    RUNTIME
}

@Inherited

允许子类继承父类中的注解

注解的注意事项

1.访问修饰符必须为public,不写默认为public;

2.该元素的类型只能是基本数据类型、String、Class、枚举类型、注解类型(体现了注解的嵌套效果)以及上述类型的一位数组;

3.该元素的名称一般定义为名词,如果注解中只有一个元素,请把名字起为value(后面使用会带来便利操作);

4.()不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法;

5.default代表默认值,值必须和第2点定义的类型一致;

6.如果没有默认值,代表后续使用注解时必须给该类型元素赋值。

注解的本质

所有的Java注解都基于Annotation接口。但是,手动定义一个继承自Annotation接口的接口无效。要定义一个有效的Java注解,需要使用@interface关键字来声明注解。Annotation接口本身只是一个普通的接口,并不定义任何注解类型。

public interface Annotation {  
    boolean equals(Object obj);
    
    int hashCode();
    
    String toString();
    
    Class<? extends Annotation> annotationType();
}

在Java中,所有的注解都是基于Annotation接口的,但是手动定义一个继承自Annotation接口的接口并不会创建一个有效的注解。要定义有效的注解,需要使用特殊的关键字@interface来声明注解类型。Annotation接口本身只是一个普通的接口,而不是一个定义注解的接口。因此,使用@interface声明注解是定义Java注解的标准方法。

public @interface MyAnnotation1 {
}
public interface MyAnnotation2 extends Annotation  {
}
// javap -c TestAnnotation1.class
Compiled from "MyAnnotation1.java"                                                                 
public interface com.spirimark.corejava.annotation.MyAnnotation1 extends java.lang.annotation.Annotation {}
​
// javap -c TestAnnotation2.class
Compiled from "MyAnnotation2.java"                                                                 
public interface com.spirimark.corejava.annotation.MyAnnotation2 extends java.lang.annotation.Annotation {}

虽然Java中的所有注解都是基于Annotation接口,但即使接口本身支持多继承,注解的定义仍无法使用继承关键字来实现。定义注解的正确方式是使用特殊的关键字@interface声明注解类型。

同时需要注意的是,通过@interface声明的注解类型不支持继承其他注解或接口。任何尝试继承注解类型的操作都会导致编译错误。

public @interface MyAnnotation1 {
}

@interface MyAnnotation2 extends MyAnnotation1 {
}

@interface MyAnnotation3 extends Annotation {
}

自定义注解使用

使用方式 1

自定义注解的玩法有很多,最常见的莫过于

  • 声明注解
  • 通过反射读取

但是上面这种一般现在在开发中不怎么常用,最常用的就是,我们通过 切面去在注解的前后进行加载

创建注解

@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface BussinessLog {
 
    
    BusinessTypeEnum value();
 
    
    boolean isSaveRequestData() default true;
 
    
    boolean isSaveResponseData() default true;
}

设置枚举

public enum BusinessTypeEnum {
    
    OTHER,
 
    
    INSERT,
 
    
    UPDATE,
 
    
    DELETE,
 
    
    GRANT,
 
    
    EXPORT,
 
    
    IMPORT,
}

创建切面操作

@Slf4j
@Aspect
@Component
public class LogConfig {
 
    @Autowired
    private IUxmLogService uxmLogService;
 
   
    @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
        handleLog(joinPoint, controllerLog, null, jsonResult);
    }
 
   
    @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) {
        handleLog(joinPoint, controllerLog, e, null);
    }
 
    protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) {
        try {
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            String title = methodSignature.getMethod().getAnnotation(ApiOperation.class).value();
            // 获取当前的用户
            String userName = CurrentUser.getCurrentUserName();
 
            // *========数据库日志=========*//
            UxmLog uxmLog = new UxmLog();
            uxmLog.setStatus(BaseConstant.YES);
            // 请求的地址
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            assert requestAttributes != null;
            HttpServletRequest request = requestAttributes.getRequest();
            String ip = getIpAddr(request);
            // 设置标题
            uxmLog.setTitle(title);
            uxmLog.setOperIp(ip);
            uxmLog.setOperUrl(request.getRequestURI());
            uxmLog.setOperName(userName);
 
            if (e != null) {
                uxmLog.setStatus(BaseConstant.NO);
                uxmLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
            }
            // 设置方法名称
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            uxmLog.setMethod(className + "." + methodName + "()");
            // 设置请求方式
            uxmLog.setRequestMethod(request.getMethod());
            // 处理设置注解上的参数
            getControllerMethodDescription(joinPoint, controllerLog, uxmLog, jsonResult, request);
            // 保存数据库
            uxmLog.setOperTime(new Date());
            uxmLogService.save(uxmLog);
        } catch (Exception exp) {
            // 记录本地异常日志
            log.error("==前置通知异常==");
            log.error("异常信息:{}", exp.getMessage());
            exp.printStackTrace();
        }
    }
 
    public static String getIpAddr(HttpServletRequest request) {
        if (request == null) {
            return "unknown";
        }
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Forwarded-For");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
 
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
 
    public void getControllerMethodDescription(JoinPoint joinPoint, Log log, UxmLog uxmLog, Object jsonResult, HttpServletRequest request) throws Exception {
        // 设置action动作
        uxmLog.setBusinessType(log.value().ordinal());
        // 是否需要保存request,参数和值
        if (log.isSaveRequestData()) {
            // 获取参数的信息,传入到数据库中。
            setRequestValue(joinPoint, uxmLog, request);
        }
        // 是否需要保存response,参数和值
        if (log.isSaveResponseData()) {
            uxmLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
        }
    }
 
    private void setRequestValue(JoinPoint joinPoint, UxmLog uxmLog, HttpServletRequest request) throws Exception {
        String requestMethod = uxmLog.getRequestMethod();
        if (RequestMethod.PUT.name().equals(requestMethod) || RequestMethod.POST.name().equals(requestMethod)) {
            String params = argsArrayToString(joinPoint.getArgs());
            uxmLog.setOperParam(StringUtils.substring(params, 0, 2000));
        } else {
            Map<?, ?> paramsMap = (Map<?, ?>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
            uxmLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));
        }
    }
 
    private String argsArrayToString(Object[] paramsArray) {
        StringBuilder params = new StringBuilder();
        if (paramsArray != null && paramsArray.length > 0) {
            for (Object o : paramsArray) {
                if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
                    try {
                        Object jsonObj = JSON.toJSON(o);
                        params.append(jsonObj.toString()).append(" ");
                    } catch (Exception e) {
                        log.error(e.getMessage());
                    }
                }
            }
        }
        return params.toString().trim();
    }
 
    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
                || o instanceof BindingResult;
    }
}

这样的话,我们就可以 在 项目当中 去在标注注解的前后去进行输出 日志

使用方式 2

我们可能还会在每次请求的时候去输出日志,所以 我们也可以去定义一个 请求的 注解

@HttpLog 自动记录Http日志

在很多时候我们要把一些接口的Http请求信息记录到日志里面。通常原始的做法是利用日志框架如log4j,slf4j等,在方法里面打日志log.info(“xxxx”)。但是这样的工作无疑是单调而又重复的,我们可以采用自定义注解+切面的来简化这一工作。通常的日志记录都在Controller里面进行的比较多,我们可以实现这样的效果:
我们自定义@HttpLog注解,作用域在类上,凡是打上了这个注解的Controller类里面的所有方法都会自动记录Http日志。实现方式也很简单,主要写好切面表达式:

日志切面

下面代码的意思,就是当标注了注解,我们通过 @Pointcut 定义了切入点, 当标注了注解,我们会在标注注解的 前后进行输出 ,当然也包含了 Spring 官方 自带的注解 例如 RestController

// 切面表达式,描述所有所有需要记录log的类,所有有@HttpLog 并且有 @Controller 或 @RestController 类都会被代理
    @Pointcut("@within(com.example.spiritmark.annotation.HttpLog) && (@within(org.springframework.web.bind.annotation.RestController) || @within(org.springframework.stereotype.Controller))")
    public void httpLog() {
    }

    @Before("httpLog()")
    public void preHandler(JoinPoint joinPoint) {
        startTime.set(System.currentTimeMillis());
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest httpServletRequest = servletRequestAttributes.getRequest();
        log.info("Current Url: {}", httpServletRequest.getRequestURI());
        log.info("Current Http Method: {}", httpServletRequest.getMethod());
        log.info("Current IP: {}", httpServletRequest.getRemoteAddr());
        Enumeration<String> headerNames = httpServletRequest.getHeaderNames();
        log.info("=======http headers=======");
        while (headerNames.hasMoreElements()) {
            String nextName = headerNames.nextElement();
            log.info(nextName.toUpperCase() + ": {}", httpServletRequest.getHeader(nextName));
        }
        log.info("======= header end =======");
        log.info("Current Class Method: {}", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        log.info("Parms: {}", null != httpServletRequest.getQueryString() ? JSON.toJSONString(httpServletRequest.getQueryString().split("&")) : "EMPTY");

    }

    @AfterReturning(returning = "response", pointcut = "httpLog()")
    public void afterReturn(Object response) {
        log.info("Response: {}", JSON.toJSONString(response));
        log.info("Spend Time: [ {}", System.currentTimeMillis() - startTime.get() + " ms ]");

    }

@TimeStamp 自动注入时间戳

如果我们想通过自定义注解,在我们每次保存数据的时候,自动的帮我们将标注注解的方法内的时间戳字段转换成 正常日期,我们就需要

我们的很多数据需要记录时间戳,最常见的就是记录created_at和updated_at,通常我们可以通常实体类中的setCreatedAt()方法来写入当前时间,然后通过ORM来插入到数据库里,但是这样的方法比较重复枯燥,给每个需要加上时间戳的类都要写入时间戳很麻烦而且不小心会漏掉。

另一个思路是在数据库里面设置默认值,插入的时候由数据库自动生成当前时间戳,但是理想很丰满,现实很骨感,在MySQL如果时间戳类型是datetime里即使你设置了默认值为当前时间也不会在时间戳为空时插入数据时自动生成,而是会在已有时间戳记录的情况下更新时间戳为当前时间,这并不是我们所需要的,比如我们不希望created_at每次更改记录时都被刷新,另外的方法是将时间戳类型改为timestamp,这样第一个类型为timestamp的字段会在值为空时自动生成,但是多个的话,后面的均不会自动生成。再有一种思路是,直接在sql里面用now()函数生成,比如created_at = now()。

但是这样必须要写sql,如果使用的不是主打sql流的orm不会太方便,比如hibernate之类的,并且也会加大sql语句的复杂度,同时sql的可移植性也会降低,比如sqlServer中就不支持now()函数。为了简化这个问题,我们可以自定义@TimeStamp注解,打上该注解的方法的入参里面的所有对象或者指定对象里面要是有setCreatedAt、setUpdatedAt这样的方法,便会自动注入时间戳,而无需手动注入,同时还可以指定只注入created_at或updated_at。实现主要代码如下:

@Aspect
@Component
public class TimeStampAspect {

    @Pointcut("@annotation(com.example.spiritmark.annotation.TimeStamp)")
    public void timeStampPointcut() {}

    @Before("timeStampPointcut() && @annotation(timeStamp)")
    public void setTimestamp(JoinPoint joinPoint, TimeStamp timeStamp) {
        Long currentTime = System.currentTimeMillis();
        Class<?> type = timeStamp.type();
        Object[] args = joinPoint.getArgs();

        for (Object arg : args) {
            if (type.isInstance(arg)) {
                setTimestampForArg(arg, timeStamp);
            }
        }
    }

    private void setTimestampForArg(Object arg, TimeStamp timeStamp) {
        Date currentDate = new Date(System.currentTimeMillis());
        TimeStampRank rank = timeStamp.rank();
        Method[] methods = arg.getClass().getMethods();

        for (Method method : methods) {
            String methodName = method.getName();
            if (isSetter(methodName) && isRelevantSetter(methodName, rank)) {
                try {
                    method.invoke(arg, currentDate);
                } catch (IllegalAccessException | InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private boolean isSetter(String methodName) {
        return methodName.startsWith("set") && methodName.length() > 3;
    }

    private boolean isRelevantSetter(String methodName, TimeStampRank rank) {
        if (rank.equals(TimeStampRank.FULL)) {
            return methodName.endsWith("At");
        }
        if (rank.equals(TimeStampRank.UPDATE)) {
            return methodName.startsWith("setUpdated");
        }
        if (rank.equals(TimeStampRank.CREATE)) {
            return methodName.startsWith("setCreated");
        }
        return false;
    }
}

1.使用@Aspect和@Component注解分别标注切面和切面类,更符合AOP的实现方式。

2.将pointCut()和before()方法分别改名为timeStampPointcut()和setTimestamp(),更能表达它们的作用。

3.通过Class.isInstance(Object obj)方法,将原先的流操作改为了一个简单的for循环,使代码更加简洁。

4.将原先的setCurrentTime()方法改名为setTimestampForArg(),更能表达它的作用。

5.新增了两个私有方法isSetter()和isRelevantSetter(),将原先在setTimestampForArg()中的逻辑分离出来,提高了代码的可读性和可维护性

到此这篇关于详解Java中自定义注解的使用的文章就介绍到这了,更多相关Java自定义注解内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

详解Java中自定义注解的使用

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

详解Java中自定义注解的使用

Annontation是Java5开始引入的新特征,中文名称叫注解,它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。本文主要介绍了自定义注解的使用,希望对大家有所帮助
2023-03-20

Java自定义注解的详解

Java自定义注解Java注解提供了关于代码的一些信息,但并不直接作用于它所注解的代码内容。在这个教程当中,我们将学习Java的注解,如何定制注解,注解的使用以及如何通过反射解析注解。Java1.5引入了注解,当前许多java框架中大量使用
2023-05-31

java中怎么自定义注解详解

在Java中,可以使用`@interface`关键字来定义注解。自定义注解的语法如下:public @interface CustomAnnotation {String value() default "";int number() d
java中怎么自定义注解详解
2023-10-28

Java中自定义注解如何使用

本文小编为大家详细介绍“Java中自定义注解如何使用”,内容详细,步骤清晰,细节处理妥当,希望这篇“Java中自定义注解如何使用”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。什么是注解在早期的工作的时候 ,自定义
2023-07-05

Java注解怎么自定义使用

这篇文章主要介绍了Java注解怎么自定义使用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Java注解怎么自定义使用文章都会有所收获,下面我们一起来看看吧。注解注解基本介绍注解概述:Java 注解(Annota
2023-07-05

Java教程:JAVA自定义注解

注解概念注解是Java SE 5.0版本开始引入的概念,它是对java源代码的说明,是一种元数据(描述数据的数据)。注解和注释的不同注释注释是对代码的说明,给代码的读者看,便于帮读者梳理业务逻辑;在程序代码中经常看到的以@ 开头的大部分是注
2023-06-02

Java中的注解(Annotation)有什么作用?如何自定义注解?(Java注解的功能是什么?如何定义自己的注解?)

Java注解用于提供附加信息,不会影响代码行为。它们用于:文档化代码代码分析代码生成运行时行为修改自定义注解通过@Retention和@Target指定范围和应用元素,并使用@Documented和@Inherited控制文档和继承行为。在使用时,通过@AnnotationName(value)语法应用注解。通过反射可以在运行时访问自定义注解。最佳实践包括保持简洁、使用标准化命名、避免过度使用和详细文档化。
Java中的注解(Annotation)有什么作用?如何自定义注解?(Java注解的功能是什么?如何定义自己的注解?)
2024-04-02

如何在Java中自定义注解

这篇文章给大家介绍如何在Java中自定义注解,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。一、自定义注解格式分析 Java 中自带的 @Override 注解 , 源码如下 :@Target(ElementType.M
2023-06-15

怎么在java中自定义注解

这篇文章给大家介绍怎么在java中自定义注解,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。Java是什么Java是一门面向对象编程语言,可以编写桌面应用程序、Web应用程序、分布式系统和嵌入式系统应用程序。1、@Val
2023-06-07

java中什么是自定义注解

今天就跟大家聊聊有关java中什么是自定义注解,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。Java可以用来干什么Java主要应用于:1. web开发;2. Android开发;3.
2023-06-14

浅析Java自定义注解的用法

注解为我们在代码中添加信息提供一种形式化的方法,使我们可以在源码、编译时、运行时非常方便的使用这些数据。本文主要为大家介绍了Java自定义注解的用法,希望对大家有所帮助
2023-03-21

Java怎么自定义注解

这篇文章主要介绍“Java怎么自定义注解”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Java怎么自定义注解”文章能帮助大家解决问题。注解注解为我们在代码中添加信息提供一种形式化的方法,使我们可以在
2023-07-05

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录