This continues to discuss functions relating to characters and strings.
Exercise : Write a program that (1) initializes a character array with "testing 1, 2, 3", (2) then prints the string onto the screen using a previously written subroutine "int st_puts(char *);" (3) Write also the "makefile"
Ans :
#include <stdio.h>
#include <assert.h>
int st_puts(char *a)
{ int i, c;
for (i=0;;i++) /* condition is missing in this for loop, i.e. no testing needed */
{c=*a;
if (c!='\0')
{putchar(c);
a++;
assert(i<=65535);
}
else
{putchar('\n');
return 1;
}
}
}
int main()
{ char a[]="testing 1, 2, 3";
st_puts(a);
return 0;
}
If this file is "st_puts_t.c" then "makefile" should be
dummy :
gcc -Wall st_puts_t.c
OR, if you have created "/home/tom/include/c.h" as described in part 1, then you may replace
#include <stdio.h>by
#include <assert.h>
#include <c.h>and the "makefile" will then be
dummy :
gcc -Wall st_puts_t.c -I/home/tom/include
int puts(const char *s); |
int puts(char *s) works like "st_puts(char *)", it prints the string onto the screen. It returns -1 (-1 is often called EOF, end-of-file) if an error occurs, and non-negative otherwise. char *gets(char *s); get a string from the keyboard. It returns an address where the string is store. |
Exercise : Write a program that reads a string from the keyboard, and then prints it out on the screen, using the two subroutines "puts(), gets()" in <stdio.h>.
Ans :
#include <stdio.h>
int main()
{ char a[1000];
L20:
printf("Enter a string, terminate with Enter\n");
gets(a);
puts(a); /* Notice that we may use "puts(gets(a));" instead */
goto L20;
}
Exercise : Find out errors in the following program.
#include <stdio.h>
int main()
{ char a[1000], *ptr;
char b[]="testing";
a = "TEST";
puts(a);
puts(b);
ptr = "Hong Kong";
puts(ptr);
ptr = "Kowloon";
puts(ptr);
ptr = "New Territories";
puts(ptr);
puts("My name is Wu");
return 0;
}
Ans : The only offending line is
a = "TEST";To understand why, we must know how the compiler handles strings.
When the compiler sees strings, e.g. "testing", "TEST", "Hong Kong", "Kowloon", "New Territories", "My name is Wu", it reserves memory for them (it is called "define storage", DS), e.g.
testing0TEST0Hong Kong0Kowloon0New Territories0My name is Wu0 ^ ^ ^ ^ ^ ^and automatically puts a 0 at the end of each string. The compiler returns an address of where the string is stored. Therefore statements like
ptr = "Hong Kong";work well, because "ptr" is a character pointer, and can accept an address. The statement
ptr = "Kowloon";
ptr = "New Territories";
puts("My name is Wu");works too, because the address is immediately used by the subroutine
But the statement
char a[1000];instructs the compiler to assign 1000 bytes for a[1000], and the starting address of a[] cannot be changed, the starting address must remain CONSTANT throughout the program.
Now
a = "TEST";attempts to replace the address of a[0] by that of "TEST", and this is not permitted.
But had we declared
char *a;then that would be O.K.
a = "TEST";
Exercise : Will the following 2 programs work?
(Program 1) :
#include <c.h>
int main()
{ char *ptr="testing one two three";
puts(ptr);
ptr="another string";
puts(ptr);
ptr="still another string";
puts(ptr);
return 0;
}
(Program 2) :
#include <c.h>
int main()
{ char *ptra[]={"error underflow", "error overflow", "division by zero"};
int i;
for (i=0;i<3;i++)
{puts(ptra[i]);
}
return 0;
}
Ans : Yes. Note that Program 2 uses an array of character pointers.
Exercise : Write your own "char *gets(char *s)" subroutine and call it
char *st_gets(char *a) {....}You are to use function "int getchar(void)" from <stdio.h> . Also write a small main program to test it. The function should return the address where the string is stored.
After you have debugged this subroutine with a test program, you are to put this subroutine in the library, say, "/home/tom/lib/libtom.a". Also, you should add one line at the end of header file "/home/tom/include/c.h", "extern char *st_gets(char *);". We assume that the header file "c.h" already has a line "extern char *st_puts(char *);"
Ans :
#include <c.h>
char *st_gets(char *a)
{ char *ptr;
int c;
ptr=a ; /* Since this function returns a character pointer,
we will not change char *a, but uses
a char *ptr instead */
L20:
c=getchar();
if (c != '\n')
{*ptr=c ;
ptr++;
goto L20;
}
else
{*ptr='\0';
return a;
}
}
int main()
{ char a[100];
L20:
st_gets(a);
st_puts(a);
goto L20;
}
"makefile" should be somewhat like,
dummy :
gcc -Wall st_gets_t.c -I/home/tom/include/ -L/home/tom/lib/ -ltom
You should make a directory, say, "/home/tom/include/" where you put the main header file. You should also make a directory, say, "/home/tom/lib/", where you put the library "libtom.a". When a subroutine has been thoroughly debugged, you should compile it, and store it in the library with, e.g. gcc -Wall -c xxxx.c -I/home/tom/include/ Also you should update your header file "/home/tom/include/c.h" by adding one sentence, e.g. extern char *st_gets(char *); Later on, when you write program, you just add one statement at the top #include <c.h>and compile your program with, e.g. gcc -Wall xxx.c -I/home/tom/include/ -L/home/tom/lib/ -ltom -lm When you have finished all the exercises in this Chapter, and have done as described above, your header file "c.h" should look somewhat like #include <stdio.h> #include <assert.h> #include <ctype.h> #include <float.h> #include <limits.h> #include <math.h> #include <stdlib.h> #include <string.h> #include <time.h> extern int st_separate(char *, char **); extern char *st_decimal(char *,int); extern char *st_hex(char *,int); extern char *st_octal(char *,int); extern char *st_invert(char *); extern int st_instr(char *,char *); extern int st_cmp(char *,char *); extern int st_cinstr(char *, int); extern char *st_chop(char *); extern char *st_rtrim(char *); extern char *st_ltrim(char *); extern char *st_copy(char *,char *); extern char *st_string(char *,int,int); extern char *st_space(char *,int); extern char *st_mid(char *,char *,int,int); extern char *st_ucase(char *); extern char *st_lcase(char *); extern char *st_right(char *,char *,int); extern char *st_left(char *,char *,int); extern char * st_add(char *, char *, char *); extern int st_len(char *); extern int st_puts(char *); extern char * st_gets(char *); |
Exercise : Please read the descriptions of the following string functions in <string.h>, p.249-250 of "The C Programming Language, B.W. Kernighan, D.M. Ritchie" (The book will be referred as K&R )
char *strcpy(char *s, char *ct)
String copy. It copies string "ct" to "s".
It returns a string pointer which contains the address of "s"
char *strncpy(char *s, char *ct, int n)
String copy at most n characters from "ct" to "s".
Returns address of "s".
char *strcat(char *s, char *ct)
Concatenate "ct" to the end of "s".
Returns address of "s".
char *strncat(char *s, char *ct, int n)
Concatenate at most n characters of "ct" to end of "s".
Returns address of "s".
int strcmp(char *cs, char *ct)
String comparison.
Returns <0 if cs < ct,
0 if cs == ct,
>0 if cs > ct.
int strncmp(char *cs, char *ct, int n)
String comparison of at most n characters.
Returns same as that in "strcmp()"
char *strchr(char *cs, int c)
String character.
Searches for character "c" in string "cs".
Returns position where match is found, or returns 0 (=NULL) if
"c" is not in "cs" (NOTE: NULL is a constant defined in header
files, usually, it has value 0.)
char *strrchr(char *cs, int c)
Reverse String Character.
Searches for character "c" in string "cs" starting from the end.
Returns position if found, or NULL if not found.
char *strstr(char *cs, char *ct)
String in String.
Searches if string "ct" is a substring of "cs".
Returns position if found, or NULL if not found.
size_t strlen(char *cs)
String Length.
"size_t" is a data type defined in header files. Usually it is
integer or unsigned long.
char *strtok(char *s, char *ct)
String token.
Chop up string "s" into tokens, "separating characters" are those
in string "ct". Please refer to K&R book for details.
|
It is extremely instructive to write your own version of the above library subroutines <string.h> , and you will learn a lot in doing so. Below, we attempt to write some of them.
Do not aim at a program with the minimum number of lines, aim at a working program first. Nowadays, memory is not important, but programmer's time is important.
For large programs, you should incorporate debugging features, the "test-points", into the program.
Lastly, write understandable codes.
Exercise : Write your own version of "strcpy(...)" (string copy) function. Give it a name "char *st_copy(char *d, char *s)"
Ans : You should think out your own logic (draw up flowchart if necessary, and dry run the codes. To understand the logic of a program written by another person is a tedious task.) Below is a suggestion only.
#include <stdio.h>
char *st_copy(char *d, char *s)
{ int c;
char *ptrd, *ptrs;
ptrd = d;
ptrs = s;
L20 :
c = *ptrs;
*ptrd = c;
if (c=='\0') {return d;}
ptrs++;
ptrd++;
goto L20;
}
int main()
{ char a[]="testing testing one two three";
char b[100];
st_copy(b,a);
puts(b);
st_copy(b, "Hong Kong");
puts(b);
return 0;
}
After you have debugged the subroutine, comment out the main
/* int main() ... return 0; } */compile the rest into an object file (How ?) and put it in library, say, "/home/tom/lib/libtom.a" (How ?), also add a statement in header file, say, "/home/tom/include/c.h",
extern char *st_copy(char *, char *);
Exercise Write a subroutine (give it a name "int st_len(char *s)") that returns the length of string, e.g. for "abc", it should return 3.
Ans :
#include <stdio.h> /*Better still, use #include <c.h> */
int st_len(char *a)
{ int ncount, c;
char *ptr;
ptr = a;
ncount=0;
L20:
c = *ptr; /* Note : we may use c = *a; ... ; a++; But form
good programming habit of not changing the original pointer */
if (c == '\0') { return ncount; }
ncount++;
ptr++;
goto L20;
}
int main()
{ char a[100];
int m;
m = st_len("abc");
printf("Length of string abc is %d\n", m);
L20:
printf("Enter a string \n");
gets(a);
printf("\n%s has length %d\n", a, st_len(a));
goto L20;
}
Exercise Write a program "st_left(char *d, char *s, int m)" that extracts n characters from the left portion of "s" and put it in "d",
e.g. st_left(d, "abcdefghij", 4)
will put
"abcd" into string "d".
Ans :
#include <stdio.h>
#include <assert.h> /* You should put all those "include" statements
in one file, say, /home/tom/include/c.h,
then you have only to put one statement
#include <c.h>
While compiling, remember to use
gcc -Wall st_left_t.c -I/home/tom/include/ \
-L/home/tom/lib/ -ltom
*/
char *st_left(char *d, char *s, int m)
{
/* This subroutine extracts the leftmost m characters of s and put
it into d
*/
char *ptrd, *ptrs;
int i,c;
ptrd = d;
ptrs = s;
assert(m>0);
for (i=0;i<m;i++)
{c = *ptrs;
*ptrd = c;
if (c=='\0') {return d;}
ptrd++;
ptrs++;
}
*ptrd='\0';
return d;
}
int main()
{ char s[100];
char d[100];
char dummy[100];
int m;
L20:
printf("Enter a string \n");
gets(s);
printf("How many characters on the left to extract ?\n");
gets(dummy);
sscanf(dummy,"%d", &m); /* Notice that we first read in a string, including
newline character. Then used sscanf(..) (not
scanf(..)) which scans a string. It is because
scanf(..) will ignore newline character, but
gets(..) will regard a newline character
as empty string! causing errors.
Format of sscanf(..) is
int sscanf(char *s, char *format, ...)
*/
puts(st_left(d,s,m));
goto L20;
}
Exercise Write a program "st_right(char *d, char *s, int m)" that extracts from the m-th characters onwards up to the end of "s" and put it in "d", e.g. st_right(d, "abcdefghij", 4) will put "defghij" into string "d".
Ans :
#include <stdio.h> /*Better still, use #include <c.h> */
char *st_right(char *d, char *s, int m)
{
/* This subroutine gets the right-hand-portion of a , starting
from character m, and put it into d
*/
char *ptrs, *ptrd;
int c;
ptrd = d;
if (m<=0 || m > strlen(s)) /* Note : you may use st_len(..) if
you have put st_len.o in a library */
{*ptrd = '\0';
return d;
}
ptrs = s + m - 1;
L20:
c = *ptrs;
*ptrd = c;
if (c=='\0') {return d;}
ptrd++;
ptrs++;
goto L20;
}
int main()
{ char a[]="abcdefghijklmnopqrstuvwxyz";
char d[100];
int m;
L20:
printf("Enter from where ");
scanf("%d",&m);
puts(st_right(d,a,m)); /* Note : you may use st_puts(..), if you
have put st_puts.o in a library
*/
goto L20;
}
Exercise Write a subroutine that changes capital letter to lower case.
char *st_lcase(char *a)
Ans :
#include <stdio.h> /*Better still, use #include <c.h> */
char *st_lcase(char *a)
{ char *ptra;
int c, idummy;
ptra = a;
idummy = - 'A' + 'a';
L20:
c=*ptra;
if (c == '\0') {return a;}
if (c>='A' && c<='Z')
{*ptra = c + idummy;
}
ptra++;
goto L20;
}
int main()
{ char a[100];
L20:
gets(a);
puts(a);
st_lcase(a);
printf("%s\n",a);
goto L20;
}
Exercise Write a subroutine that changes small letter to capital letter.
char *st_ucase(char *a)
Ans :
#include <stdio.h> /*Better still, use #include <c.h> */
char *st_ucase(char *a)
{ char *ptra;
int c, idummy;
ptra = a;
idummy = 'A' - 'a';
L20:
c=*ptra;
if (c == '\0') { return a; }
if (c>='a' && c<='z')
{*ptra = c + idummy;
}
ptra++;
goto L20;
}
int main()
{ char a[100];
L20:
gets(a);
printf("Before\n%s\n",a);
st_ucase(a);
printf("After\n%s\n", a);
goto L20;
}
Exercise Write a subroutine that concatenate string "b" to the end of string "a", and put the resulting string in "c". It returns "c".
char *st_add(char *c, char *a, char *b)
Ans :
#include <stdio.h> /*Better still, use #include <c.h> */
char *st_add(char *c, char *a, char *b)
{ char *ptrc, *ptra, *ptrb;
int d;
/* This subroutine adds string b after string a and put the
resulting string to c
*/
ptra = a;
ptrb = b;
ptrc = c;
while ((d=*ptra) != '\0')
{*ptrc=d;
ptra++;
ptrc++;
}
while ((d=*ptrb) != '\0')
{*ptrc=d;
ptrb++;
ptrc++;
}
*ptrc='\0';
return c;
}
int main()
{ char a[100], b[100], c[200];
L20:
gets(a);
gets(b);
st_add(c,a,b);
puts(c);
goto L20;
}
Exercise "c" is a character. "char *s" is a string. We wish to see if character "c" is in the string. If so, return the position, starting from 1 (not 0), if NOT, return 0. Note that it differs from the library function "char *strchr(..)" which returns a pointer.
int st_cinstr(char *s, int c)
Ans :
#include <c.h>
int st_cinstr(char *s, int c)
{ int d, iwhere;
char *ptrs;
ptrs = s;
iwhere=1;
L20:
d=*ptrs;
if (d=='\0') {return 0;}
if (d==c) {return iwhere;}
ptrs++;
iwhere++;
goto L20;
}
int main()
{ char a[]="abcdefghijk";
char b[]="dlzak"
int i, c, iwhere;
for (i=0;i<5;i++)
{c = b[i];
iwhere = st_cinstr(a,c);
if (iwhere != 0)
{printf("%c is at position %d\n",c, iwhere);
}
else
{printf("%c is not in the string\n",c);
}
}
return 0;
}
Exercise Write a subroutine that returns a string of n blanks.
char *st_space(char *d, int n)
Ans :
#include <c.h>
char *st_space(char *d, int n)
{ int i;
char *dd;
dd=d;
/* This subroutine returns a string consisting of n spaces, i.e. n blanks. */
assert(n>=0);
for (i=0;i<n;i++)
{*dd=' ';
dd++;
}
*dd='\0';
return d;
}
int main()
{ char d[100], *ptr;
int m;
L20:
printf("enter no. of spaces ");
scanf("%d",&m);
ptr=st_space(d,m);
printf("length of string %d\n",st_len(ptr));
/* st_len(..) functions like strlen(..) */
printf("%s*\n",d);
goto L20;
}
Exercise Write a subroutine that generates m given characters of ASCII code nc,
char *st_string(char *d, int nc, int m)e.g. char *st_string(char *d, 'a', 10);
Ans :
#include <c.h>
char *st_string(char *d, int nc, int m)
{ char *ptr;
int i;
ptr=d;
/* This subroutine makes a string of m characters whose ASCII is nc */
for (i=0;i<m;i++)
{*ptr=nc;
ptr++;
}
*ptr='\0';
return d;
}
int main()
{ char d[100], *ptr;
int nc='a', n;
L20:
printf("Input count ");
scanf("%d",&n);
ptr=st_string(d,nc,n);
st_puts(ptr); /* You may use puts(..) if you have not put st_puts(..)
in library */
printf("another way to print\n%s\n",d);
/* We may use puts(...) or printf("%s\n",d) to print
a string */
goto L20;
}
Exercise Write a subroutine that strips away the blanks on the left hand side of a string,
char *st_ltrim(char *d)
Ans :
#include <c.h>
/* This subroutine trims the left portion of spaces */
char *st_ltrim(char *s)
{ char *ptrs;
ptrs = s;
while (*ptrs ==' ')
{ ptrs++;
}
return ptrs;
}
int main()
{ char s[]=" my name is wu";
char *ptr;
ptr=st_ltrim(s);
st_puts(ptr);
return 0;
}
/* NOTE : if we have used
int main()
{ char s[]=" my name is wu";
st_ltrim(s);
st_puts(s);
return 0;
}
This will not work. Why ?
*/
Exercise Write a subroutine that strips away the blanks on the right hand side of a string,
char *st_rtrim(char *d)
Ans :
#include <c.h>
char *st_rtrim(char *s)
{ char *ptr;
int i;
i=st_len(s); /* Note : I have used st_len(..), but you may use strlen(..) */
ptr=s+i-1;
while (*ptr == ' ')
{ptr--;
}
++ptr;
*ptr='\0';
return s;
}
int main()
{ char s[]=" wu siu yan ";
char *ptr;
ptr=st_rtrim(s);
printf("%s***\n",ptr);
printf("another check\n%s***\n",s);
return 0;
}
Exercise Write a subroutine that strips away the blanks on both the right hand side and the right hand side of a string,
char *st_chop(char *d)
Ans :
#include <c.h>
char *st_chop(char *s)
{ char *ptr;
st_rtrim(s);
ptr=st_ltrim(s);
return ptr;
}
int main()
{ char s[]=" abcd ";
printf("%s***\n",st_chop(s));
return 0;
}
Exercise Write a subroutine that extracts the middle part of a string,
char *st_mid(char *d, char *a, int ifrom, int mchar)This subroutine will take m-characters from string "a", starting from position "ifrom" (counts from 1 and not from 0), and put the resulting string in "d".
Ans :
#include <c.h>
char *st_mid(char *d, char *a, int ifrom, int mchar)
{ char *ptrd, *ptra;
int i, c, mlen;
ptrd = d;
ptra = a;
mlen=st_len(a);
ptra += (ifrom-1); /* i.e. ptra = ptra + (ifrom -1 ) */
assert(mchar>0);
assert(ifrom<=mlen);
for (i=0;i<mchar;i++)
{c = *ptra;
*ptrd = c;
if (c=='\0') {return d;}
ptrd++;
ptra++;
}
*ptrd='\0';
return d;
}
int main()
{ char a[]="abcdefghijklmnopqrstuvwxyz";
char d[100];
int ifrom, mcount;
L20:
printf("Enter starting position, and no of characters\n");
scanf("%d%d",&ifrom, &mcount);
st_puts(st_mid(d,a,ifrom,mcount));
goto L20;
}
Exercise Write a subroutine that checks whether string "b" is part of string "a", and returns position (starting from 1, not 0) if found, and returns 0 if not found. Notice this is different from the library function "char *strstr(cs,ct)" which returns a pointer.
int st_instr(char *a, char *b)
Ans :
#include <c.h>
int st_instr(char *a, char *b)
{ int c, ka, kb, ib, iwhere;
char *ptra, *ptrb;
/* This subroutines checks if string b is in a */
ib=*b;
iwhere=1;
L20:
c=*a;
if (c=='\0') {return 0;}
if (c != ib)
{a++;
iwhere++;
goto L20;
}
ptra=a+1;
ptrb=b+1;
L40:
ka = *ptra;
kb = *ptrb;
if (kb == '\0') {return iwhere;}
if (ka == '\0') {return 0;}
if (ka != kb)
{a++;
iwhere++;
goto L20;
}
ptra++;
ptrb++;
goto L40;
}
int main()
{ char a[100], b[100];
int i;
L20:
gets(a);
gets(b);
printf("\n%s\n%s\n\n",a,b);
i=st_instr(a,b);
if (i==0)
{printf("Not part of string a \n");}
else
{printf("found in %d\n",i);}
printf("\n");
goto L20;
}
Exercise Write a subroutine that compares 2 strings. If string "a" is greatter than string "b" (according to their ASCII values), it returns 1. If they are equal, it returns 0. If "a" is less than "b", it returns -1. When the 2 strings are equal, the longer string is considered greatter.
int st_cmp(char *a, char *b)
Ans :
#include <c.h>
int st_cmp(char *a, char *b)
{ int c,d;
L20:
c=*a;
d=*b;
if (c=='\0')
{if (d=='\0')
{goto Lequal;}
else
{goto Lless;}
}
else
{if (d=='\0')
{goto Lgreat;}
else if (c==d)
{a++;
b++;
goto L20;
}
else if (c>d)
{goto Lgreat;}
else
{goto Lless;}
}
Lequal: return 0;
Lgreat: return 1;
Lless : return -1;
}
/* Note : the subroutine may be written as,
int st_cmp(char *a, char *b)
{ int c,d;
for (;;a++,b++)
{ c=*a;
d=*b;
if (c=='\0' && d=='\0') {return 0;}
if (c=='\0' && d!='\0') {return -1;}
if (c!='\0' && d=='\0') {return 1;}
if (c==d) {continue;}
if (c>d)
{return 1;}
else
{return -1;}
}
}
And the logic is the same
*/
int main()
{ char a[100],b[100];
int i;
L20:
st_gets(a);
st_gets(b);
st_puts(a);
st_puts(b);
i = st_cmp(a,b);
if (i==0)
{printf("they are equal\n");}
else if (i<0)
{printf("a is less than b\n");}
else
{printf("a is greater than b\n");}
goto L20;
}
double atof(char *s) |
The first one, although it reads "ascii to float", in fact, converts a string to a double.
e.g.
The second one, "ascii to integer", converts a string to integer, e.g.
The third one, "ascii to long", converts a string to long, e.g.
|
Exercise Given a string, e.g.
"123,435, wu siu yan, 20023"where several items are separated by comma. Write a subroutine
int st_separate(char *a, char **ptr);that returns number of substrings in string "a". Note that we have to pass to the subroutine "char **ptr", which may be seen as "char *ptr[]", an array of character pointers (Remember that array, pointer may be treated the same. Hence "char ***ptr" may be seen as "char *ptr[][]", a two dimensional array of character pointers.) The starting addresses of the component strings will be stored in the character pointers array ptr e.g.
123,435, wu siu yan, 20023
12304350 wu siu yan0 20023
^ ^ ^ ^
Ans :
#include <c.h>
int st_separate(char *a, char **ptr)
{ int c,i;
ptr[0]=a;
i=0;
L20:
c=*a;
if (c=='\0') {return i+1;}
if (c==',')
{*a='\0';
i++;
ptr[i]=a+1;
}
a++;
goto L20;
}
int main()
{ char a[]="123,435, wu siu yan, 20023";
char *ptr[20]; /* We have reserved an array of character pointers of size 20,
that should be sufficient for most purposes.
Unfortunately, should there be more than 20, the program
will fail, and we may not know why. Hence it takes extra
caution when writing C programs. */
int n,i;
n=st_separate(a,ptr);
for (i=0; i<=n; i++)
{puts(ptr[i]);
}
return 0;
}
C is a minimal language. This can be seen that C has only 42 key words (The subroutines in standard libraries are NOT key words, though we must not use them as variable names or function names). Hence it is very inconvenient to use at first.
One must form the habit of writing many utility subroutines, and store them in a library, as may be seen in the exercises above.
After some period of time, may be a year or two, when one's library has grown, then C will become easier to use, and in fact, easier and easier over the years.
| [Previous] | [Home] | [Next] |