1-1-1 Java基础知识

文章目录

说明:本文是校招复习系列文章,参考文献做统一说明!
整体目录详见:校招复习目录

1. 面向对象和面向过程的区别

My:

面向对象是模块化的编程,以对象/类为根本

面向过程则是顺序逻辑编程,

AS:

2. Java 语言有哪些特点?

My:

移植性好,表现在跨平台

简单易懂,没有指针这种物种

历史悠久,生态完善

3. 关于 JVM, JDK, 和 JRE 最详细通俗的解答

My:

JVM, 即Java虚拟机,是Java源代码执行的平台

JDK,java环境

JRE,不知道

4. Oracle JDK 和 OpenJDK 的对比

My:

不知道,猜测,Oracle是官方的,openjdk应该是添加了某些特性

5. Java 和 C++的区别?

6. 什么是 Java 程序的主类 应用程序和小程序的主类有何不同?

7. Java 应用程序与小程序之间有哪些差别?

8. 字符型常量和字符串常量的区别?

字符型只有一个,字符串数量大于一

字符型本质为整数型,是ASCII码,可以运算;字符串则是一个String,地址引用

9. 构造器 Constructor 是否可被 override?

不可以重写

10. 重载和重写的区别

重载是同名不同参,

重写是子类中对父类的方法改写,

11. Java 面向对象编程三大特性: 封装 继承 多态

封装,对外封装

继承,子类继承父类(必须继承全部,不可以选择性继承),子类可以有更多的属性和方法

多态,方法看运行

12. String, StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?

String,普通字符串类,不可变类,使用final关键字,常量,线程安全,

StringBuffer, 同步锁,线程安全的,效率低的,字符串类

StringBuilder, 没有加sychronized锁关键字,非同步,效率高,

abstract class AbstractStringBuilder implements Appendable,CharSequence{
    char[] value;
    int count;
    AbstactStringBuilder(int capacity){
        value = new char[capacity];
    }
}

13. 自动装箱与拆箱

包装类型与基本数据类型之间的自动转化。

14. 在一个静态方法内调用一个非静态成员为什么是非法的?

木知啊

15. 在 Java 中定义一个不做事且没有参数的构造方法的作用

wtf

16. import java 和 javax 有什么区别?

历史遗留问题

17. 接口和抽象类的区别是什么?

接口中只能有抽象方法,没有具体的实现,

抽象类中既有抽象方法,也可有具体的方法,

抽象方法设计的意义就是为了重写,所以不可以使用private修饰,

18. 成员变量与局部变量的区别有哪些?

1.语法上:成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被 public, private, static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。

2.从变量在内存中的存储方式来看:如果成员变量是使用static修饰的,那么这个成员变量是属于类的,如果没有使用static修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存

3.从变量在内存中的生存时间上看: 成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。

19. 创建一个对象用什么运算符? 对象实体与对象引用有何不同?

1.new运算符?

2.对象实体,即实例,

  • new 创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)
  • 一个对象引用可以指向 0 个或 1 个对象(一根绳子可以不系气球,也可以系一个气球);
  • 一个对象可以有 n 个引用指向它(可以用 n 条绳子系住一个气球)。

20. 什么是方法的返回值?返回值在类的方法里的作用是什么?

  1. 是方法调用后最终产生的结果;
  2. 作用是规定了返回结果的格式,接收结果

21. 一个类的构造方法的作用是什么? 若一个类没有声明构造方法,该程序能正确执行吗? 为什么?

用于类的初始化,

可以,默认有一个无参构造函数

22. 构造方法有哪些特性?

可以重载,不可以重写

  1. 名字类名相同;
  2. 没有返回值,也不是void
  3. 生成类的对象时自动执行,无需调用???

23. 静态方法和实例方法有何不同

  1. 静态方法可以直接用类名调用,不需要实例对象调用(也可以,只是不用)
  2. 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制(实例方法可以访问静态和实例成员,既然类都能调用,那当然实例也可以调用)
  3. 之所以不允许静态方法访问实例成员变量,是因为实例成员变量是属于某个对象的,而静态方法在执行时,并不一定存在对象。同样,因为实例方法可以访问实例成员变量,如果允许静态方法调用实例方法,将间接地允许它使用实例成员变量,所以它也不能调用实例方法。基于同样的道理,静态方法中也不能使用关键字this

24. 对象的相等与指向他们的引用相等,两者有什么不同?

  1. 普通的对象相等指的是,equals()方法执行的结果是相等的,即对象里面的各个属性值是相同的
  2. 引用相等则指的是,内存地址,即是不是同一个对象

25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?

先初始化父类,然后继承父类的特性,

26. == 与 equals(重要)

  1. == 作用是判断两个对象的地址是不是相等。即,判断两个对象是不试同一个对象。可以用于普通数据类型和引用类型的比较,普通数据就是对值进行比较,而引用类型是对引用地址(内存地址,不是哈希值)进行比较

  2. equals() 方法分为两种情况:

    类重写了equals方法,比较的是equals()方法中所写的属性,即比较的是对象的内容;

    类没有重写,使用默认的“equals()”方法,等价于“==”方法,比较的就只是内存地址。假如有new了两个实例对象,那么使用默认的equals()进行判断时,一定是不相等(即便两个实例的所有属性值相同),因为两个内存地址不相同,所以一般都会重新写equals()方法。

    • String类型默认已经重写了equals()方法,所以比较String类型的时候,equals()方法都是比较对象的内容,内容一致是必定为true;==则需要看该String方法的地址值是否相同,如果两个都是new出来的则为false,反之,从常量池中选出的,否则为ture。因为不可变类的问题。
    • 普通对象就是根据规则,比较内容(重写equals)或者比较内存地址值(==)。

    下面是示例:

        private static class Person {
            int age;
            String name;
    
            public Person(String name, int age) {
                this.name = name;
                this.age = age;
            }
    
            public String toString() {
                return name + " - " +age;
            }
    
            /** 
             * @desc 覆盖equals方法 
             */  
            @Override
            public boolean equals(Object obj){  
                if(obj == null){  
                    return false;  
                }  
                  
                //如果是同一个对象返回true,反之返回false  
                if(this == obj){  
                    return true;  
                }  
                  
                //判断是否类型相同  
                if(this.getClass() != obj.getClass()){  
                    return false;  
                }  
                  
                Person person = (Person)obj;  
                return name.equals(person.name) && age==person.age;  
            } 
        }
    }
    

    经典题:

    public class test1 {
        public static void main(String[] args) {
            String a = new String("ab"); // a 为一个引用
            String b = new String("ab"); // b为另一个引用,对象的内容一样
            String aa = "ab"; // 放在常量池中
            String bb = "ab"; // 从常量池中查找
            if (aa == bb) // true
                System.out.println("aa==bb");
            
            if (a == b) // false,非同一对象,内存地址不一样,尽管哈希值一样
                System.out.println("a==b");
            if (a.equals(b)) // true,哈希值相同
                System.out.println("aEQb");
            if (a == aa) // false,非同一对象,内存地址不一样,尽管哈希值一样,equals()相等
                System.out.println("a==b");
            
            
            if (42 == 42.0) { // true
                System.out.println("true");
            }
        }
    }
    

27. hashCode 与 equals (重要)

哈希详细内容在容器中再介绍。

  • hashCode()介绍

**hashCode() 的作用是获取哈希码,也称为散列码。返回一个int整数,作用是确定对象在哈希表中的索引位置。**散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)

**注意:**hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。 虽然,每个Java类都包含hashCode() 函数。

但是,仅仅当创建某个**“类的散列表”**时,该类的hashCode() 才有用(确定该类的每一个对象在散列表中的位置);其它情况下(创建类的单个对象,或者创建类的对象数组等等),类的hashCode() 没有作用。

类的散列表指的是:Java集合中本质是散列表的类,如HashMap,Hashtable,HashSet。

hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。


  • 为什么要有 hashCode?

我们先以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode: 当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与该位置其他已经加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现。

但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。

如果两个对象相等,则 hashcode 一定也是相同的;两个对象有相同的 hashcode 值,它们也不一定是相等的


  • hashCode()与 equals()的关系

我们以“类的用途”来将“hashCode() 和 equals()的关系”分2种情况来说明:

第一种 不会创建“类对应的散列表” 。即我们不会在HashSet, Hashtable, HashMap等这些本质是散列表的数据结构中,用到该类。例如,不会创建该类的HashSet集合。在这种情况下,该类的“hashCode() 和 equals() ”没有半毛钱关系的!,此时equals() 用来比较该类的两个对象是否相等。而hashCode() 则根本没有任何作用。

第二种 会创建“类对应的散列表” 。即在HashSet, Hashtable, HashMap等这些本质是散列表的数据结构中用到该类。在这种情况下,该类的“hashCode() 和 equals() ”是有关系的:

如果两个对象相等,那么它们的hashCode()值一定相同。(相等是指通过equals()比较两个对象时返回true)

如果两个对象hashCode()相等,它们并不一定相等。

在散列表中,hashCode()相等—>即两个键值对的哈希值相等而哈希值相等,并不一定能得出键值对相等。补充说一句:“两个不同的键值对,哈希值相等”,这就是哈希冲突。哈希冲突使用什么方法解决呢?(容器中涉及)

在这种情况下。若要判断两个对象是否相等,除了要覆盖equals()之外,也要覆盖hashCode()函数。否则,equals()无效。遵守一致性时,两个必须同时覆盖。

hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。

return nameHash ^ age;中的异或运算是二进制的运算。相同消除为0,不同2进制下相加运算。

注意:官方的hashCode 重写(容器部分涉及)

@Override
public int hashCode(){  
    int nameHash =  name.hashCode();
    return nameHash ^ age;
}

@Override
public boolean equals(Object obj){  
    if(obj == null){  
        return false;  
    }  

    //如果是同一个对象返回true,反之返回false  
    if(this == obj){  
        return true;  
    }  

    //判断是否类型相同  
    if(this.getClass() != obj.getClass()){  
        return false;  
    }  

    Person person = (Person)obj;  
    return name.equals(person.name) && age==person.age; 
    //return name == p.name && age == p.age; 也可
} 

28. 为什么 Java 中只有值传递?

涉及到了深拷贝和浅拷贝

  • 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)
  • 一个方法可以改变一个对象参数的状态。 (包括数组和普通对象)
  • 如果只是对对象的值进行普通的交换,则不会有影响。
  	public static void main(String[] args) {
        
        int x = 1;
        new HashCodeDemo().editNum(x);
        System.out.println(x); //仍旧为1,没有被该变
      
        People people1 = new People("e", 10);
        new HashCodeDemo().editName(people1);
        System.out.println(people1); // people["n", 10],改变了
      
     	Student s1 = new Student("小张");
        Student s2 = new Student("小李");
        Test.swap(s1, s2);
        System.out.println("s1:" + s1.getName()); //不变,仍旧是小张
        System.out.println("s2:" + s2.getName());//不变,仍旧是小李
  	}

    public void editName(People p){
        p.setName("n");
    }

    public void editNum(int a){
        a = 0;
    }

	public static void swap(Student x, Student y) {
        Student temp = x;
        x = y;
        y = temp;
        System.out.println("x:" + x.getName()); //交换值,但是不会影响之前的对象值
        System.out.println("y:" + y.getName());
    }

29. 简述线程、程序、进程的基本概念。以及他们之间关系是什么?

线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

程序是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。

进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如 CPU 时间,内存空间,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。

线线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。

30. 线程有哪些基本状态?(重点)

等待,运行,阻塞

Java线程的状态

初始,运行,阻塞,等待,超市等待,终止。

Java线程状态变迁

操作系统隐藏 Java 虚拟机(JVM)中的 READY 和 RUNNING 状态,它只能看到 RUNNABLE 状态,所以 Java 系统一般将这两个状态统称为 RUNNABLE(运行中) 状态 。

当线程执行 wait()方法之后,线程进入 **WAITING(等待)**状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 TIME_WAITING(超时等待) 状态相当于在等待状态的基础上增加了超时限制,比如通过 sleep(long millis)方法或 wait(long millis)方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 BLOCKED(阻塞) 状态。线程在执行 Runnable 的run()方法之后将会进入到 TERMINATED(终止) 状态。


注意:

Thread.yield():线程屈服、礼让,暂停当前线程;

Thread.join():则是进来的线程排队,遵守先来后到的规则

就绪(Ready)和运行(Running )的区别;

阻塞一般是由于锁;

31. 关于 final 关键字的一些总结

可以用于变量,方法,类的修饰

  1. 对于一个 final 变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。(什么叫指向另一个对象?)
  2. 使用 final 方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的 Java 实现版本中,会将 final 方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的 Java 版本已经不需要使用 final 方法进行这些优化了)。类中所有的 private 方法都隐式地指定为 final。
  3. 当用 final 修饰一个类时,表明这个类不能被继承。final 类中的所有成员方法都会被隐式地指定为 final 方法。

32. Java 中的异常处理

Java 异常类层次结构图

Java异常类层次结构图

在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable: 有两个重要的子类:Exception(异常)Error(错误)

Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java 虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。

这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如 Java 虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java 中,错误通过 Error 的子类描述。

Exception(异常):是程序本身可以处理的异常。Exception 类有一个重要的子类 RuntimeException。RuntimeException 异常由 Java 虚拟机抛出。NullPointerException(要访问的变量没有引用任何对象时,抛出该异常)、ArithmeticException(算术运算异常,一个整数除以 0 时,抛出该异常)和 ArrayIndexOutOfBoundsException (下标越界异常)。

注意:异常和错误的区别:异常能被程序本身处理,错误是无法处理。


Throwable 类常用方法

  • public string getMessage():返回异常发生时的简要描述
  • public string toString():返回异常发生时的详细信息
  • public string getLocalizedMessage():返回异常对象的本地化信息。使用 Throwable 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 getMessage()返回的结果相同
  • public void printStackTrace():在控制台上打印 Throwable 对象封装的异常信息

异常处理总结

  • try 块: 用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块
  • catch 块: 用于处理 try 捕获到的异常。
  • finally 块: 无论是否捕获或处理异常,finally 块里的语句都会被执行。当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行

在以下 4 种特殊情况下,finally 块不会被执行:

  1. 在 finally 语句块第一行发生了异常。
  2. 在前面的代码中用了 System.exit(int)已退出程序。 exit 是带参函数 ;若该语句在异常语句之后,finally 会执行
  3. 程序所在的线程死亡。
  4. 关闭 CPU。

注意: 1.如果try语句里有return,返回的是try语句块中变量值。 2.当 try 语句和 finally 语句中都有 return 语句时,在方法返回之前,finally 语句的内容将被执行,并且 finally 语句的返回值将会覆盖原始的返回值。

33. Java 序列化中如果有些字段不想进行序列化,怎么办?

implements Serializable

序列化:我们把变量从内存中变成可存储或传输的过程称之为序列化(把对象转换为字节序列的过程/ 将数据结构或对象转换成二进制串的过程 )。二进制串在 Java 里面所指的是 byte[],byte 是 Java 的 8 中原生数据类型之一(Primitive data types)

序列化作用,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。

对于不想进行序列化的变量,使用 transient 关键字修饰。

transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和方法。

34. 获取用键盘输入常用的两种方法

方法 1:通过 Scanner

Scanner input = new Scanner(System.in);
String s  = input.nextLine();// input.nextInt();
input.close();

方法 2:通过 BufferedReader

BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String s = input.readLine();

35. Java 中 IO 流

Java 中 IO 流分为几种?

  • 按照流的流向分,可以分为输入流和输出流;
  • 按照操作单元划分,可以划分为字节流和字符流;
  • 按照流的角色划分为节点流和处理流。

Java Io 流共涉及 40 多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java I0 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。

  • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流
  • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流

按操作方式分类结构图:

IO-操作方式分类

按操作对象分类结构图:

IO-操作对象分类


既然有了字节流,为什么还要有字符流?

问题本质想问:不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?

回答:字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。


BIO,NIO,AIO 有什么区别?

  • IO (Blocking I/O): 同步阻塞 I/O 模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机 1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
  • NIO (Non-blocking/New I/O): NIO 是一种同步非阻塞的 I/O 模型,在 Java 1.4 中引入了 NIO 框架,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它支持面向缓冲的,基于通道的 I/O 操作方法。 NIO 提供了与传统 BIO 模型中的 SocketServerSocket 相对应的 SocketChannelServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞 I/O 来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
  • AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的 IO 模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步 IO 的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO 操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。

36. 常见关键字总结:static,final,this,super

参见1.4

37. Collections 工具类和 Arrays 工具类常见方法总结

Collections 工具类和 Arrays 工具类常见方法,看后面有没有涉及,再来补充。

Collections 工具类和 Arrays 工具类常见方法

38.深拷贝 vs 浅拷贝

  1. 浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。

  2. 深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝

    deep and shallow copy

发布了194 篇原创文章 · 获赞 20 · 访问量 7956

猜你喜欢

转载自blog.csdn.net/Xjheroin/article/details/105287855