Showing posts with label Programming techniques. Show all posts
Shell - Search và Replace trong file
1. Tìm kiếm file với lệnh find
2. Tìm kiếm và thay thế string trong một file với lệnh sed
3. Tìm kiếm file, search và thay thế nội dung file với sự kết hợp của find và sed
find <search dir> -type <file or dir> -name <pattern file name>
Ex:
find /home/user/Downloads -type f -name "*.rar"
Tìm kiếm tất cả các file nén .rar trong thư mục Downloads
2. Tìm kiếm và thay thế string trong một file với lệnh sed
sed -i "s:<string to search>:<string to replace>:" infile outfile
or
sed -i "s:<string to search>:<string to replace>:" filename
# tim, thay the va save filename
Ex:
sed -i "s:day:night:" testfile.txt
3. Tìm kiếm file, search và thay thế nội dung file với sự kết hợp của find và sed
#!/bin/sh
# Tao listfile.txt luu danh sach tat ca cac file trong thu muc Downloads
#find /home/ninhld/Downloads -type f -name '*' > /home/ninhld/Downloads/listfile.txt
#Duyet qua tat ca cac file trong thu muc Downloads, search va replace tat ca
#cac file ma noi dung co noi dung /home/ninhld/Downloads
FINDPATH="/home/ninhld/Downloads"
FILETYPE="*"
PATTERN="/home/ninhld/Downloads"
REPLACE=""
find $FINDPATH -type f -name "$FILETYPE" -exec sed -i "s:$PATTERN:$REPLACE:" {} +
#for FILE in $(find $FINDPATH -type f)
#do
# #echo $FILE
# sed -i "s:$FINDPATH::" $FILE
#done
C/C++ Giá trị khởi tạo ban đầu của biến
Giá trị khởi tạo ban đầu của biến tưởng chừng đơn giản nhưng nhiều khi để lại hậu quả nghiêm trọng, trong bài viết này đề cập đến một số trường hợp bạn phải khởi tạo giá trị bạn đầu cho biến một cách rõ ràng.
initial_value.c (vd chỉ có tính minh họa)
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
int main(int argc, char **argv){
int idata; /* default: 0 */
bool bdata; /* default: false */
float *pdata; /* default: NULL */
if(!idata){
/* code here */
printf("goto here 1\n");
}
if(!bdata){
/* code here */
printf("goto here 2\n");
}
if(!pdata){
/* code here */
printf("goto here 3\n");
pdata = (float*)malloc(sizeof(float));
}
*pdata = 10;
free(pdata);
return 1;
}
Theo suy luận thông thường, khi khai báo mà không khởi tạo giá trị mặc định cụ thể cho biến thì chúng vẫn được tự động gắn các giá trị default như trên ví dụ trên, và vì vậy bạn thấy ngay các biểu trức if sẽ trả về kết quả là true.
Tuy nhiên trong thực tế (kinh nghiệm người viết bài này đã từng gặp phải) thì các giá trị thực sự mà các biến trên có được không giống như giá trị default đáng ra chúng phải bằng; có thể là idata != 0, bdata = true và pdata != NULL dẫn đến các logic của chương trình bị sai, hoặc chương trình bị crash (thao tác với con trỏ không hợp lệ).
Vì vậy khi gặp ngữ cảnh kiểu như trong vd trên thì tốt nhất bạn phải khởi tạo giá trị tường minh cho các biến:
int idata = 0;
bool bdata = false;
float *pdata = NULL;
Đặc biệt quan trọng trong lập trình C++, bạn nên khởi tạo giá trị cho tất cả các biến thuộc class trong hàm Constructor, và giải phóng bộ nhớ (nếu là con trỏ) trong hàm Destructor.
Ring buffer
1. Cấu trúc
2. Nguyên lý làm việc
3. Cách sử dụng
A single process/thread to process/thread buffer use case
Multiple processes/thread to process/thread use case
Single process/thread to multiple processes/thread use case
Single process/thread to multiple process/thread use case using a dispatcher and multiple queues
4. Source code sample
ringbufer.h
#ifndef RINGBUFFER_H
#define RINGBUFFER_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
#include <stdbool.h>
/*
* ringbuffer.h - C ring buffer (FIFO) interface.
*/
typedef struct ringbuffer_t
{
void *buffer; // data buffer
void *buffer_end; // end of data buffer
size_t capacity; // maximum number of items in the buffer
size_t count; // number of items in the buffer
size_t sz; // size of each item in the buffer
void *head; // pointer to head
void *tail; // pointer to tail
} ringbuffer_t;
bool ring_init(ringbuffer_t *cb, size_t capacity, size_t sz);
bool ring_free(ringbuffer_t *cb);
bool ring_push_head(ringbuffer_t *cb, const void *item);
bool ring_pop_tail(ringbuffer_t *cb, void *item);
#ifdef __cplusplus
}
#endif
#endif /* RINGBUFFER_H */
ringbuffer.c
#include "ringbuffer.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
bool ring_init(ringbuffer_t *cb, size_t capacity, size_t sz)
{
cb->buffer = malloc(capacity * sz);
if(cb->buffer == NULL){
printf("ring_init FAIL ! \n");
return false;
}
cb->buffer_end = (char *)cb->buffer + capacity * sz;
cb->capacity = capacity;
cb->count = 0;
cb->sz = sz;
cb->head = cb->buffer;
cb->tail = cb->buffer;
return true;
}
bool ring_free(ringbuffer_t *cb)
{
if(cb->buffer){
free(cb->buffer);
cb->buffer = NULL;
}else{
return false;
}
return true;
}
bool ring_push_head(ringbuffer_t *cb, const void *item)
{
if(cb->count == cb->capacity){
printf("Ringbuffer is Full ! \n");
return false;
}
memcpy(cb->head, item, cb->sz);
cb->head = (char*)cb->head + cb->sz;
if(cb->head == cb->buffer_end)
cb->head = cb->buffer;
cb->count++;
return true;
}
bool ring_pop_tail(ringbuffer_t *cb, void *item)
{
if(cb->count == 0){
printf("Ringbuffer is Empty ! \n");
return false;
}
memcpy(item, cb->tail, cb->sz);
cb->tail = (char*)cb->tail + cb->sz;
if(cb->tail == cb->buffer_end)
cb->tail = cb->buffer;
cb->count--;
return true;
}
ringbuffer_test.c
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "ringbuffer.h"
#define CAPACITY 10
typedef struct data_t{
int idata;
float fdata;
}data_t;
ringbuffer_t ringbf;
int main(int argc, char **argv)
{
ring_init(&ringbf, CAPACITY, sizeof(data_t));
int i;
data_t tmpdata;
for(i = 0; i < CAPACITY; i++){
tmpdata.idata = i;
tmpdata.fdata = i+1;
ring_push_head(&ringbf, (void*)&tmpdata);
printf("number of items: %d \n", ringbf.count);
}
for(i = 0; i < CAPACITY; i++){
ring_pop_tail(&ringbf, (void*)&tmpdata);
printf("number of items: %d, idata: %d, fdata: %f \n", ringbf.count, tmpdata.idata, tmpdata.fdata);
}
ring_free(&ringbf);
return 0;
}
C/C++ Cách đặt tên: hàm, biến, cấu trúc dữ liệu, file sourrce code
1. Tên cấu trúc dữ liệu tự định nghĩa
+ Tên được đặt bằng chữ thường, ngăn cách bằng dấu gạch dưới "_", kết thúc bằng "_t"
+ Tên được đặt có chữ in hoa
2. Tên hàm, con trỏ hàm
+ Chữ thường, ngăn cách bằng dấu gạch dưới "_"
Con trỏ hàm:
+ Tên được đặt có chữ in hoa
Con trỏ hàm:
3. Tên biến
4. Tên file source code
Tên file source code cũng đặt theo phong cách tương tự như với tên hàm
C source file
C++ source file
+ Tên được đặt bằng chữ thường, ngăn cách bằng dấu gạch dưới "_", kết thúc bằng "_t"
enum player_status_t{
ST_TOPPED = 0;
ST_PLAYING,
ST_PAUSED,
ST_FFW, /* fast forward */
ST_FBW, /* fast backward */
}
union myname_t{
char full_name[64];
char alias[32];
}
struct persion_t{
char name[32];
int age;
}
+ Tên được đặt có chữ in hoa
enum PlayerStatus_t{
ST_TOPPED = 0;
ST_PLAYING,
ST_PAUSED,
ST_FFW, /* fast forward */
ST_FBW, /* fast backward */
}
2. Tên hàm, con trỏ hàm
void set_value(int a){
/* code here */
}
int get_value(){
int ret_value = 0;
/* code here */
return ret_value;
}
Con trỏ hàm:
typedef int (*math_operator_t)(int a, int b);
+ Tên được đặt có chữ in hoa
void setValue(int a){
/* code here */
}
int getValue(){
int ret_value = 0;
/* code here */
return ret_value;
}
Con trỏ hàm:
typedef int (*MathOperator_t)(int a, int b);
3. Tên biến
Có rất nhiều cách đặt tên cho biến, tùy vào trường hợp; có thể dựa vào phạm vi biến hoạt động, dựa vào kiểu dữ liệu của biến, etc ..., dưới đây llà một vài ví dụ tham khảo:
+ Biến global
Bắt đầu bằng "m_" hoặc "g_"
+ Biến global
Bắt đầu bằng "m_" hoặc "g_"
player_status_t m_player_status = ST_TOPPED;
hoặc
player_status_t g_player_status = ST_TOPPED;
+ Biến local
player_status_t l_player_status = ST_TOPPED;
+ Kiểu dữ liệu
+ ...
int ndata;
float fdata;
string strdata;
+ ...
4. Tên file source code
Tên file source code cũng đặt theo phong cách tương tự như với tên hàm
C source file
user_controller.h
user_controller.c
C++ source file
user_controller.hpp
user_controller.cpp
C/C++ Signal-slot
Signal-slot là một cơ chế để thực hiện việc một object này thông báo một sự kiện đến một object khác theo cơ chế tương tự như interrupt.
Các đối tượng muốn signal-slot được với nhau cần có một đăng ký kết nối trước, một object có thể cùng lúc có nhiều kết nối đến nhiều object khác, lưu ý định dạng dữ liệu giữa bên phát và nhận phải giống nhau.
Các đối tượng muốn signal-slot được với nhau cần có một đăng ký kết nối trước, một object có thể cùng lúc có nhiều kết nối đến nhiều object khác, lưu ý định dạng dữ liệu giữa bên phát và nhận phải giống nhau.
Khai báo class signal:
class Button:public sigslot::has_signals<Button>
{
//attributes
//methods
public __signals:
sigslot::signal<char*> touchEvent;
};
Khai báo class slot:
class View:public sigslot::has_slots<> { //attributes //methods public __slots: void onUpdate(char *msg){ cout << "recvmsg: " << msg <<endl; } };
Khai báo class vừa phát vừa nhận
class TransRecv:public sigslot::has_signals<TransRecv> , sigslot::has_slots<>
{
//attributes
//methods
public __signals: //phat signal cho object A
sigslot::signal<char *> touchPoint;
public __slots: //nhan signal tu object B
void onUpdate(float fdata, int idata);
};
Duới đây là một ví dụ:
sigslot.h (định nghĩa cơ chế signal-slot của QT Framework)
main.cpp
#include <iostream>
#include "sigslot.h"
using namespace std;
class Button:public sigslot::has_signals<Button>
{
public:
Button(){ };
virtual ~Button(){ };
void onClick(){
touchEvent.emit((char*)"Hello, I am button");
}
public __signals:
sigslot::signal<char*> touchEvent;
};
class View:public sigslot::has_slots<>
{
public:
View(){ };
virtual ~View(){ };
public __slots:
void onUpdate(char *msg){
cout << "recvmsg: " << msg <<endl;
}
};
int main(int argc, char **argv){
Button btn;
View view;
btn.touchEvent.connect(&view, &View::onUpdate);
btn.onClick();
return 1;
}
Compile & Execute:
$ g++ sigslot.h main.cpp
$ ./a.out
recvmsg: Hello, I am button
C/C++ Quản lý dữ liệu bằng Linked List và C++ Containers
Trong bất cứ project nào việc quản lý dữ liệu (dưới dạng danh sách) cũng rất quan trọng, vì thế nên cần nắng chắc các kiểu cấu trúc dữ liệu đã được đề cập ở các bài viết trước
+ Linked List
+ list
+ vector
+ queue
+ stack
+ Linked List
+ list
+ vector
+ queue
+ stack
C/C++ Kết hợp C và C++ source
Trong một project thường thì sẽ có cả C source code (trong file .c) và C++ source code (trong file .cpp), vậy làm thế nào để kết hợp chúng lại với nhau. Như đã biết để compile C source thì dùng gcc, compile C++ source thì dùng g++, một project có cả C và C++ source thì sẽ dùng g++, vì thế cần loại bỏ các tính chất của C++ khi compile C source. Để làm được điều đó ta sử dụng extern "C" trong file C header.
Giữa cặp dấu ngoặc extern là các khai báo các include header, các enum, union, struct, function, ... , tất cả chúng sẽ được implement ở dạng code C thuần túy trong một file .c khác.
Ex
math_operator.h
math_operator.c
main.cpp
Compile & Execute:
#ifdef __cplusplus
extern "C"{
#endif
// ... your include, types, methods, variables ...
#ifdef __cplusplus
} //end extern "C"{
#endif
Giữa cặp dấu ngoặc extern là các khai báo các include header, các enum, union, struct, function, ... , tất cả chúng sẽ được implement ở dạng code C thuần túy trong một file .c khác.
Ex
math_operator.h
#ifndef MATH_OPERATOR_H
#define MATH_OPERATOR_H
#ifdef __cplusplus
extern "C"{
#endif
#include <stdio.h>
bool add(float a, float b, float *ret);
bool sub(float a, float b, float *ret);
bool multi(float a, float b, float *ret);
bool devision(float a, float b, float *ret);
#ifdef __cplusplus
} //extern "C"{
#endif
#endif // MATH_OPERATOR_H
math_operator.c
#include "math_operator.h"
#include <stdbool.h>
bool add(float a, float b, float *ret){
*ret = (a+b);
return true;
}
bool sub(float a, float b, float *ret){
*ret = (a-b);
return true;
}
bool multi(float a, float b, float *ret){
*ret = (a*b);
return true;
}
bool devision(float a, float b, float *ret){
if(b != 0){
*ret = ((float)a/b);
return true;
}
return false;
}
main.cpp
#include <iostream>
#include "math_operator.h"
using namespace std;
int main(int argc, char **argv){
float ret;
add(4, 2, &ret);
cout << "4 + 2 = " << ret <<endl;
sub(4, 2, &ret);
cout << "4 - 2 = " << ret <<endl;
multi(4, 2, &ret);
cout << "4 * 2 = " << ret <<endl;
devision(4, 2, &ret);
cout << "4 / 2 = " << ret <<endl;
return 1;
}
Compile & Execute:
$ g++ math_operator.h math_operator.c main.cpp
$ ./a.out
4 + 2 = 6
4 - 2 = 2
4 * 2 = 8
4 / 2 = 2
C/C++ Lập trình hướng đối tượng với C Object-Oriented
Struct chính là class trong trường hợp attributes & methods được khai báo là public, attributes trong struct là các biến như thông thường còn methods được khai báo bởi các pointer function.
Ex
class-and-struct.cpp
Compile & Execute
Ex
class-and-struct.cpp
#include <iostream>
#include <stdio.h>
using namespace std;
struct mStruct{
int data;
void (*func)();
};
void print_info(){
printf("struct method \n");
}
typedef struct mStruct mStruct;
class mClass{
public:
int data;
void func(){
cout << "class method \n";
}
};
int main () {
mStruct structObj;
structObj.data = 10;
structObj.func = print_info;
mClass classObj;
classObj.data = 10;
structObj.func();
classObj.func();
return 0;
}
Compile & Execute
$ g++ class-and-struct.cpp
$ ./a.out
struct method
class method
C/C++ Kỹ thuật sử dụng Array kết hợp với Enum
Đối với Array đã được đề cập, để truy cập các phần tử cụ thể trong mảng thì cần phải nhớ vị trí của phần tử đó trong mảng. Phần này sẽ đề cập đến vấn đề làm thế nào để truy cập một phần tử cụ thể trong mảng theo ID (enum) một các dễ dàng.
1. Array
array-enum.c
Compile & Execute
Như ví dụ trên, mảng mycolor[] có các phần tử được khai báo theo các ID đặt trong [ ], giá trị các field đặt trong { }, không cần quan tâm thứ tự của các phần tử trong mảng, các khai báo dưới đây là tương đương:
Truy cập phần tử mảng theo ID
2. map (C++)
Trong C++ cũng có cách để truy cập phần tử trong mảng bằng ID, được định nghĩa trong map containers, xem chi tiết tại đây.
1. Array
array-enum.c
#include <stdio.h>
enum {
COLOR_REG = 0,
COLOR_GREEN,
COLOR_BLUE
};
typedef struct{
char name_color[32];
int r;
int g;
int b;
}color_t;
color_t mycolor[] = {
[COLOR_BLUE] = {"blue", 0, 0, 255},
[COLOR_REG] = {"reg", 255, 0, 0},
[COLOR_GREEN] = {"green", 0, 255, 0},
};
void print_color(int color){
printf("color<%s>: r = %d, g = %d, b = %d \n",
mycolor[color].name_color,
mycolor[color].r,
mycolor[color].g,
mycolor[color].b
);
}
int main(int argc, char **argv)
{
print_color(COLOR_REG);
print_color(COLOR_GREEN);
print_color(COLOR_BLUE);
return 0;
}
Compile & Execute
$ gcc array-enum.c
$ ./a.out
color<reg>: r = 255, g = 0, b = 0
color<green>: r = 0, g = 255, b = 0
color<blue>: r = 0, g = 0, b = 255
Như ví dụ trên, mảng mycolor[] có các phần tử được khai báo theo các ID đặt trong [ ], giá trị các field đặt trong { }, không cần quan tâm thứ tự của các phần tử trong mảng, các khai báo dưới đây là tương đương:
color_t mycolor[] = {
[COLOR_BLUE] = {"blue", 0, 0, 255},
[COLOR_REG] = {"reg", 255, 0, 0},
[COLOR_GREEN] = {"green", 0, 255, 0},
};
color_t mycolor[] = {
[COLOR_REG] = {"reg", 255, 0, 0},
[COLOR_BLUE] = {"blue", 0, 0, 255},
[COLOR_GREEN] = {"green", 0, 255, 0},
};
color_t mycolor[] = {
[COLOR_GREEN] = {"green", 0, 255, 0},
[COLOR_BLUE] = {"blue", 0, 0, 255},
[COLOR_REG] = {"reg", 255, 0, 0},
};
Truy cập phần tử mảng theo ID
mycolor[COLOR_REG]
mycolor[COLOR_GREEN]
mycolor[COLOR_BLUE]
2. map (C++)
Trong C++ cũng có cách để truy cập phần tử trong mảng bằng ID, được định nghĩa trong map containers, xem chi tiết tại đây.
C/C++ Specifying Attributes of Types
Khi khai báo một struct thì nó sẽ được cấp phát tự động một vùng nhớ có độ dài mà coder khó có thể biết chính xác. Ở phần lớn các bài toán coder không cần quan tâm xem struct mà mình khai báo sẽ được cấp phát như thế nào và nó được cấp vùng nhớ bao nhiêu byte, tuy nhiên ở một vài bài toán đặc thù thì coder phải kiểm soát, biết chắc chắn struct mà mình khai báo sẽ được cấp phát như thế nào, khi đó coder được cung cấp các thuộc tính để thêm vào struct để làm việc đó.
1. __attribute__ aligned
Trong bài toán liên quan đến các protocol khi mà định dạng gói tin được chỉ rõ: có bao nhiêu field, độ dài các field là bao nhiêu, ... Vì vậy để chương trình có thể chạy đúng protocol thì khi coder khai báo struct để biểu diễn các field đó phải làm sao đó để chắc chắn struct mà mình khai báo sẽ có độ dài đúng bằng độ dài mà protocol đó định nghĩa.
Khai báo:
Thuộc tính aligned(n) dùng để cho biết struct sẽ được cấp vùng nhớ có độ dài
sizeof struct = n*x, trong đó n là số byte để aligne, giá trị n = 2^m, tức là bằng cấp số mũ của 2; x là số nguyên dương với điều kiện x >= 1. Ví dụ trên n = 8, vì vậy biến kiểu struct Aligned_t sẽ được cấp vùng nhớ có độ dài là cấp số nhân của 8.
2. __attribute__((packed))
Dùng để minimum độ dài vùng nhớ của struct, mục đích tiết kiệm bộ nhớ.
Khai báo:
Ex
attribute.c
Compile & Execute
Xem thêm Specifying Attributes of Types
1. __attribute__ aligned
Trong bài toán liên quan đến các protocol khi mà định dạng gói tin được chỉ rõ: có bao nhiêu field, độ dài các field là bao nhiêu, ... Vì vậy để chương trình có thể chạy đúng protocol thì khi coder khai báo struct để biểu diễn các field đó phải làm sao đó để chắc chắn struct mà mình khai báo sẽ có độ dài đúng bằng độ dài mà protocol đó định nghĩa.
Khai báo:
struct Aligned_t
{
char cdata;
int idata;
float fdata;
}__attribute__ ((aligned (8)));
Thuộc tính aligned(n) dùng để cho biết struct sẽ được cấp vùng nhớ có độ dài
sizeof struct = n*x, trong đó n là số byte để aligne, giá trị n = 2^m, tức là bằng cấp số mũ của 2; x là số nguyên dương với điều kiện x >= 1. Ví dụ trên n = 8, vì vậy biến kiểu struct Aligned_t sẽ được cấp vùng nhớ có độ dài là cấp số nhân của 8.
2. __attribute__((packed))
Dùng để minimum độ dài vùng nhớ của struct, mục đích tiết kiệm bộ nhớ.
Khai báo:
struct Packed_t
{
char cdata; //1
int idata; //4
float fdata; //4
}__attribute__((packed));// == minimum ==> 9
Ex
attribute.c
#include <stdio.h>
struct Normal_t
{
char cdata; //4
int idata; //4
float fdata; //4
};// 12
struct Aligned_t
{
char cdata;
int idata;
float fdata;
}__attribute__ ((aligned (8))); // 12 == align 8 ==> 16
struct Packed_t
{
char cdata; //1
int idata; //4
float fdata; //4
}__attribute__((packed));// == minimum ==> 9
int main(int argc, char **argv)
{
printf("sizeof(char): %d\n", (int)sizeof(char));
printf("sizeof(int): %d\n", (int)sizeof(int));
printf("sizeof(float): %d\n", (int)sizeof(float));
printf("sizeof(long): %d\n", (int)sizeof(long));
printf("\n\n");
struct Normal_t m1;
struct Aligned_t m2;
struct Packed_t m3;
printf("sizeof(Normal_t): %d\n", (int)sizeof(m1));
printf("sizeof(Aligned_t): %d\n", (int)sizeof(m2));
printf("sizeof(Packed_t): %d\n", (int)sizeof(m3));
return 0;
}
Compile & Execute
$ gcc attribute.c
$ ./a.out
sizeof(char): 1
sizeof(int): 4
sizeof(float): 4
sizeof(long): 8
sizeof(Normal_t): 12
sizeof(Aligned_t): 16
sizeof(Packed_t): 9
Xem thêm Specifying Attributes of Types
C/C++ Phương pháp mặt nạ Bit-Mask
Đặt vấn đề
Giả sử trong bài toán có nhiều lựa chọn - option, vậy làm thế nào để trong một thời điểm có thể chọn:
+ Một trong những option (only one)
+ Nhiều option cùng một lúc
1. Một trong những option
option.c
Compile & Execute
Đối số ope cho biết hàm math_operator phải thực hiện phép toán nào . Vì ope chỉ mang một giá trị nên hàm math_operator chỉ thực hiện duy nhất một phép toán trong các phép toán ADD, SUB, MUL và DEV.
Note: Tùy vào kiểu dữ liệu của biến ope ta sẽ có 2^n option cho biểu thức if(ope == value), vơi n là số bít của ope. Cụ thể trong trường hợp này vì ope có kiểu unsigned char nên độ dài là 8bit, suy ra ope có tất cả 2^8 = 256 giá trị khác nhau.
2. Nhiều option cùng một lúc
bitmask.c
Compile & Execute
Khác với trường hợp 1, ở đây mỗi lần gọi hàm math_operator thì có thể thực hiện được nhiều phép toán cũng với chỉ đối số ope như trên.
Ở trường hợp này ta sử dụng một kỹ thuật gọi là kỹ thuật mặt nạ - bitmask. Kỹ thuật này thay vì quan tâm đến giá trị của biến ope như trường hợp trên, ta lại quan tâm đến từng bit của ope, mỗi bit được sử dụng cho một option. Để hiểu kỹ thuật này trước hết cần xem lại các phép toán Bitwise Operators
Các define quy định bit nào sẽ sử dụng cho action nào
Như vậy theo ví dụ bitmask.c quy định bit ở vị trí số 1 sẽ dùng để biểu diễn cho action ADD, tương tự như vậy bít ở vị trí 2,3,4 biểu diễn cho action SUB, MUL và DEV.
+Để đưa các action vào function ta dùng phép OR bit (ký hiệu: |)
+Để lọc ra giá trị các bit tương ứng với các action ta dùng phép AND bit (ký hiệu: &).
Ví dụ để function thực hiện hai phép toán ADD và SUB:
Kết quả function sẽ thực hịện hai phép toán ADD và SUB đúng như mong muốn.
Note:
+ Số action đươc vào phụ thuộc vào số bit mà đối số ope được khai báo, như ví dụ trên ope được khai báo là unsigned char nên có 8 bit, vì vậy sẽ đưa vào cùng lúc được 8 action. Ví dụ ope được khai báo kiểu int thì sẽ có tối đa 32 action đưa vào cùng lúc
+ Đối số ope thường được khai báo kiểu char, unsigned char, int, unsigned int
Giả sử trong bài toán có nhiều lựa chọn - option, vậy làm thế nào để trong một thời điểm có thể chọn:
+ Một trong những option (only one)
+ Nhiều option cùng một lúc
1. Một trong những option
option.c
#include <stdio.h>
enum {
OPE_ADD = 0,
OPE_SUB,
OPE_MUL,
OPE_DEV
};
void math_operator(int a, int b, unsigned char ope)
{
if(ope == OPE_ADD){
printf("sum: a+b= %d \n", (a+b));
}
if(ope == OPE_SUB){
printf("sub: a-b= %d \n", (a-b));
}
if(ope == OPE_MUL){
printf("multi: a*b= %d \n", (a*b));
}
if(ope == OPE_DEV){
if(b!=0)
printf("devi: a/b= %f \n", ((float)a/b));
else
printf("fail ! \n");
}
printf("\n");
}
int main()
{
math_operator(4,2, OPE_ADD);
math_operator(4,2, OPE_SUB);
math_operator(4,2, OPE_MUL);
math_operator(4,2, OPE_DEV);
return 0;
}
Compile & Execute
$ gcc option.c
$ ./a.out
sum: a+b= 6
sub: a-b= 2
multi: a*b= 8
devi: a/b= 2.000000
Đối số ope cho biết hàm math_operator phải thực hiện phép toán nào . Vì ope chỉ mang một giá trị nên hàm math_operator chỉ thực hiện duy nhất một phép toán trong các phép toán ADD, SUB, MUL và DEV.
Note: Tùy vào kiểu dữ liệu của biến ope ta sẽ có 2^n option cho biểu thức if(ope == value), vơi n là số bít của ope. Cụ thể trong trường hợp này vì ope có kiểu unsigned char nên độ dài là 8bit, suy ra ope có tất cả 2^8 = 256 giá trị khác nhau.
2. Nhiều option cùng một lúc
bitmask.c
#include <stdio.h>
#define OPE_MASK_ADD (1 << 0) // 1 = 0x01 = 00000001
#define OPE_MASK_SUB (1 << 1) // 2 = 0x02 = 00000010
#define OPE_MASK_MUL (1 << 2) // 4 = 0x04 = 00000100
#define OPE_MASK_DEV (1 << 3) // 3 = 0x01 = 00001000
void math_operator(int a, int b, unsigned char ope)
{
if(ope & OPE_MASK_ADD){
printf("sum: a+b= %d \n", (a+b));
}
if(ope & OPE_MASK_SUB){
printf("sub: a-b= %d \n", (a-b));
}
if(ope & OPE_MASK_MUL){
printf("multi: a*b= %d \n", (a*b));
}
if(ope & OPE_MASK_DEV){
if(b!=0)
printf("devi: a/b= %f \n", ((float)a/b));
else
printf("fail ! \n");
}
printf("\n");
}
int main()
{
math_operator(4,2, (OPE_MASK_ADD));
math_operator(4,2, (OPE_MASK_ADD | OPE_MASK_SUB));
math_operator(4,2, (OPE_MASK_ADD | OPE_MASK_SUB | OPE_MASK_MUL));
math_operator(4,2, (OPE_MASK_ADD | OPE_MASK_SUB) | OPE_MASK_MUL | OPE_MASK_DEV);
return 0;
}
Compile & Execute
$ gcc bitmask.c
$ ./a.out
sum: a+b= 6
sum: a+b= 6
sub: a-b= 2
sum: a+b= 6
sub: a-b= 2
multi: a*b= 8
sum: a+b= 6
sub: a-b= 2
multi: a*b= 8
devi: a/b= 2.000000
Khác với trường hợp 1, ở đây mỗi lần gọi hàm math_operator thì có thể thực hiện được nhiều phép toán cũng với chỉ đối số ope như trên.
Ở trường hợp này ta sử dụng một kỹ thuật gọi là kỹ thuật mặt nạ - bitmask. Kỹ thuật này thay vì quan tâm đến giá trị của biến ope như trường hợp trên, ta lại quan tâm đến từng bit của ope, mỗi bit được sử dụng cho một option. Để hiểu kỹ thuật này trước hết cần xem lại các phép toán Bitwise Operators
| bit number 8 | bit number 7 | bit number 6 | bit number 5 | bit number 4 | bit number 3 | bit number 2 | bit number 1 |
|---|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
Các define quy định bit nào sẽ sử dụng cho action nào
#define OPE_MASK_ADD (1 << 0) // 1 = 0x01 = 00000001
#define OPE_MASK_SUB (1 << 1) // 2 = 0x02 = 00000010
#define OPE_MASK_MUL (1 << 2) // 4 = 0x04 = 00000100
#define OPE_MASK_DEV (1 << 3) // 3 = 0x01 = 00001000
Như vậy theo ví dụ bitmask.c quy định bit ở vị trí số 1 sẽ dùng để biểu diễn cho action ADD, tương tự như vậy bít ở vị trí 2,3,4 biểu diễn cho action SUB, MUL và DEV.
+Để đưa các action vào function ta dùng phép OR bit (ký hiệu: |)
+Để lọc ra giá trị các bit tương ứng với các action ta dùng phép AND bit (ký hiệu: &).
Ví dụ để function thực hiện hai phép toán ADD và SUB:
math_operator(4,2, (OPE_MASK_ADD | OPE_MASK_SUB));
Khi đó ope = 00000001 | 00000010 = 00000011
Bên trong hàm thực hiện phân tích bit if(ope & OPE_MASK_ADD){ //true vì 00000011 & 00000001 = 00000001 # 0
printf("sum: a+b= %d \n", (a+b));
}
if(ope & OPE_MASK_SUB){ //true vì 00000011 & 00000010 = 00000010 # 0
printf("sub: a-b= %d \n", (a-b));
}
if(ope & OPE_MASK_MUL){ //false vì 00000011 & 00000100 = 00000000 = 0
printf("multi: a*b= %d \n", (a*b));
}
if(ope & OPE_MASK_DEV){ //false vì 00000011 & 00001000 = 00000000 = 0
if(b!=0)
printf("devi: a/b= %f \n", ((float)a/b));
else
printf("fail ! \n");
}
Kết quả function sẽ thực hịện hai phép toán ADD và SUB đúng như mong muốn.
Note:
+ Số action đươc vào phụ thuộc vào số bit mà đối số ope được khai báo, như ví dụ trên ope được khai báo là unsigned char nên có 8 bit, vì vậy sẽ đưa vào cùng lúc được 8 action. Ví dụ ope được khai báo kiểu int thì sẽ có tối đa 32 action đưa vào cùng lúc
+ Đối số ope thường được khai báo kiểu char, unsigned char, int, unsigned int
C/C++ Sử dụng tiền xử lý - preprocessors
Bài viết này hướng dẫn sử dụng preprocessors để giải quyết các vấn đề gặp phải trong thực tế làm việc như dưới đây:
1. Trong quá trình code chuyện debug là điều không thể tránh khỏi, tuy nhiên chương trình realease cuối cùng phải tắt hết các dòng lệnh debug ra khỏi chương trình, vậy làm thế nào để làm được điều đó ?
2. Làm thế nào để add/remove, hoặc switch qua lại giữa các tính năng trong quá trình phát triển chương trình ban đầu cũng như sau này ra các bản update ?
3. Một vài thủ tục có nhiều dùng code mà lại cần lặp đi lặp lại thủ tục đó, vậy làm sao để làm cho chương trình sáng sủa và đẹp mắt trong khi vẫn thực hiện đúng chức năng ?
Trước hết xem lại Preprocessors.
#if, #ifdef, #ifndef, defined( ) thường được sử dụng nhiều nhất.
Ex
debug.h
main.c
Compile & Execute
1. Hàm printf được gói gọn trong define DBG và FATAL, được bật tắt dễ dàng bằng cách sửa lại #if 1 hoặc #if 0 trong file debug.h
2. Các tính năng được add/remove nhờ các define D_FEATURE_1, D_FEATURE_2, D_FEATURE_3, D_FEATURE_4, ...
3. Một vd đơn giản cho việc sử dụng #define để rút gon source một cách ngắn gọn khi gặp các thủ tục giống nhau và lặp đi lặp lại PRINT_MAX
1. Trong quá trình code chuyện debug là điều không thể tránh khỏi, tuy nhiên chương trình realease cuối cùng phải tắt hết các dòng lệnh debug ra khỏi chương trình, vậy làm thế nào để làm được điều đó ?
2. Làm thế nào để add/remove, hoặc switch qua lại giữa các tính năng trong quá trình phát triển chương trình ban đầu cũng như sau này ra các bản update ?
3. Một vài thủ tục có nhiều dùng code mà lại cần lặp đi lặp lại thủ tục đó, vậy làm sao để làm cho chương trình sáng sủa và đẹp mắt trong khi vẫn thực hiện đúng chức năng ?
Trước hết xem lại Preprocessors.
#if, #ifdef, #ifndef, defined( ) thường được sử dụng nhiều nhất.
Ex
debug.h
#ifndef DEBUG_H_
#define DEBUG_H_
#include <stdio.h>
#if 1 /* 1: enable, 0: disable debug */
#define DBG(x...) {printf("\e[033;34m%s<%d> \e[0m",__FUNCTION__,__LINE__); printf(x);}
#define FATAL(x...) {printf("\e[033;31m%s<%d> \e[0m",__FUNCTION__,__LINE__); printf(x);}
#else
#define DBG(x...)
#define FATAL(x...)
#endif
#endif /* DEBUG_H_ */
main.c
#include <stdio.h>
#include "debug.h"
#define D_FEATURE_1
//#define D_FEATURE_2
#define D_FEATURE_3
#define D_FEATURE_4 1
#define PRINT_MAX(a, b) \
if(a > b){ \
DBG("MAX = a = : %d \n", a); \
}else{ \
DBG("MAX = b = : %d \n", b); \
}
int main()
{
int speed = 10;
if(speed < 0){
FATAL("fail ! \n");
speed = 0;
}else{
DBG("speed: %d \n", speed);
speed++;
}
#ifdef D_FEATURE_1
DBG("code for D_FEATURE_1 \n") ;
#endif
#ifdef D_FEATURE_2
DBG("code for D_FEATURE_2 \n") ;
#endif
#if defined(D_FEATURE_3)
DBG("code for D_FEATURE_3 \n") ;
#endif
#if (D_FEATURE_4 == 1)
DBG("code for D_FEATURE_4 \n") ;
#endif
PRINT_MAX(1, 2);
PRINT_MAX(3, 2);
PRINT_MAX(10, 20);
PRINT_MAX(11, 2);
return 0;
}
Compile & Execute
$ gcc debug.h main.c
$ ./a.out
main<29> speed: 10
main<35> code for D_FEATURE_1
main<43> code for D_FEATURE_3
main<47> code for D_FEATURE_4
main<51> MAX = b = : 2
main<52> MAX = a = : 3
main<53> MAX = b = : 20
main<54> MAX = a = : 11
1. Hàm printf được gói gọn trong define DBG và FATAL, được bật tắt dễ dàng bằng cách sửa lại #if 1 hoặc #if 0 trong file debug.h
2. Các tính năng được add/remove nhờ các define D_FEATURE_1, D_FEATURE_2, D_FEATURE_3, D_FEATURE_4, ...
3. Một vd đơn giản cho việc sử dụng #define để rút gon source một cách ngắn gọn khi gặp các thủ tục giống nhau và lặp đi lặp lại PRINT_MAX






