Input to a C program may occur from the console, the standard input device (unless otherwise redirected this is the console), from a file or from a data port.
The general input command for reading data from the standard input stream 'stdin ' is scanf(). Scanf() scans a series of input fields, one character at a time. Each field is then formatted according to the appropriate format specifier passed to the scanf() function as a parameter. This field is then stored at the ADDRESS passed to scanf() following the format specifiers list.
For example, the following program will read a single integer
from the stream stdin
;
main()
{
int x;
scanf("%d",&x);
}
Notice the address operator & prefixing the variable name 'x' in the scanf() parameter list. This is because scanf() stores values at ADDRESSES rather than assigning values to variables directly.
The format string is a character string that may contain three types of data:
whitespace characters (space, tab and newline), non-whitespace characters (all ascii characters EXCEPT %) and format specifiers.
Format specifiers have the general form;
%[*][width][h|l|L]type_character
After the % sign the format specifier is comprised of:
an optional assignment suppression character, *, which suppresses
assignment of the next input field.
an optional width specifier, width, which declares the maximum number
of characters to be read.
an optional argument type modifier, h or l or L, where:
h is a short integer
l is a long
L is a long double
the data type character that is one of;
d | Decimal integer |
D | Decimal long integer |
o | Octal integer |
O | Octal long integer |
i | Decimal, octal or hexadecimal integer |
I | Decimal, octal or hexadecimal long integer |
u | Decimal unsigned integer |
U | Decimal unsigned long integer |
x | Hexadecimal integer |
X | Hexadecimal long integer |
e | Floating point |
f | Floating point |
g | Floating point |
s | Character string |
c | Character |
% | % is stored |
An example using scanf();
#include <stdio.h>
main()
{
char name[30];
int age;
printf ("\nEnter your name and age ");
scanf("%30s%d",name,&age);
printf ("\n%s %d",name,age);
}
Notice the include line, "#include <stdio.h>", this is to
tell the compiler to also read the file stdio.h that contains the function
prototypes for scanf() and printf
().
If you type in and run this sample program you will see that only one name
can be entered, that is you can't enter;
JOHN SMITH
because scanf() detects the whitespace between "JOHN" and "SMITH" and moves on to the next input field, which is age, and attempts to assign the value "SMITH" to the age field! The limitations of scanf() as an input function are obvious.
An alternative input function is gets() that reads a string of characters
from the stream stdin
until a newline character is detected. The newline character is replaced by a
null (0 byte) in the target string. This function has the advantage of allowing
whitespace to be read in. The following program is a modification to the earlier
one, using gets() instead of scanf().
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
main()
{
char data[80];
char *p;
char name[30];
int age;
printf ("\nEnter your name and age ");
/* Read in a string of data */
gets(data);
/* P is a pointer to the last character in the input string */
p = &data[strlen(data) - 1];
/* Remove any trailing spaces by replacing them with null bytes */
while(*p == ' '){
*p = 0;
p--;
}
/* Locate last space in the string */
p = strrchr(data,' ');
/* Read age from string and convert to an integer */
age = atoi(p);
/* Terminate data string at start of age field */
*p = 0;
/* Copy data string to name variable */
strcpy(name,data);
/* Display results */
printf ("\nName is %s age is %d",name,age);
}
The most common output function is printf
() that is very similar to scanf() except that it writes formatted data out to
the standard output stream stdout
. Printf() takes a list of output data fields and applies format specifiers to
each and outputs the result. The format specifiers are the same as for scanf()
except that flags may be added to the format specifiers. These flags include;
- Which left justifies the output padding to the right with spaces.
+ Which causes numbers to be prefixed by their sign
The width specifier is also slightly different for printf
(). In its most useful form is the precision specifier;
width.precision
So, to print a floating point number to three decimal places
you would use;
printf
("%.3f",x);
And to pad a string with spaces to the right or truncate the
string to twenty characters if it is longer, to form a fixed twenty character
width;
printf
("%-20.20s",data);
Special character constants may appear in the printf
() parameter list. These are;
\n | Newline |
\r | Carriage return |
\t | Tab |
\b | Sound the computer's bell |
\f | Formfeed |
\v | Vertical tab |
\\ | Backslash character |
\' | Single quote |
\" | Double quote |
\? | Question mark |
\O | Octal string |
\x | Hexadecimal string |
Thus, to display "Hello World", surrounded in quotation marks and
followed by a newline you would use;
printf
("\"Hello World\"\n");
The following program shows how a decimal integer may be displayed as a
decimal, hexadecimal or octal integer. The 04 following the % in the printf
() format tells the compiler to pad the displayed figure to a width of at least
four digits padded with leading zeroes if required.
/* A simple decimal to hexadecimal and octal conversion program */
#include <stdio.h>
main()
{
int x;
do
{
printf ("\nEnter a number, or 0 to end ");
scanf("%d",&x);
printf ("%04d %04X %04o",x,x,x);
}
while(x != 0);
}
There are associated functions to printf () that you should be aware of. fprintf() has the prototype;
fprintf(FILE
*fp,char *format[,argument,...]);
This variation on printf
() simply sends the formatted output to the specified file stream.
sprintf() has the function prototype;
sprintf(char *s,char *format[,argument,...]);
and writes the formatted output to a string. You should take care using
sprintf() that the target string has been declared long enough to hold the
result of the sprintf() output. Otherwise other data will be overwritten in
memory.
An example of using sprintf() to copy multiple data to a string;
#include<stdio.h>
int main()
{
char buffer[50];
sprintf(buffer,"7 * 5 == %d\n",7 * 5);
puts(buffer);
}
An alternative to printf () for outputting a simple string to the stream stdout is puts(). This function sends a string to the stream stdout followed by a newline character. It is faster than printf(), but far less flexible. Instead of;
printf
("Hello World\n");
You can use;
puts("Hello World");
Data may be sent to, and read directly from the console
(keyboard and screen) using the direct console I/O functions. These functions
are prefixed 'c'. The direct console I/O equivalent of printf
() is then cprintf(), and the equivalent of puts() is cputs(). Direct console
I/O functions differ from standard I?o functions:
1. They do not make use of the predefined streams, and
hence may not be redirected
2. They are not portable across operating systems (for
example you cant use direct console I/O functions in a Windows
programme).
| 3. They are faster than their standard I/O equivalents
| 4. They may not work with all video modes (especially VESA
display modes). | |
A pointer is a variable that holds the memory address of an
item of data. Therefore it points to another item. A pointer is declared like an
ordinary variable, but its name is prefixed by '*', thus;
char *p;
This declares the variable 'p' to be a pointer to a character variable. Pointers are very powerful, and similarly dangerous! If only because a pointer can be inadvertently set to point to the code segment of a program and then some value assigned to the address of the pointer!
The following program illustrates a simple, though fairly
useless application of a pointer;
#include <stdio.h>
main()
{
int a;
int *x;
/* x is a pointer to an integer data type */
a = 100;
x = &a;
printf ("\nVariable 'a' holds the value %d at memory address %p",a,x);
}
Here is a more useful example of a pointer illustrating how because the compiler knows the type of data pointed to by the pointer, when the pointer is incremented it is incremented the correct number of bytes for the data type. In this case two bytes;
#include <stdio.h>
main()
{
int n;
int a[25];
int *x;
/* x is a pointer to an integer data type */
/* Assign x to point to array element zero */
x = a;
/* Assign values to the array */
for(n = 0; n < 25; n++)
a[n] = n;
/* Now print out all array element values */
for(n = 0; n < 25; n++ , x++)
printf ("\nElement %d holds %d",n,*x);
}
To read or assign a value to the address held by a pointer you use the indirection operator '*'. Thus in the above example, to print the value at the memory address pointed to by variable x I have used '*x'.
Pointers may be incremented and decremented and have other mathematics applied to them also. For example in the above program to move variable x along the array one element at a time we put the statement 'x++' in the for loop. We could move x along two elements by saying 'x += 2'. Notice that this doesn't mean "add 2 bytes to the value of x", but rather it means "add 2 of the pointer's data type size units to the value of x".
Pointers
are used extensively in dynamic memory allocation. When a program is running it
is often necessary to temporarily allocate a block of data, say a table, in
memory. C provides the function malloc
() for this purpose that follows the general form;
any pointer type = malloc
(number_of_bytes);
malloc () actually returns a void pointer type, which means it can be any type; integer, character, floating point or whatever. This example allocates a table in memory for 1000 integers;
#include <stdio.h>
#include <stdlib.h>
main()
{
int *x;
int n;
/* x is a pointer to an integer data type */
/* Create a 1000 element table, sizeof() returns the compiler */
/* specific number of bytes used to store an integer */
x = malloc
(1000 * sizeof(int));
/* Check to see if the memory allocation succeeded */
if (x == NULL)
{
printf ("\nUnable to allocate a 1000 element integer table");
exit(0);
}
/* Assign values to each table element */
for(n = 0; n < 1000; n++)
{
*x = n;
x++;
}
/* Return x to the start of the table */
x -= 1000;
/* Display the values in the table */
for(n = 0; n < 1000; n++){
printf ("\nElement %d holds a value of %d",n,*x);
x++;
}
/* Deallocate the block of memory now it's no longer required */
free(x);
}
Pointers are also extensively used with character arrays, called strings. Since all C program strings are terminated by a zero byte we can count the letters in a string using a pointer;
#include <stdio.h>
#include <string.h>
main()
{
char *p;
char text[100];
int len;
/* Initialise variable 'text' with some writing */
strcpy(text,"This is a string of data");
/* Set variable p to the start of variable text */
p = text;
/* Initialise variable len to zero */
len = 0;
/* Count the characters in variable text */
while(*p)
{
len++;
p++;
}
/* Display the result */
printf ("\nThe string of data has %d characters in it",len);
}
The 8088/8086 group of CPUs, as used in the IBM PC, divide memory into 64K segments. To address all 1Mb of memory a 20 bit number is used comprised of an 'offset' to and a 64K 'segment'. The IBM PC uses special registers called "segment registers" to record the segments of addresses.
This leads the C language on the IBM PC to have three new keywords; near, far and huge.
near pointers are 16 bits wide and access only data within the current segment.
far pointers are comprised of an offset and a segment address allowing them to access data any where in memory, but arithmetic with far pointers is fraught with danger! When a value is added or subtracted from a far pointer it is only actualy the segment part of the pointer that is affected, thus leading to the situation where a far pointer being incremented wraps around on its own offset 64K segment.
huge pointers are a variation on the far pointer that can be successfuly incremented and decremented through the entire 1Mb range since the compiler generates code to amend the offset as applicable.
It will come as no surprise that code using near pointers is executed faster than code using far pointers, which in turn is faster than code using huge pointers.
to give a literal address to a far pointer IBM PC C compilers
provide a macro MK-FP() that has the prototype;
void far *MK_FP(unsigned segment, unsigned offset);
For example, to create a far pointer to the start of video
memory on a colour IBM PC system you could use;
screen = MK_FP(0xB800,0);
Two coressponding macros provided are;
FP_SEG() and FP_OFF()
Which return the segment and offset respectively of a far
pointer. The following example uses FP_SEG() and FP_OFF() to send segment and
offset addresses to a DOS call to create a new directory path;
#include <dos.h>
int makedir(char *path)
{
/* Make sub directory, returning non zero on success */
union REGS inreg,outreg;
struct SREGS segreg;
inreg.h.ah = 0x39;
segreg.ds = FP_SEG(path);
inreg.x.dx = FP_OFF(path);
intdosx(&inreg,&outreg,&segreg);
return(1 - outreg.x.cflag);
}
Finally, the CPU's segment registers can be read with the
function 'segread()'. This is a function unique to C compilers for the the 8086
family of processors. segread() has the function prototype:
void segread(struct SREGS *segp);
It returns the current values of the segment registers in the SREGS type structure pointed to by the pointer 'segp'. For example:
#include <dos.h>
main()
{
struct SREGS sregs;
segread(&sregs);
printf ("\nCode segment is currently at %04X, Data segment is at %04X",sregs.cs,sregs.ds);
printf ("\nExtra segment is at %04X, Stack segment is at %04X",sregs.es,sregs.ss);
}
Accessing different structures, bit fields, unions, and enumerations.
Links: Home C Programming Guide C++ programming Guide