Конструктор копирования си шарп

Обновлено: 27.04.2024

Мы обсудили введение в конструкторы в C ++ . В этом посте обсуждается конструктор копирования.

Что такое конструктор копирования?
Конструктор копирования — это функция-член, которая инициализирует объект, используя другой объект того же класса. Конструктор копирования имеет следующий общий прототип функции:

Ниже приведен простой пример конструктора копирования.

using namespace std;

Point( int x1, int y1)

Point( const Point &p2)

Point p1(10, 15); // Здесь вызывается нормальный конструктор

Point p2 = p1; // Копируем конструктор здесь

// Давайте получим доступ к значениям, назначенным конструкторами

Когда вызывается конструктор копирования?
В C ++ конструктор копирования может вызываться в следующих случаях:
1. Когда объект класса возвращается по значению.
2. Когда объект класса передается (в функцию) по значению в качестве аргумента.
3. Когда объект строится на основе другого объекта того же класса.
4. Когда компилятор генерирует временный объект.

Когда нужен определяемый пользователем конструктор копирования?
Если мы не определяем наш собственный конструктор копирования, компилятор C ++ создает конструктор копирования по умолчанию для каждого класса, который выполняет поэлементное копирование между объектами. В общем, созданный компилятором конструктор копирования работает нормально. Нам нужно определять наш собственный конструктор копирования, только если у объекта есть указатели или какое-либо распределение ресурсов во время выполнения, например, дескриптор файла, сетевое соединение и т. Д.

Конструктор по умолчанию делает только поверхностное копирование.


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


Конструктор копирования против Оператора присваивания
Какой из следующих двух операторов вызывает конструктор копирования, а какой — оператор присваивания?

MyClass t3 = t1; // ----> (1)

Конструктор копирования вызывается, когда новый объект создается из существующего объекта, как копия существующего объекта. Оператор присваивания вызывается, когда уже инициализированному объекту присваивается новое значение из другого существующего объекта. В приведенном выше примере (1) вызывает конструктор копирования и (2) вызывает оператор присваивания. Смотрите это для более подробной информации.

Написать пример класса, где нужен конструктор копирования?
Ниже приводится полная программа на C ++, демонстрирующая использование конструктора Copy. В следующем классе String мы должны написать конструктор копирования.

using namespace std;

String( const char *str = NULL); // конструктор

String( const String&); // копировать конструктор

void print() < cout // Функция для печати строки

void change( const char *); // Функция для изменения

String::String( const char *str)

size = strlen (str);

s = new char [size+1];

void String::change( const char *str)

size = strlen (str);

s = new char [size+1];

String::String( const String& old_str)

s = new char [size+1];

strcpy (s, old_str.s);

String str1( "GeeksQuiz" );

String str2 = str1;

str1.print(); // что напечатано?

str1.print(); // что печатается сейчас?

В чем будет проблема, если мы удалим конструктор копирования из кода выше?
Если мы удалим конструктор копирования из вышеуказанной программы, мы не получим ожидаемый результат. Изменения, внесенные в str2, отражаются и в str1, чего никогда не ожидается.

using namespace std;

String( const char *str = NULL); // конструктор

void change( const char *); // Функция для изменения

String::String( const char *str)

size = strlen (str);

s = new char [size+1];

void String::change( const char *str)

size = strlen (str);

s = new char [size+1];

String str1( "GeeksQuiz" );

String str2 = str1;

str1.print(); // что напечатано?

str1.print(); // что печатается сейчас?

Можем ли мы сделать конструктор копирования закрытым?
Да, конструктор копирования может быть закрытым. Когда мы делаем конструктор копирования закрытым для класса, объекты этого класса становятся недоступными для копирования. Это особенно полезно, когда у нашего класса есть указатели или динамически распределяемые ресурсы. В таких ситуациях мы можем либо написать собственный конструктор копирования, как в примере выше String, либо создать личный конструктор копирования, чтобы пользователи получали ошибки компилятора, а не сюрпризы во время выполнения.

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

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

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

Создает экземпляр String, имеющий то же значение, что и указанный экземпляр String.

Параметры

Строка для копирования.

Возвращаемое значение

Новая строка с тем же значением, что и str .

Исключения

str имеет значение null .

Комментарии

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

В зависимости от того, почему вы хотите вызвать Copy метод, существует ряд альтернативных вариантов:

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

В этом случае вызов Copy метода для создания новой строки перед вызовом Substring метода ненужно создает новый экземпляр строки.

Если вы хотите создать изменяемый буфер с тем же содержимым, что и исходная строка, вызовите String.ToCharArray конструктор или StringBuilder.StringBuilder(String) Пример:

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

Remark: I wrote "deep" within brackets because I thought it was irrelevant. Apparently others disagree, so I asked whether a copy constructor/operator/function needs to make clear which copy variant it implements.

11 Answers 11

You can now choose to sort by Trending, which boosts votes that have happened recently, helping to surface more up-to-date answers.

Trending is based off of the highest score sort and falls back to it if no posts are trending.

You should not derive from ICloneable .

Instead, you should define your own IDeepCloneable (and IShallowCloneable ) interfaces with DeepClone() (and ShallowClone() ) methods.

You can define two interfaces, one with a generic parameter to support strongly typed cloning and one without to keep the weakly typed cloning ability for when you are working with collections of different types of cloneable objects:

Which you would then implement like this:

Generally I prefer to use the interfaces described as opposed to a copy constructor it keeps the intent very clear. A copy constructor would probably be assumed to be a deep clone, but it's certainly not as much of a clear intent as using an IDeepClonable interface.

If you're going for an interface, how about having DeepClone(of T)() and DeepClone(of T)(dummy as T), both of which return T? The latter syntax would allow T to be inferred based upon the argument.

@supercat: Are you saying have a dummy parameter so the type can be inferred? It's an option I suppose. I'm not sure I like having a dummy parameter just to get the type inferred automatically. Perhaps I'm misunderstanding you. (Maybe post some code in a new answer so I can see what you mean).

@supercat: The dummy parameter would exist precisely to allow type inference. There are situations where some code might want to clone something without having ready access to what the type is (e.g. because it's a field, property, or function return from another class) and a dummy parameter would allow a means of properly inferring the type. Thinking about it, it's probably not really helpful since the whole point of an interface would be to create something like a deep-cloneable collection, in which case the type should be the collections' generic type.

Question! In what situation would you ever want the non generic version? To me, it makes sense for only IDeepCloneable to exist, because . you do know what T if you make your own implementation, ie SomeClass : IDeepCloneable

@Kyle say you had a method that took cloneable objects, MyFunc(IDeepClonable data) , then it could work on all clonables, not just a specific type. Or if you had a collection of cloneables. IEnumerable lotsOfCloneables then you could clone lots of objects at the same time. If you don't need that kind of thing though, then leave the non-generic one out.

The problem with ICloneable is, as others have mentioned, that it does not specify whether it is a deep or shallow copy, which makes it practically unuseable and, in practice, rarely used. It also returns object , which is a pain, since it requires a lot of casting. (And though you specifically mentioned classes in the question, implementing ICloneable on a struct requires boxing.)

A copy constuctor also suffers from one of the problems with ICloneable. It isn't obvious whether a copy constructor is doing a deep or shallow copy.

It would be best to create a DeepClone() method. This way the intent is perfectly clear.

This raises the question of whether it should be a static or instance method.

I slightly prefer the static version sometimes, just because cloning seems like something that is being done to an object rather than something the object is doing. In either case, there are going to be issues to deal with when cloning objects that are part of an inheritence hierarchy, and how those issues are delt with may ultimately drive the design.

Although I don't know if the DeepClone() option is best, I like your answer a lot, as it underlines the confusing situation that exists about an in my opinion basic programming language feature. I guess it's up to the user to chose which option he likes best.

I am not going to argue the points here, but in my opinion, the caller should not care so much about deep or shallow when they call Clone(). They should know that they're getting a clone with no invalid shared-state. For instance, it's perfectly possible that in a deep clone, I may not want to deep clone every element. All that the caller of Clone should care about is that they are getting a new copy that doesn't have any invalid and unsupported references to the original. Calling the method 'DeepClone" seems to convey too much implementation detail to the caller.

What's wrong with an object instance knowing how to clone itself instead of being copied by a static method? This occurs in the real world all the time with biological cells. Cells in your own body are busy cloning themselves right now as you read this. IMO, the static method option is more cumbersome, tends to hide the functionality, and deviates from using the "least surprising" implementation for the benefit of others.

@KenBeckett - The reason I feel that cloning is something done to an object is because an object should "do one thing and do it well." Normally, making copies of itself is not the core competency of a class, but rather that is functionality that is tacked on. Making a clone of a BankAccount is something that you very well might want to do, but making clones of itself is not a feature of a bank account. Your cell example is not broadly instructive, because reproduction is precisely what cells evolved to do. Cell.Clone would be a good instance method, but this is not true for most other things.

I recommend using a copy constructor over a clone method primarily because a clone method will prevent you from making fields readonly that could have been if you had used a constructor instead.

If you require polymorphic cloning, you can then add an abstract or virtual Clone() method to your base class that you implement with a call to the copy constructor.

If you require more than one kind of copy (ex: deep/shallow) you can specify it with a parameter in the copy constructor, although in my experience I find that usually a mixture of deep and shallow copying is what I need.

It is better to provide a protected (non-public) copy constructor and invoke that from the clone method. This gives us the ability to delegate the task of creating an object to an instance of a class itself, thus providing extensibility and also, safely creating the objects using the protected copy constructor.

So this is not a "versus" question. You may need both copy constructor(s) and a clone interface to do it right.

(Although the recommended public interface is the Clone() interface rather than Constructor-based.)

Don't get caught-up in the explicit deep or shallow argument in the other answers. In the real world it is almost always something in-between - and either way, should not be the caller's concern.

The Clone() contract is simply "won't change when I change the first one". How much of the graph you have to copy, or how you avoid infinite recursion to make that happen shouldn't concern the caller.

"should not be the caller's concern". I could not agree more but, here I am, trying to figure out if List aList=new List(aFullListOfT) will do a deep copy(which is what I want) or a shallow Copy(which would break my code) and whether I have to implement another way to get the job done!

A list is too generic (ha ha) for clone to even make sense. In your case, that is certainly only a copy of the list and NOT the objects pointed to by the list. Manipulating the new list won't affect the first list, but the objects are the same and unless they are immutable, they ones in the first set will change if you change the ones in the second set. If there were a list.Clone() operation in your library, you should expect the result to be a full clone as in "won't change when I do something to the first one." that applies to the contained objects as well.

A List isn't going to know anything more about properly cloning it's contents than you are. If the underlying object is immutable, you are good to go. Otherwise, if the underlying object has a Clone() method you will have to use it. List aList = new List(aFullListOfT.Select(t=t.Clone())

+1 for the hybrid approach. Both approaches have an advantage and disadvantage, but this seems to have the more overall benefit.

Implementing ICloneable's not recommended due to the fact that it's not specified whether it's a deep or shallow copy, so I'd go for the constructor, or just implement something yourself. Maybe call it DeepCopy() to make it really obvious!

@Grant, how does the constructor relay intent? IOW, if an object took itself in the constructor, is the copy deep or shallow? Otherwise, I agree completely with the DeepCopy() (or otherwise) suggestion.

I'd argue that a constructor is almost as unclear as the ICloneable interface - you'd have to read the API docs/code to know it does a deep clone or not. I just define an IDeepCloneable interface with a DeepClone() method.

Has anyone seen a use where iCloneable was used on an object of unknown type? The whole point of interfaces is that they can be used on objects of unknown type; otherwise one may as well simply make Clone be a standard method which returns the type in question.

You'll run into problems with copy constructors and abstract classes. Imagine you want to do the following:

Right after doing the above, you start wishing you'd either used an interface, or settled for a DeepCopy()/ICloneable.Clone() implementation.

The problem with ICloneable is both intent and consistency. It's never clear whether it is a deep or shallow copy. Because of that, it's probably never used in only one manner or another.

I don't find a public copy constructor to be any clearer on that matter.

That said, I would introduce a method system that works for you and relays intent (a'la somewhat self documenting)

If the object you are trying to copy is Serializable you can clone it by serializing it and deserializing it. Then you don't need to write a copy constructor for each class.

I don't have access to the code right now but it is something like this

The use of serialization as a means of implementing deep cloning is irrelevant to the question of whether the deep clone should be surfaced as a ctor or method.

I think it is another valid alternative. I didn't think he was limited to those two methods of deep copying.

It is dependent on copy semantics of the class in question, which you should define yourself as the developer. Chosen method is usually based on intended use cases of the class. Maybe it will make a sense to implement both methods. But both share similar disadvantage - it is not exactly clear which copying method they implement. This should be clearly stated in documentation for your class.

looked as clearer statement of intent.

I think there should be a standard pattern for cloneable objects, though I'm not sure what exactly the pattern should be. With regard to cloning, it would seem there are three types of classes:

  1. Those that explicitly support for deep cloning
  2. Those that where memberwise cloning will work as deep cloning, but which neither have nor need explicit support.
  3. Those which cannot be usefully deep cloned, and where memberwise cloning will yield bad results.

So far as I can tell, the only way (at least in .net 2.0) to get a new object of the same class as an existing object is to use MemberwiseClone. A nice pattern would seem to be to have a "new"/"Shadows" function Clone which always returns the present type, whose definition is always to call MemberwiseClone and then call a protected virtual subroutine CleanupClone(originalObject). The CleanupCode routine should call base.Cleanupcode to handle the base type's cloning needs and then add its own cleanup. If the cloning routine has to use the original object, it would have to be typecast, but otherwise the only typecasting would be on the MemberwiseClone call.

Unfortunately, the lowest level of class that was of type (1) above rather than type (2) would have to be coded to assume that its lower types would not need any explicit support for cloning. I don't really see any way around that.

Still, I think having a defined pattern would be better than nothing.

Incidentally, if one knows that one's base type supports iCloneable, but does not know the name of the function it uses, is there any way to reference the iCloneable.Clone function of one's base type?

В прошлой статье для создания объекта использовался конструктор по умолчанию. Однако мы сами можем определить свои конструкторы. Как правило, конструктор выполняет инициализацию объекта. При этом если в классе определяются свои конструкторы, то он лишается конструктора по умолчанию.

На уровне кода конструктор представляет метод, который называется по имени класса, который может иметь параметры, но для него не надо определять возвращаемый тип. Например, определим в классе Person простейший конструктор:

Конструкторы могут иметь модификаторы, которые указываются перед именем конструктора. Так, в данном случае, чтобы конструктор был доступен вне класса Person, он определен с модификатором public .

Определив конструктор, мы можем вызвать его для создания объекта Person:

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

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

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

Консольный вывод данной программы:

Ключевое слово this

Ключевое слово this представляет ссылку на текущий экземпляр/объект класса. В каких ситуациях оно нам может пригодиться?

В примере выше во втором и третьем конструкторе параметры называются также, как и поля класса. И чтобы разграничить параметры и поля класса, к полям класса обращение идет через ключевое слово this . Так, в выражении

первая часть - this.name означает, что name - это поле текущего класса, а не название параметра name. Если бы у нас параметры и поля назывались по-разному, то использовать слово this было бы необязательно. Также через ключевое слово this можно обращаться к любому полю или методу.

Цепочка вызова конструкторов

В примере выше определены три конструктора. Все три конструктора выполняют однотипные действия - устанавливают значения полей name и age. Но этих повторяющихся действий могло быть больше. И мы можем не дублировать функциональность конструкторов, а просто обращаться из одного конструктора к другому также через ключевое слово this , передавая нужные значения для параметров:

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

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

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

И если при вызове конструктора мы не передаем значение для какого-то параметра, то применяется значение по умолчанию.

Инициализаторы объектов

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

С помощью инициализатора объектов можно присваивать значения всем доступным полям и свойствам объекта в момент создания. При использовании инициализаторов следует учитывать следующие моменты:

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

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

Инициализаторы удобно применять, когда поле или свойство класса представляет другой класс:

Обратите внимание, как устанавливается поле company :

Деконструкторы

Деконструкторы (не путать с деструкторами) позволяют выполнить декомпозицию объекта на отдельные части.

Например, пусть у нас есть следующий класс Person:

В этом случае мы могли бы выполнить декомпозицию объекта Person так:

Значения переменным из деконструктора передаюся по позиции. То есть первое возвращаемое значение в виде параметра personName передается первой переменной - name, второе возващаемое значение - переменной age.

По сути деконструкторы это не более,чем синтаксический сахар. Это все равно, что если бы мы написали:

При получении значений из декоструктора нам необходимо предоставить столько переменных, сколько деконструктор возвращает значений. Однако бывает, что не все эти значения нужны. И вместо возвращаемых значений мы можм использовать прочерк _ . Например, нам надо получить только возраст пользователя:

Поскольку первое возвращаемое значение - это имя пользователя, которое не нужно, в в данном случае вместо переменной прочерк.

Web development, programming languages, Software testing & others

Syntax
class Name // Parameterized Constructor
public Name(parameters)
// code
>
public Name(Name instance of class) //copyconstructor
//code
>
>

The name of the constructor is the same as its class name. Method Signature of constructor consists of the name of the method along with its parameter list. There can be any number of constructors in a class. The copy constructor is not the only constructor in a class. Another constructor is required with a copy constructor to create an object. The following are the example that shows how to use copy constructor for a class.

Example 1: Without Copy Constructor

Code

class Demo
//variables
string name;
int age;
public Demo(string name, int age) //parameterized constructor
this.name = name;
this.age = age;
>
public void getData()
Console.WriteLine("Name is:", name);
Console.WriteLine("Age is:", age);
>
>
class Program
// Main method
static void Main(string[] args)
Demo obj = new Demo("John",20);
obj.getData();
Console.ReadLine();
>
>

In the above example, there is only one parameterized constructor, which has two variables. getData() is used to display the values of the object.

Output:

Without Copy Constructor

With Copy Constructor

Code

class Demo
string name;
int age;
public Demo(string name, int age) //paramertrized constructor
this.name = name;
this.age = age;
>
public Demo(Demo d) //copy constructor
this.name = d.name;
this.age = d.age;
>
public void getData()
Console.WriteLine("Name is:", name);
Console.WriteLine("Age is:", age);
>
>
class Program
//main method
static void Main(string[] args) Demo obj = new Demo("John",20);
obj.getData();
Demo obj1 = new Demo(obj); //new object
obj1.getData();
Console.ReadLine();
>
>

In the above example, Demo is a class that contains two constructors. A copy constructor is always used with another constructor. A copy constructor sends the name and age properties of one object to another object. Below is the above program’s output as the first values are for the existing object, and copy the constructor copy these values and create a new object with the same values as of existing object.

Example 2

Code

class pen
// variables
private string name;
private string color;
private int price;
// Copy constructor
public pen(pen a)
name = a.name;
color = a.color;quantity = a.price;
>
// Parameterized constructor
public pen(string name, string color, int price) this.name = name;
this.color = color;
this.quantity =price;
>
public void getPendetails()
Console.WriteLine("color is:", color);
Console.WriteLine("price is:", price);
>
// Main Method
public static void Main()
// Create a new object.
pen p1 = new pen("Parker", "Blue", 30);
pen p2 = new pen(p1);
p2.getPendetails();
Console.ReadLine();
>>
>

In the above program, we initialize three variables for a class pen that defines the properties of the class. Copy constructor copies the properties of a pen from one object to another. The main() function initializes an object with the values as the parameters for the parameterized constructor. Then the values are displayed using getPendetails(). The new object does not affect the existing object values. The output is shown below.

Output:

Copy Constructor output2

Example 3

Code

class Calculate
//variables
private int a;
private int b;
public Calculate(int x, int y) // parameterized constructor
a = x;
b = y;
>
public Calculate(Calculate cal) //copy constructor
a = cal.a;
b = cal.b;
>
public int getSum()
return a + b;
>
>
class Sum
// main method
static void Main(string[] args)
// create a new object
Calculate c1 = new Calculate(34, 4);
Calculate c2 = new Calculate(c1);
c2.getSum();
Console.ReadLine();
>
>

Now let us understand the above program.

The class contains the variables a and b and two constructors, i.e. a parameterized constructor and a copy constructor.

Code

class Calculate
//variables
private int a;
private int b;
public Calculate(int x, int y) //parameterized constructor
a = x;
b = y;
>
public Calculate(Calculate cal) //copy constructor
a = cal.a;
b = cal.b;
>
public int getSum()
return a + b;
>
>

The copy constructor sends the values of the cal object into a and b. The function gets() returns the sum of a and b. The main() is in the class Sum, which initializes the c1 as the parameters and then copies constructor is sent the value of object c1 to object c2. The sum of a and b is displayed using the getSum().

Code

class Sum
// main method
static void Main(string[] args)
Calculate c1 = new Calculate(34, 4);
Calculate c2 = new Calculate(c1);
c2.getSum();
Console.ReadLine();
>
>

Output:

Copy Constructor output3

  • The name of the copy constructor should be the same as the class name.
  • Another constructor is required with a copy constructor to copy an object.
  • There is no return type of copy constructor.
  • The copy constructor cannot be final, static and abstract.

Conclusion

So whenever you want to copy an object value to other objects, you can use a copy constructor. It’s a way to tell the compiler how to copy one object’s values in another object. It happens when we pass parameters to an object. ICloneable interface is also used by adding a Clone method to your class, making a copy of the existing object. In copy constructor, you can specify custom behavior by giving a unique ID or by copying some required fields and not others.

Recommended Articles

Читайте также: