1

我是一名初学者程序员,我正在用 C 语言编写游戏“Snake”的简单实现,并使用 SDL2 库。一切都很顺利,直到我决定将代码分成单独的文件和函数。现在,当我尝试编译我的项目时出现错误:

Snake/src\snake.c:8: duplicated definition «init_snake»; CMakeFiles\Snake.dir/objects.a(main.c.obj):Snake/src\..\src\snake.c:8: first definition

我很困惑,函数的第一个定义和它的重新定义在单个文件的同一行。我用IDE搜索了整个项目,发现只有两个地方出现了函数“init_snake”。事实上,这就是她的定义和电话的位置。在我的谷歌搜索之后,我发现我在头文件中的变量初始化方面做错了。像这样:

const static int SIZE = 10;

为了感兴趣,我从头文件中删除了变量,并决定不使用它们进行测试(尽管我觉得这无济于事,因为错误是一个函数,而不是一个变量)。它真的没有帮助。

可以肯定的是,我决定搜索有关包含的信息。为了避免代码交叉,我添加了#include guard(在互联网上找到)和#pragma once. 虽然它对这个错误没有帮助,但它很酷(如果我正确理解它是如何工作的)。

所以特地来这里请教专家。这是怎么回事?因为我在一个地方定义了一个函数,然后我从另一个地方调用它一次。

下面我应用当前版本的代码。

主程序

#include "../include/main.h"

#include "../src/snake.c"

int
main(int argc,
     char *args[])
{
    SDL_Window *window = NULL;
    window = SDL_CreateWindow("Snake",
                              SDL_WINDOWPOS_CENTERED,
                              SDL_WINDOWPOS_CENTERED,
                              525,
                              525,
                              SDL_WINDOW_SHOWN);
    if (NULL == window)
    {
        printf("Window was not created. Error: %s\n", SDL_GetError());
        exit(1);
    }

    SDL_Renderer *renderer = NULL;
    renderer = SDL_CreateRenderer(window,
                                  -1,
                                  SDL_RENDERER_ACCELERATED);
    if (NULL == renderer)
    {
        printf("Renderer was not created. Error: %s\n", SDL_GetError());
        exit(1);
    }

    SDL_SetRenderDrawColor(renderer, 0x0, 0x0, 0x0, 0xFF);
    SDL_RenderClear(renderer);
    SDL_RenderPresent(renderer);

    init_snake(renderer, 525 / 2, 525 / 2, 16);

    bool quit = false;
    SDL_Event e;
    while (!quit)
    {
        //Handle events on queue
        while (SDL_PollEvent(&e) != 0)
        {
            if (e.type == SDL_QUIT)
            {
                quit = true;
            }
        }
    }

    SDL_DestroyWindow(window);
    SDL_DestroyRenderer(renderer);
    SDL_Quit();

    return 0;
}

主文件

#pragma once
#ifndef MAIN_H
#define MAIN_H

#include "SDL2/SDL.h"

#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>

#endif

蛇.c

#include "../include/snake.h"

void
init_snake(SDL_Renderer *renderer,
           int x,
           int y,
           int length)
{
    assert(renderer);

    for (int i = 0; i < length; i++)
    {
        SDL_Rect rect;
        rect.x = x;
        rect.y = y;
        rect.w = 12;
        rect.h = 10;
        SDL_SetRenderDrawColor(renderer, 0x0, 0xFF, 0x0, 0xFF);
        SDL_RenderFillRect(renderer, &rect);
        SDL_RenderPresent(renderer);
    }
}

蛇.h

#pragma once
#ifndef SNAKE_H
#define SNAKE_H
    
#include "SDL2/SDL.h"

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
    
#endif

如果我在创建这个问题时做错了什么,请告诉我 - 我会尝试解决它。如果您需要任何其他信息,请告诉我,我会澄清。感谢您的时间。

4

2 回答 2

3

C 标准或编译器通常没有规定不能包含.c文件。但是对于我们如何命名和使用文件有一个约定:

名称以结尾的文件.c包含对象和/或函数的定义。对于对象,定义是编写的声明,以便它们导致存储被保留(就像带有初始化的外部定义,例如int MyFoo = 0;,尽管我们现在通常避免外部可见的对象)。对于函数,定义是包含函数体(它执行的代码)的声明。

名称以 结尾的文件.h包含不是定义的对象和/或函数的声明。这些声明仅仅描述了对象或函数而不定义它。对于对象,extern int MyFoo;是一个描述但不定义的声明MyFoo。因为函数extern int bar(double x);是一个没有定义的声明bar。这extern是函数的默认值,因此您可以将其关闭并编写int bar(double x);.

给定一个文件名,例如MyModuleX,我们通常使用该文件MyModuleX.c来定义 的对象和函数MyModuleX,并使用该文件MyModuleX.h告诉程序的其他部分提供的对象和函数MyModuleX

所以每个使用 from 的源文件都MyModuleX应该有#include "MyModuleX.h". 另外,MyModuleX.c也应该有,所以编译器可以同时看到声明和定义,并且可以检查它们是否匹配。没有文件应该有#include "MyModuleX.c",因为我们不会通过包含源文件来将函数放入程序中。相反,我们分别编译每个源文件(可能在一个命令中,但编译器将单独编译它们)并将它们链接在一起。

于 2021-03-11T19:32:32.233 回答
1

如果你 include snake.c,这意味着它的代码在编译时会被包含两次。在文件上使用#include只是将所有代码插入文件中。这意味着,当编译发生时,main.c包含所有snake.c,并且由于您也在编译snake.c,因此您在其中定义的所有内容都将被定义两次。所以,你必须不包括snake.cmain.c. 只需包含它的头文件。在头文件中,预定义您想在其他地方使用的任何函数,因此您不必包含整个 C 文件即可访问这些函数。

于 2021-03-11T19:29:18.630 回答