Преимущества указателей? - PullRequest
       2

Преимущества указателей?

17 голосов
/ 19 декабря 2010

Недавно я проявил интерес к программированию на C, поэтому я взял себе книгу (K & R) и начал изучать.

Исходя из университетского курса по Java (основы), указатели - это совершенно новая глава, и изто, что я читаю в Интернете, это довольно сложная концепция, чтобы разобраться в этом.Перед тем, как перейти к главе, посвященной указателям, у меня сложилось впечатление, что указатели являются основной частью языка Си и обеспечивают большие преимущества.

После прочтения главы и получения базового представления о том, что такое указатели и как они работают, преимущества для меня не очевидны.

Например (пожалуйста, исправьте меня, если я все понял неправильно) во введении указателей в книге K & R говорится, что, поскольку мы вызываем по значению, при передаче переменной в вызове функции мы в значительной степени передаем копию переменной для обработки функции, и поэтому функция не может ничего делатьк исходной переменной, и мы можем преодолеть это с помощью указателей.

В более позднем примере, в котором используется указатель на символ, в книге говорится, что увеличение указателя на символ допустимо, поскольку функция имеет частную копию указателя.Не являются ли «личные копии» причиной для использования указателей?

Полагаю, я немного запутался во всем использовании указателей.Если меня спросят, я могу использовать указатели вместо подписок на массив, например, но я сомневаюсь, что это основное использование указателей.

Программирование на Linux и Open source было основной причиной, по которой я попал в C. Я получил исходный кодиз проекта C для изучения (Geany IDE), и я вижу, что по всему исходному коду используются указатели.

Я также немного поискал по форумам и нашел пару постов с похожими вопросами.Ответ был (я цитирую):

Если вы не знаете, когда следует использовать указатели, просто не используйте их.

Это станеточевидно, когда вам нужно их использовать, каждая ситуация отличается.

Безопасно ли для меня избегать использования указателей в настоящее время и использовать их только в определенных ситуациях (где потребность в указателях будеточевидно?)

Ответы [ 11 ]

9 голосов
/ 19 декабря 2010

Одним из преимуществ указателей является то, что когда вы используете их в аргументах функций, вам не нужно копировать большие куски памяти, а также вы можете изменить состояние, разыменовав указатель.у вас может быть огромный struct MyStruct, и у вас есть функция a().

void a (struct MyStruct* b) {
   // You didn't copy the whole `b` around, just passed a pointer.
}
8 голосов
/ 19 декабря 2010

Исходя из Java, вы будете иметь несколько иную перспективу, чем представленная в K & R (K & R не предполагает, что читатель знает какой-либо другой современный язык программирования).

Указатель в C похож на чуть более способную версию ссылки в Java. Вы можете увидеть это сходство через исключение Java с именем NullPointerException. Одним из важных аспектов указателей в C является то, что вы можете изменять то, на что они указывают, путем увеличения и уменьшения.

В C вы можете хранить кучу вещей в памяти в массиве, и вы знаете, что они находятся рядом друг с другом в памяти. Если у вас есть указатель на один из них, вы можете сделать так, чтобы указатель указывал на «следующий», увеличивая его. Например:

int a[5];

int *p = a; // make p point to a[0]
for (int i = 0; i < 5; i++) {
    printf("element %d is %d\n", i, *p);
    p++; // make `p` point to the next element
}

Приведенный выше код использует указатель p для указания на каждый последующий элемент в массиве a в последовательности и распечатывает их.

(Примечание. Приведенный выше код является только пояснительным примером, и вы обычно не пишете простой цикл таким образом. Было бы проще получить доступ к элементам массива как a[i] и не использовать указатель там.)

4 голосов
/ 19 декабря 2010

В более позднем примере, в котором используется указатель на символ, в книге говорится, что увеличение указателя на символ допустимо, поскольку функция имеет закрытую копию указателя.

Я бы сказал,это означает, что они увеличивают сам указатель, что означает изменение адреса (и, следовательно, указание на другое значение).Это может быть полезно, если им был передан первый элемент массива и он хочет продолжить работу в массиве, но не изменять значения.

3 голосов
/ 19 декабря 2010

Помните, что C (и книга K & R) очень старый, вероятно, старше всего, что вы изучали раньше (определенно, старше, чем Java). Указатели не являются дополнительной функцией C, они являются основной частью работы компьютеров.

Теорию указателей не особенно сложна в освоении, просто они очень мощные, поэтому ошибка, скорее всего, приведет к аварийному завершению работы вашего приложения, и компиляторам будет трудно пытаться отловить ошибки, связанные с указателем. Одна из главных новинок в Java заключалась в том, чтобы иметь «почти» такую ​​же мощность, как C без указателей.

Так что, на мой взгляд, попытка написать C, избегая указателей, это все равно что пытаться ездить на велосипеде без одной педали. Да, это выполнимо, но ты будешь работать вдвое больше.

3 голосов
/ 19 декабря 2010

Игнорируйте этот ответ, пожалуйста.

Если вы не знаете, как использовать указатели, узнайте , как их использовать. Простой.

Как вы уже сказали, указатели позволяют передавать в функцию более одной переменной через указатель, как вы правильно заметили. Другое использование указателей - обращение к массивам данных, которые вы можете использовать, используя арифметику указателей. Наконец, указатели позволяют динамически распределять память. Поэтому совет не использовать указатели серьезно ограничит ваши возможности.

Указатели - это способ C говорить об адресах памяти. По этой причине они имеют решающее значение. Поскольку у вас есть K & R, прочитайте это.

Пример использования см. В моем ответе на this . Как я сказал в этом ответе, это не обязательно, как вы это сделаете, учитывая вопрос.

Тем не менее, эта техника именно так работает с такими библиотеками, как MPIR и GMP libgmp, если вы не встречали его, поддерживает mathematica, maple и т. д. и является библиотекой для арифметики произвольной точности. Вы обнаружите, что mpn_t является typedef для указателя; в зависимости от ОС зависит от того, на что он указывает. Вы также найдете много арифметики с указателями в медленной, C-версии этого кода.

Наконец, я упомянул об управлении памятью. Если вы хотите выделить массив чего-то, вам нужно malloc и free, которые имеют дело с указателями на области памяти; в частности, malloc возвращает указатель на некоторое количество памяти после выделения или NULL при ошибке.

Одно из моих любимых указателей - это заставить функцию-член класса C ++ действовать как поток в окнах, используя win32 API, т.е. иметь класс, содержащий поток. К сожалению, CreateThread в Windows не будет принимать функции класса C ++ по очевидным причинам - CreateThread - это функция C, которая не понимает экземпляры класса; поэтому вам нужно передать ему статическую функцию. Вот хитрость:

DWORD WINAPI CLASSNAME::STATICFUNC(PVOID pvParam)
{
    return ((CLASSNAME*)pvParam)->ThreadFunc();
}

(PVOID - это void *)

Что происходит, если он возвращает ThreadFunc, который выполняется "вместо" (на самом деле STATICFUNC вызывает его) STATICFUNC и может действительно обращаться ко всем закрытым переменным-членам CLASSNAME. Гениально, а?

Если этого недостаточно, чтобы убедить вас, что указатели вроде равны C, я не знаю, что это. Или, может быть, нет смысла в C без указателей. Или ...

3 голосов
/ 19 декабря 2010

Ваше выделенное правило очень мудрое.Это избавит вас от неприятностей, но рано или поздно вам придется выучить указатели.

Так почему мы хотим использовать указатели?

Скажем, я открыл текстовый файл и прочитал его в гигантскую строку,Я не могу передать вам гигантскую строку по значению, потому что она слишком велика, чтобы поместиться в стек (скажем, 10 МБ).Поэтому я говорю вам, где находится строка, и говорю: «Иди, посмотри на мою строку».

Массив - это указатель (почти).

int [] и int * немного отличаются, но по большей части взаимозаменяемы.

int[] i = new int[5]; // garbage data
int* j = new int[5]   // more garbage data but does the same thing
std::cout << i[3] == i + 3 * sizeof(int); // different syntax for the same thing

Более продвинутыйИспользование указателей, которые чрезвычайно полезны, это использование указателей на функции.В C и C ++ функции не являются типами данных первого класса, но указатели есть.Таким образом, вы можете передать указатель на функцию, которую вы хотите вызвать, и они могут это сделать.

Надеюсь, это поможет, но, скорее всего, будет сбивать с толку.

2 голосов
/ 19 декабря 2010

Учитывая, что вы пришли из Java-фона, вот самый простой способ разобраться с тем, что используют указатели.

Допустим, у вас есть такой класс в Java:

public class MyClass {
    private int myValue;

    public int getMyValue() { return myValue; }
    public void setMyValue(int value) { myValue = value; }
}

Вот ваша основная функция, которая создает один из ваших объектов и вызывает для него функцию.

public static void Main(String[] args) {
    MyClass myInstance = new MyClass();

    myInstance.setMyValue(1);
    System.out.printLn(myInstance.getMyValue()); // prints 1

    DoSomething(myInstance);
    System.out.printLn(myInstance.getMyValue()); // prints 2
}

public static void DoSomething(MyClass instance) {
    instance.setMyValue(2);
}

Переменная myInstance, которую вы объявили в Main, является справочной. По сути, это дескриптор, который JVM использует для отслеживания экземпляра вашего объекта. Теперь давайте сделаем то же самое в C.

typedef struct _MyClass
{
    int myValue;
} MyClass;

void DoSomething(MyClass *);

int main()
{
    MyClass myInstance;

    myInstance.myValue = 1;
    printf("i", myInstance.myValue); // prints 1

    MyClass *myPointer = &myInstance; // creates a pointer to the address of myInstance

    DoSomething(myPointer);
    printf("i", myInstance.myValue); // prints 2

    return 0;
}

void DoSomething(MyClass *instance)
{
    instance->myValue = 2;
}

Конечно, указатели в C гораздо более гибкие, но в этом и заключается суть их работы в Java.

1 голос
/ 19 декабря 2010

В этом примере:

int f1(int i); 
f1(x);

параметр i передается по значению, поэтому функция f1 не смогла изменить значение переменной x вызывающей стороны.

Но в этом случае:

int f2(int* i);
int x;
int* px = &x;
f2(px);

Здесь мы по-прежнему передаем px параметр по значению, но в то же время мы передаем x по ссылке !.Поэтому, если вызываемый объект (f2) изменит свой int* i, он не повлияет на px в вызывающем абоненте.Однако при изменении *i вызываемый абонент изменит значение x в вызывающем абоненте.

1 голос
/ 19 декабря 2010

Если вы имеете дело с динамически выделяемой памятью, вы должны использовать указатели для доступа к выделенному пространству.Многие программы имеют дело с распределенной памятью, поэтому многие программы должны использовать указатели.

0 голосов
/ 21 апреля 2012

Указатели позволяют динамически отправлять код с учетом условий или состояния программы. Простой способ понять эту концепцию - подумать о древовидной структуре, где каждый узел представляет либо вызов функции, переменную, либо указатель на узел подуровня. Как только вы поймете это, вы затем будете использовать указатели, чтобы указать на установленные области памяти, на которые программа может ссылаться по желанию, чтобы понять начальное состояние и, таким образом, первое разыменование и смещение. Тогда каждый узел будет содержать свои собственные указатели для дальнейшего понимания состояния, при котором может иметь место дальнейшее разыменование, может быть вызвана функция или получено значение.

Конечно, это всего лишь один из способов визуализации использования указателей, поскольку указатель является не чем иным, как адресом ячейки памяти. Вы также можете использовать указатели для передачи сообщений, вызовов виртуальных функций, сборки мусора и т. Д. Фактически я использовал их для воссоздания вызовов виртуальных функций в стиле c ++. В результате виртуальная функция c и виртуальные функции c ++ работают с одинаковой скоростью. Однако реализация c была намного меньше по размеру (на 66% меньше в КБ) и немного более портативна. Но репликация функций из других языков в C не всегда будет выгодна, очевидно, что другие языки могут быть лучше оснащены для сбора информации, которую компилятор может использовать для оптимизации этой структуры данных.

Таким образом, с помощью указателей можно многое сделать. Язык C и указатели в настоящее время обесцениваются, так как большинство языков более высокого уровня оснащены более часто используемыми структурами данных / реализациями, которые вам пришлось бы создавать самостоятельно с использованием указателей. Но всегда бывает, что программисту может понадобиться реализовать сложную подпрограмму, и в этом случае очень полезно знать, как использовать указатели.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...