C Указатели на структуры Вопрос - Что означает «*», когда он находится перед переменной структуры? - PullRequest
0 голосов
/ 03 февраля 2020

Ниже есть простой код, который спрашивает фамилию, и 2 класса для 5 человек отдельно. Он также находит среднее значение всех оценок и имеет более высокую оценку. Вопрос касается символа «*».

Мой профессор вызывает функцию, которая называется readstudent (Tstudent * pstu); Что означает * перед pstu и почему это необходимо?

Кроме того, когда мы go через readstudent (Tstudent * pstu), почему есть «&» для 1 и 2 класса и нет «&» для имени?

#include <stdio.h>
#include <string.h>
typedef struct student
{
    char name[20];
    float grade1;
    float grade2;
} TStudent;



void readstudent( TStudent *pstu );
void printstudent( TStudent stu );
int main( void )
{
int N = 5;
        TStudent a[N]; int i, imax; float max, mo, sum;
    for(i=0; i<N; i++)
        readstudent( &a[i] );
        printf("\n Oi karteles twn foitntwv einai:\n");
    for(i=0; i<N; i++)
        printstudent( a[i]);
        sum = 0;
    for(i=0; i<N; i++)
        sum = sum + (a[i].grade1+a[i].grade2)/2;
        mo = (float)sum / N;
        printf("\nO mesos oros bathmologias tns taksns einai %2.2f\n", mo);
        imax = 0;
        max = (a[0].grade1+a[0].grade2)/2;
    for(i=0; i<N; i++)
        if ((a[i].grade1+a[i].grade2)/2 > max)
        {   
            max = (a[i].grade1+a[i].grade2)/2;
            imax = i;
        }
printf("\nO/H foitntns/tria me ton ypsnlotero meso oro (%4.2f) einai o/h %s.\n", max, a[imax].name);
return 0;
}

void readstudent( TStudent *pstu)
{
printf("Eisagwgh foitntn/trias: epwnymo <keno> bathmos1 <keno> bathmos2 <keno>: \n");
    scanf("%s", pstu->name);
    scanf("%f", &pstu->grade1);
    scanf("%f", &pstu->grade2);
}
void printstudent( TStudent stu)
{
    printf("Epwnymo: %s\n", stu.name);
    printf("Bathmos-1: %4.2f\n", stu.grade1);
    printf("Bathmos-2: %4.2f\n", stu.grade2);
}

Спасибо за ваше время, ценим вашу помощь!

Ответы [ 3 ]

3 голосов
/ 04 февраля 2020

Итак, краткий курс sh по указателям в C.

Прежде всего, Почему мы используем указатели в C? По сути, у нас есть для использования указателей по следующим причинам:

  1. Чтобы функции могли изменять свои параметры
  2. Для отслеживания динамически выделяемой памяти

Указатели пригодятся и другими способами, поскольку они предлагают форму косвенного обращения . Они позволяют нам манипулировать другими объектами без необходимости знать имена других объектов.


Indirection - мощный инструмент программирования, который вы уже видели, когда имели дело с массивами. Вместо создания 100 уникальных целочисленных переменных мы можем создать массив целых чисел и использовать подписку для ссылки на конкретный объект c. Таким образом, вместо записи

int var0 = 0;
int var1 = 1;
int var2 = 2;
...
int var99 = 99;

мы можем написать

int var[100];

for ( int i = 0; i < 100; i++ )
  var[i] = i;

Запись с индексом массива позволяет нам ссылаться на объект косвенно , а не на уникальный имя. Он предоставляет ярлык для управления большим количеством объектов, ссылаясь на них одним выражением. Указатели служат практически той же цели. Предположим, у нас есть несколько целочисленных переменных с именами x, y и z. Мы можем создать указатель p для обращения к каждому из них по очереди:

int x = 10; 
int y = 20;
int z = 30;

int *p;

Давайте начнем с игры с x. Мы устанавливаем p для указания на x, используя унарный оператор & address-of:

p = &x; // int * = int *

Тип переменной p равен int *. Тип выражения &x равен int *, а значением выражения является адрес x. Затем мы можем изменить значение от x до p, используя унарный оператор перенаправления *:

*p = 15; // int = int

Так как тип переменной p равен int * тип выражения *p равен int, и это выражение обозначает тот же объект , что и выражение x. Таким образом, в строке выше мы меняем значение, хранящееся в x косвенно до p. Мы можем сделать то же самое с y и z:

p = &y;
*p = 25;
p = &z;
*p = 35;

Хорошо, круто, но почему бы просто не назначить x, y и z напрямую ? Почему go через боль присвоения их адресов p и присвоения значений через *p?

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

void foo( int x )
{
  x = 2 * x;
}

и вызывать ее так:

int main( void )
{
  int val = 2;
  printf( "before foo: %d\n", val );
  foo( val );
  printf( "after foo: %d\n", val );
  return 0;
}

Что мы хотим видеть это

before foo: 2
after foo: 4

но то, что мы получаем это

before foo: 2
after foo: 2

Это не работает, потому что C использует параметр- Соглашение о передаче, называемое «передачей по значению» - короче говоря, формальный параметр x в определении функции обозначает отдельный объект в памяти, чем фактический параметр val. Запись нового значения в x не влияет на val. Чтобы foo изменил фактический параметр val, мы должны передать указатель в val:

void foo( int *x )     //  x == &val
{                      // *x ==  val
  *x = *x * 2;
}

int main( void )
{
  int val = 2;
  printf( "before foo: %d\n", val );
  foo( &val );
  printf( "after foo: %d\n", val );
  return 0;
}

Сейчас мы получаем ожидаемый результат - val изменяется на foo. Выражение *x относится к тому же объекту, что и val. И теперь мы можем написать что-то вроде

 foo( &y );   // x == &y, *x == y
 foo( &z );   // x == &z, *x == z

Это наш первый вариант использования - возможность функции изменять свои параметры.


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

int x = new_memory();

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

int *p = malloc( sizeof *p ); sizeof *p == sizeof (int)

Это выделяет достаточно места для одного объекта int и присваивает адрес этого нового пространства p. Вы можете выделить блоки произвольного размера:

int *arr = malloc( sizeof *arr * 100 );

выделяет достаточно места для 100 int объектов и устанавливает arr для указания на первый из них.

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


Краткое примечание о синтаксисе указателя. С операциями с указателями связаны два оператора. Унарный оператор & используется для получения адреса объекта, тогда как унарный * разыменовывает указатель. Предположим, у нас есть int объект с именем x и указатель на int с именем p:
 p = &x; // assign the address of x to p
*p =  10; // assign a new value to whatever p points to

В объявлении унарный * указывает, что объявленная вещь имеет тип указателя:

int *p; // p has type "pointer to int"

Когда вы инициализируете указатель в объявлении, например

int *p = &x;

p, это , а не с разыменовкой. Это то же самое, что и запись

int *p;
p = &x;

Оператор * связывается с объявленной вещью, не со спецификатором типа. Вы можете написать то же объявление, что и

int* p;
int       *         p;
int*p;

, и оно всегда будет анализироваться как int (*p);.

Для любого типа T выполняются следующие условия:

 T *p;          // p has type "pointer to T"
 T *p[N];       // p has type "array of pointers to T"
 T (*p)[N];     // p has type "pointer to array of T"
 T *p();        // p has type "function returning pointer to T"
 T (*p)();      // p has type "pointer to function returning T"

Сложные объявления указателя могут стать трудными для чтения, поскольку унарный * является префиксным оператором и имеет более низкий приоритет чем операторы [] и (). Например:

T *(*(*foo)())[N];

foo - указатель на функцию, возвращающую указатель на массив N-элементных указателей на T.


С вашим кодом мы имеем дело с первым случаем - мы хотим, чтобы функция readstudent изменяла содержимое существующего экземпляра struct student. И readstudent делает это, вызывая scanf для чтения значений в каждом отдельном элементе:

scanf("%s", pstu->name); 
scanf("%f", &pstu->grade1);
scanf("%f", &pstu->grade2);

Помните, что scanf ожидает, что его аргументы будут указателями - опять же, мы пытаемся изменить содержимое объекта, поэтому мы должны передать указатель на этот объект в качестве параметра.

&pstu->grade1 вычисляется по адресу grade1 члена объекта, на который pstu указывает . &pstu->grade2 соответствует адресу grade2 члена объекта, на который указывает pstu.

Так что, черт возьми, происходит с pstu->name?

Массивы особые в C. Если это не операнд операторов sizeof или унарных & или строковый литерал, используемый для инициализации массива в объявлении символов, например

char foo[] = "test";

* выражение из Тип "N-элемент массива T" будет преобразован ("распад") в выражение типа "указатель на T", а значением выражения будет адрес первого элемента массива.

Мы не будем go вникать в это, но это было преднамеренное проектное решение Ритча ie, когда он впервые создавал язык C, и оно действительно служит цели. Это также означает, что при большинстве обстоятельств массивы теряют свое «массивность», и в действительности вы имеете дело с указателем на первый элемент, а не с самим массивом. В случае вызова scanf мы передаем эквивалент из &pstu->name[0].

2 голосов
/ 03 февраля 2020

Сначала я отвечу на немного другой вопрос; но связаны.
"что делает void printstudent( TStudent stu );?"

Ответ: он берет копию аргумента, а затем что-то делает с ним. Взяв копию данных; он не может редактировать то, что было первоначально передано.

Теперь мы переходим к тому, что делает void readstudent( TStudent *pstu );:

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

Как утверждает Джеспер; Есть много других вариантов использования *; но это основы, которые вам необходимо знать, чтобы понять ЭТО использование

2 голосов
/ 03 февраля 2020

* означает разные вещи в разных контекстах . Помните, что C (и C ++) является контекстно-зависимым языком.

Когда вы объявляете переменную, например; int* foo; * означает, что foo является «указателем на int».

Когда вы пишете оператор, такой как printf("%d", *foo);, * означает разыменование указатель хранится в foo и дает мне значение int, на которое он указывает.

При использовании в объявлении функции, например void f(int* bar), это означает, что функция принимает указатель на целое число в качестве аргумента.

Это также может означать умножение, как в int something = baz * 42;

Или это может быть просто простой символ в символьном литерале без особого значения. Как const char[] foo = "bar*baz*";.

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