C Лучший способ изменения значений в функции

1

Я разработал с Java с нескольких лет, теперь я хотел изучить C, и я заметил несколько отличий.

В Java, когда я хочу что-то вернуть из функции (например, читать пользовательский ввод, я бы написал

String s;
s = new Scanner(System.in).nextLine(); 

В CI будет писать

char s[20];
scanf("%d", name);

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

Я заметил это со многими функциями C. Когда я пишу свои собственные функции, должен ли я делать это так, как будто я привык к Java, или должен использовать стиль C для назначения/возврата значений из функций?

  • 1
    c код пытается использовать неинициализированную переменную. попробуйте char c и scanf("%d", &c)
  • 0
    Этот вопрос слишком широк. Для вас будет полезно начать работать с примерами из учебника или онлайн-учебника.
Показать ещё 4 комментария
Теги:
function
return
assign

6 ответов

6
Лучший ответ

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

Если вы хотите вернуть статус ошибки из своей функции, вы ограничены (1) возвратом ее в статической переменной (небезопасной для параллелизма), (2) возвращает ее как возвращаемое значение или (3) возвращает ее в отдельный объект "ошибка". Иногда варианты (2) и (3) объединяются.

В результате вы часто увидите API C, которые могут выходить из строя как scanf, когда статус возвращается как возвращаемое значение, а остальные значения изменяются с помощью указателей.

if (scanf("%d%d", &i, &j) == 2) {
    ... // Got 2 numbers
} else {
    fprintf(stderr, "Wrong input! Expected two numbers.");
}

API, которые не терпят неудачу (скажем, isalpha(ch) или tolower(ch)) возвращают свое значение напрямую.

  • 0
    Но что я должен использовать, когда пишу свои собственные функции? Или невозможно дать общий ответ на вопрос?
  • 1
    @ danielr1996 Когда вы пишете свои собственные функции, которые могут давать сбой и должны сообщать о состоянии, используйте стиль scanf . Все функции ввода / вывода находятся в этой категории. Когда вы пишете свои собственные функции, которые не работают, потому что они каким-то образом обрабатывают все входные значения, используйте стиль tolower .
Показать ещё 3 комментария
3

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

Еще одна причина, как @chmike указывает, что если вы хотите, чтобы вернуть строку в этом случае вы должны использовать malloc() (C эквивалент Java new). Но C не имеет сбор мусора, поэтому его нужно явно освободить. Из-за этого часто "плохой этикет" передает другим внешним функциям указатель, который должен быть освобожден. Кто скажет, что программист даже хочет, чтобы память была выделена в кучу? Использование кучи дорого, и часто вся причина, по которой программист использует C, состоит в том, что данная программа должна быть быстрой.

Если программист, который вызывает вашу функцию, хотел использовать кучу, они сделали бы это в случае scanf, вызвав сами malloc(), а затем давая scanf указатель на новую ячейку памяти, которую вы могли бы заполнить своей функцией. Таким образом, вызывающий scanf может отслеживать всю выделенную память, а какая - нет.

Когда API, действительно возвращают выделенные указатели, как правило, потому что нельзя избежать, и они явно задокументировать и, как правило, обеспечивают функцию, чтобы освободить память должным образом, как и в случае с POSIX регулярных выражений regfree или Глоб globfree. Таким образом, если библиотека выделяет память, библиотека также освобождает ее.

3

Если в Java есть такая вещь, которая называется сборщиком мусора, то в C вы отвечаете за управление памятью.

Java "дайте мне".

String s = MyFunction();    // Give it to me.

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

C "там".

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

Также ваш код должен быть более чем таким:

char s[80];         // Static memory allocation, we hope it is enough.
scanf("%79s", s);   // Now we retrieve data. (Put it there).

Или что-то вроде этого:

char* s = malloc(80 * sizeof(char)); // Dynamic memory allocation, we hope it is enough.
scanf("%79s", s);                    // Now we retrieve data. (Put it there).

// Do stuff with data 

free(s);                             // Be responsible.

Итак, да, вы должны использовать стиль C для назначения/возвращения значений (это имеет смысл, ведь вы все делаете C).

Еще один момент, если вы закодируете его как Java здесь, что у вас будет:

char* MyTest() {
    char* a = "pipo";
    return a;           // Big fail, 'a' is local and will not exist anymore after return.
}

char* MyOtherTest() {
    char a[]= "pipo";
    char* b = malloc(80 * sizeof(char));
    strncpy(b, a, sizeof(a));
    return b;           // My bet this one will never be freed.
}

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

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

// Nice and simple.
int MyAdd(int a, int b) { return a+b; }

// Why ? just Why ?
void MyAdd(int a, int b, int* r) { *r = a+b; }
  • 0
    Во многих случаях вызывающий должен отвечать за распределение, вызывающий за освобождение. Сравните getline (POSIX.1-2008) и fgets и fgets , из-за чего программа может глючить на неожиданно длинных строках (или должна иметь сложную логику для их правильной обработки, обычно не реализуемой ...).
2

ну, вы должны знать основное различие между C и Java,

Java - это Object Oriented programming а C - нет.

в Java почти все Object (кроме primitives).

То, как вы называете Scanner class, очень плохо подходит, вы должны сделать это как:

Scanner scanner= new Scanner(System.in);

затем вызовите его, чтобы получить строку:

String s = scanner.nextLine();

Причина в том, что каждый раз, когда вы вызываете new Scanner(System.in) вы назначаете свой сканер на тот же Object чтобы он начинался с самого начала значения System.in, вы продолжаете получать один и тот же ввод снова и снова.

однако в C это не так. так как там нет объекта, который вы вызываете.

Так что коротко просто помните, что в java все reference (кроме примитивных значений cource) на что-то еще, а в C это не так

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

по этой причине обычно лучше заставить метод возвращать что-то, а не отправлять данные, которые необходимо установить как параметр

например, посмотрите на сценарий:

 1. Example 1

public void addOne(int i){
   i++;
}


public static void main(String... args){
   int i=0;
   addOne(i);
   System.out.println(i);
}

output= 0;

 2. Example 2


public int addOne(int i){
  return i++;
}

//output = 1


public static void main(String... args){
   int i=0;
   i = addOne(i);
   System.out.println(i);
}
  • 0
    Спасибо за хорошее объяснение, но это не то, что я имел в виду, я знаю, что java - это OO, а C - нет, разница, о которой я спрашиваю, заключается в том, следует ли присваивать значения с помощью value=function() (функция возвращает значение, и я могу присвоить это значение, как в Java) или с function(value) (функции записывает свои значения непосредственно в аргумент, конечно, аргумент должен быть указателем, как в с)
  • 0
    @ danielr1996 я только что отредактировал свой ответ, надеюсь, это поможет мне
Показать ещё 4 комментария
2

Java объектно ориентирована и возвращает объект, который является указателем на объект.

В C вы также можете вернуть указатель, но не массив. Код C, который вы показываете, неверен, потому что s, объявленный как указатель на символ, не инициализируется. Scanf ожидает указатель на предварительно выделенный буфер в качестве второго аргумента. Он будет записывать входную строку в буфер. Наверное, вы имели в виду scanf("%d", s); ,

Код C, который вы написали, не будет работать, потому что s не указывает на выделенный буфер для хранения символов. Вы можете определить s как char s[1024]; в этом случае s эквивалентно указателю на буфер размером 1024 символа.

Вы также можете определить sa char *s = malloc(1024*sizeof(char)); который будет динамически распределять буфер для s. Затем вам нужно явно вызывать бесплатное освобождение буфера, когда он вам больше не нужен. Это связано с тем, что C не имеет сборщика мусора, как в Java.

  • 0
    Спасибо, только что забыл, отредактировал мой вопрос на char s[20]
-2

Чтобы прочитать массив символов в C, сначала нужно инициализировать фактический массив с ограничением того, как долго это может быть. Что-то вроде этого:

char str[20];
scanf("%s", &str);

Обратите внимание на строку формата% s, в которой scanf сканирует строку,% d - для целых чисел.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню