Xem kết quả thăm dò: Bạn thấy topic này thế nào

  • Hữu ích

    474 94.23%
  • Bình thường

    18 3.58%
  • Tồi

    1 0.20%
  • Cực kỳ tồi

    10 1.99%
Bạn không thể bỏ phiếu ở thăm dò này
Số người bỏ phiếu 503.
Trang 2 trên tổng số 6 Đầu tiênĐầu tiên 1234... Cuối cùngCuối cùng
Từ 11 tới 20 trên tổng số 60 kết quả

Đề tài: Hướng dẫn lập trình hướng đối tượng với C++

  1. #11
    Ngày gia nhập
    02 2011
    Nơi ở
    Hà Nội
    Bài viết
    67

    Mặc định Kiểu dữ liệu trong C++

    BÀI 8. KIỂU DỮ LIỆU CƠ SỞ


    1. Tổng quan về kiểu dữ liệu cơ sở (basic data types)
    Trong C++ có 7 kiểu cơ sở: char, wchar_t, int, float, double, bool, và void. Giả sử môi trường của chúng ta là một môi trường 32-bit thông thường:
    • char: các biến kiểu char có dung lượng 1 byte, được dùng để chứa các ký tự ASCII 8-bit (characters) hay bất kỳ một lượng 8-bit nào khác.
    • wchar_t: các biến kiểu wchar_t có dung lượng 2 bytes, được dùng để lưu giữ các ký tự Unicode (wide characters). Lý do là số lượng các ký tự ASCII là quá ít, không đủ để biểu diễn hết tất cả các ký tự của các ngôn ngữ và các ký hiệu khoa học. Vì vậy bảng mã Unicode được đề xuất để giải quyết vấn đề trên.
    • int: các biến kiểu int có dung lượng 4 bytes, được dùng để lưu trữ các giá nguyên (integers). Các biến này thường được dùng để điều khiển vòng lặp hoặc dùng trong các biểu thức điều kiện. Về bản chất, kiểu char cũng là một kiểu số nguyên.
    • float: các biến float có dung lượng 4 bytes, được dùng để lưu trữ các giá trị thực, dấu chấm động, độ chính xác đơn (floating-point number).
    • double: các biến double có dung lượng 8 bytes, được dùng để lưu trữ các giá trị thực, dấu chấm động, độ chính xác kép (double floating-point numbers).
    • bool: các biến kiểu bool (boolean) chỉ nhận một trong hai giá trị: true hoặc false. Theo quy ước của C/C++ thì giá trị zero sẽ ứng với false, còn những giá trị non-zero sẽ tương ứng với true. Thông thường khi convert từ bool sang int thì true bằng 1 còn false bằng 0.
    • void: void là một kiểu khá đặc biệt trong C/C++. Nó được gọi là kiểu không có giá trị (valueless). Kiểu void thường được dùng để khai báo các hàm không trả về giá trị, ép kiểu con trỏ để in ra địa chỉ, …


    2. Khai báo biến
    Cú pháp chung của khai báo biến là:
    C++ Code:
    1. <kiểu_dữ_liệu> <danh_sách_các_biến>;
    2. Ví dụ:
    3. int a, b, c;
    4. char ch;
    Ta cũng có thể khởi tạo giá trị cho biến ngay khi khai báo:
    C++ Code:
    1. int a=10, b=50;
    2. char ch=’T’; // ký tự phải được đặt trong dấu nháy đơn

    3. Một số modifier cho kiểu dữ liệu
    C++ cho phép “chế biến” các kiểu char, int, double bằng cách thêm modifier vào trước những kiểu dữ liệu này. Có 4 modifiers là: signed, unsigned, short, long.
    Với kiểu int: có thể sử dụng được với cả 4 modifiers trên.
    C++ Code:
    1. signed int x; // x được khai báo là kiểu nguyên có dấu.
    2. unsigned int x; // x được khai báo là kiểu nguyên không dấu.
    3. short int x; // x được khai báo là kiểu nguyên ngắn
    4. long int x; // x được khai báo là kiểu nguyên dài
    Bây giờ ta sẽ xem xét chi tiết. Kiểu int mặc định là số nguyên có dấu, vì vậy khai báo signed int là không cần thiết, chỉ int là đủ. Khi đó một biến kiểu int sẽ chứa được những giá trị nằm trong miền -32,768 đến 32,767. Còn khi khai báo unsigned int, số nguyên sẽ được hiểu là không dấu. Do đó miền giá trị nó sẽ mở rộng gấp đôi vì không phải tốn một bit làm bit dấu. Về vấn đề này các bạn cần đọc lại cách biểu diễn số nguyên dưới dạng nhị phân, mã bù một, bù hai … Mình sẽ không nói sâu về vấn đề này vì nó nằm ngoài phạm vi topic. Còn về modifier short và long thì theo quy ước thông thường của các compiler C++: kiểu long int sẽ có dung lượng tối thiểu bằng int, còn kiểu int sẽ có dung lượng tối thiểu bằng short int. Các bạn có thể kiểm tra điều này bằng toán tử sizeof. Đây là chương trình mình test thử trên IDE – Dev C++ 4.9.9.2
    C++ Code:
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. int main(){
    5.    
    6.     cout << "short int: " << sizeof(short int) << endl;
    7.     cout << "int: " << sizeof(int) << endl;
    8.     cout << "long int: " << sizeof(long int) << endl;
    9.  
    10.     return 0;
    11. }
    Kết quả:
    short int: 2
    int: 4
    long int: 4
    Đối với kiểu char: ta có thể sử dụng signed và unsigned. Thông thường mặc định char là signed. Vì vậy không cần thiết phải thêm modifier signed, miền biểu diễn của char là từ -128 đến 127. Nếu sử dụng unsigned thì miền biểu diễn sẽ được mở rộng gấp đôi, từ 0 đến 255.

    Đối với double: ta có thể dử dụng long. Một biến kiểu long double có dung lượng 12 bytes, nghĩa là được mở rộng thêm 4 bytes so với double (8 bytes).
    Ta cũng có thể sử dụng nhiều modifier cho một kiểu dữ liệu nếu cần thiết và hợp lệ. Ví dụ:
    =c++ Code:
    1. unsigned long int;
    2. unsigned short int;
    Dựa vào miền biểu diễn của các kiểu dữ liệu các bạn có thể giải thích được một số hiện tượng như trong chương trình sau:
    C++ Code:
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. int main(){
    5.    
    6.     char x=126;
    7.     x++; // bây giờ x=127
    8.     cout << (int)x << endl; // in ra số 127
    9.     x++; // ta hy vọng x bây giờ là 128
    10.     cout << (int)x << endl; // x có bằng 128 ???
    11.  
    12.     return 0;
    13. }
    Đây là kết quả (lưu ý rằng mình đã nói từ đầu, bài viết này được viết trong môi trường 32-bit điển hình. Hiện tại mình đang dùng Windows 7 _ Ultimate _ Copyright by Microsoft _ 15,000 VNĐ mua ở đại lý phân phối chính thức trên đường Lê Thanh Nghị).
    127
    -128
    Hiện tượng này gọi là “tràn số” (overflow), vì giá trị của biến vượt ngoài miền biểu diễn mà kiểu char quy định. Vì vậy, giá trị của biến bị “quay vòng” lại. Nói chung các bạn không cần confused quá nhiều về vấn đề dung lượng chính xác cho một kiểu dữ liệu cơ bản như in, float hay double là bao nhiêu. Điều này tùy thuộc vào trình biên dịch và platform của bạn.

    4. Các toán tử (operators)
    Kiểu dữ liệu không chỉ quy định miền biểu diễn của các biến thuộc kiểu đó mà nó con quy định các phép toán (hay toán tử - operators) được phép thao tác trên những biến đó. Ví dụ ta có thể lấy phần dư của một số kiểu int bằng toán tử % nhưng không thể làm điều này với kiểu double. Các toán tử của C++ có thể phân thành ba loại: toán tử số học (arithmetic), toán tử quan hệ và logic (relational and logical), và các toán tử thao tác trên bit (bitwise).

    Toán tử số học (arithmetic operator): bao gồm
    • Addition (+): toán tử cộng
    • Substraction (-): toán tử trừ
    • Multiplication (*): toán tử nhân
    • Division (/): toán tử chia
    • Modulus (%): toán tử chia dư
    • Increment (++): toán tử tăng một đơn vị
    • Decrement (--): toán tử giảm một đơn vị
    • Negative (-): toán tử phủ định một ngôi

    Chú ý rằng toán tử modulus (%) chỉ áp dụng được với giá trị nguyên (char, int, long, bool), không áp dụng được với float hay double. Nó trả về kết quả là phần dư của phép chia hai số nguyên. Ví dụ
    C++ Code:
    1. int x=5, y=3;
    2. cout << x%y << endl; // in ra kết quả là 2, vì 5 chia 3 dư 2
    Toán tử division (/) khi áp dụng với số nguyên thì sẽ là phép chia nguyên. Nghĩa là kết quả trả về sẽ là phần nguyên của phép chia, muốn thu được kết quả chính xác ta phải “ép kiểu”. Ví dụ chương trình sau:
    C++ Code:
    1. int x=5, y=2;
    2. cout << x/y << endl; // in ra 2
    3. cout << (double)x/y << endl; // in ra 2.5
    Toán tử ++ và -- là hai toán tử chỉ có trong C++. Nó cho phép viết những câu lệnh hết sức súc tích. Nó có thể dùng cả ở dạng tiền tố (prefix) lẫn hậu tố (postfix).
    C++ Code:
    1. ++x hoặc x++ : tương đương với x=x+1;
    2. --x hoặc x-- : tương đương với x=x-1;
    Hầu hết các compiler C++ đều sinh mã máy rất nhanh và hiệu quả cho cho các toán tử ++ và --. Vì vậy câu lệnh x++; hoặc ++x; sẽ nâng cao hiệu suất chương trình hơn là sử dụng câu lệnh gán x=x+1.

    Nếu các câu lệnh tăng/giảm giá trị của biến đứng độc lập, nghĩa là không được sử dụng trong các biểu thức tính toán, thì cách viết tiền tố và hậu tố là hoàn toàn như nhau. Tuy nhiên, nếu câu lệnh tăng/giảm giá trị này nằm trong biểu thức tính toán thì sẽ có sự khác biệt. Dạng tiền tố sẽ tăng giá trị của biến lên 1 đơn vị rồi mới sử dụng, còn dạng hậu tố sử dụng giá trị của biến xong rồi mới tăng giá trị biến lên 1. Xem xét chương trình sau.
    C++ Code:
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. int main(){
    5.     int a=100;
    6.     int x=5, y=5;
    7.    
    8.     cout << a*(x++) << endl; // in ra 500 vì dùng x=5 trước rồi mới tăng x thành 6
    9.     cout << a*(++y) << endl; // in ra 600 vì tăng y lên thành 6 rồi mới sử dụng
    10.  
    11.     return 0;
    12. }

    Toán tử quan hệ và logic (relational and logic operators)
    Các toán tử quan hệ thể hiện mối quan hệ giữa giá trị này với giá trị khác. Ví dụ: x>=5; y!=z; … Các toán tử logic thể hiện cách mà các giá trị logic (true/false) liên kết với nhau. Ví dụ (x>=3) && (x<=10), … Vì các toán tử quan hệ sinh ra các giá trị true/false nên nó thường đi kèm với các toán tử logic. Các toán tử quan hệ của C++ bao gồm: >, >=, <, <=, ==, !=. Các toán tử logic bao gồm: && (and), || (or), ! (not). Ý nghĩa của chúng hoàn toàn như trong đại số.

    Hêt bài 8
    Đã được chỉnh sửa lần cuối bởi first_pace : 08-03-2011 lúc 03:21 PM.
    Vấn đề không phải là bước nhanh, mà là luôn luôn bước

  2. #12
    Ngày gia nhập
    02 2011
    Nơi ở
    Hà Nội
    Bài viết
    67

    Mặc định Cấu trúc lựa chọn (rẽ nhánh)

    BÀI 9a. CÁC CẤU TRÚC ĐIỀU KHIỂN (PART 1)


    CONTROL FLOWS


    - Cấu trúc rẽ nhánh -

    Như mình đã nói trong bài 1, lập trình cấu trúc (structured programming) được xây dựng dựa theo mô hình toán học của Bohm và Guiseppe. Theo đó một chương trình máy tính có thể được viết dựa trên ba cấu trúc là: tuần tự, rẽ nhánh và lặp. C++ được thiết kế không chỉ hỗ trợ lập trình hướng đối tượng mà còn cả cho lập trình cấu trúc vì vậy nó cung cấp những cấu trúc điều khiển (control flows) để thực hiện việc cài đặt các cấu trúc trên. Bài này mình sẽ giới thiệu chi tiết cách sử dụng cũng như những đánh giá sơ lược về các cấu trúc lựa chọn (selection), lặp (iteration) và các lệnh nhảy (jump statements).

    1. Các lệnh lựa chọn (selection statements)
    Trong cuộc sống chúng ta luôn luôn phải ra những quyết định. Nếu bạn gái bạn phải lòng một gã nào đó bạn sẽ làm gì? Có rất nhiều phương án cho bạn lựa chọn:
    • A: tao sẽ giết chết thằng khốn đó ngay lập tức (hic, sư phụ thật dũng cảm và … dã man )
    • B: kệ chúng nó đi, nó đã muốn thế thì tao cũng chẳng giữ làm gì (bạn thật là độ lượng )
    • C: tao sẽ chứng minh cho bạn gái của tao thấy rằng thằng khốn đó không đáng để yêu. Tuy tao không giàu bằng nó, không đẹp trai bằng nó, không khéo ăn nói bằng nó, … blah, blah … nhưng chắc chắn một điều tao hơn nó. Đó là tao … biết lập trình và biết ít nhất 6 ngôn ngữ: C, C++, C#, Java, PHP, và tiếng Việt. (hự, cái khoản này thì .. hic hic )

    Trong lập trình cũng vậy, bạn sẽ phải đưa ra những lựa chọn trong những hoàn cảnh cụ thể. C++ cung cấp 2 lệnh lựa chọn là if (mà dạng đầy đủ là if … else) và switch.

    a. Câu lệnh if (if statement)
    Cú pháp cho câu lệnh if là:
    C++ Code:
    1. // dạng đơn
    2. if(condition){
    3.      // câu lệnh ở đây
    4. }
    5.  
    6. // dạng đầy đủ
    7. if(condition){
    8.     // câu lệnh ở đây
    9. }
    10. else{
    11.     // câu lệnh ở đây
    12. }
    Hoạt động của câu lệnh if (dạng đầy đủ) như sau: nếu điều kiện đúng (true) thì chương trình sẽ thực hiện câu lệnh ngay sau if nếu sai thì nó sẽ thực hiện câu lệnh ngay sau else. Chương trình sau đây minh họa cho hoạt động của lệnh if. Nó nhận một số nguyên nhập và từ bàn phím và xác định xem số đó là chẵn (even) hay lẻ (odd).
    C++ Code:
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. int main(){
    5.     int n;
    6.    
    7.     cout << "Enter an integer: ";
    8.     cin >> n;
    9.    
    10.     if(n%2){
    11.         cout << n << " is ODD" << endl;
    12.     }
    13.     else{
    14.         cout << n << " is EVEN" << endl;
    15.     }
    16.    
    17.     return 0;
    18. }
    Các câu lệnh if có thể lồng nhau (nested if) rất hay gặp. Một điều cần chú ý là else sẽ tương ứng với if gần nó nhất. Chương trình sau mô tả lệnh if lồng nhau.
    C++ Code:
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. int main(){
    5.     int n;
    6.    
    7.     cout << "Enter an integer in range from 0 to 2: "; // gợi ý nhập vào 1 số nguyên từ 0 đến 2
    8.     cin >> n;
    9.    
    10.     if(n==0){ // nếu là số 0
    11.         cout << "Zero" << endl;
    12.     }
    13.     else{ // nếu không là số 0
    14.         if(n==1){ // nếu là số 1
    15.             cout << "One" << endl;
    16.         }
    17.         else{ // nếu cũng không là số 1
    18.             if(n==2){ // nếu là số 2
    19.                 cout << "Two" << endl;
    20.             }
    21.             else{ // nếu cũng không là số 2
    22.                 cout << "Ivalid" << endl;
    23.             }
    24.         }
    25.     }
    26.  
    27.     return 0;
    28. }
    Tuy nhiên, nhìn chương trình trên thật “rối rắm” và đau mắt. Một phong cách lập trình biểu thị cấu trúc if-else-if lồng nhau hay được sử dụng là “thang if-else-if” (if-else-if-ladder). Chương trình trên sẽ được viết lại như sau.
    C++ Code:
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. int main(){
    5.     int n;
    6.    
    7.     cout << "Enter an integer in range from 0 to 2: ";
    8.     cin >> n;
    9.    
    10.     if(n==0){
    11.         cout << "Zero" << endl;
    12.     }
    13.     else if(n==1){
    14.         cout << "One" << endl;
    15.     }
    16.     else if(n==2){
    17.         cout << "Two" << endl;
    18.     }
    19.     else{
    20.         cout << "Invalid" << endl;
    21.     }
    22.    
    23.     return 0;
    24. }

    b. Câu lệnh switch
    Khi phải đối mặt với nhiều hơn hai lựa chọn thì ta có thể dùng thang if-else-if như trên. Tuy nhiên C++ cung cấp một cách khác, trong một số trường hợp hiệu quả hơn, đó là lệnh switch. Cú pháp cho câu lệnh switch như sau.
    C++ Code:
    1. switch(expression){
    2.         case constant1:
    3.             // các câu lệnh ở đây
    4.             break;
    5.         case constant2:
    6.             // các câu lệnh ở đây
    7.             break;
    8.         case constant3:
    9.             // các câu lệnh ở đây
    10.             break;
    11.         ...
    12.         default:
    13.             // các câu lệnh ở đây
    14. }
    Biểu thức expression của switch phải có giá trị nguyên (có thể là char, integer hoặc bool). Hoạt động câu lệnh switch như sau: giá trị của expression sẽ được so sánh lần lượt với giá trị của các case (tức là các constant1, constant2, …). Nếu phát hiện ra trường hợp nào “khớp” thì những câu lệnh sau case đó được thực hiện. Chú ý rằng nếu như không có lệnh nào “ngắt” (ví dụ break; chẳng hạn) thì nó cứ thế chạy tuột đến cuối thậm chí những case sau đó không khớp. Các câu lệnh sau nhãn default sẽ được thực hiện nếu như chạy qua hết các case mà không có trường hợp nào khớp. Chúng ta sẽ xem xét hai chương trình sử dụng switch sau đây.
    C++ Code:
    1. // version 1 – without break
    2. #include <iostream>
    3. using namespace std;
    4.  
    5. int main(){
    6.     int n;
    7.    
    8.     cout << "Enter an integer in range 0-5: ";
    9.     cin >>n;
    10.    
    11.     switch(n){
    12.         case 0:
    13.             cout << "Zero" << endl;
    14.         case 1:
    15.             cout << "One" << endl;
    16.         case 2:
    17.             cout << "Two" << endl;
    18.         case 3:
    19.             cout << "Three" << endl;
    20.         case 4:
    21.             cout << "Four" << endl;
    22.         case 5:
    23.             cout << "Five" << endl;
    24.         default:
    25.             cout << "Invalid" << endl;
    26.     }
    27.    
    28.     return 0;
    29. }
    Điều gì sẽ xảy ra khi bạn nhập vào số 3. Kết quả sẽ như sau:
    Enter an integer in range 0-5: 3

    Three
    Four
    Five
    Invalid
    Như mình đã nói ở trên, một khi đã khớp với một trường hợp nào đó thì “tất cả những câu lệnh phía sau case đó sẽ được thực thi”. Vì vậy ta cần phải có cái gì đó để “ngắt” tại những thời điểm phù hợp và giải pháp là sử dụng lệnh break. Hãy xem xét phiên bản thứ hai của chương trình trên.
    C++ Code:
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. int main(){
    5.     int n;
    6.    
    7.     cout << "Enter an integer in range 0-5: ";
    8.     cin >>n;
    9.    
    10.     switch(n){
    11.         case 0:
    12.             cout << "Zero" << endl;
    13.             break;
    14.         case 1:
    15.             cout << "One" << endl;
    16.             break;
    17.         case 2:
    18.             cout << "Two" << endl;
    19.             break;
    20.         case 3:
    21.             cout << "Three" << endl;
    22.             break;
    23.         case 4:
    24.             cout << "Four" << endl;
    25.             break;
    26.         case 5:
    27.             cout << "Five" << endl;
    28.             break;
    29.         default:
    30.             cout << "Invalid" << endl;
    31.     }
    32.    
    33.     return 0;
    34. }
    Bằng cách sử dụng lệnh break; chương trình sẽ hoạt động theo ý muốn của chúng ta. Khi gặp lệnh break; chương trình sẽ nhảy ra khỏi lệnh switch mà không thực thi những câu lệnh còn lại trong khối.

    Bây giờ ta sẽ so sánh một chút giữa lệnh if và switch:
    • Lệnh switch chỉ có thể kiểm tra tính bằng nhau để ra quyết định (khớp giữa giá trị của expression với giá trị của case), còn if có thể sử dụng mọi toán tử quan hệ và logic.
    • Biểu thức trong switch chỉ có thể nhận giá trị nguyên, còn biểu thức trong if có thể là bất kỳ kiểu hợp lệ nào, những giá trị khác 0 sẽ là true, bằng 0 sẽ là false.
    • Dùng switch (nếu có thể) sẽ hiệu quả hơn if lồng nhau. Chương trình trông sẽ sáng sủa hơn, kể cả là if-else-if ladder.


    Hết bài 9a
    Đã được chỉnh sửa lần cuối bởi first_pace : 08-03-2011 lúc 03:22 PM.
    Vấn đề không phải là bước nhanh, mà là luôn luôn bước

  3. #13
    Ngày gia nhập
    02 2011
    Nơi ở
    Hà Nội
    Bài viết
    67

    Mặc định Cấu trúc lặp

    BÀI 9a. CÁC CẤU TRÚC ĐIỀU KHIỂN (PART 2)


    CONTROL FLOWS


    - Cấu trúc lặp -


    2. Các câu lệnh lặp (iteration statements)
    C++ cung cấp 3 câu lệnh lặp là for, while, và do while. Một đoạn chương trình viết được bởi một trong ba câu lệnh trên thì cũng có thể viết được qua hai câu lệnh còn lại. Chúng là tương đương nhau. Điều này thuộc về vấn đề programming style. Trong một số trường hợp, vòng lặp này có thể thích hợp hơn những cái còn lại.

    a. Vòng lặp for
    Cú pháp chung cho vòng lặp for như sau:
    C++ Code:
    1. for(initialization, condition, increment){
    2.     // câu lệnh ở đây
    3. }
    • Initialization (biểu thức khởi tạo): thông thường đó là câu lệnh gán (assignment) khởi tạo giá trị cho biến điều kiển vòng lặp (control variable)
    • Condition (điều kiện tiếp tục vòng lặp): thông thường trước mỗi lần lặp, biến điều khiển phải so sánh giá trị của mình với một giá trị nào đó để quyết định xem vòng lặp có được lặp lại nữa hay không. Đây chính là biểu thức điều kiện tiếp tục vòng lặp.
    • Increment (thay đổi biến điều khiển): sau mỗi lần lặp biến điều khiển phải thay đổi theo một cách thức nào đó, ví dụ như tăng lên 1 đơn vị, hoặc giảm xuống 5 đơn vị chẳng hạn. Và điều này được thực hiện trong phần Increment.


    Vòng for sẽ thực hiện việc lặp cho đến khi biểu thức điều kiện tiếp tục vòng lặp không còn đúng nữa. Chương trình sau mô tả sự họa động của vòng lặp for. Nó in ra bảng chữ cái (hoa) từ A-Z.
    C++ Code:
    1. // chương trình in ra bảng chữ cái (chữ hoa)
    2. #include <iostream>
    3. using namespace std;
    4.  
    5. int main(){
    6.    
    7.     for(char i='A'; i<='Z'; i++){
    8.         cout << i << endl;
    9.     }
    10.    
    11.     return 0;
    12. }
    Bây giờ ta sẽ chế biến vòng for một chút để biết thêm một số cách sử dụng vòng for. Hãy xem xét chương trình sau:
    C++ Code:
    1. // in ra các cặp số tự nhiên có tổng bằng 9
    2. #include <iostream>
    3. using namespace std;
    4.  
    5. int main(){
    6.    
    7.     int x, y;
    8.     for(x=0, y=9; (x<=9) && (y>=0); x++, y--){
    9.         cout << "x= " << x << "   " << "y= " << y << endl;
    10.     }
    11.    
    12.     system("pause");
    13.     return 0;
    14. }

    Để ý header của vòng for
    C++ Code:
    1. for(x=0, y=9; (x<=9) && (y>=0); x++, y--)
    Nhận thấy biểu thức khởi tạo có tới hai câu lệnh gán x=0 và y=9 được phân cách nhau bởi dấu phẩy (,). C++ cho phép chúng ta khởi tạo bao nhiêu biến cũng được miễn là các câu lệnh gán này phân cách nhau bởi dấu phẩy. Dấu phẩy này thực ra là một toán tử (comma operator), nó quy định các toán hạng ở hai bên của nó được đánh giá từ trái sang phải, có thể phát biểu như sau: “hãy thực hiện việc này, rồi việc này, rồi việc này …. “, trong trường hợp của ta thì nó có nghĩa là: “hãy gán x=0 rồi gán y=9”. Tương tự biểu thức thay đổi biến điều khiển cũng vậy: “hãy tăng x lên 1 đơn vị rồi giảm y đi một đơn vị”.

    Header của vòng for có thể khuyết đi một số phần, hoặc khuyết hết cũng chả sao. Miễn là dấu chấm phẩy ngăn cách giữa các phần vẫn đủ là được. Ví dụ chương trình sau.
    C++ Code:
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. int main(){
    5.    
    6.     int i=0;
    7.     for( ; ; ){ // header vòng for rỗng
    8.         int n;
    9.         cout << "\nIf you enter a negative number, program will exit";
    10.         cout << "\nYour number: ";
    11.         cin >> n;
    12.        
    13.         if(n<0)
    14.             break;
    15.         else
    16.             cout << "Number is: " << n << endl;
    17.     }
    18.     return 0;
    19. }

    b. Vòng lặp while
    Cú pháp cho vòng lặp while như sau.
    C++ Code:
    1. while(condition){
    2.     // câu lệnh ở đây
    3. }
    Hoạt động của vòng while rất đơn giản. Nếu điều kiện vẫn còn đúng thì vòng while vẫn còn được lặp lại, tức là các câu lệnh bên trong vòng while vẫn được thực hiện, nó sẽ lặp cho tới khi điều kiện sai mới thôi. Chương trình sau sẽ sử dụng vòng while để thực hiện việc in ra số đảo ngược của một số nhập vào từ bàn phím.
    C++ Code:
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. int main(){
    5.     int n;
    6.    
    7.     cout<< "Enter an integer: ";
    8.     cin >> n;
    9.    
    10.     while(n){
    11.         cout << n%10;
    12.         n/=10;
    13.     }
    14.     cout << endl;
    15.  
    16.     return 0;
    17. }

    c. Vòng lặp do while
    Vòng lặp for và while đều kiểm tra điều kiện ở đầu vòng lặp, vì vậy có thể xảy ra khả năng ngay ở lần kiểm tra đầu tiên điều kiện tiếp tục vòng lặp đã không thỏa mãn. Và do đó các câu lệnh trong thân vòng for và do while sẽ không được thực hiện lần nào. Khác với for và while, vòng lặp đo while thực hiện câu lệnh trước rồi mới kiểm tra điều kiện ở cuối vòng lặp. Vì vậy các câu lệnh bên trong vòng do while luôn được thực hiện ít nhất là một lần. Ngoài khác biệt duy nhất đó ra thì while và do while là hoàn toàn giống nhau. Cú pháp của vòng do while là.
    C++ Code:
    1. do{
    2.    // câu lệnh ở đây
    3. } while(condition);

    3. Các câu lệnh nhảy (jump statements)
    Khi chương trình thực thi một vòng lặp, nếu thỏa mãn một số điều kiện nào đó nó có thể thoát ra ngoài vòng lặp mà không cần phải thực hiện nốt các câu lệnh còn lại trong thân vòng lặp. Để làm được được ddieuf này C++ hỗ trợ 4 câu lệnh nhảy (jump statements) là: break, continue, goto và return. Tuy nhiên return là một trường hợp đặc biệt được sử dụng để trả lại giá trị cho hàm nên mình sẽ không nói ở đây.

    a. Sử dụng continue
    Chúng ta dùng continue khi muốn kết thúc sớm lần lặp hiện tại để chuyển qua lần lặp tiếp theo. Khi gặp câu lệnh continue; chương trình sẽ bỏ qua mọi câu lệnh còn lại bên trong vòng lặp để chuyển qua lần lặp mới. Chương trình sau mô tả hoạt động của continue.
    C++ Code:
    1. // in ra các số chia hết cho 4
    2. #include <iostream>
    3. using namespace std;
    4.  
    5. int main(){
    6.    
    7.     for(int i=0; i<=100; i++){
    8.         if(i%4) continue; // nếu không chia hết cho 4
    9.         cout << i << endl;
    10.     }
    11.    
    12.     system("pause");
    13.     return 0;
    14. }

    b. Sử dụng break
    continue chỉ dùng để kết thúc sớm một lần lặp, rồi lại chuyển ngay qua lần lặp tiếp theo. Để thoát hoàn toàn khỏi vòng lặp thì ta phải sử dụng lệnh break. Khi chương trình gặp câu lệnh break, nó sẽ thoát luôn khỏi vòng lặp và tiếp tục thực thi câu lệnh tiếp theo ngay sau vòng lặp. Điều này hoàn toàn giống như trong ví dụ về lệnh switch mà mình đã đề cập ở trên. Chương trình sau mô tả cách hoạt động của lệnh break cho vòng lặp while.
    C++ Code:
    1. // chương trình tìm số tự nhiên có 3 chữ số nhỏ nhất chia hết cho 13
    2. #include <iostream>
    3. using namespace std;
    4.  
    5. int main(){
    6.     int i=100; // 100 là số tự nhiên nhỏ nhất có 3 chữ số
    7.    
    8.     while(i<=999){ // 999 là số tự nhiên lớn nhất có 3 chữ số
    9.         if(i%13==0){ // nếu i chia hết cho 13
    10.             cout << i << endl;
    11.             break;
    12.         }
    13.         i++; // nếu không duyệt tiếp
    14.     }
    15.    
    16.     return 0;
    17. }

    Chú ý: khi có nhiều vòng lặp lồng nhau thì lệnh break chỉ thoát ra khỏi vòng lặp hiện tại, chứ không phải thoát hết ra khỏi mọi vòng lặp.

    c. Sử dụng goto
    Câu lệnh goto là lệnh rẽ nhánh vô điều kiện (unconditional branch statement). Kinh nghiệm của các tiền bối đi trước cho thấy sử dụng lệnh goto sẽ là chương trình trở nên rối rắm và khó kiểm soát. Người ta thường gọi những đoạn code tối nghĩa như vậy là “spaghetti code”. Tuy nhiên có một vài trường hợp nó tỏ ra khá hữu dụng. Ví dụ khi ta muốn thoát từ vòng lặp trong cùng của một khối các vòng lặp lồng nhau, ra bên ngoài thì dùng goto là một phương án tốt, hoặc khi tốc độ xử lý chương trình là mối quan tâm hàng đầu thì goto có thể được chọn. Để sử dụng goto ta chỉ cần chỉ định một nhãn (label) để nó “nhảy tới”. Một nhãn là một định danh hợp lệ theo sau bởi dấu hai chấm. Chương trình sau in ra 100 số nguyên dương đầu tiên sử dụng lệnh goto.
    C++ Code:
    1. // chương trình in ra 100 số nguyên dương đầu tiên
    2. #include <iostream>
    3. using namespace std;
    4.  
    5. int main(){
    6.     int i=1;
    7.    
    8.     begin: // nhãn
    9.         cout << i << endl;
    10.         i++;
    11.         if(i<=100)
    12.             goto begin; // nhảy tới nhãn
    13.  
    14.         return 0;
    15. }
    Chú ý: goto chỉ nhảy từ bên trong khối lệnh ra bên ngoài khối lệnh chứ không thể nhảy từ bên ngoài vào bên trong khối lệnh được.
    Hết bài 9b
    Đã được chỉnh sửa lần cuối bởi first_pace : 08-03-2011 lúc 03:22 PM.
    Vấn đề không phải là bước nhanh, mà là luôn luôn bước

  4. #14
    Ngày gia nhập
    02 2011
    Nơi ở
    Hà Nội
    Bài viết
    67

    Mặc định Con trỏ trong C++

    BÀI 10a. CON TRỎ (PART1)


    Con trỏ (pointer) là một đặc điểm vô cùng mạnh mẽ của C/C++. Nó cho phép chúng ta giải quyết nhiều vấn đề phức tạp một cách hiệu quả và linh hoạt. Có thể kể ra một vài ứng dụng cơ bản của con trỏ như: cho phép truyền đối số theo tham chiếu, tạo và thao tác trên các cấu trúc dữ liệu động (dynamic data structure) như danh sách liên kết (linked list), ngăn xếp (stack), hàng đợi (queue), cài đặt các cơ sở dữ liệu, con trỏ hàm … Bài này mình sẽ giới thiệu những đặc điểm và cách sử dụng cơ bản của con trỏ

    1. Con trỏ là gì?
    Con trỏ hiểu nôm na là một biến lưu trữ địa chỉ của một vùng nhớ. Thông thường nó lưu trữ địa chỉ của các biến khác. Khi một con trỏ ptr lưu trữ địa chỉ của biến x, ta nói “ptr trỏ tới x” (ptr points to x). Do biến có thể thuộc nhiều kiểu dữ liệu khác nhau nên sẽ có nhiều kiểu con trỏ tương ứng. Đối với một kiểu T, thì kiểu con trỏ tương ứng sẽ là T*, và được gọi là “con trỏ đến kiểu T” (pointer to T). Điều này có nghĩa là, con trỏ là một biến (như một biến bình thường khác) có kiểu T* và có thể lưu trữ địa chỉ của một đối tượng kiểu T. Ví dụ sau minh họa cách khai báo và gán giá trị cho con trỏ.
    C++ Code:
    1. int x=5; // khai báo và khởi tạo một biến nguyên
    2. int* pi; // khai báo một con trỏ kiểu int*
    3. pi=&x; // gán địa chỉ của x cho pi
    Đoạn code ngắn trên có một số điều cần lưu ý:
    Thứ nhất: khi khai báo con trỏ chúng viết là int* pi, hay int *pi ? (dấu * viết gần kiểu int hay gần biến pi?). Xin trả lời là viết kiểu nào cũng được, compiler nó hiểu hai cách viết là như nhau. Vấn đề ở đây chỉ là style. Tuy nhiên, tại sao C++ không thống nhất cách viết mà bày vẽ ra những hai cách làm gì cho nó mệt? Để hiểu rõ vấn đề này cần phải nhìn lại lịch sử C++ một chút. C++ được Bjarne Stroustrup phát minh vào cuối những năm 70 của thế kỷ trước (khoảng năm 1979), nhưng mãi đến 1994 ANSI/ ISO mới dự thảo chuẩn hóa cho C++. Và quá trình chuẩn hóa này diễn ra trong tới 4 năm trời, chỉ vì một gã điên khùng có tên là Alexander Stepanove tự dưng nghĩ ra STL (Standard Template Library) và việc phải xem xét thêm một thư viện khổng lồ này làm chậm lại đáng kể quy trình chuẩn hóa. Nghĩa là từ lúc C++ ra đời đến phiên bản chuẩn hóa đầu tiên của nó mất tới 30 năm! Trong 30 năm đó, người ta đã dùng C++ như một ngôn ngữ chuyên nghiệp để lập trình và có tới hàng tá phiên bản cài đặt khác nhau của C++ được tung ra. Rõ ràng với một lượng code lớn như vậy hiện hữu trong các hệ thống máy tính đang vận hành thì chuẩn mới của C++ không thể đặt ra những cú pháp mới tinh được, mà phải dựa trên những cái có sẵn, nếu hợp lý thì cho làm chuẩn. Trong bản phác thảo đầu tiên của Stroustrup thì dấu * được viết gần với kiểu dữ liệu (int* pi thay vì int *pi) điều này phản ánh * là một phần của kiểu dữ liệu (tức kiểu int*) chứ không phải là một ký hiệu lạ, hay một toán tử mới. Và mình cũng thích viết theo cách này hơn. Trong tất cả các đoạn code của mình khi khai báo con trỏ hay tham chiếu thì dấu * hoặc & luôn được viết cùng với kiểu dữ liệu vì điều này phản ánh bản chất thực sự của con trỏ và tham chiếu. Tuy nhiên một số người lại thích viết theo kiểu: int *pi; hơn. Cách viết này không phải là không có cái lý của nó. Hãy xem xét đoạn code sau.
    C++ Code:
    1. int* pi, a; // pi là một con trỏ int*, còn a là một biến int
    2. int *pi, *a; // cả pi và a đều là con trỏ int
    Câu lệnh thứ nhất rất dễ làm cho người ta lầm tưởng pi và a đều là hai con trỏ kiểu int*, nhưng thực tế chỉ có pi là con trỏ, con a lại là một biến nguyên. Vì vậy người ta nghĩ viết theo cách thứ hai sẽ hạn chế được những hiểu lầm như vậy. Tuy nhiên khi bạn đã hiểu rõ bản chất của con trỏ rồi thì vấn đề chỉ còn là phong cách. Bạn muốn viết theo style nào cũng được. Riêng mình vẫn thích cách viết int* hơn. Thêm nữa, mỗi khai báo nên đặt trên một dòng, để còn tiện comment khi cần thiết. Nhà giàu tiếc gì con lợn con, hờ hờ . Như vậy sẽ không còn sự nhập nhằng nữa.
    C++ Code:
    1. int* p1; // comment vào chỗ này
    2. int* p2; // comment vào chỗ này
    Thứ hai: để ý đến câu lệnh
    C++ Code:
    1. pi=&x;
    Câu lệnh gán này thực hiện gán địa chỉ của biến x cho con trỏ pi, sau câu lệnh này ta nói pi trỏ đến x. Toán tử & là toán tử lấy địa chỉ, nó là toán tử một ngôi (unary operator) trả về địa chỉ của toán hạng (operand) bên phải nó (ở đây là x).

    Chúng ta sẽ xem xét thêm một số cú pháp khai báo các kiểu con trỏ hay gặp bao gồm con trỏ đa cấp, hàm trả về con trỏ, con trỏ hàm, mảng con trỏ, ...
    C++ Code:
    1. int* pi; // con trỏ int*
    2. char** ppc; // con trỏ hai cấp – con trỏ trỏ tới con trỏ char*
    3. int* ap[100]; // mảng 100 con trỏ kiểu int*
    4. int* func(char*); // hàm func nhận đối đầu vào là con trỏ char* và trả về con trỏ int*
    5. int (*funcp)(char*); // con trỏ tới hàm funcp (hàm funcp nhận một đối là con trỏ char* và trả về kiểu int)

    2. Thao tác trên con trỏ
    Hai toán tử * và & là hai toán tử quan trọng nhất thao tác trên con trỏ. Toán tử &, như đã nói ở trên, là toán tử một ngôi, trả về địa chỉ của toán hạng bên phải nó. Để hình dung khái niệm địa chỉ ta coi bộ nhớ như một khu phố. Bộ nhớ được chia thành các thành các vùng nhớ, giống như khu phố được phân chia thành các hộ gia đình. Mỗi một vùng nhớ được đánh số thứ tự giống như mỗi hộ gia đình có một số nhà. Số thứ tự này gọi là địa chỉ của vùng nhớ đó. Ta cũng cần phân biệt giữa địa chỉ vùng nhớ và nội dung chứa trong vùng nhớ. Địa chỉ giống như số nhà còn nội dung hay giá trị (value) chứa trong vùng nhớ giống như tài sản trong nhà vậy. Câu lệnh:
    C++ Code:
    1. pi=&x;
    đơn thuần chỉ cung cấp địa chỉ của x cho pi, nó không ảnh hưởng gì đến giá trị của x cả. Giả sử ban đầu x=5 thì sau câu lệnh trên x vẫn bằng 5. Khi con trỏ pi đã có trong tay địa chỉ của x, nó có thể “đột nhập” đến biến x để xem nội dung của x là gì, thậm chí thay đổi nó. Để làm điều này ta dùng toán tử truy xuất giá trị * (dereference). Toán tử * là toán tử một ngôi, trả về nội dung chứa trong vùng nhớ mà con toán hạng bên phải nó trỏ tới. Xem xét đoạn chương trình sau:
    C++ Code:
    1. x=5;
    2. pi=&x;
    3. cout << *pi << endl; // in ra giá trị nằm trong vùng nhớ mà pi trỏ tới, tức in ra 5
    4. *pi=100; // thay đổi nội dung của x thông qua con trỏ pi
    5. cout << x << endl; // in ra nội dung x, tức 100
    Sử dụng con trỏ như trên được gọi là gián tiếp (indirection) vì nó thay đổi nội dung của biến thông qua một biến khác (biến trỏ).

    3. Chú ý về kiểu và ép kiểu con trỏ
    Một câu hỏi được đặt ra là: thông qua con trỏ ta có thể gián tiếp thao tác trên biến. Vậy giả sử ta muốn copy giá trị của một biến a kiểu int, sang cho biến b kiểu int thông qua một con trỏ thì làm thế nào trình biên dịch biết được số lượng byte cần copy là bao nhiêu? (giả thiết môi trường của ta là 32 bit). Ví dụ đoạn chương trình sau.
    C++ Code:
    1. a=5;
    2. pa=&a; // pa trỏ đến a
    3. b=*pa; // gán nội dung của biến a cho b thông qua con trỏ pa, tức b=5
    Khi muốn copy giá trị của biến a cho biến p thông qua con trỏ pa trỏ tới a thì pa phải có kiểu tương thích với kiểu của a, tức pa có kiểu int*. Khi ta dùng pa để thao tác trên một vùng nhớ nào đó, pa luôn tự nhủ rằng mình đang trỏ đến một vùng nhớ kiểu int và mình chỉ được tọc mạch vào 4 bytes thôi, đo đó nó nó sẽ copy đúng 4 bytes dữ liệu tương ứng với địa chỉ được chỉ định. Chú ý rằng địa chỉ của a mà pa trỏ tới không phải là địa chỉ của cả 4 bytes dữ liệu của a, mà là địa chỉ byte đầu tiên của vùng nhớ đó. Còn việc đếm 3 byte còn lại như thế nào thì là việc của compiler. Nếu pa là kiểu int* còn a, b là kiểu double, muốn thực hiện đoạn code trên ta phải “ép kiểu con trỏ”. Xem xét chương trình sau.
    C++ Code:
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. int main(){
    5.     double a=12.34, b;
    6.     int* pa;
    7.    
    8.     pa=(int*)&a; // ép kiểu double* thành int*
    9.     b=*pa;
    10.    
    11.     cout << b << endl;
    12.  
    13.     return 0;
    14. }
    Bạn hãy chạy thử chương trình trên và xem kết quả ! Tại sao kết quả lại sai? Rất đơn giản khi câu lệnh
    C++ Code:
    1. pa=(int*)&a;
    được thực hiện thì pa vẫn nhận đúng địa chỉ của vùng nhớ của a, nhưng khi nó mò tới vùng nhớ này, thay vì nó copy đủ 8 bytes dữ liệu của a cho b, thì nó chỉ copy 4 bytes ! (vì nó vẫn tự nhủ với mình rằng nó là con trỏ kiểu int* và nó chỉ được tọc mạch vào 4 bytes chứ không phải 8 bytes) Đây là một lỗi logic hết sức tinh vi, nếu không nắm rõ bản chất của con trỏ bạn sẽ rất dễ mắc lỗi này khi thực hiện “ép kiểu” linh tinh.

    4. Con trỏ NULL
    Sau khi một con trỏ được khai báo, nếu như ta không gán giá trị cho nó, nó sẽ chứa một giá trị tùy ý (tức trỏ lung tung tới một vùng nhớ nào đó). Nếu ta sử dụng nó trước khi gán cho nó một giá trị hợp lệ, thì không chỉ chương trình của ta có thể bị “toi” mà có khi cả hệ điều hành của ta cũng bị “đi” luôn. Bởi vì rất có thể khi khởi tạo, con trỏ trỏ tới một vùng nhớ có chứa dữ liệu quan trọng của HĐH. Và khi ta gián tiếp thay đổi nội dung vùng nhớ đó thông qua con trỏ thì hậu quả không biết đằng nào mà lần. Theo quy ước, nếu một con trỏ chứa giá trị là 0 thì nó được hiểu là không trỏ tới cái gì cả. Bởi vì một lý do hết sức đơn giản: không một đối tượng nào được phép tồn tại trong khu vực địa chỉ là 0. Bất cứ con trỏ nào đều có thể được khởi tạo là NULL khi khai báo bằng cách gán nó bằng 0. Ví dụ:
    C++ Code:
    1. int* pi=0;
    2. double* pd=0;
    3. char* pc=0;

    Hết bài 10a
    Đã được chỉnh sửa lần cuối bởi first_pace : 08-03-2011 lúc 03:23 PM.
    Vấn đề không phải là bước nhanh, mà là luôn luôn bước

  5. #15
    Ngày gia nhập
    02 2011
    Nơi ở
    Hà Nội
    Bài viết
    67

    Mặc định Con trỏ trong C++

    BÀI 10b. CON TRỎ (PART 2)



    5. Phép toán trên con trỏ
    Các phép toán hợp lệ được sử dụng trên con trỏ bao gồm
    • Cộng/trừ con trỏ với một số nguyên: pa=pb+5;
    • Trừ hai con trỏ cùng kiểu cho nhau: x=pa-pb;
    • Tăng giảm con trỏ lên một đơn vị: pa++; pb--
    • So sánh hai con trỏ: ==, !=, >, <, >=, <=


    a. Các phép toán số học trên con trỏ: +, -, ++, --
    Khi cộng/trừ một con trỏ với một số nguyên, thì con trỏ sẽ “dịch” đi một đoạn “tương ứng” (không có nghĩa là bằng nhé, chỉ “tương ứng” thôi) với số nguyên đó. Xét đoạn chương trình sau.
    C++ Code:
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. int main(){
    5.     int a[10];
    6.     int* pa=a;
    7.    
    8.     cout << "Before: " << (void*)pa << endl; // in địa chỉ pa trước khi dịch chuyển
    9.     pa=pa+5; // dịch pa đi 5 đơn vị
    10.     cout << "After: " << (void*)pa << endl; // địa chi pa sau khi  dịch chuyển
    11.        
    12.     return 0;
    13. }
    Kết quả mình test là:
    Before: 0x22ff10
    After: 0x22ff24
    Nghĩa là nó pa đã dịch đi một đoạn là 14 ứng với số hexa hay 20 với decimal, tức là dịch qua 5 vùng nhớ kiểu int (vì mỗi biến int rộng 4 bytes). Từ ví dụ trên ta thấy khoảng dịch của con trỏ phụ thuộc vào kiểu mà nó trỏ tới. pa=pa+5 không có nghĩa là nó dịch đi 5 bytes mà nó dịch đi một đoạn ứng với 5 đối tượng có kiểu int (tức 20 bytes). Tương tự cho phép toán ++ và --. Các bạn có thể viết chương trình và test thử.

    C++ không cho phép cộng hai con trỏ cho nhau, nhưng trừ hai con trỏ cho nhau thì hoàn toàn hợp lệ. Tuy nhiên phép trừ hai con trỏ “chẳng liên quan gì đến nhau” là một phép trừ vô nghĩa, mặc dù C++ không cấm điều này. Thông thường ta chỉ trừ những con trỏ liên quan đến nhau, ví dụ các con trỏ trỏ đến các phần tử trong một mảng, kết quả trả về là “khoảng cách: giữa hai con trỏ. Xét chương trình sau.
    C++ Code:
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. int main(){
    5.     int a[10];
    6.     int* p1;
    7.     int* p2;
    8.    
    9.     p1=&a[0]; // p1 trỏ đến phần tử đầu mảng
    10.     p2=&a[9]; // p2 trỏ đến phần tử cuối mảng
    11.    
    12.     cout << p2-p1 << endl; // in ra khoảng cách giữa hai con trỏ
    13.  
    14.     return 0;
    15. }
    Khi chạy chương trình trên bạn sẽ thấy kết quả là 9 ! Nghĩa là p1 và p2 cách nhau 9 đối tượng kiểu int, tức 36 bytes (chứ không phải 9 bytes).

    b. Các phép toán quan hệ trên con trỏ: ==, !=, >=, <=, >, <
    Ta có thể so sánh hai con trỏ xem nội dung của chúng (địa chỉ của vùng nhớ mà chúng trỏ tới) có bằng nhau hay không, vùng nhớ nào cao hơn, thấp hơn, … Xem xét chương trình sau.
    C++ Code:
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. int main(){
    5.     int a[10]; // mảng 10 số nguyên
    6.     int* p=&a[9]; // con trỏ p trỏ đến phần tử cuối mảng
    7.    
    8.     cout << "Input 10 integers: \n"; // nhập vào 10 số nguyên từ cuối đến đầu mảng
    9.     while(p>=a){ // khi p còn chưa trỏ về đầu mảng
    10.         cin >> *p; // nhập dữ liệu vào các phần tử tương ứng
    11.         p--; // dịch con trỏ về đầu mảng
    12.     }
    13.    
    14.     cout << "Reverse: \n"; // in ra thứ tự đảo ngược
    15.     p=a; // cho con trỏ p trỏ về đầu mảng
    16.     while(p<=&a[9]){ // chừng nào p chưa trôi về cuối mảng
    17.         cout << *p << endl; // in ra các phần tử mảng tương ứng
    18.         p++; // dịch con trỏ p về cuối mảng
    19.     }
    20.    
    21.     return 0;
    22. }
    Ngoài các phép toán trên, đối với những con trỏ trỏ đến một mảng thì ta cũng có thể dùng phép toán lấy chỉ số như thao tác với tên mảng. Ví dụ chương trình sau:
    C++ Code:
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. int main(){
    5.     int a[5];
    6.     int* pa=a; // con trỏ pa trỏ đến đầu mảng
    7.    
    8.     cout << "Input array:\n";
    9.     for(int i=0; i<5; i++){
    10.         cin >> pa[i]; // thao tác qua p, với chỉ số
    11.     }
    12.    
    13.     cout << "Ouput array:\n";
    14.     for(int i=0; i<5; i++){
    15.         cout << pa[i] << "   "; // thao tác qua p, với chỉ số
    16.     }
    17.    
    18.     return 0;
    19. }

    6. Con trỏ và hằng (pointers and constants)
    C++ cung cấp khái niệm hằng (const) để thể hiện rằng đối tượng nào đó là cố định, không thể thay đổi được. Điều này có những lợi ích nhất định tùy thuộc vào ngữ cảnh. Ví dụ: bằng việc khai báo hằng tượng trưng (symbolic constant) sẽ giúp việc viết chương trình được thuận lợi và dễ bảo trì hơn là việc “nhúng” hẳn các hằng trực tiếp vào trong code. Ví dụ:
    C++ Code:
    1. const int PI=3.14; // như thế này tốt hơn là nhúng hẳn số 3.14 và từng đoạn code
    Hoặc ví dụ trong một số trường hợp, khi ta truyền biến bằng tham chiếu cho hàm nhưng chỉ cho phép hàm đọc dữ liệu mà không cho phép chỉnh sửa dữ liệu thì tham số hình thức của hàm sẽ được khai báo là tham chiếu hằng hoặc con trỏ hằng. Sau đây là một số ví dụ khai báo hằng:
    C++ Code:
    1. const int x=12; // x là hằng nguyên, giá trị 12
    2. const char ch[]={‘I’, ‘H’, ‘A’, ‘T’, ‘E’, ‘U’}; // ch là con trỏ hằng (const pointer) trỏ tới phần tử đầu mảng
    3. const int number; // Error: phải khởi tạo hằng ngay khi khai báo
    Khi sử dụng con trỏ có hai đối tượng cần quan tâm: thứ nhất là bản thân con trỏ và thứ hai là đối tượng mà con trỏ trỏ tới. Ta phân biệt hai khái niệm: con trỏ hằng (const pointer, một số người thì gọi là hằng con trỏ, cái này tùy thuộc vào cách dịch) và con trỏ tới hằng (pointer to const).

    Con trỏ hằng (const pointer): là con trỏ luôn luôn trỏ tới một địa chỉ cố định, và không thể thay đổi được trong suốt thời gian thực thi chương trình. Mọi nỗ lực sử đổi như gán địa chỉ mới cho con trỏ hằng hay tăng giảm con trỏ hằng đều gây lỗi biên dịch. Ví dụ quen thuộc nhất về con trỏ hằng là tên mảng:
    C++ Code:
    1. // tên mảng là một const pointer
    2. char str[]=”I am first_pace, from congdongCViet”; // str là con trỏ hằng, nhưng các str[i] không phải hằng
    3. char* p1;
    4. char* p2;
    5. p1=str; // ok: vì p1 không phải con trỏ hằng nên được phép trỏ đi chỗ khác
    6. str=p2; // error: vì str là con trỏ hằng nên không thể trỏ đi chỗ khác được
    Con trỏ tới hằng (pointer to const): là con trỏ trỏ tới một đối tượng hằng, tức là với con trỏ đó, đối tượng được "hiểu" là hằng. Con trỏ này có thể đọc (read) dữ liệu của đối tượng nhưng không được sửa đổi (write) nội dung của đối tượng. Mọi nỗ lực sửa đổi nội dung của đối tượng sẽ gây ra lỗi biên dịch. Con trỏ tới hằng khác con trỏ hằng ở hai điểm: thứ nhất, khi không thích trỏ tới đối tượng này nó có thể trỏ tới đối tượng khác, con trỏ hằng không được phép làm điều này. Thứ hai, con trỏ tới đối tượng hằng (pointer to constant) thì nó hiểu đối tượng là hằng, và không được phép “ghi” dữ liệu lên đối tượng, còn con trỏ hằng nó vẫn cho phép thao tác trên dữ liệu, nó hoàn toàn giống con trỏ bình thường ngoài việc nó chỉ trỏ tới một địa chỉ cố định.

    Khi ta đặt từ khóa const trước một khai báo con trỏ thông thường thì điều đó khiến con trỏ “hiểu” đối tượng là hằng chứ không phải con trỏ là hằng. Nghĩa là nó chỉ có thể “đọc” dữ liệu từ đối tượng nhưng không thể “ghi” dữ liệu lên đối tượng. Trong trường hợp này nó là con trỏ đến đối tượng hằng (pointer to const). Tuy nhiên đối tượng không phải là trở thành hằng, mà nó chỉ được con trỏ đó “hiểu là hằng” (chỉ là là hằng đối với con trỏ đó), đối với con trỏ khác nó có thể là biến. Để hiểu rõ hơn ta xem xét ví dụ sau:
    C++ Code:
    1. char str[]="Hello, Ajinomoto"; // str là con trỏ hằng, nhưng str[i] thì không
    2. const char* p=str; // con trỏ p (pointer to const) trỏ đến đầu mảng str, và p “hiểu” str[i] là các hằng
    3. p[3]='X'; // Error: không được, vì p chỉ được đọc dữ liệu chứ không được ghi dữ liệu lên str[i]
    4. str[3]='X'; // Ok: vì str[i] chỉ được coi là hằng theo “cách hiểu” của p, với str thì các str[i] vẫn là biến
    Để khai báo một con trỏ hằng ta phải sử dụng *const chứ không phải const. Xem xét ví dụ sau:
    C++ Code:
    1. int x, y;
    2. int *const px=&x; // bây giờ px luôn luôn trỏ đến x, không thể thay đổi được
    3. px=&y; // Error: px là con trỏ hằng, không được phép trỏ đi chỗ khác
    Có thể tóm lại như trong ví dụ sau:
    C++ Code:
    1. const int* p1=&x; // con trỏ tới hằng int (pointer to const char)
    2. int const* p2=&x; // hoàn toàn giống p1, con trỏ tới hằng int (pointer to const char)
    3. int *const p3=&x; // con trỏ hằng tới biến char (pointer to const char)
    Để dễ nhớ các bạn có thể đọc từ trái sang phải như sau: “p3 là con trỏ hằng tới kiểu int”.
    Chú ý: một đối tượng hằng là cố định, vì vậy gán địa chỉ của nó cho một “con trỏ tự do” sẽ gây lỗi biên dịch. Bởi vì thông qua con trỏ ta có thể thay đổi giá trị của hằng, và điều này là không được phép. C++ chỉ cho phép gán địa chỉ của một đối tượng hằng cho một con trỏ trỏ tới đối tượng hằng (pointer to const). Ví dụ chương trình sau:
    C++ Code:
    1. const int x=100; // khai báo hằng nguyên x=100
    2. const int* p1=&x; // Ok: con trỏ p1 trỏ tới hằng x
    3. int* p2=&x; // Error: con trỏ p2 là “con trỏ tự do”, không được phép trỏ tới hằng x
    Câu hỏi mở rộng nhé: việc gán địa chỉ của một đối tượng hằng cho con trỏ hằng (const pointer) là có được phép hay không? Tại sao? Nhưng từ từ hãy post vội, đợi mình viết nốt loạt tut này đã

    Hết bài 10b
    Đã được chỉnh sửa lần cuối bởi first_pace : 20-03-2011 lúc 11:53 PM.
    Vấn đề không phải là bước nhanh, mà là luôn luôn bước

  6. #16
    Ngày gia nhập
    02 2011
    Nơi ở
    Hà Nội
    Bài viết
    67

    Mặc định Mảng (array) trong C++

    BÀI 11. MẢNG - ARRAY



    Trong C++, mảng là một nhóm các vùng nhớ liên tiếp có cùng kiểu dữ liệu. Thông thường mảng chứa những phần tử dữ liệu có liên quan đến nhau. Mảng có thể là một chiều, hai chiều, hoặc nhiều hơn hai chiều, trong đó mảng 1 chiều là thông dụng nhất.

    1. Mảng một chiều (one-dimension array)

    a. Khai báo mảng một chiều
    Cú pháp khai báo cho mảng một chiều như sau
    C++ Code:
    1. <kiểu_dữ_liệu> <tên_mảng>[kích_thước];
    2. Ví dụ:
    3. int num[100]; // khai báo mảng 100 số nguyên
    4. char ch[12]; // khai báo mảng 12 ký tự
    Chúng ta cũng có thể khởi tạo giá trị cho mảng ngay lúc khai báo. Ví dụ:

    C++ Code:
    1. int num[100]={1,2,3,4,8,11}; // gán giá trị cho 6 phần tử đầu, các phần tử còn lại tự động set bằng 0
    2. int ch[]={‘a’, ‘b’, ‘c’, ‘d’}; // không cần chỉ rõ kích cỡ mảng, compiler sẽ tự động tính toán đủ chỗ

    b. Truy xuất đến các phần tử mảng
    Để truy xuất đến các phần tử của mảng ta có thể dùng con trỏ hoặc chỉ số mảng. Sau đây là các cách truy xuất các phần tử của mảng thông dụng

    Thông qua chỉ số mảng: xem xét chương trình sau. Chương trình nhận 10 chữ số nguyên từ bàn phím và in ra các số chẵn đã được nhập vào.
    C++ Code:
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. int main(){
    5.     int const size=10;
    6.     int n;
    7.     int a[size];
    8.    
    9.     cout << "Enter " << size << " integers\n";
    10.     for(int i=0; i<size; i++){ // nhập 10 số nguyên
    11.         cout << "a[" << i << "]= ";
    12.         cin >> a[i]; // nhập dữ liệu cho phần tử a[i]
    13.     }
    14.    
    15.     cout << "Even numbers are: \n";
    16.     for(int i=0; i<size; i++){ // duyệt hết mảng
    17.         if(a[i]%2==0){ // nếu là số chẵn
    18.             cout << a[i] << "   "; // in ra số chẵn
    19.         }
    20.     }
    21.    
    22.     return 0;
    23. }

    Thông qua con trỏ: tên mảng thực chất là một con trỏ hằng, trỏ đến phần tử đầu tiên của mảng. Vì vậy ta có thể truy xuất tới các phần tử của mảng thông qua con trỏ này. Chương trình trên được viết lại theo phong cách con trỏ.
    C++ Code:
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. int main(){
    5.     int const size=5;
    6.     int n;
    7.     int a[size];
    8.    
    9.     cout << "Enter " << size << " integers\n";
    10.     for(int i=0; i<size; i++){
    11.         cout << "a[" << i << "]= ";
    12.         cin >> *(a+i); // đọc dữ liệu vào a[i]
    13.     }
    14.    
    15.     cout << "Even numbers are: \n";
    16.     for(int i=0; i<size; i++){
    17.         if(*(a+i)%2==0){ // nếu a[i] chẵn
    18.             cout << *(a+i) << "   "; // in ra a[i]
    19.         }
    20.     }
    21.    
    22.     cout <<
    23.     system("pause");
    24.     return 0;
    25. }
    Ta có thể chỉ dùng con trỏ mà không cần dùng biến chạy i, đặc biệt là trong thao tác với chuỗi (string). Mọi tăng giảm đều thực hiện trên con trỏ, tuy nhiên tên mảng là một con trỏ hằng, không thể thay đổi được. Vì vậy ta phải thực hiện việc này thông qua một con trỏ khác. Chương trình sau thực hiện việc đổi tất cả các dấu cách (space) thành dấu chấm (dot). Lưu ý rằng, C++ không có kiểu built-in là string, string được cài đặt thông qua mảng các ký tự (kiểu char). Dấu hiệu kết thúc của một string là ký tự NULL (hay ký tự ‘\0’), đây chính là cơ sở để làm điều kiện kết thúc vòng lặp. Xem xét chương trình.
    C++ Code:
    1. #include <iostream>
    2. #include <cstdio>
    3. using namespace std;
    4. int main(){
    5.     char str[]="I am a superman, ha ha ha";
    6.     char* ptr=str;
    7.    
    8.     while(*ptr){ // nếu còn chưa hết xâu
    9.         if(*ptr==' '){ // nếu là dấu cách
    10.             *ptr='.'; // đổi thành dấu chấm
    11.         }
    12.         ptr++; // dịch đến ký tự tiếp theo
    13.     }
    14.    
    15.     cout << str << endl; // in ra xâu đã được chỉnh sửa
    16.    
    17.     return 0;
    18. }

    2. Mảng hai chiều (two-dimension array)
    Mảng hai chiều thực chất là “mảng một chiều của các mảng một chiều”. Để khai báo mảng một chiều ta dùng cú pháp sau.
    C++ Code:
    1. <kiểu_sữ_liệu> <tên_mảng>[chiều_1][chiều_2];
    2. Ví dụ:
    3. int x[10][8]; // khai báo một mảng nguyên 10 phần tử, mỗi phần tử là một mảng nguyên tám phần tử
    Ta có thể coi mảng hai chiều như một bảng (table), chiều thứ nhất là số hàng (row), chiều thứ hai là số cột (column), như mô tả trong hình vẽ sau.


    Thực tế trong máy tính mảng trên được tổ chức như một mảng một chiều gồm 4 phần tử. Mỗi phần tử lại là một mảng một chiều gồm 5 phần tử như hình vẽ sau.


    Ta cũng có thể khởi tạo mảng hai chiều khi khai báo. Chiều thứ nhất có thể bỏ trống, trình biên dịch sẽ tự động tính toán vừa đủ, nhưng chiều thứ hai phải được chỉ định rõ. Ví dụ.
    C++ Code:
    1. int a[][2]={
    2.         {1,2},
    3.         {3,4},
    4.         {2,6}
    5.     };
    Cách viết trên để cho dễ nhìn, bạn cũng có thể viết như sau mà kết quả vẫn tương đương.
    C++ Code:
    1. a[][2]={1,2,3,4,2,6};

    Hết bài 11
    Đã được chỉnh sửa lần cuối bởi first_pace : 08-03-2011 lúc 03:24 PM.
    Vấn đề không phải là bước nhanh, mà là luôn luôn bước

  7. #17
    Ngày gia nhập
    02 2011
    Nơi ở
    Hà Nội
    Bài viết
    67

    Mặc định Lớp lưu trữ

    BÀI 12. LỚP LƯU TRỮ (STORAGE CLASS)


    C++ có 5 định danh lớp lưu trữ (storage class specifiers), đó là: auto, extern, static, register và mutable. Các specifiers này quy định cách thức mà biến được lưu trữ. Specifier mutable chỉ áp dụng trong các đối tượng của lớp (class objects) nên mình sẽ đề cập đến nó trong những phần sau. Bây giờ ta chỉ khảo sát 4 specifiers đầu tiên.

    1. auto

    auto dùng để khai báo các biến cục bộ. Cú pháp khai báo một biến là auto như sau:
    C++ Code:
    1. auto <kiểu_dữ_liệu> <tên_biến>;
    Tuy nhiên khai báo auto hiếm khi được sử dụng vì mặc định các biến cục bộ là auto. Vì vậy những biến cục bộ như thế nào thì các biến auto như thế.

    2. extern

    Trong thực tế các chương trình thường có quy mô lớn. Vì vậy người ta thường phải chia chương trình thành các files riêng rẽ , nhỏ hơn. Khi có một thay đổi nhỏ nào đó xảy ra thì ta không phải compile lại cả chương trình mà chỉ cần compile lại những file có thay đổi. Trong một chương trình lớn bao gồm nhiều files, nếu chương trình có sử dụng biến toàn cục thì các files phải “biết” các biến toàn cục này. Tuy nhiên ta không thể khai báo lại các biến toàn cục trong từng file. Vì như vậy trình liên kết (linker) không biết link biến nào vì có nhiều bản sao của biến toàn cục và sẽ báo lỗi. C++ cung cấp giải pháp đó là khai báo biến toàn cục trong một file và sử dụng từ khóa extern.

    Khi khai báo một biến là extern thì nó báo cho trình biên dịch biết kiểu (type)tên (name) của biến toàn cục, nhưng thực sự cấp phát bộ nhớ cho biến, tức là không thực sự tạo ra biến. Ta sẽ khai báo nó ở đâu đó trrong chương trình và mỗi khi gặp biến này, linker tự động tìm đến biến “thực sự” để lấy dữ liệu. Ví dụ chương trình của chúng ta gồm hai files là fileA, fileB và được khai báo như sau:

    C++ Code:
    1. File A:
    2. double global_var; // biến toàn cục thực sự
    3. int main(){
    4.     // thân hàm main
    5. }
    6.  
    7. File B:
    8. extern double global_var; // thông báo global_var đã được khai báo trong một file nào đó
    9. demo_func(){
    10.     // thân hàm demo_func
    11. }
    Một ứng dụng khác của extern đó là giúp ta có thể sử dụng biến toàn cục trước khi định nghĩa chúng. Thông thường ta thường khai báo các biến toàn cục ở đầu chương trình, nhưng điều này là không cần thiết. Khi một hàm nào đó cần sử dụng biến toàn cục, ta có thể khai báo biến toàn cục trong hàm với từ khóa extern, rồi sau đó thích đặt biến toàn cục ở đâu cũng được. Ví dụ chương trình sau:
    C++ Code:
    1. int main(){
    2.     // thân hàm main
    3. }
    4. void func(){
    5.     extern int x; // khai báo extern
    6.     cout << “ x is global variable and x=<< x << endl;
    7. }
    8. // định nghĩa biến toàn cục cuối chương trình
    9. int x=100;
    Nói túm lại có hai cách sử dụng của extern: thứ nhất, dùng để thông báo biến toàn cục đã được định nghĩa trong một file nào đó của một chương trình nhiều files. Thứ hai, cho phép sử dụng biến trước khi định nghĩa chính thức cho biến.

    Tuy nhiên có một điều cần chú ý. Nếu trong khai báo extern ta khởi tạo giá trị cho biến thì khai báo (declaration) đó sẽ trở thành định nghĩa (definition). Khai báo extern thì có thể có nhiều, nhưng định nghĩa thì chỉ có một nên sẽ có thể dẫn đến lỗi. Vì vậy khi sử dụng cần phải chú ý.

    3. static

    Các biến có kiểu static là các biến “tĩnh” hay “cố định” (permanent) bên trong phạm vi khai báo của nó. Nó giống biến toàn cục ở chỗ nó được cấp phát bộ nhớ ngay khi chương trình bắt đầu, và tồn tại cho đến khi chương trình kết thúc, và nó duy trì giá trị trong suốt thời gian thực thi chương trình. Tuy nhiên điểm khác biệt là nó là biến cục bộ (local). Nó chỉ được biết đến trong phạm vi hàm, hoặc file mà nó được khai báo trong khi biến toàn cục có phạm vi toàn bộ chương trình. Có hai loại biến static là static cục bộ và static toàn cục, ta sẽ xem xét cả hai loại biến này.

    a. static cục bộ (static local variables)

    Khi một biến cục bộ được khai báo static thì nó sẽ được cấp phát một vùng nhớ cố định. Điều nay cho phép biến cục bộ duy trì giá trị của nó trong suốt thời gian thực thi chương trình. Nếu biến cục bộ của hàm thì giá trị của nó vẫn được duy trì giữa các lần gọi hàm. Điểm khác biệt chính giữa biến static cục bộ với biến toàn cục là “phạm vi” của nó. Biến static cục bộ chỉ được biết đến trong phạm vi khối lệnh mà chúng được khai báo, nó bị giới hạn bởi phạm vi. Để khai báo một biến là static ta chỉ cần thêm từ khóa static vào trước kiểu dữ liệu. Ví dụ:
    C++ Code:
    1. static int x;
    2. static int count=0;
    Chú ý là biến static cục bộ chỉ được khởi tạo một lần, khi chương trình bắt đầu, chứ không phải mỗi lần khai báo của nó được bắt gặp. Việc cho phép duy trì giá trị biến cục bộ sau khi thoát khỏi hàm giúp hàm trở nên độc lập hơn. Vì một số hàm cần lưu giữ thông tin sau mỗi lần gọi, nếu biến cục bộ không được phép lưu giữ giá trị khi thoát khỏi hàm thì ta phải sử dụng các biến toàn cục. Và như mình đã nói ở bài trước, việc sử dụng biến toàn cục nên hạn chế, vì nó làm mất đi tính độc lập của các hàm đồng thời gây ra những hiệu ứng phụ không mong muốn.

    b. static toàn cục (static global variables)

    Các biến được khai báo static toàn cục chỉ “toàn cục” trong phạm vi file mà nó được khai báo. Nghĩa là mặc dù nó là global nhưng nó chỉ được biết đến trong phạm vi file mà nó được khai báo, các đoạn code trong những files khác không thể thao tác được trên nó.

    Túm lại cho cả hai loại biến static: từ khóa static cho phép một biến được phép duy trì giá trị của mình trong suốt thời gian chương trình thực thi. Tuy nhiên giới hạn phạm vi của nó vẫn được tuân thủ. Một static cục bộ chỉ được biết đến trong khối lệnh, hoặc hàm mà nó được khai báo, một static toàn cục cũng chỉ được biết đến trong file mà nó được khai báo. Điều này giúp giảm bớt những hiệu ứng phụ không mong muốn.

    Một chú ý nữa cần phải nhắc đến là: mặc dù biến static toàn cục (static global) vẫn hợp lệ và được sử dụng rộng rãi trong lập trình C++ nhưng C++ standard thì lại phản đối sử dụng nó. Thay vào đó người ta khuyến khích sử dụng các phương án khác để kiểm soát truy nhập đến biến toàn cục, trong đó có sử dụng namespace. Mình sẽ nói đến vấn đề namespace trong phần sau.

    4. register
    register là specifier được sử dụng thường xuyên nhất. Khi một biến được khai báo là register, nó sẽ thông báo cho trình biên dịch biết để lưu trữ biến theo một cách nào đó mà biến có thể được truy cập một cách nhanh nhất có thể. Thông thương biến sẽ được lưu trữ trong thanh ghi (register) hoặc bộ nhớ đệm (cache memory). Truy cập dữ liệu từ register hoặc cache sẽ nhanh hơn truy cập từ bộ nhớ chính (main memory – RAM). Vì vậy biến lưu trữ trong register sẽ được truy cập nhanh hơn nhiều so với khi nó được lưu trữ trong RAM.

    Tuy nhiên đây chỉ là “gợi ý” yêu cầu compiler lưu trữ biến trong register, còn việc có đáp ứng yêu cầu này hay không thì đó là vấn đề khác. Compiler hoàn toàn có quyền từ chối. Lý do là trong CPU thì lượng thanh ghi là có hạn, và dung lượng của chúng là rất thấp. Vì vậy khi compiler nhận thấy các thanh ghi đã hết, nó sẽ lờ đi yêu cầu register này và chuyển biến tới lưu trữ ở khu vực khác (RAM).
    Ta phải lựa chọn hết sức cẩn thận các biến để khai báo register thì mới có thể khai thác tối đa lợi ích về hiệu suất, vì compiler có quyền lờ đi các yêu cầu yêu cầu một lượng quá lớn registers. Thông thường các biến điều khiển vòng lặp hoặc các biến được thao tác bên trong vòng lặp, với tần suất sử dụng nhiều lần, thì nên được khai báo register.

    Hết bài 12
    Vấn đề không phải là bước nhanh, mà là luôn luôn bước

  8. #18
    Ngày gia nhập
    06 2011
    Bài viết
    3

    Bạn viết bài này thật quả rất hay, thế mà sao ko thấy có ai reply nhỉ. Mình dành nguyên cả buổi tối đọc hết topic này đó. Hi vọng bạn sẽ có thêm những bải viết khác như thừa kế, đa hình....
    Mình chờ bài viết của bạn

  9. #19
    Ngày gia nhập
    01 2011
    Bài viết
    3

    Viết tiếp đi bạn ơi ! hay quá à......

  10. #20
    Ngày gia nhập
    07 2011
    Bài viết
    3

    Bài viết rất hay.Thank alot

Quyền hạn của bạn

  • Bạn không thể gửi đề tài mới
  • Bạn không thể gửi bài trả lời
  • Bạn không thể gửi các đính kèm
  • Bạn không thể chỉnh sửa bài viết của bạn