为您推荐:
精华内容
最热下载
问答
  • 5星
    10KB ailemony 2020-12-13 15:34:25
  • 5星
    6KB ailemony 2020-12-13 17:36:34
  • 5星
    519KB ailemony 2020-12-12 10:39:22
  • 5星
    16.39MB ailemony 2020-12-12 12:07:38
  • 5星
    23.87MB ailemony 2020-12-12 09:54:12
  • 5星
    5.99MB ailemony 2020-12-12 10:22:39
  • 5星
    5.18MB ailemony 2020-12-12 11:04:50
  • 5星
    18KB ailemony 2020-12-13 15:44:31
  • 5星
    21.17MB ailemony 2020-12-12 10:03:30
  • 5星
    1.32MB ailemony 2020-12-12 21:22:50
  • 5、Java中( C )。...** 不需定义类,就能创建对象属性可以是简单变量,也可以是一个对象 属性必须是简单变量 对象中必有属性和方法7、下面关键字中哪一个是不可用来控制类成员的访问( A )。 p...

    5、Java中( C )。

    一个子类可以有多个父类,一个父类也可以有多个子类 一个子类可以有多个父类,但一个父类只可以有一个子类 一个子类可以有一个父类,但一个父类可以有多个子类 上述说法都不对

    6、下列说法正确的是( D ) 。** 不需定义类,就能创建对象

    属性可以是简单变量,也可以是一个对象 属性必须是简单变量 对象中必有属性和方法

    7、下面关键字中哪一个是不可用来控制对类成员的访问( A )。 public private protected default

    8、构造方法被调用是当( C )。 类定义时

    使用对象的属性时 使用对象的方法时 对象被创建时

    9、下面不是面向对象技术特点的是( A )。 结构化 封装 继承性 多态性

    10、Java中,一个类可以有几个父类( B )。 任意 1 2 4

    11、Java中,使用哪个关键字来定义一个接口( A )。 implements class extends interface

    12、接口体中不应包含( C )。 常量定义 常量赋值 方法实现 方法声明

    13、下列不是重载方法的特征的是(? B)。 参数个数不同 名相同而类型不同 参数类型不同

    名相同且类型相同

    14、在成员变量与局部变量重名时,若想在方法内使用成员变量,要使用关键字( C)。 super import this return 15、当子类的成员变量与父类的成员变量重名时,若想在子类中使用父类中同名的成员变量,要使用关键字(A? )。 super import this return

    16、若有定义如下 class dog { float x; static int y;?},则下列说法中正确的是( B )。 x称为类变量,y为实例变量 x,y均为类变量 x,y均为实例变量

    x为实例变量,y为类变量 17、下列说法正确的是( A )。 实例方法能对类变量和实例变量操作 实例方法只能对类变量操作 实例方法只能实例变量操作

    类方法能对类变量和实例变量操作 18、Java程序是由什么组成的( B )。** 类 对象 函数 包

    19、package awt;的结果是( C )。 编译结果出错

    说明文件的类包含在Java 的awt包中 说明文件的类在自定义的awt包中 导入自定义的awt包中的类

    20、若有定义 class A{int x,y; static float f(int a){?} float g(int x1,int x2){?}} 及A a1=new A();则下列用法中非法的是( C )。 A.g(3,2) A.f(3) a1.f(4) a1.g(2,5)

    21、下列不是类的属性修饰符的是( D )。 public abstract private final

    22、类中不加任何访问权限限定的成员属于( A )。

    default public private protected

    23、要想使定义该类所在的包之外的类不能访问这个类,使用关键字(? A)。 不用 private final protected

    24、定义一个名为key的类,使之不能被继承,应选(D? )。*** class key{? }

    native class key{? } class key{ final; } final class key{? }

    25、Java中,如果类C是类B的子类,类B是类A的子类,那么下面描述正确的是( A )。 C不仅继承了B中的成员,同样也继承了A中的成员 C只继承了B中的成员 C只继承了A中的成员 C不能继承A或B中的成

    26、一个对象创建包括的操作中,没有下面的( A )。 释放内存 对象声明 分配内存 调用构造方法

    27、this关键字的含义是表示一个( D )。 指针 当前对象 一个类 一个方法

    28、若有定义如下 class A{int x,x1; int f(){int a;?} int g(){int y;?}} ,则不正确的赋值为(D? )。*** 在f()中a=x 在f()中x=a 在f()中x=x1 在g()中y=a

    29、在Java中下列关于继承的论述中,错误的是( C )。 继承具有传递性

    继承关系也称为“即是”(is a)关系 支持多继承

    继承提高了系统的可重用性

    30、在Java中,关于封装性的说法中,错误的是(? A)。**** 是一种信息隐蔽技术 使对象之间不可相互作用 是受保护的内部实现

    与类有关,封装的基本单位是对象

    31、一个消息包含的内容,不应有( D )。 消息接收者

    接受对象应采用的方法 指示接受者做何处理 方法的参数

    32、若声明一个类不能被继承,即不能有子类,用关键字( A )。 final finally abstract super

    33、下述概念中不属于面向对象方法的是(? D)。 对象、消息 继承、多态 类、封装 过程调用

    34、下列关于构造方法的叙述中,错误的是( C? )。 Java语言规定构造方法名与类名必须相同

    Java语言规定构造方法没有返回值,但不用void声明 Java语言规定构造方法不可以重载

    Java语言规定构造方法只能通过new自动调用

    35、关于被私有访问控制符private修饰的成员变量,以下说法正确的是( C? )。

    可以被三种类所引用:该类自身、与它在同一个包中的其他类、在其他包中的该类的子类 可以被两种类访问和引用:该类本身、该类的所有子类 只能被该类自身所访问和修改 只能被同一个包中的类访问

    36、下面的选项中,哪一项不属于“汽车类”的行为(? D )。 启动 刹车 减速 速度

    37、在Java中用什么关键字修饰的方法可以直接通过类名来调用?(? D)*** static final private void

    38、若在某一个类定义中定义有如下的方法:abstract void performDial( );该方法属于( C? )。 接口方法 最终方法 抽象方法 空方法

    39、如果希望某个变量只可以被类本身访问和调用,则应该使用下列哪一种访问控制修饰(? A )。

    展开全文
    weixin_36110344 2021-03-13 17:20:42
  • 下面哪些是Thread类的方法?A.Start() B.run()方法 C.exit()方法 D. getPriority()方法解析:exit()方法 是 System类的方法2.GC线程是否为守护线程?答:是解析:线程分为(1)守护线程(2)非守护线程只要当前jvm尚存...

    所有内容都是在网上查找的

    1.下面哪些是Thread类的方法?

    A.Start()  B.run()方法  C.exit()方法  D. getPriority()方法

    解析:exit()方法 是 System类的方法

    2.GC线程是否为守护线程?

    答:是

    解析:线程分为(1)守护线程(2)非守护线程

    只要当前jvm尚存任何一个非守护线程,守护线程就全部工作。

    3.Volatile关键字是否能保证线程安全?

    答:不能

    解析:Volatile关键字用在多线程同步,可保证读取的可见性。

    4.存在使 i+1

    答:存在

    解析:如果i为int型,那么当i为 int 能表示的的最大整数时,i+1就溢出变成为负数。

    5.0.6332的数据类型是?

    A float  B double  C Float  D Double

    答:B

    解析:浮点型数默认为 double类型,若为float类型 要加上 f。 如: 0.6332f

    6.不通过构造函数也能创建对象么?

    答:是

    解析:java创建对象的几种方式(重要)

    (1)使用new语句创建对象。

    (2)运用反射手段。调用java.lang.class 或 java.lang.reflect.Constructor类的 newInstance()方法。

    (3)调用对象的clone()方法。 在内存上对已有对象的影印。

    (4)运用反序列化手段。从文件中还原类的对象。

    7.ArrayList list = new ArrayList(20);中的list扩充几次?

    A 0  B 1  C 2    D 3

    答:A

    解析:默认ArrayList的长度是10个,如果往list里添加20个元素,肯定要扩充一次。 但是这里显示指明了需要多少空间,所以一次性分配了这么多空间

    不需要扩充。

    8.下面哪些是对称加密算法?

    A DES  B AES   C DSA   D RSA

    答: AB

    9.java中有没有goto?

    有,为保留字,但尚未使用。

    10.Set里的元素是不能重复的,那么用什么方法来区分重复与否? == 还是 equals(),它们有什么区别?

    答:Set里的元素不能重复,用 iterator()方法来区分重复与否。

    equals()方法是判断两个 Set 是否相等。

    == 比较内存地址;equals()比较内容

    11.List,Set,Map是否继承自 Collection接口?

    答: List Set继承自Collection接口; Map不是。

    12.接口是否可以继承接口? 抽象类是否可以实现接口? 抽象类是否可以继承实体类?

    答: 接口可以继承接口    抽象类可以实现接口    抽象类可以继承实体类;但是前提是实体类有明确的构造函数。

    13.构造器 ConStructor 是否可以被 Override 重写?

    答:构造器ConStrucot不能被继承,因此不能被重写。

    但是可以被重载。

    14.是否可以继承 String类?

    答:不能

    解析:String类时 final 类,因此不能被继承。

    15.try{}中有一个 return 语句,那么紧跟在try{}后的finally{}里的code会不会被执行,什么时候被执行,在return前还是后?

    答:会执行,在return前执行。

    解析:无论如何,finally{}块里的code都会被执行。

    16.使用最有效率的方法,算出 2乘以8等于多少?

    答: 2<<3  位运算

    17.当一个对象被当做参数传递到一个方法后,此方法可改变这个对象的属性,并返回变化后的结果。那么这里用的是值传递,还是引用传递?

    答:是值传递。

    解析:Java中只有值传递参数。 当一个对象实例作为参数,传递到方法时,参数的值就是对对象的引用。 对象的内容可以在被调用

    方法中改变,但对象的引用永远不会变。

    18.GC是什么?为什么要有GC?

    答:GC是垃圾回收器; 内存处理是编程人员容易出现的问题,忘记或错误的内存回收会导致系统不稳定,甚至崩坏。

    19.float型 float f = 3.4是否正确?

    答:不正确。

    解析:精度不准确,应用强制类型转换。 float f = (float)3.4

    20.String与Stringbuffer的区别?

    答:String的长度是不可变的,Stringbuffer的长度是可变的。

    如果对字符串中的内容经常进行操作,特别是内容要修改,那么用Stringbuffer。若是最后需要String,使用 toString()方法转化。

    21.String是java基本数据类型么?

    答:不是

    解析:八大基本数据类型: 整型: byte short int long

    浮点型:float double

    字符型: char

    布尔型:boolean

    22.以下属于面向对象特征的是?

    A 重载  B 重写  C 封装  D继承

    答:CD

    解析:java面向对象的特征(1)封装(2)继承(3)多态

    23.多态的表现形式?

    A 重写  B 抽象  C 继承  D 封装

    答:A

    解析:多态的表现形式有两种:

    (1)重写     父类与子类间的多态性

    (2)重载  一个类中的多态性

    24.以下对重载描述错误的是?

    A 方法的重载只能发生在一个类内部。

    B 构造方法不能重载

    C 重载要求方法名相同,参数列表不同。

    D 方法的返回值类型,不是区分方法重载的条件。

    答:B

    解析:构造方法不能被继承,所以不能被重写。

    但是可以被重载。

    25.以下对抽象类描述正确的是?

    A 抽象类中没有构造方法

    B 抽象类必须提供抽象方法

    C 有抽象方法的类一定是抽象类

    D 抽象类可以通过new实例化

    答:C

    解析:有抽象方法的类一定是抽象类, 抽象类不一定有抽象方法。

    26.以下对接口描述错误的是?

    A 接口没有提供构造方法

    B 接口中的方法 默认使用 public abstract 修饰

    C 接口中的属性 默认使用 public static fina修饰

    D 接口不允许多继承

    答:D

    解析:java中支持 单继承 多实现

    27.接口和抽象类描述正确的有?

    A 抽象类没有构造方法

    B 接口没有构造方法

    C 抽象类不允许多继承

    D 接口中的方法可以有方法体

    答:BC

    28.以下描述错误的是:

    A abstract  可以修饰类、接口、方法

    B abstract 修饰的类主要用于被继承。

    C abstract 可以修饰变量

    D abstract 修饰的类的子类也可以是 abstract 类

    答:C

    29.以下描述正确的是?

    A 方法的重写应用在一个类内部

    B 方法的重载与返回值类型无关。

    C 构造方法不能重载

    D 构造方法可以重写

    答:B

    30.以下对异常的描述不正确的是?

    A 异常分为 Error 和 Exception

    B Throwable 是所有异常的父类

    C Exception 是所有异常的父类

    D Exception 包括 RuntimeException 和 非 RuntimeException

    答:C

    解析:              Throwable

    Error          Exception

    RuntimeException   其他

    31.在 try - catch- finally 语句块中,以下可以单独和 finally一起使用的是?

    A catch   B try  C throws   D throw

    答:B

    32.以下描述错误的是?

    A try 块不能省  B 可以使用多重catch块   C finally块可以省略  D catch块和finally块可以同时省略

    答:D

    解析: try catch 或 try finally 或 try catch finally

    33.java.lang包的  方法,比较两个对象是否相等?相等返回值 true

    A toString  B equals()  C Compare()  D以上的不正确

    答:B

    解析:equals()方法比较内容是否相同。

    34.使用   方法可以获得Calendar类的实例?

    A get   B equals  C getTime  D getLnstance

    答:D

    35.下写说法正确的有?

    A class中的 Constructor不可省略。

    B Constructor必须与类同名,方法不能与类同名。

    C Constructor在一个对象new 时执行。

    D 一个class只能定义一个Constructor.

    答:C

    解析:普通方法可与类同名。

    36.java是从   语言改进重新设计的。

    A Ada  B c++  C Pasacal  D BASIC

    答:B

    37.下列语句哪一个正确?

    A java程序编译后会产生 machine code

    B java程序变编译后会产生 byte code

    C java程序编译后会产生 DLL

    D 以上都不正确

    答:B

    38.提供java存取数据库能力的包是?

    A java.sql  B.java.awt  Cjava.lang  D java.swing

    答:A

    39.下列哪一种叙述正确?

    A abstract修饰符可以修饰 字段、方法、类

    B 抽象方法的body部分必须用 {}包住。

    C 声明抽象方法 {}可有可无

    D 声明抽象方法,不可写出{}

    答:D

    解析:abstract不能修饰字段,抽象方法没有方法体。

    40.不能用来修饰interface的有?

    A private   B public    C protected   Ds tatic

    答:ACD

    解析:interface 只能是public,加不加修饰符都是public

    备注:java程序中的起始类名必须与存放该类的文件名相同。

    Unicode是用16位表示一个字的。  一个汉字占2个字节,一个字节 8 位。

    展开全文
    weixin_29534143 2021-03-12 23:22:01
  • 参数不合法,自动使用统一异常返回错误,例如: { "code": "-3", "message": "参数{age}最小18" } 实现代码 代码一次给全,主要包含如下几个Java类,拿过去拷贝到项目中就能用: 1、ValidatorController 2、...

    1、杜绝通篇 if else 参数判断!!!
    2、普通参数方式
    3、实体对象参数方式

    效果
    参数不合法,自动使用统一异常返回错误,例如:

    {
        "code": "-3",
        "message": "参数{age}最小18"
    }
    

    实现代码
    代码一次性给全,主要包含如下几个Java类,拿过去拷贝到项目中就能用:
    1、ValidatorController
    2、ValidatorVo
    3、ExampleValidatorService
    4、ResultCode
    5、ResultResponseBodyAdvice
    6、ResultVO_IGNORE
    7、ResultVO

    
    import javax.validation.constraints.Email;
    import javax.validation.constraints.NotNull;
    import javax.validation.constraints.Size;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import link.nuggets.user.module.example.model.ValidatorVo;
    import link.nuggets.user.module.example.service.ExampleValidatorService;
    
    /**
     * Validator示例
     * 
     * @author 单红宇
     * @date 2019年7月8日
     *
     */
    @Validated // 注意!1) 如果想在参数中使用 @NotNull 这种注解校验,就必须在类上添加 @Validated;2) 如果方法中的参数是对象类型,则必须要在参数对象前面添加 @Validated
    @RestController
    @RequestMapping("/example/valid")
    public class ValidatorController {
    	
    	@Autowired
    	private ExampleValidatorService exampleValidatorService;
    
    	/**
    	 * 直接参数校验
    	 * 要特别提醒的是,验证框架里面大部分都不需要我们显示设置message,每个注解框架都给了一个默认提示语,大多数提示还都比较友好
    	 * 
    	 * @param email
    	 * @return
    	 */
    	@GetMapping("/test1")
    	public String test1(@NotNull(message = "不能为空") @Size(max = 32, min = 6, message = "长度需要在6-32之间") @Email String email) {
    		return "OK";
    	}
    
    	/**
    	 * 实体类校验
    	 * 
    	 * @param validatorVo
    	 * @return
    	 */
    	@GetMapping("/test2")
    	public String test2(@Validated ValidatorVo validatorVo) {
    		return "Validator OK";
    	}
    
    	/**
    	 * 内部Service校验
    	 * 
    	 * @return
    	 */
    	@GetMapping("/test3")
    	public String test3() {
    		return exampleValidatorService.show("16");
    	}
    
    }
    
    
    
    import javax.validation.constraints.Max;
    import javax.validation.constraints.Min;
    import javax.validation.constraints.NotNull;
    import javax.validation.constraints.Size;
    
    import lombok.Data;
    
    /**
     * 验证示例VO
     * 
     * @author 单红宇
     * @date 2019年7月10日
     *
     */
    @Data
    public class ValidatorVo {
    
    	@NotNull(message = "不能为空")
    	@Size(max = 16, min = 6, message = "字符串长度需要在6-16之间")
    	private String name;
    	@Max(value = 100, message = "最大100")
    	@Min(value = 18, message = "最小18")
    	private String age;
    }
    
    
    
    import javax.validation.constraints.Min;
    import javax.validation.constraints.NotNull;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Service;
    import org.springframework.validation.annotation.Validated;
    
    /**
     * 示例
     * 
     * @author 单红宇
     * @date 2019年7月10日
     *
     */
    @Validated
    @Service
    public class ExampleValidatorService {
    
    	private static final Logger logger = LoggerFactory.getLogger(ExampleValidatorService.class);
    
    	public String show(@NotNull(message = "不能为空") @Min(value = 18, message = "最小18") String age) {
    		logger.info("age = {}", age);
    		return age;
    	}
    
    }
    
    
    
    /**
     * 统一返回值
     * 
     * @author 单红宇
     * @date   2019年6月26日
     *
     */
    public enum ResultCode {
    
    	/** 操作成功 */
    	SUCCESS("1", "成功"),
    
    	/** 操作失败 */
    	FAIL("0", "失败"),
    	
    	/** 操作失败 */
    	NULL("-1", "数据不存在"),
    
    	/** 系统发生异常 */
    	EXCEPTION("-2", "系统异常"),
    	
    	/** 没有权限 */
    	FORBIDDEN("9403", "没有权限"),
    	
    	/** 参数错误 */
    	PARAM_INVALID("-3", "参数错误");
    	
    	private String code;
    	private String msg;
    	
    	private ResultCode(String code, String msg) {
    		this.code = code;
    		this.msg = msg;
    	}
    
    	public String code() {
    		return code;
    	}
    
    	public String msg() {
    		return msg;
    	}
    
    }
    
    
    
    import java.util.Set;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.validation.ConstraintViolation;
    import javax.validation.ConstraintViolationException;
    
    import org.hibernate.validator.internal.engine.path.PathImpl;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
    import org.springframework.core.MethodParameter;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.MediaType;
    import org.springframework.http.ResponseEntity;
    import org.springframework.http.converter.HttpMessageConverter;
    import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
    import org.springframework.http.server.ServerHttpRequest;
    import org.springframework.http.server.ServerHttpResponse;
    import org.springframework.http.server.ServletServerHttpRequest;
    import org.springframework.validation.BindException;
    import org.springframework.validation.FieldError;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
    
    /**
     * Controller 统一返回包装
     * 
     * @author 单红宇
     * @date 2019年6月26日
     *
     */
    @ControllerAdvice
    public class ResultResponseBodyAdvice implements ResponseBodyAdvice<Object> {
    
    	private static final Logger logger = LoggerFactory.getLogger(ResultResponseBodyAdvice.class);
    
    	@Override
    	public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
    			Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
    			ServerHttpResponse response) {
    		// 此处获取到request 为特殊需要的时候处理使用
    //		HttpServletRequest req = ((ServletServerHttpRequest) serverHttpRequest).getServletRequest();
    		// 下面处理统一返回结果(统一code、msg、sign 加密等)
    		if (selectedConverterType == MappingJackson2HttpMessageConverter.class
    				&& (selectedContentType.equals(MediaType.APPLICATION_JSON)
    						|| selectedContentType.equals(MediaType.APPLICATION_JSON_UTF8))) {
    			if (body == null) {
    				return ResultVO.NULL;
    			} else if (body instanceof ResultVO) {
    				return body;
    			} else {
    				// 异常
    				if (returnType.getExecutable().getDeclaringClass().isAssignableFrom(BasicErrorController.class)) {
    					ResultVO vo = new ResultVO(ResultCode.EXCEPTION);
    					HttpServletRequest req = ((ServletServerHttpRequest) request).getServletRequest();
    					if (req.getRequestURL().toString().contains("localhost")
    							|| req.getRequestURL().toString().contains("127.0.0.1"))
    						vo.setData(body);
    					return vo;
    				} else {
    					return new ResultVO(body);
    				}
    			}
    		}
    		return body;
    	}
    
    	@Override
    	public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
    		if (returnType.hasMethodAnnotation(ResultVO_IGNORE.class))
    			return false;
    		return true;
    	}
    
    	/**
    	 * Validator 参数校验异常处理
    	 * 
    	 * @param ex
    	 * @return
    	 */
    	@ExceptionHandler(value = BindException.class)
    	public ResponseEntity<Object> handleMethodVoArgumentNotValidException(BindException ex) {
    		FieldError err = ex.getFieldError();
    		// err.getField() 读取参数字段
    		// err.getDefaultMessage() 读取验证注解中的message值
    		String message = "参数{".concat(err.getField()).concat("}").concat(err.getDefaultMessage());
    		logger.info("{} -> {}", err.getObjectName(), message);
    		return new ResponseEntity<Object>(new ResultVO(ResultCode.PARAM_INVALID, message), HttpStatus.OK);
    	}
    
    	/**
    	 * Validator 参数校验异常处理
    	 * 
    	 * @param ex
    	 * @return
    	 */
    	@ExceptionHandler(value = ConstraintViolationException.class)
    	public ResponseEntity<Object> handleMethodArgumentNotValidException(ConstraintViolationException ex) {
    		Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
    		for (ConstraintViolation<?> constraintViolation : constraintViolations) {
    			PathImpl pathImpl = (PathImpl) constraintViolation.getPropertyPath();
    			// 读取参数字段,constraintViolation.getMessage() 读取验证注解中的message值
    			String paramName = pathImpl.getLeafNode().getName();
    			String message = "参数{".concat(paramName).concat("}").concat(constraintViolation.getMessage());
    			logger.info("{} -> {} -> {}", constraintViolation.getRootBeanClass().getName(), pathImpl.toString(), message);
    			return new ResponseEntity<Object>(new ResultVO(ResultCode.PARAM_INVALID, message), HttpStatus.OK);
    		}
    		return new ResponseEntity<Object>(new ResultVO(ResultCode.PARAM_INVALID, ex.getMessage()), HttpStatus.OK);
    	}
    
    }
    
    
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * Controller默认进行ResultVO包装,对于特殊不需要的,使用该注解可以忽略包装
     * 
     * @author 单红宇
     * @date 2019年6月26日
     *
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface ResultVO_IGNORE {
    
    }
    
    
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import com.google.gson.Gson;
    
    /**
     * Controller 统一定义返回类
     * 
     * @author 单红宇
     * @date 2019年6月26日
     *
     */
    public class ResultVO {
    
    	private static final Logger logger = LoggerFactory.getLogger(ResultVO.class);
    
    	public static final ResultVO SUCCESS = new ResultVO(ResultCode.SUCCESS);
    	public static final ResultVO FAIL = new ResultVO(ResultCode.FAIL);
    	public static final ResultVO FORBIDDEN = new ResultVO(ResultCode.FORBIDDEN);
    	public static final ResultVO NULL = new ResultVO(ResultCode.NULL);
    	public static final ResultVO EXCEPTION = new ResultVO(ResultCode.EXCEPTION);
    	public static final ResultVO PARAM_INVALID = new ResultVO(ResultCode.PARAM_INVALID);
    
    	/**
    	 * 返回代码
    	 */
    	private String code;
    
    	/**
    	 * 返回信息
    	 */
    	private String message;
    
    	/**
    	 * 返回数据
    	 */
    	private Object data;
    
    	public Object getData() {
    		return data;
    	}
    
    	public void setData(Object data) {
    		this.data = data;
    	}
    
    	/**
    	 * 默认构造,返回操作正确的返回代码和信息
    	 */
    	public ResultVO() {
    		this.setCode(ResultCode.SUCCESS.code());
    		this.setMessage(ResultCode.SUCCESS.msg());
    	}
    
    	/**
    	 * 构造一个返回特定代码的ResultVO对象
    	 * 
    	 * @param code
    	 */
    	public ResultVO(ResultCode code) {
    		this.setCode(code.code());
    		this.setMessage(code.msg());
    	}
    
    	public ResultVO(String code, String message) {
    		super();
    		this.setCode(code);
    		this.setMessage(message);
    	}
    
    	/**
    	 * 默认值返回,默认返回正确的code和message
    	 * 
    	 * @param data
    	 */
    	public ResultVO(Object data) {
    		ResultCode rc = data == null ? ResultCode.NULL : ResultCode.SUCCESS;
    		this.setCode(rc.code());
    		this.setMessage(rc.msg());
    		this.setData(data);
    	}
    
    	/**
    	 * 构造返回代码,以及自定义的错误信息
    	 * 
    	 * @param code
    	 * @param message
    	 */
    	public ResultVO(ResultCode code, String message) {
    		this.setCode(code.code());
    		this.setMessage(message);
    	}
    
    	/**
    	 * 构造自定义的code,message,以及data
    	 * 
    	 * @param code
    	 * @param message
    	 * @param data
    	 */
    	public ResultVO(ResultCode code, String message, Object data) {
    		this.setCode(code.code());
    		this.setMessage(message);
    		this.setData(data);
    	}
    
    	public String getMessage() {
    		return message;
    	}
    
    	public void setMessage(String message) {
    		this.message = message;
    	}
    
    	public String getCode() {
    		// request请求响应的时候,一定会走到这里,判断如果code不是成功状态,就输出日志
    		if (!ResultCode.SUCCESS.code().equals(code))
    			logger.info("ResultVO={}", new Gson().toJson(this));
    		return code;
    	}
    
    	public void setCode(String code) {
    		this.code = code;
    	}
    
    }
    

    一个不需要强调的提示,springboot 的web项目正常都是已经包含如下依赖了,该依赖里已经包含了validation-api

    	<dependency>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-web</artifactId>
    	</dependency>
    

    validation-api 的注解清单

    注释描述
    @AssertFalse被注释的元素必须为 false
    @AssertTrue同@AssertFalse
    @DecimalMax被注释的元素必须是一个数字,其值必须小于等于指定的最大值
    @DecimalMin同DecimalMax
    @Digits带批注的元素必须是一个在可接受范围内的数字
    @Email顾名思义
    @Future将来的日期
    @FutureOrPresent现在或将来
    @Max被注释的元素必须是一个数字,其值必须小于等于指定的最大值
    @Min被注释的元素必须是一个数字,其值必须大于等于指定的最小值
    @Negative带注释的元素必须是一个严格的负数(0为无效值)
    @NegativeOrZero带注释的元素必须是一个严格的负数(包含0)
    @NotBlank同StringUtils.isNotBlank
    @NotEmpty同StringUtils.isNotEmpty
    @NotNull不能是Null
    @Null元素是Null
    @Past被注释的元素必须是一个过去的日期
    @PastOrPresent过去和现在
    @Pattern被注释的元素必须符合指定的正则表达式
    @Positive被注释的元素必须严格的正数(0为无效值)
    @PositiveOrZero被注释的元素必须严格的正数(包含0)
    @Szie带注释的元素大小必须介于指定边界(包括)之间

    (END)

    展开全文
    catoop 2019-07-10 19:23:21
  • 所谓软件设计,是“令软件做出你希望它做的事情”的步骤和方法,通常以颇为一般的构想开始,最终十足的细节,以允许特殊接口(interface)的开发。这些接口而后必须转换为C++声明式。本文讨论良好C++接口的设计...

    设计与声明

    所谓软件设计,是“令软件做出你希望它做的事情”的步骤和方法,通常以颇为一般性的构想开始,最终十足的细节,以允许特殊接口(interface)的开发。这些接口而后必须转换为C++声明式。本文讨论对良好C++接口的设计和声明。

    1. 让接口容易被正确使用,不易被误用

    C++拥有许多的接口,function接口,class接口,template接口….每一种接口实施客户与你的代码互动的手段。理想情况下,客户总是会准确的使用你的接口并获得理想的结果,而如果客户错误的使用了接口,代码就不应该通过编译。

    用结构体限制参数类型

    假设我们现在需要做一个表示时间的class

    class Date {
    public:
        Date(int month, int day, int year);
        ...
    };

    乍看起来,这个类的构造函数并没有什么问题,但其实存在着很多的隐患。我们当然希望用户可以准确的使用我们的类,但用户却有可能因为某些特定的原因无法正确使用我们的类,例如没有按照月,天,年的顺序来完成构造。而此时,为了避免用户犯错,我们需要强制用户按照我们的设计来用这个类:

    //  special design
    //  缺省情况下,struct内部都是public访问限制。
    struct Day {
    explicit Day(int d) : val(d) { }
    int val;
    };
    struct Month {
    explicit Month(int m) : val(d) { }
    int val;
    };
    struct Year {
    explicit Year(int y) : val(d) { }
    int val;
    };
    
    class Date {
    public:
        Date(const Month &m, const Day &d, const Year &y);
        ...
    };
    
    Date d1(30, 3, 1996); // error!
    Date d2(Month(3), Day(30), Year(1996)); // right!

    用struct来封装数据,可以明智而审慎地导入新类型并预防“接口被误用”。

    一旦类型限定了,限定其值也是合情合理的了。例如一年只有12个月,所以Month应该反映这一点。办法之一就是用enum表现月份,但enum不具备我们希望的类型安全性,例如enum可以被当做一个int使用。比较安全的做法是:预先定义所有有效的Month。

    class Month {
    public:
        static Month Jan() { return Month(1); }
        static Month Feb() { return Month(2); }
        ....
        static Month Dec() { return Month(12); }
    private:
        explicit Month(int m);
        ..
    };
    
    Date d(Month::Mar(), Day(30), Year(1996));

    以函数替换对象,表现某个特定的月份是一种相当不错的方法。

    限制类型内什么能做,什么不能做

    除非有更好的理由,否则尽量让你的type的行为与内置type一致!

    用户很清楚像int这样的type有什么行为,所以你应该努力让你的type在合情合理的前提下也有相同的操作。例如,如果a和b都是int,那么对a*b赋值就是不合法的。

    避免无端与内置类型不兼容,真正的理由是为了==提供行为一致的接口==。很少有其他性质比”一致性“更能导致”接口被正确使用“,也很少有性质比得上”不一致性“更加剧接口的恶化。

    2. 设计class犹如设计type

    C++就像其他OOP语言一样,当你定义一个新class,也就定义了一个新的type。包括,重载函数和操作符、控制内存的分配和归还、定义对象的初始化和析构……全都在你控制,因而你应该带着和“语言设计者当初设计语言内置类型时”一样的谨慎来设计class。以下给出了部分class设计规范。

    • 新type的对象应该如何被创建和销毁?这回应该到你如何设计class的构造函数和析构函数以及内存分配函数和释放函数。
    • 对象的初始化和对象的赋值有什么样的差别?这决定了你如何设计构造函数和赋值操作符。最重要的是别混淆“初始化”和“赋值”,因为他们对应不同的函数调用。
    • 新type对象如果被passed by value,意味着什么?记住,copy构造函数用来定义一个type的pass by value如何实现。
    • 什么是新type的“合法值”?这意味你的成员函数必须进行错误检查工作,也影响了函数抛出的异常、以及函数异常明细列。
    • 你的type需要配合某个继承体系吗?如果你继承自某些既有的class,你就会受到这些class设计的束缚,特别是受到他们的函数是virtual或non-virtual的影响。如果你允许你的class被其他class继承,那会影戏到你的析构函数是否会virtual。
    • 你的新type需要什么样的转换?因为你的type存在于其他大量的type之间,这决定了你是否需要让自己type有途径转换为其他的type(隐式还是显式的?)
    • 什么样的操作符和函数对此新type而言是合理的?这取决于你的成员函数的设计。
    • 什么样的标准函数应该驳回?那些就是你声明为private的对象。
    • 谁该取用新type的成员?这决定了如何安排函数是public,protected还是private,以及那些函数/类是friend。
    • 什么是新type的“未声明接口”?他对效率、异常安全性以及资源运用提供何种保证?
    • 你的新type有多么一般化?如果你并不是为了定义一个新type而是要定义一整个type家族,那么应该定义一个新的class template。
    • 你是否真的需要一个新的type?如果你只是为了给base class添加某些功能,那么定义一个或多个non-member 函数或template,更好。

    设计class是一件非常具有挑战的事情,所以如果你希望设计一个class,最好像设计一个type一样,把各种问题都思考一遍。

    3. 宁以pass by reference to const替换pass by value

    在缺省情况下C++总是以pass-by-value的方式传递对象至函数,实际上,就是传递复件,而这些复件都是由copy构造函数产生的,这可能使得pass-by-value称为昂贵而耗时的操作。

    问题产生

    class Person() {
    public:
        Person();
        virtual ~Person();
        ...
    private:
        std::string name;
        std::string address;
    };
    class Student : public Person {
    public:
        Student();
        ~Student();
        ...
    private:
        std::string schoolName;
        std::string schoolAddress;
    };
    
    // in main:
    bool checkStudent(Student s);
    Student one;
    bool whoh = checkStudent(one);

    在checkStudent调用时,发生了什么?

    这显然是一个pass-by-value的函数,也就意味着一定会出现copy构造函数,对于此函数而言,参数的传递成本是“一次student copy构造函数调用,加上一次student析构函数调用”。不仅如此,student还继承于person,所以还有一次person构造函数和person析构函数,以及student里面的两个string对象,和person里面的两个string对象,总而言之,总体成本就是“六次构造函数和六次析构函数!”多么可怕的开销!

    问题解决

    解决这个问题非常的简单。只要使用pass by reference to const就可以了。因为by reference不会导致构造函数和析构函数的使用,节省了大量开销,同时因为是const,也保证了参数不会再函数内被更改。

    bool checkStudent(const Student &s);

    问题产生2

    pass-by-value还会导致对象切割问题(slicing)。当一个dereived class对象以by value方式传递并被视为一个base class对象时,bass class的copy构造函数就会被调用,而“造成此对象的行为像个derived class对象”的那些特化性质全部被切割掉,只剩下base class对象。这并不奇怪。

    class Window {
    public:
        ...
        std::string name() const;
        virtual void display() const;
    };
    class SpecialWindow {
    public:
        ..
        virtual void display() const;
    };
    ....
    // in main:
    void print(Window w) {
        cout << w.name();
        w.display();
    }

    当你把一个SpecialWindow对象传递给void print(Window w)函数时,就像前文所说的,会使得SpecialWindow的特化性质全部被切割掉,于是乎,你本想着输出SpecialWindow的特别内容结果只输出了Window内容。

    问题解决2

    解决这个问题仍然是使用reference。由此来引发动态绑定,从而使用SpecialWindow的display。

    void print(const Window& w) {
        cout << w.name();
        w.display();
    }

    总结

    窥视C++编译器的底层就会发现,实际上reference就是以指针实现出来了,pass by reference通常意味着真正传递的是指针。因此,如果你有个对象属于内置类型(如int),pass-by-value通常来说效率会更好。这对于STL的迭代器和函数对象同样适用。因为习惯上他们都是设计为pass-by-value。迭代器和函数对象的实践者都有责任看看他们是否高效且不受切割问题。

    有人认为,所有小型type对象都应该适用pass-by-value,甚至对于用户定义的class。实际上是不准确的。第一,对象小,并不意味着他的copy构造函数开销小;2)即使是小型对象并不拥有昂贵的copy构造函数,也可能存在效率上的问题,例如某些编译器不愿意把只由一个double组成的对象放进缓存器,但如果你使用reference,编译器一定会把指针(就是reference的实现体)放进缓存器。3)作为用户自定义类型,其大小是很容易被改变的。随着不断的使用,对象可能会越来越大。

    一般而言,合理假设“pass-by-value更合适”的唯一对象就是内置类型和STL的迭代器和函数对象,其他的最好还是使用by reference。

    4. 必须返回对象时,别妄想返回其reference

    前面我们讨论了pass-by-reference可以提高效率,于是乎,有的人就开始坚定地使用reference,甚至开始传递一些refereence指向其实并不存在的对象。

    问题产生

    此问题产生的理由非常的简单,就是作者希望可以节省开销提高效率。并因此而产生大量的错误。

    class Rational {
    public:
        Rational(int num1 = 0, int num2 = 1);
        ...
    private:
        int n1, n2;
        friend Rational& operator*(const Rational& lhs, const Rational& rhs);

    operator*试图返回一个引用,并为此寻找合乎逻辑的实现代码。

    尝试1:直接返回

    Rational& operator*(const Rational& lhs, const Rational& rhs) {
        Rational result(lhs.n1 * rhs.n1, lhs.n2 * rhs.n2);
        return result;
    }

    问题显然。因为result是一个on the stack对象,在作用域结束后,对象就被销毁,于是返回了一个没有指向的reference。尝试失败!

    尝试2:返回on the heap对象

    Rational& operator*(const Rational& lhs, const Rational& rhs) {
        Rational* result = new Rational(lhs.n1 * rhs.n1, lhs.n2 * rhs.n2);
        return *result;
    }

    此代码乍看起来似乎没什么问题,但其实隐含杀机。你在函数中动态申请了一块内存放这个变量,这也就意味着你必须管理这块资源(见前文:资源管理)。然而管理这块资源几乎不可能,因为你不可能希望在main函数里一直有一个变量在守着这块资源并且及时的delete掉。而且当大量使用*操作符时,管理大量的资源根本不可能!就算你有这样的毅力这么管理,也不可能希望有用户愿意做这样的体力活。

    尝试3:使用static变量

    Rational& operator*(const Rational& lhs, const Rational& rhs) {
        static Rational result(lhs.n1 * rhs.n1, lhs.n2 * rhs.n2);
        return result;
    }

    这代码乍看起好像又要成功了?!其实并没有。问题出现的十分隐蔽:

    bool operator == (const Rational& lhs, const Rational& rhs);
    
    if ((a*b) == (c*d)) {
        ...
    } else {
        ...
    }

    问题就出在等号操作,等号永远会成立!因为,在operator == 被调用前,已有两个操作符被调用,每一个都返回操作函数内部的static对象,而这两个对象实际上就是一个对象!(对于调用端来说,确实如此!)于是乎,你根本就没有完成*操作符所应该具备的功能。

    问题解决

    问题的解决就是,别挣扎了!使用pass-by-value吧。不就是一点构造函数和析构函数的开销嘛。比起大量的错误和内存的管理。这点开销还是很划算的。

    class Rational {
    public:
        Rational(int num1 = 0, int num2 = 1);
        ...
    private:
        int n1, n2;
        friend Rational operator*(const Rational& lhs, const Rational& rhs) {
            return Rational(lhs.n1*rhs.n1, rhs.n2*rhs.n2);
        }

    5. 将成员变量声明为private

    在我们最初学习C++ OOP时就有一天准则,成员变量总是要声明为private。本节我们来讨论为何成员变量要被声明为private。

    • 理由一:语法一致性。
      因为成员变量不是public,客户唯一能够访问对象的办法就是通过成员函数。如果public接口内的每一样东西都是函数,客户就不用纠结调用他时是否需要使用小括号。如此便能省下大量的时间。
    • 理由二:使用函数可以让你对成员变量的处理有更准确的控制。
      如果成员变量是public,那么每个人都可以对他进行读写,但如果你以函数取得或设定其值,就可以实现“不准访问”,“只读访问”,“读写访问”等访问控制。
      如以下代码:
    class AccessLevel {
    private:
        int noAccess;
        int ReadOnly;
        int WriteOnly;
        int readWrite;
    public:
        // ...
        int getReadOnly() {
            return ReadOnly;
        }
        void setWriteOnly(int i) {
            WriteOnly = i;
        }
        void setreadWrite(int i) {
            readWrite = i;
        }
        int readreadWrite() {
            return readWrite;
        }
    };

    如此精细地对各个数据成员进行访问限制是有必要的。

    • 理由三:封装!
      这是最有说服力的理由了!C++ OOP其中最重要的一条性质就是封装性!将数据成员封装在接口的后面,可以为“所有可能的实现”提供弹性。
      封装的重要性比我们最初见到它时更重要。如果我们对客户隐藏成员变量,就可以确保class的约束条件受到维护,因为只有成员函数可以影响他们。public意味着不封装,而几乎可以说不封装意味着不可改变,特别是对被广泛使用的class而言。被广泛使用的class是最需要封装的一个族群,因为他们能够从“改采用一个教佳实现版本”中获益。

    我们继续来讨论protected的封装性。

    一般人会认为protected比public更具有封装性。其实不然。更准确的判断方法是:某些东西的封装性与“当其内容改变时可能造成的代码破坏量”成反比。所谓改变,也许是从class中移除他。于是乎,我们可以进行以下分析。对于public的成员变量,如果我们移除他,意味着我们要破坏所有使用它的客户代码。(破坏量很大吧?)而对于protected的成员变量呢,如果我们移除它,意味着要破坏所有derived class(破坏量也很大吧?)因此protected和public的封装性其实是一样的。这也就意味着,一旦我们决定把某个成员变量声明为public或protected,就很难改变某个成员变量所涉及的一切。

    结论就是,其实只有两种访问权限:private(实现封装)和其他(不实现封装)

    6. 宁以non-member、non-friend替换member函数

    面向对象守则要求,数据以及操作数据的那些函数应该被捆绑在一起,这意味着它建议所有操作数据成员的函数都应该是member函数。然而事实上是如此吗?

    问题产生

    假设我们希望写一个类来描述网页:

    class WebBrowser {
    public:
        ...
        void clearCache();
        void clearHistory();
        void removeCookies();
        ...
        // 用户希望有一个函数能够清楚所有信息
        // 问题是,该函数是否应该声明为member?
        void clearEverything();
    };
    // 也可以声明为non-member
    void clearEverything(WebBrowser &web) {
    ...
    }

    那么哪种选择更好呢?

    问题解决

    根据面向对象守则要求,声明为member函数应该是更好的选择。然而,这是对面向对象真实意义的一个误解。面向对象要求数据应该尽可能被封装,然而与直观相反地,member函数clearEverything带来的封装性比non-member函数的低。此外,提供non-member函数可允许对WebBrowser相关机能有更大的包裹弹性,从而最终导致较低的编译相依度,增加WebBrowser的可衍生性。以下我们给出理由。

    • 封装性。愈多的东西被封装,越少人可以按到它,那么我们就有越大的弹性去改变它,而我们的改变只会影响看到改变的那些人和事物。这就是我们推崇封装性的原因:它使我们能够改变事物而只影响有限客户。
    • 考虑对象内数据。越少代码可以看到数据,越多的数据可被封装,而我们也就越能自动地改变对象数据。越多的函数可以访问数据成员,数据的封装性就越差!

    因此,因为non-member non-friend函数不能直接改变数据成员,因此他就可以最大限度的实现封装

    解答优化

    在C++中,最自然的做法,是让clearEverything称为一个non-member函数并且位于WebBrowser所在的同一个namespace内:

    namespace WebBrowserStuff {
        class WebBrowser {...};
        void clearEverything(WebBroswer &web);
        ...
    }

    namespace和class是不用的!前者可以跨越多个源码文件而后者不能,这很重要!

    像clearEverything这样的函数就是便利函数,虽然没有对WebBrowser有特殊的访问权限,但可以极大的便利客户。而实际上,我们会补充大量的类似的便利函数,并且他们可能分属于不同的模块,于是我们便采用把不同模块便利函数写于不同的头文件中,但他们都隶属于同一个命名空间:

    #include "webbrowser.h" 提供class声明本身,以及其中核心机能
    namespace WebBrowserStuff {
    class WebBroser { ... };
        ...  // 核心机能,几乎所有用户都需要的non-member便利函数
    }
    
    // 头文件 “webbrowserbookmarks.h" 
    // 与标签相关
    namespace WebBrowserStuff {
        ... // 与标签相关的便利函数
    }
    // 头文件 ”webbrowsercookies.h"
    namespace WebBrowserStuff{
        ... // 与cookie相关的便利函数
    }
    ...

    注意这是C++标准程序库的组织方式。标准程序库中并不是拥有单一、整体、庞大的

    7. 若所有参数皆需类型转换,请为此采用non-member函数

    令class支持隐式类型转换通常是个糟糕的注意。当然也有例外,例如你在建立数值类型时。

    问题产生

    假设我们需要设计一个有理数类:

    class Rational {
    public:
        Rational(int numerator = 0, int denominator = 1);
        int numerator() const;
        int denominator() const;
    private:
        ...
    };
    class Rational {
    public:
        ...
        const Rational operator*(const Rational& rhs) const;
    };
    
    // 于是乎可以轻松实现乘法
    Rational oneEighth(1, 8);
    Rational oneHalf(1, 2);
    Rational result = oneHalf * oneEighth;  //  没问题
    result = result * oneEighth;  //  没问题

    到目前为止还没有实现致命问题,然而:

    result = oneHalf * 2;  // ok!
    result = 2 * oneHalf;  // error!
    // result = 2.operator*(oneHalf);  of course wrong!

    第一个式子能够成立,是因为实现了隐式类型转换。编译器知道你在传递一个int,而函数需要的是rational,但它也知道只要调用Rational构造函数并赋予你所提供的int,就可以变出一个适当的rational出来,于是就这么做了。相当于:

    const Rational temp(2);
    result = oneHalf * temp;

    当然这只涉及non-explicit构造函数,才能这么做。如果是explicit构造函数,这个语句无法通过编译。

    问题解决

    result = oneHalf * 2;  // ok!
    result = 2 * oneHalf;  // error!

    只有当参数被列于参数列内,这个参数才是隐式类型转换的合格参与者。地位相当于“被调用之成员函数所隶属的那个对象”-即this对象-那个隐喻参数,绝不是隐式转换的合格参与者。这就是为什么语句1能够通过编译而语句2不可以。

    于是,方法就是,让operator*称为一个non-member函数,允许编译器在每一实参身上执行隐式类型转换。

    const Rational operator*(const Rational& lhs, const Rational& rhs) {
        ...
    }
    Rational oneFourth(1, 4);
    Rational result = oneFourth * 2; // right!
    result = 2 * oneFourth; // right!

    补充思考:
    是否应该把该operator*声明为friend?

    答案是否定的!请注意,member的反面不是friend,而是non-member!在此代码中,operator*完全可以借由rational的public接口完成任务,于是便不必把他声明为friend。无论何时,如果可以避免friend函数就应该避免。

    总结:
    如果你需要为某个函数的所有参数(包括this)进行类型转换,那么这个函数必须是个non-member。

    8. 考虑如何写出特化的swap函数

    swap作为STL的一部分,而后成为异常安全性编程的脊柱,以及用来处理自我赋值可能性的一个常见机制。由于此函数如此有用,也意味着他具有非凡哥的复杂度。本节谈论这些复杂度以及相应处理。

    问题产生1

    namespace std {
        template<typename T>
        void swap(T &a, T &b) {
        T temp(a);
        a = b;
        b = temp;
        }
    }

    这是标准程序库提供的swap算法。非常地简单,只要T有copying相关操作即可。然而这个算法对于有些情况却显得不那么高效。例如,在处理“以指针指向一个对象,内含真正数据”的那种类型。(这种设计的常见形式是所谓“pimpl手法:pointer to implemention)

    class WidgetImpl {  //  实现细节不重要。
    public:             //  针对Widget设计的class
        ...
    private:
        int a, b, c;
        std::vector<double> v;
        ...
    };
    class Widget {
    public:
        Widget(const Widget& rhs);
        Widget& operator=(const Widget& rhs) {
        ...
        *pImpl = *(rhs.pImpl);
        ...
        }
    private:
        WidgetImpl* pImpl;
    };

    对此类调用算法库的swap就会非常低效。因为他总共要复制三个Widget和三个WidgetImpl对象!而事实上,只需要改变指针的指向就可以了。

    问题解决1

    我们可能尝试用以下方法解决,让swap针对Widget特化。

    尝试一:

    namespace std {
        template<> // 表示他是std::swap的一个全特化
        void swap<Widget>(Widget &a, Widget &b) {
        swap(a.pImpl, b.pImpl);
        }
    }

    通常来说,我们是不能够改变std命名空间内的任何东西,但可以(被允许)为标准template制造特化版本的。

    但实际上这个是无法通过编译的。因为他企图调用class的私有成员。
    所以更合理的做法,是令他调用成员函数。

    解法:

    class Widget {
    public:
        ...
        void swap(Widget& other) {
        using std::swap;
        swap(pImpl, other.pInmpl);
        }
        ...
    };
    private:
        WidgetImpl* pImpl;
    };
    
    namespace std {
        template<>
        void swap<Widget>(Widget &a,
                            Widget &b) {
            a.swap(b);
        }
    }

    这个做法不仅能够通过编译,而且与STL容器有一致性。

    问题产生2

    假设Widget和WidgetImpl都是class template而非class,也许我们可以试试把WidgetImpl内的数据类型加以参数化:

    template<typename T>
    class WidgetImpl {...};
    template<typename T>
    class Widget {...};
    // 在Widget里面放入swap成员函数就像以往一样简单
    // 但在写特化std::swap时出现了问题
    namespace std {
        template<typename T>
        void swap< Widget<T> > (Widget<T>& a, Widget<T>& b) {
        a.swap(b);
        }
    }

    以上特化swap其实有问题的。我们企图偏特化这个function template,但C++只允许对class template偏特化。(随后会介绍全特化和偏特化)。当你尝试偏特化一个function template时,更常见的做法是添加重载函数:

    namespace std {
        template<typename T>
        void swap(Widget<T>& a, Widget<T>& b) {
        a.swap(b);
        }
    }

    但实际上,这也是不行的!因为std是个特殊的命名空间,其管理规则比较特殊,客户可以全特化std内的template,但不可以添加新的template到std里面。

    问题解决2

    解决这个问题的方法就是,声明一个non-member swap让它调用member swap,但不在将那个non-member swap声明为std::swap特化版或重载版本。

    namespace WidgetStuff {
        template<typename T>
        class WidgetImpl {...};
        template<typename T>
        class Widget {...};
        ...
        template<typename T>
        void swap(Widget<T>& a, Widget<T>& b) {
        a.swap(b);
        }
    }

    现在,任何时候如果打算置换两个Widget对象,因而调用swap,C++的名称查找法则都会找到WidgetStuff内的Widget专属版本。

    这个做法对class和class template都行得通。如果你想让你的”class“专属版swap在尽可能多的语境下被调用,你需要同时在该class所在命名空间内写一个non-member版本以及一个std::swap特化版本。

    另外,如果没有像上面那样额外使用某个命名空间,上述每件事情仍然使用。但你又何必再global命名空间里面塞这么多东西呢?

    补充思考

    目前提到得都是和swap编写有关的。现在我们换位思考,从客户观点看看问题。假设我们需要写一个function template:

    template<typename T>
    void doSomething(T& obj1, T& obj2) {
    ...
    swap(obj1, obj2);
    ...
    }

    此时swap是调用哪个版本呢?我们当然希望是调用T专属版本,并且在该版本不存在的情况下,调用std内的一般化版本。

    template<typename T>
    void doSomething(T& obj1, T& obj2) {
    using std::swap;
    ...
    swap(obj1, obj2); // 为T类型对象调用最佳swap版本。
    ...
    }

    C++名称查找法则确保将找到global作用域或T所在命名空间内的任何T专属的swap。如果T是Widget并位于命名空间WidgetStuff内,编译器会使用”实参取决之查找规则“找出WidgetStuff内的swap。如果没有T专属之swap存在,编译器就使用std内的swap。

    以下是我设计的一个不大合乎逻辑的代码,但证明了上述说法是合理的。

    #include <iostream>
    using namespace std;
    
    namespace test {
        class trys {
        public:
            void swap(trys &one, trys &two) {
                cout << "yes!" << endl;
            }
        };
        void swap(trys &one, trys &two) {
            cout << "yes!" << endl;
        }
    }
    int main(int argc, const char * argv[]) {
        // insert code here...
        test::trys a;
        int b = 12;
        {
            using std::swap;
            swap(b, b);
            swap(a, a);
        }
        return 0;
    }
    /*
    yes!
    Program ended with exit code: 0
    */
    

    总结:

    如果swap缺省实现版的效率不足,(那几乎意味着你的class或template使用了某种pimpl手法),可以试着做以下事情:

    • 提供一个public swap成员函数,让他高效地置换你的类型的两个对象值。
    • 在你的class或template所在的命名空间内提供一个non-member swap,并命它调用上述swap成员函数。
    • 如果你在编写一个class,并为你的class特化std::swap,并令他调用你的swap成员函数。

    最后,如果你调用swap,请确保包含一个using声明式。

    补充内容:(全特化和偏特化)

    模板为什么要特化,因为编译器认为,对于特定的类型,如果你能对某一功能更好的实现,那么就该听你的。

    模板分为类模板与函数模板,特化分为全特化与偏特化。全特化就是限定死模板实现的具体类型,偏特化就是如果这个模板有多个类型,那么只限定其中的一部分。

    先看类模板:

    template<typename T1, typename T2>  
    class Test  
    {  
    public:  
        Test(T1 i,T2 j):a(i),b(j){cout<<"模板类"<<endl;}  
    private:  
        T1 a;  
        T2 b;  
    };  
    
    template<>  
    class Test<int , char>  
    {  
    public:  
        Test(int i, char j):a(i),b(j){cout<<"全特化"<<endl;}  
    private:  
        int a;  
        char b;  
    };  
    
    template <typename T2>  
    class Test<char, T2>  
    {  
    public:  
        Test(char i, T2 j):a(i),b(j){cout<<"偏特化"<<endl;}  
    private:  
        char a;  
        T2 b;  
    };  

    那么下面3句依次调用类模板、全特化与偏特化:

    Test<double , double> t1(0.1,0.2);  
    Test<int , char> t2(1,'A');  
    Test<char, bool> t3('A',true);  

    而对于函数模板,却只有全特化,不能偏特化:

    //模板函数  
    template<typename T1, typename T2>  
    void fun(T1 a , T2 b)  
    {  
        cout<<"模板函数"<<endl;  
    }  
    
    //全特化  
    template<>  
    void fun<int ,char >(int a, char b)  
    {  
        cout<<"全特化"<<endl;  
    }  
    
    //函数不存在偏特化:下面的代码是错误的  
    /* 
    template<typename T2> 
    void fun<char,T2>(char a, T2 b) 
    { 
        cout<<"偏特化"<<endl; 
    } 
    */  

    至于为什么函数不能偏特化,似乎不是因为语言实现不了,而是因为偏特化的功能可以通过函数的重载完成。
    (摘自:模板的全特化与偏特化

    展开全文
    stary_yan 2016-04-28 17:41:01
  • qq_42449963 2020-05-20 13:58:47
  • IT_yulei_3g 2008-04-22 22:43:00
  • Longyu_wlz 2019-07-15 06:30:14
  • HKD_WCY 2020-04-18 19:43:51
  • qq_35082030 2020-07-17 19:08:00
  • weixin_39949673 2020-12-20 01:09:42
  • m0_46653702 2021-04-05 16:48:12
  • weixin_36278817 2021-01-29 04:12:40
  • weixin_42515045 2021-02-12 10:24:55
  • github_30662571 2017-06-08 10:53:16
  • u010246197 2016-10-25 17:00:37
  • superervin 2015-03-21 01:29:16
  • lyhhj 2017-06-19 18:42:34
  • huangjianfeng21 2019-06-18 18:21:39
  • zjkC050818 2017-10-18 22:15:34
  • kaylc 2011-02-17 21:30:00
  • m0_37700275 2018-01-16 14:40:28
  • xdh1996 2020-07-16 20:15:34
  • a78270528 2018-04-20 15:40:06
  • jsetc_Vera 2016-10-05 23:21:01
  • u012039040 2020-03-25 20:45:49
  • geek_liyang 2018-05-23 16:39:51
  • dagouaofei 2010-01-08 11:16:00
  • u012889902 2020-06-23 12:33:02
  • s003603u 2016-12-05 11:21:58
  • FadeFarAway 2017-01-22 16:32:29

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 128,898
精华内容 51,559
关键字:

下面对封装性描述错误的是