Showing posts with label C. Show all posts
C - Linked List
1. Khái niệm
Danh sách liên kết là một trong các cấu trúc dữ liệu quan trọng bên cạnh các cấu trúc dữ liệu khác như stack, queue, ... trong C/C++.
Bài toán đặt ra là làm thế nào để quản lý danh sách các bài hát trong một usb khi ta không thể biết trước được chúng có số lượng bao nhiêu, điều này không thể làm được khi ta dùng Array, dưới đây là một vài so sánh giữa Array và Linked-List để bạn đọc hiểu được thế nào là Linked-List:
Danh sách liên kết là một trong các cấu trúc dữ liệu quan trọng bên cạnh các cấu trúc dữ liệu khác như stack, queue, ... trong C/C++.
Bài toán đặt ra là làm thế nào để quản lý danh sách các bài hát trong một usb khi ta không thể biết trước được chúng có số lượng bao nhiêu, điều này không thể làm được khi ta dùng Array, dưới đây là một vài so sánh giữa Array và Linked-List để bạn đọc hiểu được thế nào là Linked-List:
Array
+ Kích thước mảng cố định, nếu số lượng bài hát nhỏ hơn số lượng phần tử của mảng thì lãng phí bộ nhớ, nếu lớn hơn thì lại thiếu nên không quản lý hết được số bài hát
+ Việc add/remove/sort(sắp xếp)/search ... các bài hát trong mảng là vô cùng khó khăn
Linked List
+ Kích thước Linked-List không cố định, được thay đổi dễ dàng theo thời gian, vì thế có bao nhiêu bài hát thì ta chỉ cần cấp phát bấy nhiêu phần tử trong Linked-List, điều này giúp quản lý tối ưu bộ nhớ
+ Mỗi phần tử được đặc trưng bởi cấu trúc sau:
typedef struct slist{
void * data; /**< the pointer to data */
struct slist * next; /**< the pointer to next element of list */
}slist;
Chính nhờ trường next trong struct này đã tạo ra sợi dây liên kết động giữ các phần tử trong Linked-List
+ Dễ dàng add/remove/sort/search ... các phần tử dựa vào sợi dây liên kết next
2. Các API sử dụng với Linked-List
Các thao tác đối với Linked-List bao gồm các loại cơ bản sau:
+ Add
+ Remove/delete
+ Duyệt danh sách
+ Sort
+ Search
Bạn đọc có thể dùng các API được viết sẵn để dễ dàng sử dụng tại đây. Source code được cung cấp trong 2 file là slist.c và slist.h, muốn sử dụng ở đâu chỉ cần #include "slist.h" là được.
Dưới đây là ví dụ đơn giản mô tả các thao tác trên qua bài toán quản lý danh sách bài hát.
main.c
#include <stdio.h>
#include <string.h>
#include "slist.h"
typedef struct music_item_t{
char *name;
char *path; /* location in storage */
int id; /* 5digit, like karaoke */
}music_item_t;
bool find_by_id(void * cp_data, void * slist_data){
int *cp = (int *)cp_data;
music_item_t *song = (music_item_t *)slist_data;
if(*cp == song->id)
return true;
else
return false;
}
bool find_by_name(void * cp_data, void * slist_data){
char *cpname = (char*)cp_data;
music_item_t *song = (music_item_t *)slist_data;
return !strcmp(cpname, song->name);
}
void release_song_list(void *data){
music_item_t *song = (music_item_t *)data;
free(song->name); //strdup
free(song->path); //strdup
free(song); //malloc
}
void add_song_list(slist **song_list){
slist *music_list = NULL;
music_item_t *tmp = NULL;
tmp = (music_item_t *)malloc(sizeof(music_item_t));
tmp->name = strdup("Mai mai mot tinh yeu");
tmp->path = strdup("/run/media/MYUSB/music/mai-mai-mot-tinh-yeu.mp3");
tmp->id = 00001;
music_list = slist_append(music_list, (void*)tmp);
tmp = (music_item_t *)malloc(sizeof(music_item_t));
tmp->name = strdup("Hay nam chat tay anh nhe");
tmp->path = strdup("/run/media/MYUSB/music/hay-nam-chat-tay-anh-nhe.mp3");
tmp->id = 00002;
music_list = slist_append(music_list, (void*)tmp);
tmp = (music_item_t *)malloc(sizeof(music_item_t));
tmp->name = strdup("Co khi nao roi xa");
tmp->path = strdup("/run/media/MYUSB/music/co-khi-nao-roi-xa.mp3");
tmp->id = 00003;
music_list = slist_append(music_list, (void*)tmp);
*song_list = music_list;
}
int main (void)
{
int length=0;
slist *music_list = NULL; //NOTE: always equas NULL at declare
slist *walk=NULL;
add_song_list(&music_list);
/* LENGTH */
length = slist_size(music_list);
printf("Number of song: %d \n", length);
/* DUYET DANH SACH */
walk = music_list;
while(walk){
printf("song name: %s \n", FORCE_DATA_TYPE(music_item_t, walk)->name);
walk=walk->next;
}
/* FIND THEO ID */
int id = 1;
walk = slist_find(music_list, (void*)&id, find_by_id);
if(walk){
printf("found song id<%d>: %s \n", id, FORCE_DATA_TYPE(music_item_t, walk)->name);
}
/* FIND THEO TEN BAI HAT */
char *name = "Co khi nao roi xa";
walk = slist_find(music_list, (void*)name, find_by_name);
if(walk){
printf("found song name: %s \n", FORCE_DATA_TYPE(music_item_t, walk)->name);
}
/* XOA TAT CA LINKED LIST, LUU Y NHO FREE LUON DATA*/
music_list = slist_delete_all_x(music_list, release_song_list);
/* LENGTH */
length = slist_size(music_list);
printf("Number of song: %d \n", length);
return 0;
}
Compile & Execute:
$ gcc slist.h slist.c main.c
$ ./a.out
Number of song: 3
song name: Mai mai mot tinh yeu
song name: Hay nam chat tay anh nhe
song name: Co khi nao roi xa
found song id<1>: Mai mai mot tinh yeu
found song name: Co khi nao roi xa
Number of song: 0
C - C Library
Có rất nhiều hàm trong các thư viện C chuẩn mà coder cần biết để sử dụng
Xem chi tiết C Library. Chú ý các thư viện dưới đây.
+ C library to perform Input/Output operations (stdio.h)
+ C Standard General Utilities Library (stdlib.h)
+ C Strings (string.h)
+ C numerics library (math.h)
+ C Time Library (time.h)
+ C Diagnostics Library (assert.h)
+ C library to handle signals (signal.h)
Xem chi tiết C Library. Chú ý các thư viện dưới đây.
+ C library to perform Input/Output operations (stdio.h)
+ C Standard General Utilities Library (stdlib.h)
+ C Strings (string.h)
+ C numerics library (math.h)
+ C Time Library (time.h)
+ C Diagnostics Library (assert.h)
+ C library to handle signals (signal.h)
C - Recursion
Recursion - Đệ quy là quá trình mà một hàm gọi chính nó, nghe khá trừu tượng và khó hiểu. Trong hàm đệ quy luôn phải có một biến điều kiện để đến một thời điểm nào đó không thực hiện gọi chính nó nữa, nếu không sẽ rơi vào trang thái tắc nghẽn trong hàm và không thể thoát được hàm dẫn đến chương trình bị treo hoặc crass. Để cho dễ hiểu khái niệm đệ quy, ta cứ giả sử rằng có nhiều hàm có tên khác nhau nhưng trong ruột thực hiện các phép toán giống nhau, và việc đệ quy tương tự hàm này gọi hàm kia thành một chuỗi liên tục, nói cách khác đệ quy là gọi các hàm con có tên trùng với tên hàm cha.
Đệ quy thường được dùng cho các bài toán có tính lặp lại một việc, ví dự bài toán scan thư mục trong máy tính: Ban đầu scan thư mục gốc A nào đó, trong lúc scan gặp một thư mục con là B, lại scan thư mục con B đó, mà việc scan thu muc B ta phải làm các thủ tục y nhu khi scan thu muc A, điều đó dẫn đến việc ta gọi hàm đệ quy khi gặp thư mục B. Việc scan thư mục A tạm dùng vì chương trình nhảy vào chương trình con đệ quy để scan thư mục B, ví dụ trong lúc scan B gặp thì có một thư mục C là con của B, tiến hành gọi đệ quy. Sau khi scan hết thư mục C thì return về để tiếp tục scan B, scan xong B thì return lại A, quá trình đó cứ lặp đi lặp lại cho đến khi nào scan hế tất cả các thư mục trong thư mục gốc A thì hoàn thành để return về chương thình chính.
Một ví dụ nữa của hàm đệ quy là duyệt các node trong file xml cũng tương tự nhu scan thư mục.
Ex:
recursion.c (note: vd chi ap dung cho in = 3 thoi nhe)
Compile & Execute:
void recursion()
{
if(Expressions){
recursion(); /* function calls itself */
}
}
int main()
{
recursion();
}
Đệ quy thường được dùng cho các bài toán có tính lặp lại một việc, ví dự bài toán scan thư mục trong máy tính: Ban đầu scan thư mục gốc A nào đó, trong lúc scan gặp một thư mục con là B, lại scan thư mục con B đó, mà việc scan thu muc B ta phải làm các thủ tục y nhu khi scan thu muc A, điều đó dẫn đến việc ta gọi hàm đệ quy khi gặp thư mục B. Việc scan thư mục A tạm dùng vì chương trình nhảy vào chương trình con đệ quy để scan thư mục B, ví dụ trong lúc scan B gặp thì có một thư mục C là con của B, tiến hành gọi đệ quy. Sau khi scan hết thư mục C thì return về để tiếp tục scan B, scan xong B thì return lại A, quá trình đó cứ lặp đi lặp lại cho đến khi nào scan hế tất cả các thư mục trong thư mục gốc A thì hoàn thành để return về chương thình chính.
Một ví dụ nữa của hàm đệ quy là duyệt các node trong file xml cũng tương tự nhu scan thư mục.
Ex:
recursion.c (note: vd chi ap dung cho in = 3 thoi nhe)
#include <stdio.h>
#include <string.h>
void recursion(int in){
printf("in = %d \n", in);
if(in > 0){
in--;
recursion(in);
}else{
//exit
}
}
void recursion_deep3(int in){
printf("in = %d \n", in);
if(in > 0){
in--;
}else{
//exit
}
}
void recursion_deep2(int in){
printf("in = %d \n", in);
if(in > 0){
in--;
recursion_deep3(in);
}else{
//exit
}
}
void recursion_deep1(int in){
printf("in = %d \n", in);
if(in > 0){
in--;
recursion_deep2(in);
}else{
//exit
}
}
void recursion_deep(int in){
printf("in = %d \n", in);
if(in > 0){
in--;
recursion_deep1(in);
}else{
//exit
}
}
int main()
{
recursion(3); //de qu
printf("\n");
recursion_deep(3); //tuong tu nhu viec goi mot chuoi cac ham con
return 0;
}
Compile & Execute:
$ gcc recursion.c
$ ./a.out
in = 3
in = 2
in = 1
in = 0
in = 3
in = 2
in = 1
in = 0
C - Error Handling
As such C programming does not provide direct support for error handling but being a system programming language, it provides you access at lower level in the form of return values. Most of the C or even Unix function calls return -1 or NULL in case of any error and sets an error code errno is set which is global variable and indicates an error occurred during any function call. You can find various error codes defined in header file.
So a C programmer can check the returned values and can take appropriate action depending on the return value. As a good practice, developer should set errno to 0 at the time of initialization of the program. A value of 0 indicates that there is no error in the program.
The errno, perror() and strerror()
The C programming language provides perror() and strerror() functions which can be used to display the text message associated with errno.
- The perror() function displays the string you pass to it, followed by a colon, a space, and then the textual representation of the current errno value.
- The strerror() function, which returns a pointer to the textual representation of the current errno value.
Let's try to simulate an error condition and try to open a file which does not exist. Here I'm using both the functions to show the usage, but you can use one or more ways of printing your errors. Second important point to note is that you should use stderr file stream to output all the errors.
#include#include #include extern int errno ; int main () { FILE * pf; int errnum; pf = fopen ("unexist.txt", "rb"); if (pf == NULL) { errnum = errno; fprintf(stderr, "Value of errno: %d\n", errno); perror("Error printed by perror"); fprintf(stderr, "Error opening file: %s\n", strerror( errnum )); } else { fclose (pf); } return 0; }
When the above code is compiled and executed, it produces the following result:
Value of errno: 2 Error printed by perror: No such file or directory Error opening file: No such file or directory
Divide by zero errors
It is a common problem that at the time of dividing any number, programmers do not check if a divisor is zero and finally it creates a runtime error.
The code below fixes this by checking if the divisor is zero before dividing:
#include#include main() { int dividend = 20; int divisor = 0; int quotient; if( divisor == 0){ fprintf(stderr, "Division by zero! Exiting...\n"); exit(-1); } quotient = dividend / divisor; fprintf(stderr, "Value of quotient : %d\n", quotient ); exit(0); }
When the above code is compiled and executed, it produces the following result:
Division by zero! Exiting...
Program Exit Status
It is a common practice to exit with a value of EXIT_SUCCESS in case of programming is coming out after a successful operation. Here, EXIT_SUCCESS is a macro and it is defined as 0.
If you have an error condition in your program and you are coming out then you should exit with a status EXIT_FAILURE which is defined as -1. So let's write above program as follows:
#include#include main() { int dividend = 20; int divisor = 5; int quotient; if( divisor == 0){ fprintf(stderr, "Division by zero! Exiting...\n"); exit(EXIT_FAILURE); } quotient = dividend / divisor; fprintf(stderr, "Value of quotient : %d\n", quotient ); exit(EXIT_SUCCESS); }
When the above code is compiled and executed, it produces the following result:
Value of quotient : 4
Reference to this.
Tag :
C
C - Type Casting
1. Type Casting
Ép kiểu là hành động chuyển đổi kiểu dữ liệu của biến sang một kiểu dữ liệu khác cho phù hợp với một phép toán hay trường hợp nào đó.
Cast operator:
Ex:
type-casting.c
Compile & Execute:
Rõ ràng trong những trường hợp như thế này thì cần phải có type casting thì mời nhận được kết quả đúng.
2. Phân cấp ép kiểu
Ex:
type-casting-pointer.c
Compile & Execute:
Ép kiểu là hành động chuyển đổi kiểu dữ liệu của biến sang một kiểu dữ liệu khác cho phù hợp với một phép toán hay trường hợp nào đó.
Cast operator:
(type_name) expression
Ex:
type-casting.c
#include <stdio.h>
int main()
{
int sum;
int count;
float avg;
sum = 125;
count = 50;
avg = sum / count;
printf("<LINE%d> sum: %d, count: %d, avg: %f \n", __LINE__, sum, count, avg);
avg = (float)sum / count;
printf("<LINE%d> sum: %d, count: %d, avg: %f \n", __LINE__, sum, count, avg);
return 0;
}
Compile & Execute:
$ gcc type-casting.c
$ ./a.out
<LINE17> sum: 125, count: 50, avg: 2.000000
<LINE21> sum: 125, count: 50, avg: 2.500000
Rõ ràng trong những trường hợp như thế này thì cần phải có type casting thì mời nhận được kết quả đúng.
2. Phân cấp ép kiểu
3. Type Casting Pointer
Ép kiểu với pointer cũng tương tự như với các kiểu thông thường.
type-casting-pointer.c
#include <stdio.h>
enum {
TYPE_CHAR,
TYPE_INT,
TYPE_FLOAT
};
void print_info(int type, void *data){
switch (type) {
case TYPE_CHAR:
printf("data char: %s \n", (char*)data);
break;
case TYPE_INT:
printf("data int: %d \n", *((int*)data));
break;
case TYPE_FLOAT:
printf("data float: %f \n", *((float*)data));
break;
default:
break;
}
}
int main()
{
char name[32] = "chicken";
int age = 5;
float weight = 2.5f;
print_info(TYPE_CHAR, (void*)name);
print_info(TYPE_INT, (void*)&age);
print_info(TYPE_FLOAT, (void*)&weight);
return 0;
}
Compile & Execute:
$ gcc type-casting-pointer.c
$ ./a.out
data char: chicken
data int: 5
data float: 2.500000
Tag :
C
C - Header Files
A header file is a file with extension .h which contains C function declarations and macro definitions and to be shared between several source files. There are two types of header files: the files that the programmer writes and the files that come with your compiler.
You request the use of a header file in your program by including it, with the C preprocessing directive #include like you have seen inclusion of stdio.h header file, which comes along with your compiler.
Including a header file is equal to copying the content of the header file but we do not do it because it will be very much error-prone and it is not a good idea to copy the content of header file in the source files, specially if we have multiple source file comprising our program.
A simple practice in C or C++ programs is that we keep all the constants, macros, system wide global variables, and function prototypes in header files and include that header file wherever it is required.
Include Syntax
Both user and system header files are included using the preprocessing directive #include. It has following two forms:
#include
This form is used for system header files. It searches for a file named file in a standard list of system directories. You can prepend directories to this list with the -I option while compiling your source code.
#include "file"
This form is used for header files of your own program. It searches for a file named file in the directory containing the current file. You can prepend directories to this list with the -I option while compiling your source code.
Include Operation
The #include directive works by directing the C preprocessor to scan the specified file as input before continuing with the rest of the current source file. The output from the preprocessor contains the output already generated, followed by the output resulting from the included file, followed by the output that comes from the text after the #include directive. For example, if you have a header file header.h as follows:
char *test (void);
and a main program called program.c that uses the header file, like this:
int x; #include "header.h" int main (void) { puts (test ()); }
the compiler will see the same token stream as it would if program.c read
int x; char *test (void); int main (void) { puts (test ()); }
Once-Only Headers
If a header file happens to be included twice, the compiler will process its contents twice and will result an error. The standard way to prevent this is to enclose the entire real contents of the file in a conditional, like this:
#ifndef HEADER_FILE #define HEADER_FILE the entire header file file #endif
This construct is commonly known as a wrapper #ifndef. When the header is included again, the conditional will be false, because HEADER_FILE is defined. The preprocessor will skip over the entire contents of the file, and the compiler will not see it twice.
Computed Includes
Sometimes it is necessary to select one of several different header files to be included into your program. They might specify configuration parameters to be used on different sorts of operating systems, for instance. You could do this with a series of conditionals as follows:
#if SYSTEM_1 # include "system_1.h" #elif SYSTEM_2 # include "system_2.h" #elif SYSTEM_3 ... #endif
But as it grows, it becomes tedious, instead the preprocessor offers the ability to use a macro for the header name. This is called a computed include. Instead of writing a header name as the direct argument of #include, you simply put a macro name there instead:
#define SYSTEM_H "system_1.h" ... #include SYSTEM_H
SYSTEM_H will be expanded, and the preprocessor will look for system_1.h as if the #includehad been written that way originally. SYSTEM_H could be defined by your Makefile with a -D option.
Reference to this.
Reference to this.
Tag :
C
C - Preprocessors
The C Preprocessor is not part of the compiler, but is a separate step in the compilation process. In simplistic terms, a C Preprocessor is just a text substitution tool and they instruct compiler to do required pre-processing before actual compilation. We'll refer to the C Preprocessor as the CPP.
All preprocessor commands begin with a pound symbol (#). It must be the first nonblank character, and for readability, a preprocessor directive should begin in first column. Following section lists down all important preprocessor directives:
| Directive | Description |
|---|---|
| #define | Substitutes a preprocessor macro |
| #include | Inserts a particular header from another file |
| #undef | Undefines a preprocessor macro |
| #ifdef | Returns true if this macro is defined |
| #ifndef | Returns true if this macro is not defined |
| #if | Tests if a compile time condition is true |
| #else | The alternative for #if |
| #elif | #else an #if in one statement |
| #endif | Ends preprocessor conditional |
| #error | Prints error message on stderr |
| #pragma | Issues special commands to the compiler, using a standardized method |
Preprocessors Examples
Analyze the following examples to understand various directives.
#define MAX_ARRAY_LENGTH 20
This directive tells the CPP to replace instances of MAX_ARRAY_LENGTH with 20. Use #definefor constants to increase readability.
#include#include "myheader.h"
These directives tell the CPP to get stdio.h from System Libraries and add the text to the current source file. The next line tells CPP to get myheader.h from the local directory and add the content to the current source file.
#undef FILE_SIZE #define FILE_SIZE 42
This tells the CPP to undefine existing FILE_SIZE and define it as 42.
#ifndef MESSAGE #define MESSAGE "You wish!" #endif
This tells the CPP to define MESSAGE only if MESSAGE isn't already defined.
#ifdef DEBUG /* Your debugging statements here */ #endif
This tells the CPP to do the process the statements enclosed if DEBUG is defined. This is useful if you pass the -DDEBUG flag to gcc compiler at the time of compilation. This will define DEBUG, so you can turn debugging on and off on the fly during compilation.
Predefined Macros
ANSI C defines a number of macros. Although each one is available for your use in programming, the predefined macros should not be directly modified.
| Macro | Description |
|---|---|
| __DATE__ | The current date as a character literal in "MMM DD YYYY" format |
| __TIME__ | The current time as a character literal in "HH:MM:SS" format |
| __FILE__ | This contains the current filename as a string literal. |
| __LINE__ | This contains the current line number as a decimal constant. |
| __STDC__ | Defined as 1 when the compiler complies with the ANSI standard. |
Let's try the following example:
#includemain() { printf("File :%s\n", __FILE__ ); printf("Date :%s\n", __DATE__ ); printf("Time :%s\n", __TIME__ ); printf("Line :%d\n", __LINE__ ); printf("ANSI :%d\n", __STDC__ ); }
When the above code in a file test.c is compiled and executed, it produces the following result:
File :test.c Date :Jun 2 2012 Time :03:36:24 Line :8 ANSI :1
Preprocessor Operators
The C preprocessor offers following operators to help you in creating macros:
Macro Continuation (\)
A macro usually must be contained on a single line. The macro continuation operator is used to continue a macro that is too long for a single line. For example:
#define message_for(a, b) \ printf(#a " and " #b ": We love you!\n")
Stringize (#)
The stringize or number-sign operator ('#'), when used within a macro definition, converts a macro parameter into a string constant. This operator may be used only in a macro that has a specified argument or parameter list. For example:
#include#define message_for(a, b) \ printf(#a " and " #b ": We love you!\n") int main(void) { message_for(Carole, Debra); return 0; }
When the above code is compiled and executed, it produces the following result:
Carole and Debra: We love you!
Token Pasting (##)
The token-pasting operator (##) within a macro definition combines two arguments. It permits two separate tokens in the macro definition to be joined into a single token. For example:
#include#define tokenpaster(n) printf ("token" #n " = %d", token##n) int main(void) { int token34 = 40; tokenpaster(34); return 0; }
When the above code is compiled and executed, it produces the following result:
token34 = 40
How it happened, because this example results in the following actual output from the preprocessor:
printf ("token34 = %d", token34);
This example shows the concatenation of token##n into token34 and here we have used bothstringize and token-pasting.
The defined() Operator
The preprocessor defined operator is used in constant expressions to determine if an identifier is defined using #define. If the specified identifier is defined, the value is true (non-zero). If the symbol is not defined, the value is false (zero). The defined operator is specified as follows:
#include#if !defined (MESSAGE) #define MESSAGE "You wish!" #endif int main(void) { printf("Here is the message: %s\n", MESSAGE); return 0; }
When the above code is compiled and executed, it produces the following result:
Here is the message: You wish!
Parameterized Macros
One of the powerful functions of the CPP is the ability to simulate functions using parameterized macros. For example, we might have some code to square a number as follows:
int square(int x) { return x * x; }
We can rewrite above code using a macro as follows:
#define square(x) ((x) * (x))
Macros with arguments must be defined using the #define directive before they can be used. The argument list is enclosed in parentheses and must immediately follow the macro name. Spaces are not allowed between and macro name and open parenthesis. For example:
#include#define MAX(x,y) ((x) > (y) ? (x) : (y)) int main(void) { printf("Max between 20 and 10 is %d\n", MAX(10, 20)); return 0; }
When the above code is compiled and executed, it produces the following result:
Max between 20 and 10 is 20
Reference to this.
Tag :
C


