Thứ Bảy, 25 tháng 2, 2012

Từ Pascal đến C phần 4


Giới thiệu kiểu con trỏ
Trong ngôn ngữ C, kiểu con trỏ được sử dụng khắp mọi nơi. Nếu bạn hiểu rõ kiểu con trỏ thì sẽ dễ dàng thông thạo C hơn. Kiểu con trỏ trong C về cơ bản tương tự như Pascal, nhưng được dùng tự do hơn rất nhiều.
Con trỏ trong C  được dùng theo ba cách chính. Thứ nhất, chúng dùng để tạo ra cấu trúc dữ liệu động:
những cấu trúc dữ liệu xây dựng từ những khối bộ nhớ được phân phối trên heap trong thời điểm chạy chương trình. Đây là cách duy nhất thể hiện tường minh trong Pascal. Thứ hai, con trỏ được dùng để điều khiển những tham biến đưa vào các hàm. Và thứ ba, chúng là một phương tiện để truy nhập thông tin chứa trong các mảng; cách này đặc biệt hữu dụng khi làm việc với xâu kí tự. Trong C, mảng và con trỏ có quan hệ mật thiết với nhau.
Trong nhiều trường hợp, lập trình viên C sử dụng con trỏ vì chúng giúp chương trình tối ưu hơn một chút. Tuy nhiên, chúng cũng làm cho chương trình khó hiểu hơn.
Những khái niệm cơ bản về con trỏ
Một biến là một vị trí trên bộ nhớ dùng để chứa giá trị. Ví dụ, khi bạn khai báo biến i có kiểu integer, 4 bytes trong bộ nhớ sẽ được dành ra một bên cho nó. Trong chương trình, bạn dùng tên gọi i để chỉ vùng nhớ này. Ở cấp ngôn ngữ máy, vùng nhớ này có một địa chỉ (memory address).
Con trỏ là một biến chứa địa chỉ vùng nhớ của một biến khác.
Đối với con trỏ, ta có hai phần cần quan tâm. Đó là bản thân con trỏ (chứa địa chỉ), và giá trị mà địa chỉ đó lưu giữ. Nếu chưa từng làm quen với con trỏ trước đây, bạn sẽ cảm thấy hai khái niệm này hơi rắc rối một chút.
Đoạn chương trình dưới đây thể hiện một ví dụ về kiểu con trỏ:
#include <stdio.h>

void main()
{
    int i,j;
    int *p;   /* con trỏ đến một số nguyên */
    p = &i;
    *p=5;
    j=i;
    printf("%d %d %d\n",i,j,*p);
}
Dòng int *p khai báo một con trỏ. Bạn có thể tạo một con trỏ đến bất kì kiểu dữ liệu gì: float, struct, char, v.v…
Dòng p=&i; có lẽ hơi mới đối với bạn. Trong C, được gọi là toán tử địa chỉ (address operator). Kí hiệu &i có nghĩa là “địa chỉ nhớ của biến i”. Do đó, câu lệnh p=&i; nghĩa là “gán cho p địa chỉ của i”. Sau khi thực hiện lệnh này, p sẽ trỏ đến i. Trước đó, p chứa một địa chỉ ngẫu nhiên nào đó, thông thường nếu sử dụng nó sẽ dẫn đến lỗi “segmentation fault”
Sau khi p trỏ đến i, địa chỉ nhớ của i bây giờ có 2 tên gọi i và *p. Do đó, lệnh *p=5 có nghĩa là i=5. Tiếp tục, lệnh j=i; sẽ đặt j bằng 5, và lệnhprintf in ra 5 5 5.
Bạn hãy thử đoạn mã sau:
#include <stdio.h>

void main()
{
    int i,j;
    int *p;   /* con trỏ đến một số nguyên */
    printf("%d %d\n",p,&i);
    p = &i;
    printf("%d %d\n",p,&i);
}
Đoạn chương trình trên sẽ in ra giá trị địa chỉ chứa trong p, cùng với địa chỉ của i. Ban đầu biến được gán bằng một giá trị nào đó hoặc bằng 0. Địa chỉ của i thường là một số khá lớn. Ví dụ, khi chạy đoạn mã này, bạn có thể nhận được kết quả sau:
0   2147478276
2147478276   2147478276
Bạn hãy tiếp tục thử chương trình sau:
#include <stdio.h>

void main()
{
    int *p;   /* con trỏ đến một số nguyên */
    printf("%d\n",*p);
}
Đoạn mã trên sẽ in ra giá trị mà p trỏ đến. Tuy nhiên, p lại chưa được khởi tạo; nó chứa địa chỉ 0. Do đó sẽ xuất hiện lỗi “segmentation fault”, bởi vì bạn đã dùng một con trỏ trỏ đến một vùng nhớ không hợp lệ.

Sử dụng con trỏ để truyền tham biến
Hầu hết lập trình viên C trước hết cần phải dùng con trỏ cho việc truyền tham biến. Giả sử bạn có một thủ tục đơn giản trong Pascal, có chức năng đổi chỗ hai số nguyên:
program samp;
var a,b:integer;

procedure swap(var i,j:integer);
var t:integer;
begin
    t:=i;
    i:=j;
    j:=t;
end;

begin
    a:=5;
    b:=10;
    writeln(a,b);
    swap(a,b);
    writeln(a,b);
end.
Thủ tục Swap đổi chỗ được a và b vì nó sử dụng tham biến (var i, j: integer).
Trong C, không có cách chính thức nào để truyền tham biến như vậy; tất cả đều là tham trị. Bạn hãy thực thi đoạn mã sau:
#include <stdio.h>

void swap(int i, int j)
{
    int t;
    t=i;
    i=j;
    j=t;
}

void main()
{
    int a,b;
    a=5;
    b=10;
    printf("%d %d\n",a,b);
    swap(a,b);
    printf("%d %d\n",a,b);
}
Bạn sẽ thấy việc đổi chỗ không được thực hiện. Giá trị của a và b được đưa vào thủ tục swap, nhưng không có giá trị nào được trả về.
Để thực thi đúng được thủ tục Swap, bạn phải sử dụng con trỏ như sau:
#include <stdio.h>

void swap(int *i, int *j)
{
    int t;
    t = *i;
    *= *j;
    *= t;
}

void main()
{
    int a,b;
    a=5;
    b=10;
    printf("%d %d\n",a,b);
    swap(&a,&b);
    printf("%d %d\n",a,b);
}
Khi gọi thủ tục swap, chúng ta đưa vào các tham trị là địa chỉ của a và b. Do đó i trỏ đến a và j trỏ đến b. Bây giờ, i* là tên gọi khác của aj* là tên gọi khác của b. Vì vậy đoạn mã đổi chỗ *i và *j nghĩa là đổi chỗ a và b. Bạn để ý rằng cách dùng con trỏ như vậy cho chúng ta biết được địa chỉ thực của a và b, do đó tác động được lên giá trị của chính hai biến này.
Giả sử bạn vô tình quên dấu & khi gọi thủ tục swapswap(a,b); Điều này sẽ gây ra lỗi “segmentation fault”, bởi vì giá trị của a được truyền thay vì đúng ra phải là địa chỉ của nó. Do đó, i trỏ đến một vùng nhớ không hợp lệ dẫn đến lỗi hệ thống khi *i đựơc sử dụng.
Bây giờ bạn đã hiếu lý do tại so lệnh scanf lại không họat động khi bạn quên dấu scanf cũng dùng con trỏ để truyền tham biến. Thiếu &,scanf sẽ lưu giữ một địa chỉ không hợp lệ và gây ra lỗi.
Share this post
  • Share to Facebook
  • Share to Twitter
  • Share to Google+
  • Share to Stumble Upon
  • Share to Evernote
  • Share to Blogger
  • Share to Email
  • Share to Yahoo Messenger
  • More...

0 nhận xét

:) :-) :)) =)) :( :-( :(( :d :-d @-) :p :o :>) (o) [-( :-? (p) :-s (m) 8-) :-t :-b b-( :-# =p~ :-$ (b) (f) x-) (k) (h) (c) cheer

 
© Download do an khoa luan tai lieu
Designed by BlogThietKe Cooperated with Duy Pham
Released under Creative Commons 3.0 CC BY-NC 3.0
Posts RSSComments RSS
Back to top