关于制作贪吃蛇时,遇到的一些小细节(内附源码)

我想往vector容器内存放一个xy坐标,要求xy是作为一个整体存入的。但是遍历容器的时候要可以单独读写x或者y

你可以使用结构体或者类来存储x和y坐标,然后将这个结构体或类的实例存入vector容器。这样在遍历容器时,可以单独读写x或y。

解析:

  1. 定义一个结构体或类,包含x和y两个成员变量。

  2. 创建一个vector容器,存放该结构体或类的实例。

  3. 遍历vector容器,可以单独读写x或y。

代码示例:

#include <iostream>
#include <vector>
​
// 定义一个结构体,包含x和y两个成员变量
struct Point {
    int x;
    int y;
};
​
int main() {
    // 创建一个vector容器,存放Point结构体的实例
    std::vector<Point> points;
​
    // 向容器中添加一个点
    Point p = {1, 2};
    points.push_back(p);
​
    // 遍历容器,可以单独读写x或y
    for (const auto& point : points) {
        std::cout << "x: " << point.x << ", y: " << point.y << std::endl;
    }
​
    return 0;
}
是的,除了使用<random>库外,还可以使用C++11引入的rand()函数和srand()函数来生成随机数。以下是一个简单的示例:
#include <iostream>
#include <ctime>
#include <cstdlib>
​
int main() {
    // 设置随机数种子
    srand(static_cast<unsigned>(time(0)));
​
    // 生成随机数
    int random_number = rand();
​
    // 输出随机数
    std::cout << "随机数: " << random_number << std::endl;
​
    return 0;
}

在这个示例中,我们使用了srand()函数来设置随机数种子,然后使用rand()函数生成随机数。需要注意的是,rand()函数生成的是0到RAND_MAX之间的随机数,如果需要指定范围,可以使用取模运算符(%)来实现。

友元类

当你收到“非静态成员引用必须与特定对象相对”的编译错误,并且你正在尝试通过友元类访问私有变量时,这通常意味着你正在尝试在不创建任何对象的情况下访问类的非静态成员。

在C++中,友元类可以访问另一个类的私有和保护成员,就好像它们是自己的成员一样。然而,这种访问权仅限于友元类的成员函数内部,并且当友元类的成员函数试图访问另一个类的非静态成员时,它必须通过该类的对象来访问。

如果你在没有创建对象的情况下尝试直接访问非静态成员,编译器会抛出错误,因为非静态成员需要与特定的对象实例关联。

以下是可能导致这个错误的一些情况:

  1. 直接访问非静态成员:你可能在友元类的成员函数中直接访问了另一个类的非静态成员,而没有通过该类的对象。

  2. 误解静态成员:你可能错误地将一个应该是非静态的成员声明为了静态成员,然后试图在没有对象的情况下访问它。

  3. 未定义对象:你可能在代码中没有定义足够的对象来访问所有的非静态成员。

  4. 作用域问题:你可能在错误的作用域内访问了非静态成员,例如在类的外部直接访问而不是通过对象。

为了解决这个问题,你需要确保:

  • 在友元类的成员函数中,通过正确的对象来访问非静态成员。

  • 检查是否有正确定义的对象来访问非静态成员。

  • 确保你理解了静态和非静态成员的区别,并正确地使用了它们。

默认构造函数不是成员函数吗?我是在友元类的默认构造函数内访问的

默认构造函数确实是类的一个特殊成员函数,用于初始化对象。当你在友元类的默认构造函数内访问另一个类的私有成员时,你仍然需要通过该类的对象来访问。即使是友元类,也不能在没有对象的情况下直接访问非静态成员。

如果你在友元类的默认构造函数中直接访问了另一个类的私有非静态成员而没有通过对象,那么你就会收到“非静态成员引用必须与特定对象相对”的错误。

为了解决这个问题,你需要确保:

  1. 在友元类的默认构造函数中,你有一个对象来访问那个类的私有成员。

  2. 如果你是想在默认构造函数中访问友元类的私有成员,确保你是通过this指针来访问的,因为this指针在成员函数中指向当前对象。

下面是一个简化的例子,展示了如何在友元类的默认构造函数中正确访问另一个类的私有成员:

class A {
private:
    int privateVar;
public:
    A() : privateVar(0) {}
    friend class B; // 声明B为A的友元类
};
​
class B {
public:
    B(A& a) {
        // 通过对象a来访问A的私有成员
        a.privateVar = 10; // 正确的做法
        this->privateVar = 10; //指针做法
    }
};
​
int main() {
    A a;
    B b(a); // 创建B的对象,并传入A的对象
    return 0;
}

在这个例子中,B的构造函数接受一个A类的对象引用,并通过这个对象来访问A的私有成员privateVar。这样就避免了编译错误。

在C++中,子类(派生类)不能直接访问父类(基类)的私有成员。私有成员仅在其定义的类内部可见,这意味着它们只能在基类的成员函数和友元函数中被访问。

当一个类从另一个类继承时,子类不会继承对父类私有成员的访问权限。子类只能访问父类的公共(public)和保护(protected)成员。

如果想让子类能够访问父类的私有成员,你可以:
  1. 使用公有或保护的getter和setter方法:在父类中提供公有或保护的接口来获取或修改私有成员的值。

  2. 将子类声明为友元类:在父类中将子类声明为友元,但这样做会破坏封装,通常不推荐。

  3. 将成员设置为保护(protected)而不是私有(private):这样,子类就可以直接访问这些成员,但这会使得成员对所有子类可见,而不仅仅是特定的子类。

以下是使用公有getter和setter方法的示例:

class Animal {
private:
    std::string name;
​
public:
    Animal(const std::string& name) : name(name) {}
​
    // Getter
    std::string getName() const {
        return name;
    }
​
    // Setter
    void setName(const std::string& newName) {
        name = newName;
    }
};
​
class Dog : public Animal {
public:
    Dog(const std::string& name) : Animal(name) {}
​
    void bark() {
        std::cout << "Woof! My name is " << getName() << std::endl;
    }
};
​
int main() {
    Dog dog("Buddy");
    dog.bark(); // 输出: Woof! My name is Buddy
    return 0;
}

在这个例子中,Dog类通过Animal类提供的公有getter方法getName()来访问name成员,而不是直接访问它。这是一种更安全的方法,因为它允许基类控制对其成员的访问,并且可以在必要时进行更改而不影响派生类。

在C++中,要访问vector容器的第一个元素,确实可以使用下标操作符[]或者使用迭代器。以下是两种方法的示例:

使用下标操作符[]:

#include <iostream>
#include <vector>
​
int main() {
    std::vector<int> myVector = {1, 2, 3, 4, 5};
​
    // 访问第一个元素(下标为0)
    int firstElement = myVector[0];
​
    std::cout << "第一个元素是:" << firstElement << std::endl;
​
    return 0;
}

使用迭代器:

#include <iostream>
#include <vector>
​
int main() {
    std::vector<int> myVector = {1, 2, 3, 4, 5};
​
    // 获取指向第一个元素的迭代器
    std::vector<int>::iterator it = myVector.begin();
​
    // 访问迭代器指向的元素
    int firstElement = *it;
​
    std::cout << "第一个元素是:" << firstElement << std::endl;
​
    return 0;
}

在这两个示例中,我们分别使用了下标操作符[]和迭代器来访问vector容器的第一个元素。两种方法都是有效的,选择哪种取决于你的具体需求和个人喜好。

#include <iostream>
#include <conio.h>
#include <windows.h>
using namespace std;
​
bool gameover; // 游戏是否结束的标志
const int width = 20; // 游戏界面的宽度
const int height = 20; // 游戏界面的高度
int x, y, fruitX, fruitY, score; // 蛇的位置、果实的位置和分数
int tailX[100], tailY[100]; // 蛇的身体坐标数组
int nTail; // 蛇的身体长度
enum eDirecton { STOP = 0, LEFT, RIGHT, UP, DOWN }; // 蛇的移动方向枚举
eDirecton dir; // 蛇的当前移动方向
​
void Setup() // 初始化游戏
{
    gameover = false; // 游戏未结束
    dir = STOP; // 蛇停止移动
    x = width / 2; // 蛇的初始横坐标为界面宽度的一半
    y = height / 2; // 蛇的初始纵坐标为界面高度的一半
    fruitX = rand() % width; // 随机生成果实的横坐标
    fruitY = rand() % height; // 随机生成果实的纵坐标
    score = 0; // 初始分数为0
}
​
void Draw() // 绘制游戏界面
{
    system("cls"); // 清屏
    for (int i = 0; i < width + 2; i++) // 绘制上下边界
        cout << "#";
    cout << endl;
​
    for (int i = 0; i < height; i++) // 绘制中间部分
    {
        for (int j = 0; j < width; j++) // 绘制每一行
        {
            if (j == 0) // 绘制左边界
                cout << "#";
            if (i == y && j == x) // 绘制蛇头
                cout << "*";
            else if (i == fruitY && j == fruitX) // 绘制果实
                cout << "%";
            else // 绘制蛇身体或空白区域
            {
                bool print = false; // 标记是否打印蛇身体
                for (int k = 0; k < nTail; k++) // 遍历蛇身体的每一节
                {
                    if (tailX[k] == j && tailY[k] == i) // 如果当前坐标与蛇身体的某一节坐标相同
                    {
                        cout << "*"; // 打印蛇身体
                        print = true; // 标记已打印蛇身体
                    }
                }
                if (!print) // 如果当前坐标不是蛇身体的某一节坐标
                    cout << " "; // 打印空白区域
            }
​
            if (j == width - 1) // 绘制右边界
                cout << "#";
        }
        cout << endl;
    }
​
    for (int i = 0; i < width + 2; i++) // 绘制下边界
        cout << "#";
    cout << endl;
    cout << "Score:" << score << endl; // 显示分数
}
​
void Input() // 获取用户输入
{
    if (_kbhit()) // 如果有按键按下
    {
        switch (_getch()) // 根据按键的不同执行不同的操作
        {
        case 'a': // 向左移动
            dir = LEFT;
            break;
        case 'd': // 向右移动
            dir = RIGHT;
            break;
        case 'w': // 向上移动
            dir = UP;
            break;
        case 's': // 向下移动
            dir = DOWN;
            break;
        case 'x': // 退出游戏
            gameover = true;
            break;
        }
    }
}
​
void algorithm() // 蛇的移动算法
{
    int prevX = tailX[0]; // 记录蛇尾的前一个坐标
    int prevY = tailY[0];
    int prev2X, prev2Y;
    tailX[0] = x; // 更新蛇尾的坐标
    tailY[0] = y;
    for (int i = 1; i < nTail; i++) // 遍历蛇身体的每一节
    {
        prev2X = tailX[i]; // 记录蛇身体的前一节坐标
        prev2Y = tailY[i];
        tailX[i] = prevX; // 更新蛇身体的坐标
        tailY[i] = prevY;
        prevX = prev2X; // 更新前一节坐标为当前节坐标
        prevY = prev2Y;
    }
​
    switch (dir) // 根据蛇的移动方向更新蛇头坐标
    {
    case LEFT:
        x--;
        break;
    case RIGHT:
        x++;
        break;
    case UP:
        y--;
        break;
    case DOWN:
        y++;
        break;
    default:
        break;
    }
​
    if (x >= width) x = 0; else if (x < 0) x = width - 1; // 确保蛇不会超出界面范围
    if (y >= height) y = 0; else if (y < 0) y = height - 1;
​
    for (int i = 0; i < nTail; i++) // 检查蛇头是否碰到蛇身体
        if (tailX[i] == x && tailY[i] == y)
            gameover = true;
​
    if (x == fruitX && y == fruitY) // 如果蛇头碰到果实,增加分数并重新生成果实位置和蛇身体长度
    {
        score += 10;
        fruitX = rand() % width;
        fruitY = rand() % height;
        nTail++;
    }
}
​
int main() // 主函数
{
    Setup(); // 初始化游戏
    while (!gameover) // 当游戏未结束时
    {
        Draw(); // 绘制游戏界面
        Input(); // 获取用户输入
        algorithm(); // 执行蛇的移动算法
        Sleep(1); // 暂停10毫秒,控制游戏速度
    }
    return 0; // 游戏结束,返回0
}
  • 20
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值