У меня есть очень большой одномерный массив, содержащий значения тайлов для моей игры. Каждое значение представляет собой четырехзначное число, представляющее тип плитки (грязь, трава, воздух).
Я сохраняю значения в файле, используя fstream, вот так:
std::fstream save("game_save", std::fstream::out | std::fstream::in);
Допустим, у меня есть крошечная карта. 3 плитки шириной и 3 плитки высотой, вся грязь (значение грязи составляет 0001).
В игре это будет выглядеть
0001 0001 0001
0001 0001 0001
0001 0001 0001
В файле это выглядит так (только одномерный)
0001 0001 0001 0001 0001 0001 0001 0001 0001
Что бы я сделал, если бы хотел перейти к 5-му значению (2-й ряд, 2-й столбец) и изменить только это значение, скажем, 0002? Таким образом, когда я снова запускаю игру и она читает файл, который видит:
0001 0001 0001 0001 0002 0001 0001 0001
Любые советы о том, как это сделать, будут оценены
Если вы абсолютно уверены в 4 цифрах + ровно 1 пробел для каждого элемента, и в файле не встречаются символы табуляции или новой строки, вы можете использовать seekp(n*5,ios_base::beg)
расположить следующую запись на n-м элементе и просто перезаписать ее.
Suggesions
Если использование такого рода позиционирования безопаснее с файлами, открытыми с ios::binary
Режим.
В этом случае вы также можете рассмотреть возможность чтения / записи двоичных данных с помощью функций блока. read()
/write()
и используя n*sizeof(tile)
найти правильную позицию. После этого файл больше не будет полностью независимым от платформы, и его невозможно будет редактировать вручную с помощью текстового редактора, но у вас будет улучшенная производительность, особенно если у вас очень большая территория, и даже больше, если вы часто получаете доступ к последовательным элементам в та же строка
ПРОСТОЙ способ — просто снова записать весь массив. Тем более, что он довольно короткий. Если вы ЗНАЕТЕ, что каждый элемент равен 5 байтам, вы можете установить положение записи с помощью seekp
, так save.seekp((1 * 3 + 1) * 5)
а затем напишите это значение в одиночку. Но это, вероятно, больше работы, чем стоит, если ваш файл не ОГРОМНЫЙ (фактический файл все равно будет обновлен как минимум в 1 секторе, который составляет 512 или 4096 байт на жестком диске)
int loc=5;
save.seekg ((loc-1)*5, save.beg);
save << "0002";
попробуй этого парня 🙂
Выбранный ответ на C ++ Fstream для замены конкретной строки? кажется довольно хорошим объяснением.
То, что вы хотите сделать, это искать правильную часть нашего потока вывода.
fstream save;
...
save.seekp(POSITION_IN_FILE);
вот полный пример:
#include <iostream>
#include <fstream>
#include <string>
#include <algorithm>
using namespace std;
#define BYTES_PER_BLOCK 5
void save_to_file(fstream& save, int value, int x, int y);
string num2string(int val);
int main(){
fstream save("game_save", std::fstream::out | std::fstream::in);
save_to_file(save, 2, 1, 1);
save.close();
return 0;
}
void save_to_file(fstream& save, int value, int x, int y){
int pos = (y * 3 + x) * BYTES_PER_BLOCK;
save.seekp(pos);
save << num2string(value);
}
string num2string(int val){
string ret = "";
while (val > 0){
ret.push_back('0'+val%10);
val /= 10;
}
while (ret.length() < 4){
ret.push_back('0');
}
reverse(ret.begin(), ret.end());
return ret;
}
Я предлагаю использовать файлы с отображением в памяти вместо fstream. Вы можете использовать повышение для этого. Увеличить документацию библиотеки
Существует поток переполнения стека, который охватывает информацию о сопоставлении файлов.
Переполнение стека Тред
Это будет что-то вроде
save.seekp((row * number_columns + col)* size_of_element_data);
save.write(element_data, size_of_element_data);
Но будет намного проще и безопаснее просто прочитать файл обратно, отредактировать элемент и записать весь файл обратно. Вы не можете изменить размер или вставить элемент в середине файла, не перетасовывая оставшуюся часть файла назад (что добавляет проблему очистки теперь неиспользуемого пространства в конце файла) или вперед (что требует дополнения конец файлового потока, а затем выполнить неразрушающее перемещение).
Я бы использовал здесь отображенный в память файл и, предпочтительно, также использовал бы двоичное представление.
Таким образом, вы можете просто хранить массив целых чисел и не нуждаться в (де) коде сериализации и / или поиске.
Например.
#include <stdexcept>
#include <iostream>
#include <algorithm>
// memory mapping
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <cstring>
#include <cassert>
template <size_t N, size_t M, typename T = int>
struct Tiles {
Tiles(char const* fname)
: fd(open(fname, O_RDWR | O_CREAT))
{
if (fd == -1) {
throw std::runtime_error(strerror(errno));
}
auto size = N*M*sizeof(T);
if (int err = posix_fallocate(fd, 0, size)) {
throw std::runtime_error(strerror(err));
}
tiles = static_cast<T*>(mmap(nullptr, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0));
if (MAP_FAILED == tiles) {
throw std::runtime_error(strerror(errno));
}
}
~Tiles() {
if (-1 == munmap(tiles, N*M*sizeof(T))) {
throw std::runtime_error(strerror(errno));
}
if (-1 == close(fd)) {
throw std::runtime_error(strerror(errno));
}
}
void init(T value) {
std::fill_n(tiles, N*M, value);
}
T& operator()(size_t row, size_t col) {
assert(row >= 0 && row <= N);
assert(col >= 0 && col <= M);
return tiles[(row*M)+col];
}
T const& operator()(size_t row, size_t col) const {
assert(row >= 0 && row <= N);
assert(col >= 0 && col <= M);
return tiles[(row*M)+col];
}
private:
int fd = -1;
T* tiles = nullptr;
};
int main(int argc, char** argv) {
using Map = Tiles<3, 3, uint16_t>;
Map data("tiles.dat");
if (argc>1) switch(atoi(argv[1])) {
case 1:
data.init(0x0001);
break;
case 2:
data(1, 1) = 0x0002;
break;
}
}
Какие отпечатки:
clang++ -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && set -x; for cmd in 0 1 2; do ./a.out $cmd; xxd tiles.dat; done
+ for cmd in 0 1 2
+ ./a.out 0
+ xxd tiles.dat
0000000: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0000010: 0000 ..
+ for cmd in 0 1 2
+ ./a.out 1
+ xxd tiles.dat
0000000: 0100 0100 0100 0100 0100 0100 0100 0100 ................
0000010: 0100 ..
+ for cmd in 0 1 2
+ ./a.out 2
+ xxd tiles.dat
0000000: 0100 0100 0100 0100 0200 0100 0100 0100 ................
0000010: 0100 ..