I am almost new to C programming and I want to know what is the best way to store strings (I don't know in advance how many strings I will read, it could be sometimes 10 strings or other times 99, so a variable number of strings, depending from the input text file) read from a text file (each line has one and only one string and all strings have the same length). I have some ideas like using a struct or a bi-dimensional array of dynamically allocated number of strings (so by using "malloc" statement) with the same length. Thanks in advance for your suggestions or solutions.
EDIT: I want to create something similar to LinkedList from Java language because when needed I want to print all the stored strings and I want to remove a string from given position.
CodePudding user response:
(each line has one and only one string and all strings have the same length)
If the string has a constant length simply use char flexible array member.
You can simply read line, add null character terminator and repeat until the whole file was read.
Only one malloc and free needed.
typedef struct
{
size_t nLines;
size_t lineLen;
char text[];
}lineBuff;
lineBuff *readText(FILE *fi, const size_t lineLen)
{
lineBuff *buff = NULL;
if(fi)
{
long fileLength;
if(fseek(fi, 0, SEEK_END)) { /* error handling */}
fileLength = ftell(fi);
buff = malloc(sizeof(*buff) fileLength fileLength / lineLen);
if(buff)
{
buff -> nLines = fileLength / lineLen;
buff -> lineLen = lineLen 1; // it includes null terminating character
fseek(fi, 0, SEEK_SET);
for(size_t line = 1; line <= buff -> nLines; line )
{
if(fread(buff -> text (line - 1) * buff -> lineLen, 1, lineLen, fi) != lineLen) { /* error handling */}
buff -> text[line * lineLen] = 0;
}
}
}
return buff;
}
char *regNthLine(const lineBuff *buff, const size_t line)
{
char *result = NULL;
if(buff && line < buff -> nLines)
{
result = (char *)buff -> text line * buff -> lineLen;
}
return result;
}
or you can use array pointers:
char *getNthLine1(const lineBuff *buff, size_t line)
{
char *result = NULL;
if(buff && line < buff -> nLines)
{
char (*lineptr)[buff -> lineLen] = (char (*)[buff -> lineLen])buff -> text;
result = lineptr[line];
}
return result;
}
CodePudding user response:
An alternative take on the pointer to C strings (with some tests).
Here you can find a create_test_file()
function for making the input, and a read_fixed_size_strings()
which will read all the data in a single block of memory, then setup another piece of memory with the required pointers. Two free()
are necessary to cleanup.
In the test, I also included how to remove a line, that is removing only the pointer to it. Still, no list, since it's just O(n) with n the number of strings, but only pointers to the strings are moved. Notice that removing a string from the list doesn't free the associated memory.
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <crtdbg.h>
bool create_test_file(const char *filename, size_t len_lines, size_t num_lines)
{
FILE *f = fopen(filename, "w");
if (f == NULL) {
return false;
}
char *line = malloc(len_lines 2);
strcpy(line len_lines, "\n");
// Make num_lines lines
for (size_t i = 0; i < num_lines; i) {
// Make random line
for (size_t j = 0; j < len_lines; j) {
line[j] = 32 rand() % (126 - 32 1);
}
// Write it
fputs(line, f);
}
free(line);
fclose(f);
return true;
}
char **read_fixed_size_strings(const char *filename, size_t len_lines, size_t *n)
{
FILE *f = fopen(filename, "r");
if (f == NULL) {
return NULL;
}
len_lines = 2; // accomodate also for \n and 0.
size_t capacity = 16; // Expected number of lines (random guess)
size_t nread = 0;
char *data = malloc(capacity * len_lines);
while (fgets(data nread * len_lines, len_lines, f)) {
nread;
if (nread == capacity) {
capacity *= 2;
data = realloc(data, capacity * len_lines); // make space for more data
}
}
data = realloc(data, nread * len_lines); // shrink to fit
fclose(f);
char **lines = malloc(nread * sizeof(char*));
for (size_t i = 0; i < nread; i) {
lines[i] = data len_lines * i;
}
*n = nread;
return lines;
}
int main(void)
{
srand(1234567890);
const size_t num_lines = 1234;
const size_t len_lines = 37;
//char *filename = tmpnam(NULL);
char *filename = "test.txt";
if (!create_test_file(filename, len_lines, num_lines)) {
return EXIT_FAILURE;
}
size_t n;
char **lines = read_fixed_size_strings(filename, len_lines, &n);
// Print a line on stdout (\n is already included)
fputs(lines[7], stdout);
// Remove line 7 (notice that no memory is freed - this can be made way faster)
size_t line_to_be_removed = 7;
for (size_t i = line_to_be_removed; i < n - 1; i) {
lines[i] = lines[i 1];
}
--n;
// Print a line on stdout
fputs(lines[7], stdout);
// Free memory
free(lines[0]);
free(lines);
return EXIT_SUCCESS;
}