Delete/Modify an element in a struct array - c++

I have a program that stores a "friends" info into a struct array and writes it to a file. No problem there. But how would I be able to modify and/or delete a specific element in that struct array? Did some reading and it says I can't, unless I was to shift it all over by one after deleting in.
So I'm assuming I need to read it in, then remove it, and shift all other elements over by one and write it again...but how would I do this? Tried to include only the necessary code I have so far.
For modifying it, I would guess I'd read it in, then ask for the specific element # I want to change, then set all the values in that element to null, then allow the user to input new info? How would this code look?
struct FriendList
{
char screenname[32];
char country[32];
char city[32];
char interests[32];
short age;
};
int main()
{
FriendList friends[num_friends];
const int num_friends = 2;
// Gets user input and puts it into struct array and writes to file
case 3:
{ // Getting info and putting in struct elements
for (index = 0; index < num_friends; index++)
{
// Create Friend Records
cout << "Enter Screename " << endl;
cin.ignore();
cin.getline(friends[index].screenname, 32);
cout << "Country: " << endl;
cin >> friends[index].country;
cout << "City: " << endl;
cin >> friends[index].city;
cout << "Age: " << endl;
cin >> friends[index].age;
}
counting += index;
fstream infile;
infile.open("friends.dat", ios::out | ios::binary |ios::app);
if(infile.fail())
{ cout << "File not found!\n\t";
// exit
}
// Writing struct to file
infile.write((char*)&friends, sizeof(friends));
infile.close();
break;
}
// Delete a friend ???
case 5:
{ // Reading in file contents into struct friends
// Then????
fstream outfile;
outfile.open("friends.dat", ios::in | ios::binary);
outfile.read((char*)&friends, sizeof(friends));
break;
}

Yes, It can modify member of the struct. But you don't clear memory at the first, you will see garages in friends.dat.
in upper of main, you have better to add memset().
memset(&friends, 0, sizeof(friends));
And you use ios::app . I guess that friends is full-set datas. Then, you should remove ios::app ?
BTW, in late about C++, most of c++er don't use binary file for like this case. :)

Change is relatively easy - just read the right entry, update it in memory and write back. In order to delete I suggest the following:
Read all the entries after the one u need to delete
Write those entries in the offset of the deleted entries
Truncate the fuile to the new length
This is the trivial approach

It sounds like you want either a std::deque or a std::vector depending on usage.
If you are deleting items infrequently, then use the std::vector instead of a fixed array:
std::vector<FriendList> friends;
to add new friends:
friends.push_back(newFriend);
accessing a friend by index is the same as accessing an array:
friends[index]
to delete an entry in the vector, use erase() (not remove()!):
friends.erase(friends.begin() + index)

You could make a method delete, which pulls the friend after the one you want to delete, moves the info to the current struct, and continues until there are no more friends.

Related

Allocating memory for an array

I am trying to read numbers from a file and store then in an array using dynamic memory. When I try to print a member of the array, it is showing the address instead of the actual contents.
// CLASS METHOD IMPLEMENTATIONS
#include "DataHousing.h"
// CONSTRUCTORS
DataHousing::DataHousing() {
}
void DataHousing::FillArray() {
int tempIn = 0;
int count = 0;
// attempt to open the file with read permission
ifstream inputHandle("NumFile500.txt", ios::in);
// count how many numbers are in each file
if (inputHandle.is_open() == true) {
while (!inputHandle.eof()) {
inputHandle >> tempIn;
count++;
}
// allocate memory for array
int* pFileContents = new int[count];
// fill array
while (!inputHandle.eof()) {
for (int i = 0; i < count; i++) {
inputHandle >> pFileContents[i];
}
}
cout << &pFileContents[2];
}
else {
cout << "error";
}
}
This is my first time attempting anything like this and I am pretty stuck. What am i doing wrong here?
The unary & operator is to retrieve an address, so it is quite natural that it is showing the address.
To display the contents, remove the & in cout << &pFileContents[2]; and have it display the contents.
Also the counting part of your code
while (!inputHandle.eof()) {
inputHandle >> tempIn;
count++;
}
has two mistakes.
Firstly, you are incrementing count without the last reading was successful.
Secondly, you are trying to read from ifstream that is already reached to EOF.
You have to clear the EOF flag and seek to the beginning of the file like this:
In conclusion, the counting part should be:
while (inputHandle >> tempIn) {
count++;
}
inputHandle.clear();
inputHandle.seekg(0, ios_base::beg);
I see that you're trying to print the required value using:
cout << &pFileContents[2];
Since pFileContents is an array, pFileContents[2] will access the second element (value) of the same.
But since you've prepended & before the element, it is going to print the address of the second element of the array.
In order to print the value of the second element of the array, just use:
cout << pFileContents[2];
Notice the difference in the later code, we haven't used & just after cout <<

My code seems to enter an infinite loop after loading a list from a binary file

I was studying the STL and decided to write some code to practice the writing and reading of files. The problem consists of creating a list of int (0, 1,...,9), save it in a binary file, and finally load it again.
There are 5 basic code blocks:
1. create list
2. present list
3. save list
4. load list
5. present list again
It seems simple and straightforward; however, the code seems to get in an infinite loop.
int main(){
list<int> numbers;
/////// Create list of 10 integers ///////
for(int i=0; i<10; i++){
numbers.push_back(i);
}
/////// Present List ///////
cout << "List created: [";
list<int>::iterator it;
for(it = numbers.begin(); it != numbers.end(); it++){
if(*it != 9){
cout << *it << ", ";
}
else{
cout << *it;
}
}
cout << "]" << endl;
/////// Save list ///////
string fileName = "test.bin";
ofstream outputFile;
outputFile.open(fileName, ios::binary);
if(outputFile.is_open()){
outputFile.write(reinterpret_cast<char *>(&numbers), sizeof(numbers));
outputFile.close();
cout << "List saved to file." << endl;
}
else{
cout << "Could not open file named " << fileName << endl;
}
/////// Load list ///////
list<int> anotherList;
ifstream inputFile;
inputFile.open(fileName, ios::binary);
if(inputFile.is_open()){
inputFile.read(reinterpret_cast<char *>(&anotherList), sizeof(anotherList));
inputFile.close();
cout << "List loaded from file." << endl;
}
else{
cout << "Could not open file named " << fileName << endl;
}
/////// Present List ///////
cout << "List loaded: [";
for(it = anotherList.begin(); it != anotherList.end(); it++){
if(*it != 9){
cout << *it << ", ";
}
else{
cout << *it;
}
}
cout << "]" << endl;
return 0;
}
The problem is in the "Load List" code block, since, if I comment it out, everything works fine.
Am I saving the object correctly? What am I doing wrong?
Thanks in advance.
The problem lies in the flawed logic of reinterpret_cast<char *>(&numbers). Why?
std::list manages its storage using pointers. It simply holds a pointer to a chain of elements consisting of some objects and a pointer to the next element. You cannot simply treat it like a sequence of bytes and expect it to maintain its functionality.
What you instead need to do is to loop over the elements and write them to the file one by one:
#include <fstream>
#include <iostream>
#include <list>
int main() {
std::fstream file{};
file.open("data.txt", std::ios::binary | std::ios::out);
std::list<int> ints{2, 4, 5, 6, 8, 1, 3, 5, 7, 9};
for (int i : ints) {
file.write(reinterpret_cast<char*>(&i), sizeof(i));
}
file.flush();
file.close();
file.open("data.txt", std::ios::binary | std::ios::in);
ints.clear();
std::cout << "Before reading the file, size of the list is: " << ints.size() << '\n';
for (int i; file.read(reinterpret_cast<char*>(&i), sizeof(i)); ints.push_back(i));
for (int i : ints) {
std::cout << i << ' ';
}
}
Clarification of the second for loop:
for (int i; file.read(reinterpret_cast<char*>(&i), sizeof(i)); ints.push_back(i));
We declare a variable i since we need a place where we read the data. This one should be quite clear. We do not need to initialize i, since the condition of the loop will take care of that (although it would probably be a good practice to do it anyway).
The condition part: file.read(reinterpret_cast<char*>(&i), sizeof(i)). This may seem tricky at first, but it really isn't! First of all, we have a method call. We call std::basic_istream::read, specifying the two arguments - first, the memory address where to read the variable, and second, the number of bytes we want to read. The trick is that the read method not only reads and saves the data - it also returns the stream, so essentially after the data processing, we are left with the condition file. But it's not a bool, is it? Correct, it's not a bool (neither an int), but stream objects can be implicitly converted to bool, which is exactly what happens here! The rules are as follows: if the stream is in a correct state, the conversion returns true. It returns false otherwise. An incorrect state may be cauased, for example, by failure to read, which happens, for example, when you already have read the whole file. Essentially, this part both reads from the file and checks whether the reading process executed successfully. It's both the reading logic and the condition!
The third part: ints.push_back(i). Notice that this part only executes if the condition (reading from the file) executed successfully. It simply adds the read int (i) to the ints container.
All in all, you can read the for loop in the following way:
create a variable i, which will store, one by one, the variables from the file
as long as reading from the file is successful...
...add the read value to the container
outputFile.write(reinterpret_cast<char *>(&numbers), sizeof(numbers));
What you actually print is the binary representation of the list object itself. Unfortunately, it does not contain the data of the list directly, but instead looks similar to something like this:
template <typename T>
class list
{
struct node
{
node* next;
node* previous;
T data;
};
node* m_head;
node* m_tail;
size_t m_size;
public:
// ...
};
No direct link to the data. Even worse: With std::list, the data can get shattered all over your memory (in contrast to std::vector which assures contiguous data).
So you only can iterate over your list again (either with the iterator variant you chose already before or, more convenient, with a range based for loop):
for(auto n : numbers)
{
outputFile.write(reinterpret_cast<char*>(&n), sizeof(n));
}
Reading is different; you don't know the size in advance, do you? Well, there are ways to retrieve it (seekg, tellg), but that's more of interest if you want to read all the data into contiguous memory at once (you could reserve sufficient of in a std::vector), but that's another issue.
For the list approach:
int n;
while(inputFile.read(reinterpret_cast<char*>(&n), sizeof(n)))
{
anotherList.push_back(n);
}

C++ File to Vector

I've got a file that looks like this:
C 0484758654 95
C 0428473483 121
T 0494569848
C 0494569848 121
//.. 30 or so more entries
I want to store each "person" (represented by their phone number) into an object. I know I'll need a structure to store this in so I used a Vector:
using namespace std;
int main()
{
ifstream fin;
fin.open("test.txt");
vector<MonthlyPhoneBill> bill;
MonthlyPhoneBill temp;
while (fin >> temp.code>> temp.phoneNumber >> temp.data)
{
bill.push_back(temp);
}
for (const auto& temp : bill) {
cout << temp.code << ' ' << temp.phoneNumber << ' ' << temp.data << endl;
}
return 0;
}
Class:
class MonthlyPhoneBill {
public:
MonthlyPhoneBill();
int data;
int phoneNumber;
std::string code;
int totalBill;
};
I have a couple of problems however, the first problem is when I'm printing the file contents out it only prints the first line.
My second problem is that I have no idea how to manipulate the data now that it's stored in the vector. For example I'm wanting to calculate a bill for each individual person. I'm not sure how to access the contents of the vector and do calculations accordingly. From the file an individual person could come up multiple times and I'm not sure how to add the total cost each time they were to come up.
struct MonthPhoneBill {
int data;
int phoneNumber;
std::string code;
int totalBill;
};
int main () {
std::vector<MonthPhoneBill> dataEntry;
....
....
MonthPhoneBill temp;
// After Putting data into vector, one can simply access the data by.
while (fin >> temp.code>> temp.phoneNumber >> temp.data >> temp.totalBill) {
bill.push_back(temp);
}
Please keep in mind the content of file should be also in that format like in while loop and you have to stream all the content of that structure. you cannot miss it, if happened all the other entry will be garbage or could lead to code crash
auto temp0 = dataEntry[1].data;
auto temp1 = dataEntry[1].phoneNumber;
auto temp2 = dataEntry[1].code;
....
}
you can simply manipulate the data and access it.
Like #Sam Varshavchik said, look what's different about the third line, because indeed it is different - it's missing the last value, the data member. This causes the while to exit as it encounters no data when executing the last part of while (fin >> temp.code>> temp.phoneNumber >> temp.data), i.e. the >> temp.data.
As for the second question, again like #Sam Varshvchik said - consult your C++ book. You can manipulate the vector of your class entries in any operation, just like you did when printing contents.
To compute the total bill for each person seems to me that your program would need to undergo some changes. Since your users are repeated in the main vector, one way would be to iterate through it and group the repetitions into a single class object per user with summed bills. The other way would be to sort the main vector by phone numbers, i.e. users. Once sorted you could use approaches similar to ones in Chapter 7 of Lippman's C++ Primer.

C++ reading binary data to struct

I am currently reading a binary file that i know the structure of and i am trying to place into a struct but when i come to read off the binary file i am finding that when it prints out the struc individually it seems to come out right but then on the fourth read it seems to add it onto last member from the last read.
here the code which probably make's more sense than how i am explaining it:
Struc
#pragma pack(push, r1, 1)
struct header
{
char headers[13];
unsigned int number;
char date[19];
char fws[16];
char collectversion[12];
unsigned int seiral;
char gain[12];
char padding[16];
};
Main
header head;
int index = 0;
fstream data;
data.open(argv[1], ios::in | ios::binary);
if(data.fail())
{
cout << "Unable to open the data file!!!" << endl;
cout << "It looks Like Someone Has Deleted the file!"<<endl<<endl<<endl;
return 0;
}
//check the size of head
cout << "Size:" << endl;
cout << sizeof(head) << endl;
data.seekg(0,std::ios::beg);
data.read( (char*)(&head.headers), sizeof(head.headers));
data.read( (char*)(&head.number), sizeof(head.number));
data.read( (char*)(&head.date), sizeof(head.date));
data.read( (char*)head.fws, sizeof(head.fws));
//Here im just testing to see if the correct data went in.
cout<<head.headers<< endl;
cout<<head.number<< endl;
cout<<head.date<< endl;
cout<<head.fws<< endl;
data.close();
return 0;
Output
Size:
96
CF001 D 01.00
0
15/11/2013 12:16:56CF10001001002000
CF10001001002000
for some reason the fws seems to add to head.date? but when i take out the line to read head.fws i get a date that doesn't have anything added?
i also know thier more data to get for the header but i wanted to check the data up to what i have written is correct
cheers
1. Your date is declared as:
char date[19];
2. Your date format is exactly 19-characters long:
15/11/2013 12:16:56
3. And you print it this way:
cout<<head.date
Shortly speaking, you try to print fixed char[] using its address, which means, that it will be interpreted as null-terminated c-string. Is it null-terminated? No.
To solve this problem, declare date as:
char date[20];
And after you fill it, append null terminator:
date[19] = 0;
It applies to all members, that will be interpreted as string literals.
You have char date[19] filled with 15/11/2013 12:16:56 which is exactly 19 valid characters. This leaves no space for a terminating null and so doing cout << head.date outputs your 19 valid characters and then a load of garbage.

c++ read in array struct trouble

i am experiencing much trouble reading in files from input into an array struct. here is the code if someone can tell me what im doing wrong i can figure it out. the loop is supposed to be reading 2 strings, and 1 int, and skipping possible blank lines. but when i run it, it reads the first set and doesnt read nothing after that.
struct Instruments
{
string model;
string maker;
int year;
};
int main()
{
int size;
Instruments data[20];
int i =0;
ifstream fin;
fin.open("input.txt");
for (i=0; i<20; i++)
{
do{
getline(fin, data[size].model);
getline (fin, data[size].maker);
fin >> data[size].year;
size++;
}
while (data[size].model.length() > 0);
}
fin.close();
for(int i=0;i<size; i++)
{
cout << data[i].model << "model"<<endl;
cout << data[i].maker << "maker" << endl;
cout << data[i].year<< " year" << endl;
}
return 0;
}
There are multiple issues here:
Your first 'for' loop is using i as the loop counter but size as the array index.
After this call:
fin >> data[size].year
it will read to the end of the number and any whitespace that follows will form part of your next read, so if you are expecting to start the next record at the next line, do a blank getline() here too.
Aside from that.
Use vectors not arrays
Have a method to read from a stream into your struct, and if that succeeds, use push_back() to add it to your vector.
That doesn't necessarily mean you have to loop until the read fails, it may be that you know in advance how many you wish to read. But you should still do it this way.
size variable is not initialized. In C++, variables are not automatically initialized.
You must add:
int size = 0;
This is just a guess. In addition to the missing initialization of size, the following:
do{
.....
}
while (data[size].model.length() > 0);
looks also quite suspect to me: as soon as data[size].model has some content (which it does after the first read, this will evaluate to true and you probably have an infinite loop.
If you craft the for loop correctly, you don't need the do-while loop.