Java面向对象实践小结(含面试题)

继承

作用

  1. 提高了代码的复用性。
  2. 让类与类之间产生了关系。有了这个关系,才有了多态的特性。

代码示范

父类代码

public class Parent {
    public void say() {
        System.out.println("父类的say方法");
    }
}

子类代码,继承父类,也就拥有了say方法

public class Son extends Parent {

}

测试代码

public class Main {
    public static void main(String[] args) {
        Son son=new Son();
        son.say();
        /**
         * 输出结果
         * 父类的say方法
         */
    }
}

注意事项

  1. 不要为了某些功能而继承,继承之间应该是is a关系。
  2. Java只支持单继承
  3. Java支持多层级继承,例如3继承2,2继承1,那么3就拥有1和2的方法。

this和super关键字

简介

this代表调用该方法的引用,而super则代表父类对象的引用。当我们在类内部要使用自己的方法时可以使用this,要调用父类方法时,可以使用super。

this和super使用实例

父类的方法

public class Parent {
    public void say() {
        System.out.println("父类的say方法");
    }
}

子类方法

public class Son extends Parent {

    public void say2(){
        super.say();
        System.out.println("父类say完子类say");
    }
}

测试输出结果

public class Main {
    public static void main(String[] args) {
        Son son=new Son();
        son.say2();
        /**
         * 输出结果
         * 父类的say方法
         * 父类say完子类say
         */
    }
}

继承工作原理解析

代码示例

可以看到父类的代码如下所示,设置一个num为3

public class Parent {
    private int num=3;

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }
}

子类继承父类,num设置为4

public class Son extends Parent {

    private int num = 4;

    public void show() {
        System.out.println(this.num);
    }
}

输出结果,可以看到输出的是子类的值,当然如果show中用的是super.getNum(),输出结果就为3,那么jvm是如何工作的呢?

public class Main {
    public static void main(String[] args) {
        Son son=new Son();
        son.show();
        /**
         * 输出结果
         * 4
         */
    }
}

图解子父类初始化过程

  1. 方法区的非静态区加载父类和子类的信息
  2. 实例化son类时,堆区开辟一个空间
  3. 若son无num,super和this指向的都是父类的num,反之只有thi指向自己的num

在这里插入图片描述

重写

概述

子类编写一个方法参数和名字都和父类一样时,就会执行子类重写的代码,并且我们需要使用@Override关键字注明这个方法是重写的方法。

代码示例

父类的代码示例

public class Parent {
    public List getList() {
        return Collections.emptyList();
    }
}

子类的代码示例

public class Son extends Parent {

    @Override
    public List getList() {
        return super.getList();
    }
}

注意事项

  1. 子类重写父类方法是必须遵循两小一大原则。

     		1. 访问权限大于父类
     		2. 抛出异常小于父类
     		3. 返回值小于父类
    

代码如下所示,可以看到子类抛出的异常以及返回值都小于父类

父类

public class Parent {
    public List getList() throws Exception {
        return Collections.emptyList();
    }
}

子类

public class Son extends Parent {

    @Override
    public ArrayList getList()throws IllegalAccessException {
        return new ArrayList();
    }
}

  1. 重写时希望保留父类的逻辑可以在代码首行用代码覆盖一下。
public class Parent {
    public void say(){
        System.out.println("父类的say方法");
    }


}

如下所示子类基于父类的方法实现了自己的特定逻辑

public class Son extends Parent {

    @Override
    public void say() {
        super.say();
        System.out.println("子类的say方法");
    }
}

子父类中的构造函数

  1. 子类对象进行初始化时,会隐式的调用父类的无参构造方法。
  2. 如果你要显示调用父类构造方法一定要在第一行声明,否则你做的所有操作都有可能会被父类构造方法覆盖。

代码示例如下所示

父类的代码

public class Parent {


    public Parent() {
        System.out.println("父类的构造方法");
    }

    public void say(){
        System.out.println("父类的say方法");
    }


}

子类的代码

public class Son extends Parent {

    @Override
    public void say() {
        super.say();
        System.out.println("子类的say方法");
    }
}

输出结果如下所示,可以看到父类构造函数的方法的输出结果输出了

public class Main {
    public static void main(String[] args) {
        Son son=new Son();
        son.say();
        /**
         * 输出结果
         * 父类的构造方法
         * 父类的say方法
         * 子类的say方法
         */
    }
}

final关键字

关键字简介

  1. final关键字可以修饰类、方法、变量
  2. 修饰类,则这个类不可被继承
  3. 修饰方法则这个方法不可便重写
  4. 修饰变量,若为引用类型则该引用指向地址不可被修改,但是引用可以被修改。若基础类型则值不可修改。而且这个关键字可以修饰成员变量和局部变量

代码示例

public final class Parent {

    private final int a = 3;

    public Parent() {
        final int c=2;
        System.out.println("父类的构造方法");
    }

    public final void say() {
        System.out.println("父类的say方法");
    }


}

抽象类

使用场景

当多个类中出现相同功能,但是功能主体不同,这是可以进行向上抽取。这时,只抽取功能定义,而不抽取功能主体。

抽象类特点

  1. 抽象方法一定在抽象类中。
  2. 抽象方法和抽象类都必须被abstract关键字修饰。
  3. 抽象类不可以用new创建对象。因为调用抽象方法没意义。
  4. 抽象类中的抽象方法要被使用,必须由子类继承并实现所有的抽象方法后,才能建立子类对象调用。如果子类只覆盖了部分抽象方法,那么该子类还是一个抽象类。

抽象类和一般类没有太大的不同。

该如何描述事物,就如何描述事物,只不过,该事物出现了一些暂时不知道如何实现或者实现方式不一定。
这些不确定的部分,也是该事物的功能,需要明确出现。但是无法定义主体。这时,通过抽象方法来表示最为合适。

抽象类常见问题

abstract 关键字,和哪些关键字不能共存?

  1. final:被final修饰的类不能有子类。而被abstract修饰的类一定是一个父类。
  2. private: 抽象类中的私有的抽象方法,不被子类所知,就无法被复写。而抽象方法出现的就是需要被复写。
  3. static:如果static可以修饰抽象方法,那么连对象都省了,直接类名调用就可以了。可是抽象方法运行没意义。

抽象类常见使用场景——模板方法模式

抽象类常常作为模板方法的实施方案,以下便是笔者之前写过关于模板方法的文章,感兴趣的可以看看

设计模式-模板方法

接口

格式特点:

  1. 接口中常见定义:常量,抽象方法。
  2. 接口中的成员都有固定修饰符。
    常量:public static final
    方法:public abstract
    记住:接口中的成员都是public的。

接口和抽象类有什么共同点和区别?

共同点

  1. 接口和抽象类都可以包含抽象方法和默认方法
  2. 都不能被实例化

不同点

  1. 接口的变量必须是public static final修饰,抽象类可以默认不赋初值
  2. 类可以继承多接口,抽象类只能继承一个
  3. 接口常用于定义行为,继承该接口的类就会拥有某些行为,而抽象类则是抽取共性提供抽象方法给子类实现

多态概述

什么是多态

多态即事物可以有多种存在形态,如:猫可以是猫类也可以是动物类

代码示例

Cat cat=new Cat();
Animal animal=new Cat();

多态的体现

父类引用指向子类对象,通俗来说就是父类引用可以指向自己的对象

public void static main(String[] args){
function(new Cat());//会输出cat的eat方法而不是Animal的
function(new Dog());//会输出dog的eat方法而不是Animal的
}


public static void function(Animal a)//Animal a = new Cat();
	{
		a.eat();
	}

多态的好处

提高了程序的扩展性,使得同一个函数得以复用,方便后续开发的扩展。

使用多态的前提

  1. 必须是类与类之间有继承(extends)或者实现关系(写一个接口让另一个类implement)
  2. 存在对父类的覆盖

多态的弊端

只能使用父类引用访问父类成员,即cat继承Animal类并且覆盖Animal的eat或者相关方法。

多态的转型

Animal a=new Cat();//父类指向子类 向下转型

if(a instanceof Cat )//判断是否该animal类是否可以转为cat
Cat c=(Cat)a;//强转为cat 向下转型

多态的常见面试题

成员函数在多态调用输出结果是什么?

答:编译看左边,运行看右边

在多态中成员函数的特点:
在编译时期:参阅引用型变量所属的类中是否有调用的方法。如果有,编译通过,如果没有编译失败。
在运行时期:参阅对象所属的类中是否有调用的方法。

父类

public class Parent {
    public void func() {
        System.out.println("Parent");
    }
}

子类

public class Son extends Parent {

    @Override
    public void func() {
        System.out.println("Son");
    }
}

输出结果可以看到,编译看左边,输出结果完全看右边

public class Main {
    public static void main(String[] args) {
        Parent p = new Son();
        p.func();
        /**
         * 输出结果
         * Son
         */
    }
}

成员变量编译和结果取决于什么?

成员变量编译和结果全都取决于引用类型,即左边声明的类名,代码如下所示

父类

public class Parent {
    public  int num=1;
    public void func() {
        System.out.println("Parent");
    }
}

子类

public class Son extends Parent {
    public  int num=2;
    @Override
    public void func() {
        System.out.println("Son");
    }
}

可以看到输出解决是父类的数据

public class Main {
    public static void main(String[] args) {
        Parent p = new Son();
        System.out.println(p.num);
        /**
         * 输出结果
         * 1
         */
    }
}

静态函数编译运行结果取决于什么?

全看左边的引用类型,引用类型是什么就输出什么

class Fu
{
	
	static void method4()
	{
		System.out.println("fu method_4");
	}
}


class Zi extends Fu
{
	

	static void method4()
	{
		System.out.println("zi method_4");
	}
}


class  Test
{
	public static void main(String[] args) 
	{
		
		Fu f = new Zi();
		f.method4();//看该对象引用类型 所以输出fu method_4

		Zi z = new Zi();
		z.method4();//看该对象引用类型 所以输出zi method_4
	}
}

图解代码运行原理

如下图,静态区加载的是类信息,而不是this和spuer关键字,而且静态方法也不可以被重写所以,所以运行结果就根据左边的引用类型决定

在这里插入图片描述

object类

概述

Object:是所有对象的直接后者间接父类,传说中的上帝。
该类中定义的肯定是所有对象都具备的功能。

equals和toString

equals方法

比较的是对象的地址空间是否相同

toString方法

输出的是对象的类型+“@”+内存地址的十六进制
Object类中已经提供了对对象是否相同的比较方法。

如果自定义类中也有比较相同的功能,没有必要重新定义。
只要沿袭父类中的功能,建立自己特有比较内容即可。这就是覆盖

代码

class Demo //extends Object
{
	private int num;
	Demo(int num)
	{
		this.num = num;
	}
	
	//覆盖原有的equals
	public boolean equals(Object obj)//Object obj = new Demo();
	{

		if(!(obj instanceof Demo))
			return false;
		Demo d = (Demo)obj;

		return this.num == d.num;
	}
	
	//覆盖原有的toString
	public String toString()
	{
		return "demo:"+num;
	}


}
class Person 
{
}


class ObjectDemo 
{
	public static void main(String[] args) 
	{
		Demo d1 = new Demo(4);
		System.out.println(d1);//输出语句打印对象时,会自动调用对象的toString方法。打印对象的字符串表现形式。
		Demo d2 = new Demo(7);
		System.out.println(d2.toString());//打印的是d2.getName()+"@@"+Integer.toHexString(d2.hashCode()


	}
}

创建和销毁对象注意事项

用静态工程替代构造器

遇到多构造器参数建议使用建造者模式
优点:

 1. 保证类的不可变
 2. 实现的可变参数
 3. 增加可读性
/**
 * 遇到多构造器参数建议使用建造者模式
 * 优点:
 * 1. 保证类的不可变
 * 2. 实现的可变参数
 * 3. 增加可读性
 */
public class NutritionFacts {

    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder {
        private int servingSize;
        private int servings;

        //        可选参数,赋值上默认值
        private int calories = 0;
        private int fat = 0;
        private int sodium = 0;
        private int carbohydrate = 0;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }

        public Builder calories(int val) {
            calories = val;
            return this;
        }

        public Builder fat(int val) {
            fat = val;
            return this;
        }

        public Builder sodium(int val) {
            sodium = val;
            return this;
        }

        public Builder carbohydrate(int val) {
            carbohydrate = val;
            return this;
        }

        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }

    @Override
    public String toString() {
        return "NutritionFacts{" +
                "servingSize=" + servingSize +
                ", servings=" + servings +
                ", calories=" + calories +
                ", fat=" + fat +
                ", sodium=" + sodium +
                ", carbohydrate=" + carbohydrate +
                '}';
    }

    public NutritionFacts(Builder builder) {
        this.servingSize = builder.servingSize;
        this.servings = builder.servings;
        this.calories = builder.calories;
        this.fat = builder.fat;
        this.sodium = builder.sodium;
        this.carbohydrate = builder.carbohydrate;
    }

    
}

对象创建示例

public static void main(String[] args) {
        NutritionFacts nutritionFacts = new NutritionFacts.Builder(240, 8).calories(300).sodium(50).build();
        System.out.println(nutritionFacts.toString());//NutritionFacts{servingSize=240, servings=8, calories=300, fat=0, sodium=50, carbohydrate=0}

    }

避免创建不必要的对象

频繁创建对象会增加GC回收压力以及系统开销,所以我们应该避免创建不必要的对象

下面这段代码,从源码我们就可以看到matches方法会创建一个Pattern,所以我们可以对正则匹配的方法进行响应重构

 // Performance can be greatly improved! (Page 22)
    static boolean isRomanNumeralSlow(String s) {
        /**
         * 底层实际会创建一个Pattern 频繁调用会创建多个Pattern对象
         *  Pattern p = Pattern.compile(regex);
         *         Matcher m = p.matcher(input);
         *         return m.matches();
         */
        return s.matches("^(?=.)M*(C[MD]|D?C{0,3})"
                + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
    }

所以我们建议基于matches底层调用方式自己手写一个match方法避免没必要的对象创建

 /**
     * 手动创建pattern避免频繁创建对象
     */
    private static final Pattern pattern = Pattern.compile("^(?=.)M*(C[MD]|D?C{0,3})"
            + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");

    static boolean isRomanNumeralFast(String s) {
        return pattern.matcher(s).matches();
    }

消除过期的对象引用

如下所示,Stack 的elements成员变量是一个数组,当我们调用pop逻辑上是将栈顶元素弹出,实际上jvm是无法感知这种逻辑弹出的,所以我们需要手动消除这个对象引用,否则会出现OOM异常

public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        //引用指向数组,我们所谓的size以及活动非活动元素对于虚拟机来说都是无感的
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        Object element = elements[--size];
        //消除过期引用
        elements[--size] = null;
        return element;

    }

    /**
     * Ensure space for at least one more element, roughly
     * doubling the capacity each time the array needs to grow.
     */
    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }


    public static void main(String[] args) {
        Stack stack = new Stack();
        for (String arg : args)
            stack.push(arg);

        while (true)
            System.err.println(stack.pop());
    }
}

try-with-resource优先于try-finally

使用try-with-resource相较于后者更加简洁、清晰、产生的异常信息也更有价值

public class Copy {
    private static final int BUFFER_SIZE = 8 * 1024;

    // try-finally is ugly when used with more than one resource! (Page 34)
    static void copy(String src, String dst) throws IOException {
        InputStream in = new FileInputStream(src);
        try {
            OutputStream out = new FileOutputStream(dst);
            try {
                byte[] buf = new byte[BUFFER_SIZE];
                int n;
                while ((n = in.read(buf)) >= 0)
                    out.write(buf, 0, n);
            } finally {
                out.close();
            }
        } finally {
            in.close();
        }
    }


    static void copyWithTryResource(String src, String dst) throws IOException {

        try (InputStream in = new FileInputStream(src); OutputStream out = new FileOutputStream(dst)) {

            byte[] buf = new byte[BUFFER_SIZE];
            int n;
            while ((n = in.read(buf)) >= 0)
                out.write(buf, 0, n);
        }
    }

    public static void main(String[] args) throws Exception{
        copy("D:\\source.txt","dst.txt");
        copyWithTryResource("D:\\source.txt","dst2.txt");
    }
}

参考文献

Java基础常见面试题总结(中)

Effective Java中文版(第3版)

Java程序性能优化

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/233302.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java表达式、java中jexl3的使用,java中jexl3如何自定义函数方法,jexl3自定义函数怎么传集合数组列表

引入jexl3 <dependency><groupId>org.apache.commons</groupId><artifactId>commons-jexl3</artifactId><version>3.2.1</version> </dependency> 基本用法 //引入对应包 import org.apache.commons.jexl3.*;public class …

操作系统学习笔记---内存管理

目录 概念 功能 内存空间的分配和回收 地址转换 逻辑地址&#xff08;相对地址&#xff09; 物理地址&#xff08;绝对地址&#xff09; 内存空间的扩充 内存共享 存储保护 方式 源程序变为可执行程序步骤 链接方式 装入方式 覆盖 交换 连续分配管理方式 单一连…

SpringBoot3-集成mybatis

1、pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.…

接口测试-Jmeter使用

一、线程组 1.1 作用 线程组就是控制Jmeter用于执行测试的一组用户 1.2 位置 右键点击‘测试计划’-->添加-->线程(用户)-->线程组 1.3 特点 模拟多人操作线程组可以添加多个&#xff0c;多个线程组可以并行或者串行取样器(请求)和逻辑控制器必须依赖线程组才能…

Linux:进程优先级与命令行参数

目录 1.进程优先级 1.1 基本概念 1.2 查看系统进程 1.3 修改进程优先级的命令 2.进程间切换 2.1 相关概念 2.2 Linux2.6内核进程调度队列&#xff08;了解即可&#xff09; 3.命令行参数 1.进程优先级 1.1 基本概念 cpu资源分配的先后顺序&#xff0c;就是指进程的优…

【vtkWidgetRepresentation】第八期 vtkImplicitCylinderRepresentation

很高兴在雪易的CSDN遇见你 前言 本文分享vtkImplicitCylinderRepresentation&#xff0c;主要从源码解析、和实际应用方面展开&#xff0c;希望对各位小伙伴有所帮助&#xff01; 感谢各位小伙伴的点赞关注&#xff0c;小易会继续努力分享&#xff0c;一起进步&#xff01; …

软件设计不是CRUD(7):低耦合模块设计实战——组织机构模块(中)

接上文《软件设计不是CRUD&#xff08;6&#xff09;&#xff1a;低耦合模块设计实战——组织机构模块&#xff08;上&#xff09;》 组织机构功能是应用系统中常见的业务功能之一&#xff0c;但是不同性质、不同行业背景、不同使用场景的应用系统对组织机构功能的要求可能完全…

Sprint Boot 3.0

1. 简介 视频教程特点&#xff1a; Spring Cloud带动了Spring BootSpring Boot成就了Spring Cloud

“创未来,享非凡“ 昇腾AI开发者创享日广州站圆满成功

在羊城广州的科技新风潮中&#xff0c;一个以创新为核心、以智能为驱动的盛会在这座南国明珠城市如火如荼地展开。这不仅是一场技术的盛宴&#xff0c;更是人工智能产业发展动力的一次集结。 12月9日&#xff0c;在广州市工业和信息化局的倡导下&#xff0c;一场主题为“创未来…

大数据Doris(三十五):Unique模型(唯一主键)介绍

文章目录 Unique模型(唯一主键)介绍 一、创建doris表 二、插入数据

为 Compose MultiPlatform 添加 C/C++ 支持(2):在 jvm 平台使用 jni 实现桌面端与 C/C++ 互操作

前言 在上篇文章中我们已经介绍了实现 Compose MultiPlatform 对 C/C 互操作的基本思路。 并且先介绍了在 kotlin native 平台使用 cinterop 实现与 C/C 的互操作。 今天这篇文章将补充在 jvm 平台使用 jni。 在 Compose MultiPlatform 中&#xff0c;使用 jvm 平台的是 An…

京东数据运营(京东API接口):10月投影仪店铺数据分析

鲸参谋监测的京东平台10月份投影仪市场销售数据已出炉&#xff01; 10月份&#xff0c;环同比来看&#xff0c;投影仪市场销售均上涨。鲸参谋数据显示&#xff0c;今年10月&#xff0c;京东平台投影仪的销量为16万&#xff0c;环比增长约22%&#xff0c;同比增长约8%&#xff1…

免费分享一套Springboot+Vue前后端分离的在线商城系统,挺实用的

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的SpringbootVue前后端分离的在线商城系统&#xff0c;分享下哈。 项目视频演示 【免费】SpringbootVue在线商城系统 毕业设计 Java毕业设计_哔哩哔哩_bilibili【免费】springbootvue在线商城系统 毕业设计 …

六何分析法分析uniApp

一、什么是 uniApp&#xff08;What&#xff09; uni-app 是一个使用 Vue.js 开发所有前端应用的框架&#xff0c;开发者编写一套代码&#xff0c;可发布iOS、Android、H5、以及各种小程序( 微信/支付宝/百度/头条/00/钉钉/淘宝)、快应用等多个平台。uni-app 在手&#xff0c;…

【蜗牛到家】获南明电子信息产业引导基金战略投资

智慧社区生活服务平台「蜗牛到家」已于近期获得贵阳南明电子信息产业引导基金、华科明德战略投资。 贵阳南明电子信息产业引导基金属于政府旗下产业引导基金&#xff0c;贵州华科明德基金管理有限公司擅长电子信息产业、高科技产业、城市建设及民生保障领域的投资&#xff0c;双…

TCP的滑动窗口机制

网络的错误检测和补偿机制非常复杂。 一、等待超时时间&#xff08;返回ACK号的等待时间&#xff09; 当网络繁忙时会发生拥塞&#xff0c;ACK号的返回变慢&#xff0c;较短的等待时间会导致频繁的数据重传&#xff0c;导致本就拥塞的网络雪上加霜。如果等待时间过长&#xf…

如何一个例子玩明白GIT

一个例子玩明白GIT GIT的介绍和教程五花八门&#xff0c;但实际需要用的就是建仓、推送、拉取等操作&#xff0c;这儿咱可以通过一个例子熟悉这些操作&#xff0c;一次性搞定GIT的使用方法学习。下面这个例子的内容是内容是建立初始版本库&#xff0c;然后将数据复制到 "远…

【Linux】第二十七站:内存管理与文件页缓冲区

文章目录 一、物理内存和磁盘交换数据的最小单位二、操作系统如何管理内存三、文件的页缓冲区四、基数树or基数&#xff08;字典树&#xff09;五、总结 一、物理内存和磁盘交换数据的最小单位 我们知道系统当中除了进程管理、文件管理以外&#xff0c;还有内存管理 内存的本质…

【数据结构】面试OJ题———栈|队列|互相实现|循环队列|括号匹配

目录 1. 有效的括号 思路&#xff1a; 2.用队列实现栈 思路&#xff1a; 3.用栈实现队列 思路&#xff1a; 4.设计循环队列 思路&#xff1a; 1. 有效的括号 20. 有效的括号 - 力扣&#xff08;LeetCode&#xff09; 给定一个只包括 (&#xff0c;)&#xff0c;{&…

Gazebo 跟踪8字形和U形轨迹(1) — 错误处理

Gazebo 跟踪8字形和U形轨迹(1) — 错误处理 整个过程还是比较曲折的&#xff0c;主要都是一些细小的问题&#xff0c;跑了很多遍模型才发现 参考轨迹生成问题不大&#xff0c;主要是参考横摆角和参考曲率部分有问题 atan和atan2 首先看下两者的区别 atan 函数&#xff1a;…