Передача по ссылке или по значению? Передача параметров по ссылке и по значению Передача параметров в функции в языке с.

Итак, пусть Factorial(n) – это функция вычисления факториала числаn . Тогда, учитывая, что нам «известен» факториал 1 – это 1, можно построить следующую цепочку:

Factorial(4)=Factorial(3)*4

Factorial(3)=Factorial(2)*3

Factorial(2)=Factorial(1)*2

Но, если бы у нас не было терминального условия, что при n=1 функцияFactorial должна возвратить 1, то такая теоретически цепочка никогда бы не завершилась, и это могло ошибкойCall Stack Overflow – переполнение стека вызова. Чтобы понять что такое стек вызова и как он может переполниться, давайте посмотрим на рекурсивную реализацию нашей функции:

Function factorial(n: Integer): LongInt;

If n=1 Then

Factorial:=Factorial(n-1)*n;

End;

Как мы видим, для того, чтобы цепочка работала корректно, необходимо перед каждым очередным вызовом функции самой себя, где-то сохранять все локальные переменные, чтобы затем при обратном «разворачивании» цепочки результат был правильным (вычисленное значение факториала от n-1 умножилось наn ). В нашем случае – при каждом вызове функцииfactorial из самой себя, должны сохраняться все значения переменнойn . Область, в которой сохраняются локальные переменные функции при рекурсивном обращении к самой себе, называется стеком вызова. Разумеется, этот стек не бесконечный и при неправильном построении рекурсивных вызовов может быть исчерпан. Конечность итераций нашего примера гарантируется тем, что приn=1 вызов функции прекратится.

Передача параметров по значению и по ссылке

До сих пор мы не могли изменить в подпрограмме значение фактического параметра (т.е. того параметра, который указывается при вызове подпрограммы), а в некоторых прикладных задачах это было бы удобным. Вспомним процедуруVal , которая изменяет значение сразу двух ее фактических параметров: первый – это тот параметр, куда будет записано преобразованное значение строковой переменной, а второй – это параметрCode , куда помещается номер ошибочного символа, в случае неудачи при преобразовании типа. Т.е. все-таки существует механизм, при помощи которого подпрограмма может изменять фактические параметры. Это возможно благодаря различным способам передачи параметров. Давайте разберемся детально в этих способах.

Программирование на языке Pascal

Передача параметров по значению

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

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

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

Передача параметров по ссылке

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

Для того, чтобы указать, что какой-либо аргумент необходимо передать по ссылке, перед его объявлением добавляется ключевое словоvar :

Procedure getTwoRandom(var n1, n2:Integer; range: Integer);

n1:=random(range);

n2:=random(range); end ;

var rand1, rand2: Integer;

Begin getTwoRandom(rand1,rand2,10); WriteLn(rand1); WriteLn(rand2);

End.

В этом примере, в процедуру getTwoRandom в качестве фактических параметров передаются ссылки на две переменные:rand1 иrand2 . Третий фактический параметр (10) передается по значению. Процедура записывает посредством формальных параметров

Заранее извиняюсь за пафосную аннотацию про "расстановку точек", но надо же как-то завлечь вас в статью)) Со своей стороны постараюсь, чтобы аннотация все же оправдывала ваши ожидания.

Вкратце о чем речь

Все это и так знают, но все же в начале напомню, как в 1С могут передаваться параметры метода. Передаваться они могут "по ссылке" и "по значению". В первом случае, мы передаем в метод то же самое значение, что и в точке вызова, а во втором - его копию.

По умолчанию в 1С аргументы передаются по ссылке, и изменение параметра внутри метода будет видно извне метода. Здесь дальнейшее понимание вопроса зависит от того, что именно вы понимаете под словом "изменение параметра". Так вот, имеется в виду повторное присваивание и ничего более. Причем, присваивание может быть неявным, например вызовом метода платформы, который возвращает что-то в выходном параметре.

Но если мы не хотим, чтобы наш параметр передавался по ссылке, то мы можем указать перед параметром ключевое слово Знач.

Процедура ПоЗначению(Знач Параметр) Параметр = 2; КонецПроцедуры Параметр = 1; ПоЗначению(Параметр); Сообщить(Параметр); // выведет 1

Все работает, как обещано - изменение (а правильнее сказать "замена") значения параметра не приводит к изменению значения вне метода.

Ну а в чем прикол-то?

Интересные моменты начинаются, когда мы начинаем передавать в качестве параметров не примитивные типы (строки, числа, даты и т.п.), а объекты. Вот тут-то и всплывают такие понятия, как "мелкая" и "глубокая" копия объекта, а также указатели (не в терминах C++, а как абстрактные дескрипторы (handles)).

При передаче объекта (например, ТаблицыЗначений) по ссылке, мы передаем само значение указателя (некий handle), который в памяти платформы "держит" объект. При передаче по значению платформа сделает копию этого указателя.

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

Процедура ОбработатьЗначение(Параметр) Параметр = Новый Массив; КонецПроцедуры Таблица = Новый ТаблицаЗначений; ОбработатьЗначение(Таблица); Сообщить(ТипЗнч(Таблица)); // выведет Массив

Если же, мы передадим объект по значению, то в точке вызова наша ТаблицаЗначений не пропадет.

Содержимое объекта и его состояние

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

Процедура ОбработатьЗначение(Параметр) Параметр.Очистить(); КонецПроцедуры Таблица = Новый ТаблицаЗначений; Таблица.Добавить(); ОбработатьЗначение(Таблица); Сообщить(Таблица.Количество()); // выведет 0

При передаче объектов в методы платформа оперирует указателями (условными, не прямыми аналогами из C++). Если объект передается по ссылке, то ячейка памяти виртуальной машины 1С, в которой лежит данный объект, может быть перезаписана другим объектом. Если объект передается по значению, то указатель копируется и перезапись объекта не приводит к перезаписи ячейки памяти с исходным объектом.

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

И это верно всегда, за исключением...

Клиент-серверное взаимодействие

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

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

  • Явное объявление намерений программиста. Глядя на сигнатуру метода, можно четко сказать, какие параметры входные, а какие выходные. Такой код легче читать и сопровождать
  • Для того, чтобы изменение на сервере параметра "по ссылке" было видно в точке вызова на клиенте, п араметры, передаваемые на сервер по ссылке, платформа обязательно будет сама возвращать на клиента, чтобы обеспечить поведение, описанное в начале статьи. Если параметр не нужно возвращать, то будет перерасход трафика. Для оптимизации обмена данными параметры, значения которых нам не нужны на выходе, нужно помечать словом Знач.

Здесь примечателен второй пункт. Для оптимизации трафика платформа не будет возвращать значение параметра на клиент, если параметр помечен словом Знач. Все это замечательно, но приводит к интересному эффекту.

Как я уже говорил, при передаче объекта на сервер происходит сериализация, т.е. выполняется "глубокая" копия объекта. А при наличии слова Знач объект не поедет с сервера обратно на клиента. Складываем эти два факта и получаем следующее:

&НаСервере Процедура ПоСсылке(Параметр) Параметр.Очистить(); КонецПроцедуры &НаСервере Процедура ПоЗначению(Знач Параметр) Параметр.Очистить(); КонецПроцедуры &НаКлиенте Процедура ПоЗначениюКлиент(Знач Параметр) Параметр.Очистить(); КонецПроцедуры &НаКлиенте Процедура ПроверитьЗнач() Список1= Новый СписокЗначений; Список1.Добавить("привет"); Список2 = Список1.Скопировать(); Список3 = Список1.Скопировать(); // объект копируется полностью, // передается на сервер, потом возвращается. // очистка списка видна в точке вызова ПоСсылке(Список1); // объект копируется полностью, // передается на сервер. Назад не возвращается. // Очистка списка НЕ ВИДНА в точке вызова ПоЗначению(Список2); // копируется только указатель объекта // очистка списка видна в точке вызова ПоЗначениюКлиент(Список3); Сообщить(Список1.Количество()); Сообщить(Список2.Количество()); Сообщить(Список3.Количество()); КонецПроцедуры

Резюме

Если вкратце, то резюмировать можно следующим образом:

  • Передача по ссылке позволяет "затереть" объект совсем другим объектом
  • Передача по значению не позволяет "затереть" объект, но изменения внутреннего состояния объекта будут видны, т.к. идет работа с одним и тем же экземпляром объекта
  • При серверном вызове работа идет с РАЗНЫМИ экземлярами объекта, т.к. выполнялось глубокое копирование. Ключевое слово Знач запретит копирование серверного экземпляра обратно в клиентский, и изменение внутреннего состояния объекта на сервере не приведет к аналогичному изменению на клиенте.

Надеюсь, что этот несложный перечень правил позволит вам легче решать споры с коллегами насчет передачи параметров "по значению" и "по ссылке"

Программирование методов с использованием строк

Цель лабораторной работы : изучить методы в языке C#, правила работы с символьными данными и с компонентом ListBox. Написать программу для работы со строками.

Методы

Метод – это элемент класса, который содержит программный код. Метод имеет следующую структуру:

[атрибуты] [спецификторы] тип имя ([параметры])

Тело метода;

Атрибуты – это особые указания компилятору на свойства метода. Атрибуты используются редко.

Спецификаторы – это ключевые слова, предназначенные для разных целей, например:

· Определяющие доступность метода для других классов:

o private – метод будет доступен только внутри этого класса

o protected – метод будет доступен также дочерним классам

o public – метод будет доступен любому другому классу, который может получить доступ к данному классу

· Указывающие доступность метода без создания класса

· Задающие тип

Тип определяет результат, который возвращает метод: это может быть любой тип, доступный в C#, а также ключевое слово void, если результат не требуется.

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

Параметры – это список переменных, которые можно передавать в метод при вызове. Каждый параметр состоит из типа и названия переменной. Параметры разделяются запятой.

Тело метода – это обычный программный код, за исключением того, что он не может содержать определения других методов, классов, пространств имён и т. д. Если метод должен возвращать какой-то результат, то обязательно в конце должно присутствовать ключевое слово return с возвращаемым значением. Если возвращение результатов не нужно, то использование ключевого слова return не обязательно, хотя и допускается.

Пример метода, вычисляющего выражение:

public double Calc(double a, double b, double c)

return Math.Sin(a) * Math.Cos(b);

double k = Math.Tan(a * b);

return k * Math.Exp(c / k);

Перегрузка методов

Язык C# позволяет создавать несколько методов с одинаковыми именами, но разными параметрами. Компилятор автоматически подберёт наиболее подходящий метод при построении программы. Например, можно написать два отдельных метода возведения числа в степень: для целых чисел будет применяться один алгоритм, а для вещественных – другой:

///

/// Вычисление X в степени Y для целых чисел

///

private int Pow(int X, int Y)

///

/// Вычисление X в степени Y для вещественных чисел

///

private double Pow(double X, double Y)

return Math.Exp(Y * Math.Log(Math.Abs(X)));

else if (Y == 0)

Вызывается такой код одинаково, разница лишь в параметрах – в первом случае компилятор вызовет метод Pow с целочисленными параметрами, а во втором – с вещественными:

Параметры по умолчанию

Язык C# начиная с версии 4.0 (Visual Studio 2010) позволяет задавать некоторым параметрам значения по умолчанию – так, чтобы при вызове метода можно было опускать часть параметров. Для этого при реализации метода нужным параметрам следует присвоить значение прямо в списке параметров:

private void GetData(int Number, int Optional= 5 )

Console.WriteLine("Number: {0}", Number);

Console.WriteLine("Optional: {0}", Optional);

В этом случае вызывать метод можно следующим образом:

GetData(10, 20);

В первом случае параметр Optional будет равен 20, так как он явно задан, а во втором будет равен 5, т.к. явно он не задан и компилятор берёт значение по умолчанию.

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

private void GetData(int Optional= 5 , int Number)

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

private void Calc(int Number)

Видно, что внутри метода происходит изменение переменной Number, которая была передана как параметр. Попробуем вызвать метод:

Console.WriteLine(n);

На экране появится число 1, то есть, не смотря на изменение переменной в методе Calc, значение переменной в главной программе не изменилось. Это связано с тем, что при вызове метода создаётся копия переданной переменной, именно её изменяет метод. При завершении метода значение копий теряется. Такой способ передачи параметра называется передачей по значению .

Чтобы метод мог изменять переданную ему переменную, её следует передавать с ключевым словом ref – оно должно быть как в сигнатуре метода, так и при вызове:

private void Calc(ref int Number)

Console.WriteLine(n);

В этом случае на экране появится число 10: изменение значения в методе сказалось и на главной программе. Такая передача метода называется передачей по ссылке , т.е. передаётся уже не копия, а ссылка на реальную переменную в памяти.

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

private void Calc(out int Number)

int n; // Ничего не присваиваем!

Тип данных string

Для хранения строк в языке C# используется тип string. Для того, чтобы объявить (и, как правило, сразу инициализировать) строковую переменную, можно написать следующий код:

string a = "Текст";

string b = "строки";

Над строками можно выполнять операцию сложения – в этом случае текст одной строки будет добавлен к тексту другой:

string c = a + " " + b; // Результат: Текст строки

Тип string на самом деле является псевдонимом для класса String, с помощью которого над строками можно выполнять ряд более сложных операций. Например, метод IndexOf может осуществлять поиск подстроки в строке, а метод Substring возвращает часть строки указанной длины, начиная с указанной позиции:

string a = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

int index = a.IndexOf("OP"); // Результат: 14 (счёт с 0)

string b = a.Substring(3, 5); // Результат: DEFGH

Если требуется добавить в строку специальные символы, это можно сделать с помощью escape-последовательностей, начинающихся с обратного слэша:

Компонент ListBox

Компонент ListBox представляет собой список, элементы которого выбираются при помощи клавиатуры или мыши. Список элементов задается свойством Items . Items – это элемент, который имеет свои свойства и свои методы. Методы Add , RemoveAt и Insert используются для добавления, удаления и вставки элементов.

Объект Items хранит объекты, находящиеся в списке. Объект может быть любым классом – данные класса преобразуются для отображения в строковое представление методом ToString. В нашем случае в качестве объекта будут выступать строки. Однако, поскольку объект Items хранит объекты, приведённые к типу object, перед использованием необходимо привести их обратно к изначальному типу, в нашем случае string:

string a = (string)listBox1.Items;

Для определения номера выделенного элемента используется свойство SelectedIndex .

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

В языках программирования имеется два основных способа передачи параметров подпрограмме. Первый из них – передача по значению . При его применении в формальный параметр подпрограммы копируется значение фактического параметра (аргумента). В таком случае изменения формального параметра на фактический аргумент не влияют.

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

В языке С есть только один способ сопоставления фактических и формальных параметров – передача по значению (передачи параметров по ссылке есть в С++). В Паскале есть передача по значенияю и по ссылке. Бывают и другие методы (в Fortran – копирование-восстановление, в Algol – передача по имени).

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

Метод передачи по значению реализуется следующим способом:

    формальный параметр рассматривается как локальная переменная, так что память для нее выделяется в записи активации вызываемой функции, т.е. в стеке;

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

19.2. Передача параметров в функции в языке с

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

void f(int k) { k = -k; } void main() { int i = 1; f(i); printf("i = %d\n", i); // результат: i = 1 }

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

19.3. Передача указателей в функции

А что делать, если функция должна изменить значение фактического параметра? Самый очевидный, но не самый лучший, способ – заменить такой параметр глобальной переменной. Минус – повышение шансов ошибиться из-за неучтенных побочных эффектов при вызове функций.

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

Указатель передается функции так, как и любой другой аргумент – по значению. Понятно, что при передаче адреса параметр следует объявлять как один из типов указателей.

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

Задача. Написать функцию для замены местами значений двух переменных и вызвать ее из функции main ().

void swap(int *pa, int *pb) { // параметры-указатели

*pa = *pb; // занести b в a

*pb = temp; // занести a в b

void main(void) {

int i = 10, j = 20;

printf("i и j перед обменом значениями: %d %d\n", i, j);

swap(&i, &j); // передаем адреса переменных i и j

Функция swap() может выполнять обмен значениями двух переменных, на которые указывают pa и pb, потому что в функцию передаются адреса переменных, а не их значения. Внутри функции, используя стандартные операции с указателями, можно получить доступ к содержимому переменных и провести обмен их значений.

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

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

void main(void) {

int i = 10, j = 20;

int *pi = &i, *pj = &j;

printf("i и j перед обменом значениями: %d %d\n", i, j);

swap(pi, pj); // передаем адреса переменных i и j

printf("i и j после обмена значениями: %d %d\n", i, j);

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

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

Задача. Написать две функции для вычисления суммы двух отрицательных чисел и их вызов из функции main (). Исходные данные должны вводиться в функции main (). Первая функция должна возвращать заданную величину. Во второй функции обеспечить контроль правильности исходных данных. Функция, кроме вычисления заданной величины, должна возвращать признак правильности исходных данных.

int sum1(int a, int b) {

int sum2(int a, int b, int *sum) {

if (a >= 0 || b >= 0)

return 0; // признак неверных данных

return 1; // признак правильных данных

void main(void) {

scanf(“%d %d”, &x, &y);

printf("Сумма 1 = %d\n", sum1(x,y));

if (sum2(x,y,&s) == 1)

printf("Сумма 2 = %d\n", s);

printf("Неверные данные!\n");

 
Статьи по теме:
Размеры huawei mediapad m3 8
В начале сентября был анонсирован планшет под названием Huawei MediaPad M3. Это событие произошло в рамках выставки IFA 2016. Аппарат относится к премиальному классу, на что намекают буквально все параметры гаджета.Внешний вид и эргономикаМеталлический ко
Прошивка или перепрошивка телефона Huawei Honor Обновления операционки для huawei honor 5a
Смартфон Huawei Honor 5A оценён в 3 баллов по шкале производительности и работает под управлением ОС Android 5.1. Этот смартфон - достаточно производительный.. Узнайте характеристики, как получить рут и сделать сброс настроек. К слову, у нас можно скачат
Print Friendly & PDF — сохранить веб-страницу в PDF в удобном для чтения виде Преобразовать веб страницу в pdf онлайн
PrintFriendly - онлайн сервис и расширение для браузеров для сохранения веб страниц в удобном для чтения виде. Название этого портала можно перевести на русский язык, примерно так: «распечатать в удобном виде».Многим пользователям, наверное, приходилось х
Создаем крутую аву для вашего канала
Наталья Комарова , 28.05.2009 (25.03.2018 ) Когда читаешь форум или блог, запоминаешь авторов сообщений по нику и … по картинке пользователя, так называемому аватару. Последний играет одну из главных ролей в восприятии пользователей в интернете. Несмот