Tôi nên bắt đầu bằng cách nói rằng C và C ++ là ngôn ngữ lập trình đầu tiên tôi học được. Tôi bắt đầu với C, sau đó học C ++ ở trường rất nhiều, và sau đó quay lại C để thành thạo nó.
Điều đầu tiên làm tôi bối rối về con trỏ khi học C là đơn giản:
char ch;
char str[100];
scanf("%c %s", &ch, str);
Sự nhầm lẫn này chủ yếu bắt nguồn từ việc đã được giới thiệu sử dụng tham chiếu đến một biến cho các đối số OUT trước khi các con trỏ được giới thiệu đúng với tôi. Tôi nhớ rằng tôi bỏ qua văn bản cho vài ví dụ đầu tiên trong C cho Dummies vì họ quá đơn giản chỉ để không bao giờ có được chương trình đầu tiên tôi đã ghi vào làm việc (rất có thể vì điều này).
Điều khó hiểu về điều này là những gì &ch
thực sự có ý nghĩa cũng như tại saostr
không cần nó.
Sau khi tôi trở nên quen thuộc với điều đó, tôi nhớ rằng mình đang bối rối về phân bổ động. Tại một số điểm, tôi nhận ra rằng việc có các con trỏ tới dữ liệu là cực kỳ hữu ích nếu không có sự phân bổ động của một số loại, vì vậy tôi đã viết một cái gì đó như:
char * x = NULL;
if (y) {
char z[100];
x = z;
}
để cố gắng phân bổ động một số không gian. Nó không hoạt động. Tôi không chắc rằng nó sẽ hoạt động, nhưng tôi không biết nó có thể hoạt động như thế nào.
Sau đó tôi đã biết về malloc
và new
, nhưng chúng thực sự giống như những máy tạo trí nhớ kỳ diệu đối với tôi. Tôi không biết gì về cách họ có thể làm việc.
Một thời gian sau, tôi được dạy đệ quy một lần nữa (trước đây tôi đã tự học nó, nhưng bây giờ đang ở trong lớp học) và tôi đã hỏi nó hoạt động như thế nào dưới mui xe - nơi các biến riêng biệt được lưu trữ. Giáo sư của tôi đã nói "trên chồng" và rất nhiều điều trở nên rõ ràng với tôi. Tôi đã nghe thuật ngữ này trước đây và đã thực hiện các ngăn xếp phần mềm trước đó. Tôi đã nghe người khác nhắc đến "chồng" từ lâu, nhưng đã quên nó.
Trong khoảng thời gian này tôi cũng nhận ra rằng việc sử dụng các mảng đa chiều trong C có thể rất khó hiểu. Tôi biết làm thế nào họ làm việc, nhưng họ rất dễ bị rối khi tôi quyết định cố gắng sử dụng chúng bất cứ khi nào tôi có thể. Tôi nghĩ rằng vấn đề ở đây chủ yếu là cú pháp (đặc biệt là chuyển đến hoặc trả lại chúng từ các hàm).
Vì tôi đã viết C ++ cho trường học trong một hoặc hai năm tới, tôi đã có nhiều kinh nghiệm sử dụng con trỏ cho cấu trúc dữ liệu. Ở đây tôi đã có một loạt rắc rối mới - trộn lẫn con trỏ. Tôi sẽ có nhiều cấp độ con trỏ (những thứ như node ***ptr;
) làm tôi vấp ngã. Tôi đã bỏ qua một con trỏ số lần sai và cuối cùng tìm ra số lượng *
tôi cần bằng cách dùng thử và lỗi.
Tại một số thời điểm, tôi đã học được cách một đống chương trình hoạt động (loại, nhưng đủ tốt để nó không còn khiến tôi thức đêm nữa). Tôi nhớ đọc rằng nếu bạn xem một vài byte trước khi con trỏ malloc
trên một hệ thống nhất định trả về, bạn có thể thấy lượng dữ liệu thực sự được phân bổ. Tôi nhận ra rằng mã trong malloc
có thể yêu cầu thêm bộ nhớ từ HĐH và bộ nhớ này không phải là một phần của các tệp thực thi của tôi. Có một ý tưởng làm việc tốtmalloc
việc là một thực sự hữu ích.
Ngay sau đó tôi đã tham gia một lớp lắp ráp, nó không dạy tôi nhiều về con trỏ như hầu hết các lập trình viên có thể nghĩ. Nó đã khiến tôi suy nghĩ nhiều hơn về việc tập hợp mã của tôi có thể được dịch sang. Tôi đã luôn cố gắng viết mã hiệu quả, nhưng bây giờ tôi đã có một ý tưởng tốt hơn làm thế nào.
Tôi cũng đã tham gia một vài lớp học mà tôi phải viết một số lisp . Khi viết lisp tôi không quan tâm đến hiệu quả như khi tôi ở C. Tôi có rất ít ý tưởng mã này có thể được dịch thành gì nếu được biên dịch, nhưng tôi biết rằng nó có vẻ giống như sử dụng nhiều ký hiệu (biến) được đặt tên cục bộ mọi thứ dễ dàng hơn nhiều Tại một số thời điểm tôi đã viết một số mã xoay cây AVL trong một chút ít, rằng tôi đã có một thời gian rất khó để viết bằng C ++ vì các vấn đề về con trỏ. Tôi nhận ra rằng sự ác cảm của tôi đối với những gì tôi nghĩ là các biến cục bộ dư thừa đã cản trở khả năng của tôi để viết điều đó và một số chương trình khác trong C ++.
Tôi cũng đã tham gia một lớp trình biên dịch. Mặc dù trong lớp này, tôi đã tìm hiểu về tài liệu nâng cao và tìm hiểu về phép gán đơn tĩnh (SSA) và các biến chết, điều đó không quan trọng ngoại trừ việc nó dạy tôi rằng bất kỳ trình biên dịch tử tế nào cũng sẽ làm tốt việc xử lý các biến đó là Không còn được sử dụng. Tôi đã biết rằng nhiều biến số hơn (bao gồm cả con trỏ) với các loại chính xác và tên tốt sẽ giúp tôi giữ mọi thứ trong đầu, nhưng bây giờ tôi cũng biết rằng tránh chúng vì lý do hiệu quả thậm chí còn ngu ngốc hơn các giáo sư có tư duy tối ưu hóa vi mô của tôi đã nói tôi.
Vì vậy, đối với tôi, biết một chút về cách bố trí bộ nhớ của một chương trình đã giúp ích rất nhiều. Suy nghĩ về ý nghĩa của mã của tôi, cả về mặt tượng trưng và phần cứng, giúp tôi hiểu. Sử dụng con trỏ cục bộ có loại chính xác sẽ giúp rất nhiều. Tôi thường viết mã trông giống như:
int foo(struct frog * f, int x, int y) {
struct leg * g = f->left_leg;
struct toe * t = g->big_toe;
process(t);
Vì vậy, nếu tôi làm hỏng một loại con trỏ, nó rất rõ ràng bởi lỗi trình biên dịch, vấn đề là gì. Nếu tôi đã làm:
int foo(struct frog * f, int x, int y) {
process(f->left_leg->big_toe);
và có bất kỳ loại con trỏ sai trong đó, lỗi trình biên dịch sẽ khó khăn hơn rất nhiều để tìm ra. Tôi sẽ bị cám dỗ để dùng thử và thay đổi lỗi trong sự thất vọng của tôi, và có lẽ làm cho mọi thứ tồi tệ hơn.