Я повинен почати, сказавши, що C і C ++ були першими мовами програмування, які я вивчив. Я почав з C, потім багато навчався на C ++ у школі, а потім повернувся до C, щоб в ньому вільно говорити.
Перше, що мене бентежило щодо покажчиків під час вивчення C, було просте:
char ch;
char str[100];
scanf("%c %s", &ch, str);
Ця плутанина здебільшого вкорінена в тому, що я був введений до використання посилання на змінну для аргументів OUT до того, як покажчики були належним чином введені до мене. Я пам'ятаю, що я пропустив написання перших кількох прикладів в C для чайників, тому що вони були занадто простими, щоб ніколи не отримати першу програму, яку я написав для роботи (швидше за все, через це).
Що бентежить в цьому те, що &ch
насправді означало, а також чому str
це не потрібно.
Після того, як я ознайомився з цим, я пам'ятаю, що плутався з приводу динамічного розподілу. Я зрозумів у якийсь момент, що наявність покажчиків на дані не була надзвичайно корисною без динамічного розподілу якогось типу, тому я написав щось на кшталт:
char * x = NULL;
if (y) {
char z[100];
x = z;
}
намагатися динамічно виділити деякий простір. Це не спрацювало. Я не був впевнений, що це буде працювати, але не знав, як ще це може працювати.
Пізніше я дізнався malloc
і про new
, але вони мені справді здалися магічними генераторами пам’яті. Я нічого не знав про те, як вони можуть працювати.
Через деякий час мене знову навчали рекурсії (я вже навчився самостійно, але зараз був у класі), і я запитав, як це працює під кришкою - де зберігалися окремі змінні. Мій професор сказав "на стеку", і багато чого мені стало зрозумілим. Я чула цей термін і раніше реалізовувала стеки програмного забезпечення. Я чув, як інші посилаються на "стек" задовго, але забув про це.
Приблизно в цей час я також зрозумів, що використання багатовимірних масивів в C може отримати дуже заплутану думку. Я знав, як вони працюють, але їх просто так легко заплутати, що я вирішив намагатися обійтися, використовуючи їх, коли тільки міг. Я думаю, що питання тут було здебільшого синтаксичним (особливо це стосується переходу до функцій або повернення їх).
Оскільки я писав C ++ для школи на наступний рік-два, я отримав багато досвіду використання покажчиків для структур даних. Тут у мене виник новий набір проблем - змішування покажчиків. У мене було б декілька рівнів покажчиків (таких речей node ***ptr;
), як мене. Я би знешкодив вказівник неправильну кількість разів і врешті-решт вдався до з'ясування, скільки *
мені потрібно для спроб та помилок.
В якийсь момент я дізнався, як працює купа програми (начебто, але досить добре, що вона більше не підтримувала мене вночі). Я пам'ятаю, як читав, що якщо подивитися на кілька байт перед покажчиком, який malloc
на певній системі повертається, ви зможете побачити, скільки насправді було виділено даних. Я зрозумів, що код у програмі malloc
може попросити більше оперативної пам'яті, і ця пам'ять не входила до моїх виконуваних файлів. Мати гідне робоче уявлення про те, як malloc
працює - це справді корисно.
Незабаром після цього я взяв клас складання, який не навчив мене так багато щодо покажчиків, як, напевно, думає більшість програмістів. Це змусило мене більше задуматися про те, на яку збірку міг перекласти мій код. Я завжди намагався написати ефективний код, але тепер у мене було краще уявлення, як це зробити.
Я також взяв пару занять, де мені довелося написати якийсь ліс . Коли я писав lisp, я не так переймався ефективністю, як був у C. Я мав дуже мало уявлення про те, що цей код може бути перетворений у разі його компіляції, але я знав, що, здається, використовується безліч локальних названих символів (змінних) речі набагато простіше. У якийсь момент я трохи написав код обертання дерева AVL, що мені було дуже важко писати на C ++ через проблеми з вказівниками. Я зрозумів, що моя неприязнь до того, що я вважав зайвими локальними змінними, заважала моїй здатності писати це та кілька інших програм на C ++.
Я також взяв клас компіляторів. Хоча в цьому класі я перевернувся до передового матеріалу та дізнався про статичне одиночне призначення (SSA) та мертві змінні, що не так важливо, за винятком того, що він навчив мене, що будь-який гідний компілятор буде робити гідну роботу з змінними, які є більше не використовується. Я вже знав, що більше змінних (включаючи покажчики) з правильними типами та добрими іменами допоможуть мені зберегти речі прямо в моїй голові, але тепер я також знав, що уникати їх з міркувань ефективності було ще дурішим, ніж мої менші професори, що налаштовані на оптимізацію я.
Тож для мене дуже багато знав про макет пам'яті програми. Думка про те, що мій код означає, як символічно, так і на апаратному забезпеченні, допомагає мені. Використання місцевих покажчиків, які мають правильний тип, дуже допомагає. Я часто пишу код, який виглядає так:
int foo(struct frog * f, int x, int y) {
struct leg * g = f->left_leg;
struct toe * t = g->big_toe;
process(t);
так що якщо я накручую тип вказівника, помилка компілятора зрозуміла, в чому проблема. Якби я:
int foo(struct frog * f, int x, int y) {
process(f->left_leg->big_toe);
і в них помиляється будь-який тип вказівника, помилку компілятора було б набагато складніше зрозуміти. Мені б сподобатися вдатися до спроб і помилок у своїх розчаруваннях, і, мабуть, погіршити ситуацію.