Evan Weaver (School of Computer Studies, Seneca College of Applied Arts and Technology, July 2006). Foundations of Programming Using C.

LEARNING MATERIAL

Textbook: https://www.mediafire.com/file/yebbvjfl4pna2k4/Foundations-of-Programming-Using-C.pdf/file

LAB

Bài 1 Viết chương trình nhập số quả táo và giá mỗi quá, sau đó in ra tổng tiền tương ứng. image

Chú ý về định dạng dữ liệu trong C (dùng cho cả input và output):

  • %c : Ký tự đơn
  • %s : Chuỗi
  • %d : Số nguyên hệ 10 có dấu
  • %f : Số chấm động (VD 5.54 khi in sẽ ra 5.540000)
  • %e : Số chấm động (ký hiệu có số mũ)
  • %g : Số chấm động (VD 5.54 khi in sẽ in ra 5.54)
  • %x : Số nguyên hex không dấu (hệ 16)
  • %o : Số nguyên bát phân không dấu (hệ 8)
  • l : Tiền tố dùng kèm với %d, %x, %o để chỉ số nguyên dài (ví dụ%ld)
  • %5c : Xuất ký tự có bề rộng 5
  • %5d : Số nguyên có bề rộng 5
  • %20s : Xuất chuỗi có bề rộng 20
  • %5.3f : Xuất số thực có bề rộng 5 trong đó có 3 số sau dấu phẩy
  • %-5d : Số nguyên có bề rộng 5 nhưng căn lề trái

In dữ liệu ra với hàm printf:

1
2
3
printf("Tong cua %d va %f la %f \n", a, b, a+b);
int ngay = 8, thang = 8, nam = 1982;
printf("SN: %02d/%02d/%04d\n", ngay, thang, nam); // 08/08/1982

Bài 2 Viết chương trình nhập số quả táo sẽ mua và giá mỗi quá, sau đó in ra tổng tiền phải trả theo quy tắc sau:

  • Tiền thuế (tax) là 10% tổng tiền;
  • Sẽ được giảm 10% tổng tiền (trước khi tính thuế) nếu tổng tiền vượt quá 100$; image

Có hai cách tính số tiền được giảm:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Cách 1:
if (total > 100)
  total = 100 + (total - 100) * 0.9;
tax = 0.1 * total;
printf("That will cost ($%.2lf plus $%.2lf tax): $%.2lf\n", total, tax, total + tax);
// Cách 2:
if (total > 100) {
  discount = (total - 100) * 0.1;
  total = total - discount;
  printf("Receiving a discount of $%.2lf\n", discount);
}
tax = 0.1 * total;
printf("That will cost ($%.2lf plus $%.2lf tax): $%.2lf\n", total, tax, total + tax);

Câu hỏi thêm: Sửa chương trình đã viết để nếu tổng tiền (trước thuế) lớn hơn 1000 thì được chiết khấu 2 lần so với khi tổng tiền lớn hơn 100. Tìm chỗ thích hợp cho đoạn code dưới đây:

1
2
if (total > 1000)
  discount = 2 * discount;

Bài 3 Tự làm bài như đoạn code sau để hiểu về cách sử dụng vòng lặp while, sau đó trả lời câu hỏi thêm bên dưới:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

int main() {
	int guess;
	printf("Guess a number: ");
	scanf("%d", &guess);
	while (guess != 42) {
		if (guess > 42)
		printf("Too high! ");
		if (guess < 42)
		printf("Too low! ");
		printf("Try again: ");
		scanf("%d", &guess);
	}
	printf("You guessed the magic number! YOU WIN!!\n");
}

image

Câu hỏi 1: sửa code để chương trình đưa ra thông báo như sau:

  • Nếu chỉ đoán một lần đã đúng thì: WOW! Either you are very lucky or you CHEAT!
  • Còn lại đưa ra tổng số lần đã đoán, ví dụ nếu đoán 8 lần: You win in 8 tries.

Câu hỏi 2: sửa code để chương trình đưa ra thông báo về số lần thử đoán:

  • Nếu chỉ đoán một lần đã đúng: WOW! Either you are very lucky or you CHEAT!
  • Nếu đoán từ 2 đến dưới 5 lần, ví dụ 3 lần: Very good. You got it in 3 turns.
  • Nếu từ 5 đến dưới 15 lần, ví dụ 8 lần: You win in 8 turns, an average performance
  • Nếu từ 15 đến dưới 100 lần, ví dụ 99 lần: Duh, it took you 99 tries to get it!
  • Nếu đoán 100 lần mới đúng: You got it in the nick of time
  • Nếu đoán sai: You lose

Bài 4 Viết chương trình nhập một số là điểm theo thang điểm 10 rồi in ra điểm vừa nhập, tham khảo code bên dưới:

1
2
3
4
5
6
7
8
printf("Enter your mark: ");
scanf("%d", &mark);
while (mark < 0 || mark > 10) {
	printf("Mark must be between 0 and 10 \n");
	printf("Enter your mark: ");
	scanf("%d", &mark);
}
printf("Your mark is: %d \n", mark);

Câu hỏi 1: Đoạn code trên đang dùng 2 lần các lệnh printf("Enter your mark: "); scanf("%d", &mark);, hãy sửa lại để có thể rút ngắn bớt.

Câu hỏi 2: Khác với lệnh while sẽ kiểm tra điều kiện trước rồi mới có/không thực thi nội dung bên trong, lệnh do/while sẽ thực hiện nội dung bên trong (các statement) rồi mới kiểm tra điều kiện để quyết định có lặp lại hay không theo cú pháp chung như bên dưới. Hay áp dụng do/while thay cho while với bài tập ở trên.

1
2
3
do
// các câu lệnh-statements
while (/* các điều kiện-conditions */); // chú ý luôn có dấu ";" ở cuối

Câu hỏi 3: Áp dụng luật DeMorgan (DeMorgan’s Law) để sửa lại điều kiện (mark < 0 || mark > 10).

Bài 5 Ví dụ về cấu trúc lặp for có dạng for (initial; condition; trailing) { conditions }:

1
2
3
4
5
6
7
8
int n, sum, counter;
sum = 0;
for (counter=1; counter<=10; counter=counter+1) {
  printf("Please enter number %d: ", counter);
  scanf("%d", &n);
  sum = sum + n;
}
printf("The sum of those numbers is %d\n", sum); // tổng 10 số do người dùng nhập

Câu hỏi 1: Hiển thị lập phương (mũ 3) của các số trong khoảng 1 đến n do người dùng nhập.

Input number of terms: 5
Number is: 1 and cube of the 1 is: 1
Number is: 2 and cube of the 2 is: 8
Number is: 3 and cube of the 3 is: 27
Number is: 4 and cube of the 4 is: 64
Number is: 5 and cube of the 5 is: 125

Câu hỏi 2: Hiển thị bảng cửu chương của số n do người dùng nhập.

Input the number (Table to be calculated): 12
12 x 1 = 12
12 x 2 = 24
12 x 3 = 36
...
12 x 10 = 120

Câu hỏi 3: Hiển thị bảng cửu chương của các số 1-n do người dùng nhập, ví dụ n=10. image

Câu hỏi 4: Viết chương trình hiển thị n số chẵn, số lẻ đầu tiên, n do người dùng nhập. image

Bài 6 Ví dụ về cấu trúc điều kiện switch có dạng như sau:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
switch (expression) {
  case constant1:
    zero or more statements
  case constant2:
    zero or more statements
  //...and so on (as many cases as you like)...
  default:
    zero or more statements
}
int a=4, b=6; float c=4.5; char ch1='a', ch2='c';
switch((a * b) % 2)  //valid
switch(c)            //invalid
switch(ch2 + ch1)    //valid
/*
Note:
- switch() can only contain char and int.
- break is used to exit from switch statement, it is optional.
- switch case can be without default case.
*/

Câu hỏi 1: Nhập tháng ở dạng số rồi in ra tên tháng (tiếng Anh).

Nhap thang: 8 
August

Câu hỏi 2: Nhập tháng ở dạng số và in ra số ngày của tháng đó.

Nhap thang: 1
Thang 1 co 31 ngay.

Choosing Appropriate Control Structures

The formal term for statements executing one after the other is sequence. To be complete, a programming language needs two ways to modify the normal sequence of statements: selection, the ability to select which statements will execute based on current conditions, and iteration, the ability to repeat a sequence of statements as much as necessary.

We have seen that C has three ways of performing selection (if, if/else and switch) and three ways of performing iteration (while, do/while and for). If you find the choices daunting, keep in mind that the basic if and while statements are all you really need. We will attempt to use the most suitable control statements in our examples from now on. However, it is not wrong to use different ones from the one we will use. If in doubt, always use a simple if for selection and a simple while for looping. As you see more examples and do more programming, you will start to use the others more and more. Eventually you will become comfortable at choosing an appropriate control structure for your programs.

By using nested if/else also we can also create multiple blocks whenever required, but for creating “n” no of blocks we are required to create “n-1” conditions. In the switch statement, we can create multiple blocks under a single condition that reduces the coding part. When we are working with nested if/else at even any point of time among those all blocks only one block gets executed. But in the switch statement, we can create more than one block depending on the requirement by removing the break statement between the blocks.


Bài 7 We want a program that will repeatedly ask the user for a principal amount, an annual interest rate and the number of years to invest, and shows what the value of the investment will be at the end. We will use a function, called invest, which is given the principal, the rate and the number of years:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
double invest (double principal, double rate, int time) {
	int i;
	for (i=1; i<=time; i=i+1)
		principal = principal*(1 + rate/100);
	return principal;
}

int main() {
	double start, end, rate;
	int years;
	
	printf("Enter an investment amount (or 0 to stop): ");
	scanf("%lf", &start);
	while (start > 0) {
		printf("Enter rate (e.g. 10.5 for 10.5%% per annum): ");
		scanf("%lf", &rate);
		printf("Enter year to invest: ");
		scanf("%d", &years);
		end = invest(start, rate, years);
		printf("%.2lf invested at %.1lf%% for %d years: %.2lf \n", start, rate, years, end);
		printf("Enter another investment amount (0 to stop): ");
		scanf("%lf", &start);
	}
	
	double percent;
	printf("Comparison of 5-year returns at different rates \n");
	printf("	Rate		Profit (per Thousand $) \n");
	printf("	----		------------------------\n");
	for (percent=3.5; percent<9.6; percent=percent+0.5)
		printf("	%.2lf		%.2lf \n", percent, invest(1000.0, percent, 5)-1000);
	
	system("PAUSE");
	return 0;

}

Putting it all together

A typical program organization is to have the following pieces, written in the following order:

  1. A comment describing what the program does, and who wrote it.
  2. #include directives for all functions from the standard library (or from other function libraries that may have been purchased or written).
  3. function prototypes for all functions, other than main, that appear in the source file.
  4. the definition of the main function.
  5. the definition of the remaining functions, each preceded by a comment.

To apply all these guidelines to the last program, we would get something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/************************************************
* Display a table of investment profits over a *
* 5-year period, using different interest rates *
* Written by: Evan Weaver October 9, 1996 *
************************************************/
#include <stdio.h>
double invest(double principal, double rate, int time);
main()
{
	double percent;
	printf("Comparison of 5-year returns at different rates\n");
	printf(" Rate Profit (per Thousand $)\n");
	printf(" ---- -----------------------\n");
	for (percent = 3.5; percent < 9.6; percent = percent + 0.5)
		printf(" %.2lf %.2lf\n", percent,
		invest(1000.0, percent, 5) - 1000);
}
/* returns the future value of "principal", if it is
* invested for "time" compounding periods, at an
* interest rate of "rate" percent per period.
*/
double invest(double principal, double rate, int time)
{
	int i;
	for (i = 1; i <= time; i = i + 1)
		principal = principal * (1 + rate/100);
	return principal;
}

Bài 8 Suppose that we wanted to write a function, called hours_and_minutes, that would break a number of minutes (say, 147) into hours and minutes (2 hours and 27 minutes in this case). Here, we would want to pass the function an int (the total minutes), and have it send back two ints (the hours and the minutes left over). We might be tempted to write something like: int hours_and_minutes(int total, int minutes_left), where the function sends back the hours as a return value and passes an extra variable to hold the minutes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main()
{
	int tmin, hr, min;
	printf("Enter total minutes for videotape: ");
	scanf("%d", tmin);
	hr = hours_and_minutes(tmin, min);
	printf("That is %d hours and %d minutes\n", hr, min);
	return 0;
}

int hours_and_minutes(int total, int minutes_left)
{
	minutes_left = total % 60;
	return total/60;
}

The problem with this is that the parameters, total and minutes_left, are copies of the actual function arguments. When this function changes minutes_left, it is changing a copy of the second argument, not the argument itself. When the function returns the number of hours, the two parameter variables, which are local to the function, simply disappear.

As confusing as pointers may seem, the general rule is really quite simple: if a function is going to change a variable, it needs to be passed the address of that variable. The calling function supplies an address as an argument (usually by using &) and the function itself resolves its corresponding pointer parameter by putting * before every occurrence of that parameter, including its declaration in the function header line.

Bài 9 (Passing arrays to functions) There is one instance in which the name of an array is used without brackets, and that is when passing an array to a function. When one function wants to pass an array to another function, it simply uses the name of the array as the argument. The function that is receiving the array declares the corresponding parameter as an array, except that it doesn’t need to specify a size for the array (since the size was specified when the originating array was defined).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#define SIZE 5

void fill(int x[]);
void reverse(int x[]);

main()
{
	int nums[SIZE];
	printf("Enter %d numbers: ", SIZE);
	fill(nums);
	printf("Thank you.\n");
	printf("In reverse order, those numbers are:\n");
	reverse(nums);
}
	
void fill(int x[])
{
	int i;
	for(i = 0; i < SIZE; i++)
	scanf("%d", &x[i]);
}
			    
void reverse(int x[])
{
	int i;
	for (i = SIZE - 1; i >= 0; i--)
	printf("%d ", x[i]);
	printf("\n");
}

In this program, notice that even though it looks like the function, fill, is receiving a copy of the nums array, it is not. The name of an array, without any brackets after it, is really the address of the start of the array, and the parameter declaration int x[] in fill’s header line makes x (which is a pointer to some array) point to num. Because of these mechanics concerning the passing of arrays, any time a function is passed an array, that function has the opportunity to change the originating array. While this may sound dangerous, it is done for efficiency reasons: passing an address doesn’t involve much data transfer, whereas making a copy of a large array would consume memory and the time of the CPU to make the copy.

Bài 10 (Passing strings to functions) A string is a sequence of characters enclosed in double quotes, or strings are saved in arrays so, to pass an one dimensional array to a function we will have the following declaration: returnType functionName(char str1[], char str2[],...);, for example void displayString(char str[]);, strings can be passed to a function in a similar way as arrays.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void displayString(char str[]);

main()
{
    char str[50];
    printf("Enter string: ");
    fgets(str, sizeof(str), stdin);             
    displayString(str);  
    return 0;
}
void displayString(char str[])
{
    printf("String Output: ");
    puts(str);
}

Commonly Used String Functions:

  • strlen(str) calculates the length of a string
  • strcmp(str1, str2) compares two strings, return 0 if strings are equal, 1 if the first non-matching character in str1 is greater (in ASCII) than that of str2, -1 if the first non-matching character in str1 is lower (in ASCII) than that of str2.
  • strcpy(destination, source) copies a string to another: copies the source (including the null character) to the destination and returns the copied string.
  • strcat(destination, source) concatenates two strings: the destination string and the source string, and the result is stored in the destination string.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
char a[20] = "Program";
char b[20] = {'P','r','o','g','r','a','m','\0'};
char c[20];
char d[20] = "Process";
int x, y;

printf("Length of string a = %d \n",strlen(a)); // 7
printf("Length of string b = %d \n",strlen(b)); // 7
strcpy(c, b);
puts(c); // Program
printf("strcmp(a, b) = %d\n", strcmp(a,b)); // 0
printf("strcmp(a, d) = %d\n", strcmp(a,d)); // 1
printf("strcmp(d, a) = %d\n", strcmp(d,a)); // -1
strcat(a, b);
puts(a); // ProgramProgram
puts(b); // Program

Bài 11 Ghi và đọc file với fprintf() và fscanf().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
FILE *fp;
fp = fopen("test.txt", "w");
if (fp == NULL) {
	printf("Failed to open file!\n");
}
else {
	fprintf(fp, "%s", "C\n");
	fprintf(fp, "%s", "Java\n");
	fprintf(fp, "%s", "Python\n");
}
fclose(fp);
char n[25];
fp = fopen("test.txt", "r");
fscanf(fp, "%s", &n);
printf("%s\n", n);
fscanf(fp, "%s", &n);
printf("%s\n", n);
fscanf(fp, "%s", &n);
printf("%s\n", n);
fclose(fp);

Câu hỏi 1: Sửa code mẫu ở trên, dùng các hàm fputs(char[], FILE)fgets(char[], sizeof(char[]), FILE) để ghi và đọc dữ liệu từ file.

Câu hỏi 2: Biết rằng hàm fgets() trả về NULL khi đọc hết dữ liệu, sử dụng vòng lặp để đọc file với hàm này.

Câu hỏi 3: Biết rằng hàm fgetc(FILE) trả về từng ký tự đọc được từ FILE và hàm putchar(char) sẽ in từng ký tự đọc được ra màn hình, sửa code ở các phần trên để đọc dữ liệu từ một file với các hàm này. Chú ý rằng khi đọc đến cuối file thì ký tự fgetc() nhận được là EOF.