Con trỏ trong lập trình C: Con trỏ, Các loại & Ví dụ là gì – GeekPeach.net

Ý nghĩa của con trỏ trong C là tính linh hoạt mà nó mang lại trong lập trình. Con trỏ cho phép chúng ta đạt được tham số truyền qua tham chiếu, xử lý ngắn gọn và hiệu quả các mảng, biểu diễn cấu trúc dữ liệu phức tạp và làm việc với bộ nhớ được cấp phát động.

Mặc dù rất nhiều lập trình có thể được thực hiện mà không cần sử dụng con trỏ, nhưng việc sử dụng chúng giúp nâng cao khả năng thao tác dữ liệu của ngôn ngữ. Con trỏ cũng được sử dụng để truy cập các phần tử mảng, truyền mảng và chuỗi cho các hàm, tạo cấu trúc dữ liệu như danh sách được liên kết, cây, đồ thị, v.v.

Biến con trỏ là gì

Bộ nhớ có thể được hình dung như một chuỗi có thứ tự các vị trí lưu trữ được đánh số liên tục. Một mục dữ liệu được lưu trữ trong bộ nhớ ở một hoặc nhiều vị trí lưu trữ liền kề tùy thuộc vào loại của nó. Địa chỉ của một mục dữ liệu là địa chỉ của vị trí lưu trữ đầu tiên của nó. Địa chỉ này có thể được lưu trữ trong một mục dữ liệu khác và được thao tác trong một chương trình. Địa chỉ của một mục dữ liệu được gọi là con trỏ tới mục dữ liệu và một biến giữ địa chỉ được gọi là biến con trỏ.

Sử dụng con trỏ

  1. Theo dõi địa chỉ của các vị trí bộ nhớ.
  2. Bằng cách thay đổi địa chỉ trong biến kiểu con trỏ, bạn có thể thao tác dữ liệu trong các vị trí bộ nhớ khác nhau.
  3. Phân bổ bộ nhớ có thể được thực hiện động.

Toán tử địa chỉ và phân biệt (& AND *)

Hãy xem xét khai báo:

Trình biên dịch sẽ tự động gán bộ nhớ cho mục dữ liệu này. Mục dữ liệu có thể được truy cập nếu chúng ta biết vị trí (tức là địa chỉ) của ô nhớ đầu tiên.

Địa chỉ của vị trí bộ nhớ của num có thể được xác định bằng biểu thức & num, trong đó & là toán tử một ngôi, được gọi là toán tử ‘địa chỉ của’. Nó đánh giá địa chỉ của toán hạng của nó. Chúng ta có thể gán địa chỉ của num cho một biến khác, pnum như sau:

Biến pnum mới này được gọi là pointer to num, vì nó trỏ đến vị trí lưu trữ num trong bộ nhớ. Do đó pnum được gọi là pointer variable. Mục dữ liệu được đại diện bởi num, có thể được truy cập bằng biểu thức * pnum, trong đó * là toán tử một ngôi, được gọi ‘the value at the address’ nhà điều hành. Nó chỉ hoạt động trên một biến con trỏ.

Nó có thể được minh họa như sau:

khái niệm cơ bản về con trỏ c

Mối quan hệ giữa pnum và num (trong đó pnum = & num và num = * pnum). Do đó, * pnum và num đều đại diện cho cùng một mục dữ liệu. Truy cập một mục dữ liệu thông qua một con trỏ được gọi là Hội nghị truyền hình và dấu sao của toán tử dereferencing or indirection operatornó được gọi là

.

Khai báo kiểu con trỏ

Con trỏ cũng là các biến và do đó, phải được định nghĩa trong một chương trình giống như bất kỳ biến nào khác. Quy tắc khai báo tên biến con trỏ cũng giống như biến thường.

Khai báo một con trỏ có dạng sau:
typeở đâu,
variable_name: Kiểu dữ liệu của biến được trỏ bởi biến con trỏ.
*(asterisk): Tên của biến con trỏ

: Báo hiệu với trình biên dịch rằng biến này phải được coi là một con trỏ đến kiểu dữ liệu được chỉ định bởi kiểu.

int *int_ptr       ### int_ptr is a pointer to data of type integer
char *ch_ptr       ### ch_ptr is a pointer to data of type character
double *db_ptr     ### db_ptr is a pointer to data of type double

Ví dụ,Ghi chú

: Kích thước của bất kỳ con trỏ nào trong C cũng giống như kích thước của một số nguyên không dấu. Do đó nó phụ thuộc vào Kiến trúc.

Chỉ định con trỏ

Toán tử addressof (&), khi được sử dụng làm tiền tố cho tên biến, sẽ cung cấp địa chỉ của biến đó.

Vì vậy,

/* Example of ‘&’ - address of operator */ 
#include <stdio.h>
void main(void) 
{ 
    int a=100; 
    int b=200; 
    int c=300; 
    printf(Address:%u contains value :%dn, &a, a); 
    printf(Address:%u contains value :%dn, &b, b); 
    printf(Address:%u contains value :%dn, &c, c); 
}

gán địa chỉ của biến i cho ptr.

Address:65524 contains value :100 
Address:65520 contains value :200 
Address:65516 contains value :300

Đầu ra:

Một giá trị con trỏ có thể được gán cho một con trỏ khác cùng loại.

int i=1, j, *ip; 
ip=&i; 
j=*ip; 
*ip=0;

Ví dụ, trong chương trình dưới đây:

Phép gán đầu tiên gán địa chỉ của biến i cho ip. Địa chỉ thứ hai gán giá trị tại địa chỉ ip, nghĩa là 1 đến j, và cuối cùng cho địa chỉ thứ ba gán 0 cho i vì * ip giống với i.

Hai tuyên bố

tương đương với nhiệm vụ duy nhất

hoặc nhiệm vụ

tức là, địa chỉ của toán tử & là nghịch đảo của toán tử hội nghị truyền hình *.

#include <stdio.h>
void main(void) 
{ 
    char *ch; 
    char b = ’A’; 
    ch = &b; /* assign address of b to ch */ 
    printf(%c, *ch); 
}

Hãy xem xét đoạn mã sau:

Biểu diễn bộ nhớ của con trỏ
bTrong ví dụ trên,
&b: giá trị của b, là ‘A’
ch: địa chỉ của b, tức là, 36624
&ch: giá trị của ch, là 36624
*ch: địa chỉ của ch, tức là, 4020 (tùy ý)

: nội dung của ch, => giá trị ở 36624, tức là A. Điều này giống với * (& b)

Khởi tạo con trỏ

type *identifier=initializer; 

Việc khai báo một biến con trỏ có thể đi kèm với một bộ khởi tạo. Hình thức khởi tạo biến con trỏ là:

Bộ khởi tạo phải đánh giá đến một địa chỉ của dữ liệu được xác định trước đó thuộc kiểu thích hợp hoặc nó có thể là con trỏ NULL. Ví dụ, khai báo khởi tạo fp với giá trị null.

char c[10]; 
char *cp=&c[4]; 

Các tuyên bố

khởi tạo cp thành địa chỉ của phần tử thứ năm của mảng c.

khởi tạo cfp thành địa chỉ của phần tử đầu tiên của mảng c. Nó cũng có thể được viết là:

/* Example : Usage of Pointers */ 
# include <stdio.h>
void main(void) 
{ 
    int i, j=1; 
    int *jp1, *jp2=&j; /* jp2 points to j */ 
    jp1 = jp2; /* jp1 also points to j */ 
    i = *jp1; /* i gets the value of j */ 
    *jp2 = *jp1 + i; /* i is added to j */ 
    printf(i=%d j=%d *jp1=%d *jp2=%dn, i, j, *jp1, *jp2); 
}

Địa chỉ của phần tử đầu tiên của mảng còn được gọi là địa chỉ cơ sở của mảng. Chương trình sau minh họa khai báo, khởi tạo, gán và tham chiếu đến con trỏ.

Đầu ra:

Số học con trỏ

Số học có thể được thực hiện trên con trỏ. Tuy nhiên, trong số học con trỏ, một con trỏ chỉ là một toán hạng hợp lệ cho các toán tử cộng (+) và trừ (-). Một giá trị tích phân n có thể được thêm vào hoặc trừ đi từ một con trỏ ptr. Giả sử rằng mục dữ liệu mà ptr trỏ đến nằm trong một mảng các mục dữ liệu đó. Kết quả là một con trỏ tới mục dữ liệu đặt n mục dữ liệu sau hoặc trước một ptr trỏ tới tương ứng.

#include <stdio.h>
void main(void) 
{ 
    int i=3, *x; 
    float j=1.5, *y; 
    char k=’C’, *z; 
    printf(Value of i=%dn, i); 
    printf(Value of j=%fn, j); 
    printf(Value of k=%cn, k); 
    x=&i; 
    y=&j; 
    z=&k; 
    printf(Original Value in x=%un, x); 
    printf(Original Value in y=%un, y); 
    printf(Original Value in z=%un, z); 
    x++; 
    y++; 
    z++; 
    printf(New Value in x=%un, x); 
    printf(New Value in y=%un, y); 
    printf(New Value in z=%un, z); 
 }

Giá trị của ptr ± n là vị trí lưu trữ ptr ± n * sizeof (* ptr), trong đó sizeof là một toán tử cho ra kích thước tính bằng byte của toán hạng của nó. Hãy xem xét ví dụ sau:

Value of i=3 
Value of j=1.500000 
Value of k=C 
Original Value in x=1002 
Original Value in y=2004 
Original Value in z=5006 
New Value in x=1006 
New Value in y=2008 
New Value in z=5007

Đầu ra:

Trong ví dụ trên, Giá trị mới trong x là 1002 (giá trị gốc) +4, Giá trị mới trong y là 2004 (giá trị gốc) +4, Giá trị mới trong z là 5006 (giá trị gốc) +1.

Điều này xảy ra bởi vì mỗi khi con trỏ được tăng lên, nó sẽ trỏ đến vị trí tiếp theo ngay lập tức cùng loại của nó. Đó là lý do tại sao, khi con trỏ số nguyên x được tăng lên, nó trỏ đến một địa chỉ bốn vị trí sau vị trí hiện tại, vì một int luôn dài 4 byte. Tương tự, y chỉ đến một địa chỉ 4 vị trí sau vị trí hiện tại và z chỉ 1 vị trí sau vị trí hiện tại.

Một số số học con trỏ hợp lệ như được hiển thị bên dưới:

Phép cộng một số vào một con trỏ

int *ip;
int a[10];
ip = &a[3];

Ví dụ, chúng ta có thể viết[0] và chúng ta sẽ kết thúc với việc ip trỏ vào ô thứ tư của mảng a (hãy nhớ rằng các mảng dựa trên 0, vì vậy a

là ô đầu tiên).  Chúng tôi có thể minh họa tình huống như thế này:

Phép cộng một số vào một con trỏ trong C[3]Chúng ta sẽ sử dụng ip này giống như ip trong phần trước: * ip cung cấp cho chúng ta ip trỏ đến, trong trường hợp này sẽ là giá trị trong[3]. Khi chúng ta có một con trỏ trỏ vào một mảng, chúng ta có thể bắt đầu thực hiện số học con trỏ. Cho rằng ip là một con trỏ tới một

chúng ta có thể thêm 1 vào ip:[4]Thêm một vào con trỏ có nghĩa là gì? Trong C, nó cung cấp một con trỏ đến ô ở xa hơn, trong trường hợp này là

. Để làm rõ điều này, hãy gán con trỏ mới này cho một biến con trỏ khác:

Bây giờ bức tranh trông như thế này:

Con trỏ C

Nếu bây giờ chúng ta làm[4] chúng tôi đã thiết lập một

đến 4.

Phép trừ một số từ một con trỏ

int arr[ 10 ] ; 
int * p1, * p2 ; 
 
p1 = arr + 3 ; // p1 == & arr[ 3 ] 
p2 = p1 - 2 ; // p1 == & arr[ 1 ]

Chúng ta cũng có thể tính ptr – i. Ví dụ, giả sử chúng ta có một mảng int được gọi là arr. scale factorĐiều này là do khi một con trỏ được giảm (hoặc tăng), nó được thực hiện theo độ dài của kiểu dữ liệu mà nó trỏ tới, được gọi là

.

  • Một số số học con trỏ không hợp lệ là:
  • Cộng hai con trỏ.
  • Phép nhân một số với một con trỏ.

Phép chia con trỏ với một số.

So sánh con trỏ

Phép so sánh quan hệ ==,! = Được phép giữa các con trỏ cùng loại. Phép so sánh quan hệ <, <=,>,> = được phép giữa các con trỏ cùng kiểu và trỏ đến cùng kiểu. Kết quả phụ thuộc vào vị trí tương đối của hai mục dữ liệu được trỏ tới.

Ví dụ,

cách diễn đạt

là true nếu ap đang trỏ đến phần tử cuối cùng của mảng a và biểu thức

là đúng miễn là ap chỉ đến một trong các phần tử của a.

Con trỏ và Hàm

double *maxp(double *xp, double *yp) 
{ 
    return *xp >= *yp ? x; 
}

Một hàm có thể nhận một con trỏ đến bất kỳ kiểu dữ liệu nào, làm đối số và có thể trả về một con trỏ cho bất kỳ kiểu dữ liệu nào. Ví dụ, định nghĩa hàm

chỉ định rằng hàm maxp () trả về một con trỏ cho một biến kép và mong đợi hai đối số, cả hai đều là con trỏ đến biến kép. Hàm loại bỏ tham chiếu đến hai con trỏ đối số để nhận giá trị của các biến tương ứng và trả về con trỏ cho biến có giá trị lớn hơn trong hai giá trị. Do đó,

tuyên bố

làm cho mp trỏ tới v.

gọi theo giá trị

/* Example: Function parameters passed by Value */ 
#include  
void main(void) 
{ 
   int a=5, b=7; 
   void swap(int, int); 
   printf(Before function call: a=%d b=%d, a, b); 
   swap(a, b); /* Variables a and b are passed by value */ 
   printf(After function call: a=%d b=%d, a, b); 
} 
void swap(int x, int y) 
{ 
   int temp; 
   temp=x; 
   x=y; 
   y=temp; 
}

Trong một lệnh gọi theo giá trị, các giá trị của các đối số được sử dụng để khởi tạo các tham số của hàm được gọi, nhưng địa chỉ của các đối số không được cung cấp cho hàm được gọi. Do đó, bất kỳ thay đổi nào về giá trị của một tham số trong hàm được gọi sẽ không được phản ánh trong biến được cung cấp dưới dạng đối số trong hàm gọi.

Before function call: a=5 b=7 
After function call: a=5 b=7

Đầu ra:

Gọi bằng cách tham khảo

Ngược lại, trong một lệnh gọi bằng tham chiếu, địa chỉ của các biến được cung cấp cho hàm được gọi và những thay đổi đối với giá trị tham số trong hàm được gọi gây ra những thay đổi về giá trị của biến trong hàm đang gọi.

/* Example : Arguments as pointers */ 
#include  
void main(void)
{ 
   int a=5, b=7; 
   void swap(int*, int*); 
   printf(Before function call: a=%d b=%d, a, b); 
   swap(&a, &b); /* Address of variable a and b is passed */ 
   printf(After function call: a=%d b=%d, a, b); 
} 
void swap(int *x, int *y) 
{ 
    int temp; 
    /* The contents of memory location are changed */
    temp=*x; 
    *x=*y; 
    *y=temp; 
}

Gọi bằng tham chiếu có thể được thực hiện bằng cách chuyển con trỏ đến các biến làm đối số cho hàm. Sau đó, các con trỏ này có thể được sử dụng bởi hàm được gọi để truy cập các biến đối số và thay đổi chúng.

Before function call: a=5 b=7 
After function call: a=7 b=5 

Đầu ra:

  1. Các bước liên quan để sử dụng con trỏ trong một hàm là
  2. Truyền địa chỉ của biến (Sử dụng dấu và (&) hoặc các biến con trỏ trực tiếp).
  3. Khai báo biến dưới dạng con trỏ trong quy trình.

Tham chiếu đến các giá trị có trong một vị trí bộ nhớ thông qua dấu hoa thị

/* Returning more than one values from a function through arguments */ 
# include <stdio.h>
void main(void) 
{ 
    float radius; 
    float area, peri; 
    void areaperi(float, float*, float*); 
    printf("Enter radius : "); 
    scanf("%f", &radius); 
    areaperi(radius, &area, &peri); 
    printf("nArea = %.2f n", area); 
    printf("Perimeter = %.2f", peri); 
} 
void areaperi(float r, float *a, float *p) 
{ 
    *a = 3.14 * r * r; 
    *p = 2 * 3.14 * r; 
}

.

Enter radius of a circle : 5 
Area=78.50 
Perimeter=31.40