Phong cách lập trình C++
Bạn đang học lập trình, vậy bạn nghĩ về một chương trình máy tính như thế nào?
Một chương trình máy tính có thể xem như một tác phẩm, bởi vì nó được đọc bởi bạn (có thể bây giờ,
mà cũng có thể là 10 năm sau!), và bởi những lập trình viên khác sau bạn (để phát triển, sửa chửa,
cập nhật .). Chính vì lẽ đó, một chương trình máy tính nên đáp ứng cả 3 yêu cầu sau: đúng, dễ đọc
và dễ hiểu.
Mục đích của style là làm cho chương trình trở nên dễ đọc đối với người viết và những người khác,
một style tốt là một phần thiết yếu của việc lập trình tốt. Viết một chương trình chạy đúng là chưa đủ
bởi chương trình không chỉ để cho máy tính đọc mà còn để các lập trình viên khác đọc. Hơn nữa, một
chương trình có style tốt luôn có nhiều khả năng chạy đúng hơn một chương trình có style tồi.
ính từ ngoặc mở hàm “{“ tới ngoặc kết thúc hàm “}”. 7 | P h o n g c á c h l ậ p t r ì n h C + + CODE 1.Viết code theo chuẩn ISO dù compiler có bắt buộc hay không. • Hạn chế #include ngoài chuẩn, VD: conio.h • Nên để int main()và return 0;thay vì void main() • Khai báo using std:: ngay cả khi IDE không bắt buộc. (dùng using namespace std; không tốt lắm) 2. Các hằng số không nên viết trực tiếp vào chương trình. Thay vì thế, người ta thường sử dụng lệnh #define hay const để đặt cho những hằng số này những tên có ý nghĩa. Điều này sẽ giúp lập trình viên dễ kiểm soát những chương trình lớn vì giá trị của hằng số khi cần thay đổi thì chỉ phải thay đổi một lần duy nhất ở giá trị định nghĩa (ở #define hay const). Ví dụ: popChange = (0.1758 - 0.1257) * population; nên được viết là: const double BIRTH_RATE = 0.1758, DEATH_RATE = 0.1257; hoặc: #define BIRTH_RATE 0.1758 #define DEATH_RATE 0.1257 //... popChange = (BIRTHRATE - DEATH_RATE) * population; Ghi chú: bạn không nên dùng #define thường xuyên để định nghĩa các hằng số, bởi vì trong quá trình debug, bạn sẽ không thể xem được giá trị của một hằng số định nghĩa bằng #define. Khi làm việc với các kí tự, hãy sử dụng các hằng kí tự thay vì các số nguyên. Ví dụ để kiểm tra xem c có phải một chữ cái hoa hay không, có thể dùng đoạn mã sau: if( c >= 65 && c <= 90 ) ... Nhưng đoạn mã này hoàn toàn phụ thuộc vào bộ mã biểu diễn kí tự đang được sử dụng. Cách tốt hơn là viết như sau: if( c >= ‘A’ && c <= ‘Z’ ) ... 3.Luôn viết new và delete thành từng cặp. 4. Khi khai báo con trỏ, dấu con trỏ nên được đặt liền với tên, nhằm tránh trường hợp sau: char* p, q, r; // q, r không là con trỏ 8 | P h o n g c á c h l ậ p t r ì n h C + + Trong trường hợp này nên viết là: char *p, *q, *r; (luật này cũng được dùng khi khai báo tham chiếu với dấu &) 5. Nên sử dụng các dấu ( ) khi muốn tránh các lỗi về độ ưu tiên toán tử. Ví dụ: // No! int i = a >= b && c < d && e <= g + h; // Better int j = (a >= b) && (c < d) && (e <= (g + h)); Bảng sau trong sách C Programming Language chỉ ra thứ tự ưu tiên các toán tử trong C. Hàng trên cùng có mức ưu tiên cao nhất. Toán tử Dịch ( [ - . Từ trái qua phải ! -- ++ { * & (type-cast) sizeof + - (1 ngôi) Từ phải qua trái * / % Từ trái qua phải + - Từ trái qua phải > Từ trái qua phải >= Từ trái qua phải == != Từ trái qua phải & Từ trái qua phải ^ Từ trái qua phải | Từ trái qua phải && Từ trái qua phải || Từ trái qua phải ?: Từ trái qua phải = += -= *= /= %= &= ^= |= >= Từ phải qua trái , Từ trái qua phải Dùng bảng này, có thể thấy rằng char *a[10]; là một mảng 10 con trỏ kí tự. Bạn cũng thấy rằng tại sao lại cần dấu ngoặc khi dùng (*p).i. Sau khi thực hành, bạn sẽ nhớ bảng này. 9 | P h o n g c á c h l ậ p t r ì n h C + + 6.Tách các biểu thức phức tạp thành các biểu thức đơn giản hơn Biểu thức sau đây rất ngắn gọn nhưng lại chứa quá nhiều phép toán: *x += ( *xp = ( 2*k < ( n – m ) ? c[ k + 1 ] : d[ k — ] ) ); Chúng ta nên viết lại như sau: if( 2*k < n – m ) *xp = c[ k + 1 ]; else *xp = d[ k– ]; *x += *xp; 7.Viết các lệnh dễ hiểu, không viết các lệnh “khôn ngoan” Các lập trình viên thường thích viết các lệnh càng ngắn gọn càng tốt. Tuy nhiên điều này thường gây phiền toái cho người khác. Hãy xem biểu thức sau đây làm gì: subkey = subkey >> ( bitoff – ( ( bitoff >> 3 ) << 3 ) ); Biểu thức trong cùng ( bitoff >> 3 ) dịch phải bitoff 3 bit. Kết quả thu được lại được dịch trái 3 bit. Bởi vậy 3 bit cuối cùng của bitoff được thay thế bởi các số 0. Kết quả này lại được trừ đi bởi giá trị ban đầu của bitoff, kết quả của phép trừ chính là 3 bit cuối cùng trong giá trị ban đầu của bitoff. Ba bit này được dùng để dịch subkey sang phải. Bởi vậy, biểu thức nói trên tương đương với biểu thức sau đây: subkeu = subkey >> ( bitoff & 0×7 ); Rõ ràng cách viết thứ hai dễ hiểu hơn nhiều. Một ví dụ khác về cách viết biểu thức ngắn gọn nhưng làm phức tạp hóa vấn đề: child = ( ! LC && ! RC ) ? 0 : ( ! LC ? RC : LC ); Cách viết dưới đây dài hơn, nhưng dễ hiểu hơn nhiều: if( LC == 0 && RC == 0 ) child = 0; else if( LC == 0 ) child = RC; else child = LC; 10 | P h o n g c á c h l ậ p t r ì n h C + + Toán tử ? : chỉ thích hợp cho những biểu thức ngắn kiểu như sau đây: max = ( a > b ) ? a : b; hoặc: printf( “The list has %d item%s\n”, n, n == 1 ? “” : “s” ); Hãy nhớ rằng mục tiêu của chúng ta là viết những đoạn mã dễ hiểu, chứ không phải các đoạn mã ngắn gọn. 8.Cẩn thận với dấu = = và == là 2 toán tử gây nhần lẫn nhất trên C, nhưng bạn có thể tránh gặp nó bằng thói quen viết r-value (biểu thức bên phải phép gán) sang bên trái phép so sánh: if ( a == 42 ) { ... }// Cách viết thông thường. if ( 42 == a ) { ... }// Nên viết thế này. Và đây là sự khác biệt, khi bạn nhầm ... if ( a = 42 ) { ... }// Chạy bình thường, khó tìm ra lỗi if ( 42 = a ) { ... }// Báo lỗi ngay chỗ này 9. Các idiom Cũng giống như ngôn ngữ tự nhiên, ngôn ngữ lập trình cũng có các idiom (thành ngữ !?), là các cách viết code chính tắc cho các trường hợp thông dụng, tạm hiểu idiom là các chuẩn không bắt buộc nhưng được đa số người dùng tuân theo. Sử dụng các idiom giúp giảm bớt khả năng mắc lỗi đồng thời làm chương trình dễ đọc hơn và nhất là có vẻ “chuyên nghiệp” hơn Sau đây là một số idiom phổ biến: 1.Các idiom cho mảng Để duyệt qua n phần tử của một mảng và khởi tạo chúng, có các cách viết sau đây: i = 0; while ( i <= n – 1 ) array[ i++ ] = 1.0; hoặc for( i = 0; i < n; ) array[ i++ ] = 1.0; hoặc for( i = n; -–i >= 0; ) array[ i ] = 1.0; Tất cả những cách viết trên đều đúng, tuy nhiên idiom cho trường hợp này là: for( i = 0; i < n; ++i ) array[ i ] = 1.0; 11 | P h o n g c á c h l ậ p t r ì n h C + + Một lưu ý nhỏ là sự khác biệt giữa i++ và ++i: • i++ lấy giá trị của i trước rồi tăng nó lên. • ++i tăng giá trị của i rồi lấy giá trị mới. Do đó đối với các con đếm vòng lặp (for(),while()) nên dùng ++i để tăng tốc độ. Idiom của vòng lặp duyệt qua các phần tử của một danh sách (list) là for( p = list; p != NULL; p = p->next ) ... Đối với container: vector::iterator it; for(it = v.begin(); it != v.end(); ++it) std::cout << *it; Đối với các vòng lặp vô hạn, idiom là: for ( ; ; ) hoặc while( 1 ) Khởi tạo danh sách: struct info { char *name; char *job; char *address; }; info *array[] = { { "name1", "job1", "add1" }, { "name1", "job1", "add1" }, { "name1", "job1", "add1" }, //... }; Hàm tìm kiếm tuyến tính: template int find (T obj,T* array,int size,int from = 0) { for(int i = from; i<size; ++i) if(array[i] == T) return i; return size; } Sao chép mảng: Giả sử 2 mảng double *a,*b; thay vì: for(int i=0; i<n; ++i) b[i]=a[i]; ta có thể dùng: //#include 12 | P h o n g c á c h l ậ p t r ì n h C + + memcpy(b,a,n*sizeof(double)); Cấp phát động cho mảng 2 chiều: int **pp = new type*[n]; int *p = new type[n*m]; for (int i = 0; i < n; ++i) pp[i] = p + i * m; //... //use array here delete[] p; delete[] pp; 2.Idiom cho lệnh if Tiếp theo là một idiom dành cho câu lệnh if. Hãy xem đoạn mã loằng ngoằng sau đây làm gì if ( argc==3 ) if ( ( fin = fopen(argv[l] , “r” ) ) != NULL ) if ( ( fout = fopen( argv[2], “w” ) ) != NULL ) { while ( ( c = getc( fin ) ) != EOF ) putc( c, fout ); fclose( fin ); fclose( fout ); } else printf ( “Can’t open output file %s\n”, argv[2] ) ; else printf( “Can’t open input file %s\n”, argv[l] ) ; else printf ( “Usage: cp input file outputfile\n” ) ; Viết lại đoạn mã này theo đúng idiom như sau: if ( argc != 3 ) printf ( “Usage: cp input file outputfile\n” ) ; else if ( ( fin = fopen( argv[l] , “r” ) ) == NULL ) printf( “Can’t open input file %s\n”, argv[l] ); else if ( ( fout = fopen( argv[2], “w” ) ) == NULL ) { printf ( “Can’t open output file %s\n”, argv[2] ) ; fclose( fin ) ; } else 13 | P h o n g c á c h l ậ p t r ì n h C + + { while ( ( c = getc( fin ) ) != EOF) putc( c, fout ); fclose( fin ) ; fclose( fout ) ; } Nguyên tắc khi viết các lệnh if() là đặt các phép toán kiểm tra điều kiện càng gần các hành động tương ứng càng tốt. 3.Idiom cho switch() case: Xét ví dụ: switch (c) { case '-': sign = -1; case '+': c = getchar(); case '.': break; case '0': case 'o': default: if (!isdigit(c)) return 0; } cách viết sau tuy dài nhưng dễ đọc hơn: switch (c) { case '-': sign = -1; case '+': c = getchar(); break; case '.': break; default: case '0': case 'o': if (!isdigit(c)) return 0; break; } 4.Số 0 trong chương trình Số 0 thường xuyên xuất hiện trong các chương trình với nhiều ý nghĩa khác nhau. Trình dịch sẽ tự động chuyển số 0 thành kiểu thích hợp. Tuy nhiên nên viết ra một cách tường minh bản chất của số 0 mà chúng ta đang nói đến. Cụ thể, hãy sử dụng ( void* )0 hoặc NULL để biểu diễn con trỏ null trong C, sử dụng ‘\0′ cho kí tự null ở cuối mỗi xâu và sử dụng 0.0 cho các số float hoặc double có giá trị không. Đừng viết đoạn mã như sau p = 0; name[ i ] = 0; 14 | P h o n g c á c h l ậ p t r ì n h C + + x = 0; Hãy viết: p = NULL; name[ i ] = '\ 0'; x = 0.0; Số 0 nên để dành cho các số nguyên có giá trị bằng không. Tuy nhiên trong C++, 0 (thay vì NULL) lại được sử dụng rộng rãi cho các con trỏ null, điều này không được khuyến khích. Mọi sự vi phạm đều được cho phép nếu nó giúp cho tối ưu đoạn mã của bạn. Mục đích chính của các quy tắc này là làm cho mã nguồn dễ đọc hiểu hơn, dễ dàng sửa lỗi và bảo trì, nâng chất lượng chung của mã nguồn. Tuy nhiên, nó sẽ không thể áp dụng đúng với mọi trường hợp cụ thể, và các lập trình viên phải sử dụng mềm dẻo các quy ước này.
File đính kèm:
- Phong cách lập trình C++.pdf