c++:-4
上一节学习了C++的数组,指针和字符串,c++:-3。本节学习C++的继承与派生:
继承
继承和派生的关系
- 继承与派生是同一过程从不同的角度看
保持已有类的特性而构造新类的过程称为继承
在已有类的基础上新增自己的特性而产生新类的过程称为派生。 - 被继承的已有类称为基类(或父类)
- 派生出的新类称为派生类(或子类)
- 直接参与派生出某类的基类称为直接基类
- 基类的基类甚至更高层的基类称为间接基类
继承与派生的目的
继承的目的:实现设计与代码的重用。
派生的目的:当新的问题出现,原有程序无法解决(或不能完全解决)时,需要对原有程序进行改造。
继承定义
单继承
(1)语法:
class 派生类名:继承方式 基类名
{
成员声明;
}
(2)例:
class Derived: public Base
{
public:
Derived ();
~Derived ();
};
多继承
(1)语法
class 派生类名:继承方式1 基类名1,继承方式2 基类名2,...
{
成员声明;
}
注意:每一个“继承方式”,只用于限制对紧随其后之基类的继承。
(2)例:
class Derived: public Base1, private Base2
{
public:
Derived ();
~Derived ();
};
继承方式
(1)不同继承方式的影响主要体现在:
- 派生类成员对基类成员的访问权限
- 通过派生类对象对基类成员的访问权限
(2)有三种:公有继承、私有继承、保护继承
公有继承(public)
(1)继承的访问控制
- 基类的public和protected成员:访问属性在派生类中保持不变;
- 基类的private成员:不可直接访问。
(2)访问权限
- 派生类中的成员函数:可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员;
- 通过派生类的对象:只能访问public成员。
(3)举例
//Point.h
#ifndef _POINT_H
#define _POINT_H
class Point {
//基类Point类的定义
public:
//公有函数成员
void initPoint(float x = 0, float y = 0){
this->x = x;
this->y = y;
}
void move(float offX, float offY){
x += offX;
y += offY;
}
float getX() const { return x; }
float getY() const { return y; }
private:
//私有数据成员
float x, y;
};
#endif //_POINT_H
//Rectangle.h
#ifndef _RECTANGLE_H
#define _RECTANGLE_H
#include "Point.h"
class Rectangle: public Point {
//派生类定义部分
public:
//新增公有函数成员
void initRectangle(float x, float y, float w, float h) {
initPoint(x, y); //调用基类公有成员函数
this->w = w;
this->h = h;
}
float getH() const { return h; }
float getW() const { return w; }
private:
//新增私有数据成员
float w, h;
};
#endif //_RECTANGLE_H
//main.cpp
#include <iostream>
#include "Rectangle.h"
using namespace std;
int main() {
Rectangle rect; //定义Rectangle类的对象
//设置矩形的数据
rect.initRectangle(2, 3, 20, 10);
rect.move(3,2); //移动矩形位置
cout << "The data of rect(x,y,w,h): " << endl;
//输出矩形的特征参数
cout << rect.getX() <<", "
<< rect.getY() << ", "
<< rect.getW() << ", "
<< rect.getH() << endl;
return 0;
}
输出:
The data of rect(x,y,w,h):
5, 5, 20, 10
私有继承(private)
(1)继承的访问控制
- 基类的public和protected成员:都以private身份出现在派生类中;
- 基类的private成员:不可直接访问。
(2)访问权限
- 派生类中的成员函数:可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员;
- 通过派生类的对象:不能直接访问从基类继承的任何成员。
(3)举例:
//Point.h
#ifndef _POINT_H
#define _POINT_H
class Point { //基类Point类的定义
public: //公有函数成员
void initPoint(float x = 0, float y = 0)
{ this->x = x; this->y = y;}
void move(float offX, float offY)
{ x += offX; y += offY; }
float getX() const { return x; }
float getY() const { return y; }
private: //私有数据成员
float x, y;
};
#endif //_POINT_H
//Rectangle.h
#ifndef _RECTANGLE_H
#define _RECTANGLE_H
#include "Point.h"
class Rectangle: private Point { //派生类定义部分
public: //新增公有函数成员
void initRectangle(float x, float y, float w, float h) {
initPoint(x, y); //调用基类公有成员函数
this->w = w;
this->h = h;
}
void move(float offX, float offY) { Point::move(offX, offY); }
float getX() const { return Point::getX(); }
float getY() const { return Point::getY(); }
float getH() const { return h; }
float getW() const { return w; }
private: //新增私有数据成员
float w, h;
};
#endif //_RECTANGLE_H
//main.cpp
#include <iostream>
#include <cmath>
#include "Rectangle.h"
using namespace std;
int main() {
Rectangle rect; //定义Rectangle类的对象
rect.initRectangle(2, 3, 20, 10); //设置矩形的数据
rect.move(3,2); //移动矩形位置
cout << "The data of rect(x,y,w,h): " << endl;
cout << rect.getX() <<", " //输出矩形的特征参数
<< rect.getY() << ", "
<< rect.getW() << ", "
<< rect.getH() << endl;
return 0;
}
保护继承(protected)
(1)继承的访问控制
- 基类的public和protected成员:都以protected身份出现在派生类中;
- 基类的private成员:不可直接访问。
(2)访问权限
- 派生类中的成员函数:可以直接访问基类中的public和protected成员,但不能直接访问基类的private员;
- 通过派生类的对象:不能直接访问从基类继承的任何成员。
(3)protected 成员的特点与作用
- 对建立其所在类对象的模块来说,它与 private 成员的性质相同。
- 对于其派生类来说,它与 public 成员的性质相同。
- 既实现了数据隐藏,又方便继承,实现代码重用。
- 如果派生类有多个基类,也就是多继承时,可以用不同的方式继承每个基类。
(4)举例:单继承
class A {
protected:
int x;
};
class B: public A{
public:
void function();
};
void B::function() {
x = 5; //正确,派生类成员函数可以访问
}
int main() {
A a;
a.x = 5;//错误,派生类对象可以访问
}
(5)举例:多继承
class A {
public:
void setA(int);
void showA() const;
private:
int a;
};
class B {
public:
void setB(int);
void showB() const;
private:
int b;
};
class C : public A, private B {
public:
void setC(int, int, int);
void showC() const;
private:
int c;
};
void A::setA(int x) {
a=x;
}
void B::setB(int x) {
b=x;
}
void C::setC(int x, int y, int z) {
//派生类成员直接访问基类的
//公有成员
setA(x);
setB(y);
c = z;
}
//其他函数实现略
int main() {
C obj;
obj.setA(5);
obj.showA();
obj.setC(6,7,9);
obj.showC();
// obj.setB(6); 错误,private继承的派生类对象类外不能访问
// obj.showB(); 错误
return 0;
}
基类与派生类转换
(1)公有派生类对象可以被当作基类的对象使用,反之则不可。
- 派生类的对象可以隐含转换为基类对象;
- 派生类的对象可以初始化基类的引用;
- 派生类的指针可以隐含转换为基类的指针。
(2)通过基类对象名、指针只能使用从基类继承的成员。
(3)举例
#include <iostream>
using namespace std;
class Base1 { //基类Base1定义
public:
void display() const {
cout << "Base1::display()" << endl;
}
};
class Base2: public Base1 { //公有派生类Base2定义
public:
void display() const {
cout << "Base2::display()" << endl;
}
};
class Derived: public Base2 { //公有派生类Derived定义
public:
void display() const {
cout << "Derived::display()" << endl;
}
};
void fun(Base1 *ptr) { //参数为指向基类对象的指针
ptr->display(); //"对象指针->成员名"
}
int main() { //主函数
Base1 base1; //声明Base1类对象
Base2 base2; //声明Base2类对象
Derived derived; //声明Derived类对象
fun(&base1); //用Base1对象的指针调用fun函数
fun(&base2); //用Base2对象的指针调用fun函数
fun(&derived); //用Derived对象的指针调用fun函数
return 0;
}
输出:
Base1::display()
Base1::display()
Base1::display()
派生类
构造函数
(1)默认情况:
- 基类的构造函数不被继承;
- 派生类需要定义自己的构造函数。
(2)C++11规定:
using B::B;
派生类新增成员可以通过类内初始值进行初始化。
可用using语句继承基类构造函数。
但是只能初始化从基类继承的成员。
(3) 建议:
如果派生类有自己新增的成员,且需要通过构造函数初始化,则派生类要自定义构造函数。
(4)若不继承基类的构造函数
派生类新增成员:派生类定义构造函数初始化;
继承来的成员:自动调用基类构造函数进行初始化;
派生类的构造函数需要给基类的构造函数传递参数。
单继承
派生类只有一个直接基类的情况,是单继承。单继承时,派生类的构造函数只需要给一个直接基类构造函数传递参数。
(1)语法
派生类名::派生类名(基类所需的形参,本类成员所需的形参):基类名(参数表), 本类成员初始化列表
{
//其他初始化;
};
(2)举例
#include<iostream>
using namespace std;
class B {
public:
B();
B(int i);
~B();
void print() const;
private:
int b;
};
B::B() {
b=0;
cout << "B's default constructor called." << endl;
}
B::B(int i) {
b=i;
cout << "B's constructor called." << endl;
}
B::~B() {
cout << "B's destructor called." << endl;
}
void B::print() const {
cout << b << endl;
}
class C: public B {
public:
C();
C(int i, int j);
~C();
void print() const;
private:
int c;
};
C::C() {
c = 0;
cout << "C's default constructor called." << endl;
}
C::C(int i,int j): B(i), c(j){
cout << "C's constructor called." << endl;
}
C::~C() {
cout << "C's destructor called." << endl;
}
void C::print() const {
B::print();
cout << c << endl;
}
int main() {
C obj(5, 6);
obj.print();
return 0;
}
输出:
B's constructor called.
C's constructor called.
5
6
C's destructor called.
B's destructor called.
多继承
多继承时,有多个直接基类,如果不继承基类的构造函数,派生类构造函数需要给所有基类构造函数传递参数。
(1)语法
派生类名::派生类名(参数表) : 基类名1(基类1初始化参数表), 基类名2(基类2初始化参数表), ...基类名n(基类初始化参数表), 本类成员初始化列表
{
//其他初始化;
};
(2)派生类与基类的构造函数
当基类有默认构造函数时:
派生类构造函数可以不向基类构造函数传递参数。
构造派生类的对象时,基类的默认构造函数将被调用。如需执行基类中带参数的构造函数
派生类构造函数应为基类构造函数提供参数。
(3)多继承且有对象成员时派生的构造函数定义
派生类名::派生类名(形参表):基类名1(参数), 基类名2(参数), ..., 基类名n(参数), 本类成员(含对象成员)初始化列表
{
//其他初始化
};
执行顺序
(1)调用基类构造函数。
- 顺序按照它们被继承时声明的顺序(从左向右)。
(2)对初始化列表中的成员进行初始化。
- 顺序按照它们在类中定义的顺序
- 对象成员初始化时自动调用其所属类的构造函数。由初始化列表提供参数。
(3)执行派生类的构造函数体中的内容。
举例
#include <iostream>
using namespace std;
class Base1 {//基类Base1,构造函数有参数
public:
Base1(int i)
{ cout << "Constructing Base1 " << i << endl; }
};
class Base2 {//基类Base2,构造函数有参数
public:
Base2(int j)
{ cout << "Constructing Base2 " << j << endl; }
};
class Base3 {//基类Base3,构造函数无参数
public:
Base3()
{ cout << "Constructing Base3 *" << endl; }
};
class Derived: public Base2, public Base1, public Base3 {
public:
Derived(int a, int b, int c, int d): Base1(a), member2(d), member1(c), Base2(b)
//此处的次序与构造函数的执行次序无关
{ }
private:
Base1 member1;
Base2 member2;
Base3 member3;
};
int main() {
Derived obj(1, 2, 3, 4);
return 0;
}
输出:
//先调用基类构造函数
Constructing Base2 2
Constructing Base1 1
Constructing Base3 *
//再对初始化列表中的成员进行初始化。
Constructing Base1 3
Constructing Base2 4
Constructing Base3 *
复制构造函数
(1)派生类未定义复制构造函数的情况
- 编译器会在需要时生成一个隐含的复制构造函数;
- 先调用基类的复制构造函数;
- 再为派生类新增的成员执行复制。
(2)派生类定义了复制构造函数的情况
- 一般都要为基类的复制构造函数传递参数。
- 复制构造函数只能接受一个参数,既用来初始化派生类定义的成员,也将被传递给基类的复制构造函数。
- 基类的复制构造函数形参类型是基类对象的引用,实参可以是派生类对象的引用
- 例如: C::C(const C &c1): B(c1) {…}
析构函数
- 析构函数不被继承,派生类如果需要,要自行声明析构函数。
- 声明方法与无继承关系时类的析构函数相同。
- 不需要显式地调用基类的析构函数,系统会自动隐式调用。
- 先执行派生类析构函数的函数体,再调用基类的析构函数。
调用顺序
#include <iostream>
using namespace std;
class Base1 {
public:
Base1(int i)
{ cout << "Constructing Base1 " << i << endl; }
~Base1() { cout << "Destructing Base1" << endl; }
};
class Base2 {
public:
Base2(int j)
{ cout << "Constructing Base2 " << j << endl; }
~Base2() { cout << "Destructing Base2" << endl; }
};
class Base3 {
public:
Base3() { cout << "Constructing Base3 *" << endl; }
~Base3() { cout << "Destructing Base3" << endl; }
};
class Derived: public Base2, public Base1, public Base3 {
public:
Derived(int a, int b, int c, int d): Base1(a), member2(d), member1(c), Base2(b)
{ }
private:
Base1 member1;
Base2 member2;
Base3 member3;
};
int main() {
Derived obj(1, 2, 3, 4);
return 0;
}
输出:
//构造函数
Constructing Base2 2
Constructing Base1 1
Constructing Base3 *
Constructing Base1 3
Constructing Base2 4
Constructing Base3 *
//析构函数
Destructing Base3
Destructing Base2
Destructing Base1
Destructing Base3
Destructing Base1
Destructing Base2
虚基类
访问从基类继承的成员
(1)当派生类与基类中有相同成员时:
- 若未特别限定,则通过派生类对象使用的是派生类中的同名成员。
- 如要通过派生类对象访问基类中被隐藏的同名成员,应使用基类名和作用域操作符(::)来限定。
(2)举例
#include <iostream>
using namespace std;
class Base1 {
public:
int var;
void fun() { cout << "Member of Base1" << endl; }
};
class Base2 {
public:
int var;
void fun() { cout << "Member of Base2" << endl; }
};
class Derived: public Base1, public Base2 {
public:
int var;
void fun() { cout << "Member of Derived" << endl; }
};
int main() {
Derived d;
Derived *p = &d;
//访问Derived类成员
d.var = 1;
d.fun();
//访问Base1基类成员
d.Base1::var = 2;
d.Base1::fun();
//访问Base2基类成员
p->Base2::var = 3;
p->Base2::fun();
return 0;
}
输出:
Member of Derived
Member of Base1
Member of Base2
二义性问题
如果从不同基类继承了同名成员,但是在派生类中没有定义同名成员,“派生类对象名或引用名.成员名”、“派生类指针->成员名”访问成员存在二义性问题
- 解决方式:用类名限定
举例
//7_7.cpp
#include <iostream>
using namespace std;
class Base0 { //定义基类Base0
public:
int var0;
void fun0() { cout << "Member of Base0" << endl; }
};
class Base1: public Base0 { //定义派生类Base1
public: //新增外部接口
int var1;
};
class Base2: public Base0 { //定义派生类Base2
public: //新增外部接口
int var2;
};
class Derived: public Base1, public Base2 {
public:
int var;
void fun()
{ cout << "Member of Derived" << endl; }
};
int main() { //程序主函数
Derived d;
d.Base1::var0 = 2;
d.Base1::fun0();
d.Base2::var0 = 3;
d.Base2::fun0();
return 0;
}
虚基类
需要解决的问题
当派生类从多个基类派生,而这些基类又共同基类,则在访问此共同基类中的成员时,将产生冗余,并有可能因冗余带来不一致性
声明
以virtual说明基类继承方式
例:class B1:virtual public B
作用
- 主要用来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题
- 为最远的派生类提供唯一的基类成员,而不重复产生多次复制
注意:
在第一级继承时就要将共同基类设计为虚基类。
举例
#include <iostream>
using namespace std;
class Base0 {
public:
int var0;
void fun0() { cout << "Member of Base0" << endl; }
};
class Base1: virtual public Base0 {
public:
int var1;
};
class Base2: virtual public Base0 {
public:
int var2;
};
class Derived: public Base1, public Base2 {
//定义派生类Derived
public:
int var;
void fun() {
cout << "Member of Derived" << endl;
}
};
int main() {
Derived d;
d.var0 = 2; //直接访问虚基类的数据成员
d.fun0(); //直接访问虚基类的函数成员
return 0;
}
构造函数
- 建立对象时所指定的类称为最远派生类。
- 虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的。
- 在整个继承结构中,直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化表中为虚基类的构造函数列出参数。如果未列出,则表示调用该虚基类的默认构造函数。
- 在建立对象时,只有最远派生类的构造函数调用虚基类的构造函数,其他类对虚基类构造函数的调用被忽略。
举例:
#include <iostream>
using namespace std;
class Base0 {
public:
Base0(int var) : var0(var) { }
int var0;
void fun0() { cout << "Member of Base0" << endl; }
};
class Base1: virtual public Base0 {
public:
Base1(int var) : Base0(var) { }
int var1;
};
class Base2: virtual public Base0 {
public:
Base2(int var) : Base0(var) { }
int var2;
};
class Derived: public Base1, public Base2 {
public:
Derived(int var) : Base0(var), Base1(var), Base2(var)
{ }
int var;
void fun()
{ cout << "Member of Derived" << endl; }
};
int main() { //程序主函数
Derived d(1);
d.var0 = 2; //直接访问虚基类的数据成员
d.fun0(); //直接访问虚基类的函数成员
return 0;
}
举例:
Member of Base0
程序
(1)
#include <iostream>
using namespace std;
class Animal {
//int age;
public:
int age;
};
class Dog : Animal {
public:
void SetAge(int n) {age = n;}
};
int main()
{
Dog d;
d.SetAge(2);
return 0;
}
(2)
#include <iostream>
using namespace std;
class BaseClass {
public:
int Number;
int getNumber() {return Number;}
BaseClass(){ cout<<"BaseClass construct"<<endl;}
~BaseClass(){ cout<<"BaseClass destruct"<<endl;}
};
class DerivedClass : BaseClass {
public:
DerivedClass() {
Number = 0;
Number ++;
cout << "DerivedClass Construction. Number = " << getNumber() << endl;
}
~DerivedClass() {
Number --;
cout << "DerivedClass Destruction. Number = " << getNumber() << endl;
}
};
int main()
{
DerivedClass d;
return 0;
}
说明:
BaseClass construct
DerivedClass Construction. Number = 1
DerivedClass Destruction. Number = 0
BaseClass destruct
(3)
#include <iostream>
using namespace std;
class Vehicle
{
public:
int MaxSpeed;
int Weight;
void Run(){cout<<"Vehicle run"<<endl;};
void Stop(){cout<<"Vehicle stop"<<endl;};
};
class Bicycle : virtual public Vehicle
{
public:
int Height;
};
class Motorcar : virtual public Vehicle
{
public:
int SeatNum;
};
class Motorcycle : public Bicycle, public Motorcar
{
};
int main()
{
Motorcycle m;
m.Height=1;
m.SeatNum=2;
m.Weight=100;
m.MaxSpeed=60;
m.Run();
m.Stop();
return 0;
}
c++:-4的更多相关文章
- java web 开发三剑客 -------电子书
Internet,人们通常称为因特网,是当今世界上覆盖面最大和应用最广泛的网络.根据英语构词法,Internet是Inter + net,Inter-作为前缀在英语中表示“在一起,交互”,由此可知In ...
- 所有selenium相关的库
通过爬虫 获取 官方文档库 如果想获取 相应的库 修改对应配置即可 代码如下 from urllib.parse import urljoin import requests from lxml im ...
- In-Memory:内存数据库
在逝去的2016后半年,由于项目需要支持数据的快速更新和多用户的高并发负载,我试水SQL Server 2016的In-Memory OLTP,创建内存数据库实现项目的负载需求,现在项目接近尾声,系统 ...
- 从直播编程到直播教育:LiveEdu.tv开启多元化的在线学习直播时代
2015年9月,一个叫Livecoding.tv的网站在互联网上引起了编程界的注意.缘于Pingwest品玩的一位编辑在上网时无意中发现了这个网站,并写了一篇文章<一个比直播睡觉更奇怪的网站:直 ...
- 【.net 深呼吸】细说CodeDom(8):分支与循环
有人会问,为啥 CodeDom 不会生成 switch 语句,为啥没生成 while 语句之类.要注意,CodeDom只关心代码逻辑,而不是语法,语法是给写代码的人用的.如果用.net的“反编译”工具 ...
- 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新
本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...
- App开发:模拟服务器数据接口 - MockApi
为了方便app开发过程中,不受服务器接口的限制,便于客户端功能的快速测试,可以在客户端实现一个模拟服务器数据接口的MockApi模块.本篇文章就尝试为使用gradle的android项目设计实现Moc ...
- TODO:macOS编译PHP7.1
TODO:macOS编译PHP7.1 本文主要介绍在macOS上编译PHP7.1,有兴趣的朋友可以去尝试一下. 1.下载PHP7.1源码,建议到PHP官网下载纯净到源码包php-7.1.0.tar.g ...
- In-Memory:在内存中创建临时表和表变量
在Disk-Base数据库中,由于临时表和表变量的数据存储在tempdb中,如果系统频繁地创建和更新临时表和表变量,大量的IO操作集中在tempdb中,tempdb很可能成为系统性能的瓶颈.在SQL ...
- In-Memory:内存优化表的事务处理
内存优化表(Memory-Optimized Table,简称MOT)使用乐观策略(optimistic approach)实现事务的并发控制,在读取MOT时,使用多行版本化(Multi-Row ve ...
随机推荐
- kafka-linux-install
linux按照kafka 必须先按照java jdk包!!!!!!!!!!!! 先安装zookeeper 下载:http://mirrors.hust.edu.cn/apache/zookeeper/ ...
- 什么是切点JoinPoint?
程序运行中的一些时间点, 例如一个方法的执行, 或者是一个异常的处理. 在 Spring AOP 中, join point 总是方法的执行点.
- python学习笔记(五)——模块导入
模块是一个包含所有你定义的函数和变量的文件,其后缀名是.py.模块可以被别的程序引入,以使用该模块中的函数等功能.这也是使用 python 标准库的方法. 1.模块的定义与分类 在python中模块实 ...
- 推荐一些好用的 HTML5 & JavaScript 游戏引擎开发库
推荐一些好用的 HTML5 & JavaScript 游戏引擎开发库 0. 引言 如果你是一个游戏开发者,并且正在寻找一个可以与 JavaScript 和 HTML5 无缝工作的游戏引擎.那么 ...
- 8 个有用的 HTML5 标签
作为一个 web 前端开发者,在制作页面的时候你会从一大堆不同的标签中选择合适的标签来完成相应的功能.有些 HTML5 标签广为流传,例如 <article> <header> ...
- 微信小程序自定义tab,多层tab嵌套实现
小程序最近是越来越火了-- 做小程序有一段时间了,总结一下项目中遇到的问题及解决办法吧. 项目中有个多 tab 嵌套的需求,进入程序主界面下面有两个 tab,进入A模块后,A模块最底下又有多个tab, ...
- java中如何能知道应该捕获什么样的异常?举例
我怎么知道应该捕获什么样的异常? 马克-to-win:如上例1.1:开始没加try时,程序崩溃,系统打印的是如下的错误,Exception in thread "main" jav ...
- 在vue中实现点击哪个哪个区域变化背景色和字体颜色,其他默认(uni-app框架中也可以使用)
template: 1 <view class="wrap"> 2 <view class="total" :class="{ se ...
- 基于node实现一个简单的脚手架工具(node控制台交互项目)
实现控制台输入输出 实现文件读写操作 全原生实现一个简单的脚手架工具 实现vue-cli2源码 一.实现控制台输入输出 关于控制台的输入输出依然是基于node进程管理对象process,在proces ...
- 《头号玩家》AI电影调研报告(五)
4.VR自由行走跑步机 电影中,它可以让玩家朝任意方向无限奔跑并保持在平台最中央,还可以模拟上台阶和走斜坡的情况.而下面这件VR跑步装备,可以让你在游戏世界穿行自如. 奥地利创意公司Cyberith公 ...