Подтвердить что ты не робот

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

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

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

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

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

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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

Скажем, я открыл текстовый файл и прочитал его в гигантскую строку. Я не могу передать вам гигантскую строку по значению, потому что она слишком большая, чтобы поместиться в стек (скажем, 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 и С++ функции не являются первоклассными типами данных, но указателями являются. Таким образом, вы можете передать указатель на функцию, которую вы хотите вызвать, и они могут это сделать.

Надеюсь, что это поможет, но более чем вероятно будет запутать.

Ответ 2

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

Например, у вас может быть огромный struct MyStruct, и у вас есть функция a().

void a (struct MyStruct* b) {
   // You didn't copy the whole `b` around, just passed a pointer.
}

Ответ 3

Приступив к 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

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

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

Ответ 5

Игнорируйте этот ответ.

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

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

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

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

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

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

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

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

(PVOID - void *)

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

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

Ответ 6

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

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

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

Ответ 7

Учитывая, что вы исходите из фона 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.

Ответ 8

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

Ответ 9

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

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

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

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

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

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

Ответ 10

Позвольте мне объяснить это больше в терминах ссылок на Java (как указано в ответе @Greg)

В Java существуют ссылочные типы (например, ссылка на класс) и типы значений (т.е. int). Как и C, Java передается только по значению. Если вы передаете примитивный тип в функцию, вы фактически передаете значение значения (в конце концов, это "тип значения" ), и поэтому любые изменения этого значения внутри этой функции не отражаются в вызывающем коде. Если вы передадите ссылочный тип в функцию, вы можете изменить значение этого объекта, потому что когда вы передаете ссылочный тип, вы передаете ссылку на этот ссылочный тип по значению.

Ответ 11

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

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

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