I'm currently trying to get "variadic functions" down and just trying to load 4 names in a string vector and then print them out. When I do this with 'int' type and use numbers, it works fine, but when I use a string vector I get the error.
#include "stdafx.h"
#include<cstdio>
#include<cstdarg>
#include<string>
#include<vector>
#include<iostream>
using namespace std;
int count;
vector<string> namesVector;
void names(int count, ...)
{
va_list namesList;
int i; // for loop
va_start(namesList, count);
for (i = 0; i < count; i++)
{
string currentElement;
currentElement = va_arg(namesList, string);
namesVector[i] = currentElement;
}
va_end(namesList);
}
int main()
{
int nameCount = 4;
names(nameCount,"jon", "maggie", "joan", "alfred");
for (int i = 0; i < nameCount; i++)
{
cout << "Name at element " << i << " is: " << namesVector[i] << endl;
}
}
C++ only allows to use trivially-copyable types as variadic arguments. As std::string is constructible from a char* pointer which points to a null-terminated buffer, you may use char* instead of std::string type. Just replace
currentElement = va_arg(namesList, string);
with
currentElement = va_arg(namesList, char*);
in your code. To get rid of this limitation, consider variadic templates, which generate code in compile-time for any type you use.
Your code also contains a run-time error. This:
namesVector[i] = currentElement;
is very likely to crash your program as you didn't allocate any memory in the vector. Vectors are actually dynamic arrays, so you should either pass a size argument to appropriate constructor, or call resize on the vector. In you case you may do neither of it, but just use push_back method:
namesVector.push_back(currentElement);
Related
I am working on a project where I parse a string in to an array and then return it back to the main function. It parses fine but when I return it to the main function I can't get access to the array elements.
//This is from the Main function. It calls commaSeparatedToArray which returns the array.
for (int i = 0; i < numberOfStudents; i++) {
string * parsedToArray = mainRoster->commaSeparatedToArray(studentData[i]);
Degree degreeType = SOFTWARE;
for (int i = 0; i < 3; i++) {
if (degreeTypeStrings[i] == parsedToArray[8])
degreeType = static_cast<Degree>(i);
}
mainRoster->add(parsedToArray[0], parsedToArray[1], parsedToArray[2], parsedToArray[3], stoi(parsedToArray[4]), stoi(parsedToArray[5]), stoi(parsedToArray[6]), stoi(parsedToArray[7]), degreeType);
}
//Here is the commaSeparatedToArray function
string * roster::commaSeparatedToArray(string rowToParse) {
int currentArraySize = 0;
const int expectedArraySize = 9;
string valueArray[expectedArraySize];
int commaIndex = 0;
string remainingString = rowToParse;
while (remainingString.find(",") != string::npos) {
currentArraySize++;
if (currentArraySize <= expectedArraySize) {
commaIndex = static_cast<int>(remainingString.find(","));
valueArray[currentArraySize - 1] = remainingString.substr(0, commaIndex);
remainingString = remainingString.substr(commaIndex + 1, remainingString.length());
}
else {
cerr << "INVALID RECORD. Record has more values then is allowed.\n";
exit(-1);
}
}
if (currentArraySize <= expectedArraySize) {
currentArraySize++;
commaIndex = static_cast<int>(remainingString.find(","));
valueArray[currentArraySize - 1] = remainingString.substr(0, commaIndex);
remainingString = remainingString.substr(commaIndex + 1, remainingString.length());
}
if (currentArraySize < valueArray->size()) {
cerr << "INVALID RECORD. Record has fewer values then is allowed.\n";
exit(-1);
}
return valueArray;
}
1) You can't return arrays in C++. Your code (as I'm sure you know) returns a pointer to an array. That's an important difference.
2) The array is declared locally in the function and therefore no longer exists after the function has exitted.
3) Therefore once you have returned from the function you have a pointer to something which no longer exists. Bad news.
4) You must always consider the lifetime of objects when you program C++. One solution to this problem is to dynamically allocate the array (using new[]). This means that the array will still exist when you exit the function. But it has the signifcant disavantage that you must remember to delete[] the array at a suitable later time.
5) The best solution (in general) is to use a std::vector. Unlike an array a std::vector can be returned from a function. So this option leads to the simplest, most natural code.
vector<string> roster::commaSeparatedToArray(string rowToParse) {
...
vector<string> valueArray(expectedArraySize);
...
return valueArray;
}
Since your array/vector is constant size, you could also use a std::array
array<string, expectedArraySize> valueArray;
To complete the answer that John has already given, I made some example code to show you, how such function could look like.
Parsing, or tokenizing can be easily done with the std::sregex_token_iterator. That is one of the purposes for this iterator. You can see the simplicity of the usage below.
In the function we define a vector af string and use its range constructor to do the whole tokenizing.
Then we make a sanity check and return the data.
Please see:
#include <string>
#include <regex>
#include <iterator>
#include <vector>
#include <algorithm>
#include <iostream>
const std::regex separator(",");
constexpr size_t ExpectedColumnSize = 9;
std::vector<std::string> commaSeparatedToArray(std::string rowToParse)
{
// Parse row into substrings
std::vector<std::string> columns{
std::sregex_token_iterator(rowToParse.begin(),rowToParse.end(),separator ,-1),
std::sregex_token_iterator() };
// Check number of columns
if (columns.size() != ExpectedColumnSize) {
std::cerr << "Error. Unexpected number of columns in record\n";
}
return columns;
}
// test code
int main()
{
// Define test data
std::string testInputData{ "1,2,3,4,5,6,7,8,9" };
// Get the result from the parser
std::vector<std::string> parsedElements{ commaSeparatedToArray(testInputData) };
// show the result on the console
std::copy(parsedElements.begin(), parsedElements.end(), std::ostream_iterator<std::string>(std::cout, "\n"));
return 0;
}
I'm just starting to code, and am learning about arrays right now. I am trying to write a program that takes in a list of arrays, and tells me if the first or last number is a 2. To do this, I'm using a function.
My code looks like:
#include <iostream>
using namespace std;
const int size = 6;
bool firstlast(int array[size]);
int main()
{
int array[size];
for (int index = 0; index < size; index++)
{
cout << "Enter value for array[" << index << "]\n";
cin >> array[index];
}
bool check = firstlast(array[size]);
if (check)
cout << "The array either starts or ends in 2!\n";
else
cout << "The array does not start or end with 2.\n";
return 0;
}
bool firstlast(int array[size])
{
if (array[0] == 2)
return true;
if (array[size - 1] == 2)
return true;
return false;
}
What am I doing wrong?
The compiler gives me the error:
candidate function not viable: no known conversion from 'int' to 'int *' for 1st argument; take the address of the argument with and
The compiler is recognising your function fine.
The problem is in the manner your code calls the function
bool check = firstlast(array[size]);
which attempts to pass array[size] (a non-existent element of array) to a function expecting a pointer.
The call, presumably, should be
bool check = firstlast(array);
since arrays are implicitly converted to pointers when passed to functions.
This code
bool check = firstlast(array[size], size);
tries to pass the sizeth element of array not the array itself. In C++ arrays are passed by pointer, even if you write the function parameter with array syntax.
To avoid confusing yourself, change firstlast to
bool firstlast`(int* array, int size)`
and call it with
bool check = firstlast(array, size);
I have:
vector<string> myVector = {0};
myVector.push_back("first");
myVector.push_back("second");
char *list[] = ????
I want it to be initialized like if I was doing this
char *list[] = { "first", "second", NULL };
I know I can start allocating memory based on the size and of the vector and the size of the longest string in the vector (list[v.size()+1][longest_string_in_vector]) but I wanted to see I'm not thinking of something that might be easier/faster.
If the legacy code requires a char **, then to create a variable list, you can create a vector as you initially are doing in your question.
After that, create a std::vector<char *>, where the pointers are pointers within the vector for each item. Of course, you have to ensure that the vector doesn't go out of scope or is resized. It has to be fully "set up" before creating the std::vector<char *>.
In addition, since you are certain that the legacy function does not attempt to alter the strings sent to it, we should take away the "constness" of the strings.
#include <vector>
#include <string>
#include <iostream>
void legacy_function(char **myList)
{
for (int i = 0; myList[i]; ++i)
std::cout << myList[i] << "\n";
}
using namespace std;
int main()
{
vector<string> myVector;
myVector.push_back("first");
myVector.push_back("second");
//...
// create the pointer vector
vector<char *> myPtrVector;
// add pointer to string to vector
for (size_t i = 0; i < myVector.size(); ++i)
myPtrVector.push_back(const_cast<char*>(myVector[i].c_str()));
// stick the null at the end
myPtrVector.push_back(NULL);
// ...
// call legacy function
legacy_function(&myPtrVector[0]);
}
Basically, we created the strings in a vector, and created another vector that stores pointers to the strings.
Note that the function legacy_function takes a char **, and all we need to do is pass it the address of the first element in our pointer vector.
Live Example: http://ideone.com/77oNns
Edit: Rather than having the code strewn in different areas of your program, a better approach in terms of code organization is to encapsulate the creation of the array:
#include <vector>
#include <string>
class CharPtrPtr
{
std::vector<std::string> m_args;
std::vector<char *> m_argsptr;
public:
void add(const std::string& s) { m_args.push_back(s); }
char ** create_argsPtr()
{
m_argsptr.clear();
for (size_t i = 0; i < m_args.size(); ++i)
m_argsptr.push_back(const_cast<char*>(m_args[i].c_str()));
m_argsptr.push_back(NULL);
return &m_argsptr[0];
}
char **get_argsPtr() { return m_argsptr.empty()?NULL:&m_argsptr[0]; }
void clear_args() { m_args.clear(); m_argsptr.clear(); }
};
#include <iostream>
void legacy_function(char **myList)
{
for (int i = 0; myList[i]; ++i)
std::cout << myList[i] << "\n";
}
int main()
{
CharPtrPtr args;
args.add("first");
args.add("second");
legacy_function(args.create_argsPtr());
}
Live Example: http://coliru.stacked-crooked.com/a/834afa665f054a1f
I tried these two ways,
1.Initialize manually
char *list[] = { (char*)&myVector[0][0], (char*)&myVector[1][0] };
2.Initialize in a loop
char **list2 = new char*[ myVector.size() ];
for ( unsigned int i = 0; i < myVector.size(); ++i ) {
list2[ i ] = (char*)&myVector[ i ][0];
}
However these lists only have pointers to the each string in the vector and don't actually have a copy. If you change the strings, you'll see the changes from the lists. But if you empty the vector then the lists will have a dangling pointer.
3.If you want a copy of the strings then,
char **list = new char*[ myVector.size() ];
for ( unsigned int i = 0; i < myVector.size(); ++i ) {
list[ i ] = new char[myVector[i].size()+1];
strcpy( list[ i ], &myVector[i][0] );
}
I wouldn't write this code but, there you go..
This is a class template for an Array. I overloaded the [ ] operator in hopes it would fix the "out of bounds" issue. The print outs work well, except if it falls out of range, the compiler enables the range by default and it displays a 6 digit number.
Perhaps looking for a better way to initialize the arrays with the appropriate element number for a better check and if it does fall out of range when looking up the element, display an error.
// implement the class myArray that solves the array index
// "out of bounds" problem.
#include <iostream>
#include <string>
#include <cmath>
using namespace std;
template <class T>
class myArray
{
private:
T* array;
int begin;
int end;
int size;
public:
myArray(int);
myArray(int, int);
~myArray() { };
void printResults();
// attempting to overload the [ ] operator to find correct elements.
int operator[] (int position)
{if (position < 0)
return array[position + abs(begin)];
else
return array[position - begin];
}
};
template <class T>
myArray<T>::myArray(int newSize)
{
size = newSize;
end = newSize-1;
begin = 0;
array = new T[size] {0};
}
template <class T>
myArray<T>::myArray(int newBegin, int newEnd)
{
begin = newBegin;
end = newEnd;
size = ((end - begin)+1);
array = new T[size] {0};
}
// used for checking purposes.
template <class T>
void myArray<T>::printResults()
{
cout << "Your Array is " << size << " elements long" << endl;
cout << "It begins at element " << begin << ", and ends at element " << end << endl;
cout << endl;
}
int main()
{
int begin;
int end;
myArray<int> list(5);
myArray<int> myList(2, 13);
myArray<int> yourList(-5, 9);
list.printResults();
myList.printResults();
yourList.printResults();
cout << list[0] << endl;
cout << myList[2] << endl;
cout << yourList[9] << endl;
return 0;
}
First of all, your operator[] is not correct. It is defined to always return int. You will get compile-time error as soon as you instantiate array of something, that is not implicitly convertible to int.
It should rather be:
T& operator[] (int position)
{
//...
}
and, of course:
const T& operator[] (int position) const
{
//you may want to also access arrays declared as const, don't you?
}
Now:
I overloaded the [ ] operator in hopes it would fix the "out of bounds" issue.
You didn't fix anything. You only allowed clients of your array to define custom boundaries, nothing more. Consider:
myArray<int> yourList(-5, 9);
yourList[88] = 0;
Does your code check for out-of-bounds cases like this one? No.
You should do it:
int operator[] (int position)
{
if((position < begin) || (position > end)) //invalid position
throw std::out_of_range("Invalid position!");
//Ok, now safely return desired element
}
Note, that throwing exception is usually the best solution in such case. Quote from std::out_of_range doc:
It is a standard exception that can be thrown by programs. Some components of the standard library, such as vector, deque, string and bitset also throw exceptions of this type to signal arguments out of range.
An better option to redefining an array class is to use the containers from the std library. Vector and array(supported by c++11). They both have an overloaded operator [] so you can access the data. But adding elements using the push_back(for vector) method and using the at method to access them eliminates the chance or getting out of range errors, because the at method performs a check and push_back resizes the vector if needed.
Changed completely due to suggestions from other member. Most problems solved, still having problems. Now won't output any names from the array in main. Not sure if I'm passing them back correctly from function.
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
void bubblesort(string[], const int);
int sub = 0;
int main()
{
const int maxsize = 100;
string friendArray[maxsize];
ifstream friends;
friends.open("myFriends.dat");
while (sub < maxsize)
{
getline(friends, friendArray[sub]);
sub++;
}
bubblesort(friendArray, maxsize);
cout<<friendArray[0]<<" "<<friendArray[1]<<" "<<friendArray[2];
system("pause");
return 0;
}
void bubblesort(string *array, const int size)
{
bool swap;
string temp;
do
{
swap = false;
for (int count = 1; count < (size - 1); count++)
{
if(array[count-1] >array[count])
{
temp = array[count-1];
array[count-1] = array[count];
array[count] = temp;
swap = true;
}
}
}
while(swap);
}
Your problem isn't necessarily that temp inside bubblesort is not a char, the problem is that array is declared as a string and not a string[].
The reason you're getting the error is because array[count+1] is of type char, and temp is of type string. std::swap expects two elements of the same type.
However, that may be the least of your problems, your code doesn't compile for quite a few reasons. Not just that but you're passing in maxsize to bubblesort at each iteration. There's a flaw in both your logic and your syntax.
EDIT: Since you're still having trouble getting the sorting to work, here's a working modification of your code:
#include <iostream>
void bubblesort(std::string array[], size_t size)
{
bool bSwapped;
std::string temp;
do
{
bSwapped = false;
for (size_t count = 1; count < size; count++)
{
if(array[count-1] > array[count])
{
std::swap(array[count-1], array[count]);
bSwapped = true;
}
}
}
while(bSwapped);
}
int main(void)
{
std::string array[] = { "def", "ghk", "abc", "world", "hello" };
bubblesort(array, sizeof(array)/sizeof(*array));
for (size_t i = 0; i < sizeof(array)/sizeof(*array); ++i)
std::cout << array[i] + " ";
std::cout << std::endl;
return 0;
}
bubblesort could also be written as: void bubblesort(std::string *array, size_t size). There's no difference in this case since, when passed to a function, arrays decay into pointers.
Since arrays are passed by reference, a pointer to the first element, any modifications made to array inside of bubblesort will actually be modifying your array in main. So that's how arrays are "returned".
std::vector is a good alternative to the standard array, since it automatically resizes and obviously contains the length of the internal array so that you don't have to pass the size everywhere you pass an std::vector. You can also use it the same way as a regular array.
temp is a string, array[count] is a char (since an std::string is a vector of char elements.) I'm not sure what you're trying to do here, but the compiler is correct - you can't assign a char to a string.
You could change temp to be a char, since all you do with it is assign a char to it, and then assign it back to an element of array, which is also a char.
You need to declare temp as char. You can use std::swap to avoid such mistakes in the future:
std::swap(array[count], array[count+1]);
This would make your code compile, but it would not do what you're trying to do (bubblesort). The problem is that you are passing a single string (which is also an "array" of characters) instead of an array of strings, which is, in a very lose sense, "an array of arrays of characters". Your bubblesort needs to accept string *array as its first parameter.