clark cs2123 Course Outline lecture notes programming assignments homework set up Last Updated: 1/8/2015 (subject to change within 24 hours of lecture) Complete Program Example Compiling and Linking To compile in unix, we use gcc to generate compiled object and executables: gcc -g -o executableName cSourceFileName.c where -g tells the compiler to generate information suitable for the debugger. This generated an executable with the name specified after the output switch (-o). Until we have more complex assignments, we will use that form of the gcc. To compile a c source file, producing only a compiled object: gcc -c -g -o cSourceFileName.o cSourceFileName.c where specifying -c will cause it to only compile, producing a compiled object named cSourceFileName.o To link a compiled object file, producing an executable: gcc -g -o executableName cSourceFileName.o anyOther.o ... # compile first.c with debugging, producing first # as executable gcc -g -o first first.c # only compile, producing an object (.o) file gcc -c -g -o first.o first.c # link a .o file, producing first as an executable gcc -g -o first first.o # you can link many .o files, producing an executable gcc -g -o cs2123p1 cs2123p1Driver.o toPost.o int main() Within your linked module, there should be one int main(). This is the function that begins execution. It is passed an array of pointers to strings and a count of the number of items in the array. It returns an integer. By convention, 0 is returned for normal return. The contents of argv includes the command name and the command line arguments to this program. Example: average program Suppose the program average is passed a list of integers and returns the average. In unix, we would invoke it by keying $ average 10 20 40 30 25.00000 variables dataType variableName = initialValue; The intialization is optional. Some of the data types: int - integer (could be 2 or 4 bytes depending on implementation) long - 4 byte integer float - 4 byte floating point double - 8 byte floating point char - single character #include <stdio.h> int main(int argc, char *argv[]) { double dSum = 0.0; int i; int iScanfCount; // count of the successful // conversions in sscanf int iNum; // number from the command line /* check for no arguments */ if (argc < 2) { fprintf(stderr, "Error: must pass values to average"); return 0; } for (i = 1; i < argc; i++) { // convert command argument to int iScanfCount = sscanf(argv[i], "%ld", &iNum); // sscanf returns the number of successful formats if (iScanfCount < 1) { fprintf(stderr, "Invalid command argument: %s" , argv[i]); return -1; } dSum += iNum; } printf("%lf", dSum / (argc - 1)); return 0; } int iStudentCount; int iStudentCount = 0; int iOrderQuantity = 0; double dAverage = 0.0; char cLetterGrade = 'F'; int bEOFEncounteredInd = 0; // 0 is false, non-zero // is true. Declaring an Array dataType variableName[numberOfElments]; dataType variableName[] = {initialValueList}; Note that subscripts in C begin with 0. (Some languages have subscripts that begin with 1 such as COBOL and FORTRAN. PL/I by default begins with 1, but can be changed to any integer.) To declare a string of characters (note strings end with a zero byte terminator): char variableName[sizePlus1]; char variableName[] = initialValue; A zero byte is the constant '\0'. A literal character string surrounded in double quotes is automatically terminated with a zero byte. Note that arrays of characters do not have to be zero byte terminated, but if they aren't, several C functions do not work property. We try to be clear in coding for whether the string is zero-terminated or not. Our convention: szName- string terminated by zero byte sbName - string not terminated by zero byte. It might contain binary data. double dExamWeightingM[3] = {200.0, 200.0, 300.}; double dExamWeightingM[] = {200.0, 200.0, 300.}; char szFirstName[21]; // max of 20 characters // plus zero byte char szFileName[] = "myInput.txt"; char szABC[] = {'A', 'B', 'C', '\0'}; char szABC[] = "ABC"; // automatically zero // byte terminated A B C \0 char sbDelims[] = {',', ' ', '\t', '\n'}; int iDelimCount = sizeof(sbDelims); printf("number of delims = %d\n", iDelimCount); if statement The if statement is the most important statement for flow control (as it is in most languages). Syntax options: if (conditionalExpression) truePart; if (conditionalExpression) truePart; else falsePart; Comparison operators: == equal to != not equal to > greater than < less than >= greater than or equal to <= less than or equal to Logical operators: && and || or ! not Warning! Warning! Warning ! == is the equivalence test operator. = is an assignment operator and must not be used for an equivalence test. && is the logical and. || is the logical or. A single & is a bitwise and. if (dExamScore == 0) iScoreCount += 1; if (strcmp(szCommand,"DEPOSIT") == 0) dBalance += dAmount; else if (strcmp(szCommand, "WITHDRAWAL") == 0) { if (dBalance < dAmount) { printf("overdrawn\n"); return(ERROR_OVERDRAWN); } else dBalance -= dAmount; } Example of a common mistake: if (iCount = 3) That conditional is always true since an assignment returns its value (3) and 3 is not zero. Since some other programming languages (e.g., PL/I, SQL) use = for equivalence tests, this is a common mistake for people using multiple languages. A single | is a bitwise or. Warning! Warning! Warning! comparing strings The equivalence operator (==) cannot be used to compare strings. The compiler would think it is comparing addresses. while statement if (cLetterGrade == 'W') { printf("last day to drop is %s\n", szDropDate); dCourseGradePoints = 0.0; dCourseCredits = 0.0; } if (szCommand == "DEPOSIT") would check to see if the address of szCommand is the same as the address for the literal "DEPOSIT". Use strcmp() or strncmp() for string comparisons. The while statement continues to loop while the condition is true (not zero). Syntax: while (conditionalExpr) whileBody; for statement The for statement is the iterative loop in C. Unlike Java, you cannot declare a variable in the (). for (initializationStmts; conditionalExpr; incrementStmts) forBody; When the for statement is encountered, the initalizationStmts are executed. The for loop continues while the conditionalExpr is non-zero. At the bottom of each iteration, the incrementStmts are executed. assigning values The = operator is used for assigning values. Strings cannot be assigned using the = operator in C. variableReference = valueExpr; Strings are assigned values using strcpy(target, source); assigns a null terminated string to the target strncpy(target, source, length); assigns length characters from source to target. If the actual length of source is > length, the result in target won't be null terminated. If a null byte is encountered before length, no other characters are copied from source. memcpy(target, source, length); assigns exactly length characters from source to target. Warning! Warning! Warning! string assignments can overwrite memory One of the most common and frustrating bugs in C is overwriting memory. // read the student data until EOF (fgets returns null pointer) pszGetsResult = fgets(szInputBuffer, 100, pfileStudentData); while (pszGetsResult != NULL) { printf(%s\n", szInputBuffer); pszGetsResult = fgets(szInputBuffer, 100, pfileStudentData); } // Print the names of students earning an A for (i = 0; i < iStudentCount; i++) { if (studentM[i].dGradeAvrage >= 90) printf("%s\n", studentM[i].szName); } dAverage = dSum / iCount; dCircumference = 2.0 * PI * dRadius; if (iCount % 5 == 0) // look for a count that is a // multiple of 5 dBalance += dAmount; dBalance -= dAmount; // deposit // withdrawal char szWinnerFullName[15] = "Anita Break"; char szContestantFullName[15] = "Bob Wire"; strcpy(szWinnerFullName, szContestantFullName); Value of szWinnerFullName is B o b W i r e \0 a char szName[10]; char szSpouse[] = "Anita Break"; strcpy(szName, szSpouse); k \0 ? ? ? sizeof and strlen sizeof ( thing ) is a compile-time function which returns the compile-time (not runtime) size of the thing. strlen ( variable ) returns the length of a null-terminated string. int iCount = 0; double dPrice = 10.5; char szNickName[21] = "champ"; struct Account { char szAccountNr[15]; double dBalance; } account = { "123456789", 100.20 }; printf("%-20s %-10s %-10s\n", "Thing", "sizeof", "strlen"); printf("%-20s %-10d %-10s\n", "iCount", sizeof(iCount), "n/a"); printf("%-20s %-10d %-10s\n", "dPrice", sizeof(dPrice), "n/a"); printf("%-20s %-10d %-10d\n", "szNickName", sizeof(szNickName) , strlen(szNickName)); printf("%-20s %-10d %-10d\n", "szAccountNr" , sizeof(account.szAccountNr), strlen(account.szAccountNr)); printf("%-20s %-10d %-10s\n", "dBalance" , sizeof(account.dBalance), "n/a"); printf("%-20s %-10d %-10s\n", "account", sizeof(account) , "n/a"); Output: Thing iCount dPrice szNickName szAccountNr dBalance account Exercise: Given a name, store the reverse of the name in another array. char szName[21] = "Perry Scope"; char szReversedName[21]; sizeof 4 8 21 15 8 24 strlen n/a n/a 5 9 n/a n/a switch statement The switch statement is used when there are options of what should be done that are dependent on the value of a variable. switch (variableReference) { case value: … // possibly many case value: statements; break; case value: … // possibly many case value: statements; break; default: statements; } Notes: There may be multiple case value clauses followed by statements. A break statement is necessary to specify branching to immediately after the switch statement. default statements are optional. If present, the default will execute if none of the case values match the value of the variableReference. switch (cLetterGrade) { case 'A': case 'B': printf("Better than Average\n"); printf("You are exempt from the exam\n"); break; case 'C': printf("About Average\n"); break; case 'D': case 'F': prinf("Below Average\n"); } break statement in for or while The break statement can also be used to exit a for or while statement. Unfortunately, if you have multiple nested looping statements, you can't specify which one to break out of. It always assumes the immediatley surrounding switch, while or for. // keep looping until a player wins while (TRUE) { iResult = takeTurnForPlayer(player1); if (iResult == PLAYER1_WINNER) break; iResult = takeTurnForPlayer(player2); if (iResult == PLAYER2_WINNER) break; // another example switch (cLetterGrade) { case 'A': case 'B': case 'C': case 'D': printf("You passed\n"); break; case 'F': printf("You failed\n"); break; default: fprintf(stderr, "unknown grade %c\n" ,cLetterGrade); } continue statement in for or while The continue statement allows you to skip the rest of the code in a loop and continue with the increment/test. It always assumes the immediatley surrounding while or for. printf This prints output based on a specification. printf(specification, value1, value2, …); The specification contains strings and format codes. Some format codes: %s show a null terminated string %d show an integer value %ld show a long integer value %f show a floating point value %lf show a double value %c show single character %x show a hex value We use \n to cause the output to do a line feed. A format code can be in the following form: flag minWidth precision lengthModifier code We go over this in more detail later in the semester, but here are some helpful examples: %10.2lf where 10 is the minWidth, 2 is the precision, l is the lengthModifier (long floating point), and f is the code. } // keep looping until there is only one player left while (iNumActivePlayers > 1) { // get the next player iCurrentPlayer++; if (iCurrentPlayer > iNumPlayers) iCurrentPlayer = 0; // if player cannot move, skip him if (cannotMove(iCurrentPlayer, game) == TRUE) continue; // skip this player startTimer(iCurrentPlayer, game); iResult = takeTurn(iCurrentPlayer, game); if (iResult == WINNER_DETERMINED) break; prepareForNextPlayer(iCurrentPlayer, game); } char szName[21] = "Bob Wire"; int iOrderQuantity = 5; double dUnitCost = 7.25; char cGender = 'F'; printf("name is %s\n", szName); printf("Order Quantity is %d\n", iOrderQuantity); printf("Unit cost is %lf\n", dUnitCost); printf("Gender is %c\n", cGender); Output: name is Bob Wire Order Quantity is 5 Unit cost is 7.250000 Gender is F printf("Unit printf("Unit printf("Unit printf("Unit printf("Unit Cost Cost Cost Cost Cost in in in in in 10.2 is '%10.2lf'\n", dUnitCost); 9.3 is '%9.3lf'\n", dUnitCost); .2 is '%.2lf'\n", dUnitCost); -10.2 is '%-10.2lf'\n", dUnitCost); -9.3 is '%-9.3lf'\n", dUnitCost); printf("Name '%10s' and '%-10s'\n", szName, szName); %.2lf %10s %-10s where this is the same as above, but it doesn't have a minWidth. where 10 is the minWidth and s is the code. The value will be right-justified. where - specifies to left-justify, 10 is the minWidth, and s is the code. The value will be left-justified. Redirecting output to a file In unix, you can specify where to direct the standard output by specifying command > filename You can also use fprintf which has an additional parameter for the file. fprintf(file, specification, value1, value2, …); The file should be opened with an fopen(). Output: Unit Cost in 10.2 is ' 7.25' Unit Cost in 9.3 is ' 7.250' Unit Cost in .2 is '7.25' Unit Cost in -10.2 is '7.25 ' Unit Cost in -9.3 is '7.250 ' Name ' Bob Wire' and 'Bob Wire ' // Suppose the executable is one.exe. On the command line, // we can specify where to direct the output when we run // the program. $ one.exe >myOutput.txt #define OUT_FILE "myoutFile.txt" FILE *fileOut; fileOut = fopen(OUT_FILE, "w"); if (fileOut == NULL) { fprintf(stderr, "Could not open %s\n", OUT_FILE); return 2; } fprintf(fileOut, "name is %s\n", szName); Exercise Given these variables: double dScoreM[20]; // scores on the exams int iScoreCount; // count of the scores in dScoreM. Show code which prints the contents of dScoreM, but only print 4 values per line. fgets and scanf These are used to receive input from standard input. fgets(stringVariable, maxLength, file); This reads from standard input until either maxLength - 1 characters are read or until a line feed character is encountered. scanf(formatString, address1, address2, …); char szStockNumber[21]; char *pszGetsResult; printf("Enter stock number:"); pszGetsResult = fgets(szStockNumber, 21, stdin); if (pszResult != NULL) { // do something } Facts about scanf(): must be passed addresses since it stores its results in those addresses returns the number of successful conversions from its input to those addresses null terminates resulting strings format codes are typically in the form: maxSize lengthModifier code char szStockNumber[21]; double dInventoryUnitPrice; int iStockCount; int iScanfCount; printf("Enter stockNumber unitPrice stockCount:\n"); iScanfCount = scanf("%20s %lf %d" , szStockNumber , &dInventoryUnitPrice , &iStockCount); printf("Stock Number = %s, Unit Price = %lf, Stock Count = %d\n" , szStockNumber , dInventoryUnitPrice , iStockCount); Warning!!! Warning !!! Warning !!! scanf() must be passed addresses. Numeric variables pass values instead of addresses. You must pass the address of numeric variables using an &. This is also necessary for single byte variables. Since the addresses of arrays are automatically passed, it isn't necessary to use the & with arrays. Some interesting scanf format codes: %[^,] reads everything but commas into the corresponding variable. This means that input terminates with a comma instead of a space. %20[^\n] reads everything but line feeds into the corresponding variable. This means that the input terminates with a line feed or \0. It places a maximum of 20 characters (plus a \0) in the variable. Functions In C, functions are used to modularize coding. A function has the following syntax: dataType functionName (parameterList) int iOrderQuantity; char szCustomerId[10]; iScanfCount = scanf("%s %d", szCustomerId, iOrderQuantity); Since iOrderQuantity is numeric, we must pass its address. In this example, we passed its value which scanf will attempt to use as an address. Depending on that value, it might have an addressing error or store a value at that questionable address. // sscanf gets its input from a variable (1st argument) char szLongAddress [] = "123 Dirt Rd\n"; long lStreetNumber; char szStreet[11]; iScanfCount = sscanf(szInputStr, "%ld %10[^\n]\n" , &lStreetNumber , szStreet); double calculateAverage(double dScoreM[], int iScoreCount) { int i; double dSum = 0.0; if (iScoreCount <= 0) return 0.0; for (i = 0; i < iScoreCount; i++) { dSum += dScoreM[i]; } return dSum / iScoreCount; { functionBodyStatements return expression; } If the function doesn’t functionally return a value, we specify void functionName (parameterList) { functionBodyStatements } } void showScores(char *pszName, double dScoreM[] , int iScoreCount) { int i; printf("Scores for %s\n", pszName); for (i = 0; i < iScoreCount; i++) { printf("%3f\n", dScoreM[i]); } } Parameter passing Numeric variables are passed by value. To pass the address, you must precede the variable name with a & reference operator. In the invoked function, the parameter must be declared to be pointer to receive the address. Arrays pass the address. Structures pass the value. This can hurt performance and is rarely necessary. To pass the address, you must precede the variable name with a &. In the invoked function, the parameter must be declared to be pointer to receive the address. // typedefs and prototypes typedef struct { char szName[31]; double dScoreM[50]; int iScoreCount; } StudentScores; void determineMinMax(StudentScores *pstudentScores, double *pdMin , double *pdMax); int main() { double dMinimum; double dMaximum; StudentScores studentOne = { "Marcus Absent" , { 40, 0, 30, 55, 0, 25 } , 6}; determineMinMax(&studentOne, &dMinimum, &dMaximum); return 0; } void determineMinMax(StudentScores *pstudentScores, double *pdMin, double *pdMax) { int i; *pdMin = 200.0; // arbitrary high value *pdMax = 0.0; for (i = 0; i < pstudentScores->iScoreCount; i++) { if (pstudentScores->dScoreM[i] < *pdMin) *pdMin = pstudentScores->dScoreM[i]; if (pstudentScores->dScoreM[i] > *pdMax) *pdMax = pstudentScores->dScoreM[i]; } } Exercise Create the function, calculateTotalCost, which is passed two arrays and a count of the number of entries. Does it need another parameter? dUnitPriceM[] - each entry represents a price per unit lOrderQuantiy[] - each entry represents an order quanity It should return the total cost which is the sum of the cost (unit price * order quantity) of all of the entries. Return that total cost as a parameter. Exercise Same as previous exercise, but return the total cost as the functional value. Storage Classifications auto Automatic local variables receive memory when their declaration is encountered at runtime. When the block of code containing an automatic variable is exited, their memory is freed. Automatic variables can be declared at the beginning of A function A compound statement enclosed in {} In C++ and Java, you can declare variables as you need them. Automatic variables are allocated and freed from the top of void funcA () { int iVar1; double dVar2; statements1 if (iVar1 > 5) { char szString1[50]; int iLength; statements2 } statements3 } // auto allocation of iVar1 // auto allocation of dVar2 // auto allocation of szString1 // auto allocation of iLength // frees szString1 and iLength // frees iVar1 and dVar2 the runtime memory stack. Storage Classifications extern Extern global variables receive their memory when the program is loaded. Global variables declared at the top of a file (before function definitions) are a type of external global variable. Multiple C files can reference the same external global variables. One declaration must not contain the keyword extern. It is considered the basis declaration. The basis declaration can contain initializations. All references to the extern variable other than the single basis declaration must use the extern keyword. Memory is freed when the program exits. Storage Classifications static Static variables retain their values throughout execution. Additionally, static variables are private to the C function or C file where they are declared. They do not conflict with extern variables in other files. Note that static variables at the beginning of a file are also global. Storage Classifications dynamic Pointer variables must be automatic, extern or static; however, what they reference can be allocated memory dynamically via malloc() or calloc(). A pointer also can be contained within dynamically allocated memory. Memory is allocated from a heap. /* file partA.c */ double dVectorM[300] = {100, 200, 150, 300, 20, 5, 100, 30}; int iVectorLength = 8; /* functions in partA.c */ /* file partB.c */ extern double dVectorM[300]; extern int iVectorLength; /* functions in partB.c */ /* file partC.c */ extern double dVectorM[300]; extern int iVectorLength; /* file partD.c */ static dVectorM[200] = {50, 60, 80}; file static int iVectorLength = 3; file //basis // basis // extern reference // extern reference // extern reference // extern reference // static global to // static global to void myFunc() { static int iRetainValue; // static local to myFunc int iAutoValue; // automatic local to myFunc statements; } // iAutoValue is freed, iRetainValue is not freed typedef struct { char szName[30]; double dScore[50]; int iScoreCount; } StudentScores; StudentScores *pstudentScores; Explicitly allocated with a malloc() or calloc() Must be freed explicitly using a free(). Helpful for managing objects where the size varies If memory is not managed well, memory leaks can occur. Exercise Consider the code for files exA.c and exB.c. /* file exB.c */ extern int iMyVar; static int iMyVar2; void bfunc() { int iHey; /* statements */ } Variable iMyVar in exA.c iMyVar2 in exA.c iParm in exA.c (myFunc) iHello in exA.c iElectricity in exA.c iHey in exA.c iKite in exA.c iMyVar in exB.c iMyVar2 in exB.c iHey in exB.c When does it get allocated? pstudentScores = malloc(sizeof(StudentScores)); … free(pstudentScores); /* file exA.c */ int iMyVar; int iMyVar2; void myFunc (int iParm) { int iHello; static int iElectricity; /* statements */ } int anotherFunc () { int iHey; static int iKite; /* statements */ } When is it freed? Same as another variable? ones? Which