Đây là một lời giải thích chi tiết mà tôi hy vọng sẽ hữu ích. Hãy bắt đầu với chương trình của bạn, vì nó đơn giản nhất để giải thích.
int main()
{
const char *p = "Hello";
while(*p++)
printf("%c",*p);
return 0;
}
Tuyên bố đầu tiên:
const char* p = "Hello";
tuyên bố p
như một con trỏ tới char
. Khi chúng ta nói "con trỏ tới một char
", điều đó có nghĩa là gì? Nó có nghĩa là giá trị của p
là địa chỉ của a char
; p
cho chúng ta biết nơi nào trong bộ nhớ có một khoảng trống được đặt sang một bên để giữ a char
.
Câu lệnh cũng khởi tạo p
để trỏ đến ký tự đầu tiên trong chuỗi ký tự "Hello"
. Vì lợi ích của bài tập này, điều quan trọng là phải hiểu p
là không chỉ toàn bộ chuỗi, mà chỉ vào ký tự đầu tiên , 'H'
. Rốt cuộc, p
là một con trỏ đến một char
, không phải toàn bộ chuỗi. Giá trị của p
là địa chỉ của 'H'
trong "Hello"
.
Sau đó, bạn thiết lập một vòng lặp:
while (*p++)
Điều kiện vòng lặp *p++
có nghĩa là gì? Có ba điều đang làm việc ở đây khiến điều này trở nên khó hiểu (ít nhất là cho đến khi sự quen thuộc bắt đầu):
- Ưu tiên của hai toán tử, postfix
++
và indirection*
- Giá trị của biểu thức tăng hậu tố
- Tác dụng phụ của biểu thức tăng hậu tố
1. Ưu tiên . Nhìn lướt qua bảng ưu tiên cho các toán tử sẽ cho bạn biết rằng gia tăng hậu tố có mức ưu tiên cao hơn (16) so với quy định / quy định (15). Điều này có nghĩa là biểu thức phức tạp *p++
sẽ được nhóm lại thành : *(p++)
. Có nghĩa là, *
phần sẽ được áp dụng cho giá trị của p++
phần. Vì vậy, hãy tham p++
gia đầu tiên.
2. Giá trị biểu thức Postfix . Giá trị của p++
là giá trị p
trước khi tăng . Nếu bạn có:
int i = 7;
printf ("%d\n", i++);
printf ("%d\n", i);
đầu ra sẽ là:
7
8
bởi vì i++
đánh giá i
trước khi tăng. Tương tự như vậy p++
sẽ đánh giá giá trị hiện tại của p
. Như chúng ta biết, giá trị hiện tại p
là địa chỉ của 'H'
.
Vì vậy, bây giờ p++
một phần của *p++
đã được đánh giá; đó là giá trị hiện tại của p
. Sau đó, *
một phần xảy ra. *(current value of p)
có nghĩa là: truy cập giá trị tại địa chỉ được tổ chức bởi p
. Chúng tôi biết rằng giá trị tại địa chỉ đó là 'H'
. Vì vậy, biểu thức *p++
đánh giá để 'H'
.
Bây giờ hãy chờ một phút, bạn đang nói. Nếu *p++
đánh giá 'H'
, tại sao không 'H'
in trong đoạn mã trên? Đó là nơi tác dụng phụ đến.
3. Tác dụng phụ biểu hiện Postfix . Postfix ++
có giá trị của toán hạng hiện tại, nhưng nó có tác dụng phụ là tăng toán hạng đó. Huh? Hãy xem int
mã đó một lần nữa:
int i = 7;
printf ("%d\n", i++);
printf ("%d\n", i);
Như đã lưu ý trước đó, đầu ra sẽ là:
7
8
Khi i++
được đánh giá trong lần đầu tiên printf()
, nó ước tính là 7. Nhưng tiêu chuẩn C đảm bảo rằng tại một thời điểm nào đó trước khi lần thứ hai printf()
bắt đầu thực thi, tác dụng phụ của ++
toán tử sẽ xảy ra. Điều đó có nghĩa là, trước khi điều thứ hai printf()
xảy ra, i
sẽ được tăng lên do kết quả của ++
toán tử trong lần đầu tiên printf()
. Nhân tiện, đây là một trong số ít đảm bảo tiêu chuẩn đưa ra về thời gian của các tác dụng phụ.
Trong mã của bạn, sau đó, khi biểu thức *p++
được ước tính, nó sẽ ước lượng 'H'
. Nhưng đến lúc bạn nhận được điều này:
printf ("%c", *p)
tác dụng phụ đáng tiếc đó đã xảy ra. p
đã được tăng lên. Ái chà! Nó không còn chỉ đến 'H'
, mà là một nhân vật đã qua 'H'
: 'e'
nói cách khác, nói cách khác. Điều đó giải thích đầu ra cockneyfied của bạn:
ello
Do đó, điệp khúc của những gợi ý hữu ích (và chính xác) trong các câu trả lời khác: để in Phát âm đã nhận "Hello"
và không phải là đối tác của nó, bạn cần một cái gì đó như
while (*p)
printf ("%c", *p++);
Quá nhiều cho nó. Phần còn lại thì sao? Bạn hỏi về ý nghĩa của những điều này:
*ptr++
*++ptr
++*ptr
Chúng ta chỉ nói về cái đầu tiên, vì vậy hãy nhìn vào cái thứ hai : *++ptr
.
Chúng tôi đã thấy trong lời giải thích trước đó của chúng tôi rằng sự gia tăng hậu tố p++
có một ưu tiên nhất định , một giá trị và tác dụng phụ . Gia số tiền tố ++p
có tác dụng phụ tương tự như đối tác hậu tố của nó: nó tăng toán hạng của nó lên 1. Tuy nhiên, nó có một ưu tiên khác và một giá trị khác .
Gia tăng tiền tố có mức độ ưu tiên thấp hơn tiền tố; nó có quyền ưu tiên 15. Nói cách khác, nó có quyền ưu tiên giống như toán tử dereference / indirection *
. Trong một biểu thức như
*++ptr
vấn đề không phải là ưu tiên: hai toán tử giống hệt nhau trong quyền ưu tiên. Vì vậy, tính kết hợp bắt đầu. Gia tăng tiền tố và toán tử gián tiếp có tính kết hợp phải trái. Do tính kết hợp đó, toán hạng ptr
sẽ được nhóm với toán tử ngoài cùng bên phải ++
trước toán tử nhiều hơn bên trái , *
. Nói cách khác, biểu thức sẽ được nhóm lại *(++ptr)
. Vì vậy, như với *ptr++
nhưng vì một lý do khác, ở đây cũng là *
phần sẽ được áp dụng cho giá trị của ++ptr
phần.
Vậy giá trị đó là gì? Giá trị của biểu thức tăng tiền tố là giá trị của toán hạng sau khi tăng . Điều này làm cho nó trở thành một con thú rất khác với toán tử gia tăng hậu tố. Hãy nói rằng bạn có:
int i = 7;
printf ("%d\n", ++i);
printf ("%d\n", i);
Đầu ra sẽ là:
8
8
... khác với những gì chúng ta đã thấy với toán tử postfix. Tương tự, nếu bạn có:
const char* p = "Hello";
printf ("%c ", *p); // note space in format string
printf ("%c ", *++p); // value of ++p is p after the increment
printf ("%c ", *p++); // value of p++ is p before the increment
printf ("%c ", *p); // value of p has been incremented as a side effect of p++
đầu ra sẽ là:
H e e l // good dog
Bạn có thấy tại sao không?
Bây giờ chúng ta đến biểu thức thứ ba mà bạn đã hỏi về ++*ptr
. Đó thực sự là khó khăn nhất trong số rất nhiều. Cả hai toán tử đều có cùng mức độ ưu tiên và tính kết hợp phải trái. Điều này có nghĩa là biểu thức sẽ được nhóm lại ++(*ptr)
. Phần ++
sẽ được áp dụng cho giá trị của *ptr
phần.
Vì vậy, nếu chúng ta có:
char q[] = "Hello";
char* p = q;
printf ("%c", ++*p);
sản lượng tự nhiên đáng ngạc nhiên sẽ là:
I
Gì?! Được rồi, vì vậy *p
một phần sẽ đánh giá 'H'
. Sau đó, ++
trò chơi bắt đầu, tại thời điểm đó, nó sẽ được áp dụng cho 'H'
, chứ không phải cho con trỏ! Điều gì xảy ra khi bạn thêm 1 vào 'H'
? Bạn nhận được 1 cộng với giá trị ASCII là 'H'
72; bạn nhận được 73. Thể hiện điều đó như một char
và bạn nhận được char
giá trị ASCII là 73 : 'I'
.
Điều đó quan tâm đến ba biểu thức bạn đã hỏi trong câu hỏi của bạn. Đây là một cái khác, được đề cập trong bình luận đầu tiên cho câu hỏi của bạn:
(*ptr)++
Đó cũng là một điều thú vị. Nếu bạn có:
char q[] = "Hello";
char* p = q;
printf ("%c", (*p)++);
printf ("%c\n", *p);
nó sẽ cung cấp cho bạn đầu ra nhiệt tình này:
HI
Chuyện gì đang xảy ra vậy? Một lần nữa, đó là vấn đề ưu tiên , giá trị biểu hiện và tác dụng phụ . Do dấu ngoặc đơn, *p
phần được coi là biểu thức chính. Biểu thức chính thổi phồng mọi thứ khác; họ được đánh giá đầu tiên. Và *p
, như bạn biết, đánh giá 'H'
. Phần còn lại của biểu thức, ++
phần, được áp dụng cho giá trị đó. Vì vậy, trong trường hợp này, (*p)++
trở thành 'H'++
.
Giá trị của là 'H'++
gì? Nếu bạn nói 'I'
, bạn đã quên (đã!) Thảo luận của chúng tôi về giá trị so với tác dụng phụ với gia tăng hậu tố. Ghi nhớ, 'H'++
đánh giá giá trị hiện tại của 'H'
. Vì vậy, đầu tiên printf()
là sẽ in 'H'
. Sau đó, như là một tác dụng phụ , điều đó 'H'
sẽ được tăng lên 'I'
. Bản printf()
in thứ hai đó 'I'
. Và bạn có lời chào vui vẻ của bạn.
Được rồi, nhưng trong hai trường hợp cuối cùng, tại sao tôi cần
char q[] = "Hello";
char* p = q;
Tại sao tôi không thể có cái gì đó như
/*const*/ char* p = "Hello";
printf ("%c", ++*p); // attempting to change string literal!
Bởi vì "Hello"
là một chuỗi theo nghĩa đen. Nếu bạn cố gắng ++*p
, bạn đang cố gắng thay đổi 'H'
chuỗi thành 'I'
, tạo toàn bộ chuỗi "Iello"
. Trong C, chuỗi ký tự là chỉ đọc; cố gắng sửa đổi chúng gọi hành vi không xác định. "Iello"
cũng không được xác định bằng tiếng Anh, nhưng đó chỉ là sự trùng hợp.
Ngược lại, bạn không thể có
char p[] = "Hello";
printf ("%c", *++p); // attempting to modify value of array identifier!
Tại sao không? Bởi vì trong trường hợp này, p
là một mảng. Một mảng không phải là giá trị l có thể sửa đổi; bạn không thể thay đổi p
điểm theo trước hoặc tăng hoặc giảm, vì tên của mảng hoạt động như thể đó là một con trỏ không đổi. (Đó không phải là những gì nó thực sự là; đó chỉ là một cách thuận tiện để xem xét nó.)
Tóm lại, đây là ba điều bạn đã hỏi về:
*ptr++ // effectively dereferences the pointer, then increments the pointer
*++ptr // effectively increments the pointer, then dereferences the pointer
++*ptr // effectively dereferences the pointer, then increments dereferenced value
Và đây là một phần tư, mọi thứ đều vui như ba người kia:
(*ptr)++ // effectively forces a dereference, then increments dereferenced value
Đầu tiên và thứ hai sẽ sụp đổ nếu ptr
thực sự là một định danh mảng. Thứ ba và thứ tư sẽ sụp đổ nếu ptr
trỏ đến một chuỗi bằng chữ.
Có bạn có nó. Tôi hy vọng tất cả bây giờ là tinh thể. Bạn đã là một khán giả tuyệt vời, và tôi sẽ ở đây cả tuần.
(*ptr)++
(dấu ngoặc đơn cần thiết để định hướng từ*ptr++
)