【C++】多态and多态原理
目录
一、多态的概念
二、多态的定义及实现
🌟多态的构成条件
🌟虚函数
🌟虚函数的重写
🌠小贴士:
🌟C++11 override 和 final
🌟重载、重写(覆盖)、重定义(隐藏)的对比
三、抽象类
🌟概念
🌟接口继承和实现继承
四、多态的原理
🌟虚函数表
🌟多态的原理
🌟动态绑定与静态绑定
🌠小贴士:
一、多态的概念
多种形态,去完成某个行为,当不同的对象去完成时会产生出不同的状态。
二、多态的定义及实现
🌟多态的构成条件
多态是在不同关系的类对象,去调用同一函数,产生了不同的行为。比如Student继承了Person。Person对象买票全价,Student对象买票半价。
在继承中构成多态的两个条件:(指向谁调用谁)
1、必须通过基类的指针或者引用调用虚函数;
2、被调用的函数必须是虚函数(对象都不行),且派生类必须对基类的虚函数进行重写;
🌟虚函数
即 被 virtual 修饰的类成员函数称为虚函数。
class A
{
public:virtual void test(){ cout << "A : test()" << endl;}
};
🌟虚函数的重写
虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同)。
#include<iostream>using namespace std;class Person
{
public:virtual void BuyTicket(){cout << "买票--全价" << endl;}
};class Student : public Person
{
public:virtual void BuyTicket(){cout << "买票--半价" << endl;}
};void Func(Person& p)
{p.BuyTicket();
}int main()
{Person p;Student s;Func(p);Func(s);return 0;
}
注意:
在重写基类虚函数时,派生类的虚函数在不加 virtual 关键字时,虽然也可以构成重写(因为继承后,基类的虚函数被继承下来了,在派生类依旧保持虚函数属性),但是改种写法不是很规范,不建议这样使用。
✨函数重写的两个例外:
<1>协变(基类与派生类虚函数返回值类型不同)
派生类重写虚函数时,与基函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。
class A
{};class B : public A
{};class Person
{
public:virtual A* f(){return new A;}
};class Student : public Person
{
public:virtual B* f(){return new B;}
};
<2>析构函数的重写(基类与派生类析构函数的名字不同)
如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加 virtual 关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。函数名不相同,看起来违背了重写的规则,其实不然,在这可以理解为编译器对析构函数的名称做了特殊处理,编译析构函数的名称统一处理成 destructor 。
class Person
{
public:virtual ~Person(){cout << "~Person()" << endl;}
};class Student : public Person
{
public:virtual ~Student(){cout << "~Student()" << endl;}
};//只有派生类Student的析构函数重写了Person的析构函数,
//下面的delete对象调用析构函数,才能构成多态
//才能保证p1和p2指向的对象正确的调用析构函数
int main()
{Person* p1 = new Person;Person* p2 = new Student;delete p1;delete p2;return 0;
}
🌠小贴士:
<1> 多态调用,看指向对象类型,指向谁调用谁的虚函数;
普通调用,看调用者的类型,调用调用者的函数;
<2> 重写是一种特殊的隐藏;
隐藏:不符合多态就是隐藏。
<3>对于普通对象,写不写成虚函数都不受影响:
class Person { public:virtual ~Person() { cout << "~Person()" << endl;} };class Student : public Person { public:virtual ~Student() {cout << "~Student()" << endl;} };int main() {Student s;//加不加 virtaul 普通对象都不受影响 //析构先调用子类析构,在调用父类析构//所以说在子类/派生类结束了以后会自动调用父类的析构//派生类里面有一个父类对象return 0; }
<4>为什么析构函数一定建议设计成虚函数?
只有派生类的析构函数重写了基类的析构函数,delete对象调用析构函数才能构成多态,才能保证指向的对象能正确的调用析构函数。
//加 virtual 变成多态,此时指向子类,调用子类;指向父类,调父类 //析构是先析构子类,再自动调用父类的析构 //如果不加虚函数,就是直接调用父类, // 这样子的话,子类的析构就没有调用到,就会产生内存泄露 class Person { public:virtual ~Person(){cout << "~Person()" << endl;} };class Student : public Person { public:virtual ~Student(){delete _ptr;cout << "~Student():" << _ptr << endl;} protected:int* _ptr = new int[10]; }; //内存泄漏 //没有调到派生类的析构函数( *_ptr 这个指针没有被释放)int main() {// 只有派生类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函//数,才能构成多态,才能保证p1和p2指向的对象正确的调用析构函数。Person* p1 = new Student;//指向子类调用子类的析构,最后默认调用父类的析构delete p1;Person* p2 = new Person;//指向父类,调用父类的析构,delete p2; //但是不会自动调用子类的析构,就会有可能参数子类的内存泄漏//因此析构函数一定建议设计成虚函数,在继承里面,尤其是基类的析构函数//因为只有当基类的析构函数设计成虚函数,派生类不管加不加virtual,派生类都构成了重写//构成了重写后,才能正常去选择调用子类/父类的析构,才能符合指向谁调用谁return 0; }
🌟C++11 override 和 final
C++对函数重写的要求比较严格,这两个关键字可以帮助用户检测是否重写。
<1> final:修饰虚函数,表示该虚函数不能再被重写。
class Person {
public:virtual ~Person() final //不想被完成重写,此时子类就不能被重写{cout << "~Person()" << endl;}
};class Student : public Person {
public:~Student() //报错,不能完成重写{delete _ptr;cout << "~Student():" << _ptr << endl;}
protected:int* _ptr = new int[10];
};int main()
{Person* p1 = new Student;delete p1;Person* p2 = new Person;delete p2;return 0;
}
<2> override :检查派生类虚函数是否重写了基类的某个函数,如果没有重写编译报错。
class Person {
public:~Person()//没有完成重写{cout << "~Person()" << endl;}
};class Student : public Person {
public:~Student() override //检查派生类是否完成重写,没有完成就报错{delete _ptr;cout << "~Student():" << _ptr << endl;}
protected:int* _ptr = new int[10];
};int main()
{Person* p1 = new Student;delete p1;Person* p2 = new Person;delete p2; return 0;
}
🌠小贴士:
<1> final可修饰类,修饰的类叫最终类,语法规定,这个类不能被继承,继承就会报错。
class A final {};class B : public A//报错 {};
<2>把基类的构造函数私有,子类就生不成对象:
class A { private:A(){} };class B : public A//报错 {};int main() {B bb;return 0; }
此时可以把基类的构造函数写成静态成员,就可以进行构造:
class A { public:static A CreateObj()//此时就可以用构造{return A();} private:A(){} };class B : public A {};int main() {//B bb;A::CreateObj();return 0; }
🌟重载、重写(覆盖)、重定义(隐藏)的对比
三、抽象类
🌟概念
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写。另外纯虚函数更体现出了接口继承。
在实践中,一个类型在现实没有实体对象,不想实例化出对象,设计成抽象类。
class Car
{
public:virtual void Drive() = 0;
};
//当父类是纯虚函数,派生类不想继承时,要重写虚函数
class Benz :public Car
{
public:virtual void Drive()//重写虚函数{cout << "Benz-舒适" << endl;}
};
class BMW :public Car
{
public:virtual void Drive()//重写虚函数{cout << "BMW-操控" << endl;}
};
int main()
{Benz bz;//不想继承父类的纯虚函数时,在派生类中重写虚函数Car* pBenz = new Benz;pBenz->Drive();Car* pBMW = new BMW;pBMW->Drive();return 0;
}
🌟接口继承和实现继承
普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。
四、多态的原理
🌟虚函数表
class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;\
};int main()
{Base b;cout << sizeof(Base) << endl;return 0;
}
• 在x86的环境下,通过观察测试我们发现 b 的对象是 8bytes ,除了_b成员,还多一个 _vfptr 放在对象的前面(注意有些平台可能放在对象的最后面,这跟平台有关),对象中的这个 _vfptr 我们叫做虚函数表指针(v为virtual,f为 function)。一个含有虚函数的类中至少都有一个虚函数表指针,因为虚函数的地址要被放到对象的最后面,这个跟平台有关)。
• 虚函数表 本质是一个函数指针数组。
• 一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表。那派生类中这个表放了些什么呢?咱接着往下看:
🌟多态的原理
// 针对上面的代码我们做出以下改造
// 1.我们增加一个派生类Derive去继承Base
// 2.Derive中重写Func1
// 3.Base再增加一个虚函数Func2和一个普通函数Func3
class Base
{
public:virtual void Func1(){cout << "Base::Func1()" << endl;}virtual void Func2(){cout << "Base::Func2()" << endl;}void Func3(){cout << "Base::Func3()" << endl;}
private:int _b = 1;
};class Derive : public Base
{
public:virtual void Func1(){cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};void Func1(Base* p)
{//运行时绑定/动态绑定//运行时去虚表里面找到函数的地址,确认函数的地址,所以指向谁调用谁p->Func1();//有虚函数,放进虚表里面//普通的调用//编译时绑定/静态绑定 //即编译时,用函数名找,如果只有声明就链接的时候找,如果直接就有定义,就在编译的时候,用这个函数名,在符号表里面找这个函数的地址p->Func3();//没有虚函数,不放进虚表
}int main()
{Base b;Derive d;Func1(&b);Func1(&d);//Func1(new Base);//Func1(new Derive);//指向的是子类当中父类的那一部分return 0;
}
• 派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员。
• 基类b对象和派生类d对象虚表是不一样的,这里我们发现Func1完成了重写,所以d的虚表中存的是重写的Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。
• 另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函
数,所以不会放进虚表。• 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。
• 总结一下派生类的虚表生成:
a.先将基类中的虚表内容拷贝一份到派生类虚表中;
b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
🌠注意:
虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是
他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。
🌟动态绑定与静态绑定
• 满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象中去找的,不满足多态的函数调用时编译时就确认好的。
• 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,
比如:函数重载• 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体
行为,调用具体的函数,也称为动态多态。
🌠小贴士:
<1>指定类域后,还能实现多态吗? 不能。
原因:编译器在编译时,指定类域,就不会识别成多态调用,就不会按照运行时绑定的方式去生成指令,此时就跟指向的对象没有关系;
<2>为什么虚函数要放到虚表?
运行的时候要去虚表里面找对应的虚函数,如果完成了重写就可以达到指向那个对象调用哪个对象;
<3>普通函数为什么不用放到虚表?
虚表时运行时才用的,普通函数是在编译的时候,通过函数名去确定地址,所以就支持了函数重载,按照函数名修饰规则,参数不同,修饰出来的函数名就不相同,就可以找到对应的地址;
函数名的地址有可能是在编译的时候找,也有可能是在链接的时候找,
在编译的时候,当前文件就用定义,编译好的时候当前文件就有这个函数的地址,编译就可以变成call的地址;
在链接的时候,只有声明,定义在其他.cpp,编译的时候时匹配的,再链接时才会拿修饰的函数名去其他文件的符号表里面找,找不到就会链接报错。
🌠小贴士:
<1> inline函数可以是虚函数吗?
可以,不过编译器就忽略了inline属性,这个函数就不再是Inline,因为虚函数要放到虚表中去。
<2> 静态成员可以是虚函数吗?
不能,因为静态成员函数没有this指针,使用 类型::成员函数 的调用方法无法访问虚函数表,所以静态成员函数无法放进虚函数表。
<3> 构造函数可以是虚函数吗?
不能,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。
<4>析构函数可以是虚函数吗?什么场景下析构函数是虚函数?
可以,并且最好把基类的析构函数定义成虚函数。
<5>对象访问普通函数快还是虚函数快?
首先如果是普通对象,是一样快的。
如果是指针对象或者引用对象,则调用普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。
<6>虚函数表是在什么阶段生成的?存在哪?
虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的。
<7>什么是抽象类?抽象类的作用?
抽象类强制重写了虚函数,另外抽象类体现了接口继承关系。
如若对你有帮助,记得点赞、收藏、关注哦!
若有误,望各位,在评论区留言或者私信我 指点迷津!!!谢谢^ ^ ~
相关文章:

【C++】多态and多态原理
目录 一、多态的概念 二、多态的定义及实现 🌟多态的构成条件 🌟虚函数 🌟虚函数的重写 🌠小贴士: 🌟C11 override 和 final 🌟重载、重写(覆盖)、重定义…...

C# 实现二维数据数组导出到 Excel
目录 功能需求 范例运行环境 Excel DCOM 配置 设计实现 组件库引入 编辑 方法设计 生成二维数据数组 核心方法实现 调用示例 总结 功能需求 将数据库查询出来的数据导出并生成 Excel 文件,是项目中经常使用的一项功能。本文将介绍通过数据集生成二维…...
nlohmann::json中有中文时调用dump转string抛出异常的问题
问题描述 Winodows下C开发想使用一个json库,使用的nlohmann::json,但是遇到json中使用中文时,转成string,会抛出异常。 nlohmann::json contentJson;contentJson["chinese"] "哈哈哈";std::string test con…...

Unity中InputField一些属性的理解
先看代码: using UnityEngine; using UnityEngine.UI;public class TestInput : MonoBehaviour {[SerializeField]InputField inputField;void Start(){Debug.Log(inputField.text);Debug.Log(inputField.text.Length);Debug.Log(inputField.preferredWidth);Debug…...

【webpack4系列】webpack构建速度和体积优化策略(五)
文章目录 速度分析:使用 speed-measure-webpack-plugin体积分析:使用webpack-bundle-analyzer使用高版本的 webpack 和 Node.js多进程/多实例构建资源并行解析可选方案使用 HappyPack 解析资源使用 thread-loader 解析资源 多进程并行压缩代码方法一&…...
从零开始搭建 PHP
🛠️ 从零开始搭建 PHP 环境:详细教程 PHP(Hypertext Preprocessor)是最流行的后端脚本语言之一,广泛用于构建动态网站和 Web 应用程序。在开始 PHP 开发之前,首先需要搭建 PHP 运行环境。无论你使用的是 …...

【数据结构】8——图3,十字链表,邻接多重表
数据结构8——图3,十字链表,邻接多重表 文章目录 数据结构8——图3,十字链表,邻接多重表前言一、十字链表结构例子 复杂例子 二、邻接多重表(Adjacency Multilist)例子 前言 除了之前的邻接矩阵和邻接表 …...
eth-trunk 笔记
LACP:Link Aggregation Control protocol 链路聚合控制协议 将多条以太网物理链路捆绑在一起成为一条逻辑链路,从而实现增加链路带宽的目的。同时,这些捆绑在一起的链路通过相互间的动态备份,可以有效地提高链路的可靠性 一、配…...

通信工程学习:什么是接入网(AN)中的TF传送功能
接入网(AN)中的TF传送功能 在通信工程中,TF(Transfer Function)传送功能是指为接入网(AN)不同位置之间提供通道和传输介质,以实现数据的有效传输。以下是关于TF传送功能的详细解释&a…...

【JavaEE】IO基础知识及代码演示
目录 一、File 1.1 观察get系列特点差异 1.2 创建文件 1.3.1 delete()删除文件 1.3.2 deleteOnExit()删除文件 1.4 mkdir 与 mkdirs的区别 1.5 文件重命名 二、文件内容的读写----数据流 1.1 InputStream 1.1.1 使用 read() 读取文件 1.2 OutputStream 1.3 代码演示…...

安卓13系统导航方式分析以及安卓13修改默认方式为手势导航 android13修改导航方式
总纲 android13 rom 开发总纲说明 文章目录 1.前言2.问题分析3.代码分析4.代码修改5.彩蛋1.前言 系统导航方式默认一般是按键的,如果要改成手势的话,我们来看看用户怎么修改的: 设置=>系统=>手势=>系统导航,在这里进行修改。我们来分析下这个流程,并且将其修改为…...
[技术杂谈]暗影精灵8plus电竞版台式机安装和使用注意
最近买回二手台式机准备做深度学习训练模型使用。由于个人不是十分有钱,因此下血本入手一台,不然深度学习玩不转。配置:i9-12900K / 64G d4 3733频率 / 1TSSD2TB机械 / RTX3090 24G显卡 旗舰版 机箱45L超大机箱。买回来后整体不错&#…...
【加密算法基础——AES解密实践】
AES 解密实践 AES 解密是对使用 AES 加密算法加密的数据进行恢复的过程。 常用的解密方式有三种: 在线解密工具:格式比较好控制,但是有些在线工具兼容性不好,有时候无法解出,不知道是自己的密文密钥没找对࿰…...

Spring01
spring框架 spring是轻量级的容器框架 spring framework 1、Spring核心学习内容 IOC、AOp, jdbcTemplate,声明式事务 2、IOC:控制反转,孚以管理部8号对象 3.AOP:切面编程4.JDBCTemplate:是spring提供一套访问数据库的技术,应用性强,相对好理解5.声明式…...
gogps 利用广播星历解算卫星位置matlab函数satellite_orbits详细注解版
主要注释了广播星历计算GPS BDS卫星位置的。 function [satp, satv] satellite_orbits(t, Eph, sat, sbas)% SYNTAX: % [satp, satv] satellite_orbits(t, Eph, sat, sbas); % % INPUT: % t clock-corrected GPS time % Eph ephemeris matrix % sat satellite…...

Oracle按照某一字段值排序并显示,相同的显示序号
Oracle按照某一字段值排序并显示,相同的显示序号 最近的工作遇到对于相同的字段,按照序号去显示值,并对相同的值进行排序 实验了半天,感觉满意的答案,分享给大家 第一种: ROW_NUMBER 语法: ROW_NUMBER() OVER (ORDER BY your_column) AS sequence_number 说明: 根据your_column…...

【Java基础】String详解
文章目录 String一、String 概述1、基本特性2、不可变性3、String、StringBuilder、StringBuffer 二、字符串 创建与内存分配1、字面量 / 双引号2、new关键字3、StringBuilder.toString()4、intern() 方法5、小结 三、字符串 拼接1、常量常量2、变量 拼接3、final变量 拼接4、拼…...

cmd命令
常用命令 查看电脑名称: hostname 查看网卡信息: ipconfig 快速打开网络设置界面: control.exe netconnections 或 rundll32.exe shell32.dll,Control_RunDLL ncpa.cpld 打开防火墙设置: wf.msc 指定网卡设置IP地址&#…...

《中文Python穿云箭量化平台二次开发技术11》股票基本信息获取分析及应用示例【前十大股东占股比例提取及分析】
《中文Python穿云箭量化平台二次开发技术11》股票基本信息获取分析及应用示例【前十大股东占股比例提取及分析】 《中文Python穿云箭量化平台》是纯Python开发的量化平台,因此其中很多Python模块,我们可以自己设计新的量化工具,例如自己新的行…...
OSINT技术情报精选·2024年9月第1周
OSINT技术情报精选2024年9月第1周 2024.8.15版权声明:本文为博主chszs的原创文章,未经博主允许不得转载。 1、中国信通院:《大模型落地路线图研究报告(2024年)》 近年来,大模型技术能力不断创出新高,产业应用持续走…...

在WPS中如何启用宏VBA wps.vba.exe下载和安装
首先我们点击导航栏中的【工具】,点击左侧 运行宏,根据提示 点击 立即加载。加载卡在50%时间比较长,耐心等待。 关闭wps重新打开后, word和xls表格都可以使用了。 如果电脑无法联网,需要提前下载 WPS VBA插件 WPS VB…...
12.7Swing控件5 JProgressBar
Swing 进度条(JProgressBar)是用于可视化展示任务完成进度的组件,通常用于显示长时间运行任务的完成百分比。以下是关于 Swing 进度条的详细介绍: 1. 基本概念与用途 作用:直观展示任务完成进度,避免用户…...
python版若依框架开发:集成Dash应⽤
python版若依框架开发 从0起步,扬帆起航。 python版若依部署代码生成指南,迅速落地CURD!项目结构解析前端开发规范后端开发规范集成Dash应⽤文章目录 python版若依框架开发后端部分1.安装 Dash2.在 sub_applications 目录下新建 dash_app.py ⽂件3.在 sub_applications/han…...

理解世界如淦泽,穿透黑幕需老谋
理解世界如淦泽,穿透黑幕需老谋 卡西莫多 2025年06月07日 安徽 极少主动跟别人提及恩师的名字,生怕自己比孙猴子不成器但又比它更能惹事的德行,使得老师跟着被拖累而脸上无光。不过老师没有象菩提祖师训诫孙猴子那样不能说出师傅的名字&a…...
OpenCV 图像通道的分离与合并
一、知识点 1、一张彩色图像可以由R、G、B三个通道的灰度图合并而成。 2、void split(InputArray m, OutputArrayOfArrays mv); (1)、将多通道阵列划分为几个单通道阵列。 (2)、参数说明: m: 要分离的多通道阵列。 mv: 输出的vector容器,每个元素都…...

macOS 升级 bash 到最新版本
macOS 的默认「终端」,千年不变的版本。 》〉bash --version GNU bash, version 3.2.57(1)-release (arm64-apple-darwin24) Copyright (C) 2007 Free Software Foundation, Inc. 官方 bash.git - bash 已经将 bash 升级到了 5.2的大版本。 macOS 最新版系统的 ba…...
SQL-labs通关(level1-22)
SQL-labs靶场详解 靶场下载 靶场下载地址 关卡 level1联合注入 用order by语句来查询字段数 顺便提一下,使用联合注入语句union select也可以查询字段数,在不能使用order by的情况下,可以使用union select来查询字段数。这里我们通过查询…...

生成模型+两种机器学习范式
生成模型:从数据分布到样本创造 生成模型(Generative Model) 是机器学习中一类能够学习数据整体概率分布,并生成新样本的模型。其核心目标是建模输入数据 x 和标签 y 的联合概率分布 P(x,y),即回答 “数据是如何产生的…...
C++动态规划-线性DP
这是一套C线性DP题目的答案。如果需要题目,请私信我,我将会更新题干 P1:单子序列最大和 #include <bits/stdc.h> using namespace std; int n,A,B,C; int a[200005]; int s[200005]; int main() {ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)…...
Vue解决开发环境 Ajax 跨域问题
一、前言 在使用 Vue 进行前后端分离开发时,前端通常运行在本地开发服务器(如 http://localhost:8080),而后端接口可能部署在其他域名或端口下(如 http://api.example.com:3000)。这时就可能出现 跨域&…...