Tại sao con trỏ tăng?


25

Gần đây tôi mới bắt đầu học C ++ và như hầu hết mọi người (theo những gì tôi đã đọc) Tôi đang vật lộn với con trỏ.

Không phải theo nghĩa truyền thống, tôi hiểu chúng là gì và tại sao chúng được sử dụng và làm thế nào chúng có thể hữu ích, tuy nhiên tôi không thể hiểu làm thế nào con trỏ tăng sẽ hữu ích, bất cứ ai cũng có thể giải thích về việc tăng con trỏ là như thế nào Khái niệm hữu ích và thành ngữ C ++?

Câu hỏi này được đưa ra sau khi tôi bắt đầu đọc cuốn sách A Tour of C ++ của Bjarne Stroustrup, tôi đã được giới thiệu cuốn sách này, bởi vì tôi khá quen thuộc với Java và những người ở Reddit nói với tôi rằng nó sẽ là một cuốn sách 'chuyển đổi' tốt .


11
Một con trỏ chỉ là một trình vòng lặp
Charles Salvia

1
Đây là một trong những công cụ yêu thích để viết virus máy tính đọc những gì chúng không nên đọc. Đây cũng là một trong những trường hợp dễ bị tổn thương phổ biến nhất trong các ứng dụng (khi một con trỏ tăng con trỏ qua khu vực mà chúng được cho là, sau đó đọc hoặc ghi nó)> Xem lỗi HeartBleed.
Sam

1
@vasile Đây là những gì xấu về con trỏ.
Cruncher

4
Điều hay / xấu về C ++ là nó cho phép bạn làm được nhiều hơn trước khi gọi segfault. Thông thường, bạn nhận được một segfault khi cố gắng truy cập bộ nhớ, bộ nhớ hệ thống hoặc bộ nhớ ứng dụng được bảo vệ của tiến trình khác. Bất kỳ quyền truy cập nào bên trong các trang ứng dụng thông thường đều được hệ thống cho phép và tùy thuộc vào lập trình viên / trình biên dịch / ngôn ngữ để thực thi các giới hạn hợp lý. C ++ khá nhiều cho phép bạn làm bất cứ điều gì bạn muốn. Đối với openssl có trình quản lý bộ nhớ riêng - điều đó không đúng. Nó chỉ có các cơ chế truy cập bộ nhớ C ++ mặc định.
Sam

1
@INdek: Bạn sẽ chỉ nhận được một segfault nếu bộ nhớ bạn đang cố truy cập được bảo vệ. Hầu hết các hệ điều hành chỉ định bảo vệ ở cấp độ trang, vì vậy bạn thường có thể truy cập mọi thứ trên trang mà con trỏ của bạn bắt đầu. Nếu HĐH sử dụng kích thước trang 4K, đó là một lượng dữ liệu hợp lý. Nếu con trỏ của bạn bắt đầu ở đâu đó trong heap, bất kỳ ai cũng đoán được bạn có thể truy cập bao nhiêu dữ liệu.
TMN

Câu trả lời:


46

Khi bạn có một mảng, bạn có thể thiết lập một con trỏ để trỏ đến một phần tử của mảng:

int a[10];
int *p = &a[0];

Ở đây pchỉ ra yếu tố đầu tiên a, đó là a[0]. Bây giờ bạn có thể tăng con trỏ để trỏ đến phần tử tiếp theo:

p++;

Bây giờ pchỉ đến yếu tố thứ hai a[1],. Bạn có thể truy cập phần tử ở đây bằng cách sử dụng *p. Điều này khác với Java khi bạn sẽ phải sử dụng biến chỉ số nguyên để truy cập các phần tử của một mảng.

Tăng một con trỏ trong C ++ trong đó con trỏ đó không trỏ đến một phần tử của một mảng là hành vi không xác định .


23
Đúng, với C ++, bạn có trách nhiệm tránh các lỗi lập trình như truy cập bên ngoài giới hạn của một mảng.
Greg Hewgill

9
Không, tăng một con trỏ trỏ đến bất cứ thứ gì ngoại trừ một phần tử mảng là hành vi không xác định. Tuy nhiên, nếu bạn đang làm một cái gì đó ở mức độ thấp và không có khả năng di động, thì việc tăng một con trỏ thường không có gì khác hơn là truy cập vào thứ tiếp theo trong bộ nhớ, bất cứ điều gì có thể xảy ra.
Greg Hewgill

4
Có một vài điều, hoặc có thể được coi là một mảng; trên thực tế, một chuỗi văn bản là một mảng các ký tự. Trong một số trường hợp, một int dài được coi là một mảng byte, mặc dù điều này có thể dễ dàng khiến bạn gặp rắc rối.
AMADANON Inc.

6
Điều đó cho bạn biết loại , nhưng hành vi được mô tả trong 5.7 Toán tử cộng gộp [expr.add]. Cụ thể, 5.7 / 5 nói rằng đi bất cứ nơi nào bên ngoài mảng ngoại trừ một kết thúc là UB.
Vô dụng

4
Đoạn cuối là: Nếu cả toán hạng con trỏ và điểm kết quả đến các phần tử của cùng một đối tượng mảng, việc đánh giá sẽ không tạo ra tràn; nếu không thì hành vi không xác định . Vì vậy, nếu kết quả không nằm trong mảng cũng không phải là một kết thúc, bạn sẽ nhận được UB.
Vô dụng

37

Con trỏ tăng là C ++ thành ngữ, bởi vì ngữ nghĩa con trỏ phản ánh một khía cạnh cơ bản của triết lý thiết kế đằng sau thư viện chuẩn C ++ (dựa trên STL của Alexander Stepanov )

Khái niệm quan trọng ở đây, là STL được thiết kế xung quanh các thùng chứa, thuật toán và các trình vòng lặp. Con trỏ chỉ đơn giản là các vòng lặp .

Tất nhiên, khả năng tăng (hoặc cộng / trừ từ) con trỏ trở lại C. Rất nhiều thuật toán thao tác chuỗi C có thể được viết đơn giản bằng cách sử dụng số học con trỏ. Hãy xem xét các mã sau đây:

char string1[4] = "abc";
char string2[4];
char* src = string1;
char* dest = string2;
while ((*dest++ = *src++));

Mã này sử dụng số học con trỏ để sao chép chuỗi C kết thúc null. Vòng lặp tự động chấm dứt khi nó gặp null.

Với C ++, ngữ nghĩa con trỏ được khái quát hóa theo khái niệm của các trình vòng lặp . Hầu hết các bộ chứa C ++ tiêu chuẩn đều cung cấp các trình vòng lặp, có thể được truy cập thông qua các hàm beginendthành viên. Các trình vòng lặp hoạt động giống như các con trỏ, theo đó chúng có thể được tăng lên, hủy đăng ký và đôi khi giảm dần hoặc nâng cao.

Để lặp lại qua một std::string, chúng tôi sẽ nói:

std::string s = "abcdef";
std::string::iterator it = s.begin();
for (; it != s.end(); ++it) std::cout << *it;

Chúng tôi tăng iterator giống như chúng tôi sẽ tăng một con trỏ tới một chuỗi C đơn giản. Lý do khái niệm này mạnh mẽ là vì bạn có thể sử dụng các mẫu để viết các hàm sẽ hoạt động cho bất kỳ loại trình vòng lặp nào đáp ứng các yêu cầu khái niệm cần thiết. Và đây là sức mạnh của STL:

std::string s1 = "abcdef";
std::vector<char> buf;
std::copy(s1.begin(), s1.end(), std::back_inserter(buf));

Mã này sao chép một chuỗi thành một vector. Các copychức năng là một mẫu mà sẽ làm việc với bất kỳ iterator rằng hỗ trợ incrementing (bao gồm con trỏ đồng bằng). Chúng ta có thể sử dụng cùng copychức năng trên một chuỗi C đơn giản:

   const char* s1 = "abcdef";
   std::vector<char> buf;
   std::copy(s1, s1 + std::strlen(s1), std::back_inserter(buf));

Chúng tôi có thể sử dụng copytrên một std::maphoặc một std::sethoặc bất kỳ vùng chứa tùy chỉnh nào hỗ trợ các trình vòng lặp.

Lưu ý rằng con trỏ là một loại trình vòng lặp cụ thể: Trình lặp truy cập ngẫu nhiên , có nghĩa là chúng hỗ trợ tăng, giảm và tiến với toán tử +-toán tử. Các kiểu trình vòng lặp khác chỉ hỗ trợ một tập hợp con về ngữ nghĩa con trỏ: một trình vòng lặp hai chiều hỗ trợ ít nhất là tăng và giảm; một iterator chuyển tiếp hỗ trợ ít nhất là tăng. (Tất cả các loại trình vòng lặp đều hỗ trợ hội thảo.) copyHàm yêu cầu một trình vòng lặp ít nhất hỗ trợ tăng dần.

Bạn có thể đọc về các khái niệm lặp khác nhau ở đây .

Vì vậy, tăng con trỏ là một cách thành ngữ C ++ để lặp lại trên một mảng C hoặc truy cập các phần tử / offset trong một mảng C.


3
Mặc dù tôi sử dụng các con trỏ như trong ví dụ đầu tiên, tôi chưa bao giờ nghĩ về nó như một trình vòng lặp, nhưng bây giờ nó rất có ý nghĩa.
dyesdyes

1
"Vòng lặp tự động chấm dứt khi nó gặp null." Đây là một thành ngữ đáng sợ.
Charles Wood

9
@CharlesWood, sau đó tôi đoán bạn phải tìm C khá kinh hoàng
Im lặng

7
@CharlesWood: Cách khác là sử dụng độ dài của chuỗi làm biến điều khiển vòng lặp, nghĩa là đi qua chuỗi hai lần (một lần để xác định độ dài và một lần để sao chép các ký tự). Khi bạn đang chạy trên PDP-7 1 MHz, điều đó thực sự có thể bắt đầu tăng lên.
TMN

3
@INdek: trước hết, C và C ++ cố gắng tránh bằng mọi giá để đưa ra các thay đổi vi phạm - và tôi sẽ nói rằng việc thay đổi hành vi mặc định của chuỗi ký tự sẽ là một sửa đổi. Nhưng quan trọng nhất, các chuỗi kết thúc bằng 0 chỉ là một quy ước (dễ dàng theo dõi bởi thực tế là các chuỗi ký tự được kết thúc bằng 0 theo mặc định và các hàm thư viện mong đợi chúng), thực tế không ai ngăn bạn sử dụng các chuỗi được tính trong C - một số thư viện C sử dụng chúng (xem ví dụ BLE của OLE).
Matteo Italia

16

Con trỏ số học là trong C ++ vì nó được trong C. con trỏ số học là trong C bởi vì nó là một thành ngữ bình thường trong lắp ráp .

Có rất nhiều hệ thống trong đó "thanh ghi gia tăng" nhanh hơn "tải giá trị không đổi 1 và thêm vào thanh ghi". Ngoài ra, khá nhiều hệ thống cho phép bạn thực hiện "tải DWORD vào A từ địa chỉ được chỉ định trong thanh ghi B, sau đó thêm sizeof (DWORD) vào B" trong một lệnh. Ngày nay, bạn có thể mong đợi một trình biên dịch tối ưu hóa để sắp xếp thứ này cho bạn, nhưng đây không thực sự là một lựa chọn vào năm 1973.

Về cơ bản, đây là lý do tương tự mà các mảng C không được kiểm tra giới hạn và các chuỗi C không có kích thước được nhúng trong chúng: ngôn ngữ được phát triển trên một hệ thống có mỗi byte và mọi lệnh được tính.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.