【c++】万字长文,浅析c++继承特性

news2024/12/26 0:03:37

继承

  • 1. 继承的概念和定义
    • 1.1 概念
    • 1.2 定义
      • 1.2.1 定义格式
  • 2.基类和派生类对象赋值转换(##)
  • 3. 继承中的变量和函数隐藏(#)
  • 4.派生类的默认成员函数(###)
  • 5.友元函数和静态成员
    • 5.1.友元函数
    • 5.2.静态成员
  • 6.菱形继承(###)
  • 7.虚拟继承(#####)
  • 总结
    • 继承和组合(##)
    • 面试题
  • 结语

1. 继承的概念和定义

1.1 概念

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。

这种概念在现实生活中也是广泛存在,比如我们自己:
我们最基本的身份是一个人,然后以此为基础,衍生出来了各种各样的身份,

如: 学生,子女 …… 但是都是继承了的这个身份。

1.2 定义

下面我们看到Person是父类,也称作基类(base class)。Student是子类,也称作派生类(Derived class)。

父类:

class Person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
	}
protected:
		string _name = "peter"; // 姓名
		int _age = 18; // 年龄
};

子类:

class Student : public Person
{
	protected:
	int _stuid; // 学号
};

在Student 子类中,我们继承了父类 Person 中_name,_age 数据。

继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了Student和Teacher复用了Person的成员。

1.2.1 定义格式

在这里插入图片描述

在这里插入图片描述

继承方式精辟的总结就是:小小取小

基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
注意:是会被子类继承,但是子类无论在类内还是类外都无法访问

基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected
可以看出保护成员限定符是因继承才出现的。

使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式

在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承。

2.基类和派生类对象赋值转换(##)

派生类(子类)对象 可以赋值给 基类(父类)的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。

在这里插入图片描述

就是把子类中父类的那一部分切割下来,然后调用父类对象的拷贝构造或者拷贝赋值函数,对父类对象进行赋值。

注意:

  1. 基类对象不能赋值给派生类对象
// 子类对象可以赋值给父类对象/指针/引用
	Student s;
	Person p1;
	Person p2 = s;
	Person* p3 = &s;
	Person& p4= s;

//父类对象不能给子类对象赋值
	s=p1;

在这里插入图片描述

3. 继承中的变量和函数隐藏(#)

  1. 在继承体系中基类和派生类都有独立的作用域。
  2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏
    也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
  3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
  4. 注意在实际中在继承体系里面最好不要定义同名的成员。
class A {
public:
	int val = 10;

	void fun()
	{
		cout << "A::fun()" << endl;
	}
};

class B : public A
{
public:
	int val = 20;
	void fun()
	{
		cout << "B::fun()" << endl;
	}
};

int main()
{
	A a;
	B b;
	cout<<a.val<< " "<<b.val<<endl;    //    10     20
	a.fun();    //打印出  "A::fun()"
	b.fun();    //打印出  "B::fun()"
	//作为子类 ,也可以显示调用父类的数据

	b.A::a;   //打印 10
	b.A::fun();   //   "A::fun()"
	
	return 0;
}

fun()函数构成的不是函数重载,因为他们不在同一个作用域中,父类作用域和子类是不同的作用域,他们构成了重定义。

4.派生类的默认成员函数(###)

在每个类中,如果我们不去定义,那么编译器会自动生成的默认函数。那么衍生类的默认函数该如何呢?

  1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认
    的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
  2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
  3. 派生类的operator=必须要调用基类的operator=完成基类的复制

我们来一一验证
看如下代码:

class  Person {
public:
	Person() {
		cout << "Person()" << endl;
	}
	Person(const string& n, int a)
		:name(n)
		, age(a)
	{}
	Person(const Person& p)
	{
		cout << "Person(const Person& p)" << endl;
	}
	Person& operator=(const Person& p) {
		cout << "operator=(const Person& p)" << endl;
		return *this;
	}
	~Person()
	{
		cout << "~Person()" << endl;
	}

	string name="张三";
	int age=18;
};


class  Student : public  Person
{
public:
	Student(int id = 123456)
		:id(123456)
	{}
	int id;
};

子类我们并没有实现构造函数,拷贝赋值函数等,但
来看:

Student s;
Student s1 = s;

定义了两个Student 类,看调试结果:
在这里插入图片描述
编译器调用了父类的默认构造函数,和拷贝构造函数。

再看:

	Student s1;
	Student s2;
	s1 = s2;

在这里插入图片描述
子类会调用父类的拷贝赋值函数,同时在析构时,也会去调用 父类的析构函数。

我们也可以显示调用父类的赋值等函数

class  Student : public  Person
{
public:
	Student(int id = 123456)
		:id(123456)
	{}
	Student(const string& s, int a, int num)
		:Person(s, a)
		, id(num)
	{}
	Student(const Student& s)
		:Person(s)//切片后在调用父类的拷贝构造
		,id(s.id)
	{}
	Student operator=(const Student& s)
	{
		Person::operator=(s);
		id = s.id;
	}
	
	~Student()
	{
		Person::~Person();

);
	}
	int id;
};

但是需要注意的一点,是析构函数,按照其它默认函数的方法,我们这样定义子类的构造函数

~Student()
	{
	   ~Person();
	   _id=0;
	}

但是,这样写是错误的
在这里插入图片描述
编译器识别不了~Person() 函数,为什么呢?

每个类的析构函数会被处理为 Destuctor(),所以父类和子类的析构函数构成隐藏,编译器无法识别。

那么,我们还可以加上作用域符::

~Student()
	{
	   Person::~Person();
	   _id=0;
	}

可是,这么写正确嘛,我们运行一下:
在这里插入图片描述
可见,这里调用了两次析构函数 ,为什么呢?

因为子类的析构函数会自动调用父类的析构函数,无论你是否已经显示调用了父类的析构函数。

所以:

  1. 子类的析构会自动调用父类的析构函数,如果我们自己显示调用,会造成父类资源多次释放,在涉及动态资源开辟时会报错。
  2. 无需我们显示调用父类的析构函数,因为编译器要保证析构的顺序。
  3. 析构时是先析构子类区别于父类的资源,在调用父类的析构函数析构父类。
  4. 构造时先父类再子类,析构时先子类再父类,符合栈的规则(都是定义在栈区上的变量)

5.友元函数和静态成员

5.1.友元函数

可以用一句话来说————父亲的朋友不是我的朋友

也就是说,父类的友元函数不会继承给子类。

也就不多演示了。

5.2.静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子
类,都只有一个static成员。

class  Person {
public:
	static int count;
public:
	Person() {
		cout << "Person()" << endl;
		count++;
	}
	Person(const string& n, int a)
		:name(n)
		, age(a)
	{
		count++;
	}
	Person(const Person& p)
	{
		cout << "Person(const Person& p)" << endl;
		count++;
	}
}

int Person::count = 0;// 类外对静态变量进行初始化

基于静态成员变量的特性,我们可以实现这样一个功能:

对整个继承体系的对象创建计数

Person p1;
Person P2;
Person s3=s2;
Student s1;
Student s2;
Student s3=s2;

cout<<Person::count<<endl;

我们创建了六个对象,试运行一遍看结果:
在这里插入图片描述
结果正确。

6.菱形继承(###)

在当初设计继承时,我们的c++之父链接: 本贾尼大佬在设计这一块时,都没有想到这一个大坑,为此我们的本大佬也是花费了很多精力才解决了这一个困难。

回到菱形继承上来,首先引入的概念是多继承

根据我们的现实生活经验,一个事物可能同时兼具两种甚至多种属性,
比如:我们,同时有着学生和子女的属性,又如,我们是学生的同时,也是老师的助手或者助教。
在这里插入图片描述
但是Student类和Teacher类都继承自Person 类,所以他们构成了菱形继承。
代码:

class Person{....};

class Student :public Person
{....};
class Teacher :public Person
{....};

class Assistant :public Student public Teacher
{....};

所以,我们的本大佬就设计出了多重继承的方式,非常贴合现实,也是非常实用的,但是也有菱形继承这一个大坑。

从上图可以看出,菱形继承有数据冗余和二义性的问题:

Student 类继承了Person 类中的 _name=“小张” ,_age =18;
Teacher 类继承了Person 类中的 _name=“老张”,_age=18;

我们可以看到,_age 出现了数据冗余的问题,我们只需一份_age 数据,而这里出现了两份,当我们的父类Person 类中的数据较多时 ,会造成极大的空间浪费。

那么二义性呢,你可能会这么想,当我是学生时同学喜欢称呼我叫小张,当我是老师是,同事叫我老张,不同场景不同称呼啊,没有错误呀。

但是计算机喜欢一个能直接表示你的身份和属性的名字,也就是我们现实生活中我们身份证上的名字,计算机不关心你在家时的小名,和你在学校里的外号,他只需要记录你的真实信息。

接下我们看代码:

class Person
{
public:
	string _name; // 姓名
};
class Student : public Person
{
protected:
	int _num; //学号
};
class Teacher : public Person
{
protected:
	int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修课程
};


int main()
{
	Assistant a;
	a._name = "张三";
	return 0;
}

在这里插入图片描述

由于二义性的问题,对_name 的访问不明确,我们还可以用类访问限定符::

int main()
{
	Assistant a;
	a.Student::_name = "张三";
	return 0;
}

我们调试来看:

在这里插入图片描述
可以看到,我们对Person类中的_name cg==成功赋值了,解决了二义性的问题,但是_name 也存在着数据冗余,如何解决,

就需要看我们本贾尼大佬专门设计出的虚继承了。

7.虚拟继承(#####)

虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和Teacher的继承Person时使用虚拟继承,即可解决问题。

需要注意的是,虚拟继承不要在其他地方去使用。(虚拟继承是专门设计出来解决菱形继承稳定的)。

那么是如何解决问题的呢,我们来看本贾尼大佬及其聪明的才智吧:

监视窗口已经看不到虚继承真实的面目,我们需要借助内存窗口来进行调试,
看如下代码:
这是不加上虚继承的代码,

class A {
public:
	int _a;
};

class B :public A
{
public:
	int _b;
};

class C :public A
{
public:
	int _c;
};
class D :public B ,public C
{
public:
	int _d;
};

int main()
{
	D d;
	d.B::_a = 1;
	d.B::_b = 2;
	d.C::_a = 3;
	d.C::_c = 4;
	d._d = 5;
	
	return 0;
}

在这里插入图片描述
还是存在着数据冗余。

我们来看虚继承,关键字::virtual

class A {
public:
	int _a;
};

class B : virtual public A
{
public:
	int _b;
};

class C : virtual  public A
{
public:
	int _c;
};
class D : virtual public B ,virtual public C
{
public:
	int _d;
};

int main()
{
	D d;
	d.B::_a = 1;
	d.B::_b = 2;
	d.C::_a = 3;
	d.C::_c = 4;
	d._d = 5;
	
	return 0;
}

在这里插入图片描述
是不是有点不一样了,我们从内存窗口来看更多的细节:
在这里插入图片描述

我们可以看到,编译器把父类A 摘出来了,将基类里的数据单独存储在一个空间,但是它又同时属于B和C,那么我们如何找到公共的A 呢?
我们可以看到 B类,C类 中存储的自己的数据,在首地址处还存储了一个地址,名为虚机表地址指向了一块表,叫做虚机表:该表的第二个地址是一个偏移量,由图可得,其大小为该类的地址到我们的基类 A 的相对距离。 我们就可以通过这个偏移量找到A。

同时表上还有一个位置,那里的知识涉及了多态,我们稍后再说。

可以看到,只有一份 A的数据 ,且不需要加上类访问限定符 便可以访问公共的A ,完美解决了 数据冗余 和 二义性 的问题。

但是,又有同学问了:你说你解决了数据冗余的问题,我怎么一对比,你虚继承后需要的空间还比原来的要大呢?

这个是因为,我们的基类A 中现在只有一个int 类型,数据量比较小,所以会出现 内存还比原来大的情况 。

重点::当我们定义多个D对象时,D d1,D d2时,每一个对象都指向同一块虚机表,所以虚机表的空间消耗可以忽略。

这些消耗 是 解决数据冗余和二义性的必备要素。

总结

  1. 很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度及性能上都有问题。
  2. 多继承可以认为是C++的缺陷之一,很多后来的OO(面向对象)语言都没有多继承,如Java。

继承和组合(##)

public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。

is-a 的意思就是 子类(学生)就是一个父类(人) 白箱复用(white-box reuse)

has-a 的意思是 我在我的域中使用你 ,你是我的一部分,比如我使用我的鼻子,在STL库中,每个容器中的iterator迭代器 就是属于这样的关系,黑箱复
用(black-box reuse)。

实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合。

面试题

最后还有一道面试题 ,来测试一下你是否真真搞懂了继承啊:

class A{
public:
	A(char *s) { cout << s << endl; }
	~A(){}
};

class B :virtual public A
{
public:
	B(char *s1, char*s2) :A(s1) { cout << s2 << endl; }
};

class C :virtual public A
{
public:
	C(char *s1, char*s2) :A(s1) { cout << s2 << endl; }
};

class D : public B, public C
{
public:
	D(char *s1, char *s2, char *s3, char *s4) :B(s1, s2), C(s1, s3), A(s1)
	{
		cout << s4 << endl;
	}
};

int main() {
	D *p = new D("class A", "class B", "class C", "class D");
	delete p;
	return 0;
}

答案是 “class A”, “class B”, “class C”, "class D“

分割线:

别偷看哦。

####################################################

解析:我们知道,类对象初始化的时候,其初始化顺序不由初始化列表控制,而是根据定义时候的顺序进行初始化,而且根据继承规则,先构造父类数据,在构造子类变量,我们知道,D类先继承了B类,再继承C类,所以,先初始化B类 ,B又是先初始化A类,然后再是B类,随后调用C类,由于父类A 类在初始化时只能被初始化一次,所以C类不会调用A类,最后便是D类

所以 结果是 A B C D。

结语

本次的博客就到这了。

我是Tom-猫,
如果觉得有帮助的话,记得
一键三连哦ヾ(≧▽≦*)o。

咱们下期再见。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

广德上汽通用汽车平行试车场

技术栈&#xff1a;使用vue2JavaScriptElement UIvuexaxioscesium 项目描述&#xff1a;广德上汽通用汽车平行试车场是依托千寻孪界开发的一套展示实时车辆位置同步展示光照&#xff0c;时间&#xff0c;阴影等特效&#xff0c;完成平行时空效果的一款软件。 工作内容&#xff…

Linux 桌面份额突破 3%

导读今天来聊一聊linux桌面。 Linux 桌面份额突破 3% 根据 Statcounter 的数据&#xff0c;Linux 的使用率在过去几年中一直在缓慢上升&#xff0c;趋势非常明显。今年&#xff0c;Linux 桌面的统计数据如下&#xff1a;一月&#xff0c;2.91%&#xff1b;二月&#xff0c;2.9…

基于Python+WaveNet+CTC+Tensorflow智能语音识别与方言分类—深度学习算法应用(含全部工程源码)

目录 前言总体设计系统整体结构图系统流程图 运行环境Python 环境Tensorflow 环境 模块实现1. 方言分类数据下载及预处理模型构建模型训练及保存 2. 语音识别数据预处理模型构建模型训练及保存 3. 模型测试功能选择界面语言识别功能实现界面方言分类功能实现界面 系统测试1. 训…

SpringCloud是SpringBoot 的升级版吗?有什么区别?

目录 一、什么是SpringBoot 二、什么是SpringCloud 三、SpringCloud是SpringBoot 的升级版吗 四、SpringCloud和SpringBoot 有什么区别 一、什么是SpringBoot Spring Boot是一种用于快速构建基于Spring框架的Java应用程序的开发框架。它简化了Spring应用程序的配置和部署过…

超详细图文教程:3DS Max 中创建低多边形游戏长剑模型-下部

推荐&#xff1a; NSDT场景编辑器助你快速搭建可二次开发的3D应用场景 在由两部分组成的教程的第一部分中&#xff0c;我向您展示了如何&#xff1a; 剑柄建模为剑的护手建模剑刃建模 在本教程系列的第二部分中&#xff0c;我将向您展示如何&#xff1a; 打开紫外线包装创建…

【Redis深度专题】「核心技术提升」探究Redis服务启动的过程机制的技术原理和流程分析的指南(基础功能分析)

探究Redis服务启动的过程机制的技术原理和流程分析的指南 Redis基本概念Redis特点说明 Redis源码结构Redis功能架构Redis启动流程初始化全局服务器配置源码分析分析说明initServerConfig方法初始化的内容保存机制的初始化策略优化的初始化策略 指定配置文件加载配置文件默认的数…

【每日运维】RockyLinux8非容器化安装Mysql、Redis、RabitMQ单机环境

系统版本&#xff1a;RockyLinux 8.6 安装方式&#xff1a;非容器化单机部署 安装版本&#xff1a;mysql 8.0.32 redis 6.2.11 rabbitmq 3.11.11 elasticsearch 6.7.1 前置条件&#xff1a;时间同步、关闭selinux、主机名、主机解析host 环境说明&#xff1a;PC电脑VMware Work…

Hadoop生态体系-HDFS

目录标题 1、Apache Hadoop2、HDFS2.1 设计目标&#xff1a;2.2 特性&#xff1a;2.3 架构2.4 注意点2.5 HDFS基本操作2.5.1 shell命令选项2.5.2 shell常用命令介绍 3、HDFS基本原理3.1 NameNode 概述3.2 Datanode概述 1、Apache Hadoop Hadoop&#xff1a;允许使用简单的编程…

webpack require.context

require.context((directory: String),(includeSubdirs: Boolean) /* 可选的&#xff0c;默认值是 true */,(filter: RegExp) /* 可选的&#xff0c;默认值是 /^\.\/.*$/&#xff0c;所有文件 */,(mode: String) /* 可选的&#xff0c; sync | eager | weak | lazy | lazy-onc…

【C#】使用this进行扩展方法以及静态类和静态成员

2023年&#xff0c;第30周&#xff0c;第2篇文章。给自己一个目标&#xff0c;然后坚持总会有收货&#xff0c;不信你试试&#xff01; 本篇文章主要简单讲讲&#xff0c;使用this进行扩展方法以及静态类和静态成员 目录 一、this扩展1、扩展条件2、举例代码 二、静态知识点1、…

【Linux网络】 网络套接字(三)socket编程_TCP网络程序

目录 TCP网络程序服务端创建套接字并绑定服务端监听服务端获取连接服务器处理请求 客户端客户端创建套接字客户端连接服务器客户端发起请求测试 服务器存在的问题多进程版的TCP网络程序多线程版的TCP网络程序线程池版的TCP网络程序 TCP网络程序总结图 TCP网络程序 服务端 创建…

一站式财务管家工具:Zoho Books审批功能详细介绍

Zoho Books作为一款功能强大的财务管理软件&#xff0c;提供了多种实用的功能&#xff0c;其中审批流程是非常重要的一个。那么&#xff0c;Zoho Books的审批功能是如何实现的呢&#xff1f;本文将为您详细介绍。 1. 什么是审批功能 审批是企业内部重要业务流程中的前置环节&a…

华为数通HCIP-OSPF基础

路由协议 作用&#xff1a;用于路由设备学习非直连路由&#xff1b; 动态路由协议&#xff1a;使路由设备自动学习到非直连路由&#xff1b; 分类&#xff1a; 按照算法分类&#xff1a; 1、距离矢量路由协议&#xff1b;&#xff08;RIP、BGP&#xff09; 只交互路由信息…

基于FPGA实现OSD功能

简介 基于FPGA平台实现简单的OSD的功能,对于FPGA实现OSD只能实行简单的画框和文字叠加,如果实现复杂的车道线画框,则没法实现(起码我个人感觉,这个功能没有思路执行)。 FPGA实现OSD功能需要7系列平台,以及VDMA、OSD等Xilinx公司的IP使用(本功能工程采用Vivado2017.4平台…

windows关闭某个进程

一、使用命令 &#xff08;1&#xff09;winR键打开命令提示符&#xff0c;输入cmd &#xff08;2&#xff09;输入netstat -ano &#xff08;3&#xff09;输入taskkill /f /pid 进程ID。例如&#xff1a;taskkill /f /pid 19216 如果成功终止的话&#xff0c;会出现成功&…

Vue+Nodejs 使用WebSocket创建一个简易聊天室

文章目录 一、页面效果二、架构流程三、技术细节1.客户端2. 服务端 一、页面效果 二、架构流程 使用vue编写前端页面&#xff0c;nodejs处理服务端消息&#xff0c;WebSocket进行实时通信 三、技术细节 1.客户端 <template><div><form onsubmit"return…

Ubuntu录屏软件Kazam

1. 安装 1.1. 桌面右键“打开终端” 1.2. 安装kazam这款软件。 sudo apt-get install kazam 2. 使用 2.1. 安装后打开&#xff0c;我们看看这款软件界面还是很友好很简洁的。 2.2. 除了录像我们还可以截图&#xff0c;也可以选择全屏、窗口、区域的方式录制。 2.3. 如果要录…

Hybird app 热更新工作原理

大家对于原生应用和混合应用已经非常熟悉了&#xff0c;这里就不再进行详细的介绍&#xff0c;用通俗易懂的话解释下他们的一些特点。 1、原生应用 在 Android、iOS 等移动平台上利用提供的开发语言、开发类库、开发工具进行 App 软件开发。比如 Android 是用 Java、Eclipse、…

改进的北方苍鹰算法优化VMD参数,最小包络熵、样本熵、信息熵、排列熵(适应度函数可自行选择,一键修改)包含MATLAB源代码...

今天给大家带来一期由改进的北方苍鹰算法(SCNGO)优化VMD的两个参数。 同样以西储大学数据集为例&#xff0c;选用105.mat中的X105_BA_time.mat数据中1000个数据点。没有数据的看这篇文章。西储大学轴承诊断数据处理&#xff0c;matlab免费代码获取 选取四种适应度函数进行优化&…

【开发问题】flink-cdc不用数据库之间的,不同类型的转化

不同的数据库之期间数据类型转化 问题来源与原因解决过程&#xff0c;思路错误&#xff0c;导致各种错误错误思路是什么 正确解决方式&#xff0c;找官网对应的链接器&#xff0c;数据转化 问题来源与原因 我一开始是flink-cdc&#xff0c;oracle2Mysql&#xff0c;sql 我一开…