Tuple c что это
Кортежи предоставляют удобный способ для работы с набором значений, который был добавлен в версии C# 7.0.
Кортеж представляет набор значений, заключенных в круглые скобки:
var tuple = (5, 10);
В данном случае определен кортеж tuple, который имеет два значения: 5 и 10. В дальнейшем мы можем обращаться к каждому из этих значений через поля с названиями:
Item[порядковый_номер_поля_в_кортеже]
var tuple = (5, 10); Console.WriteLine(tuple.Item1); // 5 Console.WriteLine(tuple.Item2); // 10 tuple.Item1 += 26; Console.WriteLine(tuple.Item1); // 31
В данном случае тип определяется неявно. Но мы также можем явным образом указать для переменной кортежа тип:
(int, int) tuple = (5, 10);
Так как кортеж содержит два числа, то в определении типа нам надо указать два числовых типа. Или другой пример определения кортежа:
(string, int, double) person = ("Tom", 25, 81.23);
Первый элемент кортежа в данном случае представляет строку, второй элемент — тип int, а третий — тип double.
Мы также можем дать названия полям кортежа:
var tuple = (count:5, sum:10); Console.WriteLine(tuple.count); // 5 Console.WriteLine(tuple.sum); // 10
Теперь чтобы обратиться к полям кортежа используются их имена, а не названия Item1 и Item2.
Мы даже можем выполнить декомпозицию кортежа на отдельные переменные:
var (name, age) = ("Tom", 23); Console.WriteLine(name); // Tom Console.WriteLine(age); // 23
Одной из задач, которую позволяет элегантно решить кортеж — это обмен значениями:
string main = "Java"; string second = "C#"; (main, second) = (second, main); Console.WriteLine(main); // C# Console.WriteLine(second); // Java
Что можно использовать, например, при простейшей сортировке массива:
int[] nums = < 54, 7, -41, 2, 4, 2, 89, 33, -5, 12 >; // сортировка for (int i = 0; i < nums.Length - 1; i++) < for (int j = i + 1; j < nums.Length; j++) < if (nums[i] >nums[j]) (nums[i], nums[j]) =(nums[j], nums[i]); > > // вывод Console.WriteLine(«Вывод отсортированного массива»); for (int i = 0; i
Кортеж как результат метода
Кортежи могут выступать в качестве результата функции. Например, одной из распространенных ситуаций является возвращение из функции двух и более значений, в то время как функция может возвращать только одно значение. И кортежи представляют оптимальный способ для решения этой задачи:
var tuple = GetValues(); Console.WriteLine(tuple.Item1); // 1 Console.WriteLine(tuple.Item2); // 3 (int, int) GetValues()
Здесь определен метод GetValues() , который возвращает кортеж. Кортеж определяется как набор значений, помещенных в круглые скобки. И в данном случае мы возвращаем кортеж из двух элементов типа int, то есть два числа.
var tuple = GetValuesData(new int[] < 1, 2, 3, 4, 5, 6, 7 >); Console.WriteLine(tuple.count); Console.WriteLine(tuple.sum); (int sum, int count) GetValuesData(int[] numbers) < var result = (sum: 0, count: numbers.Length); foreach(var n in numbers) < result.sum += n; >return result; >
Кортеж как параметр метода
И также кортеж может передаваться в качестве параметра в метод:
PrintPerson(("Tom", 37)); // Tom - 37 PrintPerson(("Bob", 41)); // Bob - 41 void PrintPerson((string name, int age) person) < Console.WriteLine($"- "); >
Здесь в метод PrintPerson передается кортеж из двух элементов, первый из которых предоставляет строку, а второй — значение типа int.
Класс tuple
Создает последовательность элементов фиксированной длины.
Синтаксис
class tuple < tuple(); explicit tuple(P1, P2, . PN); // 0 < N tuple(const tuple&); template tuple(const tuple&); template tuple(const pair&); // N == 2 void swap(tuple& right); tuple& operator=(const tuple&); template tuple& operator=(const tuple&); template tuple& operator=(const pair&); // N == 2 >;
Параметры
TN
Тип N-го элемента кортежа.
Замечания
Шаблон класса описывает объект, в котором хранятся N-объекты типов T1 , T2 . TN соответственно, где 0 кортежа — это количество N его аргументов шаблона. Индекс аргумента Ti шаблона и соответствующего сохраненного значения этого типа i — 1 . Таким образом, хотя мы нумеруем типы от 1 до N в этой документации, соответствующие значения индекса варьируются от 0 до N – 1.
Пример
// tuple.cpp // compile with: /EHsc #include #include #include #include #include using namespace std; typedef tuple ids; void print_ids(const ids& i) < cout (i) (i) (i) int main( ) < // Using the constructor to declare and initialize a tuple ids p1(10, 1.1e-2, "one"); // Compare using the helper function to declare and initialize a tuple ids p2; p2 = make_tuple(10, 2.22e-1, "two"); // Making a copy of a tuple ids p3(p1); cout.precision(3); cout v; v.push_back(p1); v.push_back(p2); v.push_back(make_tuple(3, 3.3e-2, "three")); cout ::const_iterator i = v.begin(); i != v.end(); ++i) < print_ids(*i); >>
The tuple p1 is: ( 10, 0.011, one ). The tuple p2 is: ( 10, 0.222, two ). The tuple p3 is: ( 10, 0.011, one ). The tuples in the vector are ( 10, 0.011, one ). ( 10, 0.222, two ). ( 3, 0.033, three ).
operator=
Назначает объект tuple .
tuple& operator=(const tuple& right); template tuple& operator=(const tuple& right); template tuple& operator=(const pair& right); // N == 2 tuple& operator=(tuple&& right); template tuple& operator=(pair&& right);
Параметры
ООН
Тип n-го скопированного элемента кортежа.
right
Кортеж, из которого выполняется копирование.
Замечания
Первые два оператора-члены назначают элементы права соответствующим элементам *this . Третий оператор-член назначает right.first элементу с индексом 0 в *this и right.second — элементу с индексом 1. Все три оператора-члена возвращают значение *this .
Остальные операторы-члены являются аналогами более ранних версий, но с деклараторами ссылок rvalue: &&.
Пример
// std__tuple__tuple_operator_as.cpp // compile with: /EHsc #include #include #include typedef std::tuple Mytuple; int main() < Mytuple c0(0, 1, 2, 3); // display contents " 0 1 2 3" std::cout (c0); std::cout (c0); std::cout (c0); std::cout (c0); std::cout << std::endl; Mytuple c1; c1 = c0; // display contents " 0 1 2 3" std::cout (c1); std::cout (c1); std::cout (c1); std::cout (c1); std::cout c2; c2 = std::make_pair('x', 4); // display contents " x 4" std::cout (c2); std::cout (c2); std::cout
0 1 2 3 0 1 2 3 x 4
swap
Обмен элементами между двумя кортежами.
template void swap(tuple left, tuple right);
Параметры
left
Кортеж, элементы которого необходимо обменять с кортежем справа.
right
Кортеж, элементы которого должны быть обмениваются левыми кортежами.
Замечания
Функция выполняет left.swap(right) .
tuple
Формирует объект tuple .
constexpr tuple(); explicit constexpr tuple(const Types&. ); template explicit constexpr tuple(UTypes&&. ); tuple(const tuple&) = default; tuple(tuple&&) = default; template constexpr tuple(const tuple&); template constexpr tuple(tuple&&); // only if sizeof. (Types) == 2 template constexpr tuple(const pair&); template constexpr tuple(pair&&);
Параметры
ООН
Тип n-го скопированного элемента кортежа.
right
Кортеж, из которого выполняется копирование.
Замечания
Первый конструктор создает объект, элементы которого создаются по умолчанию.
Второй конструктор создает объект, элементы которого представляют собой копии, созданные из аргументов P1 , P2 , . PN , при этом каждый Pi инициализирует элемент с индексом i - 1 .
Третий и четвертый конструкторы создают объект, элементы которого копируются из соответствующего элемента справа.
Пятый конструктор создает объект, элемент которого с индексом 0 создан с копированием на основе right.first , а элемент с индексом 1 — создан копированием из right.second .
Остальные конструкторы являются аналогами более ранних версий, но с декларатором ссылок Rvalue: &&.
Пример
// std__tuple__tuple_tuple.cpp // compile with: /EHsc #include #include #include typedef std::tuple Mytuple; int main() < Mytuple c0(0, 1, 2, 3); // display contents "0 1 2 3" std::cout (c0) (c0) (c0) (c0); std::cout << std::endl; Mytuple c1; c1 = c0; // display contents "0 1 2 3" std::cout (c1) (c1) (c1) (c1); std::cout c2(std::make_pair('x', 4)); // display contents "x 4" std::cout (c2) (c2); std::cout << std::endl; Mytuple c3(c0); // display contents "0 1 2 3" std::cout (c3) (c3) (c3) (c3); std::cout Mytuple2; Mytuple c4(Mytuple2(4, 5, 6, 7)); // display contents "4 5 6 7" std::cout (c4) (c4) (c4) (c4); std::cout
0 1 2 3 0 1 2 3 x 4 0 1 2 3 4 5 6 7
Типы кортежей (справочник по C#)
Функция кортежей предоставляет краткий синтаксис для группировки нескольких элементов данных в упрощенной структуре данных. В следующем примере показано, как можно объявить переменную кортежа, инициализировать ее и получить доступ к ее элементам данных.
(double, int) t1 = (4.5, 3); Console.WriteLine($"Tuple with elements and ."); // Output: // Tuple with elements 4.5 and 3. (double Sum, int Count) t2 = (4.5, 3); Console.WriteLine($"Sum of elements is ."); // Output: // Sum of 3 elements is 4.5.
Как показано в предыдущем примере, для определения типа кортежа необходимо указать типы всех его элементов данных и, при необходимости, имена полей. Невозможно определить методы в типе кортежа, но можно использовать методы, предоставляемые .NET, как показано в следующем примере:
(double, int) t = (4.5, 3); Console.WriteLine(t.ToString()); Console.WriteLine($"Hash code of is ."); // Output: // (4.5, 3) // Hash code of (4.5, 3) is 718460086.
Типы кортежей поддерживают операторы == равенства и != . Дополнительные сведения см. в разделе Равенство кортежей.
Типы кортежей являются типами значений, а элементы кортежа — общедоступными полями. Поэтому кортежи представляют собой изменяемые типы значений.
Можно определить кортежи со сколь угодно большим числом элементов.
var t = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26); Console.WriteLine(t.Item26); // output: 26
Варианты использования кортежей
Чаще всего кортежи используются как возвращаемый методом тип. То есть вместо определения параметров метода out можно сгруппировать результаты метода в возвращаемый тип кортежа, как показано в следующем примере.
int[] xs = [4, 7, 9]; var limits = FindMinMax(xs); Console.WriteLine($"Limits of [] are and "); // Output: // Limits of [4 7 9] are 4 and 9 int[] ys = [-9, 0, 67, 100]; var (minimum, maximum) = FindMinMax(ys); Console.WriteLine($"Limits of [] are and "); // Output: // Limits of [-9 0 67 100] are -9 and 100 (int min, int max) FindMinMax(int[] input) < if (input is null || input.Length == 0) < throw new ArgumentException("Cannot find minimum and maximum of a null or empty array."); >// Initialize min to MaxValue so every value in the input // is less than this initial value. var min = int.MaxValue; // Initialize max to MinValue so every value in the input // is greater than this initial value. var max = int.MinValue; foreach (var i in input) < if (i < min) < min = i; >if (i > max) < max = i; >> return (min, max); >
Как показано в предыдущем примере, с возвращаемым экземпляром кортежа можно работать напрямую или деконструировать его в отдельные переменные.
Типы кортежей можно также использовать вместо анонимных типов, например в запросах LINQ. Дополнительные сведения см. в статье Выбор между анонимными типами и кортежами.
Как правило, кортежи используются для группирования слабо связанных элементов данных. В общедоступных API рекомендуется определить класс или тип структуры .
Имена полей кортежей
Вы явно указываете имена полей кортежей в выражении инициализации кортежа или в определении типа кортежа, как показано в следующем примере:
var t = (Sum: 4.5, Count: 3); Console.WriteLine($"Sum of elements is ."); (double Sum, int Count) d = (4.5, 3); Console.WriteLine($"Sum of elements is .");
Если имя поля не указано, оно может быть выведено из имени соответствующей переменной в выражении инициализации кортежа, как показано в следующем примере:
var sum = 4.5; var count = 3; var t = (sum, count); Console.WriteLine($"Sum of elements is .");
Это называется инициализаторами проекции кортежей. Имя переменной не проецируется на имя поля кортежа в следующих случаях:
- Имя кандидата — это имя элемента типа кортежа, например Item3 , ToString или Rest .
- Имя кандидата является дубликатом другого имени поля кортежа, явного или неявного.
В предыдущих случаях можно явно указать имя поля или получить доступ к полю по умолчанию.
По умолчанию поля кортежа имеют имена Item1 , Item2 , Item3 и т. д. Всегда можно использовать имя поля по умолчанию, даже если имя поля указано явно или является выводимым, как показано в следующем примере.
var a = 1; var t = (a, b: 2, 3); Console.WriteLine($"The 1st element is (same as )."); Console.WriteLine($"The 2nd element is (same as )."); Console.WriteLine($"The 3rd element is ."); // Output: // The 1st element is 1 (same as 1). // The 2nd element is 2 (same as 2). // The 3rd element is 3.
Во время компиляции компилятор заменяет имена полей не по умолчанию соответствующими именами по умолчанию. В результате явно указанные или выводимые имена полей будут недоступны во время выполнения.
Включите правило стиля кода .NET IDE0037 , чтобы задать предпочтения для выводимых или явных имен полей кортежей.
Начиная с C# 12, можно указать псевдоним для типа кортежа с директивой using . В следующем примере добавляется global using псевдоним для типа кортежа с двумя целыми значениями допустимого Min и Max значения:
global using BandPass = (int Min, int Max);
После объявления псевдонима можно использовать BandPass имя в качестве псевдонима для этого типа кортежа:
BandPass bracket = (40, 100); Console.WriteLine($"The bandpass filter is to ");
Псевдоним не вводит новый тип, но создает синоним только для существующего типа. Вы можете деконструировать кортеж, объявленный псевдонимом BandPass так же, как и с его базовым типом кортежа:
(int a , int b) = bracket; Console.WriteLine($"The bracket is to ");
Как и при назначении кортежа или деконструкции, имена элементов кортежа не должны соответствовать; Типы выполняются.
Аналогичным образом можно использовать второй псевдоним с одинаковыми типами arity и членов взаимозаменяемо с исходным псевдонимом. Можно объявить второй псевдоним:
using Range = (int Minimum, int Maximum);
Кортеж можно назначить Range BandPass кортеже. Как и во всех назначениях кортежей, имена полей не должны совпадать, только типы и arity.
Range r = bracket; Console.WriteLine($"The range is to ");
Псевдоним для типа кортежа предоставляет более семантические сведения при использовании кортежей. Он не вводит новый тип. Чтобы обеспечить безопасность типов, вместо этого следует объявить позиционный record элемент.
Присваивание и деконструкция кортежей
В C# поддерживается присваивание между типами кортежей, которые соответствуют обоим следующим условиям:
- оба типа кортежей должны содержать одинаковое количество элементов;
- для каждой позиции кортежа тип правого элемента кортежа аналогичен типу соответствующего левого элемента кортежа или может быть неявно преобразован в этот тип.
Значения элементов кортежа присваиваются в порядке расположения элементов кортежа. Имена полей кортежа не учитываются и не присваиваются, как показано в следующем примере.
(int, double) t1 = (17, 3.14); (double First, double Second) t2 = (0.0, 1.0); t2 = t1; Console.WriteLine($": and "); // Output: // t2: 17 and 3.14 (double A, double B) t3 = (2.0, 3.0); t3 = t2; Console.WriteLine($": and "); // Output: // t3: 17 and 3.14
Оператор присваивания = можно также использовать для деконструкции экземпляра кортежа в отдельные переменные. Это можно сделать различными способами:
-
Вы можете использовать ключевое слово var за пределами круглых скобок, чтобы объявить неявно типизированные переменные и позволить компилятору вывести их типы.
var t = ("post office", 3.6); var (destination, distance) = t; Console.WriteLine($"Distance to is kilometers."); // Output: // Distance to post office is 3.6 kilometers.
var t = ("post office", 3.6); (string destination, double distance) = t; Console.WriteLine($"Distance to is kilometers."); // Output: // Distance to post office is 3.6 kilometers.
var t = ("post office", 3.6); (var destination, double distance) = t; Console.WriteLine($"Distance to is kilometers."); // Output: // Distance to post office is 3.6 kilometers.
var destination = string.Empty; var distance = 0.0; var t = ("post office", 3.6); (destination, distance) = t; Console.WriteLine($"Distance to is kilometers."); // Output: // Distance to post office is 3.6 kilometers.
Назначение деконструкционного выражения может включать как существующие переменные, так и переменные, объявленные в объявлении деконструкции.
Вы также можете объединить деконструкцию с сопоставлением шаблонов, чтобы проверить характеристики полей в кортеже. В следующем примере выполняется цикл по нескольким целым числам и выводится число, которое делится на 3. Он деконструирует результат Int32.DivRem кортежа и соответствует Remainder 0:
for (int i = 4; i < 20; i++) < if (Math.DivRem(i, 3) is ( Quotient: var q, Remainder: 0 )) < Console.WriteLine($"is divisible by 3, with quotient "); > >
Подробнее о деконструкции кортежей с помощью и других типов см. в статье Деконструкция кортежей и других типов.
Равенство кортежей
Типы кортежей == поддерживают операторы и != операторы. Эти операторы сравнивают элементы левого операнда с соответствующими элементами правого операнда в соответствии с порядком расположения элементов кортежа.
(int a, byte b) left = (5, 10); (long a, int b) right = (5, 10); Console.WriteLine(left == right); // output: True Console.WriteLine(left != right); // output: False var t1 = (A: 5, B: 10); var t2 = (B: 5, A: 10); Console.WriteLine(t1 == t2); // output: True Console.WriteLine(t1 != t2); // output: False
Как показано в предыдущем примере, в операциях == и != не учитываются имена полей кортежей.
Два кортежа сравнимы, если выполнены оба следующих условия:
- оба кортежа содержат одинаковое количество элементов. Например, t1 != t2 не компилируется, если t1 и t2 имеют разное количество элементов.
- Для каждой позиции кортежа соответствующие элементы из левого и правого операндов кортежа сравниваются с помощью операторов == и != . Например, не компилируется, (1, (2, 3)) == ((1, 2), 3) так как 1 не сравнивается с (1, 2) .
Операторы == и != сравнивают кортежи с сокращенной обработкой. Это значит, что операция останавливается, как только она соответствует паре неравных элементов или достигает конца кортежей. Однако перед любым сравнением все элементы кортежа вычисляются, как показано в следующем примере.
Console.WriteLine((Display(1), Display(2)) == (Display(3), Display(4))); int Display(int s) < Console.WriteLine(s); return s; >// Output: // 1 // 2 // 3 // 4 // False
Кортежи как параметры вывода
Как правило, вы выполняете рефакторинг метода, имеющего параметры out , в метод, возвращающий кортеж. Однако бывают случаи, когда параметр out может иметь тип кортежа. В следующем примере показано, как работать с кортежами в виде параметров out .
var limitsLookup = new Dictionary() < [2] = (4, 10), [4] = (10, 20), [6] = (0, 23) >; if (limitsLookup.TryGetValue(4, out (int Min, int Max) limits)) < Console.WriteLine($"Found limits: min is , max is "); > // Output: // Found limits: min is 10, max is 20
Кортежи и System.Tuple
Кортежи C# с типами System.ValueTuple, отличаются от кортежей, представленных типами System.Tuple. Основные различия заключаются в следующем.
- Типы System.ValueTuple являются типами значений. Типы System.Tuple являются ссылочными типами.
- Типы System.ValueTuple являются изменяемыми. Типы System.Tuple являются неизменяемыми.
- Элементами данных типов System.ValueTuple являются поля. Элементами данных типов System.Tuple являются свойства.
Спецификация языка C#
Дополнительные сведения см. в разделе:
- Типы кортежей
- Операторы равенства кортежей
См. также
- Типы значений
- Выбор между анонимными типами и кортежами
- System.ValueTuple
Совместная работа с нами на GitHub
Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.
Массивы и кортежи
В C++ есть несколько способов объединить группу переменных фиксированного размера в одну переменную.
#Массивы в C
В языке C есть три основных способа определить массив:
Получившиеся переменные функционально идентичные, но немного отличаются:
- Определенный глобально массив a будет лежать в заранее выделенной области памяти на протяжении всего времени исполнения программы. Все элементы изначально заполнены своим значением по умолчанию (для int , нулём).
- Определенный внутри функции массив b будет лежать на стеке — специальной области памяти для временных переменных — и будет удален сразу когда функция (или любой другой блок вроде тела цикла или if -а) завершится. Так как размер стека исполнения ограничен, большие массивы ($>10^6$) выделять так нельзя. Изначально он заполнен чем-то случайным, что лежало на тот момент в памяти — чтобы заполнить нулями, можно написать int x[100] = <> . Чтобы заполнить все элементы заданными значениями, можно написать int y[5] = .
- Определенный через оператор new массив c выделен динамически. Он существует, пока его специально не удалили через оператор del[] . Он также заполнен тем, что на тот момент лежало в памяти. В отличие от предыдущих двух вариантов, он может быть любого размера, даже неизвестного заранее.
Важно. В первых двух вариантах размер массива должен быть известной на момент компиляции константой. Компилятор GCC может скомпилировать выражение вида int a[n] , и действительно выделится массив не-константного размера; IDE поэтому может и не подчеркнуть его, хотя это не является частью стандарта.
Все элементы массива хранятся последовательно в памяти, а сами переменные a , b и c на самом деле являются указателями на первый элемент массива. Скобочки — это просто «синтаксический сахар»:
Для инициализации и копирования в C есть две полезные функции, memset и memcpy соответственно.
Первая берет указатель «куда», указатель «откуда» и количество байт, которые нужно перекопировать:
Вторая берет указатель «куда» и один байт — значение, которое нужно раскопировать по всему массиву.
Важно. memset работает именно с сырыми байтами, а не типами вроде int или float . Поэтому через memset массивы целочисленных типов можно заполнять только «периодичными» значениями, вроде $0$ и $-1$ (отрицательная единица в двоичной записи выглядит как 111..111 ).
Также важно помнить, что последний аргумент в обоих функциях — это число байтов, а не количество элементов. В случае с массивами не-константного размера можно домножить размер типа на размер массива:
Здесь sizeof(int) = 4 . Вместо просто четверки так пишут для самокомментируемости.
#std::array
В C++11 добавили свой класс для массивов константного размера:
; Все операции с ним работают по аналогии с сишными. Основное отличие — он является контейнером STL, то есть у него есть итераторы и с ним работают все алгоритмы из стандартной библиотеки.
С обычными массивами, впрочем, тоже — указатели автоматически приводятся к итераторам:
Массивы из STL, как и обычные массивы константного размера, поддерживают итерирование:
Также можно изменять элементы в массиве во время итерирования следующим синтаксисом:
В STL также есть более ошибкоустойчивая альтернатива memset — std::fill :
Она уже работает с полными типами, хотя и немного медленнее.
#std::pair и std::tuple
Тип pair хранит пару из переменных не обязательно одинаковых типов:
Первый элемент доступен через поле .first , а второй через .second .
Его обобщение, tuple , хранит кортеж из произвольного количества переменных:
Вместо .first , .second , .third и так далее с tuple нужно использовать индексы.
Пары и тюплы удобно возвращать из функций:
Также по массивам из них удобно итерироваться:
#struct
Очень рекомендуется по возможности вместо пар и тюплов объявлять структуры.
Вместо возни с .first и .second или индексами вы получаете именованные поля, а также возможность определять свои методы и перегружать операторы:
Единственный минус структур в том, что по для пар и тюплов будут определены функции сравнения и хеширования, и поэтому их можно сразу в таком виде класть в качестве ключа в структуры из STL вроде set или unordered_set , а для структур их нужно писать отдельно.