Mobile wallpaper 1Mobile wallpaper 2Mobile wallpaper 3Mobile wallpaper 4Mobile wallpaper 5Mobile wallpaper 6Mobile wallpaper 7Mobile wallpaper 8
2446 字
12 分钟
Software Development with C and Cpp Week5 Seminar

C and C++: Further Classes#

TIP

FidgetCube: 解压魔方

Complete each of the exercises below using CLion. Test each exercise after implementing it; the exercise is not complete until the program works.

  1. Create a C++ project. Copy and paste the following code, which utilizes several classes with inheritance.
#include <iostream>
#include <cstdlib>
using std::string;
using std::cout;
using std::endl;
class CubeSide {
public:
virtual const char *getName() = 0;
};
class FidgetCube {
public:
CubeSide *getSide(uint8_t sideNo) const;
bool addSide(CubeSide *newSide);
protected:
CubeSide *sides[6];
uint8_t lastSide = 0;
};
bool FidgetCube::addSide(CubeSide *newSide) {
if (lastSide < 6) {
sides[lastSide] = newSide;
lastSide++;
return true;
}
return false;
}
CubeSide *FidgetCube::getSide(uint8_t sideNo) const {
return sides[sideNo];
}
class DialSide : public CubeSide {
public:
const char *getName();
void setDial(uint16_t value);
protected:
uint16_t dialValue = 0;
static constexpr char const *dialSideName = "Dial";
};
const char *DialSide::getName() {
return DialSide::dialSideName;
}
void DialSide::setDial(uint16_t value) {
if (dialValue <= 10) {
dialValue = value;
cout << "A dial was set to " << dialValue << "." << endl;
}
}
class DialCube : public FidgetCube {
public:
DialCube();
};
DialCube::DialCube() : FidgetCube() {
for (uint8_t x = 0; x<6; x++ ){
addSide(new DialSide());
}
}
int main() {
DialCube theDialCube;
cout << theDialCube.getSide(0)->getName();
((DialSide*)theDialCube.getSide(0))->setDial(5);
}

Note the following conventions you may not have seen before:

  • const after the method declaration of FidgetCube::getSide() promises that the method does not change the object, and so can be called via a const pointer.
  • constexpr is used when a constant value has to be evaluated by the compiler, rather than just appearing in the program. If we were setting a value to 5, that could be const, because 5 can simply be placed into memory. But a string has to be allocated into memory and turned into an address in the program code, so we have to use constexpr instead of const.
  • constexpr char const * in the declaration of dialSideName indicates that the string pointed to cannot change (it is of type constexpr char*) and also that the pointer cannot be changed to point to a different string (the second const).
  1. Looking at the code above, think about and answer the following questions.
  • Why is dialSideName declared as a static string?
  • Why do we need to cast theDialCube.getSide(0) to DialSide* to call setDial, but not to call getName?

Answers are at the end of this file.

  1. Create a new class JoystickSide which inherits CubeSide. It represents a joystick which can be pushed in any of four directions, but immediately returns to the center when released. Add a method push to it which prints out when the joystick is pushed. Create a FidgetCube with several JoystickSides in the main program, and call their push methods.
  2. Create a new class JoystickDialCube which inherits FidgetCube. Its constructor should accept two parameters, one of which specifies how many joystick sides the cube should have, and one of which specifies how many dial sides. Test it by creating several cubes with different numbers of sides, including 0.
  3. It is awkward to need to cast sides to their “real” type in order to call their methods. (Not awkward method: auto) Create a new method Fidget in CubeSide which is overridden by each particular type of CubeSide to call the method representing its interaction, with random parameters if necessary. (To generate a random number, add #include <random> at the top of the file, then call rand() % x for a random number 0x0-x. This may generate a warning because rand() is less sophisticated; there is a more sophisticated random number generator in C++ but it is complicated to use. You can research this yourself if you like.)
  4. Write a function playWith that inputs a FidgetCube by reference and calls Fidget on all its sides.
  5. Create a class RandomCube which represents a FidgetCube, but when constructed, has its sides randomly selected from the 2 available.
  6. Create a new kind of CubeSide called ButtonSide. It has two buttons and a display which shows a number starting at 5. When the first button is pressed the number goes up by 1, but not above 9. When the second button is pressed the number goes down by 1, but not below 0. The number should be printed whenever a button is pressed. Test this by adding it to some cubes and using the buttons.

参考答案:

//
// Created by 不死鸟Anka on 2025/10/27.
//
#include <iostream>
#include <random>
#include <cstdlib>
#include <string>
#include <cstdint>
using std::string;
using std::cout;
using std::endl;
class CubeSide {
public:
virtual ~CubeSide() = default;
virtual const char *getName() = 0; // pure virtual function must be overridden
virtual void Fidget();
};
class JoystickSide : public CubeSide {
public:
int direction = 0; // center=0, up=1, right=2, down=3, left=4
void push(int _direction);
const char *getName() override;
void Fidget() override; // override to perform a random push
};
void JoystickSide::push(int _direction) {
direction = _direction;
string direction_word;
switch (direction)
{
case 1:
direction_word = "UP"; break;
case 2:
direction_word = "RIGHT"; break;
case 3:
direction_word = "DOWN"; break;
case 4:
direction_word = "LEFT"; break;
default:
direction_word = "CENTER"; break;
}
cout << "A joystick was pushed to direction " << direction_word << "." << endl;
direction = 0;
cout << "This joystick returns to center since released" << endl;
}
const char *JoystickSide::getName() {
return "Joystick";
}
void JoystickSide::Fidget() {
// generate a random direction 0..4
/*static thread_local std::mt19937 eng{std::random_device{}()}; std::uniform_int_distribution<int> dist(0, 4); int dir = dist(eng);*/ int dir = rand() % 5; // 0..4
cout << "(Fidget) ";
push(dir);
}
class FidgetCube {
public:
FidgetCube();
~FidgetCube();
CubeSide *getSide(uint8_t sideNo) const;
CubeSide** getSides();
bool addSide(CubeSide *newSide);
protected:
CubeSide *sides[6];
uint8_t lastSide = 0;
};
FidgetCube::FidgetCube() : lastSide(0) {
for (int i = 0; i < 6; ++i) sides[i] = nullptr;
}
FidgetCube::~FidgetCube() {
for (int i = 0; i < 6; ++i) {
if (sides[i]) {
delete sides[i];
sides[i] = nullptr;
}
}
}
bool FidgetCube::addSide(CubeSide *newSide) {
if (lastSide < 6) {
sides[lastSide] = newSide;
lastSide++;
return true;
}
return false;
}
CubeSide *FidgetCube::getSide(uint8_t sideNo) const {
return sides[sideNo];
}
CubeSide** FidgetCube::getSides() {
return sides;
}
class DialSide : public CubeSide {
public:
const char *getName() override;
void setDial(uint16_t value);
void Fidget() override; // perform a random dial set
protected:
uint16_t dialValue = 0;
static constexpr char const *dialSideName = "Dial";
};
const char *DialSide::getName() {
return DialSide::dialSideName;
}
void DialSide::setDial(uint16_t value) {
// accept values up to 10
if (value <= 10) {
dialValue = value;
cout << "A dial was set to " << dialValue << "." << endl;
} else {
cout << "Dial value " << value << " out of range (0..10)." << endl;
}
}
void DialSide::Fidget() {
/* static thread_local std::mt19937 eng{std::random_device{}()};
std::uniform_int_distribution<int> dist(0, 10); uint16_t val = static_cast<uint16_t>(dist(eng));*/ int val = rand() % 11; // 0..10
cout << "(Fidget) ";
setDial(val);
}
// New: ButtonSide with two buttons and a numeric display (0..9)
class ButtonSide : public CubeSide {
public:
ButtonSide() : display(5) {}
const char *getName() override { return "Button"; }
// press first button: increment (max 9)
void pressButton1() {
if (display < 9) ++display;
cout << "Button 1 pressed. Display: " << display << endl;
}
// press second button: decrement (min 0)
void pressButton2() {
if (display > 0) --display;
cout << "Button 2 pressed. Display: " << display << endl;
}
void Fidget() override {
// randomly press one of the two buttons
int which = rand() % 2;
cout << "(Fidget) ";
if (which == 0) pressButton1();
else pressButton2();
}
protected:
int display;
};
// Provide a sensible default for CubeSide::Fidget in case a derived type doesn't override it
void CubeSide::Fidget() {
cout << "(" << getName() << ") has no specific fidget action." << endl;
}
class DialCube : public FidgetCube {
public:
DialCube();
};
DialCube::DialCube() : FidgetCube() {
for (uint8_t x = 0; x<6; x++ ){
addSide(new DialSide());
}
}
class JoystickDialCube : public FidgetCube {
public:
int joystickSides;
int dialSides;
JoystickDialCube(int jsSides, int dSides) : joystickSides(jsSides), dialSides(dSides) {
for (int i = 0; i < joystickSides; ++i) {
if (addSide(new JoystickSide())) {
cout << "Added joystick side " << i << endl;
} else {
cout << "Could not add joystick side " << i << endl;
}
}
for (int j = 0; j < dialSides; ++j) {
if (addSide(new DialSide())){
cout << "Added dial side " << j << endl;
} else {
cout << "Could not add dial side " << j << endl;
}
}
}
// 根据addSide的逻辑,joystickSides 和 dialSides 的和不能超过 6};
// New: RandomCube fills its 6 sides randomly with JoystickSide, DialSide or ButtonSide
class RandomCube : public FidgetCube {
public:
// default uses nondeterministic seed
RandomCube() {
std::mt19937 eng(std::random_device{}());
std::uniform_int_distribution<int> dist(0, 2); // 0=Joystick,1=Dial,2=Button
for (int i = 0; i < 6; ++i) {
int pick = dist(eng);
if (pick == 0) addSide(new JoystickSide());
else if (pick == 1) addSide(new DialSide());
else addSide(new ButtonSide());
}
}
// deterministic seed for reproducible cubes
explicit RandomCube(unsigned seed) {
std::mt19937 eng(seed);
std::uniform_int_distribution<int> dist(0, 2);
for (int i = 0; i < 6; ++i) {
int pick = dist(eng);
if (pick == 0) addSide(new JoystickSide());
else if (pick == 1) addSide(new DialSide());
else addSide(new ButtonSide());
}
}
};
void playWith(FidgetCube &fidget_cube) {
for (int i = 0; i < 6; ++i) {
CubeSide *side = fidget_cube.getSide(i);
if (!side) continue;
cout << "Side " << i << ": " << side->getName() << endl;
side->Fidget();
}
}
int main() {
DialCube theDialCube;
cout << theDialCube.getSide(0)->getName();
((DialSide*)theDialCube.getSide(0))->setDial(5);
// C++样式的类型转换:static_cast<DialSide*>
// Create a cube and populate with several JoystickSides and DialSides FidgetCube theCube;
// Add 2 joystick sides
theCube.addSide(new JoystickSide());
theCube.addSide(new JoystickSide());
// Add 1 Button side
theCube.addSide(new ButtonSide());
// Fill remaining sides with dials
for (int i = 0; i < 3; ++i) theCube.addSide(new DialSide());
// Iterate all sides and call push on joystick sides or buttons/dials
for (uint8_t i = 0; i < 6; ++i) {
CubeSide *s = theCube.getSide(i);
if (!s) continue;
cout << "Side " << int(i) << ": " << s->getName() << endl;
if (auto *js = dynamic_cast<JoystickSide*>(s)) {
// push different directions for demonstration
int dir = 1 + (i % 4); // 1..4
js->push(dir);
} else if (auto *ds = dynamic_cast<DialSide*>(s)) {
ds->setDial(uint16_t(i + 1));
} else if (auto *bs = dynamic_cast<ButtonSide*>(s)) {
// test button presses and bounds
bs->pressButton1(); // increment
bs->pressButton1(); // increment again
bs->pressButton2(); // decrement
// press multiple times to test clamping for (int k = 0; k < 6; ++k) bs->pressButton1();
for (int k = 0; k < 12; ++k) bs->pressButton2();
}
}
JoystickDialCube joystick_dial_cube(1, 5);
// Demonstrate RandomCube
cout << "\nCreating a RandomCube and playing with it:\n";
RandomCube rc; // random sides
playWith(rc);
// deterministic example
cout << "\nCreating a deterministic RandomCube with seed 42:\n";
RandomCube rc2(42);
playWith(rc2);
return 0;
}
  1. Can you work out an implementation of RandomCube that will allow for the new ButtonSide, and any other sides you might write in the future, without needing to reprogram it? 下面给出一个基于工厂注册表的实现:各个 CubeSide 派生类在定义处静态注册一个“工厂函数”,RandomCube 从注册表中随机挑选工厂并创建实例。今后新增派生类只需在类定义处加入一行注册语句,无需修改 RandomCube。
#include <vector>
#include <functional>
#include <random>
// 工厂注册表(放在 `Week5/FidgetCube.cpp` 或单独工具头文件中)
struct CubeSideFactory {
using Factory = std::function<CubeSide*()>;
static std::vector<Factory>& registry() {
static std::vector<Factory> reg;
return reg;
}
static void registerFactory(const Factory& f) {
registry().push_back(f);
}
static const std::vector<Factory>& getRegistry() {
return registry();
}
};
// 辅助模板:在某个派生类的实现文件中声明一个静态实例即可完成注册
template<typename T>
struct SideRegistrar {
SideRegistrar() {
CubeSideFactory::registerFactory([]() -> CubeSide* { return new T(); });
}
};
// 示例:在每个派生类定义后添加一行(在同一翻译单元中)以完成注册
// 在 `JoystickSide` 定义处加入:
// static SideRegistrar<JoystickSide> _regJoystick;
// 在 `DialSide` 定义处加入:
// static SideRegistrar<DialSide> _regDial;
// 在 `ButtonSide` 定义处加入:
// static SideRegistrar<ButtonSide> _regButton;
// (新派生类只需同样加入一行即可自动被 RandomCube 使用)
// RandomCube 使用注册表来随机生成 6 个面
class RandomCube : public FidgetCube {
public:
RandomCube() : RandomCube(static_cast<unsigned>(std::random_device{}())) {}
explicit RandomCube(unsigned seed) {
const auto& reg = CubeSideFactory::getRegistry();
if (reg.empty()) return; // 没有注册的侧面,保持空立方体
std::mt19937 eng(seed);
std::uniform_int_distribution<size_t> dist(0, reg.size() - 1);
for (int i = 0; i < 6; ++i) {
addSide(reg[dist(eng)]()); // 从工厂创建并添加
}
}
};

用途摘要:

  • 在每个派生类的实现文件中添加一行静态注册变量,例如 static SideRegistrar<ButtonSide> _regButton;
  • RandomCube 不再需要为每种类型写分支,新增侧面只需注册即可自动支持。

Answer to Question 2#

  1. We create a dialCube which can only be dialed at each side of this cube, so we use the same name “Dial” for every side and it shouldn’t be changed.
  2. The getSide() in theDialCube.getSide(0) inherited from class FigetCube, it declared as a const method, that means it will not change any object. And also it returns a CubeSide type pointer which points to a DailSide object this time.
    getName() is declared in CubeSide as a virtual method, so when class DialSide derived from CubeSide, and override the getName() method. That means the class DialSide has its own getName() method.
    So theDialCube.getSide(0)->getName() means it call the getname() overrode in class Dialside of object theDialCube;
    theDialCube.getSide(0) return a CubeSide type pointer, but we don’t define method setDial() in base class CubeSide, that means CubeSide type pointer can not call setDial() directly.
    So we must cast CubeSide type pointer into DialSide type pointer ,until then we can call method setDial().
    So we must add (DialSide*) before theDialCube.getSide(0) to cast Cubside* to DialSide*, until then we can call setDial(5) as a method of class DialSide object. It cannot be found as a method of class CubeSide.
NOTE

思考题
为何不可以:
int *p;
*p = 123;
但这样又可以:
static constexpr char const *dialSideName = "Dial";

Software Development with C and Cpp Week5 Seminar
https://mizuki.anka2.top/posts/l5-cpp-week5-seminar/
作者
🐦‍🔥不死鸟Anka
发布于
2025-10-28
许可协议
MIT

部分信息可能已经过时

封面
示例歌曲
示例艺术家
封面
示例歌曲
示例艺术家
0:00 / 0:00