So sánh con trỏ int và array int năm 2024

Trong bài học này, chúng ta sẽ cùng nhau tìm hiểu một số điểm cần lưu ý khi sử dụng con trỏ trỏ đến mảng kí tự (C-style string).

C-style string symbolic constants

C-style string là một trường hợp đặc biệt của mảng một chiều, được ngôn ngữ C++ hổ trợ một số đặc điểm nhằm giúp lập trình viên thao tác với C-style string một cách thuận tiện hơn.

Ngoài cách khởi tạo mảng một chiều thông thường, C-style string còn có thể khởi tạo bằng một hằng chuỗi kí tự như sau:

char my_name[] = "Le Tran Dat";

Chuỗi kí tự "Le Tran Dat" được xem như là một chuỗi hằng kí tự, nó có địa chỉ cụ thể trên bộ nhớ ảo, nó được lưu trên bộ nhớ ảo, nhưng không có tên biến để truy xuất đến địa chỉ của chuỗi hằng kí tự này. Nhưng sau khi sử dụng chuỗi hằng kí tự "Le Tran Dat" để khởi tạo cho mảng

char my_name[] = "Le Tran Dat";
my_name[1] = 'E'; //=> "LE Tran Dat"

2, mảng

char my_name[] = "Le Tran Dat";
my_name[1] = 'E'; //=> "LE Tran Dat"

2 không được khai báo là kiểu chuỗi hằng kí tự (

char my_name[] = "Le Tran Dat";
my_name[1] = 'E'; //=> "LE Tran Dat"

  1. nên các kí tự trong mảng

char my_name[] = "Le Tran Dat";
my_name[1] = 'E'; //=> "LE Tran Dat"

2 hoàn toàn có thể bị thay đổi.

Ví dụ:

char my_name[] = "Le Tran Dat";
my_name[1] = 'E'; //=> "LE Tran Dat"

Điều này chứng tỏ mảng

char my_name[] = "Le Tran Dat";
my_name[1] = 'E'; //=> "LE Tran Dat"

2 được cấp phát bộ nhớ tại địa chỉ khác chuỗi hằng kí tự "Le Tran Dat", việc khởi tạo mảng kí tự bằng một chuỗi hằng kí tự chỉ đơn giản là copy từng kí tự của chuỗi "Le Tran Dat" và đưa vào mảng.

Do đó, con trỏ kiểu char (

char my_name[] = "Le Tran Dat";
my_name[1] = 'E'; //=> "LE Tran Dat"

  1. trỏ đến mảng

char my_name[] = "Le Tran Dat";
my_name[1] = 'E'; //=> "LE Tran Dat"

2 và trỏ đến vùng nhớ của chuỗi hằng kí tự "Le Tran Dat" là 2 trường hợp khác nhau.

Mình lấy ví dụ một con trỏ kiểu char (

char my_name[] = "Le Tran Dat";
my_name[1] = 'E'; //=> "LE Tran Dat"

  1. trỏ đến mảng

char my_name[] = "Le Tran Dat";
my_name[1] = 'E'; //=> "LE Tran Dat"

2:

char my_name[] = "Le Tran Dat";
char *p_name = my_name;
p_name[1] = 'E';
cout << my_name << endl;

Kết quả in ra màn hình là:

LE Tran Dat

Như vậy, con trỏ

char my_name[] = "Le Tran Dat";
char *p_name = my_name;
p_name[1] = 'E';
cout << my_name << endl;

1 sau khi trỏ đến mảng

char my_name[] = "Le Tran Dat";
my_name[1] = 'E'; //=> "LE Tran Dat"

2 thì có thể thay đổi giá trị bên trong vùng nhớ mà mảng

char my_name[] = "Le Tran Dat";
my_name[1] = 'E'; //=> "LE Tran Dat"

2 đang nắm giữ, vì vùng nhớ này không phải là vùng nhớ hằng.

Trường hợp tiếp theo, mình sẽ cho một con trỏ kiểu char (char *) trỏ trực tiếp đến chuỗi hằng kí tự:

char *p_name = "Le Tran Dat";
p_name[1] = 'E';
cout << p_name << endl;

Khi nhấn F5 để Debug đoạn chương trình này, Visual studio 2015 đưa ra thông báo xảy ra xung đột vùng nhớ.

So sánh con trỏ int và array int năm 2024

Nguyên nhân là do vùng nhớ lưu trữ chuỗi kí tự "Le Tran Dat" là vùng nhớ hằng, giá trị bên trong vùng nhớ này không thể thay đổi, trong khi đó lệnh

char my_name[] = "Le Tran Dat";
char *p_name = my_name;
p_name[1] = 'E';
cout << my_name << endl;

4 cố gắng thay đổi giá trị bên trong vùng nhớ hằng.

Đến đây có thể có một số bạn thắc mắc về địa chỉ của chuỗi hằng kí tự "Le Tran Dat" mà mình sử dụng. Mặc dù chuỗi hằng kí tự không được khai báo như một biến thông thường, nhưng nó được tạo ra và có địa chỉ cụ thể trên vùng nhớ ảo. Chúng ta truy xuất địa chỉ của chuỗi hằng kí tự bằng chính nội dung của chuỗi đó:

int main()
{
  cout << &("Le Tran Dat") << endl;
  cout << &("LE TRAN DAT") << endl;
  system("pause");
  return 0;
}

Kết quả của đoạn chương trình này trên máy tính của mình là:

00EF8CC8
00EF8B30

Như vậy, mỗi chuỗi hằng kí tự có nội dung khác nhau sẽ có một địa chỉ khác nhau. Chúng ta có thể sử dụng nội dung của chuỗi hằng kí tự này như mảng một chiều, nhưng không thể thay đổi nội dung của nó.

for (int i = 0; i < strlen("Le Tran Dat"); i++)
{
  cout << "Le Tran Dat"[i];
}
cout << endl;
"Le Tran Dat"[1] = 'E'; //this line will make an error

std::cout and char pointers

Với các mảng một chiều có kiểu dữ liệu khác, để xem được nội dung bên trong mảng, chúng ta cần sử dụng vòng lặp để duyệt từng phần tử bên trong mảng. Ví dụ:

float arr[] = { 2.5, 1.6, 0.2, 3.14 };
int size = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < size; i++)
{
  cout << arr[i] << " ";
}

Đối với mảng kí tự (C-style string) chúng ta có thể in toàn bộ nội dung của mảng bằng cách sử dụng đối tượng cout như sau:

char str[] = "This is an example string";
cout << str << endl;

Đối với các kiểu dữ liệu không phải kiểu con trỏ char (

char my_name[] = "Le Tran Dat";
my_name[1] = 'E'; //=> "LE Tran Dat"

7), đối tượng cout chỉ in ra địa chỉ của mảng (vì

char my_name[] = "Le Tran Dat";
char *p_name = my_name;
p_name[1] = 'E';
cout << my_name << endl;

6 tương đương với

char my_name[] = "Le Tran Dat";
char *p_name = my_name;
p_name[1] = 'E';
cout << my_name << endl;

7), nhưng với kiểu con trỏ char (

char my_name[] = "Le Tran Dat";
my_name[1] = 'E'; //=> "LE Tran Dat"

7), đối tượng cout có cách định nghĩa khác.

Thực ra đối tượng cout chỉ hổ trợ cho kiểu con trỏ char (

char my_name[] = "Le Tran Dat";
my_name[1] = 'E'; //=> "LE Tran Dat"

7), nhưng vì sử dụng tên mảng

LE Tran Dat

0 tương đương với

LE Tran Dat

1. Như các bạn biết, toán tử address-of trả về kiểu con trỏ, nên

LE Tran Dat

0 truyền vào đối tượng cout được xem là con trỏ kiểu char (

char my_name[] = "Le Tran Dat";
my_name[1] = 'E'; //=> "LE Tran Dat"

7).

char my_name[] = "Le Tran Dat";
my_name[1] = 'E'; //=> "LE Tran Dat"

0

Do đó, đoạn chương trình này in ra 2 dòng có nội dung giống nhau.

Điều này dẫn để một hệ quả, chúng ta không thể in ra địa chỉ của một biến kiểu kí tự (

LE Tran Dat

4).

char my_name[] = "Le Tran Dat";
my_name[1] = 'E'; //=> "LE Tran Dat"

1

Trên máy tính của mình, kết quả cho ra màn hình là:

LE Tran Dat

5 trả về dữ liệu kiểu (

char my_name[] = "Le Tran Dat";
my_name[1] = 'E'; //=> "LE Tran Dat"

  1. nên đối tượng cout xem nó như là C-style string nên in ra kí tự A và tiếp tục cho đến khi gặp giá trị '\0'.