A Crash Course in C Notes: Chapter 1: Fundamentals
A Crash Course in C Notes: Chapter 1: Fundamentals
Notes
Table of Contents
Introduction
Chapter 1: Fundamentals
o Example programs
o Variables
o Input / Output
o Keywords and Operators: The C Language
o Expressions and Statements
o Control Flow
o The C Preprocessor
o Problems
Chapter 2: Functions
o Reasons for Using Functions
o Basic Structure
o Return Statement
o Difference between ANSI-C and "Traditional C"
o Object Storage Classes and Scope
o Larger Programs
o Macros
o Problems
Chapter 3: Pointers
o Pointer Definition and Use
o Pointers as Function Arguments: "Call by Value"
o Arrays
o Functions Returning Pointers
o Multidimensional Arrays
o Strings
o Command Line Arguments
o Pointers to Functions
o Problems
Chapter 4: Structures
o Syntax and Operations
o typedef
o Array of Structures
o Use with Functions
o Linked Lists
o union
o enum
o Example: Complex Numbers
o Problems
Chapter 5: C Libraries
o Memory Allocation
o Math Libraries
o Random Variables
o Input / Output
o Strings
Appendix A: Make Program
Appendix B: Style Guide
o General Style
o Layout
o Coding Practice
o Naming Conventions
Appendix C: Answers to Problems
Introduction
Reasons to use C:
References:
Brian Kernighan and Dennis Ritchie, The C Programming Language (2nd edition) (K\&R)
Stephen G. Kochan, Programming in C, Programming in ANSI C (Kochan)
Al Kelley and Ira Pohl, A Book on C
American National Standard for Information Systems---Programming Language C, X3.159-1989.
% name
% name input.file
% name output.file
% name input.file output.file
Different systems have different procedures to use C. See the manual or help files for details.
Chapter 1: Fundamentals
Example programs
The following are simple examples to get things started. They do not do anything useful, but they illustrate some key characteristics of
C.
/* this is a comment */
main() /* function definition */
{ /* start of block */
printf("Hello world\n"); /* output; statement ends with a semicolon */
/* use '\n' in printf to get a linefeed */
} /* end of block */
main()
{
variable declarations
statements
}
main()
{
int x; /* declaration and definition */
x=5; /* assignment */
printf("%d\n", x); /* output x as an integer */
}
main()
{
int x=17, y=12, z; /* declaration, definition, and initialization */
z = x + y; /* arithmetic expression */
printf("z = %d + %d = %d\n", x, y, z); /* each %d prints one integer */
printf("%d * %d = %d\n", x, y, x*y);
}
main()
{
int x;
scanf("%d", &x); /* values input from the keyboard using scanf(): */
printf("x = %d\n", x); /* and a pointer to x */
}
A common error when using scanf() is not giving pointers as the arguments; to input a variable, use
int x;
scanf("%d", &x); /* correct */
NOT
int x;
scanf("%d", x); /* incorrect */
Variables
All variables must be declared and defined before they can be used. Variable names can be composed of characters, digits, and
the underscore character ( _), and can usually be up to 32 characters long. Variable names should not begin with an
underscore---these names are used by the compiler and the libraries.
can specify signed or unsigned, although signed is preferred for compatibility with int
int: integers
arrays (array[10])
pointers (*pointer)
functions (function())
structures (structure.member)
enumerated (enumerated_variable)
union (union_name.member)
All variables must be declared and defined; they can also be initialized and assigned a value.
int n = 10;
int n_oct = 065; /* octal */
int n_hex = 0x3d; /* hexadecimal */
long m = 10L;
unsigned int k = 304U;
unsigned long l = 3040UL;
float x1 = 143.0;
float x2 = 24.6e-3;
double y = 34.1L;
Input / Output
Basic input/output is obtained using the standard library. The syntax is given below.
The control string has place holders for the variables being used (see tables on page 16 - click here) there can also be characters in the
control string. For scanf(), the pointers to the variables being inputed must be used. Special characters are listed on table 5 on page 16
(click here).
printf("(3) x = ");
printf("%d", x);
printf("\n"); /** same as (4) **/
C has a small set of keywords; all are used to either declare data types or control the flow of the program. See table 1 below for a list
of the keywords. The C operators are listed in Table 2 at the end of the chapter.
Basic Operators
arithmetic operators
o *, / , %, +, -
o % is called modulus division (remainder)
logical operators
o <,>, <=, >=, ==, !=, &&, ||
o the logical operators return a 1 (true) or 0 (false)
o for any conditional test, any non-zero value is true and a 0 is false
o == is the equality comparision ( not =)
o != not equal to
o &&, || logical AND and OR operators
o A common error is using the assignment operator = when the logical operator == is required, e.g. (x = 2) instead of (x
== 2); both are valid expressions, so the compiler will not indicate an error.
assignment operators
o =, +=, -=, *=, /=, %= !
o op= assignment, E1 op=E2 <==> E1=E1 op (E2), with E1 evaluated once
o for example, x+=2 ==>x=x+2
o increment/decrement by 1, as a prefix or postfix
o ++, --
o prefix: increments the variable and gives the new value as the result
o postfix: gives the old value as the result and then increments the variable
negation
o !
o !(0) ==> 1
o !(any non-zero value) ==> 0
conditional, compact if-else as an expression instead of a statement
o ?
(type)
o casts object into a different type
, (comma)
o combines separate expressions into one
o evaluates them from left to right
o the value of the whole expression is the value of the right most sub-expression
Examples of Conditional Operators
main()
{
int x=0, y=10, w=20, z, T=1, F=0;
x=5;
y = ++x; /** prefix increment **/
printf("++x: x=%d, y=%d\n", x, y);
x=5;
y = x++; /** postfix increment **/
printf("x++: x=%d, y=%d\n", x, y);
x=5;
y = --x; /** prefix decrement **/
printf("--x: x=%d, y=%d\n", x, y);
x=5;
y = x--; /** postfix decrement **/
printf("x--: x=%d, y=%d\n", x, y);
}
Output:
++x: x=6, y=6
x++: x=6, y=5
--x: x=4, y=4
x--: x=4, y=5
More Operator Examples
main()
{
int x, y, z;
x = 128;
y = x / 10; /** y = 12, the remainder is dropped **/
y = x % 10; /** y = 8, which is remainder **/
x = 10;
y = !x; /** y = 0 **/
y = !(!x); /** y = 1 **/
x = 0;
y = !x; /** y = 1 **/
x = 10;
x += 2; /** x = 12 **/
x += 2; /** x = 14 **/
x = 10;
x -= 4; /** x = 6 **/
x = 10;
x *= 5; /** x = 50 **/
x = 10;
x /= 2; /** x = 5 **/
x /= 2; /** x = 2 **/
x = 2
y = (x < 5) ? 5 : 10; /** y=5 **/
x = 8
y = (x < 5) ? 5 : 10; /** y=10 **/
Operands of most operators will be evaluated in an unspecified order; do not assume a particular order and be careful about side effects
of evaluations. Also, the order in which function arguments are evaluated is not specified. There are four operators, however, that do
have a specified order of operand evaluation: && || , ?:
/** test of order of operations **/
main()
{
int x=5, y, i=4;
y = x * ++x;
printf("x = %d, y = %d\n", x, y);
Depending on the order of evaluation of the function arguments, the output can be
x = 6, y = 30
I = 4, ++I = 5
or
x = 6, y = 36
I = 5, ++I = 5
C does some automatic type conversion if the expression types are mixed. If there are no unsigned values, the rules for conversion for
two operands are:
If there are unsigned expressions, the conversion rules are in K&R, p. 198.
Any explicit type conversions can be made by casting an expression to another type, using (double), (float), etc., before the
expression to be cast.
z1 = x/y;
z2 = (float) x / (float) y;
printf("z1 = %6.2f, z2 = %6.2f\n", z1, z2);
}
Output:
z1 = 0.00, z2 = 0.80
Expressions and Statements
Expressions include variable names, function names, array names, constants, function calls, array references, and structure references.
Applying an operator to an expression is still an expression, and an expression enclosed within parentheses is also an expression. An
lvalue is an expression which may be assigned a value, such as variables.
i++ x+y z = x+y
A statement is
Control Flow
Basic control flow is governed by the if..else, while,do...while, and for statements.
Decision Making
Use the if...else for conditional decisions. (exp is any valid expression, statement is any valid statement)
Syntax:
if (exp)
statement
if (exp)
statement
else
statement
if (exp1)
statement
else if (exp2)
statement
.
.
.
else
statement
main() /*** check if a number is odd or even ***/
{
int i;
scanf("%d", &i);
if (i%2 == 0) /** OR if (!(i%2)) **/
printf("i is even\n");
else
printf("i is odd\n");
}
Examples of the 'if' Statement
main()
{
int x=5;
if (x > 0)
printf("x = %d\n", x);
if (x < 10)
{
printf("x = %d\n", x);
x += 10;
}
else
x -= 10;
if (x==1)
printf("one\n");
else if (x==2)
printf("two\n");
else if (x==4)
{
printf("four\n");
x /= 2;
}
else if (x==5)
printf("five\n");
else
{
printf("x = %d\n", x);
x %= 10;
}
/** 'else' matches up with last unmatched 'if' in same block level **/
if (x > 0)
if (x % 2)
printf("odd\n");
else
printf("even\n");
else
printf("negative\n");
if (!(x % 2))
{
if (!(x % 5))
x /= 10;
}
else
printf("odd\n");
}
Looping
Syntax:
while (exp)
{
statement
}
do
{
statement
} while (exp);
i=1;
while (i<=10)
{
printf("%d\n", i);
i++;
}
}
i=1;
do
{
printf("%d\n", i++);
} while (i<=10);
}
Other program control flow is governed by the switch statement, which allows comparison to a series of constant values, and the
goto, continue, break, and return statements.
Syntax:
switch(exp)
{
case (const-exp):
statement-opt
break;
case (const-exp):
statement-opt
statement-opt
break;
.
.
.
default:
statement-opt
break;
}
Here const-exp is a constant expression, and statement-opt is an optional statement. The break; after the default: case is optional.
Syntax:
label: statement
goto label;
continue;
break;
return (exp-opt);
}
The break; statement is used inside the while, do...while, for, and switch statements to automatically drop out of the current
loop. The continue statement is used inside the loops (not switch) to jump to the end of the loop. With while and do...while, this
jumps to the next test; with for, it jumps to the increment step, and then the test.
/** Example with 'do...while' and 'switch': waiting for a yes/no answer **/
main()
{
char ans, c;
int answer;
do
{
printf("enter y/n: "); scanf("%c", &ans);
switch (ans)
{
case 'y': case 'Y': answer = 1; break;
case 'n': case 'N': answer = 0; break;
default: answer = -1; break;
}
} while (answer == -1);
printf("answer = %d\n", answer);
}
The C Preprocessor
Two essential preprocessor commands are #define and #include. Constants and macros are defined with #define. The program can
be split into separate files and combined with #include, and header files can also be inserted. (See sections 2.6 and 2.7)
#include <stdio.h>
#include <math.h>
#include "functions.c"
#define MAX 10
#define TRUE 1
#define FALSE 0
After inserting the math.h header file, math subroutines can be used properly. To compile using the math library on Unix, use
% cc -o .c -lm
/** program to calculate x using the quadratic formula **/
#include
main()
{
float a, b, c, d, x, x1, x2;
printf("input a, b, c: ");
scanf("%f %f %f", &a, &b, &c);
d = b*b - 4.0*a*c;
if (d >= 0) /** check if solution will be real or complex **/
{
x1 = (-b+sqrt(d)) / (2.0*a); /** sqrt() from the math library **/
x2 = (-b-sqrt(d)) / (2.0*a);
/** need parentheses for proper order of operations **/
printf("x = %f, %f\n",x1,x2);
}
else
printf("x is complex");
}
for (i=0, x=1; i< } x)); log((double) x, printf(?log(%5d)='%8.3lf\n",' *** operator comma uses
x*="2)" i++,>
Output:
log( 1) = 0.000
log( 2) = 0.693
log( 4) = 1.386
log( 8) = 2.079
log( 16) = 2.773
log( 32) = 3.466
log( 64) = 4.159
log( 128) = 4.852
log( 256) = 5.545
log( 512) = 6.238
The #if...#endif preprocessor command can be used to comment out a section of code which may have comments within it.
main()
{
int x=5;
#if FALSE /** everything until the matching #endif is commented **/
x = 304;
#endif
C = (F-32)*5/9
for 20 degree increments from 32 to 212. (See K&R,p. 9 if you are stuck.)
x, X int; unsigned hexadecimal number (without a leading Ox or OX, using abcdef or ABCDEF for 10,...,15)
s char; print characters from the string until a '\0' or the number of charachters given by the precision
f double; [-]m.dddddd, where the number of d's is given by the precision (default is 6)
e, E double; [-]m.dddddde ± xx or [-]m.ddddddE ± xx where the number of d's is given by the precision (default is 6)
double; use %e or %E if the exponent is less than -4 or greater than or equal to the precision; otherwise use %f; trailing
g, G
zeros and a trailing decimal point are not printed
p void *; pointer (implementation-dependent representation)
% no argument is converted; print a %
Chapter 2: Functions
Reasons for Using Functions
Basic Structure
where the argument declarations must include the types of the arguments, but the argument names are optional. If the function
is defined before its use, then a prototype is not necessary, since the definition also serves as a declaration.
If the return-type is omitted, int is assumed. If there are no argument declarations, use void, not empty parentheses.
void print_message(void)
{
printf("hello\n");
}
main()
{
print_message();
}
A function that takes an argument. The arguments are separated by commas; the order in which they are evaluated is
unspecified. The value of each argument is passed to the corresponding parameter of the function.
void print_integer(int i)
{
printf("i = %d\n", i);
}
main()
{
int n=5;
print_integer(n);
}
main()
{
int x, y, z;
x = input_integer();
y = input_integer();
printf("the sum is %d\n", z=x+y);
}
int input_integer(void)
{
int a;
do
{
printf("input a positive integer: ");
scanf("%d", &a);
} while (a <= 0);
return a;
}
main()
{
int x, y, z=100;
int input_integer_le_n(int n); /** prototype declaration can be **/
/** inside a function **/
x = input_integer_le_n(z);
y = input_integer_le_n(z);
printf("the sum is %d\n", x+y);
}
int input_integer_le_n(int n)
{
int a;
do
{
printf("input a positive integer less than %d: ", n);
scanf("%d", &a);
} while (a<=0 || a>n);
return a;
}
Return Statement
A function can return a value of any type, using the return statement,
Syntax:
return exp;
return (exp);
return;
The return statement can occur anywhere in the function, and will immediately end that function and return control to the
function which called it. If there is no return statement, the function will continue execution until the closing of the function
definition, and return with an undefined value.
The type of the expression returned should match the type of the function; C will automatically try to convert exp to the
return-type.
float max(float x, float y) /** argument types are in the definition **/
{
if (x < y)
return y;
else
return x;
}
float max(); /** argument types are not included in the declaration **/
main()
{
float x,y,z;
...
z = max(x,y); /** the function call is the same **/
...
}
float max(x,y)
float x,y; /** argument types listed after the definition **/
{
if (x < y)
return y;
else
return x;
}
Functions, as with any compound statement designated by braces {}, have their own scope, and can therefore define any
variables without affecting the values of variables with the same name in other functions. To be able to affect the variables,
they can be made "global" by defining them externally
automatic: declared when entering the block, lost upon leaving the block; the declarations must be the first thing after
the opening brace of the block
static: the variable is kept through the execution of the program, but it can only be accessed by that block
extern: available to all functions in the file AFTER the declaration; use extern to make the variable accessible in other
files
register: automatic variable which is kept in fast memory; actual rules are machine-dependent, and compilers can
often be more efficient in choosing which variables to use as registers.
The scope of an object can be local to a function or block, local to a file, or completely global.
local to a function/block: automatic or static variables which can only be used within that function/block. Function
parameters (arguments) are local to that function.
global (local to a file): static variables declared outside all functions, accessible to any functions following the
declaration
external (accessible in several files): declared as extern, accessible to functions in that file following the declaration,
even if the variable is defined in another file.
main()
{
int x;
int x; /*** illegal: cannot define x twice ***/
x=6;
}
Also, external variables can only be defined once, although they can be declared as often as necessary. If an external variable is
initialized during the declaration, then that also serves as a definition of the variable.
main()
{
printf("%d", z);
}
/** An example with limited variable scope within a file **/
main()
{
int m=2, n=5;
float x=10.0, y=14.1;
int count;
print_pair_i(m, n);
count = print_pair_i(m, n); printf("%d\n", count);
reset_count();
count = print_pair_f(x, y); printf("%d\n", count);
print_pair_d(15.0, 28.0);
count = print_pair_d(20.0, 30.0); printf("%d\n", count);
}
Larger Programs
A program can also be made of pieces, with a header file. This uses the #include preprocessor command. One way is to
compile all the files separately. This can most easily be done with make (see Appendix A).
/* FILE: large_prog.c */
#include "large.h"
main()
{
int n=1;
float x=5.0;
float A(float x)
{
return (x*x*x);
}
/*
** to compile:
** % cc -o large large_prog.c large_sub.c
**
** if large_sub.c will not been changed, can use
** % cc -c large_sub.c
** once, followed by
** % cc -o large large_prog.c large_sub.o
** whenever large_prog.c is changed
*/
/* FILE: large.h */
#include
#define TRUE 1
#define FALSE 0
/* FILE: large_sub.c */
#include "large.h"
int num; /*** only functions in this file can access num ***/
float B(void)
{
return ((float) num);
}
float C(int n)
{
num=n;
return (n*4.0);
}
This has the following output, which shows a dependence on the argument evaluation order in printf().
Output:
125.000000, 0.000000, 4.000000
125.000000, 8.000000, 2.000000
Alternatively, the files can be put together by the preprocessor. This is simpler, but it compiles all the files, not just the
unchanged ones.
/* FILE: large-2.h */
#include
#define TRUE 1
#define FALSE 0
int num; /*** only functions in this file can access num ***/
float B(void)
{
return ((float) num);
}
float C(int n)
{
num=n;
return (n*4.0);
}
/* FILE: large-2_prog.c */
#include "large-2.h"
#include "large-2_sub.c"
main()
{
int n=1;
float x=5.0;
float A(float x)
{
return (x*x*x);
}
/***
to compile:
% cc -o large large-2_prog.c
***/
Macros
Small procedures like swap() and max() can also be written as macros using #define
#define MAX(x,y) ((x) > (y) ? (x) : (y)) /** macro for maximum **/
There are advantages to each. Since #define causes a macro substitution, code is replaced before compilation. It will run
faster, since it won't make a function call, but it will evaluate either x or y twice, which may have side effects or take extra
computational effort. MAX will work on any arithmetic type, while max() will only work on floats. The functions dmax() and
imax() are needed for double and integer values.
Macro substitutions do not happen within quoted strings or as parts of other identifiers: `#define NO 0' does not replace `NO' in
`x = NOTHING;' or `printf("NO!");.' Also, parentheses are very important:
#define RECIPROCAL_1(x) 1/(x)
#define RECIPROCAL_2(x) 1/x
main()
{
float x=8.0, y;
y = RECIPROCAL_1(x+10.0);
printf("1/%3.1f = %8.5f\n", x, y);
y = RECIPROCAL_2(x+10.0);
printf("1/%3.1f = %8.5f\n", x, y);
}
Output:
1/8.0 = 0.05556
1/8.0 = 10.12500
To continue a macro definition onto the next line, use a '\' at the end of the current line.
Problems
Chapter 3: Pointers
Pointer Definition and Use
A pointer is a variable whose value is the address of some other object. This object can have any valid type: int, float, struct,
etc. The pointer declaration syntax is
type *ptr-name;
A pointer to an integer is declared as
int *p;
where `p' is of type `int *', pointer to an integer, and `*p' is of type integer. The type of object the pointer references must be
specified before a value can be obtained. The operators used with pointers are the pointer reference/indirection (*) and address
(&) operators:
main()
{
int x, *px; /* defines the pointer px */
px = &x; /* &x ==> address of x */
*px = x; /* *px ==> value px points to */
}
Figure 1: Memory diagram with pointers. The memory address are given by (n)
/* example program */
main()
{
int x=5, y=6, *p;
p = &x; /** pointer needs to point to something **/
printf("1. x=%d, y=%d, *p=%d\n", x, y, *p);
x = 7;
printf("2. x=%d, y=%d, *p=%d\n", x, y, *p);
*p = 8;
printf("3. x=%d, y=%d, *p=%d\n", x, y, *p);
p = &y;
printf("4. x=%d, y=%d, *p=%d\n", x, y, *p);
*p += 10 * (x * *p);
printf("5. x=%d, y=%d, *p=%d\n", x, y, *p);
}
Output:
1. x=5, y=6, *p=5
2. x=7, y=6, *p=7
3. x=8, y=6, *p=8
4. x=8, y=6, *p=6
5. x=8, y=486, *p=486
main()
{
int x, y;
int *px=(&x); /** can initialize an automatic pointer **/
int *py;
void *pv;
py = px; /** assign to pointer of same type **/
px = (int *) pv; /** recast a (void *) pointer **/
pv = (void *) px; /** recast to type (void *) **/
py = px+2; /** for use with arrays **/
px++; /** for use with arrays **/
if (px == NULL) /** compare to null pointer **/
py=NULL; /** assign to null pointer **/
}
void swap_A(float *x, float *y) /** passes addresses of x and y **/
{
float tmp = *x;
*x = *y;
*y = tmp;
}
Arrays
An array is a contiguous space in memory which holds a certain number of objects of one type. The syntax for array
declarations is
type array-name[const-size];
static type array-name[const-size] = initialization-list;
static type array-name[] = initialization-list;
int x[10];
with index values from 0 to 9.
where the remaining five elements of x[10] will automatically be 0. Part of the array can be passed as an argument by taking
the address of the first element of the required subarray (using &), so &x[6] is an array with 4 elements.
Figure 3: Box-and-pointer diagram of arrays: static int[5]: = {7, 23, 0, 45, 9};
main()
{
int i;
int *p, *p_max;
static int a[] = {1, 5, 645, 43, 4, 65, 5408, 4, 7, 90};
The array and pointer are closely related, in that x[i] and *(x+i) both get the i+1 element in the array, and &x[i] and (x+i) are
both pointers to the i+1 element.
There are differences between them, however. An array name is a constant, while a pointer is a variable, so
Also, defining the pointer only allocates memory space for the address, not for any array elements, and the pointer does not
point to anything. Defining an array (x[10]) gives a pointer to a specific place in memory and allocates enough space to hold
the array elements. To allocate space for an array declared as a pointer, use *malloc() or *calloc(), which are declared in
stdlib.h, and then free() to deallocate the space after it is used.
main()
{
int n;
float *a, *b;
free(a);
free(b);
}
In addition to passing pointers to functions, a pointer can be returned by a function. Three pointers must be of the same type
(or be recast appropriately):
main()
{
int i,n;
int A[100], *max;
max = maximum_ptr1(A,n);
printf("maximum value = %d\n", *max);
}
A multidimensional array can be defined and initialized with the following syntax:
type array-name[const-num-rows][const-num-cols];
static type array-name[const-num-rows][const-num-cols] = init-list;
static type array-name[][const-num-cols] = initialization-list;
static int x[][3] = {{3, 6, 9}, {4, 8, 12}}; /* static--can be initialized */
static int y[2][3] = {{3},{4}}; /* OR {{3,0,0},{4,0,0}} */
main()
{
int z[2][3];
printf("%d\n", x[1][2]); /** output: 12 **/
}
Alternatively, a pointer to a pointer can be used instead of a multidimensional array, declared as int **y. Then y points to a
one-dimensional array whose elements are pointers, *y points to the first row, and **y is the first value.
#define MAX 5
#define LINEFEED printf("\n")
main()
{
int i, j, n = MAX;
int **A, **b, C[MAX][MAX];
A = imatrix(n, n);
for (i=0; i< } i++, (i="1;" for i++) b); scanf(?%d?, b++) j++, j
Output:
0 1 2 3 4
1 2 3 4 5
2 3 4 5 6
3 4 5 6 7
4 5 6 7 8
0 0 0 0 0
0 1 2 3 4
0 2 4 6 8
0 3 6 9 12
0 4 8 12 16
100
100
Strings
Strings are simply character arrays, or more accurately, pointers to characters. A string literal is enclosed within quotes,"...",
and is an array with those characters and `0' at the end, so "hello" <==>{'h','e','l','l','o','\0'}. The string can be defined as
static char *p = "hello"
An example illustrating the use of pointers with the string copies one string into another:
main()
{
char *t = "hello", s[100];
void strcpy(char *, char *);
strcpy(s,t);
printf("%s\n", s); /** will print 'hello' **/
}
It is often useful to give a program arguments when it is run. This is done with command line arguments, which are arguments
of main(). There are two arguments: an integer, argc, which is the number of items in the command line (including the
program name), and an array of strings, *argv[], which are the actual items. The first string, argv[0], is the name of the
function being executed.
If a number is needed, it has to be obtained using sscanf(), which is the same as scanf() except it takes a string as its first
argument. This example prints the square root of a number.
#include
main(int argc, char *argv[]) /** program to find sqrt(x) **/
{
float x;
if (argc == 2)
{
sscanf(argv[1], "%f", &x);
printf("the square root of %f is %f\n", x, sqrt(x));
}
else
{
printf("Wrong number of arguments\n");
printf("usage: %s x \n", *argv);
}
}
Pointers to Functions
Since the precedence of () is higher than *, parentheses are needed around *f to get a pointer to a function.
float square(float x);
float cube(float x);
float arith(float x, float (*func)(float));
main()
{
float x, y, z;
printf("Enter x: "); scanf("%f", &x);
y = arith(x, square);
z = arith(x, cube);
printf("x = %f, x^2 = %f, x^3 = %f\n", x, y, z);
}
float square(float x)
{
return x*x;
}
float cube(float x)
{
return x*x*x;
}
Problems
11. Write a program to input two matrices, add and multiply them, and print the resulting matrices. The matrices can be
any size up to a maximum (#define MAX 5, for example). Use functions input_matrix, print_matrix,
multiply_matrix.
Chapter 4: Structures
Syntax and Operations
A structure is a data type which puts a variety of pieces together into one object. The syntax is given below.
struct structure-tag-opt {
member-declarations
structure-names-opt ; }
structure-name.member ptr-to-structure->member
struct time
{
int hur;
int minute;
int second;
} now;
main()
{
struct time later;
now.hour = 10;
now.minute = 30;
now.second = 4;
later = now;
printf("the later time is %d:%2d:%2d\n", later.hour, later.minute, later.second);
}
This declares structure tag, struct time, which keeps the members, hour, minute, second, in one piece. The variables now and
later are structures of type struct time, declared the same way as integers. The members of the structure can be any type: int,
float, double, char, arrays, other structures, and pointers to any of these. To access the members, there are two operators
available (. and ->). To access the specified member, use the . operator.
Pointers to structures are very useful, and often necessary when functions are used. For example,
The variable t is a pointer to a structure. Since the precedence of . is higher that that of *, the parentheses are needed to get the
actual structure from the pointer to it. Since this is used often, the -> operator was made to do the same thing.
Structure members can be other structures. For example,
struct time
{
int hour, minute, second;
} ;
struct date
{
int month, day, year;
} ;
struct dt
{
struct date d;
struct time t;
} ;
main()
{
struct dt start_class;
start_class.d.month = 1;
start_class.d.day = 5;
start_class.d.year = 93;
}
typedef
typedef defines a new type, which can simplify the code. Here is the syntax:
typedef data-type TYPE-NAME;
typedef struct structure-tag TYPE-NAME;
typedef struct
{
member-declarations
} TYPE-NAME ;
Using typedef also helps portability, especially with machine dependent data types and sizes. With typedef, the change can be
made in one place and is fixed for the whole program. For example,
typedef int Length, Width, Height;
typedef struct time TIME;
TIME now, *t;
will specify the types Length, Width, Height and TIME, and now and t are defined above. typedef is a syntactic simplification
to aid reading and modifying the program. It does not actually create new types.
Array of Structures
main()
{
int i;
DATE birthdays[10], *bday;
bday = birthdays; /*** pointer <==> array name ***/
for (i=0; i<10; i++, bday++)
scanf("%d %d %d", &bday->month, &((*bday).day), &birthdays[i].year);
When bday is defined as a pointer of type DATE (struct date), incrementing will be done properly to get to successive
structures in the array.
Use with Functions
Structures can be used with functions. Just as with other data types, either the structure or a pointer to the structure can be
passed to the function. The choice should be based on three things,
1. does the structure need to be changed by the function,
2. is the structure small enough that copying it as a local argument will not affect performance,
3. does compatibility with old compilers require using pointers to the structures.
typedef struct
{
int hour, minute, second;
} TIME;
typedef struct
{
int month, day, year;
} DATE;
main()
{
TIME now;
DATE today;
time_increment(&now, &today);
PRINT_TIME(now); LINEFEED;
PRINT_DATE(today); LINEFEED;
date_increment(&today);
PRINT_DATE(today); LINEFEED;
PRINT_DATE(date_increment2(today)); LINEFEED;
/*** calls date_increment2 three times in macro ***/
}
A member of a structure can also be a pointer to the same structure type. This is useful with linked lists and trees.
#define NodeMemory (NodePtr) malloc(sizeof (struct node))
struct node
{
int val;
struct node *r_branch;
struct node *l_branch;
} ;
typedef struct node * NodePtr;
main()
{
NodePtr tree, branch;
tree->r_branch->val = 3;
tree->l_branch->val = 40;
printf("%d, %d, %d\n", tree->val, tree->r_branch->val, tree->l_branch->val);
}
union
With union, different types of values can be stored in the same location at different times. Space is allocated to accomodate the
largest member data type. They are syntactically identical to structures, Syntax:
union union-tag-opt
{
member-declarations
} union-names-opt;
union-name.member
ptr-to-union->member
/** a simple example with unions **/
union union_ifd /** can store either an integer, float, or double value **/
{
int ival;
float fval;
double dval;
} ;
main()
{
union union_ifd u1;
u1.ival = 10;
printf("%d\n", u1.ival);
u1.fval = 10.34;
printf("%f\n", u1.fval);
u1.dval = 10.03454834;
printf("%.10lf\n", u1.dval);
}
It is the programmer's reponsibility to know which variable type is being stored at a given time. The code
u1.ival=10;
printf("fn", u1.fval);
enum
The type enum lets one specify a limited set of integer values a variable can have. For example, flags are very common, and
they can be either true or false.
Syntax:
enum enum-tag-opt {enum-tags} enum-variable-names-opt;
enum-name variable-name
The values of the enum variable are integers, but the program can be easier to read when using enum instead of integers. Other
common enumerated types are weekdays and months.
The enumerated values can be set by the compiler or set explicitly. If the compiler sets the values, it starts at 0 and continues in
order. If any value is set explicitly, then subsequent values are implicitly assigned.
main()
{
int x;
FLAGS test2;
FLAGS if_even(int n)
{
if (n%2)
return ODD;
else
return EVEN;
}
This is a more detailed program, showing an array of unions and ways to manipulate them, as well as enum.
/*** example with unions ***/
#define ASSIGN_U_NONE(x) {x.utype = NONE;}
#define ASSIGN_U_INT(x,val) {x.utype = INT; x.u.i = val;}
#define ASSIGN_U_FLOAT(x,val) {x.utype = FLOAT; x.u.f = val;}
#define ASSIGN_U_DOUBLE(x,val) {x.utype = DOUBLE; x.u.d = val;}
typedef union
{
int i;
float f;
double d;
} Arith_U;
typedef enum {NONE, INT, FLOAT, DOUBLE} Arith_E;
typedef struct
{
Arith_E utype;
Arith_U u;
} Var_Storage;
main()
{
int i;
Var_Storage a[10];
int print_Var(Var_Storage x)
{
switch (x.utype)
{
case INT: printf("%d",x.u.i); break;
case FLOAT: printf("%f",x.u.f); break;
case DOUBLE: printf("%.8lf",x.u.d); break;
default: return (0); break;
}
return (1);
}
and
main()
{
static COMPLEX z1 = {{1.0, 2.0}, {0.0, 0.0}};
static COMPLEX z[MAX] = { {{1.0, 1.0}, {0.0, 0.0}},
{{2.0, -1.0}, {0.0, 0.0}} };
rect_to_polar(&z1);
rect_to_polar(z);
rect_to_polar(z+1);
complex_print(z1, BOTH);
complex_print(*z, BOTH);
complex_print(*(z+1), BOTH);
z[2] = z1;
struct rect
{
double a, b;
};
struct polar
{
double r, theta;
};
struct complex
{
struct rect r;
struct polar p;
} ;
typedef struct complex COMPLEX;
Problems
12. Write the functions polar_to_rect, complex_multiply, and complex_input to be used with the answer to question 11.
Chapter 5: C Libraries
The standard C libraries include functions for
memory allocation
math functions
random variables
input/output operations
file manipulations
string manipulations
other functions (see K&R, Appendix~B)
Memory Allocation
in ANSI C, size_t+ is the size of a character. The (sizeof) operator returns the size of an object in units of size_t. In addition,
the type of the pointer returned by malloc and calloc has to be cast as needed. For example,
#include
main()
{
int i, n;
double *A, *a;
scanf("%d", &n);
A = (double *) malloc(n * sizeof (double));
Math Libraries
There are a variety of math functions available, all declared in /usr/include/math.h. Most take arguments of type double and
return values of type double. For example,
#include
main()
{
double x,y,z,theta;
z = sqrt(x);
z = sin(theta); /*** theta is in radians ***/
z = asin(x);
z = atan(x);
z = atan2(y, x); /*** atan(y/x) ***/
z = exp(x); /*** e^x ***/
z = log(x); /*** ln(x) [natural log] ***/
z = pow(x, y); /*** x^y ***/
}
#include
main()
{
double x, y, theta;
scanf("%lf", &x);
printf("sqrt(%f) = %f\n", x, sqrt(x));
printf("sin(0.6) = %f\n", sin(0.6));
printf("atan(10) = %lf\n", atan(10.0));
printf("atan(10/20) = %lf\n", atan2(10.0, 20.0));
printf("exp(10) = %lf\n", exp(10.0));
printf("log(10) = %lf\n", log(10.0));
printf("log_10(10) = %lf\n", log10(10.0));
printf("10^1.3 = %lf\n", pow(10.0, 1.3));
}
Output:
sqrt(10.000000) = 3.162278
sin(0.6) = 0.564642
atan(10) = 1.471128
atan(10/20) = 0.463648
exp(10) = 22026.465795
log(10) = 2.302585
log_10(10) = 1.000000
10^1.3 = 19.952623
When compiling, is must be specified that the math libraries are being used. For example, on Unix systems, use
cc -o .c -lm
Random Variables
Using random variables is system dependent. The ANSI C functions are rand() and srand().
int rand(void);
void srand(unsigned int seed);
int RAND_MAX;
The function rand() will return a value between 0 and RAND\_MAX, where RAND\_MAX is defined in and is at least 32,767.
The function srand() is used to seed the random number generator. Many systems have better random number generators, and
C can usually access them, although this would then not be very portable. A good practice is to write a function or macro
which returns a random number, and have this call the system-specific routines.
#include
main()
{
int i, n, seed;
double *x;
The conversions for printf() and scanf() are described in tables 3 and 4. In addition, I/O includes character output, sscanf(), and
file manipulation.
#include
int i;
char c, s[], file_name[], access_mode[];
FILE *fp;
fp = fopen(file_name, access_mode);
fflush(fp); /*** flush the buffers ***/
fclose(fp);
c=getchar();
c=getc(fp);
gets(s);
fgets(s,i,fp); *** first i characters or till newline ***/
scanf(format, &arg1, ...) /*** formatted ***/
fscanf(fp, format, &arg1, ...);
sscanf(s, format, &arg1, ...);
/*** reads in n integers from a file,
then prints the values to another file as type float ***/
#include /*** for file manipulation functions ***/
#include /*** for malloc() ***/
main()
{
int i, n, *x;
char file_name[FILENAME_MAX];
FILE *fp;
Strings
int i;
size_t n;
char *s, *s1, *s2, *to, *from;;
s = strcat(s1, s2);
s = strchr(s, char c);
i = strcmp(s1, s2); /*** s1=s2 ? 0 : (s1>s2 ? 1 : -1) ***/
s = strcpy(to, from); /*** returns *to ***/
n = strlen(s);
s = strstr(s1, s2); /*** is s2 in s1? ***/
s = strncat(s1, s2, int n); /*** only use first n characters ***/
i = strncmp(s1, s2, int n);
s = strncpy(to, from, int n);
#include
#include
main()
{
char *s, *ct = "hello";
s = (char *) malloc(100 * sizeof(char));
strcpy(s, ct);
printf("%s\n", s);
if (strcmp(s, ct) == 0)
printf("strings equal\n");
else
printf("\"%s\" and \"%s\" differ\n", s, ct);
}
#include
#include
main()
{
int i, j;
float x;
char *s1 = "10 20 15.5", *s2;
s2 = (char *) malloc(100 * sizeof(char));
The target is the file to be produced, either an executable file or an object file. The dependency list specifies the files on which
the target depends, so if any of the dependency files has been modified since the target file was created, make will create a new
target file. The command lines specify how the target is supposed to be make. Although this is primarily using the cc
command, other commands can also be executed. The syntax is given below.
# comments
var = string value
$(var) # uses variable value
target: dependencies
TAB command-lines
% make -k
% make -k
When calling make, the -k argutment indicates that all specified files shluld be compiled, even if there is an error compiling
one file. Otherwise, make will stop when there is a compile error in any file.
$(PROGS) : $(PROGS).c
$(CC) -o $(PROGS) $(PROGS).c
Output:
% make program
/bin/cc -O program.c -o program
# a makefile for program large, Section 2
% make -k
/bin/cc -O -c large_prog.c
/bin/cc -O -c large_sub.c
/bin/cc -o large large_prog.o large_sub.o
% make -k large_prog.o
/bin/cc -O -c large_prog.c
% make clean
rm large_prog.o large_sub.o
General Style
The first question is the placement of statements and braces. Consistency makes it easier to follow the flow of the program,
since one can get used to looking for matching and optional braces in specific places.
/*--- avoid putting more than one statement on the same line ---*/
statement1; statement2; /* BAD */
statement1; /* OK */
statement2;
int func(int arg)
{ /* no indentation on the first set of braces */
/* around a procedure */
switch (...)
{
case xxx: /* case labels are indented clearly mark the */
statement; /* places where a 'break' statement is */
/*fall thru*/ /* expected but missing */
case yyy:
statement;
break;
default:
statement;
break; /* should have a break statement even at the end */
} /* of the default case */
if (...) /* BETTER */
statement;
else if (...)
statement;
else if (...)
statement;
switch (...) /* OK */
{
case xxx:
statement;
break;
case yyy:
statement;
break;
}
if (...) /* OK */
statement;
else if (...)
statement;
else if (...)
statement;
Layout
Avoid using the TAB character in a source file. Use spaces instead. This is because there is no standard governing how
TAB's are expanded. Thus, the same file may look very differently from one editor to another.
Avoid having any text past column 78.
Coding Practice
void proc(void)
{
char *p = "a";
...
*p = 'b'; /* VERY BAD */
}
Whenever possible, use a single assignment statement to modify an entire structure or union. However, do not use this
to initialize structures or unions. For example,
typedef struct
{
int x, y;
} COORD;
Naming Conventions
Avoid using '_' as the first character of a name; these names are generally reserved for use by the compiler and
libraries.
Pre-processor symbols (#define's) should almost always be in upper case, with '_' as an optional separator (e.g.,
DELETE_CHAR).
Type names can be either
o all upper case, with '_' (e.g., RING_BUFFER), or
o upper case followed by mixed case, sans '_' (e.g., RingBuffer ).
Variables can be either
o all lower case, with or without '_' (e.g.,old_value, oldvalue)
o lower case followed by mixed case, sans '_' (e.g., oldValue).
Procedure names can be either
o all lower case, with or without '_' (e.g., get_first, getfirst)
o upper case followed by mixed case, sans '_' (e.g., GetFirst)
One can have each global name preceded by a one to three character prefix that indicates where the name is defined.
E.g., RbRemoveElement}, RB_remove_element (indicating that these procedures are defined in the RB module). Some
older compilers do not allow global names with '_' or more than 8 characters, but this restriction usually no longer
applies.
A local procedure/variable should have a name that is as descriptive as possible. For example,
static int MDinsls1(...) /* too cryptic */
static int insert_list_head(...) /* much better */
#define FREEZE 32
#define BOIL 212
#define STEP 20
main()
{
int f;
for (f=FREEZE; f<=BOIL; f+=STEP)
printf("F = %3d, C = %5.1f\n",f,(f-32.0)*5.0/9.0);
}
/** "A Crash Course in C," problem 3:
** input a number and print all its factors **/
#include
main()
{
int i,n;
main()
{
int i,n, nmax, prime_flag=TRUE;
if (prime_flag)
printf("%6d is prime\n", n);
else
printf("%6d is not prime\n", n);
}
/** "A Crash Course in C," problem 5
** program to calculate x using the quadratic formula
**/
#include
main()
{
float a, b, c, d, x1, x2;
printf("input a,b,c: ");
scanf("%f %f %f",&a, &b, &c);
d = b*b - 4.0*a*c;
if (d >= 0) /*** check if solution will be real or complex ***/
{
x1 = (-b+sqrt(d)) / (2.0*a);
/*** need parentheses for proper order ***/
x2 = (-b-sqrt(d)) / (2.0*a);
/*** use sqrt() from the math library ***/
printf("x = %f, %f\n",x1,x2);
}
else
{
x1 = -b/(2.0*a);
x2 = sqrt(-d)/(2.0*a);
printf("x = %f + %fi, %f - %fi\n", x1, x2, x1, x2);
}
}
/** "A Crash Course in C," problem 6
** input an integer and print it in English **/
main()
{
int n, pow_ten;
printf("input an integer: "); scanf("%d",&n);
do {
pow_ten=1;
while (n / pow_ten)
pow_ten *= 10;
main()
{
char c;
int characters, lines;
main()
{
int n;
float x;
printf("input x, n: ");
scanf("%f %d", &x, &n);
printf("%f^%2d = %f\n", x, n, x_to_int_n(x,n));
}
main()
{
int n;
printf("input n: ");
scanf("%d", &n);
printf("factorial(%d) = %d\n", n, factorial(n));
printf("factorial(%d) = %d\n", n, factorial_r(n));
}
int factorial(int n)
{
int fact=1;
for ( ; n>1; n--)
fact *= n;
return fact;
}
int factorial_r(int n)
{
if (n>1)
return n*factorial_r(n-1);
else
return 1;
}
/** "A Crash Course in C," problems 11
** input a number and find all primes less than it
**/
#include
main()
{
int n, i;
while (n=get_positive_int())
for (i=2; i<=n; i++)
if (is_prime(i))
printf("%d is prime\n", i);
}
int is_prime(int n)
{
int i, max;
max = (int) sqrt((double> n);
int get_positive_int(void)
{
int n;
do
{
printf("input a positive number, 0 to quit: ");
scanf("%d", &n);
} while (n < 0);
return n;
}
/** "A Crash Course in C," problems 11
** matrix functions:
** input_matrix(), print_matrix, add_matrix, multiply_matrix **/
#define MAX 5
main()
{
int n;
float A[MAX][MAX], B[MAX][MAX], C[MAX][MAX];