В чем разница между char * и всеми другими типами указателей в C? - PullRequest
0 голосов
/ 06 мая 2020

Я заметил, что есть несколько различий между тем, как char* работает в C, и тем, как работают все другие указатели в C. Одно из отличий, например, заключается в следующем: при печати самого указателя char он возвращает его значение до тех пор, пока он не будет завершен как таковой: Код:

char* var = "Hello World";
printf("%s", var);

Вывод:

Hello World

Но при выполнении того же самого с другими указателями он возвращает адрес значения, хранящегося внутри, например: Код:

int var = 5;
int* pVar = &var;
printf("%d", pVar);

Вывод

//Random memory address

Я также заметил, что вы не можете напрямую объявлять указатели которые не char* таковы:

int* var = 5;

Может ли кто-нибудь перечислить все различия между этими указателями и почему эти различия существуют? Спасибо

Ответы [ 2 ]

2 голосов
/ 06 мая 2020

Давайте go рассмотрим каждый из ваших вопросов один за другим.

Печать

В первом фрагменте кода вы показываете, что printf может печатать строки. Конечно, он напечатал строку. Вы дали ему %s, что означает строку. Но сначала, что такое строка? Чтобы объяснить это, нам нужно понять массивы и символы.

Что такое строка?

Во-первых, что такое char? char - это особый символ (или 8-битное число, но для наших целей это символ). Символом может быть буква (a, b, c) m или любой другой символ (?, !, ., цифры, также есть некоторые управляющие символы). Обычно, если вам нужен только один символ, вы объявляете его так:

char letter_a = 'a';

Итак, что такое массив? Массив - это группа значений, расположенных рядом друг с другом. Рассмотрим следующий код:

int int_array[] = int[50];
int_array[0] = 1;
int_array[1] = 2;
...

В этом примере что такое int_array? Ответ кажется очевидным. Это массив. Но это еще не все. Что, если мы сделаем это?

printf("%d\n", *int_array);

Он напечатает 1. Зачем? Потому что int_array на самом деле просто указатель на первый элемент массива.

Так почему я говорю о массивах? Потому что строка - это просто массив символов. Когда вы запускаете char* string = "Hello!", вы просто создаете массив, который выглядит так: ['H', 'e', 'l', 'l', 'o', '!', '\0']. C знает, что строка закончилась, как только достигнет нулевого символа ('\0').

В вашем первом фрагменте var является указателем на букву 'H', а оператор печати продолжает печатать символы пока он не достигнет нуля.

А как насчет второго фрагмента?

%d не разыменовывает переменную, как %s. Он просто печатает число как целое число со знаком. Целое число в данном случае - это адрес памяти вашего целого числа.

Почему вы не можете назначать указатели?

Можно. Вы получите предупреждение, которое, вероятно, вызовет ошибку сегментации, но вы можете попробовать. Я скомпилировал ваш пример кода, используя clang, и вот что у меня получилось:

test.c:1:1: warning: return type of 'main' is not 'int' [-Wmain-return-type]
void main() {
^
test.c:1:1: note: change return type to 'int'
void main() {
^~~~
int
test.c:2:7: warning: incompatible integer to pointer conversion initializing 'int *' with an expression of type 'int' [-Wint-conversion]
        int* var = 5;
             ^     ~
2 warnings generated.

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

Почему это сработало для строки?

Потому что оно не указывает на конкретное c местоположение. Он указывает на расположение строки, которую C сделал для вас. Ваш код примерно эквивалентен этому:

char h = 'H';
char e = 'e';
...
char* var = &h;
1 голос
/ 06 мая 2020

char* ничем не отличается от других типов указателей. Он просто указывает на один символ. Большинство строковых операций в C рассматривают это как указание на последовательность символов и читают его до тех пор, пока не будет найден нулевой ограничитель.

Указатель на int может означать указатель на единицу int значение ИЛИ это может означать указатель на начало последовательности целых чисел, ИЛИ оно может не указывать ни на что, что является нулевым указателем. Указатель не хранит длину, вам нужно знать, как долго что-то длится, прежде чем читать из дополнительных индексов. C строки используют нулевой терминатор для обозначения конца.

Вы не можете сделать int *var = 5, потому что это не имеет смысла (на самом деле, если вы приведете его как int *var = (int*)5, это может сделать смысл, если вы хотите получить доступ к некоторому регистру ввода-вывода, сопоставленному с этим точным адресом 0x00000005 в неясной системе). Значение указателя содержит адрес памяти. Вместо этого вы можете сделать целочисленное значение, например int myInt = 5;, а затем int *myPointer = &myInt;. Вы можете получить доступ к 5 с помощью оператора косвенного обращения *.

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

Символ формата %s для printf ищет указатель на строку, который является обычным указателем char *, но он ожидает, что он будет содержать более одного символа до нулевого терминатора. Символ формата %d запрашивает одно значение int, поэтому, когда вы передаете туда указатель, он обрабатывает его как значение int - что, заметьте, является поведением undefined. %p предназначен для чтения адреса памяти (указателя), а не %d.

Основное различие между каждым типом указателя - это размер каждой ячейки памяти для арифметики индексации c. Индексирование указателя char, например chars[10], вернет 11-й char, а в большинстве современных систем char составляет 1 байт, поэтому он вернет 10-й байт. При индексировании указателя int* используется более широкий шаг, обычно 4 байта на индекс, поэтому myInts [10] вернет 11-е значение int, начиная с адреса указателя, но каждый шаг памяти здесь составляет 4 байта. Иногда люди просто приводят указатель размером в байты для удобства измерения байтов при индексировании массива, а затем приводят обратно к типу, который они хотят прочитать.

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