21

Re: Процедури в мові C

Вище ви писали, що розумієте, як написати сортування. З оформленням підпрограм я вам поміг, і зараз саме час вмикати розуміння, як саме те сортування робиться. За межами тіла функції нічого не зачіпайте (хіба що можна дати більш осмислене ім'я параметру j) — всі помилки виключно в її тілі.

22 Востаннє редагувалося wander (07.12.2019 23:21:53)

Re: Процедури в мові C

P.Y. написав:

float *A[] — не масив float, а масив вказівників на float

Це не так.

sahanvalentun написав:

> void sort(int j ...
>     for (int j ...

Redefinition.
Те, що компілятор на вас не вилаявся - врятувало лише те, що цикл надає додатковий локальний скоуп.

А якщо включити всі попереджувальні повідомлення компілятора, то можна було б побачити таке ФУ:

<source>:2:15: warning: unused parameter 'j' [-Wunused-parameter]

void sort(int j, float* S) {

              ^

23

Re: Процедури в мові C

Це не так.

Чому ж. float A[][], float *A[] та float **A — хіба не одне й те ж? Можна зробити масив вказівників на float і передати як фактичний параметр для цього формального параметра. Або можна передати йому вказівник на вказівник на float — і це теж працюватиме. Але, в будь-якому разі, це незовсім те, що доцільно використовувати для даної задачі.

24 Востаннє редагувалося wander (08.12.2019 00:02:21)

Re: Процедури в мові C

P.Y. написав:

Це не так.

Чому ж. float A[][], float *A[] та float **A — хіба не одне й те ж? Можна зробити масив вказівників на float і передати як фактичний параметр для цього формального параметра. Або можна передати йому вказівник на вказівник на float — і це теж працюватиме. Але, в будь-якому разі, це незовсім те, що доцільно використовувати для даної задачі.

Масив вказівників на float - це ось це:

float* a[10]; // масив з 10 вказівників на float

float** - це вказівник на вказівник на тип float.

#include <type_traits>
#include <iostream>
int main() {
    std::cout << std::boolalpha << std::is_same_v<float**, float*[]>;
}

Out:
Program returned: 0
Program stdout
false

25

Re: Процедури в мові C

adziri написав:
P.Y. написав:

Це не так.

Чому ж. float A[][], float *A[] та float **A — хіба не одне й те ж? Можна зробити масив вказівників на float і передати як фактичний параметр для цього формального параметра. Або можна передати йому вказівник на вказівник на float — і це теж працюватиме. Але, в будь-якому разі, це незовсім те, що доцільно використовувати для даної задачі.

Масив вказівників на float - це ось це:

float* a[10]; // масив з 10 вказівників на float

float** - це вказівник на вказівник на тип float.

#include <type_traits>
#include <iostream>
int main() {
    std::cout << std::boolalpha << std::is_same_v<float**, float*[10]>;
}

Out:
Program returned: 0
Program stdout
false

Безумовно, масив фіксованого розміру з вказівників на float та вказівник на вказівник на float — не одне й те ж. Проте, масив невизначеного розміру з вказівників на float ідентичний вказівникові на вказівник на float.

26

Re: Процедури в мові C

P.Y. написав:

Проте, масив невизначеного розміру з вказівників на float ідентичний вказівникові на вказівник на float.

Ні.
Масив != вказівник.

#include <type_traits>
#include <iostream>
int main() {
    std::cout << std::boolalpha << std::is_same_v<float**, float*[]>;
}

Out:
Program returned: 0
Program stdout
false

Все ще false.

27

Re: Процедури в мові C

У чому на практиці проявляються розбіжності між float** та float*[] (особливо якщо йдеться про формальний параметр функції)?

Подякували: leofun011

28 Востаннє редагувалося wander (08.12.2019 01:12:43)

Re: Процедури в мові C

P.Y. написав:

У чому на практиці проявляються розбіжності між float** та float*[] (особливо якщо йдеться про формальний параметр функції)?

Зпустімося на рівень нижче до float* та float[]. Зі сторони мови це два різні типи, зі сторони комп'ютера це теж різні сутності, перша це одна комірка пам'яті, що мість в собі адресу на іншу комірку, друге - це якийсь блок пам'яті.

Але так, коли ми декларуємо ф-ю, з таким параметром void foo(float a[]), то компілятор це перетворить у void foo(float* a).
Чому так?
Загалом може бути кілька варіантів, проте ми звернемось до стандарту:

http://eel.is/c++draft/dcl.fct#5 написав:

The type of a function is determined using the following rules. The type of each parameter (including function parameter packs) is determined from its own decl-specifier-seq and declarator. After determining the type of each parameter, any parameter of type “array of T” or of function type T is adjusted to be “pointer to T”. After producing the list of parameter types, any top-level cv-qualifiers modifying a parameter type are deleted when forming the function type. The resulting list of transformed parameter types and the presence or absence of the ellipsis or a function parameter pack is the function's parameter-type-list. [ Note: This transformation does not affect the types of the parameters. For example, int(*)(const int p, decltype(p)*) and int(*)(int, const int*) are identical types. — end note ]

І основний винуватець Array-to-pointer conversion:

http://eel.is/c++draft/conv.array написав:

An lvalue or rvalue of type “array of N T” or “array of unknown bound of T” can be converted to a prvalue of type “pointer to T”. The temporary materialization conversion ([conv.rval]) is applied. The result is a pointer to the first element of the array.

P.S. - лиш зараз помітив, що тема по С, а не по С++, проте критичної різниці у цьому питанні я думаю нема.

Подякували: leofun01, P.Y.2

29

Re: Процедури в мові C

P.Y. написав:

У чому на практиці проявляються розбіжності між float** та float*[] (особливо якщо йдеться про формальний параметр функції)?

Стандарт каже про те, що відбувається, але не каже, чому. Спробую пояснити. Коли у нас є простий тип - int, float, вказівник - ми точно знаємо його розмір у пам'яті. Функція, що приймає параметр типу int, точно знає, де саме буде знаходитися цей int. Наприклад,

int a; //a - це байти пам'яті, наприклад, 1000, 1001, 1002, 1003 (якщо у нас int займає 4 байти)
int *b = &a; //b - це байти 1004-1007, всередині яких у якійсь формі записано посилання на 1000
int *c = new int; //c - це байти 1008-1011, значення яких буде прописано під час виконання

Це дозволяє легко написати, наприклад, функцію, що приймає int чи int * - треба просто домовитися, в які саме 4 байти треба покласти цей int, і надалі той, хто викликає, може вже не думати про того, кого він викликає; точно відомо, що змінні не переплутаються.
А от масив у загальному виді не має одного розміру. int a[10] - це не те саме, що int a[12]; відтак, передати його у функцію просто так не вийде. Вийде лише посилання на нього. Тому в C/C++ масив у більшості виразів автоматично перетворюється на посилання. Посилання куди? На його ж власний початковий елемент: a==&a[0]. Причому саме елемент:

int arr[10]; //байти 11000-11039
int *ptr = arr; //ptr - це байти 11040-11043, що містять в собі посилання на 11000
int *ptr1 = &arr; //в C так можна, а в C++ треба додатково перетворити типи, бо посилання на масив - не те саме, що посилання на елемент

І тільки так працюватиме арифметика вказівників, бо для того, щоб знайти наступний елемент, треба знати розмір поточного: 

a[3]==*(a+3) //3 чого саме?

А тепер - до вашого питання. Це зовсім різні речі:
float (*a)[10] - вказівник на масив чи на масив масивів float. Зокрема, a[1] буде зсунутим на 10*sizeof(float), як і a+1. Так можна адресуватися до float x[5][10]  чи float x[50][10].
float **a - вказівник на вказівник (чи масив) вказівників (чи масивів). Тобто, щоб скористатися ним, нам потрібні не лише елементи float у пам'яті, а й додатковий масив вказівників на рядки масиву. Запишу статично:

  float **pparr;
  float *(parr[10]);//масив з 20 float *
  pparr = parr;//так можна, parr == &parr[0];
  float arr[10][20]; //масив float
  for(int i=0;i<10;++i)
     parr[i]=arr[i];//arr[i] тут перетворюється на вказівник на свій нульовий елемент, тобто на &float[10]

І от лише тепер pparr[x][y] буде означати те саме, що й arr[x][y].

Подякували: leofun011

30

Re: Процедури в мові C

koala, це все мені відомо — моє питання стосувалось лише того випадку, коли квадратні дужки при описі змінної (чи, точніше, параметра функції) лишаються порожніми. Гадаю, пан adziri дав вичерпну відповідь.

31 Востаннє редагувалося P.Y. (08.12.2019 12:07:47)

Re: Процедури в мові C

Але так, коли ми декларуємо ф-ю, з таким параметром void foo(float a[]), то компілятор це перетворить у void foo(float* a).

Іншими словами, формальний параметр виду float a[] називати масивом також не слід? (Питання стосується не того, чим він є по суті — про ідентичність [] та * у списку параметрів я сам писав вище — а виключно термінології, прийнятої серед програмістів. Поки що я дотримувався думки, що формальний параметр-вказівник можна називати як вказіником, так і масивом, оскільки він може використовуватись для прийому фактичних параметрів обох типів).

Подякували: leofun011

32

Re: Процедури в мові C

https://www.google.com/search?client=fi … amp;uact=5

33

Re: Процедури в мові C

ur_naz написав:

https://www.google.com/search
?client=firefox-b-d
&sxsrf=ACYBGNScEVtjePf-B1E1R5M9q5NZr50fvA%3A1575797619436
&ei=c8PsXdGJGs3KrgTdw7fwDA
&q=%D0%B1%D1%83%D0%BB%D1%8C%D0%B1%D0%B0%D1%88%D0%BA%D0%BE%D0%B2%D0%B5+%D1%81%D0%BE%D1%80%D1%82%D1%83%D0%B2%D0%B0%D0%BD%D0%BD%D1%8F+%D0%BC%D0%BE%D0%B2%D0%B0+%D0%A1
&oq=%D0%B1%D1%83%D0%BB%D1%8C%D0%B1%D0%B0%D1%88%D0%BA%D0%BE%D0%B2%D0%B5+%D1%81%D0%BE%D1%80%D1%82%D1%83%D0%B2%D0%B0%D0%BD%D0%BD%D1%8F+%D0%BC%D0%BE%D0%B2%D0%B0+%D0%A1
&gs_l=psy-ab.3...12470.17930..19254...0.2..0.139.946.0j8......0....1..gws-wiz.......0i71j35i304i39.sWAxScLexBU
&ved=0ahUKEwiRj-DK36XmAhVNpYsKHd3hDc4Q4dUDCAo
&uact=5

Ваше посилання не відкриється в інших користувачів, бо ви запхали купу сміття в URL.

34

Re: Процедури в мові C

У мене відкрилось — гуглопошук бульбашкового сортування. Параметра q було б достатньо, звичайно.

Подякували: leofun011

35

Re: Процедури в мові C

Все , я помітив помилку , дуже дякую а допомогу

36

Re: Процедури в мові C

Тримайте ClearUrls або окремого писа.

Подякували: leofun011

37 Востаннє редагувалося wander (08.12.2019 21:06:48)

Re: Процедури в мові C

P.Y. написав:

Іншими словами, формальний параметр виду float a[] називати масивом також не слід?

Якщо ми говоримо про параметр виду float a[] як параметр функції - то так, це не масив.
Загалом все доволі просто, ми не можемо в C просто передати по значенню блок пам'яті (масив) у функцію, можемо лише передати як вказівник на перший елемент масиву, або як посилання/вказівник на масив (так це різні речі, у яких є свій синтаксис), тож float a[] можна спокійно перетворити у float* a, бо це не більш ніж синтаксичний цукор.

Тут також логічно виникає питання чому не можна передати по значенню масив?
Ну, знаєте, мова С була мовою компромісів, тому це було зроблено таким чином, щоб зберегти синтаксичну та смислову сумісність з мовою B - це мова, яка була розроблена в тій самій Bell Labs, тим самим Деннісом Рітчі та ще одним його колегою (і ця мова частково стала прабатьком С), і в якій масиви реалізовувались як фізичні вказівники і завжди передавалися "по вказівнику".

Про це розповідав сам Денніс Рітчі, у статті The Development of the C Language
Дивись секції "Critique" та "Embryonic C".

Подякували: leofun011