Lập trình C: Bài 12 – Mối quan hệ giữa con trỏ và mảng, chuỗi ký tự
Nội dung
1. Con trỏ và mảng 1 chiều.
Như trong bài Mảng chúng ta đã biết ta có thể coi biến mảng như một con trỏ, vì vậy ta có thể sử dụng chính biến mảng đó để truy cập mảng theo cách của con trỏ.
//code by nguyenvanquan7826 #include <stdio.h> void nhapMang(int a[], int n) { int i; for (i = 0; i < n; i++) { printf("Nhap a[%d] = ", i); scanf("%d", &a[i]); } } void nhapContro(int a[], int n) { int i; for (i = 0; i < n; i++) { printf("Nhap a[%d] = ", i); scanf("%d", a + i); } } void xuatMang(int a[], int n) { int i; for (i = 0; i < n; i++) { printf ("%d \t", a[i]); } } int main() { // khai bao mang a co n phan tu int n = 5; int a[n]; nhapContro(a, n); xuatMang(a, n); return 0; }
Ở hàm thứ nhất ta đã quen với cách nhập bình thường, mình sẽ không nói nhiều nữa.
Ở hàm thứ hai, chúng ta chỉ thay &a bằng a+i vì khi khai báo a[20] thì a coi như là một con trỏ và máy sẽ cấp phát cho ta các ô nhớ liên tiếp từ a đến a + 19. Và a + i là địa chỉ của a[i] (tức nó tương đương với &a[i]). a trỏ đến vị trí a[0].
Ngoài ra bạn có thể khai báo 1 mảng sau đó dùng 1 con trỏ trỏ tới đầu mảng thì con trỏ đó cũng trở thành mảng đó.
//code by nguyenvanquan7826 #include <stdio.h> int main() { int n = 5, i; int a[n], *pa; pa = a; // con tro pa tro toi dau mang a for (i = 0; i < n; i++) { printf("Nhap a[%d] = ", i); scanf("%d", pa + i); } for (i = 0; i < n; i++) { printf ("%d \t", *(pa + i)); } return 0; }
Các bạn chú ý: tại sao ta không cần cấp phát ô nhớ cho con trỏ pa mà vẫn sử dụng được bình thường, bởi vì ta đã khai báo mảng a[20] nên máy đã cấp phát ô nhớ để lưu trữ mảng a, khi ta thực hiện trỏ con trỏ pa tới mảng a thì các ô nhớ này đã có rồi nên không cần cấp phát ô nhớ cho pa nữa. Ta xét ví dụ sau:
//code by nguyenvanquan7826 #include <stdio.h> #include <stdlib.h> int main() { int n = 5, i; // cap phat bo nho cho pa int *pa = (int *) malloc(n * sizeof(int)); for (i = 0; i < n; i++) { printf("Nhap a[%d] = ", i); scanf("%d", pa + i); } for (i = 0; i < n; i++) { printf ("%d \t", *(pa + i)); } return 0; }
Ở trong VD này ta không khai báo mảng a như VD trước, do đó cũng không thể trỏ pa tới vị trí nào, mà muốn thực hiện được ta cần cấp phát các ô nhớ cho pa như trên.
2. Nhập mảng trong hàm
Việc nhập mảng không phải lúc nào cũng thuận lợi vì như các ví dụ trước chúng ta cần phải có số lượng phần tử của mảng trước khi cấp phát hoặc nhập, vậy nếu chúng ta chưa biết trước số phần tử mà lại phải dùng hàm để nhập mảng thì sao. Các bạn xem ví dụ sau.
//code by nguyenvanquan7826 #include <stdio.h> #include <stdlib.h> void nhapContro(int *(*a), int *n) { int i; printf("Nhap so phan tu cua mang: "); scanf("%d", n); // khong phai &n *a = (int *) malloc ((*n) * sizeof(int)); // *a : lay dia chi cua mang a chu khong phai gia tri cua a for (i = 0; i < *n; i++) { printf("Nhap a[%d] = ", i); scanf("%d", (*a + i)); } } void xuatMang(int *a, int n) { int i; for (i = 0; i < n; i++) { printf ("%d \t", a[i]); } } int main() { int *a, n; nhapContro(&a, &n); // lay dia chi cua a va n xuatMang(a, n); return 0; }
Trong VD này ta thực hiện nhập và xuất mảng trong hàm, cấp phát bộ nhớ cũng trong hàm luôn. Các chú thích mình đã ghi rõ trong chương trình rồi.
Có 1 điểm chú ý là trong hàm nhập mảng a bằng con trỏ thì có 2 dấu *. 1 dấu là của mảng a (dấu thứ 2), còn dấu đầu tiên là dùng để truyền địa chỉ làm giá trị của mảng có thể giữ nguyên khi ra khỏi hàm, nó giống như là dấu * trong hàm HoanVi(int *a,int *b) vậy.
3.Con trỏ và xâu ký tự
Do xâu ký tự bản chất cũng là mảng các ký tự nên phần này nó cũng tương tự như mảng 1 chiều, mình sẽ nói qua 1 chút bằng 1 ví dụ đơn giản.
//code by nguyenvanquan7826 #include <stdio.h> #include <stdlib.h> int main() { char *name; name = (char *) malloc (100*sizeof(char)); printf("What your name? "); gets(name); printf("Oh, Hello %s\n", name); return 0; }
4. Con trỏ và mảng 2 chiều, mảng các con trỏ – Con trỏ đa cấp
a. Con trỏ và mảng 2 chiều
Phần trên chúng ta đã tìm hiểu về con trỏ và mảng 1 chiều, và phần này con trỏ và mảng 2 chiều cũng tương tự như vậy.
Như ta đã biết thực chất trong máy tính thì bộ nhớ lưu mảng 2 chiều giống như mảng 1 chiều. Vì vậy ta hoàn toàn có thể biểu diễn mảng 2 chiều bằng con trỏ giống như mảng 1 chiều.
//code by nguyenvanquan7826 #include <stdio.h> #include <stdlib.h> int main() { double a[10][10], *pa; int n, m, i; pa = (double *) a; printf("Nhap so hang va so cot:\n"); scanf("%d %d", &n, &m); for (i = 0 ; i < n * m; i++) { printf("Nhap a[%d][%d] = ", i / m, i % m); scanf("%lf", pa + i); } for (i = 0 ; i < n * m; i++) { if (i % m == 0) printf("\n"); // xuong dong printf("%-5.2lf", *(pa + i)); } return 0; }
Kết quả:
Nhap so hang va so cot:
2
3
Nhap a[0][0] = 4.23
Nhap a[0][1] = 5.7
Nhap a[0][2] = 1.2
Nhap a[1][0] = 8.6
Nhap a[1][1] = 3.456
Nhap a[1][2] = 124.23 5.70 1.20
8.60 3.46 12.00
Ngoài ra, chúng ta có thể không cần dùng pa mà dùng ngay a là 1 con trỏ. Hoặc ta có thể nhập 1 cách tương tự như mảng 2 chiều bình thường như sau.
Các bạn lưu ý là tại sao ta lại có pa + i*10 + j. Đó là vì ở hàm main() ta khai báo là a[10][10]
nên máy cấp phát cho chúng ta các ô nhớ của mảng 2 chiều với 10 hàng, 10 cột mà nếu ta ko dùng hết thì nó vẫn tồn tại.
Các bạn chú ý là ta có thể viết a[i][j] nhưng không thể viết pa[i][j] vì theo khai báo thì a là mảng 2 chiều nên ta có thể viết như vậy còn pa là 1 con trỏ và khi ta gán pa = a thì ta đã ngầm định coi a là mảng 1 chiều. Vì vậy nếu có thì chúng ta cũng chỉ được phép viết pa[i10+j] (lấy giá trị) và &pa[i10+j] (lấy địa chỉ).
b. Mảng các con trỏ
Mảng con trỏ là một mảng chứa tập hợp các con trỏ cùng một kiểu.
Float *a[10]; // khai báo một mảng con trỏ. Gồm 10 con trỏ: a[0], a[1], …a[9]; là 10 con trỏ.
Liên hệ với mảng 2 chiều thì ta có nhận xét sau: Mỗi hàng của mảng 2 chiều ta coi như 1 mảng 1 chiều. Mà mỗi con trỏ thì lại có mối quan hệ với 1 mảng 1 chiều, ta có hình vẽ:
//code by nguyenvanquan7826 #include <stdio.h> #include <stdlib.h> int main() { double a[10][10], *pa[10]; int n, m, i, j; printf("Nhap so hang va so cot: "); scanf("%d %d", &n, &m); for (i = 0 ; i < n; i++) { pa[i] = a[i]; // con tro thu i tro den hang thu i for (j = 0 ; j < m; j++) { printf("Nhap a[%d][%d] = ", i, j); scanf("%lf", &pa[i][j]); } } for (i = 0 ; i < n; i++) { printf("\n"); // xuong dong for (j = 0 ; j < m; j++) { printf("%-5.2lf", pa[i][j]); } } return 0; }
Sở dĩ ở VD này ta viết được pa[i][j] đó là do mỗi pa là 1 con trỏ đến mảng một chiều. pa[i][j] tức là phần tử thứ j của con trỏ pa[i].
Các ví dụ ở trên chúng ta đều xét khi mà khai báo mảng a[][] nên không cần cấp phát bộ nhớ cho con trỏ, bây giờ muốn cấp phát bộ nhớ cho con trỏ với mảng 2 chiều giống như cấp phát ở mảng 1 chiều thì ta làm như sau:
//code by nguyenvanquan7826 #include <stdio.h> #include <stdlib.h> int main() { double **pa; int n, m, i, j; printf("Nhap so hang va so cot: "); scanf("%d %d", &n, &m); // cap phat n o nho cho n con tro (n hang) pa = (double**) malloc(n * sizeof(double)); for (i = 0 ; i < n; i++) { // cap phat m o nho cho moi con tro (moi hang) pa[i] = (double *) malloc(m * sizeof(double)); for (j = 0 ; j < m; j++) { printf("Nhap a[%d][%d] = ", i, j); scanf("%lf", &pa[i][j]); } } for (i = 0 ; i < n; i++) { printf("\n"); // xuong dong for (j = 0 ; j < m; j++) { printf("%-5.2lf", pa[i][j]); } } return 0; }
Đây cũng có thể coi là mảng con trỏ, hoặc con trỏ trỏ đến con trỏ (con trỏ đa cấp).
anh ơi.chỗ i/m với i%m là sao z anh…. em không hiểu chỗ đó
cái i / m là lấy hàng hiện tại, i % m là lấy cột hiện tại. VD mảng a[10][10] thì khi i = 21, hàng hiện tại là 2, cột hiện tại là 1. Do mảng 2 chiều được coi như các hàng xếp thành 1 hàng như mảng 1 chiều ý bạn.
a ơi có giản thêm c++ hay opp không…cho e học hỏi với…tìm chỉ thấy ngôn ngử c thoi anh
Hiện tại chỉ có C thôi nhé.
A ơi cho e hỏi chút: Trong ví dụ nhập mảng bằng hàm a có sử dụng con trỏ cấp 2 với chú thích là mảng có thể giữ nguyên giá trị khi ra khỏi hàm.E có thử viết lại hàm đó nhưng với tham số là con trỏ cấp 1,kết quả là mảng vẫn nhận được giá trị a có thể giải thích cho e được không?Nhân tiện hỏi a khi nào thì cần dùng con trỏ cấp n?
code của e đây ạ:
#include
#include
#include
void nhapmang(int *a,int n)
{
for(int i=0;i<n;i++)
{
printf("\na[%d]=",i);
scanf("%d",(a+i));
}
}
void in(int *a,int n)
{
for(int i=0;i<n;i++)
{
printf("%d ",a[i]);
}
}
int main()
{
int *a;
int n;
a=(int*)malloc(n*sizeof(int));
printf("\nNhap n=");
scanf("%d",&n);
nhapmang(a,n);
in(a,n);
}
Xin lỗi bạn mấy hôm không vào blog. Mảng giữ lại được giá trị thì con trỏ đương nhiên giữ lại bạn ah. Vì con trỏ là một dạng của mảng, chẳng qua nó linh động hơn.
Con trỏ cấp n dùng khi bạn cần thao tác với mảng cấp n. Vì mảng cấp n khi vào hàm bắt buộc phải định nghĩa số hàng số cột, con trỏ thì không cần.
Bài viết của anh rất tỉ mỉ, hay và bổ ích. Cảm ơn anh đã gửi bài. Mong sẽ có nhiều bài hơn nữa được gửi lên! Thân.
Phần cấp phát bộ nhớ động cho con trỏ chưa có hả anh. Hi vongj anh sớm thêm phàn này.
Bạn xem ở vài 9 nhé.
a oi, e khong hieu chỗ “*(*a)” này là gì ạ?
void nhapContro(int *(*a), int *n)
Dấu * thứ nhất là để có thể giữ nguyên giá trị khi ra khỏi hàm. Dấu * thứ 2 là ám chỉ a là con trỏ
Anh ơi, ở phần cấp phát động cho mảng 2 chiều tại sao em dùng như này chương trình không chạy đc vậy.
*pa = (double*) malloc(n * sizeof(double));
for (i = 0 ; i < n; i++) {
// cap phat m o nho cho moi con tro (moi hang)
pa[i] = (double *) malloc(m * sizeof(double));
Lệnh đầu tiên bạn cần dùng như này nhé
pa = (double**) malloc(n * sizeof(double));
làm thế nào để cho con trỏ trỏ vào vị trí cuối của xâu kí tự ak
for (i = 0; i < *n; i++) {
printf("Nhap a[%d] = ", i);
scanf("%d", (*a + i));
}
}
// *n với (*a+i) ở đây em vẫn chưa hiểu lắm, anh giảng lại giúp em được hk, thanks anh nhiều nhiều :)))
Do n truyền vào là con trỏ, nên muốn lấy giá trị của nó phải dùng *n, đo đó *n nghĩa là giá trị của n. Còn a là mảng nên có thể dùng như con trỏ. *a + i là vị trí thứ i trong mảng a.
Ủa bạn kích thước của mảng phải là 1 hằng số không phải là biến thường dc.
int main()
{
const int n=5;
int a[n];
nhapContro(a, n);
xuatMang(a, n);
getch();
return 0;
}
anh cho em hỏi là em dùng mảng con trỏ để lưu tên và lưu họ đệm được tách ra từ 1 mảng các họ tên thì đang chạy chương trình thì bị dừng. không biết lỗi ở đâu anh xem giúp em
void TachTen(char *s, char *a){
int l, i, j, k;
i = strlen(s)-1;
j = 0; k = 0;
l = strlen(s);
fflush(stdin);
while(s[i] != ‘ ‘){
i–;
}
while(i < l){
a[j] = s[i+1];
i++;
j++;
}
}
void TachHo(char *s, char *b){
int i, j, k;
i = strlen(s)-1;
j = k = 0;
fflush(stdin);
while(s[i] != ' '){
i–;
}
for(j=0; j<i; j++){
b[j] = s[j];
}
}
case 3: //sx theo ten
printf("\nn = %d", n);
for(i=0; i<n; i++){
a[i] = (char*)malloc(100*sizeof(char));
b[i] = (char*)malloc(100*sizeof(char));
}
for(i=0; i<n; i++){
TachTen(SV[i].Ten, a[i]);
printf("\nTen sv[%d]: ", i+1);
for(j=0; j<strlen(a[i]); j++){
printf("%c", a[i][j]);
}
TachHo(SV[i].Ten, b[i]);
printf("\nHo sv[%d]: ", i+1);
for(j=0; j<strlen(b[i]); j++){
printf("%c", b[i][j]);
}
}
Co the ban bi loi o lenh while(s[i] != ‘ ‘){
i–;
}
i– moi đung.
cho mình hỏi ở phần con trỏ và mảng 2 chiều tại sao code này
pa = (double *) a;
tại sao mình phải thêm cái (double *) a; ?.
Đó là phép ép kiểu biến a thành con trỏ kiểu double. Vì biến pa là con trỏ nên cần ép kiểu như vậy.
cho mình hỏi đối với bài tập mảng thì khi nào gán con trỏ pa=a và khi nào thi gán pa= (double *) a vậy. Mình xin cảm ơn !!
Ah với bài mảng thì không cần đến con trỏ đâu, bạn cứ làm bình thường là ok. Bài này mình nêu lý thuyết về mối quan hệ giữa chúng thôi.
anh ơi chỗ pa+ i*10+j anh có thể giải thích kỹ hơn được không ạ.
pa là con trỏ. pa + x là con trỏ trỏ tiến thêm x đơn vị. Nó tương đuơng với a[x]. pa + i*10 + j tương đương với a[i*10+j].
a ơi cho e hỏi ở ví dụ thứ 3 phần 1, tai sao khi e cấ phát bộ nhớ cho pa em chỉ cho 3 hoặc 2 ô nhớ(n) thì vẫn nhập đc đủ 5 phân tử ạ:
pa = (int *) malloc(3 * sizeof(int));
Anh cho em hỏi là ở phần void(int *(*a), int *n)
không cần cấp phát bộ nhớ cho n mà vẫn nhập đc à anh ?
Vì khi gọi hàm này dùng &n để truyền địa chỉ vào nên không cần nhé.
Ad mình sử dụng mảng con trỏ để nhập và xuất các ký tự nhưng bị lội mình sữa nhiều lần không được, ad xem giùm mình với:
#include
#include
void nhap(char** s, int n){
for(int i=0;i<n;i++){
printf("\ns[%d]=",i);
scanf("%s",s[i]);
}
printf("\ns[%d]=%s",0,*s[0]);
}
void xuat(char** s, int n){
for(int i=0;i<n;i++){
printf("\ns[%d]=%s",i,*s[i]);
}
}
int main(){
int n;
printf("nhap n:");
scanf("%d",&n);
char *s[n];
nhap(s,n);
printf("\nMang vua nhap: ");
xuat(s,n);
}
em có sử dụng a là con trỏ luôn cho mảng 2 chiều nhưng sao kết quả lại k ra giống như con số đã nhập vậy anh ?
#include
int main()
{
int a[10][10];
int n,m,i;
printf(“nhap so hang va so cot : “);
scanf(“%d %d”,&n,&m);
for(i = 0;i < n*m;i++)
{
printf("a[%d][%d] :",i/m,i%m);
scanf("%d",a+i);
}
for(i = 0;i<n*m;i++)
{
if(i % m == 0) printf("\n");
printf("%d ",*(a+i));
}
return 0;
}
bài viết của anh rất bổ ích.