You are on page 1of 46

1.

Con trỏ - Pointer


2. Chuỗi ký tự - String
Nội dung

• Khái niệm con trỏ & Khai báo


• Thao tác: Các toán tử “&”, “*”, “=”, “+”
• Nhắc lại về truyền tham số địa chỉ
• Con trỏ và mảng
• Cấp phát vùng nhớ động
Kiến trúc máy tính
• Bộ nhớ máy tính
– Bộ nhớ RAM chứa rất nhiều ô nhớ, mỗi ô nhớ có
kích thước 1 byte.
– RAM dùng để chứa một phần hệ điều hành, các
lệnh chương trình, các dữ liệu…
– Mỗi ô nhớ có địa chỉ duy nhất và địa chỉ này được
đánh số từ 0 trở đi.
– Ví dụ
• RAM 512MB được đánh địa chỉ từ 0 đến 229 – 1
• RAM 2GB được đánh địa chỉ từ 0 đến 231 – 1
Cấu trúc một CT C/C++ trong bộ nhớ
• Toàn bộ tập tin chương trình sẽ được nạp
vào bộ nhớ tại vùng nhớ còn trống, gồm 4
phần:
STACK Lưu đối tượng cục bộ
Last-In First-Out Khi thực hiện hàm

Vùng nhớ trống

Vùng cấp phát động


HEAP (RAM trống và bộ nhớ
Đối tượng toàn cục Vùng cấpảo)
phát tĩnh
& tĩnh (kích thước cố định)
Gồm các lệnh và hằng
Mã chương trình
(kích thước cố định)
Nhắc lại về biến
• Chúng ta đã biết các biến đều có kích thước, kiểu dữ
liệu xác định (biến tĩnh) chính là các ô nhớ mà chúng
ta có thể truy xuất dưới các tên.
• Các biến này được lưu trữ tại những chỗ cụ thể trong
bộ nhớ, tồn tại trong suốt thời gian thực thi chương
trình
• Hạn chế của biến tĩnh:
– Cấp phát ô nhớ dư: Gây lãnh phí
– Cấp phát ô nhớ thiếu: Lỗi chương trình
Con trỏ
Ngôn ngữ C cung cấp một kiểu biến gọi là con trỏ với những
đặc điểm sau:
• Chỉ phát sinh trong quá trình thực hiện chương trình
• Khi chạy chương trình, kích thước của biến, vùng nhớ và địa
chỉ vùng nhớ được cấp phát cho biến có thể thay đổi
• Sau khi thực hiện xong có thể giải phóng để tiết kiệm chỗ
trong bộ nhớ
• Kích thước của biến con trỏ không phụ thuộc kiểu dữ liệu, nó
luôn có kích thước cố định là 4 byte (tùy thuộc HĐH)
Con trỏ – Một số lý do nên sử dụng
• Con trỏ là kiểu dữ liệu lưu trữ địa chỉ của
các vùng dữ liệu trong bộ nhớ máy tính
• Kiểu con trỏ cho phép:
– Truyền tham số kiểu địa chỉ
– Biểu diễn các kiểu, cấu trúc dữ liệu động
– Lưu trữ dữ liệu trong vùng nhớ heap
• Ví dụ con trỏ đã được sử dụng trong hàm
scanf(…)
Con trỏ – Khai báo trong C
• Con trỏ đơn giản chỉ là địa chỉ của một vị trí bộ nhớ và cung
cấp cách gián tiếp để truy xuất dữ liệu trong bộ nhớ

Tên con trỏ

type *pointer_name;

• Kiểu dữ liệu được Ý nghĩa: Khai báo một biến có tên là


trỏ tới pointer_name dùng để chứa địa chỉ
• Không phải là kiểu của các biến có kiểu là type
của con trỏ
Con trỏ – Khai báo trong C
• Giá trị của một biến con trỏ là địa chỉ mà nó
trỏ đến
• Nếu chưa muốn khai báo kiểu dữ liệu mà con
trỏ chỉ đến, ta có thể khai báo như sau:

void *<tên_con_trỏ>;
Ví dụ
int *number;
char *character;
float *greatnumber;
• Đây là ba khai báo của con trỏ
• Mỗi biến trỏ tới một kiểu dữ liệu khác nhau
• Chúng đều chiếm một lượng bộ nhớ như nhau
(kích thước của một biến con trỏ tùy thuộc vào hệ điều
hành)
• Dữ liệu mà chúng trỏ tới không chiếm lượng bộ nhớ
như nhau, một kiểu int, một kiểu char và cái còn lại
kiểu float
Con trỏ – Khai báo trong C
int *pi;

long int *p;

float *pf;

char c, d, *pc; /* c và d kiểu char


pc là con trỏ đến char */

double *pd, e, f; /* pd là con trỏ đến double


e and f are double */

char *start, *end;


Con trỏ- Toán tử lấy địa chỉ (&)
• Khai báo một biến thì nó phải được lưu trữ trong
một vị trí cụ thể trong bộ nhớ
• Chúng ta không quyết định nơi nào biến đó được đặt
• Vậy:
Hệ điều hành
– Ai đặt vị trí của biến?
– Chúng ta có thể biết biến đó được lưu trữ ở đâu ?

Điều này có thể được thực hiện bằng cách


đặt trước tên biến một dấu và (&), có
nghĩa là "địa chỉ của".
Con trỏ - Toán tử lấy địa chỉ “&”
• “&”: toán tử lấy địa chỉ của 1 biến
• Địa chỉ của tất cả các biến trong chương trình
đều đã được chỉ định từ khi khai báo
char g = 'z';
p c
int main() 0x1132 'a'
0x1132
{
char c = 'a'; p g
char *p;
0x91A2 'z'
p = &c; 0x91A2
p = &g;
return 0;
}
Con trỏ - Toán tử “*”
• Có thể truy xuất trực tiếp đến giá trị được lưu trữ trong biến
được trỏ bởi con trỏ bằng cách đặt trước tên biến con trỏ một
dấu sao (*) - ở đây được dịch là "giá trị được trỏ bởi"

*<tên_con_trỏ>;

• Vì vậy, nếu chúng ta viết: int beth = *ted;


• C thể đọc nó là: "beth bằng giá trị được trỏ bởi ted”
Con trỏ - Toán tử “*”

#include <stdio.h> p c
char g = 'z'; 0x1132 'a'
int main() a 0x1132
{ z
char c = 'a'; p g
char *p; 0x91A2 'z'
p = &c; 0x91A2
printf("%c\n", *p); xuất giá trị do p đang
p = &g; quản lý
printf("%c\n", *p);
return 0;
}
Phân biệt toán tử & và *
• Toán tử lấy địa chỉ (&)
Nó được dùng như là một tiền tố của biến và có thể
được dịch là "địa chỉ của“
&variable1 có thể được đọc là "địa chỉ của variable1“

• Toán tử (*)
Nó chỉ ra rằng cái cần được tính toán là nội dung được
trỏ bởi biểu thức được coi như là một địa chỉ. Được
dịch là "giá trị được trỏ bởi"..
*mypointer được đọc là "giá trị được trỏ bởi mypointer"
Con trỏ - Truyền tham số địa chỉ
#include <stdio.h>
void change(int *v);

int main()
{
int var = 5;
change(&var);
printf("main: var = %i\n", var);
return 0;
}

void change(int *v)


{
(*v) *= 100;
printf("change: *v = %i\n", (*v));
}
Con trỏ NULL
• Giá trị đặc biệt để chỉ rằng con trỏ không quản
lý vùng nào. Giá trị này thường được dùng để
chỉ một con trỏ không hợp lệ.
#include <stdio.h>
int main()
{
int i = 13;
short *p = NULL;
if (p == NULL)
printf(“Con trỏ không hợp lệ!\n");
else
printf(“Giá trị : %hi\n", *p);
return 0;
}
Con trỏ - Toán tử gán “=”
• Có sự khác biệt rất quan trọng khi thực hiện
các phép gán: p i
int i = 10, j = 0x15A0 10 14
14; 0x15A0
int *p = &i; q j
int *q = &j; 0x15A4 14
0x15A4
và: *p = *q;
int i = 10, j = p 0x15A4 i
14; 0x15A0 10
int *p = &i; 0x15A0
q j
int *q = &j;
0x15A4 14
0x15A4
p = q;
Luyện tập – Điền vào ô trống

int main(void) i
{ 0x2100
int i = 10, j = 14,
k; j
int *p = &i; 0x2104
int *q = &j; k
0x1208
*p += 1;
p = &k; p
*p = *q; 0x120B
p = q;
q
*p = *q;
0x1210
return 0;
}
Con trỏ và Mảng

• Biến kiểu mảng là địa chỉ tĩnh của một vùng


nhớ, được xác định khi khai báo, không thay
đổi trong suốt chu kỳ sống.
• Biến con trỏ là địa chỉ động của một vùng nhớ,
được xác#include
định qua phép gán địa chỉ khi
<stdio.h>
chương int
trình main()
thực thi.
{
int a[10] = {1, 3, 4, 2, 0};
int *p;
p = a; //a = p: sai
printf(“0x%04X %i 0x%04X %i\n“,
a, a[0], p, *p);
return 0;
}
Con trỏ - Toán tử “+” với số nguyên
#include <stdio.h>
int main()
{
short a[10] = {1, 3, 5, 2, 0};
short *p = a;
printf(“0x%04X %i 0x%04X %i\n“, );
a, a[0], p, *p);
p ++;
printf(“0x%04X %i 0x%04X %i\n“, );
a, a[0], p, *p);
(*p) ++;
printf(“0x%04X %i 0x%04X %i\n“, );
a, a[0], p, *p);
return 0;
}
Tăng (giảm) 1 ngôi trên biến con trỏ
• Giả
Nguyên nhân:ta có 3 con trỏ sau:
sử chúng
Khi *mychar;
char cộng thêm 1 vào một con trỏ thì nó sẽ trỏ tới
phần *myshort;
short tử tiếp theo có cùng kiểu mà nó đã được
định*mylong;
long nghĩa, vì vậy kích thước tính bằng byte
của lần
Chúng kiểulượt
dữ liệu nó ôtrỏnhớ
trỏ tới tới 1000,
sẽ được cộng
2000 và thêm
3000.
vào biến con trỏ
• Nếu chúng ta viết
mychar++; 1000 1001
myshort++; 2000 2002
mylong++;
3000 3004
Tăng (giảm) 1 ngôi trên biến con trỏ

Viết cách khác:


mychar = mychar + 1;
myshort = myshort + 1;
mylong = mylong + 1;
Con trỏ - Luyện tập
#include <stdio.h>
int main() 2 2
{ 3 1
int a[10] = {2, 3, 5, 1, 4, 7, 0}; 1 9
int *p = a; 1 3
printf(“%i %i\n“, a[0], *p);
p ++;
printf(“%i %i\n“, *p, p[2]);
p ++; a[2] = 9;
printf(“%i %i\n“, p[1], *p);
p -= 2;
printf(“%i %i\n”, p[3], p[1]);
return 0;
}
Con trỏ - Cấp phát vùng nhớ động (1)
• Bộ nhớ tĩnh (stack)
– Vùng nhớ được sử dụng để lưu trữ các biến
toàn cục và lời gọi hàm
• Tất cả những phần bộ nhớ chúng ta có thể sử dụng là
các biến các mảng và các đối tượng khác mà chúng ta
đã khai báo. Kích cỡ của chúng là cố định và không
thể thay đổi trong thời gian chương trình chạy
Con trỏ - Cấp phát vùng nhớ động (2)
• Bộ nhớ động (heap)
– Bộ nhớ mà kích cỡ của nó chỉ có thể được xác định khi chương trình
chạy
• Có thể chỉ định vùng bộ nhớ động cho 1 con trỏ quản lý bằng các
lệnh hàm malloc, calloc hoặc toán tử new của C++
• Vùng nhớ do lập trình viên chỉ định phải được giải phóng bằng lệnh
free (malloc, calloc) hoặc toán tử delete (new) trong C++
#include <stdio.h>
#include <malloc.h>
int main()
{
int *p = (*int)malloc(10);
p[0] = 1;
p[3] = -7;
free(p);
return 0;
}
Một số lưu ý
• Một số lưu ý
– Con trỏ là khái niệm quan trọng và khó nhất trong
C/C++. Mức độ thành thạo C/C++ được đánh giá
qua mức độ sử dụng con trỏ.
– Nắm rõ quy tắc sau, ví dụ int a, *pa = &a;
• *pa và a đều chỉ nội dung của biến a.
• pa và &a đều chỉ địa chỉ của biến a.
– Không nên sử dụng con trỏ khi chưa được khởi
tạo. Kết quả sẽ không lường trước được.
Tài liệu tham khảo
• [PVA] Chương 6
• [K&R] Chapter 5
Chuỗi ký tự - String
Chuỗi ký tự – Strings

• Một số qui tắc


• Nhập / xuất
• Con trỏ và chuỗi ký tự
• Một số hàm thư viện
Chuỗi ký tự - Một số qui tắc
• Chuỗi ký tự là mảng một chiều có mỗi
thành phần là một số nguyên được kết
thúc bởi số 0 (số không).
• Ký tự kết thúc (\0) ở cuối chuỗi ký tự
thường được gọi là ký tự null (không
giống con trỏ NULL).
• Được khai báo và truyền tham số như
mảng một chiều.

char s[100];
unsigned char s1[1000];
Chuỗi ký tự - Ví dụ
char first_name[5] = { 'J', 'o', 'h', 'n', '\0' };

char last_name[6] = "Minor";

char other[] = “Tony Blurt";

char characters[7] = "No null";

first_name 'J' 'o' 'h' 'n' ‘\0’

last_name 'M' 'i' 'n' 'o' 'r' ‘\0’

othe 'T' 'o' ‘n’ 'y' 32 'B' 'l' 'u' 'r' 't' ‘\0’
r
characters 'N' 'o' 32 'n' 'u' 'l' 'l' ‘\0’
Chuỗi ký tự - Nhập / xuất
• Có thể nhập / xuất chuỗi ký tự s bằng cách
nhập từng ký tự của s
• Hoặc sử dụng các hàm scanf và printf
với ký tự định dạng “%s”
char other[] = "Tony Blurt";
printf("%s\n", other);

• Nhập chuỗi có khoảng trắng dùng hàm


fgets
char name[100];
printf("Nhap mot chuoi ky tu s: ");
fgets(name, 100, stdin);
Lưu ý: kết thúc chuỗi
#include <stdio.h>

int main()
{ "Blurt" sẽ không
char other[] = "Tony Blurt"; được in ra

printf("%s\n", other);

other[4] = '\0';

printf("%s\n", other); Tony Blurt


Tony
return 0;
}

othe 'T' 'o' ‘n’ 'y' 32 'B' 'l' 'u' 'r' 't' 0
r
Chuỗi ký tự – Một số hàm thư viện

• #include<string.h>
• Lấy độ dài chuỗi
l = strlen(s);
• Đổi toàn bộ các ký tự của chuỗi thành IN
HOA
strupr(s);
• Đổi toàn bộ các ký tự của chuỗi thành in
thường
strlwr(s);
Chuỗi ký tự – Một số hàm thư viện
• So sánh chuỗi: so sánh theo thứ tự từ điển

Phân biệt IN HOA – in thường:


strcmp(s1,s2);

Không phân biệt IN HOA – in thường:


stricmp(s1, s2);
Chuỗi ký tự – ví dụ strcmp
#include <stdio.h>

int main()
Minor < Tony
{
char s1[] = "Minor";
char s2[] = "Tony";
int cmp = strcmp(s1, s2);
if (cmp < 0)
printf("%s < %s", s1, s2);
else
if (cmp == 0)
printf("%s = %s", s1, s2);
else
printf("%s > %s", s1, s2);
return 0;
}
Chuỗi ký tự – ví dụ strcmp
Nguyên tắc so sánh
Duyệt lần lượt từng ký tự của 2 chuỗi. So sánh mã ACSII của 2
ký tự đó, mã ký tự nào lớn hơn tức là chuỗi lớn hơn và ngừng so
sánh. Nếu một chuỗi nào hết ký tự để so sanh trước thì chuỗi đó
bé hơn.
Chuỗi ký tự – Một số hàm thư viện
• Gán nội dung chuỗi:
o Chép toàn bộ chuỗi s2 sang chuỗi s1:
strcpy(s1,s2);
o Chép tối đa n ký tự từ s2 sang s1:
strncpy(s1,s2,n);
• Cộng chuỗi: ghép chuỗi s2 vào chuỗi s1
strcat(s1,s2);
• Tạo chuỗi mới từ chuỗi đã có:
strdup(s);
• Tìm kiếm kí tự trong chuỗi:
strchar(s,chr);
• Tìm kiếm chuỗi trong chuỗi:
strstr(s1,s2);
• Đảo ngược trật tự một chuỗi:
strrev(s);
Chuỗi ký tự – ví dụ strcpy
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main() Tony Blurt
{ To123Blurt
char s[] = "Tony Blurt"; Blurt
char s2[100], *s3;

strcpy(s2, s);
printf("%s\n", s2);
strncpy(s2 + 2, "12345", 3);
printf("%s\n", s2);
s3 = strdup(s + 5);
printf("%s\n", s3);
free(s3);
return 0;
}
Chuỗi ký tự – ví dụ strcpy
Hàm strcpy(char* s1, char* s2) copy nội dung vùng nhớ s2
vào vùng nhớ s1. Vùng nhớ s1 phải được cấp phát tĩnh hoặc
cấp phát động trước đó (malloc( ),. Kích thước vùng nhớ s1
phải đủ để chứa chuỗi s2. Nếu không đủ vùng nhớ, gây ra
buffer overrun.

Hàm strdup(const char* s) tạo ra vùng nhớ mới (gọi hàm


malloc( ) để cấp phát vùng nhớ) để lưu chuỗi s. Sau đó, trả về
con trỏ trỏ tới vùng nhớ được cấp phát. Nếu cấp phát fail, hàm
trả về NULL. Chú ý: phải free vùng nhớ trả về bởi hàm strdup.
Chuỗi ký tự – ví dụ tìm kiếm
#include <stdio.h>

int main()
{
char s[]= "Thu tim kiem chuoi";
char *p;

p = strchr(s, 'm');
printf("%s\n", p);
p = strstr(s, "em");
printf("%s\n", p); m kiem chuoi
return 0; em chuoi
}
Tổng kết

• Khai báo
• Nhập / xuất
• Con trỏ và chuỗi ký tự
• Một số hàm thư viện
• Chèn / loại bỏ một đoạn con
Tài liệu tham khảo

• [K&R] Chapter 7

You might also like