Introduction to C
From Education
Introduction
This is not a real C tutorial. If this were a real C tutorial, it would attempt to be comprehensive, give enough information for you to follow along, and never introduce anything in an example before being defined.
There are some real tutorials out there. You might try the one at the following link http://www.le.ac.uk/cc/tutorials/c/.
But maybe you'll find this useful anyway.
Hello World (compiling and running a C program)
Programs in C have 3 stages:
- source code - human readable/editable instructions: this is the code you type up in a text editor.
- c files
- header files
- object code - intermediate stage machine readable code
- executables - runnable program: this is the program that you actually run.
To go from source, to objects, to an executable you use a special program called a compiler.
Start by creating a c source file, hello.c. On the BCCD, you might do this using the nedit or nano command.
>> nedit hello.c
The beginning of any C source code typically has statements at the beginning defining any headers (files that include definitions of C functions). Functions, the most important of which is the main function ("main"), follow the header definitions. Main is always the first function to be executed when a program starts.
The following main routine uses a standard print function called printf (print formatted) to print the message "Hello World". This function comes from the stdio (standard i/o) library - we specify this by putting #include <stdio.h> at the beginning of the file. The formatting character "\n" indicates that a new line should be printed after the "Hello World" string.
#include <stdio.h>
int main(int argc, char ** argv) {
printf("Hello World\n");
}
Save your file and compile it with the "cc" command. -o indicates the name of the output file; in this case, it will be hello. Do not name your output the same name as your program! It's easily possibly to overwrite your source code when you compile, losing your work. After you've compiled it, type ls to view the generated object and executable. Then run your program with ./hello.
>> cc -o hello hello.c >> ./hello
Comments
You can comment your C codes by either using two slashes, with the comment taking up the remainder of the line, or by enclosing a comment over many lines between a /* and a */. Below, comments are displayed in blue. Some C editors will color code your comments for you, making it easier to see them.
// This is exactly one line of comment
printf("The quick brown fox..."); // This ends the line of code
/* This comment extends
to multiple
lines */
Variables
Variables are used to store information. C is a strictly typed language, meaning that each variable can only store one type of information. When a variable is created (called declaring it), the type of variable is specified.
There are four basic types:
- int - integer, comes in many varieties
- char - character
- float - floating point, or real, number
- double - double precision floating point number
There is also a programmer-defined type of variable called a struct, short for structure. These include any combination of the other types of variables.
Consider the following code. There are a few new ideas in here. First, in Hello World, we didn't print any variables - we told it exactly what to print ("Hello World"). In this program, we're printing out variables and we have to do this a special way. In the case of int variables, we use %d to hold the place where we want to print out the variable, and then put the int variable that we want after that string. %d is a way of saying, "print the variable right here!" For instance, printf("a = %d\n",a); tells the program to print "a = " and then the variable that follows the comma, which is a. printf("a + b = %d \n",(a+b)); tells it to print "a + b = " followed by the result of adding a + b. We could print the contents of the variables a and b with printf("a = %d, b = %d \n", a, b); The variables are put into their %d placeholders in the order they are listed (a and then b).
Doubles work the same way except instead of %d, the placeholder for double is %lf. String placeholders are %s and characters placeholders are %c.
What do you think should be printed out for each line? Type the code in and run it. Do you get the results that you expect?
/******************************************
* numbers .c
* this program shows the difference between
* integers and doubles
*****************************************/
#include <stdio.h>
int main() {
int a,b,c; // declare integer variables
double x,y,z; // declare double variables
a = 2; // initialize integer variables
b = 5;
c = 7;
printf("a = %d\n",a); // do some stuff and output
printf("b = %d\n",b);
printf("c = %d\n",c);
printf("a + b = %d \n",(a+b));
printf("b * c = %d \n",(b*c));
printf("c / b = %d \n",(c/b));
x = 2.0; // initialize double variables
y = 5.0;
z = 7.0;
printf("x = %lf\n",x); // do some stuff and output
printf("y = %lf\n",y);
printf("z = %lf\n",z);
printf("x + y = %lf \n",(x+y));
printf("y * z = %lf \n",(y*z));
printf("z / y = %lf \n",(z/y));
}
Conditions
The if and if-else structures can be used to conditionally execute code based on specific conditions. For example, consider the following code which computes an absolute value of a number.
/*******************
* condition.c
* example of if-else structure
* using absolute value
******************/
#include <stdio.h>
int main(int argc, char ** argv) {
double x, absx;
sscanf(argv[1], "%lf", &x); // Get x from command line argument
// Yes, I know, we haven't talked about this yet.
// No, I'm not going to go over it right now.
// Yes, I'll come back to this later.
if(x >= 0.0) {
absx = x;
} else {
absx = -x;
}
printf("x = %lf, |x| = %lf \n", x, absx);
}
Compile this using gcc. Then run it by typing the command followed by your argument. Which part of the code gets executed for -3? 2? 5? 1234? -1234?
>> gcc -o condition condition.c >> ./condition -3.0 x = -3.000000, |x| = 3.000000 >> ./condition 2.0 x = 2.000000, |x| = 2.000000
How would you modify this program, still using if-else, to divide one hundred by the number entered and print an error message if the number entered is zero?
Loops
Loops allow you to do the same things over and over again. There are two basic types of loops:
- For loops repeat a set number of times
- While loops repeat as long as a condition is true
Consider the following example code. The int i is created ahead of the for loop and used to direct the loop to execute a certain number of times. for (i=1; i<100; i++) says to set i equal to one, to go until i is greater than one, and for each iteration, increment i by one. The while loop, on the other hand, looks much less complicated: while(fib_old_1 <= 100). Rather than ending at a specific point, like the for loop, the while look operates on the opposite principle - it keeps looping as long as something is true. In this example, the while loop keeps executing as long as the variable fib_old_1 is less than or equal to one hundred.
/******************
* loops.c
******************/
#include <stdio.h>
int main() {
int i;
int sum1_100;
int fib_old_2, fib_old_1, fib_new;
// Compute sum of all numbers inclusive from 1 to 100
sum1_100 = 0;
for (i=1; i<100; i++) { // 'i++' is short for 'i = i + 1'
// Yes, there is a '++i' syntax as well.
// No, we are not talking about that right now either.
sum1_100 += i; // 'x += a' is short for x = x + a
}
printf("Sum from 1 to 100 = %d \n", sum1_100);
// Compute the Fibbonacci sequence as long as the numbers in the sequence
// are less than or equal to 100
// 1 , 1 , 2 , 3 , 5 , 8 , 11, etc.
fib_old_2 = 1;
fib_old_1 = 1;
printf("1");
while(fib_old_1 <= 100) {
printf(", %d", fib_old_1); // print last term in sequence
fib_new = fib_old_1 + fib_old_2; // find next term in sequence
fib_old_2 = fib_old_1; // shift storage of sequence to prep for next time
// through loop;
fib_old_1 = fib_new;
}
printf("\n");
}
Functions
Functions allow you to create shortcuts for operations that you do often. Many
functions are already created for you, such as the mathematical functions (sin, cos, exp,
etc.) in the math.h library. Others you can create for yourself. These can either be inside your source code in the file that contains the main function, or they can be in a separate file and declared as a header within the file with the main function. All functions must have a return type (an int, character, double, float, struct, etc.); if they do not return a value, then the return type is void.
Consider the following example, which uses a function that takes in two doubles and returns the one that is larger than the other. Notice our new function, maxmin is declared before the main function. The syntax for declaring a function is <function return type> <function name> (<arguments, if any>).
/*****************
* maxmin.x
****************/
#include <stdio.h>
#include <stdlib.h>
// HERE IS WHERE WE DEFINE THE FUNCTION !!!!
// format is return type, function name, arguments ...
double maxmin (double a, double b) {
if (a > b) {
// ... and return a value before you're done unless your
// return type is "void."
return a;
} else {
return b;
}
}
int main(int argc, char ** argv) {
double a, b;
// IF WE DONT DEFINE THIS IN A HEADER FILE, WE NEED EVERY OTHER FUNCTION
// TO KNOW WHAT TO LOOK FOR
extern double maxmin(double,double);
// read arguments from command line with some error checking
if (argc>=3) {
if(!sscanf(argv[1],"%lf",&a)) { // Again, really, don't ask yet.
fprintf(stderr,"Problem with argument 1.\n");
exit(0);
}
if(!sscanf(argv[2],"%lf",&b)) {
fprintf(stderr,"Problem with argument 2.\n");
exit(0);
}
} else {
fprintf(stderr,"Problem with input.\n");
fprintf(stderr,"USAGE: maxmin [number] [number] \n");
exit(0);
}
// HERE IS WHERE WE CALL THE MAXMIN FUNCTION
printf("The max of %lf and %lf is %lf.\n",
a,b,maxmin(a,b));
}
Run the code and verify that the maxmin function returns the correct value. This code also demonstrates nested ifs - notice that one if statement is within the other one. It's important to ident well to demonstrate how two if statements are within the if of the outside if-else block. This code also shows how the program can exit without reaching the end of the code. exit(0) tells the program to end.
Pointers
For an excellent introduction to pointers, watch the Binky Pointer Fun Video.
Pointers are actually another type of variable, but they don't have to be explicitly declared. Every variable has a pointer associated with it. They are used to "point" to variables in memory. A pointer is literally the address in memory of a variable. They can be really useful, particularly when we want another function to be able to change our variable. They are also useful in defining lists, but we'll get to that later.
The following code example shows a function where we use a pointer so that an external function can modify our variable. In the last example, we defined our maxmin function as maxmin (double a, double b). When we passed maxmin the doubles a and b, we didn't actually send the variables themselves - we just send the values of the variables. Now that we want our function - we'll call it setEqualToTwo - to actually modify a variable we send it, we need to send it the address of the variable in memory. Once it knows where the variable is stored, then it can access that value, change it, and then save the new value over the old one.
We declare two functions - one to demonstrate that passing a pointer to a variable will allow the function to change the value of the variable, and one to demonstrate that passing the variable itself will not allow the function to change the value of the variable. Notice the declaration for setEqualToTwo: void setEqualToTwo(int * p). void indicates that the function will not return a value. int * p indicates that it will take in a pointer to an int. When we call this function, we send use setEqualToTwo(&x); which says to send the address of p (the pointer) to the function.
The format for accessing the pointer to a variable x is &x.
The format for accessing the value of a pointer p is *p.
/********************
* pointers.c
*******************/
#include <stdio.h>
// This function sets a variable equal to 2.
// Isn't it exciting.
// A cooler example would have been a routine to clean up your
// dynamic memory allocation, but we haven't even gotten to
// arrays yet. And aren't you tired of my don't ask me yet
// comments?
void setEqualToTwo(int * p) {
// note that the argument includes an asterisk. You could read this as either
// "content of p is an int" or "p is a pointer to an int"
// The next line sets the contents of p to 2.
*p = 2;
}
void dontSetEqualToTwo(int x) {
// This is not going to work
x = 2;
}
int main() {
int x = 1;
printf("The value of x is %d\n",x);
printf(" calling dontSetEqualToTwo\n");
dontSetEqualToTwo(x);
printf("The value of x is %d\n",x);
printf(" calling setEqualToTwo\n");
// notice that I pass the address of x (pointer to x)
setEqualToTwo(&x);
printf("The value of x is %d\n",x);
}
Notice that when you run this, the value of x is not changed upon return from dontSetEqualToTwo. You could check, just for fun, to see if the variable x is changed while you are in the function, which could lead to a really interesting discussion of variable scope in C [1]. Or not.
Also, remember that I said I would explain that 'sscanf(argv[1],"%lf",&a)' line a few examples ago? Now you can see why there is an ampersand for the 'a' argument, you need to pass an address for a to the sscanf function so that it can set its value in your function. And you thought I would never get back to that.
Arrays
Arrays are lists of values. They are stored contiguously in memory, and accessed typically by the address of the first value. This means that the variable for a list is not a value, but a pointer to the first value. You can define arrays with a set length, or you can dynamically allocate an array, but if you do the latter you have to free the memory when you are done, or else you get memory leaks and maybe even the dreaded segmentation fault.
Array values are accessed by the index, or place within the list. You start counting from zero and go to n-1 for a list of length n.
The following code will set up a couple of lists, both using fixed length allocation and using dynamic allocation.
/********************
* arrays.c
********************/
#include <stdio.h>
#include <stdlib.h>
#define n_a 10 // we haven't talked about the define keyword
// define is almost like a variable, but not quite.
// with a define statement, n_a is not a variable
// with a value of 10, n_a IS 10, and always shall
// be, yea until the program is changed by hand
// and recompiled.
int main() {
// we use n_a that has been defined here because non-dynamic memory allocation
// won't take a variable, it needs a number, and n_a has been defined, not assigned,
// so to speak.
int a_list[n_a];
int * b_list;
int n_b=10;
int i;
// This looks ugly doesn't it. You are thinking, I'm never going to
// dynamically allocate memory like this. You are thinking, does setting up
// my arrays with a defined n_a really have less functionality than an assigned
// n_b. Yes, it does have less functionality actually.
// It's really not that bad,
// but this is why we write helper functions in C to do this, and
// use the 'new' keyword in C++.
b_list = (int *)malloc(sizeof(int)*n_b);
for(i=0;i<n_a;i++) {
a_list[i]=2*i+1;
printf("a_list[%d] = %d\n",i,a_list[i]);
}
for(i=0;i<n_b;i++) {
b_list[i]=-2*i+3;
printf("b_list[%d] = %d\n",i,b_list[i]);
}
// when you are done, clean up any memory you allocate
free(b_list);
}
Ok, so you can run that and test it.
Now, remember again that whole scanf thing? Notice the first argument is argv[1]? (or the second argv value, but argv is a pointer to a pointer to a character, or a list of list of characaters, or a list of words. argv stores a list of the words you type on the command line (including the command) and argv[1] is the second word you typed on the command line, which the sscanf [2] command scans to find a value.
File I/O
File input and output requires that you open and close any file that you are going to use, but beyond that, much of what you would do with file i/o is like regular i/o, but you add an f in front of the command. printf becomes fprintf, and you add a file pointer to the argument list.
Your main commands areFILE * fopen(const char * filename, const char * mode),
where for the mode you typically put "r", "w", or "a" (read, write, or append). There are variations, but those are the basic modes. When writing a file, C actually writes
to a buffered string for a while and only periodically writes to the file, but you can force a flush withfflush(FILE * fp). You only want to do this in special occasions,
such as if you are closing a file and know you will re-use it within the same program, or when you are debugging and are willing to spend lots of time on file i/o in order to ensure that the file is always up to date. After finishing with a file, you close it with the
fclose(FILE * fp)command. Writing to a file is as simple as using
fprinf(FP * fp,const char * format,...),
just like with printf.
There are commands to scan data directly from a file, but they can be unstable. Use a combination of grabbing a line from the file and scanning the line instead. The commands
fgets(char * str,int STR_MAX,FILE * fp)and
sscanf(char * str,const char * format,...)are useful for this.
#include <stdio.h>
#define LINE_MAX 360
#define DEBUG 1
int main(int argc, char ** argv) {
FILE * outfile;
FILE * infile;
char linestring[LINE_MAX];
int test_int;
outfile = fopen("test.txt","w");
fprintf(outfile,"1\n2\n3\n4\n");
// if you plan to re-use the same file, flush before closing!
fflush(outfile);
close(outfile);
infile = fopen("test.txt","r");
while(fgets(linestring,LINE_MAX,infile)!=NULL) {
sscanf(linestring,"%d",&test_int);
printf("%d\n",test_int);
//when debugging, flushing ensures that the file is always up
// to date
if(DEBUG) fflush(infile);
}
close(infile);
outfile = fopen("test.txt","a");
fprintf(outfile,"p.s. you can append to a file too\n");
close(outfile);
}
