Что обозначает запись int p1 x
Перейти к содержимому

Что обозначает запись int p1 x

  • автор:

Записи (справочник по C#)

Модификатор используется record для определения ссылочного типа , который предоставляет встроенные функции для инкапсулирования данных. C# 10 позволяет record class синтаксису в качестве синонима уточнить ссылочный тип и record struct определить тип значения с аналогичной функциональностью.

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

В следующих двух примерах демонстрируются record ссылочные типы: record class

public record Person(string FirstName, string LastName); 
public record Person < public required string FirstName < get; init; >public required string LastName < get; init; >>; 

В следующих двух примерах показаны record struct типы значений:

public readonly record struct Point(double X, double Y, double Z); 
public record struct Point < public double X < get; init; >public double Y < get; init; >public double Z < get; init; >> 

Вы также можете создавать записи с изменяемыми свойствами и полями:

public record Person < public required string FirstName < get; set; >public required string LastName < get; set; >>; 

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

public record struct DataMeasurement(DateTime TakenAt, double Measurement); 
public record struct Point < public double X < get; set; >public double Y < get; set; >public double Z < get; set; >> 

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

  • Краткий синтаксис для создания ссылочного типа с неизменяемыми свойствами.
  • Встроенное поведение, полезное для ссылочного типа, ориентированного на данные:
    • Равенство значений
    • Краткий синтаксис для обратимого изменения.
    • Встроенное форматирование для отображения.

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

    • Объект record или record class объявляет ссылочный тип. Ключевое слово class является необязательным, но может добавить ясность для читателей. record struct объявляет тип значения.
    • Свойства позиционирования являются неизменяемыми в record class и readonly record struct . Они являются изменяемыми в record struct .

    В оставшейся части этой статьи обсуждаются типы record class и record struct . Различия подробно описаны в каждом разделе. Выберите между record class и record struct , как вы выбираете между class и struct . Термин запись используется для описания поведения, которое применяется ко всем типам записей. record struct или record class используется для описания поведения, которое применяется только к типам структур или классов соответственно. Тип record struct появился в C# 10.

    Позиционный синтаксис для определения свойств

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

    public record Person(string FirstName, string LastName); public static void Main() < Person person = new("Nancy", "Davolio"); Console.WriteLine(person); // output: Person < FirstName = Nancy, LastName = Davolio >> 

    При использовании позиционного синтаксиса для определения свойства компилятор создает следующие элементы:

    • Общедоступное свойство autoimplemented для каждого позиционного параметра, предоставленного в объявлении записи.
      • Для типов record и readonly record struct : свойство только для инициализации.
      • Для типов record struct : свойство для чтения и записи.

      Вы можете добавить атрибуты в любой из этих элементов, создаваемых компилятором из определения записи. Вы можете добавить целевой объект к любому атрибуту, который применяется к свойствам позиционной записи. В следующем примере System.Text.Json.Serialization.JsonPropertyNameAttribute применяется к каждому свойству записи Person . Целевой объект property: указывает, что атрибут применяется к свойству, созданному компилятором. Другие значения — field: для применения атрибута к полю и param: для применения атрибута к параметру.

      /// /// Person record type /// /// First Name /// Last Name /// /// The person type is a positional record containing the /// properties for the first and last name. Those properties /// map to the JSON elements "firstName" and "lastName" when /// serialized or deserialized. /// public record Person([property: JsonPropertyName("firstName")] string FirstName, [property: JsonPropertyName("lastName")] string LastName); 

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

      Если созданное автоматическое определение свойства не является нужным, можно определить собственное свойство того же имени. Например, возможно, потребуется изменить доступность либо изменяемость или предоставить реализацию для метода доступа get либо set . Если вы объявляете свойство в источнике, его необходимо инициализировать из позиционного параметра записи. Если свойство является автоматически заполненным свойством, необходимо инициализировать это свойство. При добавлении резервного поля в источник необходимо инициализировать резервное поле. Созданный деконструктор использует определение свойства. Например, в следующем примере объявляются свойства FirstName и LastName для позиционной записи public , но параметр позиционирования Id ограничен до internal . Этот синтаксис можно использовать для типов записей и структур записей.

      public record Person(string FirstName, string LastName, string Id) < internal string Id < get; init; >= Id; > public static void Main() < Person person = new("Nancy", "Davolio", "12345"); Console.WriteLine(person.FirstName); //output: Nancy >

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

      public record Person(string FirstName, string LastName) < public string[] PhoneNumbers < get; init; >= []; >; 

      Если вы определите свойства с использованием стандартного синтаксиса свойств, но опустите модификатор доступа, эти свойства неявно становятся private .

      Неизменяемость

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

      Неизменяемость может быть полезной, если требуется обеспечить потокобезопасность для типа, ориентированного на данные, или существует необходимость сохранять хэш-код в неизменном виде в хэш-таблице. Но все же неизменяемость пригодна не для всех сценариев работы с данными. Entity Framework Core, например, не поддерживает обновление с неизменяемыми типами сущностей.

      Свойства только для инициализации, созданные на основе позиционных параметров ( record class и readonly record struct ) или путем указания методов доступа init , имеют неполную неизменяемость. После инициализации вы не сможете изменить значения свойств с типом значения или ссылки на свойства ссылочного типа. Но вы можете изменить сами данные, на которые ссылается свойство ссылочного типа. В следующем примере показано, что содержимое неизменяемого свойства ссылочного типа (в нашем примере это массив) является по сути изменяемым:

      public record Person(string FirstName, string LastName, string[] PhoneNumbers); public static void Main() < Person person = new("Nancy", "Davolio", new string[1] < "555-1234" >); Console.WriteLine(person.PhoneNumbers[0]); // output: 555-1234 person.PhoneNumbers[0] = "555-6789"; Console.WriteLine(person.PhoneNumbers[0]); // output: 555-6789 > 

      Возможности, уникальные для типов записей, реализуются синтезированными компилятором методами, ни один из которых не нарушает неизменяемость путем изменения состояния объекта. Если не указано иное, синтезированные методы создаются для объявлений record , record struct и readonly record struct .

      Равенство значений

      Если методы равенства не переопределяются или не заменяются, то тип, который вы объявляете, управляет определением равенства.

      • Для типов class два объекта равны, если они ссылаются на один и тот же объект в памяти.
      • Для типов struct два объекта равны, если хранят одинаковые значения и имеют один и тот же тип.
      • Для типов с модификатором record ( record class , record struct и readonly record struct ), два объекта равны, если они имеют одинаковый тип и хранят одинаковые значения.

      Определение равенства для record struct совпадает с struct . Разница заключается в том, что для struct реализация находится в ValueType.Equals(Object) и зависит от отражения. Для записей реализация синтезируется компилятором и использует объявленные члены данных.

      Для некоторых моделей данных требуется ссылочное равенство. Например, Entity Framework Core использует ссылочное равенство, чтобы гарантировать использование только одного экземпляра типа сущности в том случае, когда разные объекты концептуально являются одной сущностью. По этой причине записи и структуры записей не подходят для использования в качестве типов сущностей в Entity Framework Core.

      Следующий пример демонстрирует равенство значений для типов записей:

      public record Person(string FirstName, string LastName, string[] PhoneNumbers); public static void Main() < var phoneNumbers = new string[2]; Person person1 = new("Nancy", "Davolio", phoneNumbers); Person person2 = new("Nancy", "Davolio", phoneNumbers); Console.WriteLine(person1 == person2); // output: True person1.PhoneNumbers[0] = "555-1234"; Console.WriteLine(person1 == person2); // output: True Console.WriteLine(ReferenceEquals(person1, person2)); // output: False >

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

      • Переопределение Object.Equals(Object). Это ошибка, если переопределение объявляется явно. Этот метод используется как основа для статического метода Object.Equals(Object, Object), если оба параметра имеют отличное от NULL значение.
      • sealed Или virtual , Equals(R? other) где R находится тип записи. Этот метод реализует IEquatable . Этот метод можно объявить явным образом.
      • Если тип записи является производным от базового типа Base записи, . Equals(Base? other) Это ошибка, если переопределение объявляется явно. Если вы предоставляете собственную реализацию Equals(R? other) , укажите также реализацию GetHashCode .
      • Переопределение Object.GetHashCode(). Этот метод можно объявить явным образом.
      • Переопределения операторов == и != . Это ошибка, если операторы объявлены явным образом.
      • Если тип записи является производным от базового типа записи, . protected override Type EqualityContract < get; >; Это свойство можно объявить явным образом. Дополнительные сведения см. в разделе «Равенство в иерархиях наследования».

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

      Обратимое изменение

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

      public record Person(string FirstName, string LastName) < public string[] PhoneNumbers < get; init; >> public static void Main() < Person person1 = new("Nancy", "Davolio") < PhoneNumbers = new string[1] >; Console.WriteLine(person1); // output: Person < FirstName = Nancy, LastName = Davolio, PhoneNumbers = System.String[] >Person person2 = person1 with < FirstName = "John" >; Console.WriteLine(person2); // output: Person < FirstName = John, LastName = Davolio, PhoneNumbers = System.String[] >Console.WriteLine(person1 == person2); // output: False person2 = person1 with < PhoneNumbers = new string[1] >; Console.WriteLine(person2); // output: Person < FirstName = Nancy, LastName = Davolio, PhoneNumbers = System.String[] >Console.WriteLine(person1 == person2); // output: False person2 = person1 with < >; Console.WriteLine(person1 == person2); // output: True > 

      Выражение with может задавать позиционные свойства или свойства, созданные с помощью стандартного синтаксиса свойств. Явно объявленные свойства должны init иметь или set метод доступа для изменения в with выражении.

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

      Для реализации такой возможности для типов record class компилятор синтезирует метод клонирования и конструктор копии. Метод виртуального клонирования возвращает новую запись, инициализированную конструктором копий. При использовании выражения with компилятор создает код, который вызывает метод клона, а затем устанавливает указанные в выражении with свойства.

      Если требуется другое поведение копирования, вы можете написать собственный конструктор копирования в record class . При этом компилятор не синтезируется. Присвойте конструктору атрибут private , если запись является sealed , или protected в противном случае. Компилятор не выполняет синтезирование конструктора копии для типов record struct . Вы можете написать его, но компилятор не создает вызовы для with выражений. При назначении объекта копируются значения record struct .

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

      Встроенное форматирование для отображения

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

      Строка, которая выводится для , — это строка, возвращаемая ToString() для типа свойства. В следующем примере ChildNames используется , System.Arrayгде ToString возвращается System.String[] :

      Person

      Для реализации этой функциональной возможности в типах record class компилятор синтезирует виртуальный метод PrintMembers и переопределение ToString. В типах record struct этот член является private . Переопределение ToString создает объект StringBuilder с именем типа, за которым следует открывающая квадратная скобка. Затем оно вызывает метод PrintMembers , который добавляет имена и значения свойств, а затем добавляет закрывающую скобку. В следующем примере показан код, аналогичный синтезированному переопределению:

      public override string ToString() < StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("Teacher"); // type name stringBuilder.Append(" < "); if (PrintMembers(stringBuilder)) < stringBuilder.Append(" "); >stringBuilder.Append(">"); return stringBuilder.ToString(); > 

      Вы можете предоставить собственную реализацию PrintMembers или переопределения ToString . Примеры можно изучить в разделе Форматирование PrintMembers в производных записях далее в этой статье. В C# 10 и более поздних версиях ваша реализация ToString может включать модификатор sealed , который не позволяет компилятору создавать реализацию ToString для любых производных записей. Вы можете создать согласованное строковое представление в иерархии record типов. (Производные записи по-прежнему PrintMembers имеют метод, созданный для всех производных свойств.)

      Наследование

      Этот раздел относится только к типам record class .

      Запись может наследовать от другой записи. Но запись не может наследовать от класса, а класс не может наследовать от записи.

      Позиционные параметры в производных типах записей

      Производная запись объявляет позиционные параметры для всех параметров, определенных в основном конструкторе базовой записи. Базовая запись объявляет и инициализирует эти свойства. Производная запись не скрывает их, но создает и инициализирует только свойства для параметров, которые не объявлены в базовой записи.

      Следующий пример демонстрирует наследование с использованием синтаксиса позиционных свойств:

      public abstract record Person(string FirstName, string LastName); public record Teacher(string FirstName, string LastName, int Grade) : Person(FirstName, LastName); public static void Main() < Person teacher = new Teacher("Nancy", "Davolio", 3); Console.WriteLine(teacher); // output: Teacher < FirstName = Nancy, LastName = Davolio, Grade = 3 >> 

      Равенство в иерархиях наследования

      Этот раздел относится к типам record class , но не к типам record struct . Чтобы две переменные записи считались равными, у них должен совпадать тип времени выполнения. При этом типы содержащихся в них переменных могут отличаться. В следующем примере кода показано сравнение на унаследованное равенство:

      public abstract record Person(string FirstName, string LastName); public record Teacher(string FirstName, string LastName, int Grade) : Person(FirstName, LastName); public record Student(string FirstName, string LastName, int Grade) : Person(FirstName, LastName); public static void Main() < Person teacher = new Teacher("Nancy", "Davolio", 3); Person student = new Student("Nancy", "Davolio", 3); Console.WriteLine(teacher == student); // output: False Student student2 = new Student("Nancy", "Davolio", 3); Console.WriteLine(student2 == student); // output: True >

      В этом примере все переменные объявляются как Person , даже если экземпляр является производным типом Student или Teacher . Все экземпляры имеют одинаковые свойства и одинаковые значения этих свойств. Но student == teacher возвращает False , хотя обе переменные имеют тип Person , а student == student2 возвращает True , хотя одна переменная имеет тип Person , а другая — Student . Проверка на равенство зависит от типа фактического объекта в среде выполнения, а не от объявленного типа переменной.

      Чтобы реализовать такое поведение, компилятор синтезирует свойство EqualityContract , которое возвращает объект Type, соответствующий типу записи. EqualityContract позволяет методам равенства учитывать тип времени выполнения при проверке объектов на равенство. Если запись имеет базовый тип object , это свойство получает атрибут virtual . Если базовый тип имеет другой тип записи, это свойство становится переопределением. Если тип записи — sealed , это свойство фактически имеет значение sealed , так как тип имеет значение sealed .

      Если код сравнивает два экземпляра производного типа, синтезированные методы равенства проверка всех элементов данных базовых и производных типов для равенства. Синтезированный GetHashCode метод использует GetHashCode метод из всех элементов данных, объявленных в базовом типе и производном типе записи. Члены данных включают все объявленные поля и поле резервной record копии компилятора для любых автоматически создаваемых свойств.

      Выражения with в производных записях

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

      public record Point(int X, int Y) < public int Zbase < get; set; >>; public record NamedPoint(string Name, int X, int Y) : Point(X, Y) < public int Zderived < get; set; >>; public static void Main() < Point p1 = new NamedPoint("A", 1, 2) < Zbase = 3, Zderived = 4 >; Point p2 = p1 with < X = 5, Y = 6, Zbase = 7 >; // Can't set Name or Zderived Console.WriteLine(p2 is NamedPoint); // output: True Console.WriteLine(p2); // output: NamedPoint < X = 5, Y = 6, Zbase = 7, Name = A, Zderived = 4 >Point p3 = (NamedPoint)p1 with < Name = "B", X = 5, Y = 6, Zbase = 7, Zderived = 8 >; Console.WriteLine(p3); // output: NamedPoint < X = 5, Y = 6, Zbase = 7, Name = B, Zderived = 8 >> 

      Форматирование PrintMembers в производных записях

      Синтезированный метод PrintMembers из производного типа записи вызывает базовую реализацию. Это означает, что в выходные данные ToString включаются все свойства и поля с атрибутом public, как в производных, так и базовых типах, как показано в следующем примере:

      public abstract record Person(string FirstName, string LastName); public record Teacher(string FirstName, string LastName, int Grade) : Person(FirstName, LastName); public record Student(string FirstName, string LastName, int Grade) : Person(FirstName, LastName); public static void Main() < Person teacher = new Teacher("Nancy", "Davolio", 3); Console.WriteLine(teacher); // output: Teacher < FirstName = Nancy, LastName = Davolio, Grade = 3 >> 

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

      • Для записи sealed , которая является производной от object (не объявляет базовую запись): private bool PrintMembers(StringBuilder builder) .
      • для записи sealed , производной от другой записи (обратите внимание, что включающий тип — sealed , поэтому метод фактически имеет значение sealed ): protected override bool PrintMembers(StringBuilder builder) ;
      • Для записи, которая не является sealed и наследует от объекта: protected virtual bool PrintMembers(StringBuilder builder); .
      • Для записи, которая не является sealed и наследует от другой записи: protected override bool PrintMembers(StringBuilder builder); .

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

      public abstract record Person(string FirstName, string LastName, string[] PhoneNumbers) < protected virtual bool PrintMembers(StringBuilder stringBuilder) < stringBuilder.Append($"FirstName = , LastName = , "); stringBuilder.Append($"PhoneNumber1 = , PhoneNumber2 = "); return true; > > public record Teacher(string FirstName, string LastName, string[] PhoneNumbers, int Grade) : Person(FirstName, LastName, PhoneNumbers) < protected override bool PrintMembers(StringBuilder stringBuilder) < if (base.PrintMembers(stringBuilder)) < stringBuilder.Append(", "); >; stringBuilder.Append($"Grade = "); return true; > >; public static void Main() < Person teacher = new Teacher("Nancy", "Davolio", new string[2] < "555-1234", "555-6789" >, 3); Console.WriteLine(teacher); // output: Teacher < FirstName = Nancy, LastName = Davolio, PhoneNumber1 = 555-1234, PhoneNumber2 = 555-6789, Grade = 3 >> 

      В C# 10 и более поздних версий компилятор будет синтезировать PrintMembers в производных записях, даже если базовая запись запечатала метод ToString . Вы также можете создать собственную реализацию PrintMembers .

      Поведение деконструктора в производных записях

      Метод Deconstruct производной записи возвращает значения всех позиционных свойств с типом времени компиляции. Если переменная имеет тип базовой записи, деконструкция выполняется только для свойств базовой записи, если объект не приведен к производному типу. Следующий пример демонстрирует вызов деконструктора для производной записи.

      public abstract record Person(string FirstName, string LastName); public record Teacher(string FirstName, string LastName, int Grade) : Person(FirstName, LastName); public record Student(string FirstName, string LastName, int Grade) : Person(FirstName, LastName); public static void Main() < Person teacher = new Teacher("Nancy", "Davolio", 3); var (firstName, lastName) = teacher; // Doesn't deconstruct Grade Console.WriteLine($", ");// output: Nancy, Davolio var (fName, lName, grade) = (Teacher)teacher; Console.WriteLine($", , ");// output: Nancy, Davolio, 3 > 

      Общие ограничения

      Ключевое слово record — это модификатор для типа или struct типа class . record Добавление модификатора включает в себя поведение, описанное ранее в этой статье. Не существует общих ограничений, по которым тип обязан являться записью. Значение record class удовлетворяет ограничению class . Значение record struct удовлетворяет ограничению struct . Дополнительные сведения см. в статье Ограничения параметров типа.

      Спецификация языка C#

      Дополнительные сведения см. в разделе о классах в спецификации языка C#.

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

      • Записи
      • Методы задания init
      • Ковариантные возвращаемые значения

      См. также

      • справочник по C#
      • Рекомендации по проектированию — выбор между классом и структурой
      • Рекомендации по проектированию — проектирование структуры
      • Система типов C#
      • Выражение with

      Совместная работа с нами на GitHub

      Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.

      Что обозначает запись int p1 x

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

      Присваивание адреса

      Указателю можно присвоить адрес объекта того же типа, либо значение другого указателя. Для получения адреса объекта используется операция & :

      int a ; int *pa ; // указатель pa хранит адрес переменной a

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

      Разыменование указателя

      Операция разыменования указателя представляет выражение в виде *имя_указателя . Эта операция позволяет получить объект по адресу, который хранится в указателе.

      #include int main() < int a ; int *pa ; // хранит адрес переменной a std::cout #include int main() < int a ; int b ; int *pa ; // указатель на переменную a int *pb ; // указатель на переменную b std::cout pa: address=0x56347ffc5c value=10 pb: address=0x56347ffc58 value=2 pa: address=0x56347ffc58 value=2 b value=125

      Нулевые указатели

      Нулевой указатель (null pointer) - это указатель, который не указывает ни на какой объект. Если мы не хотим, чтобы указатель указывал на какой-то конкретный адрес, то можно присвоить ему условное нулевое значение. Для определения нулевого указателя можно инициализировать указатель нулем или константой nullptr :

      int *p1; int *p2<>;

      Ссылки на указатели

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

      #include int main() < int a ; int b ; int *p<>; // указатель int *&pRef

      ; // ссылка на указатель pRef = &a; // через ссылку указателю p присваивается адрес переменной a std::cout &:

      int a ; int *pa ; std::cout >, >=, , ,==, !=. Операции сравнения применяются только к указателям одного типа. Для сравнения используются номера адресов:

      #include int main() < int a ; int b ; int *pa ; int *pb ; if(pa > pb) std::cout

      Консольный вывод в моем случае:

      pa (0xa9da5ffdac) is greater than pb (0xa9da5ffda8)

      Приведение типов

      Иногда требуется присвоить указателю одного типа значение указателя другого типа. В этом случае следует выполнить операцию приведения типов с помощью операции (тип_указателя *) :

      #include int main() < char c ; char *pc ; // указатель на символ int *pd <(int *)pc>; // указатель на int void *pv ; // указатель на void std::cout std::cout

      Что обозначает запись: int *p1=&x; ?

      Что обозначает запись: #define s1s2s3. sn q1q2. qm ?

      В объявлении [static] tv namev [=value] запись tv определяет

      В объявлении const [tc] namec=value; tc обозначает

      Верна ли запись: template . ?

      Что определяет запись: template . ?

      Какая функция повторяет k раз символ ch в строке S , возвращает указатель на 1 ?

      В функции fact(n) , которая вычисляет n! , значение аргумента n не может превосходить

      Какая функция копирует содержимое CS2 в S1 , возвращает указатель на S1 ?

      Диапазон данных типа int в Borland C++ под управлением MS-DOS составляет

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

      Кратко об указателях в Си: присваивание, разыменование и перемещение по массивам

      Приветствую вас, дорогие читатели. В данной статье кратко описаны основные сведения об указателях в языке Си. Кроме основных операций с указателями (объявление, взятие адреса, разыменование) рассмотрены вопросы безопасности типов при работе с ними. К сожалению, в данной статье вы не найдёте информацию по операциям сравнений указателей. Однако, статья будет полезна новичкам, а также тем, кто работает с массивами. Все примеры в данной статье компилировались компилятором gcc (восьмой версии).

      Введение

      Указатель - переменная, которая хранит адрес сущностей (т.е. других переменных любого типа, будь то структура, или массив), и над которой возможно выполнять операцию разыменования (dereferencing). Адрес обычно выражен целым положительным числом. Диапазон адресов зависит от архитектуры компьютера. Указателю надо указать тип переменной, адрес которой он хранит, или же использовать ключевое слово void, для обозначения указателя, хранящего адрес чего-угодно (т.е. разрешён любой тип). Указатели объявляются как и обычные переменные, с той разницей, что имя типа переменной указателя имеет префикс, состоящий как минимум из одной звёздочки (*). Например:

      int a = 12; /* usual variable */ int * ptr = &a; /* ptr-variable which contains address of variable a */ int **pptr = &ptr; /* ptr-variable which contains address of variable ptr */ int aval = **pptr; /* get value by adress which is contained in pptr. */ int aval2 = *ptr; /* get value of a by address (value of ptr) */

      Количество звёздочек лишь указывает на длину цепочек хранимых адресов. Поскольку указатель также является переменной и имеет адрес, то его адрес также можно хранить в другом указателе. В выше приведённом примере адрес переменной a сохраняется в переменной-указателе ptr. Адрес же самой переменной ptr сохраняется в другом указателе pptr. Чтобы получить адрес переменной, перед её именем надо поставить знак амперсанда (&). Наконец, чтобы выполнить обратную операцию, т.е. получить значение (содержимое) по адресу, хранимому в указателе, имя указателя предваряется звёздочкой, почти как при объявлении. Почти, потому что одной звёздочки достаточно чтобы "распаковать" указатель. Поскольку pptr указывает по адресу на значение, хранимое в ptr, то необходимо два раза применить операцию разыменования.

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

      int b = 0xff; void *pb = &b; void **ppb = &pb; int bval1 = *((int *) pb); int bval2 = *((int *) *ppb);

      В данном примере адреса хранятся в указателе типа void. Перед получением значения по адресу, хранимым в pb, необходимо привести указатель pb к типу int*. Затем, воспользоваться стандартной операцией разыменования. Что касается указателя ppb, то он разыменовывается два раза. Первый раз до приведения к типу, для получения содержимого переменной pb, на которую он указывает. Второй раз - после приведения к типу int*.

      Изменения значения переменной через указатель.

      Так как указатель хранит адрес переменной, мы можем через адрес не только получить значение самой переменной, но также его изменить. Например:

      char a = 'x'; char *pa = &a; /* save address of a into pa */ *pa = 'y'; /* change content of variable a */ printf("%c\n", a); /* prints: y */

      Как было сказано выше, указатели хранят адреса. Естественно, что адреса могут указывать не только на ячейки данных переменных в вашей программе, но и на другие вещи: адрес стека процедур, адрес начала сегмента кода, адрес какой-то процедуры ядра ОС, адрес в куче и т. д. Логично, что не все адреса можно использовать напрямую в программе, поскольку некоторые из них указывают на те участки памяти, которые нельзя изменять (доступ для чтения), или которые нельзя затирать. В случае, при обращении к участку, доступному только для чтения, при попытке изменить значение получим ошибку Segmentation Fault (SF).

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

      И ещё, указатели могут указывать на один и тот же объект. Например:

      int a = 123; int *p1 = &a; //Теперь p2 хранит тот же адрес, что и p1. int *p2 = &a; *p1 -= 3; // a = 123 - 3. printf("*p2 = %d\n", *p2); //Выведет 120

      Этот простой пример показывает, что через адреса можно менять содержимое простых переменных, а также остальных указателей, ссылающихся на тоже самое. Таким образом, указатель p2 как бы является псевдонимом (alias) для p1.

      Передача параметров через указатели.

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

      int swap(int *a, int *b)

      Здесь переменные а и b меняются своими значениями друг с другом (при условии, что параметры содержат не нулевой адрес). Отметим ещё раз, что мы можем изменить содержимое, указываемое по параметру-указателю методов. И, конечно, мы можем стереть данный адрес, присвоив параметру новое значение.

      Проверка типов и массивы

      Как было сказано, указатели хранят адреса переменных. Несмотря на указание типа для переменной указателя, это не мешает присвоить ему адрес переменной другого типа, если вы компилируете БЕЗ флагов. Например, следующий код не скомпилируется, если вы включили флаги -Werror -Wall .

      #include #include int main(int argc, char **argv)

      Конечно, компилятор gcc и без -Wall заметит недопустимую операцию в 7 строке кода. Флаг -Wall покажет все предупреждения компилятора. Главный флаг -Werror не позволит компилировать код, если есть предупреждения.

      Что же касается массивов, то для массива не нужно предварять имя переменной амперсандом, поскольку компилятор автоматически при присваивании адреса массива присвоит адрес первого его элемента в указатель. Для многомерных массивов потребуются указатели на массивы, а не массивы указателей. Первые имеют форму объявления вида int (*arr)[] , а вторые вида int *arr[] . В квадратных скобках обязательно нужно указать размер массива. Для трёхмерных массивов потребуется уже две пары скобок, например int (*arr)[2][2] . Для четырёхмерных - три и так далее.

      // В ПУСТОМ теле метода main. int A[2] = ; // A -> (int *) ptr to A[0] element, &A -> (int (*)[]) -> ptr to whole Array. int *ptr = A; printf("ptr -> A[1] = %d\n", *(ptr + 1)); // A[1] => 20. //Illegal usage of A. // int a_2 = ++A; //expected lvalue. //But with ptr you can do this. int b_2 = *++ptr; //Now ptr contains address of A[1]. (b_2 = A[1]); int (*ptr2)[2] = &A; //ptr to array, not to literal element. //*ptr2 => get array. //**ptr2 => get first element of array. //*ptr2 + 1 => get address of second element of array. printf("ptr2 -> A[1] = %d\n", *( *ptr2 + 1) ); int M[2][2] = < , >; // (*mp)[k] => (*mp)[k] => mp[0][k]. int (*mp)[2] = M; //again you must not add '&' to variable M. printf("M[0][0] = %d\n", **mp);//get array and extract it first element printf("M[1][0] = %d\n", **(mp + 1));//move to the address of second element printf("M[1][1] = %d\n", *( *(mp + 1) + 1));

      В выше приведённом коде даны примеры для работы с массивами (одномерными и двумерными). В квадратных скобках указывается размер последнего измерения. Важно помнить, что первое разыменование приводит вас ко всему массиву (т. е. к типу int * ). А второе разыменование распаковывает элемент данного массива. В случае одномерного массива, у нас всего одна ячейка, и указатель ссылается на неё. В случае двумерного массива, у нас две ячейки - массивы, а указатель ссылается на первую. Для перемещения на второй массив, достаточно прибавить единицу к адресу, хранимому в переменной mp, например, так mp + 1 . Чтобы получить первый элемент второго массива, надо два раза распаковать указатель с соответствующим адресом массива, т.е. **(mp + 1) .

      Постоянные (const) и указатели.

      Напомним, чтобы сделать переменную с постоянным, фиксированным значением, надо добавить ключевое слово const перед её именем (до имени типа или после). Например:

      const int i1 = 10; int const i2 = 222; // Warning: variable e3 is unitialized. With -Werror it won't be compiled. // (Внимание: переменной e3 не присвоено значение. С флагом gcc -Werror // данный код не скомпилируется). // const int e3;

      Для объявления указателя на постоянное значение, ключевое слово const должно быть ПЕРЕД звёздочкой.

      int A[2] = ; const int *a0 = A; printf("content of a0 = %d\n", *a0); //*a0 *= 10; //error: cannot change constant value. a0 = (A + 1); // A[1] printf("content of a0 = %d\n", *a0); //prints: A[1]

      В примере выше была создана переменная-указатель, ссылающееся на постоянное значение. Слово const перед звёздочкой указывает, что нельзя менять содержимое напрямую (путём разыменования, обращения к ячейке). Но сама переменная указатель постоянной не является. А значит, ей можно присвоить новый адрес. Например, адрес следующей ячейки в массиве.

      Чтобы запретить менять адрес (значение переменной) указателя, надо добавить слово const ПОСЛЕ звёздочки. Кроме того, можно добавить ключевые слова const перед и после '*' , чтобы сделать переменную фиксированной ещё сильнее, например так:

      // Переменная с постоянным адресом и постоянным содержимым. const int *const ptr = A; // constant address with constant content // Переменная с постоянным адресом (содержимое можно менять) int *const ptr2 = A; // constant address only. // Переменная с постоянным содержимым, но с изменяемым адресом (значение справа) const int *ptr3 = A; // constant content only (can change address (rvalue))
      • Программирование
      • Системное программирование
      • C

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *