views:

329

answers:

8

I'm trying to use the following code to convert every letters from a paragraph (strings) into lowercase letters before storing it back to the string. But the compiler wouldn't compile =(

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void spellCheck(char article[], char dictionary[]) {
    int i = 0;
    char* tempArticle;

    tempArticle = malloc((strlen(article)+1) * sizeof(char));

     strcpy(tempArticle, article);
     *tempArticle = toLower(*tempArticle);
     printf("%s", tempArticle);
}


char toLower(char letter){
    if (letter >= 'A' && letter <='Z'){
     letter = letter + 32;
    }
    return letter;
}
+1  A: 

So, there is a standard function called tolower to do this. What this new code does is declare a char pointer, sets it to point at the first element in the string, and goes character by character until the null terminator converting it to lower case.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>


void spellCheck(char article[], char dictionary[]) {
    int i = 0;
    char* tempArticle;
    char *c;

    tempArticle = malloc((strlen(article)+1) * sizeof(char));

     strcpy(tempArticle, article);
    /* *tempArticle = toLower(*tempArticle);*/

     for(c=tempArticle; *c != '\0'; c++)
          *c = (char)tolower(*c);

     printf("%s", tempArticle);
}

/* there is a standard function for this */
/*char toLower(char letter){
    if (letter >= 'A' && letter <='Z'){
     letter = letter + 32;
    }
    return letter;
}*/
BobbyShaftoe
`tolower()` is in the `ctype.h` header.
Chris Lutz
You just need to remove this: *tempArticle = toLower(*tempArticle);
James Black
There is a function toLower in the original code which isn't declared.
Kinopiko
bobby, thank you for your help. i feel like i still havn't figure out the right approach to learn programming. i am a beginner taking my first programming class in college, would you give me some advise that would help me in the future?
Thanks guys, I made those changes but somehow SO lost my edits or my browser was acting up.
BobbyShaftoe
BobbyShaftoe
you still need to dereference `c` in the for loop condition.
yjerem
@jeremy Ruten, thanks. :)
BobbyShaftoe
I had to buy a book on pointers (that is what the entire book was about) before I could really understand C.
James Black
@bobbyis this how every great programmer learn how to program?
I don't know about "every great programmer" but I would say most good C programmers have read through (at least skimmed) through that book. Dennis Ritchie was the developer of the C language and is a pretty good writer. Many courses use it as a textbook.
BobbyShaftoe
@Bobby - I haven't read it, and I think I'm good enough, but I've been corrected by enough people on Stack Overflow who _have_ read it (and have read the standard) that I think it's rubbed off on me. :P
Chris Lutz
The argument to `tolower()` should be cast to `unsigned char`, because the `char` type may be signed and `tolower()` is defined to take an argument that is in the range of `unsigned char` or is `EOF`.
caf
+1  A: 

The only compile error I get from that is from a failure to declare toLower.

See http://codepad.org/3R4ChOMe

Kinopiko
yes that could be the problem: the toLower definition (or its prototype) needs to go above the spellCheck definition.
yjerem
Yeah, you're right. But the problem is he wasn't looping so that code would just work on the first character; also I think there are a couple other issues there.
BobbyShaftoe
A: 

char toLower(char) takes a character and returns a character which is fine, but you need to iterate over all of the characters in your string using a for loop.

The compile problem is probably from a missing function prototype for toLower(). Try something like:

char toLower(char letter);

void spellCheck(char article[], char dictionary[]) {
    size_t len = strlen(article) + 1;
    char* tempArticle = malloc(len * sizeof(char));
    if (tempArticle != NULL) {
        size_t i = 0;
        for (i=0; i<len; ++len)
            tempArticle[i] = toLower(article[i]);
        printf("%s", tempArticle);
        free(tempArticle);
    } else {
        perror("malloc");
    }
}

If you have to iterate over the string, then there is no need to call strcpy(). You were also missing a call to free() - you need to make sure that you deallocate any memory that you have allocated.

As others have noted, you should probably replace your toLower() function with the Standard supplied tolower(). It is declared in <ctype.h> and will do the case-folding task in a much safer way.

D.Shawley
This won't nul-terminate the string.
Chris Lutz
And you shouldn't use an `int` to store a size. There's a type specifically for that: `size_t`
Chris Lutz
And to even further flood your answer with comments, his version of `toLower()` should be replaced by the standard-library `tolower()` which is guaranteed to work (and probably be faster). I remember some quote about reinventing wheels...
Chris Lutz
+4  A: 

BobbyShaftoe has the correct idea, but there is one minor change I would make:

void spellCheck(char article[], char dictionary[]) {
    int len = 0;
    int i = 0;
    char* tempArticle;
    char *c;

    len = strlen(article);
    tempArticle = malloc((len+1) * sizeof(char));

    for(i = 0; i < len; i++) 
      tempArticle[i] = (char)tolower(article[i]);
    tempArticle[i] = '\0';

     printf("%s", tempArticle);
}

The difference is why do a string copy when you can just convert as you move them over.

James Black
This has no real overall efficiency change, because you call `strlen()` twice.
Chris Lutz
True, strlen should be assigned to a variable and called one time, but I didn't feel like doing that part.
James Black
you could write it as `for(i = 0; article[i] != '\0'; i++)`
yjerem
Actaully, I was wrong. You call `strlen()` a lot more than twice, you call it in _every loop iteration_ as the conditional. Let us not spend so much time in high-level languages where such operations are O(1) that we forget the low-level details.
Chris Lutz
@Chris Lutz - before it causes a problem I fixed the code to only call strlen one time. I don't know of any language where calling strlen in the for loop is a good idea.
James Black
@James - Some higher-level languages like Perl or Python might store the string length as part of the string object, and update it dynamically along with other string operations. In those languages, `strlen()` would be an O(1) operation, and would be pretty safe in a loop. Not saying that makes it a good idea per se.
Chris Lutz
Hello james, you code really works! but when it comes to pointers, *tempArticle *articlewould dereference us to the value in the heap where the string is stored. why does tempArticle[i] = (char)tolower(article[i]) work? shouldn't it be *tempArticle[i] = (char)tolower(*article[i])
tempArticle[i] is a dereference as you are pointing to the character in the string at that particular point.
James Black
`tempArticle[i]` is the same as `*(tempArticle + i)` and therefore it _is_ dereferencing the pointer.
Chris Lutz
The argument to `tolower()` should be cast to `unsigned char`, because the `char` type may be signed and `tolower()` is defined to take an argument that is in the range of `unsigned char` or is `EOF`. Additionally `sizeof(char)` is defined to be 1, so you can safely use `malloc(len + 1)`.
caf
Well, I was trying not to modify everything in the original code. Not to put too fine a point on it but the function here is called spellcheck and the function clearly doesn't do that or use the dictionary array and so on and so forth. :) The nice thing about discussions on C is there is never any shortage of a peanut gallery! ...
BobbyShaftoe
+3  A: 

The Executive Summary (and Notes)

(I'm marking this as Community Wiki because, with a few exceptions, I'm just pulling all the answers and comments into one answer.)

The code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

void spellCheck(char article[], char dictionary[]) {
    size_t len = strlen(article) + 1;
    size_t i;
    char* tempArticle;

    tempArticle = malloc(len * sizeof *tempArticle);

    if (tempArticle == NULL) {
        fprintf(stderr, "spellcheck: Memory allocation failed.\n");
        return;
    }

    for(i = 0; i < len; i++)
        tempArticle[i] = tolower((unsigned char)article[i]);

    printf("%s", tempArticle);

    free(tempArticle);
}

The changes:

  1. Your toLower() function is identical to the C standard library tolower() function (except that it's not as portable and probably less efficient), so let's use that instead.
  2. tolower() works on single characters, not full strings. To convert an entire string to lowercase, you have to use it in a loop.
  3. strlen(), strcpy(), and the tolower() loop all have to loop through the string. We can eliminate the strcpy() loop by copying and tolower()ing at the same time. This will result in a slight increase in efficiency.
  4. Changed sizeof(char) to sizeof(*tempArticle). There is no difference - sizeof(char) is guaranteed by the standard to be 1, and *tempArticle is a char, so it's size will always be 1 - but if in the future you change tempArticle to be a wchar_t or some other type, the malloc() line will still work correctly. Some would argue to leave off the sizeof() altogether, since sizeof(char) is guaranteed to be 1. I would leave it.
  5. Add a call to free() at the end of the function - otherwise, the function will leak memory.
  6. Check for malloc() failure.

Improvements over other posts in this thread:

  1. strlen() returns a size_t, not an int. The difference is more than semantic - int is often signed, while size_t is unsigned, and is guaranteed by the standard to be what you want. size_t should be used as a variable to hold array/pointer indexes, not int.
  2. Calling strlen() is an O(n) operation, so we should save it's result, particularly if we're going to need it later.
  3. The argument to tolower() is cast to unsigned char, as required by the rather odd calling convention of this function.
Chris Lutz
@caf - Thanks. I saw your comment for James Black's answer and was about to fix this. Don't know why I forget this so often.
Chris Lutz
Chris: No worries, I like the idea of a community wiki answer - I reckon it should be standard practice for all of the really simple done-to-death questions. Forgetting the calling convention for `tolower` is understandable, since it's so unusual (forgetting to `free()` on the other hand... ;)
caf
A: 

Forgive me if I misunderstood the problem, but wouldn't it be better just to do the conversion in place rather than copying the string into a temporary buffer? After all, the questioner claims to want to store the result back in the original string.

I have in mind something like this:

#include <stdio.h>
#include <ctype.h>

void spellCheck(char article[], char dictionary[]) {
    for (char *pos = article; *pos != '\0'; pos++) {
        *pos = tolower(*pos);
    }

    // Remove the following line if you don't want to print the result.
    printf("%s", article);
}

Note that depending on the C standard you're using, you may need to move the declaration of pos out of the for loop header.

David
Given that the function is named `spellCheck` I think it's doing more than just making a lowercase copy of the string. But the OP is very unclear about what he's trying to do.
Chris Lutz
A: 

I would do it this way:

void spellCheck(char *article, char *dictionary)
{
  while(*article)
  {
    *article = tolower(*article);
    article++;
  }
}

As this is the "standard" "iterate over a string" thing.

But I don't really think the OP wants that, spellCheck seems quite a name for something more evolved.

Also, beware that tolower uses the current locale and is restricted to unsigned char range (even if it takes an int).

To all those snippets using malloc, check the return of malloc for NULL.
A: 

Did you ever heard of <ctype.h>

Arabcoder