Hiển thị các bài đăng có nhãn C Programing. Hiển thị tất cả bài đăng
Hiển thị các bài đăng có nhãn C Programing. Hiển thị tất cả bài đăng

Thứ Sáu, 22 tháng 4, 2016

Cấp phát động cho mảng 2 chiều

-------------------------------------------------------------------------------------------------
#include <stdio.h>
#include<conio.h>
#include <stdlib.h>

void NhapMaTran(int **a, int dong, int cot)
{
int i, j;
for (i = 0; i < dong; i++)
for (j = 0; j < cot; j++)
{
printf("a[%d][%d] = ", i, j);
scanf("%d", &a[i][j]);
}
}

void XuatMaTran(int **a, int dong, int cot)
{
int i, j;
for (i = 0; i < dong; i++)
{
for (j = 0; j < cot; j++)
printf("%5d", a[i][j]);

printf("\n");
}
}
int main()
{
int **a = NULL, dong, cot;
int i;
printf("Nhap vao so dong: "); scanf("%d", &dong);
printf("Nhap vao so cot: "); scanf("%d", &cot);

a = (int**)malloc(dong * sizeof(int*)); 

for(i = 0; i < dong; i++)
{
a[i] = (int *)malloc(cot * sizeof(int));
}

NhapMaTran(a, dong, cot);
XuatMaTran(a, dong, cot);

for(i = 0; i < dong; i++)
{
free(a[i]);
}

// giai phong tat ca con tro quan ly dau moi dong
free(a);


getch();
return 0;
}
-------------------------------------------------------------

Thứ Hai, 4 tháng 1, 2016

- Đây là mã nguồn tôi viết
//a + i <=> &a[0 + i] ;

//*(a+i) <=> a[i]
void NhapMang(int *a, int n)
{
//int *pa = a; // Cách 1: Lúc này địa chỉ biến pa cũng là địa chỉ biến a, dùng cho TH có con trỏ pa
int *pa = &a[0]; // Cách 2
for(int i = 0; i < n; i++)
{
printf("\nNhap a[%d]: ", i);
//scanf("%d", &a[i]);
//scanf("%d", &pa[i]);
//scanf("%d", a + i);
//scanf("%d", pa + i);
//scanf("%d", a++);
scanf("%d", pa++);
}
}
// &a[i] <=> (a + i) <=> (pa + i) <=> &pa[i]
void XuatMang(int *a, int n)
{
//int *pa = a; // Giải thích giống
int *pa = &a[0]; // Cách 2
for(int i = 0; i < n; i++)
{
//printf("%4d", a[i]);
//printf("%4d", pa[i]);
//printf("%4d", *(a + i));
//printf("%4d", *(pa + i));
//printf("%4d", *(a++));
printf("%4d", *(pa++));
}
}
int main()
{
int n;
do{
printf("\nNhap so luong phan tu cua mang: ");
scanf("%d", &n);
if(n < 0)
{
printf("\nSo luong phan tu khong hop le. Xin kiem tra lai !");
}
}while(n < 0);

int *a = (int *)malloc(n * sizeof(int));
//a = (int *)calloc(n, sizeof(int));
//a = (int *)realloc(0, n * sizeof(int));

NhapMang(a, n);
XuatMang(a, n);
free(a);

getch();
return 0;
}
- Hàm cấp phát mảng n số thực(xác định số lượng phần tử trước khi cấp phát)
void arrayFloatOutput(float a[], int n)
{
for(int i = 0; i < n; i++)
{
printf("\n%f", a[i]);
}
}

float* arrayFloatInput(int* n)
{
float *a = NULL;
int m;
scanf("%d", &m);
if(m <= 0)
{
return NULL;
}
*n = m;
a = (float*)calloc(m, sizeof(float));
if(a != NULL)
{
for(int i = 0; i < m; i++)
{
scanf("%f", &a[i]);
}
}
return a;
}

int main()
{
float *B; int nB;
printf("Number and elements: ");
B = arrayFloatInput(&nB);
if(B != NULL)
{
// Do something here
arrayFloatOutput(B, nB);
// Free Allocated memory
free(B);
}
getch();
return 0;
void arrayFLoatOutput(float a[], int n)
{
for(int i = 0; i < n; i++)
{
cout << a[i] << "\n";
}
}

float* arrayFloatInput(int &n)
{
cin >> n;
if(n <= 0)
{
return NULL;
}
float* a = new float[n];
if(a != NULL)
{
for(int i = 0; i < n; i++)
{
cin >> a[i];
}
}
return a;
}
int main()
{
float *B; int nB;
printf("Number and elements: ");
B = arrayFloatInput(nB);
if(B != NULL)
{
// Do something here
arrayFLoatOutput(B, nB);
// Free Allocated memory
delete[]B;
}
- Hàm cấp phát mảng n số thực(dùng biến cấu trúc)
typedef struct{
int n;
float* members;
}floatArray;

void floatArrayInit(floatArray* a, int n)
{
a->n = n;
a->members = (float*)calloc(n, sizeof(float));
}

void floatArrayFree(floatArray* a)
{
if(a != NULL)
{
if(a->members != NULL)
{
free(a->members);
}
}
}

void floatArrayInput(floatArray* a)
{
int m;
if(a == NULL) return;
scanf("%d", &m);
if(m <= 0) return;
floatArrayInit(a, m);
if(a->members != NULL)
{
for(int i = 0; i < m; i++)
{
scanf("%f", &(a->members[i]));
}
}
}

void floatArrayOutput(floatArray* a)
{
if(a == NULL || a->members == NULL)
{
return;
}
for(int i = 0; i < a->n; i++)
{
printf("\n%f", a->members[i]);
}
}

int main()
{
floatArray B;
printf("\nNumber and elements: ");
floatArrayInput(&B);
// Do something here
printf("\nArray is: ");
floatArrayOutput(&B);

// Free allocated memory
floatArrayFree(&B);

getch();
return 0;
typedef struct{
int n;
float* members;
}floatArray;

void floatArrayInit(floatArray& a, int n)
{
a.n = n;
a.members = new float[n];
}

void floatArrayFree(floatArray& a)
{

if(a.members != NULL)
{
delete[]a.members;
}

}

void floatArrayInput(floatArray& a)
{
int m;
cin >> m;
if(m <= 0) return;
floatArrayInit(a, m);
if(a.members != NULL)
{
for(int i = 0; i < m; i++)
{
cin >> a.members[i];
}
}
}

void floatArrayOutput(floatArray& a)
{
if(a.members == NULL)
{
return;
}
for(int i = 0; i < a.n; i++)
{
cout << a.members[i] << " ";
}
}

int main()
{
floatArray B;
printf("\nNumber and elements: ");
floatArrayInput(B);
// Do something here
printf("Array is: ");
floatArrayOutput(B);

// Free allocated memory
floatArrayFree(B);

getch();
return 0;
- Hàm cấp phát mảng n số thực (không hỏi trước số lượng phần tử)
void floatArrOut(float a[], int n)
{
for(int i = 0; i < n; i++)
{
printf("%f", a[i]);
}
}

float* floatArrPushBack(float*a, int *n, float x)
{
int m = (*n) + 1;
float* anew = (float*)realloc(a, m * sizeof(float));
if(anew != NULL)
{
anew[*n] = x;
(*n)++;
}
return anew;
}

float * floatArrIn(int *n)
{
float* anew, *a = NULL;
float x;
*n = 0;
while(scanf("%f", &x) > 0)
{
anew = floatArrPushBack(a, n, x);
if(anew != NULL)
{
a = anew;
}
}
return a;
}

int main()
{
float *B;
int nB;
printf("Input element: \n");
B = floatArrIn(&nB);
// Do something here
printf("%d element(s): \n", nB);
floatArrOut(B, nB);
// Free Allocated memory
if(B != NULL)
free(B);
getch();
return 0;
}
void floatArrOut(float a[], int n)
{
for(int i = 0; i < n; i++)
{
cout << a[i] << " ";
}
}

void floatArrPushBack(float *&a, int &n, float x)
{
int m = n + 1;
float *anew = (float*)realloc(a, m * sizeof(float));
if(anew != NULL)
{
anew[n] = x;
n++;
a = anew;
}
}

void floatArrIn(float*& a, int &n)
{
float x;
a = NULL;
n = 0;
while(cin >> x)
{
floatArrPushBack(a, n, x);
}
}
int main()
{
float *B; int nB;
cout << "Input elements: " << endl;
floatArrIn(B, nB);
// Do something here
cout << nB << "element(s): " << endl;
floatArrOut(B, nB);
// Free Alocated memory
if(B != NULL)
free(B);

getch();
return 0;
}
- Hàm cấp phát mảng các phần tử kiểu tùy ý và áp dụng cho kiểu Phân Số
// Tập tin _ARRAY1D_T_H
template <class T>
void arrPushBack(T *&a, int &n, T x)
{
int m = n + 1;
T* anew = (T*)realloc(a, m * sizeof(T));
if(anew != NULL)
{
anew[n] = x; n++;
a = anew;
}
}
template <class T>
void arrInput(T *&a, int &n)
{
T x;
a = NULL; n = 0;
while(cin >> x)
{
arrPushBack(a, n, x);
}
}
template <class T>
void arrOutput(T a[], int n)
{
for(int i = 0; i < n; i++)
{
cout << a[i] << " ";
}
}
#endif // -------End of FILE-------------
struct PhanSo
{
int tuso, mauso;
};

ostream& operator<< (ostream& outDev, const PhanSo& ps)
{
if(ps.mauso == 1|| ps.tuso == 0)
outDev << ps.tuso;
else
outDev << ps.tuso << "/" << ps.mauso;
return outDev;
}
istream& operator>> (istream& inDev, PhanSo& ps)
{
inDev >> ps.tuso >> ps.mauso;
return inDev;
}

int main()
{
PhanSo *B; int nB;
cout << "Input elements of array: \n";
arrInput(B, nB);
// Do something here
cout << nB << " element(s): \n";
arrOutput(B, nB);
// Free Alocated memory
if(B != NULL)
free(B);

getch();
return 0;
}
Đoạn chương trình minh họa về việc tính hiệu của hai con trỏ đang lưu địa chỉ bộ nhớ. Trước tiên là các dòng mã đang bị chú thích, nếu gỡ bỏ việc ghi chú thì sẽ bị lỗi biên dịch. Khi chạy chương trình thì kết quả in ra là d1 = 6, d2 = 24, d3 = 12, Distance = 24. Trong đó giá trị d2 và Distance là 24 chính là độ lệch tính bằng byte của địa chỉ bộ nhớ do cách tính toán ở các dòng "ép kiểu sang char*" và "ép kiểu thô bạo thành số nguyên long". Bởi vì ptr1 giữ địa chỉ &a[7] và ptr2 giữ địa chỉ &a[1] nên độ lệch địa chỉ tính bằng byte giữa ptr1 và ptr2 là d2 = (7 - 1) x sizeof(long) = 6 x 4 = 24 byte. Giá trị d1 là 6 vì lý do là d1 = 24/sizeof(long) = 24/4 = 6. Giá trị d3 = 12 vì lý do d3 = 24/sizeof(short) = 24/2 = 12.

int main()
{
long a[10];
void *ptr1 = &a[7], *ptr2 = &a[1];
// long d0 = ptr1 - ptr2; Lỗi do tính hiệu số hai con trỏ kiểu void không biết kích thước
long d1 = (long*)ptr1 - (long*)ptr2;
long d2 = (char*)ptr1 - (char*)ptr2;
long d3 = (short*)ptr1 - (short*)ptr2;
printf("\nd1 = %ld", d1);
printf("\nd2 = %ld", d2);
printf("\nd3 = %ld", d3);
printf("\nDistance = %ld", (long)ptr1 - (long)ptr2);

// long d4 = (long*)ptr1 - (char*)ptr2 // Lỗi vì tính hiệu số hai con trỏ khác kiểu
// long d5 = (long*)ptr1 - (float*)ptr2 // Lỗi vì tính hiệu số hai con trỏ khác kiểu
getch();
return 0;
}
Chúng ta sẽ xét 1 số ví dụ mình họa cho quy tắc tính toán địa chỉ vừa được trình bày. Hình trên trình bày một ví dụ về tính toán địa chỉ con trỏ và các ký pháp lúc viết chương trình. Con trỏ p kiểu double nhận địa chỉ của biến a[1] là phần tử thứ hai trong mảng năm số. Vì p = &a[1] và đang dùng cùng kiểu double nên theo quy ước ký pháp ở bài trước thì *p, p[0], và a[1] là cùng 1 biến, tức là mặc dù viết khác nhưng chúng tham chiếu đến cùng 1 ô nhớ 8-byte(kích thước biến double) có thể chứa một số thực kiểu double. Địa chỉ p + 1 dời đi so với p một độ dời bằng với 1 x sizeof(double) = 8 byte, nên p + 1 cũng là &a[2], do đó *(p + 1), p[1], a[2] là ba cách viết khác nhau cùng tham chiếu đến cùng một biến. Tương tự *(p + 3), p[3], a[4] cũng tham chiếu đến cùng 1 biến a[4].
Địa chỉ (float*)p + 4 là một con trỏ kiểu float* dời đi so với p một độ dời là 4 x sizeof(float) = 16 byte = 2 x (8 byte). Do đó vì p đang giữ địa chỉ của a[1] nên (float*)p + 4 bằng với: địa chỉ (a[1]) + 16 byte và trùng với địa chỉ của a[3]. Tuy nhiên ký pháp biến *((float*)p + 4) (một biến float kích thước 4-byte) vô nghĩa vì tương đương với phân nửa của biến a[3] (một biến double kích thước 8-byte). Nếu ép kiểu để chuyển về double như là *(double*)((float*)p + 4) thì chính là biến a[3], cũng cùng nghĩa với *(p + 2).

Chủ Nhật, 3 tháng 1, 2016

-Các phép tính cơ bản cho các biến con trỏ có kiểu(tức không phải là void*)
*Phép cộng, trừ với số: con trỏ(địa chỉ bộ nhớ) +(hoặc -) số nguyên. Phép toán này có ý nghĩa là tạo ra một địa chỉ bộ nhớ có độ lệch so với địa chỉ gốc một số byte tùy thuộc vào kiểu con trỏ và số nguyên mà ta cộng hay trừ vào địa chỉ.
*Phép trừ 2 con trỏ: con trỏ(địa chỉ 1) - con trỏ(địa chỉ 2). Phép toán này yêu cầu 2 con trỏ phải có cùng kiểu. Kết quả cho ra độ lệch địa chỉ có đơn vị tính tùy thuộc vào kiểu con trỏ.
 -Riêng đối với kiểu void*(con trỏ vô kiểu) thì không thể thực hiện 2 phép toán nói trên. Khi muốn tính toán địa chỉ trên con trỏ vô kiểu thì ta phải ép kiểu con trỏ. Thông thường nếu muốn tính theo đơn vị byte thì các con trỏ được ép kiểu sang char*.
- Giả sử pVar là biến con trỏ kiểu T* với T là một kiểu dữ liệu nào đó khác kiểu void và n là 1 số nguyên(có thể âm hay dương). Khi đó pVar + n là địa chỉ bộ nhớ sẽ dời đi so với địa chỉ pVar một độ dời là n x sizeof(T).Nghĩa là chúng ta có công thức sau:
 Địa chỉ(pVar + n) = Địa chỉ(pVar) + n x sizeof(T)
Công thức nói trên cũng lý giải tại sao con trỏ kiểu void* không thể cộng hay trừ với số nguyên bởi vì sizeof(void) không xác định. Đối với con trỏ kiểu char* vì sizeof(char) = 1 nên khi T = char thì pVar + n dời đi so với pVar đúng n byte.
 Quy ước ký pháp: Với điều kiện địa chỉ hợp lệ thì ký hiệu *(pVar + n) được đồng nhất với biến kiểu T tại địa chỉ pVar + n, biến này cũng có thể được truy xuất đến bằng cách dùng ký hiệu mảng pVar[n].
 - Giả sử 2 biến con trỏ pVar1 và pVar2 cùng có kiểu là T* với T là 1 kiểu dữ liệu nào đó khác void. Khi đó pVar1 - pVar2 là độ lệch được tính bằng sizeof(T), nói 1 cách đơn giản hơn là độ lệch này được tính bằng số lượng các phần tử có kiểu là T. Tức là ta có công thức sau:
                             Địa chỉ(pVar1) - Địa chỉ(pVar2) 
 pVar1 - pVar2 = ---------------------------------------
                                          sizeof(T) 
-> Áp dụng công thức khi T = char thì (char*)pVar1 - (char*)pVar2 là độ lệch địa chỉ giữa 2 con trỏ được tính bằng đơn vị là byte.
Các chương trình viết bằng C/C++ chuẩn có thể xin cấp phát bộ nhớ từ hệ điều hành, địa chỉ đầu của vùng nhớ được cấp phát sẽ được trả về thông qua biến con trỏ. Sau khi vùng nhớ sử dụng xong, người lập trình cần phải giải phóng vùng nhớ, để trả về bộ nhớ cho hệ thống.

void main()
{
int n; float *a = NULL; // Khai báo con trỏ a kiểu float*
printf("\nEnter array size: ");
scanf("%d", &n);
if(n < 1)
{
printf("\nInvalid size!"); return;
}
a = (float*)malloc(n * sizeof(float)); // gán đến địa chỉ vùng nhớ xin được cấp phát
if(a == NULL)
{
printf("Memory error!"); return;
}
for(int i = 0; i < n; i++)
{
printf("a[%d] = ", i);
scanf("%f", &a[i]);
}
printf("\nData: ");
for(int i = 0; i < n; i++)
{
printf("%f", a[i]);
// do something here
// Finally freeing memory
}
free(a);
getch();
}

void main()
{
int n; float *a = NULL; // Khai báo con trỏ a kiểu float*
cout << "Enter array size: ";
cin >> n;
if(n < 1)
{
cout << "Invalid size!"; return;
}
a = new float[n]; // gán đến địa chỉ vùng nhớ xin được cấp phát
if(a == NULL)
{
cout << "Memory Error!"; return;
}
for(int i = 0; i < n; i++)
{
cout << "a[" << i << "] = ";
cin >> a[i];
}
cout << "Data: ";
for(int i = 0; i < n; i++)
{
cout << a[i] << " ";
// do something here
// Finally freeing memory
}
delete[]a;
getch();
}
Mã nguồn minh họa việc gán con trỏ bằng 1 vùng nhớ được cấp phát, được viết bằng ngôn ngữ C và C++. Chương trình này chỉ làm 1 việc đơn giản là xin cấp phát 1 mảng n số thực kiểu float, nhập n số thực, xuất ra màn hình và sau cùng giải phóng bộ nhớ. Biến con trỏ a kiểu float* được khai báo, sau đó gán đến địa chỉ vùng nhớ xin được cấp phát. Đối với C++, việc gọi new float[n] nghĩa là xin 1 vùng nhớ liên tục chứa được n số float, thực chất thì vùng nhớ này gồm 4n byte bởi vì mỗi số float có kích thước là 4 byte theo chuẩn IEEE. Đối với C, bởi vì hàm malloc() cần nhận vào 1 tham số quy định số byte được cấp phát và trả về con trỏ kiểu void* nên việc gọi hàm cấp phát có phức tạp hơn. Chỉ thị (float*)malloc(n*sizeof(float)) có nghĩa là xin 1 vùng nhớ có kích thước là n*sizeof(float) = 4n byte, bởi vì địa chỉ vùng nhớ trả về kiểu void* nên phải ép kiểu sang float* mới có thể gán được vào biến con trỏ a có kiểu là float*.
Trong nhiều tình huống, chúng ta có thể cần viết các hàm kết hợp sử dụng cả tham số con trỏ và giá trị trả về kiểu con trỏ. Chằng hạn hàm trả về chuỗi lớn hơn khi so sánh theo thứ tự alphabet. Hàm này có thể được mô tả là char* strmax(char* str1, char* str2); khi chuỗi str1 lớn hơn str2 (tức là đứng sau str2 theo thứ tự từ điển) thì hàm trả về str1, ngược lại trả về str2. Trường hợp này ta thấy hàm strmax() nhận vào các tham số là 2 con trỏ kiểu char* và cũng trả về con trỏ kiểu char*.

char* strmax(char* str1, char* str2)
{
if(strcmp(str1, str2) > 0)
{
return str1;
}
return str2;
}
Một trong những lợi thế của hàm trả về con trỏ là chúng ta có thể gọi lồng các hàm để viết mã gọn hơn. Chẳng hạn sau khi tìm chuỗi lớn nhất chúng ta có nhu cầu chuyển hết các ký tự trong chuỗi đó thành chữ hoa bằng cách gọi hàm strupr().

int main()
{
const int maxLen = 256;
char s1[maxLen], s2[maxLen];
printf("\nInput s1 = "); gets(s1);
printf("\nInput s2 = "); gets(s2);
printf("\nResult: %s", strupr(strmax(s1, s2))); // Gọi lồng hàm
getch();
return 0;
}
Bản thân hàm printf() cũng cho phép nhận vào thàm số con trỏ kiểu char* khi chúng ta dùng định dạng %s trong chuỗi định dạng in. Một điểm cần lưu ý thêm là 2 biến s1 và s2 được khai báo trong hàm main() dưới dạng 2 mảng ký tự, nhưng ý nghĩa sử dụng lại là các chuỗi ký tự. Khi truyền s1 và s2 đến hàm strmax() thì s1 tương đương &s1[0], còn s2 tương đương &s2[0]. Vì vậy cả 2 đối số s1, s2 là 2 địa chỉ bộ nhớ có kiểu là char*.
-Kiểu tham chiếu (reference) của C++ thực chất được cài đặt bằng kỹ thuật con trỏ, nhưng cú pháp lập trình lại tương tự như biến thông thường.Dưới đây là hàm refMinAbs() cài đặt tương tự như 2 hàm indexMinAbs() và pointerMinAbs() ở bài trước và chỉ khác ở 2 chỗ: giá trị trả về và chỉ thị return.

float& refMinAbs(float a[], int n)
{
int i = 1, idx = 0;
while(i < n)
{
if(fabs(a[i]) < fabs(a[idx]))
{
idx = i;
}
i++;
}
return a[idx];
}
- Trong hàm main() của chương trình chạy thử, việc gọi hàm với chỉ thị float& refMin = refMinAbs(B, n) nhằm để refMin tham chiếu đến phần tử tìm được. Lưu ý dấu & trong float& là thực sự cần thiết vì nếu ta thay bằng lời gọi hàm không có dấu & như là float refMin = refMinAbs(B, n) thì chương trình vẫn chạy nhưng biến refMin được gán bằng giá trị của phần tử tìm được chứ không tham chiếu(xem như đồng nhất) với phần tử tìm được. Trong hàm main() thì cả ba địa chỉ &B[id], pMin, &refMin đều cùng 1 giá trị địa chỉ bộ nhớ.

int main()
{
float B[] = {-9, 12.7F, 2.3F, 11, -10, -3.6F};
int n = sizeof(B)/sizeof(B[0]);
int id = indexMinAbs(B, n);
float* pMin = pointerMinAbs(B, n);
float& refMin = refMinAbs(B, n);
printf("Values: %f %f %f\n", B[id], *pMin, refMin);
printf("Memory Adresses: 0x%x 0x%x 0x%x", &B[id], pMin, &refMin);

getch();
return 0;
}
- 2 cách cài đặt hàm tìm phần tử có giá trị tuyệt đối nhỏ nhất trong mảng 1 chiều gồm n số thực kiểu float

int indexMinAbs(float a[], int n)
{
int i = 1, idx = 0;
while(i < n)
{
if(fabs(a[i]) < fabs(a[idx]))
{
idx = i;
}
i++;
}
return idx;
}

float *pointerMinAbs(float a[], int n)
{
int i = 1, idx = 0;
while(i < n)
{
if(fabs(a[i]) < fabs(a[idx]))
{
idx = i;
}
i++;
}
return &a[idx];
}
- Địa chỉ của phần tử tìm được là &a[idx] được trả về thay vì là chỉ số của mảng idx

int main()
{
float B[] = {-9, 12.7F, 2.3F, 11, -10, -3.6F};
int n = sizeof(B)/sizeof(B[0]);
int id = indexMinAbs(B, n);
float* pMin = pointerMinAbs(B, n);
printf("Values: %f %f\n", B[id], *pMin);
printf("Memory Adresses: 0x%x 0x%x", &B[id], pMin);

getch();
return 0;
}
- Chương trình chính minh họa cho việc gọi thử 2 hàm indexMinAbs() và pointerMinAbs(). Khi chạy chương trình ta sẽ thấy B[id] và *pMin in tra cùng giá trị, hơn nữa các địa chỉ bộ nhớ &B[id] và pMin cũng in ra cùng giá trị.

Thứ Bảy, 2 tháng 1, 2016

Đôi khi địa chỉ biến được truyền qua một chuỗi các hàm trong 1 quy trình xử lý nào đó. Chẳng hạn ta muốn viết hàm ajust() để sau khi gọi ajust(&a, &b) thì nếu cần thiết ta phải hoán chuyển giá trị của a và b sao cho |a| <= |b| Trường hợp này, ajust() hàm nhận tham số địa chỉ biến và trong quá trình xử lý lại tiếp tục truyền các địa chỉ biến cho hàm swap() khi gọi hàm.

void swap(float *x, float *y)
{
float u = *x;
*x = *y;
*y = u;
}
void adjust(float *x, float *y)
{
if(fabs(*x) > fabs(*y))
//swap(&(*x), &(*y));
swap(x, y);
}
int main()
{
float a = -1845.3F, b = 234.3F;
adjust(&a, &b);
printf("\nNew values of a and b now are a = %.2f, b = %.2f", a, b);

getch();
return 0;
}
Hàm ajust() nhận 2 tham số x và y là địa chỉ của 2 biến số thực sẽ được gửi đến, tức là *x và *y có thể xem như chính là 2 biến số thực đó. Khi |*x| > |*y| thì chúng ta cần gọi hàm swap() để hoán chuyển nội dung của 2 biến gửi đến. Dòng mã thực hiện điều này là swap(&(*x), &(*y)) với ý nghĩa là đưa địa chỉ của các biến *x và *y đến hàm swap(). Tuy nhiên nếu diễn dịch kỹ thì &(*x) chính là "địa chỉ của biến mà x đang lưu địa chỉ": cụm từ này cho thấy &(*x) = x.

Thứ Tư, 30 tháng 12, 2015

Một ví dụ thường thấy là hàm HoanVi() nhằm hoán chuyển nội dung của 2 biến a, b khi gửi địa chỉ của chúng đến khi gọi hàm với lời gọi là HoanVi(&a, &b).
- Sau khi gọi hàm, ta muốn a nhận giá trị của b, còn b nhận giá trị cũ của a, trong đó các tham số x và y là địa chỉ của các biến cần hoán chuyển nội dung, còn *x và *y chính là các biến mà địa chỉ của chúng lưu tương ứng trong x và y.
- Đối với lời gọi hàm thì địa chỉ của biến a được chuyến vào x (tức là lúc đó x = &a), còn địa chỉ của biến b được chuyển vào y(tương đương y = &b).
-> Do vậy *x nghĩa là *(&a) chính là a, còn *y chính là *(&b) hay là b
void HoanVi(int* x, int* y) // int* x = &a;  int* y = &b;
{
int temp = *x;
*x = *y;
*y = temp;
}

void main()
{
int a = 1;
int b = 2;
HoanVi(&a, &b);
cout << "a = " << a <<" ,b = " << b;
}
Một hàm có thể nhận tham số là biến con trỏ hay cũng có thể trả về địa chỉ vùng nhớ hay địa chỉ biến
void InHoa(char* pCh)
{
if(*pCh >= 'a' && *pCh <= 'z')
*pCh = (*pCh) - 32;
}
int main()
{
char ch;
printf("\nNhap 1 ky tu: ");
scanf("%c", &ch);
InHoa(&ch);
printf("\nKy tu sau khi in hoa: %c", ch);
Mỗi khi gọi hàm thì *pCh sẽ liên kết đồng nhất với biến mà được gửi địa chỉ đến hàm. Lời gọi hàm InHoa(&ch) truyền địa chỉ của biến ch (tức là &ch) đến hàm, lúc đó *pCh và ch là như nhau nên các dòng mã trong hàm InHoa làm thay đổi biến ch.
Địa chỉ của biến là một con số. Ta có thể tạo biến khác để lưu địa chỉ của biến này. Ở đây ta dùng Con trỏ. Giống như mọi biến khác, biến con trỏ muốn sử dụng cũng cần phải được khai báo. Con trỏ NULL là con trỏ không trỏ vào đâu cả. Khác với con trỏ chưa được khởi tạo.

int n;
int *p1 = &n; // khai báo và đặt địa chỉ của biến vào con trỏ
int *p2; // unreferenced local variable
int *p3 = NULL;
Khi mới khai báo, biến con trỏ được đặt ở địa chỉ nào đó (không biết trước). chứa giá trị không xác định trỏ đến vùng nhớ không biết trước. Đặt địa chỉ của biến vào con trỏ sử dụng (toán tử &). Con trỏ chứa một số nguyên chỉ địa chỉ. Để truy xuất đến ô nhớ mà con trỏ trỏ đến, sử dụng (toán tử *). Ví dụ: Trong C:

int a = 5, *pa = &a;
printf("\nGia tri bien pa: %d", pa);
printf("\nDia chi cua bien a: %d", &a);
// -> pa và &a đều chỉ địa chỉ của biến a

printf("\nGia tri vung nho pa tro den: %d", *pa);
printf("\nGia tri cua bien a: %d", a);
// -> *pa và a đều chỉ nội dung của biến a

printf("\nDia chi bien pa: %d", &pa);
// -> địa chỉ biến pa,

// có 2 cái địa chỉ
// 1 là địa chỉ của biến con trỏ pa
// 2 là địa chỉ mà con trỏ pa trỏ đến
// Toán tử * để lấy ra giá trị mà con trỏ đang trỏ đến
// Ko để toán tử gì cả thì lấy ra địa chỉ mà con trỏ đang trỏ đến
// Nếu để cả dấu & trước tên con trỏ thì ta đang lấy ra "địa chỉ của riêng" con trỏ.

Thứ Năm, 24 tháng 12, 2015

Chúng ta cùng tìm hiểu một cấu trúc dữ liệu cũng khá hữu ích là Danh sách liên kết vòng (Circular Linked List). Nó biểu diễn một cách tự nhiên các cấu trúc dạng tròn như các góc của đa giác, v.v... DSLK vòng có hai dạng thường thấy là dạng vòng đơnvà vòng kép.

Dạng vòng đơn thực chất là một danh sách liên kết đơn có phần tử cuối trỏ về phần tử đầu tiên. Nó cũng có nhược điểm là chỉ duyệt từ một chiều. Dạng vòng kép cũng là một danh sách liên kết kép có phần tử cuối trỏ về đầu và đầu trỏ ngược về cuối.

Với DSLK vòng ta cần biết một vài thao tác cơ bản đủ dùng và các thao tác này sẽ được minh họa bằng C++. Bài này chỉ nói về danh sách liên kết vòng kép và bạn cũng nên sử dụng vòng kép để việc code lại đơn giản hơn.

Tổ chức dữ liệu
Một danh sách gồm có các phần tử gọi là node, mỗi node gồm 1 biến chứa dữ liệu và một hoặc nhiều biến con trỏ để liên kết với các node khác. Dưới đây là khai báo cấu trúc node:

1
2
3
4
5
struct DoublyNode
{
   <datatype> info;
   DoublyNode* prev, *next;
};

Do cấu trúc này ở dạng vòng nên một danh sách ta chỉ cần chọn một phần tử đầu thôi.
1
2
3
4
struct DoublyList
{
   DoublyNode* head;
};


Các thao tác cơ bản
Tạo danh sách rỗng
Do đặc điểm của cách cài đặt hướng cấu trúc và dùng con trỏ trong C++ nên cần thiết phải tạo danh sách rỗng bằng cách gán NULL cho phần tử đầu.
1
2
3
4
void CreateList(DoublyList &list)
{
   list.head = NULL;
}

Đưa dữ liệu vào node
Đơn giản là đưa dữ liệu của bạn vào một node để có thể thêm vào danh sách.
1
2
3
4
5
6
7
8
9
10
DoublyNode* CreateNode(<datatype> data)
{
   DoublyNode* node = new DoublyNode;
   if (node)
   {
      node->info = data;
      node->next = node->prev = NULL;
   }
   return node;
}

Thêm node vào danh sách
Ở đây ta chỉ cần 2 trường hợp: thêm trước 1 node và thêm sau 1 node (mà thực ra cũng chỉ cần thêm trước 1 node là đủ dùng), nhưng mình cũng code thêm hàm thêm vào cuối (có thể hiểu như thêm trước phần tử đầu).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
void AddTail(DoublyList &list, DoublyNode* node)
{
   if (!list.head)
   {
      list.head = node;
      node->next = node->prev = list.head;
   }
   else
   {
      node->prev = list.head->prev;
      node->next = list.head;
      list.head->prev->next = node;
      list.head->prev = node;
   }
}
void AddBefore(DoublyList &list, DoublyNode* node, DoublyNode* before)
{
   if (!before)
   {
      list.head = node;
      node->next = node->prev = list.head;
   }
   else
   {
      node->prev = before->prev;
      node->next = before;
      before->prev->next = node;
      before->prev = node;
   }
}
void AddAfter(DoublyList &list, DoublyNode* node, DoublyNode* after)
{
   if (!after)
   {
      list.head = node;
      node->next = node->prev = list.head;
   }
   else
   {
      node->prev = after;
      node->next = after->next;
      after->next->prev = node;
      after->next = node;
   }
}

Duyệt danh sách
Duyệt là đến từng phần tử để thực hiện thao tác nào đó. Trong DSLK vòng phải có một điều kiện dừng nào đó để dừng duyệt (nếu không nó cứ đi lòng vòng). Cái này mình không nói cụ thể ở đây mà tùy trường hợp cụ thể điều kiện dừng sẽ khác nhau.
1
2
3
4
5
6
7
void Browse(DoublyList list)
{
   for (DoublyNode* i = list.head; <condition>; i=i->next)
   {
      ///
   }
}

Xóa phần tử và danh sách
Ở đây ta chỉ quan tâm việc xóa một phần tử cụ thể và xóa danh sách.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void RemoveKey(DoublyList &list, int key)
{
   DoublyNode *i = list.head;
   do
   {
      if (i->info == key)
      {
         i->prev = i->next;
         i->next = i->prev;
         if (i == list.head)
            list.head = NULL;
         delete i;
         break;
      }
      i = i->next;
   } while (i != list.head);
}
void RemoveList(DoublyList &list)
{
   DoublyNode *i = NULL;
   do
   {
      i = list.head;
      list.head->prev->next = list.head->next;
      list.head->next->prev = list.head->next;
      list.head = list.head->next;
      delete i;
      if (!i) list.head = NULL;
   } while (!list.head)
}

Các thao tác cơ bản này đã đủ dùng với danh sách liên kết vòng. Tuy nhiên các cài đặt trên chỉ là cài đặt mẫu, bạn cần phải cài đặt linh động hơn trong một số thao tác để giải quyết bài toán nhanh hơn.

Categories

Sample Text

Được tạo bởi Blogger.

Must Read

Biểu mẫu liên hệ

Tên

Email *

Thông báo *

Popular Posts

Video

Popular Posts

Our Facebook Page