Announcement

Collapse
No announcement yet.

con trỏ trong C

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • #61
    Originally posted by 11520327 View Post
    như các bạn đã biết con trỏ là 1 vấn đề khá là khó trong C, có liên quan đến các môn học như tin đại cương, cấu trúc dữ liệu và giải thuật, OOP,... chính vì vậy mình đưa ra chủ đề này với mong muốn trao đổi các vấn đề của con trỏ. hi vọng mọi người cùng tham gia để củng cố kiến thức. có 1 chú ý nho nhỏ là các bạn nên viết code bằng mã C vì như vậy tạo điều kiện cho K7 dễ dàng tìm hiểu
    mình xin bắt đầu bằng 1 đoạn code:
    PHP Code:
    void Tag(int *a)
    {
        *
    a=5;
    }
    int main()
    {    
        
    int numb=3;
        
    Tag(&numb);
        
    printf("gia tri cua numb: %d"numb);
        
    getch();

    câu hỏi: kết quả của đoạn code trên và giải thích tại sao lại có kết quả như vậy?
    Mình cũng ko đọc gì những bài đã post , chỉ thấy cái đề nên xin phép chia sẻ kiến thức mình biết nha

    Thứ nhất ko nói đến việc viết sai cú pháp hay thiếu sót của những cái râu ria ngòai hàm main. Ko quan trọng.

    Thứ hai mình chỉ có đọc 3 cái dòng sau

    int numb=3; (1)
    Tag(&numb); (2)
    printf("gia tri cua numb: %d", numb); (5)

    Và hàm Tag nữa

    void Tag(int *a) (3)
    {
    *a=5; (4)
    }

    Giải thích các lệnh
    (1) xin ram cho một ô nhớ tên là numb và cho nó giá trị 3 vào ô nhớ này, lúc này giá trị trong ô nhớ numb là 3.
    (2) và (3) hai lệnh này làm cho con trỏ a bám vào ô nhớ numb , nhờ vậy nó có thể làm bất cứ điều gì trên ô nhớ numb ( kiểu như điều khiển từ xa ấy ).
    (4) con trỏ a điều khỉên ô nhớ numb xóa giá trị 3 đang có đi và thay bằng giá trị 5, lúc này giá trị trong ô nhớ numb đã là 5 nhé.
    (5) in giá trị của ô nhớ numb ra màn hình , tất nhiên nó không thể khác 5. ^^

    Con trỏ là một ô nhớ rất đặc biệt, nó ko chứa dữ liệu như các ô nhớ thông thường mà nó chỉ chứa được địa chỉ của các ô nhớ này.
    Chính vì vậy nó có thể truy xuất được đến các ô nhớ mà nó đã "bám" vào , và làm bất kỳ trò gì mà các ô nhớ này có thể làm được.

    Comment


    • #62
      Originally posted by kengo14 View Post
      Mình cũng ko đọc gì những bài đã post , chỉ thấy cái đề nên xin phép chia sẻ kiến thức mình biết nha

      Thứ nhất ko nói đến việc viết sai cú pháp hay thiếu sót của những cái râu ria ngòai hàm main. Ko quan trọng.

      Thứ hai mình chỉ có đọc 3 cái dòng sau

      int numb=3; (1)
      Tag(&numb); (2)
      printf("gia tri cua numb: %d", numb); (5)

      Và hàm Tag nữa

      void Tag(int *a) (3)
      {
      *a=5; (4)
      }

      Giải thích các lệnh
      (1) xin ram cho một ô nhớ tên là numb và cho nó giá trị 3 vào ô nhớ này, lúc này giá trị trong ô nhớ numb là 3.
      (2) và (3) hai lệnh này làm cho con trỏ a bám vào ô nhớ numb , nhờ vậy nó có thể làm bất cứ điều gì trên ô nhớ numb ( kiểu như điều khiển từ xa ấy ).
      (4) con trỏ a điều khỉên ô nhớ numb xóa giá trị 3 đang có đi và thay bằng giá trị 5, lúc này giá trị trong ô nhớ numb đã là 5 nhé.
      (5) in giá trị của ô nhớ numb ra màn hình , tất nhiên nó không thể khác 5. ^^

      Con trỏ là một ô nhớ rất đặc biệt, nó ko chứa dữ liệu như các ô nhớ thông thường mà nó chỉ chứa được địa chỉ của các ô nhớ này.
      Chính vì vậy nó có thể truy xuất được đến các ô nhớ mà nó đã "bám" vào , và làm bất kỳ trò gì mà các ô nhớ này có thể làm được.
      cách giải thích của a đọc rất dễ hiểu, tuy nhiên a chưa nói (4): tại sao a lại có thể điều khiển ô nhớ numb?

      Comment


      • #63
        Originally posted by 10520058 View Post
        Đây là một phép tính thừa không cần thiết vì:
        lưu tổng của 2 số vào 1 và sau đó trừ cái kia ra và ngược lại luôn đúng với bất kì tập số a,b nào thuộc R (không phụ thuộc tràn số) nhưng sinh ra ba phép tình và 3 phép gán :-) Nên tránh.
        theo ý kiến của e thì khi bộ nhớ máy tính còn ít thì không dùng biến tạm sẽ tiết kiệm bộ nhớ. nhưng bây giờ bộ nhớ máy tính lớn(RAM) thì ta nên thiết kế theo hướng tốc độ thực thi.
        theo a, cách nào là tối ưu?

        Comment


        • #64
          Originally posted by 11520327 View Post
          cách giải thích của a đọc rất dễ hiểu, tuy nhiên a chưa nói (4): tại sao a lại có thể điều khiển ô nhớ numb?
          bởi vì a là biến con trỏ ? Con trỏ có thể điều khiển bất kỳ ô nhớ nào ?
          Mình không hiểu lắm là bạn Sang đang hỏi thật, hay đang vặn vẹo người khác tại vì mình có cảm giác bạn chỉ chấp nhận bạn đúng thôi, không chấp nhận bất kỳ lời giải thích nào kể cả ý nghĩa thì giống hệt chỉ khác cách phát biểu. Làm vậy thì bạn luôn đúng đấy, nhưng mà ít người thích thảo luận tiếp với bạn thôi.

          Originally posted by 11520327 View Post
          theo ý kiến của e thì khi bộ nhớ máy tính còn ít thì không dùng biến tạm sẽ tiết kiệm bộ nhớ. nhưng bây giờ bộ nhớ máy tính lớn(RAM) thì ta nên thiết kế theo hướng tốc độ thực thi.
          theo a, cách nào là tối ưu?
          có thể xem là nhanh nhất(*) và tối ưu theo RAM:

          Code:
          void swap(int *a,int *b)
          {
              *a ^= *b;
              *b ^= *a;
              *a ^= *b;
          }
          có thể xem là nhanh nhì (gần như không đáng kể về tốc độ) và bug free, được xem như là standard
          Code:
          void swap(int *a,int *b)
          {
              int temp = *a;
              *a = *b;
              *b = temp;
          }
          (*): việc gán thẳng bằng lệnh XOR thì luôn nhanh hơn phép gán =, nhưng cái này chỉ tính trong TH compiler sinh ra optimized code. Nếu không thì code 2 luôn luôn là nhanh nhất.
          theo anh thì ưu tiên dùng đoạn code 2, vì nó không bao giờ sinh ra bug
          còn cái TH mà môi trường còn ít bộ nhớ quá bla bla bla thì chỉ tồn tại trong TH lý thuyết ? Vì nếu lập trình assembly thì không cần thêm bộ nhớ mà dùng register để swap luôn (nhanh hơn), còn không phải assembly thì ít nhất bạn vẫn còn heap hay stack nào đó chứ cạn tới mức ko thể xin thêm 1 biến thì chương trình của bạn gần như sẽ bị stack overflow bất kỳ lúc nào.
          Last edited by 09520019; 15-10-2012, 10:22.
          Khoảng cách giữa bạn và ước mơ của bạn là bao xa ?

          Comment


          • #65
            Originally posted by 09520019 View Post
            bởi vì a là biến con trỏ ? Con trỏ có thể điều khiển bất kỳ ô nhớ nào ?
            Mình không hiểu lắm là bạn Sang đang hỏi thật, hay đang vặn vẹo người khác tại vì mình có cảm giác bạn chỉ chấp nhận bạn đúng thôi, không chấp nhận bất kỳ lời giải thích nào kể cả ý nghĩa thì giống hệt chỉ khác cách phát biểu. Làm vậy thì bạn luôn đúng đấy, nhưng mà ít người thích thảo luận tiếp với bạn thôi.
            cảm ơn a đã nhắc nhở.

            Comment


            • #66
              Mình muốn chia sẻ,giải thích thêm một chút nha. Mình có 2 vấn đề muốn trình bày.

              Đầu tiên mình thấy các bài viết của các bạn có dính đến việc swap 2 biến bằng phép toán XOR. Đây là 1 cách rất hay tuy nhiên mình chưa thấy ai giải thích họat động của nó cả.Có thể các bạn hiểu nhưng những người vô đây xem chưa chắc ai cũng biết ^^. Mình xin mạn phép giải thích theo ý của mình như sau.

              Để hiểu cái này trước tiên cần nhắc lại kt cơ bản. Một con số khi được lưu trên máy tính nó sẽ biểu diễn dưới dạng nhị phân ( chứ ko phải hệ thập phân hay dùng trong đời sống ) , nôm na là bạn chỉ thấy 2 số 0 và 1 thôi.
              Ví dụ: số 5 ở ngòai đời mà đưa vô máy tính nó sẽ là 0101

              Ok bạn chỉ cần biết là một con số ở trên máy tính sẽ chỉ là một dãy 0 và 1 đại lọai như thế , ở đây chẳng cần biết tại sao 5 lại là 0101 mà ko phải là 1010 đâu.Bạn có thể cho 5 là 0111 nếu bạn thích, ta vẫn có thể giải thích tốt bài tóan swap này , nhưng đừng cho 5 là … 0123 nhé ( cái này thì sai ko chấp nhận đc ^^ !)

              Tiếp theo là phép XOR là gì? Là 1 trong những phép tóan luận lý 2 ngôi, XOR còn được viết là ^, và quy tắc cuả nó là như sau
              0 xor 1 = 1 , 0 xor 0 = 0
              1 xor 0 = 1 , 1 xor 1 = 0
              (Khác 1 cùng 0 )

              Vậy ta thử đem một số 0011 xor với 1 số 0110 nhé
              0011
              Xor 0110
              0101

              Ok , phép xor sẽ cho ra 1 kết quả như thế đấy. Tương tự cho bất kỳ 2 con số nào.
              Vậy phép xor này làm được gì cho bài toán đổi giá trị của 2 số cho nhau? À, nếu để ý ta sẽ thấy phép XOR có 2 tính chất rất đặc biệt, mà chính nó sẽ giúp ích cho bài toán này.

              Thứ nhất , một số XOR với 0 sẽ bằng chính nó. Thử nhé, lấy đại 0111 đi
              0111
              Xor 0000
              0111

              Hehe, đúng với bất kỳ số nào. Tính chất thứ 2, một số XOR với chính nó sẽ bằng 0.
              0111
              Xor 0111
              0000

              Đúng với số nào bất kỳ luôn. Vậy ta có n ^ 0 = n , n ^ n =0 ( n bất kỳ ). Nhớ nhé!
              Thuật toán swap(x,y) dùng phép xor
              x= (x ^ y);
              y= y ^ x;
              x= x ^ y;

              Có 2 biến x ,y như thế, thuật tóan này xử lý như sau

              ban đầu nó cho x thành x^y , ok , chẳng cần biết x^y là cái số quái gì. Giờ x là (x^y).

              Sau đó nó cho y = y ^ x <-> y= y ^ ( x ^ y ) <-> y = (y ^ y ) ^ x ( giao hóan thôi )
              Mà y^y =0 <-> y= 0^x
              Mà 0^x=x <-> y=x

              Hehe , y mang giá trị của x rồi nhé. Còn x vẫn là (x^y) không đổi gì cả. Nhưng y đã là x rồi đấy.
              Tiếp theo là lệnh x = x ^ y , một cách tương tự ta có <-> x= (x^y)^x <-> x = y ^ (x^x) <-> x = y ^ 0 <-> x= y
              Vậy là x thành y rồi. 2 cái đã đổi giá trị cho nhau

              Hết vấn đề 1

              Vấn đề thứ 2 là vấn đề con trỏ và tại sao phải ra đời con trỏ.

              Con trỏ là 1 khái niệm rất quan trọng trong lập trình , chính nó đã giúp giải quyết vô số vấn đề của các bài toán thông thường cho đến việc cài đặt cấu trúc dữ liệu, xa hơn nữa nó chính là một phần nền tảng của lập trình hướng đối tượng mà các ngôn ngữ hiện đại đang sử dụng.

              Nói thêm một tí , tuy các ngôn ngữ ngày nay người ta sử dụng khái niệm tham chiếu thay cho con trỏ, bạn sẽ ko còn thấy dấu * nào đặt ở đầu khai báo biến nữa.

              int b=3;
              int *a=&b; // ngay xua oi

              Mà sẽ thay thế bằng các lệnh giống giống thế này

              JLabel mylbl = new JLabel(); // modern hien nay

              Nhưng bản chất của tham chiếu chính là con trỏ mà thôi. Hai cách viết lệnh trên,tuy khác nhau xíu nhưng về mặt bản chất là như nhau ( ko nói về kiểu dữ liệu nhen , ở đây là về góc độ quản lý vùng nhớ ). Cách viết đầu khiến cho con trỏ a nắm đầu thằng b và nó có thể làm cho b họat động theo ý nó, khi nó bảo b làm gì là b phải làm theo y như vậy. Bản thân a tất nhiên chẳng bị ảnh hưởng, ngọai trừ khi a chuyển sang quản lý thằng khác hoặc khi b “chết” ( bị giải phóng vùng nhớ ) thì a lúc này sẽ ko còn làm gì được b nữa. Cách thứ hai cũng thế, nhưng người ta ko gọi mylbl là 1 con trỏ kiểu JLabel mà mylbl là 1 đối tượng , nó quản lý một cái vùng nhớ được sinh ra từ câu lệnh new, bản thân cái vùng nhớ này chẳng có tên gì ( khác với câu lệnh trên , b là 1 cái tên đại diện cho vùng nhớ mới sinh ra ) , nhưng nó ko cần biết điều đó , chỉ cần biết người ta cho nó nắm đầu được 1 vùng nhớ gì đó rồi , và nó có quyền điều khiển vùng nhớ mà nó đang “tham chiếu” đến ấy. Sau này nó mà ko giữ cái vùng nhớ đó nữa và cũng chẳng chịu gửi lại cho ai, thì tự động cái vùng nhớ này được giải phóng khỏi ram.

              Nói dài dòng vậy chứ, ý tóm lại cả 2 cách trên đều giúp ta quản lý vùng nhớ “một cách gián tiếp”.

              Điều này xuất phát từ một tư tưởng rất hay : điều khiển từ xa

              Nói về chuyện cái tivi. ^^
              Ngày xưa, lúc tivi vừa mới ra đời, bạn muốn bật tắt hay làm gì đó thì phải chạy lại chính nó mà tìm nút ấn.Vì đơn giản tất cả mọi nút chỉnh của cái tivi nào nằm trên cái tivi đó. Bạn ko thể ngồi ở xa mà nói cho nó hiểu bạn muốn cái gì được ( mà ngay cả bây giờ cũng chưa làm được điều đó ^^! )
              Một thời gian, người ta thấy rất mệt mỏi mỗi khi muốn chỉnh lại volume một tí, hay chuyển qua kênh khác xem, ko lẽ đang nằm trên giường lại phải bật dậy lại gần đến cái tivi chỉ vì mẹ bạn muốn loa bé 1 chút... ---> cái đìêu khiển từ xa ra đời, và bạn có thể vẫn nằm yên một chỗ mà vặn loa tivi bé lại được.
              Một hình ảnh ví von để nói về tầm quan trọng của con trỏ ( hay tham chiếu )trong đời sống cũng như trong lập trình. Điều khiển gián tiếp có rất nhiều cái lợi, nói đến đây bạn cũng có thể tự nhận thấy điều đó trong cuộc sống rồi đúng ko? Còn gì tuyệt hơn khi chúng ta có thể alo từ xa để giao công việc cho người khác mà ko cần phải hẹn gặp nhau tại 1 quán café cho tốn tiền ( đt cũng tốn nhưng thường là ít hơn ^^), tốn xăng, tốn thời gian đi lại v..v…

              Trong lập trình ,một ứng dụng dễ thấy của con trỏ là về MẢNG. Mảng rất “lợi hại”, đồng ý ko? Thế thì nguyên tắc quản lý mảng như thế nào? À, mảng về bản chất là một dãy ô nhớ cùng kiểu dữ liệu.Nó chẳng khác gì một tập hợp nhiều ô nhớ nhưng chỉ khác biệt ở chỗ những ô nhớ này là hàng xóm của nhau, tức là từ nhà thằng này có thể biết nhà thằng kia nếu ta biết 2 nhà cách nhau bao nhiêu căn ( vị trí ). Vậy nếu ta muốn quản lý hết cả dãy mảng, ta chỉ cần một người biết nhà đầu tiên thôi.

              Trong C , mảng khai báo thế này

              Int a[10];

              Thật chất a chính là một con trỏ, và nó nắm được địa chỉ của nhà đầu tiên a[0] và nhờ đó nó có thể qua lại mọi căn nhà trong dãy này. Điều này cho thấy tính quản lý tốt của con trỏ.

              Ta cũng có thể áp dụng cách giải thích này cho các cấu trúc dữ liệu khác như danh sách liên kết, cây …Tương tự thôi vì bản thân mảng cũng là 1 cấu trúc dữ liệu rồi.

              Comment

              LHQC

              Collapse
              Working...
              X