130

How can I convert a mixed case string to a lowercase string in C?

Evan Carroll
  • 71,692
  • 44
  • 234
  • 400
Tony Stark
  • 23,318
  • 38
  • 93
  • 112

6 Answers6

188

It's in the standard library, and that's the most straight forward way I can see to implement such a function. So yes, just loop through the string and convert each character to lowercase.

Something trivial like this:

#include <ctype.h>

for(int i = 0; str[i]; i++){
  str[i] = tolower(str[i]);
}

or if you prefer one liners, then you can use this one by J.F. Sebastian:

for ( ; *p; ++p) *p = tolower(*p);
Evan Carroll
  • 71,692
  • 44
  • 234
  • 400
Earlz
  • 59,859
  • 94
  • 288
  • 489
  • 41
    `for ( ; *p; ++p) *p = tolower(*p);` seems more idiomatic. – jfs Apr 18 '10 at 09:58
  • 14
    @J.F. there you go. Depends on if they want the code to look scary or nice :) (very readable one liner, but it does look scary) – Earlz Apr 18 '10 at 10:05
  • this gives me a segfault if str is a `char *`, but not if str is a char array. Got any explanation for that? – Electric Coffee Nov 22 '16 at 22:07
  • @ElectricCoffee I guess if you used `char *` you used really a string constant which gets placed in unwriteable memory section. So if you try to change the value of that it gives you a segfault. If, on the other hand, you would copy that constant string to a `char *copy = strdup (const_string)` then `copy` could be altered because it is allocated on the heap, which is writeable. So all in all it does not have anything to do with using a pointer (`char *`) but with using constant strings like in code: `char *const_str = "my const string"`. – Jan Sep 05 '17 at 11:42
  • 4
    I believe the one liner will cause you to lose your pointer to the string. – Ace.C Sep 08 '17 at 19:55
  • 2
    I believe that one liner will have untold ramifications. – NOP da CALL Mar 25 '18 at 01:12
  • the one-liner results in `error: lvalue required as increment operand` – nmz787 Apr 25 '18 at 21:00
  • @jfs Why not `while (*p = tolower(*p)) ++p;` or `while (*p = tolower(*p++))` for the purpose of obfuscation (the latter only works since C++17) ;-) – L. F. May 18 '19 at 07:46
9

to convert to lower case is equivalent to rise bit 0x60 if you restrict yourself to ASCII:

for(char *p = pstr; *p; ++p)
    *p = *p > 0x40 && *p < 0x5b ? *p | 0x60 : *p;
Deduplicator
  • 43,322
  • 6
  • 62
  • 109
Oleg Razgulyaev
  • 5,457
  • 4
  • 27
  • 28
  • 7
    To make it slightly more readable you could do `for(char *p = pstr;*p;++p) *p=*p>='A'&&*p<='Z'?*p|0x60:*p;` – Grant Peters Apr 18 '10 at 10:54
  • 8
    This version is actually slower than glibc's `tolower()`. 55.2 vs. 44.15 on my machine. – jfs Apr 18 '10 at 18:10
  • i can't imagine that: tolower() deals with chars; only if it's macro – Oleg Razgulyaev Apr 18 '10 at 18:37
  • 1
    @oraz: tolower() has `int (*)(int)` signature. Here's the code used for performance measurements http://gist.github.com/370497 – jfs Apr 18 '10 at 19:32
  • @J.F.: i see, they've used table, but i can optimize: for ( ; *p; ++p) if(*p > 'Z') {continue;} else if (*p < 'A') {continue;} else {*p = *p|0x60;} – Oleg Razgulyaev Apr 18 '10 at 20:27
  • @oraz: `if (*p > 'Z')` optimization performs better on the input I've used, but if there are many upper-case letters it takes the same time as the previous version. – jfs Apr 18 '10 at 21:33
  • The fastest version uses a lookup table instead of branches. – Joe May 08 '20 at 23:24
  • Note that this is an ASCII-only conversion that is not strictly conforming C. C provides no guarantees that upper- (or lower-)case letter are represented consecutively in any character set. – Andrew Henle Jul 02 '20 at 23:25
2

Looping the pointer to gain better performance:

#include <ctype.h>

char* toLower(char* s) {
  for(char *p=s; *p; p++) *p=tolower(*p);
  return s;
}
char* toUpper(char* s) {
  for(char *p=s; *p; p++) *p=toupper(*p);
  return s;
}
cscan
  • 304
  • 1
  • 9
  • Well if you're going the one-liner way, then `s` is a local variable in your function, you can directly use it instead of declaring `p`.` – NewbiZ Mar 08 '22 at 02:32
1

If we're going to be as sloppy as to use tolower(), do this:

char blah[] = "blah blah Blah BLAH blAH\0";
int i = 0;
while( blah[i] |=' ', blah[++i] ) {}

But, well, it kinda explodes if you feed it some symbols/numerals, and in general it's evil. Good interview question, though.

Aubin
  • 14,250
  • 9
  • 61
  • 81
Ken S
  • 35
  • 1
  • 6
    Yeah, this will fold/spindle/mutilate a variety of symbols (in ASCII, any symbol, control character, or numeral with bit 5 clear will become the same character code with bit 5 set, etc) so really, seriously, don't use it. – Ken S May 22 '13 at 21:26
  • 1
    This post is discussed on [meta](http://meta.stackoverflow.com/questions/270402/is-ghetto-an-offensive-word). – Patrick Hofman Sep 02 '14 at 08:31
  • Can you elaborate more? When I read about tolower(), they all mention that they only work on characters that have a lowercase character defined for them. From opengroup.org: "If the argument of tolower() represents an uppercase letter, and there exists a corresponding lowercase letter [CX] [Option Start] (as defined by character type information in the program locale category LC_CTYPE ), [Option End] the result shall be the corresponding lowercase letter. All other arguments in the domain are returned unchanged." If this is the case, where does tolower() fail? – 9a3eedi Mar 21 '22 at 11:20
1

If you need Unicode support in the lower case function see this question: Light C Unicode Library

Community
  • 1
  • 1
Eduardo
  • 2,237
  • 5
  • 25
  • 42
0

Are you just dealing with ASCII strings, and have no locale issues? Then yes, that would be a good way to do it.

Mark Byers
  • 767,688
  • 176
  • 1,542
  • 1,434
  • what happens if tolower() is called on a non-ascii a-z char? like '!' or '#'. i tested it on '#' and it seemed to work ok. is this generally true for all ascii chars that aren't letters a-z? – Tony Stark Apr 18 '10 at 10:29
  • 1
    @hatorade: `tolower()` leaves argument unchanged if it is not in 'A'..'Z' range. – jfs Apr 18 '10 at 18:20
  • 1
    ! and # are both ascii chars. Mark was referring to other encodings like UTF8, where you can't assume that there is one byte per character (as this solution does) – hdgarrood Nov 23 '12 at 12:31