0

I'm fairly new to C++ and just learning it on my own, so please go somewhat easy on me. What I am trying to do:

  • Print an array that has program names on it and ask the user to type the name of a program
  • Get the user input and check if it matches an entry in the string array
  • If the user input has a match, run the program that matched

The code I wrote functions as I want it to, yet the errors and warnings in the build terminal indicate that something is not correct. I have tried using different data types but I have run out of ideas. By using a char * array for the program names I got it down to one warning: "ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]". I would love to get some clarity on what I am doing wrong that causes this warning.

This is the code:

   #include <stdio.h>
   #include <conio.h>
   #include <iostream>
   #include <string.h>
   #include <windows.h>
   using namespace std;



   void keyControl(), randomNumber();

   // What data type for array?
   string programList[99] = {"keyControl", "randomNumber"};
   char writeWord[18] = "";

   int main(){

       // Is there an equivalent of strcmp() for string datatype?
       while(strcmp(writeWord, "quit") != 0){

           system("@cls");

           cout << ("\n\tProgram list:\n\t");
           for(int x = 0; x < 99; x++){cout << (programList[x]) << (", ");}
           cout << ("\n\n\tType a program to run or 'quit' to exit:\n\n<");

           // Is there an equivalent of gets() for string datatype?
           gets(writeWord);

           system("@cls");

           if(strcmp(writeWord, programList[1]) == 0){
               //keyControl();
           }

           if(strcmp(writeWord, programList[2]) == 0){
               //randomNumber();
           }

       }

       return 0;
   }

And yes, I have read everywhere that gets() is a no-no but this is only a quickly thrown together test program and I would use something else if I was actually writing code seriously or for work, etc. I'm only doing this for fun and learning purposes currently, and want to focus on one thing at a time :-)

Thanks

  • See [Why gets() is so dangerous it should never be used!](https://stackoverflow.com/q/1694036/3422102) You don't really have C++, you have ancient and outdated C with a few C++ headers and strings included. Never include `conio.h` -- it is an archaic DOS header and 100% non-portable to anything other than DOS/windows. See also [Why is “using namespace std;” considered bad practice?](https://stackoverflow.com/q/1452721/364696) – David C. Rankin Mar 14 '21 at 23:17

1 Answers1

1

Fast forward a couple of decades in C++ and find the STL container std::map which maintains a unique set of key/value pairs. In your case you want to associate (map) and string with a function pointer so that when "keyControl" is entered by the user, the keyControl() function is called, and so on.

In your case you can populate a std::map with your key/values pairs by creating a map that associates a std::string and void(*)(void) pointer as a pair, e.g.

    /* std::map with string as key and function pointer as value */
    std::map<std::string, void(*)(void)> proglist {{"keyControl", keyControl},
                                                   {"randomNumber", randomNumber}};

Then it is simply a matter of looping continually, prompting the user for input and checking if the input matches one of the keys in the std::map, if it does, you executed the mapped function.

getline() is used for input into a std::string (read again the page on why gets() should NEVER be used). C++ provides an overload of == for comparing string equality. You can set various exit conditions based on the stream-state after your call to getline() or based on the content of the input filled. For example:

    std::string line {};    /* line for input */
    
    while (1) { /* loop continually */
        std::cout << "\nenter program name: ";  /* prompt for input */
        /* read line, break on manual EOF, empty input or "quit" */
        if (!getline (std::cin, line) || line.length() == 0 || line == "quit")
            break;

If the user generates a manual EOF (Ctrl + d Linux or Ctrl + z windows), if the string is empty (e.g. the user simply pressed Enter) or the string read is "quit" -- adjust to meet your needs.

All you need to do to check if the input matches a key in your std::map is to use the .find() member function that will return a pointer to the mapped pair if found or will return the end of the std::map if not found. The value part of the key/value pairt held in the map is accessed through the member name second (key is first, value is second) So you can do:

        /* read line, break on manual EOF, empty input or "quit" */
        if (!getline (std::cin, line) || line.length() == 0 || line == "quit")
            break;
        auto found = proglist.find (line);  /* search for input in map */
        if (found == proglist.end()) {      /* if not found, show error */
            std::cerr << "  error: no matching program name.\n";
            continue;
        }
        found->second();        /* otherwise execute the function */

Putting it altogether, you would have:

#include <iostream>
#include <string>
#include <map>

void keyControl()
{
    std::cout << "keyControl() called.\n";
}

void randomNumber()
{
    std::cout << "randumNumber() called.\n";
}

int main() {
    
    /* std::map with string as key and function pointer as value */
    std::map<std::string, void(*)(void)> proglist {{"keyControl", keyControl},
                                                   {"randomNumber", randomNumber}};
    std::string line {};    /* line for input */
    
    while (1) { /* loop continually */
        std::cout << "\nenter program name: ";  /* prompt for input */
        /* read line, break on manual EOF, empty input or "quit" */
        if (!getline (std::cin, line) || line.length() == 0 || line == "quit")
            break;
        auto found = proglist.find (line);  /* search for input in map */
        if (found == proglist.end()) {      /* if not found, show error */
            std::cerr << "  error: no matching program name.\n";
            continue;
        }
        found->second();        /* otherwise execute the function */
    }
}

Example Use/Output

$ ./bin/map_str_fn

enter program name: foo
  error: no matching program name.

enter program name: keyControl
keyControl() called.

enter program name: randomNumber
randumNumber() called.

enter program name: quit

Getting familiar with the STL containers will save you no end of grief using C++. The library has more than a decade and a half of validation and efficiency tweaks and will be far more robust than whatever you "whip-up" on the fly.

Look things over and let me know if you have questions.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • Thanks man for the comprehensive answer. I'll be sure to get familiar with maps, had no idea what they were used for before now. So getline() is much better than gets() and I should be able to use it just like gets() but for string data type, correct? – Markus Vaittinen Mar 15 '21 at 02:29
  • Yes you can. `getline()` will read up to the first `'\n'` and extract it from the input stream but NOT include it in the buffer filled. Save the [cppreference.com](https://en.cppreference.com/) link. It is by far the best C++ reference on the internet (even with their horrible current test of using an external search). Good luck with your coding! – David C. Rankin Mar 15 '21 at 02:32
  • Alright, good to know. One thing about maps: Is there an easy way to print the strings (keys) from the map like I did with the array, or do I need a whole separate thing for it? It would be very useful to display the program names on screen. – Markus Vaittinen Mar 15 '21 at 13:44
  • Simplest is to use a [Range-based for loop](https://en.cppreference.com/w/cpp/language/range-for), e.g. `for (auto p : proglist) std::cout << p.first << '\n';` – David C. Rankin Mar 15 '21 at 16:57
  • `for (const auto& p : proglist) std::cout << p.first << '\n';` with all I's dotted and T's crossed. – David C. Rankin Mar 15 '21 at 17:22
  • Thanks a lot man. Works like a charm now :-) – Markus Vaittinen Mar 15 '21 at 18:02
  • Glad it helps. The "new" C++ takes a bit to wrap your head around. I've used the language since '89 and it is has evolved into a complete and almost entirely separate language from C. It takes a bit to wrap your head around all the changes (if you haven't used it in a decade, it will be close to starting over). A lot of the work that has been done, was to add features for things that programmers do over and over again. The STL now provides everything from strings to vectors to linked-lists to hash tables, etc.. and the algorithm and numeric headers automate a number of frequent iterative tasks – David C. Rankin Mar 15 '21 at 18:14