Files
张宗平 13b3ebbe14 课程代码仓库初始化:Part1 (144) + Part3 (29) 全量生成与验证
把川大 wiki「02.Qt方向」课程代码整理为 CMake 管理的可编译运行项目。

Part1 C/C++ 强化 (p01/, 144 目标, host gcc/g++ 构建, 全绿):
- 修复全部编译失败:补 <cstring>/<fstream>/<iomanip>/<vector> 等缺失头
  (common_fix + infer_stl_includes 自动检测注入)、修正 pause() 壳注入
  (改检测已转换后的 pause 调用)、namespace 提升出函数体、const 成员函数
  正确性、dedupe 不再破坏函数重载集、K&R 隐式 int / 动态异常规范等
  C++17 移除特性的可编译等价改写。
- 目录采用深缩写 p01/ch04/s03/ (原 part1_cpp/ch04_class_object/03_ctor_dtor)。

Part3 Qt 桌面 (p03/, 29 目标, 隔离 Qt 5.14.2 构建, 全绿):
- gen_part3.py: Qt 外壳模板 (main+QApplication+show, QTimer 自动退出便于
  offscreen 验证)、AUTOMOC + .moc include、rewrite_* 处理散文混入/重载/
  资源依赖、生成占位 png/gif + .qrc + rundata 样例数据。
- 修正 QLable 笔误、全角分号、.→->、Windows 路径、QApplication 阻塞事件
  循环改为 QCoreApplication 等。

任务1 概念/版本验证:
- tools/std_probe.py: 跨 -std= 编译探针 (--pedantic-errors 让已废除特性硬失败)。
- tools/demo_versions/: K&R 隐式 int、三目左值、void* 转换、enum 赋整、
  const 经指针、动态异常规范 共 6 个跨版本演示。
- docs/VERSION_NOTES.md: 每条「原文说法→C17/C++17 是否成立→何版本成立→已验证」。

文档: 顶层 README、docs/SOURCE_PAGES.md (页面→目录映射)、
docs/ERRATA.md (Part1 修正)、docs/ERRATA_part3.md (Part3 修正)。
另修复 tools/run.sh / run_qt.sh 的 ${1:?...} 引号语法 bug。

隔离 Qt 验证: ldd 0 系统 Qt 引用; 173/173 目标全绿; p03 offscreen 运行通过。
2026-06-30 14:16:55 +08:00

1 line
21 KiB
JSON
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"id":"58954469","type":"page","status":"current","title":"08.多态","version":{"by":{"type":"known","username":"ethanliu","userKey":"2c90931b6cfb654f016d5c69becc0001","profilePicture":{"path":"/images/icons/profilepics/default.svg","width":48,"height":48,"isDefault":true},"displayName":"刘浩","_links":{"self":"https://wiki.suncaper.net/rest/api/user?key=2c90931b6cfb654f016d5c69becc0001"},"_expandable":{"status":""}},"when":"2022-07-09T01:51:24.000Z","message":"","number":1,"minorEdit":false,"hidden":false,"_links":{"self":"https://wiki.suncaper.net/rest/experimental/content/58954469/version/1"},"_expandable":{"content":"/rest/api/content/58954469"}},"body":{"storage":{"value":"<ac:layout><ac:layout-section ac:type=\"two_right_sidebar\"><ac:layout-cell><h2><span>4.8 </span>多态</h2><h3><span>4.8.1 </span>多态基本概念</h3><p>多态是面向对象程序设计语言中数据抽象和继承之外的第三个基本特征。</p><p>多态性<span>(polymorphism)</span>提供接口与具体实现之间的另一层隔离,从而将”<span>what</span>”和”<span>how</span>”分离开来。多态性改善了代码的可读性和组织性,同时也使创建的程序具有可扩展性,项目不仅在最初创建时期可以扩展,而且当项目在需要有新的功能时也能扩展。</p><p>c++支持编译时多态<span>(</span>静态多态<span>)</span>和运行时多态<span>(</span>动态多态<span>)</span>,运算符重载和函数重载就是编译时多态,而派生类和虚函数实现运行时多态。</p><p>静态多态和动态多态的区别就是函数地址是早绑定<span>(</span>静态联编<span>)</span>还是晚绑定<span>(</span>动态联编<span>)</span>。如果函数的调用,在编译阶段就可以确定函数的调用地址,并产生代码,就是静态多态<span>(</span>编译时多态<span>)</span>,就是说地址是早绑定的。而如果函数的调用地址不能编译不能在编译期间确定,而需要在运行时才能决定,这这就属于晚绑定<span>(</span>动态多态<span>,</span>运行时多态<span>)</span>。</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"62728a08-0d09-4a97-8612-2fd1e94a7ec4\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[//计算器\nclass Caculator{\npublic:\n\tvoid setA(int a){\n\t\tthis->mA = a;\n\t}\n\tvoid setB(int b){\n\t\tthis->mB = b;\n\t}\n\tvoid setOperator(string oper){\n\t\tthis->mOperator = oper;\n\t}\n\tint getResult(){\n\t\t\n\t\tif (this->mOperator == \"+\"){\n\t\t\treturn mA + mB;\n\t\t}\n\t\telse if (this->mOperator == \"-\"){\n\t\t\treturn mA - mB;\n\t\t}\n\t\telse if (this->mOperator == \"*\"){\n\t\t\treturn mA * mB;\n\t\t}\n\t\telse if (this->mOperator == \"/\"){\n\t\t\treturn mA / mB;\n\t\t}\n\t}\nprivate:\n\tint mA;\n\tint mB;\n\tstring mOperator;\n};\n\n//这种程序不利于扩展,维护困难,如果修改功能或者扩展功能需要在源代码基础上修改\n//面向对象程序设计一个基本原则:开闭原则(对修改关闭,对扩展开放)\n\n//抽象基类\nclass AbstractCaculator{\npublic:\n\tvoid setA(int a){\n\t\tthis->mA = a;\n\t}\n\tvirtual void setB(int b){\n\t\tthis->mB = b;\n\t}\n\tvirtual int getResult() = 0;\nprotected:\n\tint mA;\n\tint mB;\n\tstring mOperator;\n};\n\n//加法计算器\nclass PlusCaculator : public AbstractCaculator{\npublic:\n\tvirtual int getResult(){\n\t\treturn mA + mB;\n\t}\n};\n\n//减法计算器\nclass MinusCaculator : public AbstractCaculator{\npublic:\n\tvirtual int getResult(){\n\t\treturn mA - mB;\n\t}\n};\n\n//乘法计算器\nclass MultipliesCaculator : public AbstractCaculator{\npublic:\n\tvirtual int getResult(){\n\t\treturn mA * mB;\n\t}\n};\n\nvoid DoBussiness(AbstractCaculator* caculator){\n\tint a = 10;\n\tint b = 20;\n\tcaculator->setA(a);\n\tcaculator->setB(b);\n\tcout << \"计算结果:\" << caculator->getResult() << endl;\n\tdelete caculator;\n}]]></ac:plain-text-body></ac:structured-macro><h3><span>4.8.2 </span>向上类型转换及问题</h3><h4><span>4.8.2.1 </span>问题抛出</h4><p>对象可以作为自己的类或者作为它的基类的对象来使用。还能通过基类的地址来操作它。取一个对象的地址<span>(</span>指针或引用<span>)</span>,并将其作为基类的地址来处理,这种称为向上类型转换。</p><p>也就是说:父类引用或指针可以指向子类对象,通过父类指针或引用来操作子类对象。</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"f0b2809b-832b-4ede-a942-ee049f61128a\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[class Animal{\npublic:\n\tvoid speak(){\n\t\tcout << \"动物在唱歌...\" << endl;\n\t}\n};\n\nclass Dog : public Animal{\npublic:\n\tvoid speak(){\n\t\tcout << \"小狗在唱歌...\" << endl;\n\t}\n};\n\nvoid DoBussiness(Animal& animal){\n\tanimal.speak();\n}\n\nvoid test(){\n\tDog dog;\n\tDoBussiness(dog);\n}]]></ac:plain-text-body></ac:structured-macro><p><span> </span></p><table><colgroup><col /></colgroup><tbody><tr><td><p><strong>运行结果</strong>: 动物在唱歌</p><p><strong>问题抛出<span>:</span></strong> 我们给<span>DoBussiness</span>传入的对象是<span>dog</span>,而不是<span>animal</span>对象,输出的结果应该是<span>Dog::speak</span>。</p></td></tr></tbody></table><p><span> </span></p><h4><span>4.8.2.1 </span>问题解决思路</h4><p>解决这个问题,我们需要了解下绑定<span>(</span>捆绑<span>,binding)</span>概念。</p><table><tbody><tr><td><p>把函数体与函数调用相联系称为绑定<span>(</span>捆绑,<span>binding)</span></p></td></tr></tbody></table><p><span> </span></p><p>当绑定在程序运行之前<span>(</span>由编译器和连接器<span>)</span>完成时,称为早绑定<span>(early binding).C</span>语言中只有一种函数调用方式,就是早绑定。</p><p>上面的问题就是由于早绑定引起的,因为编译器在只有<span>Animal</span>地址时并不知道要调用的正确函数。编译<strong>是根据指向对象的指针或引用的类型</strong>来选择函数调用。这个时候由于<span>DoBussiness</span>的参数类型是<span>Animal&amp;,</span>编译器确定了应该调用的<span>speak</span>是<span>Animal::speak</span>的,而不是真正传入的对象<span>Dog::speak</span>。</p><p>解决方法就是迟绑定<span>(</span>迟捆绑<span>,</span>动态绑定<span>,</span>运行时绑定,<span>late binding),</span>意味着绑定要根据对象的实际类型,发生在运行。</p><p>C++语言要实现这种动态绑定,必须有某种机制来确定运行时对象的类型并调用合适的成员函数。对于一种编译语言,编译器并不知道实际的对象类型(编译器并不知道<span>Animal</span>类型的指针或引用指向的实际的对象类型)。</p><h4><span>4.8.2.1 </span>问题解决方案<span>(</span>虚函数<span>,vitual function)</span></h4><p>C++动态多态性是通过虚函数来实现的,虚函数允许子类(派生类)重新定义父类(基类)成员函数,而子类(派生类)重新定义父类(基类)虚函数的做法称为覆盖<span>(override)</span>,或者称为重写。</p><p>对于特定的函数进行动态绑定,<span>c++</span>要求在基类中声明这个函数的时候使用<span>virtual</span>关键字<span>,</span>动态绑定也就对<span>virtual</span>函数起作用<span>.</span></p><ul><li>为创建一个需要动态绑定的虚成员函数,可以简单在这个函数声明前面加上<span>virtual</span>关键字,定义时候不需要<span>.</span></li><li>如果一个函数在基类中被声明为<span>virtual</span>,那么在所有派生类中它都是<span>virtual</span>的<span>.</span></li><li>在派生类中<span>virtual</span>函数的重定义称为重写<span>(override).</span></li><li>Virtual关键字只能修饰成员函数<span>.</span></li><li>构造函数不能为虚函数</li></ul><p><span> </span></p><table><colgroup><col /></colgroup><tbody><tr><td><p><strong>注意<span>: </span></strong>仅需要在基类中声明一个函数为<span>virtual.</span>调用所有匹配基类声明行为的派生类函数都将使用虚机制。虽然可以在派生类声明前使用关键字<span>virtual(</span>这也是无害的<span>)</span>,但这个样会使得程序显得冗余和杂乱。<span>(</span>我建议写上<span>)</span></p></td></tr></tbody></table><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"79f5df66-d9e9-48c7-adf3-97164a9a60f9\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[class Animal{\npublic:\n\tvirtual void speak(){\n\t\tcout << \"动物在唱歌...\" << endl;\n\t}\n};\nclass Dog : public Animal{\npublic:\n\tvirtual void speak(){\n\t\tcout << \"小狗在唱歌...\" << endl;\n\t}\n};\nvoid DoBussiness(Animal& animal){\n\tanimal.speak();\n}\nvoid test(){\n\tDog dog;\n\tDoBussiness(dog);\n}\n\n]]></ac:plain-text-body></ac:structured-macro><h3><span>4.8.3 C++</span>如何实现动态绑定</h3><p>动态绑定什么时候发生?所有的工作都是由编译器在幕后完成。当我们告诉通过创建一个<span>virtual</span>函数来告诉编译器要进行动态绑定,那么编译器就会根据动态绑定机制来实现我们的要求, 不会再执行早绑定。</p><p><br /></p><p>问题<span>:C++</span>的动态捆绑机制是怎么样的?</p><p>首先,我们看看编译器如何处理虚函数。当编译器发现我们的类中有虚函数的时候,编译器会创建一张虚函数表,把虚函数的函数入口地址放到虚函数表中,并且在类中秘密增加一个指针,这个指针就是<span>vpointer(</span>缩写<span>vptr)</span>,这个指针是指向对象的虚函数表。在多态调用的时候,根据<span>vptr</span>指针,找到虚函数表来实现动态绑定。</p><table><colgroup><col /></colgroup><tbody><tr><td><p>验证对象中的虚指针:</p></td></tr></tbody></table><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"40be2df4-f44b-4e5b-b506-c53c96e116d6\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[class A{\npublic:\n\tvirtual void func1(){}\n\tvirtual void func2(){}\n};\n\n//B类为空,那么大小应该是1字节,实际情况是这样吗?\nclass B : public A{};\n\nvoid test(){\n\tcout << \"A size:\" << sizeof(A) << endl;\n\tcout << \"B size:\" << sizeof(B) << endl;\n}]]></ac:plain-text-body></ac:structured-macro><p>在编译阶段,编译器秘密增加了一个<span>vptr</span>指针,但是此时<span>vptr</span>指针并没有初始化指向虚函数表<span>(vtable),</span>什么时候<span>vptr</span>才会指向虚函数表?在对象构建的时候,也就是在对象初始化调用构造函数的时候。编译器首先默认会在我们所编写的每一个构造函数中,增加一些<span>vptr</span>指针初始化的代码。如果没有提供构造函数,编译器会提供默认的构造函数,那么就会在默认构造函数里做此项工作,初始化<span>vptr</span>指针,使之指向本对象的虚函数表。</p><p>起初,子类继承基类,子类继承了基类的<span>vptr</span>指针,这个<span>vptr</span>指针是指向基类虚函数表,当子类调用构造函数,使得子类的<span>vptr</span>指针指向了子类的虚函数表。</p><ul><li>396px当子类无重写基类虚函数时<span>:</span></li></ul><p><ac:image><ri:attachment ri:filename=\"image2021-5-13_17-7-20.png\" /></ac:image></p><p><span> </span></p><table><colgroup><col /></colgroup><tbody><tr><td><p><strong>过程分析<span>:</span></strong></p><p>     Animal* animal = new Dog;</p><p>     animal-&gt;fun1();</p><p>     当程序执行到这里,会去<span>animal</span>指向的空间中寻找<span>vptr</span>指针,通过<span>vptr</span>指针找到<span>func1</span>函数,此时由于子类并没有重写也就是覆盖基类的<span>func1</span>函数,所以调用<span>func1</span>时,仍然调用的是基类的<span>func1.</span></p><p><strong>执行结果<span>: </span></strong>我是基类的<span>func1</span></p><p><strong>测试结论<span>: </span></strong>无重写基类的虚函数,无意义</p></td></tr></tbody></table><ul><li>当子类重写基类虚函数时<span>:</span></li></ul><p><ac:image><ri:attachment ri:filename=\"image2021-5-13_17-8-10.png\" /></ac:image></p><p><span> </span></p><table><colgroup><col /></colgroup><tbody><tr><td><p><strong>过程分析<span>:</span></strong></p><p>     Animal* animal = new Dog;</p><p>     animal-&gt;fun1();</p><p>     当程序执行到这里,会去<span>animal</span>指向的空间中寻找<span>vptr</span>指针,通过<span>vptr</span>指针找到<span>func1</span>函数,由于子类重写基类的<span>func1</span>函数,所以调用<span>func1</span>时,调用的是子类的<span>func1.</span></p><p><strong>执行结果<span>: </span></strong>我是子类的<span>func1</span></p><p><strong>测试结论<span>: </span></strong>无重写基类的虚函数,无意义</p></td></tr></tbody></table><p><span> </span></p><table><colgroup><col /></colgroup><tbody><tr><td><p><strong>多态的成立条件:</strong></p><ol><li>有继承</li><li>子类重写父类虚函数函数<ol><li>返回值,函数名字,函数参数,必须和父类完全一致<span style=\"letter-spacing: 0.0px;\">(</span><span style=\"letter-spacing: 0.0px;\">析构函数除外)</span></li><li>子类中<span style=\"letter-spacing: 0.0px;\">virtual</span><span style=\"letter-spacing: 0.0px;\">关键字可写可不写,建议写</span></li></ol></li><li><span style=\"letter-spacing: 0.0px;\">类型兼容,父类指针,父类引用 指向 子类对象</span></li></ol></td></tr></tbody></table><h3><span>4.8.4 </span>抽象基类和纯虚函数<span>(pure virtual function)</span></h3><p>在设计时,常常希望基类仅仅作为其派生类的一个接口。这就是说,仅想对基类进行向上类型转换,使用它的接口,而不希望用户实际的创建一个基类的对象。同时创建一个纯虚函数允许接口中放置成员原函数,而不一定要提供一段可能对这个函数毫无意义的代码。</p><p><span> </span></p><p>做到这点,可以在基类中加入至少一个纯虚函数<span>(pure virtual function),</span>使得基类称为抽象类<span>(abstract class).</span></p><ul><li>纯虚函数使用关键字<span>virtual</span>,并在其后面加上<span>=0</span>。如果试图去实例化一个抽象类,编译器则会阻止这种操作。<ul><li>当继承一个抽象类的时候,必须实现所有的纯虚函数,否则由抽象类派生的类也是一个抽象类。</li><li>Virtual void fun() = 0;告诉编译器在<span>vtable</span>中为函数保留一个位置,但在这个特定位置不放地址。</li></ul></li></ul><p><span> </span></p><table><colgroup><col /></colgroup><tbody><tr><td><p>   <strong>建立公共接口目的</strong>是为了将子类公共的操作抽象出来,可以通过一个公共接口来操纵一组类,且这个公共接口不需要事先<span>(</span>或者不需要完全实现<span>)</span>。可以创建一个公共类<span>.</span></p></td></tr></tbody></table><h3><span>4.8.5 </span>纯虚函数和多继承</h3><p>多继承带来了一些争议,但是接口继承可以说一种毫无争议的运用了。</p><p>绝大数面向对象语言都不支持多继承,但是绝大数面向对象对象语言都支持接口的概念,<span>c++</span>中没有接口的概念,但是可以通过纯虚函数实现接口。</p><p><span> </span></p><table><tbody><tr><td><p>    接口类中只有函数原型定义,没有任何数据定义。</p></td></tr></tbody></table><p><span> </span></p><p>多重继承接口不会带来二义性和复杂性问题。接口类只是一个功能声明,并不是功能实现,子类需要根据功能说明定义功能实现。</p><p><strong>注意<span>:</span>除了析构函数外,其他声明都是纯虚函数。</strong></p><p><span>         </span></p><h3><span>4.8.6 </span>虚析构函数</h3><h4><span>4.8.6.1 </span>虚析构函数作用</h4><p>虚析构函数是为了解决<span><a href=\"http://baike.baidu.com/view/535539.htm\">基类</a></span>的<span><a href=\"http://baike.baidu.com/view/159417.htm\">指针</a></span>指向派生类对象,并用基类的指针删除派生类对象。</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"3aac04b3-1113-47b9-8f61-e3f4f2445459\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[class People{\npublic:\n\tPeople(){\n\t\tcout << \"构造函数 People!\" << endl;\n\t}\n\tvirtual void showName() = 0;\n\tvirtual ~People(){\n\t\tcout << \"析构函数 People!\" << endl;\n\t}\n};\n\nclass Worker : public People{\npublic:\n\tWorker(){\n\t\tcout << \"构造函数 Worker!\" << endl;\n\t\tpName = new char[10];\n\t}\n\tvirtual void showName(){\n\t\tcout << \"打印子类的名字!\" << endl;\n\t}\n\t~Worker(){\n\t\tcout << \"析构函数 Worker!\" << endl;\n\t\tif (pName != NULL){\n\t\t\tdelete pName;\n\t\t}\n\t}\nprivate:\n\tchar* pName;\n};\n\nvoid test(){\n\n\tPeople* people = new Worker;\n\tpeople->~People();\n}]]></ac:plain-text-body></ac:structured-macro><h4><span>4.8.6.2 </span>纯虚析构函数</h4><p>纯虚析构函数在<span>c++</span>中是合法的,但是在使用的时候有一个额外的限制:必须为纯虚析构函数提供一个函数体。</p><p>那么问题是:如果给虚析构函数提供函数体了,那怎么还能称作纯虚析构函数呢?</p><p>纯虚析构函数和非纯析构函数之间唯一的不同之处在于纯虚析构函数使得基类是抽象类,不能创建基类的对象。</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"3310d877-671b-4bbd-941e-798b4575d580\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[//非纯虚析构函数\nclass A{\npublic:\n\tvirtual ~A();\n};\n\nA::~A(){}\n\n//纯析构函数\nclass B{\npublic:\n\tvirtual ~B() = 0;\n};\n\nB::~B(){}\n\nvoid test(){\n\tA a; //A类不是抽象类,可以实例化对象\n\tB b; //B类是抽象类,不可以实例化对象\n}]]></ac:plain-text-body></ac:structured-macro><table><colgroup><col /></colgroup><tbody><tr><td><p>如果类的目的不是为了实现多态,作为基类来使用,就不要声明虚析构函数,反之,则应该为类声明虚析构函数。</p></td></tr></tbody></table><h3><span>4.8.7 </span>重写 重载 重定义</h3><ul><li>重载,同一作用域的同名函数</li></ul><ol><li>同一个作用域</li><li>参数个数,参数顺序,参数类型不同</li><li>和函数返回值,没有关系</li><li>const也可以作为重载条件<span> //do(const Teacher&amp; t){}  do(Teacher&amp; t)</span></li></ol><ul><li>重定义(隐藏)</li></ul><ol><li>有继承</li><li>子类(派生类)重新定义父类(基类)的同名成员(非<span>virtual</span>函数)</li></ol><ul><li>重写(覆盖)</li></ul><ol><li>有继承</li><li>子类(派生类)重写父类(基类)的<span>virtual</span>函数</li><li>函数返回值,函数名字,函数参数,必须和基类中的虚函数一致</li></ol><p><br /></p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"b0d0a89f-1148-4148-8594-86696c99e58a\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[class A{\npublic:\n\t//同一作用域下,func1函数重载\n\tvoid func1(){}\n\tvoid func1(int a){}\n\tvoid func1(int a,int b){}\n\tvoid func2(){}\n\tvirtual void func3(){}\n};\n\nclass B : public A{\npublic:\n\t//重定义基类的func2,隐藏了基类的func2方法\n\tvoid func2(){}\n\t//重写基类的func3函数,也可以覆盖基类func3\n\tvirtual void func3(){}\n};\n\n]]></ac:plain-text-body></ac:structured-macro></ac:layout-cell><ac:layout-cell><p><br /></p></ac:layout-cell></ac:layout-section></ac:layout>","representation":"storage","_expandable":{"content":"/rest/api/content/58954469"}},"_expandable":{"editor":"","view":"","export_view":"","styled_view":"","anonymous_export_view":""}},"extensions":{"position":"none"},"_links":{"webui":"/pages/viewpage.action?pageId=58954469","edit":"/pages/resumedraft.action?draftId=58954469","tinyui":"/x/5ZKDAw","collection":"/rest/api/content","base":"https://wiki.suncaper.net","context":"","self":"https://wiki.suncaper.net/rest/api/content/58954469"},"_expandable":{"container":"/rest/api/space/2CT","metadata":"","operations":"","children":"/rest/api/content/58954469/child","restrictions":"/rest/api/content/58954469/restriction/byOperation","history":"/rest/api/content/58954469/history","ancestors":"","descendants":"/rest/api/content/58954469/descendant","space":"/rest/api/space/2CT"}}