Search code examples
c++arraysstructlowercase

C++ - Changing single lowercase charachter in a word to uppercase and vice versa using struct data type


As you can see from the title I want to change lowercase charachter in word to uppercase and vice versa. Also I need to use struct object (in my case name). I have a problem when I change charachter from lowercase to uppercase it only changes in the first word not in the second,third and so on. I am also reading words from file

Here is the input file

Aayak Audi 
Ahmed Golf7 
Samed Golf5

Here is the code

#include <iostream>
#include <fstream>
#include <string>
#include <algorithm>
#include <cctype>
using namespace std;

struct pismaStr
{
    string ime;
    string objekat;
};


void malaVelikaSlova (string name)
{
    for (int i = 0; i < name.length()-1; i++)
    {
            if (name.at(i) >= 'A' && name.at(i) <= 'Z')
            name.at(i) += 32;
            else if (name.at(i) >= 'a' && name.at(i) <= 'z')
            name.at(i) -= 32;
            cout << name;
            break;
    }
}
int main()
{
    ifstream pismo;
    pismo.open("pismo.txt");
    ofstream novoPismo;
    novoPismo.open("novaSlova.txt");
    pismaStr stvari[200];
    int brojStvari = 0;

    while(pismo >> stvari[brojStvari].ime >> stvari[brojStvari].objekat)
    {
        brojStvari++;
    }
for (int i = 0; i < brojStvari; i++)
    {
       vector <pismaStr> vec = {pismaStr{stvari[i].ime}};
       for (auto obj : vec)
       {
        malaVelikaSlova (obj.ime);
       }
}

Here is the output:

aayak
ahmed
samed

It was:

Aayak 
ahmed 
samed

I want it to look like this

aAYAK
sAMED
aHMED

How can I fix this? Any tips?


Solution

  • Tangential,

    but it will be an issue, is this line

    for (int i = 0; i < name.length()-1; i++)
    

    This will loop from name[0] to name[name.length() - 2]. The std::string::length returns the number of usable characters. It does not include the null terminator, so you don't need to subtract 1. It should be

    for (int i = 0; i < name.length(); i++)
    

    Your bigger problem

    is the break statement at the end of your loop (indentation added for clarity)

    for (int i = 0; i < name.length()-1; i++)
    {
        if (name.at(i) >= 'A' && name.at(i) <= 'Z')
            name.at(i) += 32;
        else if (name.at(i) >= 'a' && name.at(i) <= 'z')
            name.at(i) -= 32;
        cout << name;
        break;          // <--- this exits the loop entirely
    }
    

    Your break; tells the program to exit the loop immediately. No further iterations of the loop are performed. Your cout statement is also within the loop. Once you do get the loop running for each iteration, you'll output each step of the transformation. To only output it once (at the end) you put it outside of the loop. If you want to loop over every character (and you do), your final code looks like

    void malaVelikaSlova (string name)
    {
        for (int i = 0; i < name.length() - 1; i++)
        {
            if (name.at(i) >= 'A' && name.at(i) <= 'Z')
                name.at(i) += 32;
            else if (name.at(i) >= 'a' && name.at(i) <= 'z')
                name.at(i) -= 32;
        }
        cout << name;
    }
    

    Other things you can change

    You don't need to do bounds checking on your string indexes, since you're looping based on the string length, and it's not changing, so you don't need to extra overhead of std::string::at. You can just use the index operator:

    // name.at(i); // <-- no need to do this anywhere
    name[i]        // <-- slightly faster
    

    Since you're applying some operation to each element (character) in your container (string), this is a great candidate for std::transform, from the <algorithm> library. I'm also using a lambda expression here, which is a great too from C++ to get familiar with.

    https://en.cppreference.com/w/cpp/algorithm/transform
    https://en.cppreference.com/w/cpp/language/lambda

    void malaVelikaSlova (string name)
    {
        std::transform(
            name.begin(),
            name.end(),
            [](char c) -> char
            {
                if (c >= 'A' && c <= 'Z')
                    return c + 32;
                if (c >= 'a' && c <= 'z')
                    return c - 32;
                return c; // <-- for all other characters
            }
        );
        
        std::cout << name << "\n";
    }
    

    You could even take advantage of the std::isupper, std::islower, std::toupper, and std::tolower functions to make your code more explicit. Note that std::string is an alias for std::basic_string<char> (its value type is char), the upper and lower functions operate on unsigned chars, so you'll need to convert the chars to unsigned chars:

    https://en.cppreference.com/w/cpp/string/basic_string
    https://en.cppreference.com/w/cpp/string/byte/tolower
    https://en.cppreference.com/w/cpp/string/byte/toupper
    https://en.cppreference.com/w/cpp/string/byte/isupper
    https://en.cppreference.com/w/cpp/string/byte/islower

    void malaVelikaSlova (string name)
    {
        std::transform(
            name.begin(),
            name.end(),
            [](unsigned char c) -> unsigned char // <-- must convert to unsigned to be safe with upper/lower functions
            {
                if std::isupper(c) return std::tolower(c);
                if std::islower(c) return std::toupper(c);
    
                return c; // <-- for all other characters
            }
        );
        
        std::cout << name << "\n";
    }