Задачки на C#
В прошлый четверг на конференции SECR я сделал небольшой доклад по некоторым не совсем очевидным моментам в C#, которые могут поставить в тупик программиста. В этом сообщении я публикую список задач, о котором рассказал на конференции.
Я предлагаю сыграть в следующую игру - посмотрите на задачи, запишите решения пришедшие вам в голову (условие: не запускать и не компилировать программы!), а потом напишите в комментариях к сообщению количество правильных ответов (можно проверить запустив компилятор, а можно дождаться публикации решений - я сделаю это через несколько дней). Если не стесняетесь, то сразу напишите ваши предположения и объяснения по полученному результату.
Задачи я собирал давно, ниже приведены лишь самые простые, которые очень удобно показывать на слайдах презентации. Саму презентацию меня сподвигла сделать книга Java Puzzlers: Traps, Pitfalls, and Corner Cases, поскольку часть задачек оказалась похожими как две капли воды. Кстати, не может не радовать, что многие проблемные моменты Java, на которых строятся задачи из книги, в C# просто отсутствуют :)
Если у вас есть интересные задачи на C# - присылайте, я с радостью добавлю их в коллекцию!
Задачи
1. Каков результат компиляции и выполнения приведенного ниже кода?
static void Main(string[] args)
{
Console.WriteLine(GetSomeResult(10000));
}
static long GetSomeResult(long someValue)
{
long value1 = 10 * 1000 * 10000 * someValue;
long value2 = 10 * 1000 * 10000 * 100000;
return value2 / value1;
}
2. Какое значение присвоено x, если приведенный ниже код выводит False?
float x;
Console.Write(x == x)
3. А почему следующий код выводит False?
public static void Main()
{
Test t = new Test();
Console.WriteLine(t.Equals(t));
}
4. Что будет выведено на экран при выполнении приведенного ниже кода?
static void Main(string[] args)
{
char a = 'a';
int b = 0;
Console.WriteLine(true ? a : b);
}
5. А в этом случае, что будет на экране?
NameValueCollection col = new NameValueCollection();
Console.WriteLine("Элемент test " + col["test"] != null ? "Существует!" : "Не существует!");
6. Что следует ожидать на экране?
Console.WriteLine("A" + "B" + "C");
Console.WriteLine('A' + 'B' + 'C');
7. Циклическая инициализация полей? Интересненько, а в результате что будет на консоли выведено?
public class A { public static int x = B.y + 1; }
public class B { public static int y = A.x + 1; }
static void Main(string[] args)
{
Console.WriteLine("A.x = " + A.x);
Console.WriteLine("B.y = " + B.y);
}
8. Инкремент, инкремент, а что же будет?
int j = 0;
for (int i = 0; i < 10; i++)
j = j++;
Console.WriteLine(j);
9. А что будет выведено в результате такого цикла?
int end = int.MaxValue;
int begin = end - 100;
int counter = 0;
for (int i = begin; i <= end; i++)
counter++;
Console.WriteLine(counter);
10. А такого?
float begin = 1000000000;
int counter = 0;
for (float i = begin; i < (begin + 10); i++)
counter++;
Console.WriteLine(counter);
11. Какой же метод выберет компилятор?
class A { public void Test(int n) { Console.WriteLine("A"); } }
class B : A { public void Test(double n) { Console.WriteLine("B"); } }
static void Main(string[] args)
{
B b = new B();
b.Test(5);
}
12. А в этом случае?
public class Test
{
public Test(object obj) { Console.WriteLine("object"); }
public Test(int[] obj) { Console.WriteLine("int[]"); }
}
public static void Main() { Test t = new Test(null); }
13. Что будут выведено на экран в результате выполнения кода приведенного ниже?
List<int> list = new List<int>() { 1, 2, 3, 4, 5 };
List<int> all = list.FindAll(
i => { Console.Write(i); return i < 3; }
);
14. А такого кода?
List<int> list = new List<int>() { 1, 2, 3 };
var x = list.GroupBy(i => { Console.Write(i); return i; });
var y = list.ToLookup(i => { Console.Write(i); return i; });
15. И наконец, сложный вопрос из трех частей. Что будет выведено на экран в каждом из трех случаев, приведенных ниже:
А)
try {
Console.WriteLine("Hello ");
return;
}
finally { Console.WriteLine("Goodbye "); }
Console.WriteLine("world!");
Б)
try {
Console.WriteLine("Hello ");
Thread.CurrentThread.Abort();
}
finally { Console.WriteLine("Goodbye "); }
Console.WriteLine("world!");
В)
try {
Console.WriteLine("Hello ");
System.Environment.Exit(0);
}
finally { Console.WriteLine("Goodbye "); }
Console.WriteLine("world!");
Comments
- Anonymous
October 26, 2008
- OverflowException?
- Naan или как там обозначается значение бесконечности.
- Перегружен опретор Equals? Происходит не ReferenceEquals?
- Код символа 'а'.
- "Существует"
- "ABC" и число, сумма кодов символов A, B, C.
- Какой-нибудь StackOverflowException?
- 10
- 101
- 1000000010
- B
- int[]
- 12345
- Ничего
- A) Hello Goodbye Б) Hello Goodbye В) Hello
Anonymous
October 26, 2008
NaN и infinity это разные значенияAnonymous
October 26, 2008
Если нужны подобные "головоломки" то советую заглянуть на http://rsdn.ru/Forum/?group=dotnet и поискать темы начатые nikov. Что то типа http://rsdn.ru/Forum/Message.aspx?mid=3106078&only=1Anonymous
October 26, 2008
В шестом вопросе ошибка в слове "следуюет". Поправьте. Свои ответы записал на бумажке. Жду правильных ответов )))Anonymous
October 26, 2008
1 Задача! Ужас! Посмотрел код , результатом работы программы должно быть число 10, то что компилятор откажется это компилировать я даже и не подозревал, но почему? Ведь long прнимает числа от –9,223,372,036,854,775,808 до9,223,372,036,854,775,807 ! Проблему решил так : static void Main(string[] args) { Console.WriteLine(GetSomeResult(10000)); Console.Read(); } static long GetSomeResult(long someValue) { long value1 = 10 * 1000 * 10000 * someValue; long value2 = 10 * 1000 * 10000 * someValue*10; return value2 / value1; } 2 Задача Представления не имею, но скорей всего какое то выражение!
- Задача public static void Main() { Test t = new Test(); Console.WriteLine(t.Equals(t)); } public class Test { public Test() { } } У меня возвращает true; 4 Задача. Не знаю!
- Задача Не знаю!
- Задача static void Main(string[] args) { Console.WriteLine("A" + "B" + "C"); Console.WriteLine('A' + 'B' + 'C'); Console.Read(); } Ответ: ABC и 198 Проверил так : static void Main(string[] args) { Console.WriteLine("A" + "B" + "C"); Console.WriteLine((int)'A');//+ Console.WriteLine((int)'B');//+ Console.WriteLine((int)'C');//+ Console.WriteLine('A' + 'B' + 'C');//= Console.Read(); }
- Задача public class A { public static int x = B.y + 1; } public class B { public static int y = A.x + 1; } static void Main(string[] args) { Console.WriteLine("A.x = " + A.x); Console.WriteLine("B.y = " + B.y); } Результат исполнения: A.x = 2 B.y =1 Попробую определить логику компилятора: Итак, нужно вывести A.x , компилятор пытается вычислить A.x по формуле B.y+1; но B.y равен A.x+1, т.к A.x на данный момент не вычеслен, а по умолчанию переменные класса типа int если они не заданы инициализируются 0 , значит формула A.x+1 превращается в 0+1 тем самым B.y=1, далее формула B.y+1 превращается в 1+1 тем самым A.x=2; А вот дальше не понятно так как A.x=2 то формула A.x+1 должна вернуть 3 , но y-ку присваиваится почему то 1, видимо потому что компилятор думает что A.x всё ещё не вычеслен и по умолчанию инициализирован 0-ём.
- Задача int j = 0; for (int i = 0; i < 10; i++) j = j++; Console.WriteLine(j); Парадокс. Результат 0. Если разбить код то получается следующее: Стока j = j++; превращается в следующие две j=j и j=j+1; и так 10 раз. //////////////////////////////// int j=0; Цикл(10раз) j = 0; //1 j = j + 1; //1 j = 1; //2 j = 1 + 1; //2 j = 2; //3 j = 2 + 1; //3 j = 3; //4 j = 3 + 1; //4 j = 4; //5 j = 4 + 1; //5 j = 5; //6 j = 5 + 1; //6 j = 6; //7 j = 6 + 1; //7 j = 7; //8 j = 7 + 1; //8 j = 8; //9 j = 8 + 1; //9 j = 9; //10 j = 9 + 1; //10 ///////////////////////////////////////// А вот так работает int j = 0; for (int i = 0; i < 10; i++) j = j++; Console.WriteLine(j); или так int j = 0; for (int i = 0; i < 10; i++) j +=1 ; Console.WriteLine(j);
- Задача int end = int.MaxValue; int begin = end - 100; int counter = 0; for (int i = begin; i <= end; i++) counter++; Console.WriteLine(counter); Я в шоке!!!! Сначала всё нормально, цикл доходит до 2147483647 и вместо того чтобы остановиться начинает работать в обратную сторону на уменьшение i-того. Жесть!
- Задача ! Не разбирался, мне 9-ой хватило. 11.Задача Где то я уже с таким приколом встречался! Интересно что в Microsoft говорят.
- Задача. То же самое что и 11 задача
- Anonymous
October 26, 2008
- Ошибка компиляции. 10 * 1000 * 10000 * * 100000 - здесь все литералы имеют тип int, их произведение, соответственно, тоже, а overflow в констатных выражениях всегда проверяется на этапе компиляции.
- float.NaN
- Либо в Test так переопределен Equals(), либо это структура с float или double полем, в котором NaN.
- ((int)'a').ToString()
- Всегда будет "Существует". Тут проблема с приоритетами операций - сначала посчитается значение выражения ("Элемент test " + col["test"]), которое, разумеется, никогда не будет null. Потом оно сравнится с null (результат - всегда false). И уже потом этот результат используется в операторе ?: в качестве условия.
- "ABC", затем ((int)'A' + (int)'B' + (int)'C').ToString()
- В данном виде - порядок инициализации не определен, т.к. у классов нет статических конструкторов, и, как следствие, на них будет стоять beforefieldinit - а значит, рантайм имеет право инициализировать любой из них в любой момент (и начать как с A, так и с B). Если конструкторы добавить, то для данного кода будет "A.x = 2", "B.y = 1".
- "0", т.к. в C# строго определен порядок вычисления подвыражений. В данном случае сначала выполняется оператор ++, потом =.
- Уйдем в бесконечную рекурсию, т.к. нет такого значения у int, для которого (x <= int.MaxValue) дало бы false - по определению MaxValue.
- Тоже рекурсия, но по другой причине. У float не хватит точности, чтобы запомнить 10 значащих цифр у числа. Поэтому произойдет округление, и (1000000000f + 1) == 1000000000f.
- A.Test(int). Это вам не C++.
- Не скомпилируется - ambiguity.
- Вообще-то, не определено, т.к. порядок вызова предиката List<T>.FindAll не специфицирует. В общем случае, будут выведены все элементы коллекции, в произвольном порядке.
- "123". Enumerable.GroupBy ничего не сделает из-за "deferred execution". А вот ToLookup - не deferred.
A) Hello Goodbye (очевидно) B) Hello Goodbye (ThreadAbortException гарантирует выполнение finally) C) Hello Goodbye (Environment.Exit гарантирует выполнение finally, в отличие от FailFast).
Anonymous
October 26, 2008
Как я посмотрю, задачки не только на C#, но и на FCL. В таком случае, вот вам еще: class Foo : IComparable<Foo> { public int x; public int CompareTo(Foo other) { return other == null ? -1 : x.CompareTo(other.x); } public override string ToString() { return x.ToString(); } } Foo[] a = { null, new Foo { x = 1 }, new Foo { x = 2 } }; Array.Sort(a); Console.WriteLine("{0}|{1}|{2}", a[0], a[1], a[2]); что выведется, и почему?Anonymous
October 26, 2008
to int19 По задаче №8 С чего это в конструкции j=j++; первым выполнится ++ , а потом уже присваивание.Если бы было j=++j; тогда да, даже если и так почему результат равен 0?Anonymous
October 26, 2008
- Ошибка приведения типов будет в этом случае... результат операции деления в этой ситуации должен приводится к long явно.
- Да абсолютно любое. Сравнение чисел с плавающей запятой никогда true не дает. 3.Здесь ссылочный тип, а значит сравниваться будут ссылки на объекты, а в этой ситуации (боюсь ошибиться) они будут различны.
- Приведение типов будет, т.е. на экране будет код символа 'a'. Во-первых, компилятор уберет b, т.к. выражение тут всегда верное, а значит будет типа такого: WriteLine(a)... но т.к. была переменная с типом int, то будет приведение (int)а, т.к. если я не ошибаюсь, то к типу int компилятор в таких ситуацию приводит почти все если может. А char он привести может.
- Это равносильно "Элемент test"+null или Строка+ничего будет строка... а в этой ситуации строка никогда null не будет, а значит всегда будет "Существует!"
- Выше уже отвечали, я этого же варианта придерживаюсь. Просто компилятор посчитает сумму 'A'+'B'+'C', как число, а не как строку.
- не знаю
- Ноль будет, там операция. Вот почему... сначало будет вычисляться правая часть выражения... а значит в стек, в этой ситуации, будет запихнуто значение 0, а потом j будет увеличино на 1 и запихнуто снова с стек... после этого, и стека в переменную j вывалится 1, а потом для j (левой части), извлечется 0, т.к. он там был перед еденицей... поэтому всегда в этом выражении будет 0. Что бы выражение было верным, нужно либо исключить левую часть выражения, либо сделать операцию ++ префиксной. ладно времени больше на анализ нету... :)
- Anonymous
October 27, 2008
to seagul2000 Потому что
- Вычисляется j (j=0)
- Вычисляется j++ (j=1)
- j присваивается результат вычисления j (j=0) Кстати в С++ все не так просто :)
Anonymous
October 27, 2008
Потому, что операторы выполняются в порядке приоритетов, и слева направо. Приоритет у оператора ++ выше, чем у =. Причем и у префиксной, и у постфиксной версии. Соответственно - сначала выполняется постфиксный ++. Он увеличивает значение переменной j, и возвращает ее значение до увеличения (по определению данного оператора) - но после его выполнения (и до присваивания) значение переменной уже увеличено. А потом возвращенное этим оператором значение - т.е. j до увеличения - снова присваивается в j. Таким образом, все выражение вместе - это NOP, и j будет всегда равен тому, что в него присвоили сначала - т.е. 0.Anonymous
October 27, 2008
to int19h Спасибо, разобрался! Т.е получается в виде псевдо-кода так: Конструкция j=j++: int j=0; 1)Выполняется j++ 2)но после выполнения j++; который увеличил j на 1 , j-ому присваивается 0, и так 10 раз;Anonymous
October 28, 2008
to int19h: "3. Либо в Test так переопределен Equals(), либо это структура с float или double полем, в котором NaN." а где есть структуры с конструктором без параметров...Anonymous
October 28, 2008
И что же получается, префиксные и постфиксные операторы работают совершенно одинаково?Anonymous
October 29, 2008
> Да абсолютно любое. Сравнение чисел с плавающей запятой никогда true не дает. Вот это неправда. Числа с плавающей запятой как таковые замечательно сравниваются, проблемы при сравнении начинаются тогда, когда сравнивают результаты вычислений с применением таких чисел (из-за ошибок округления). Но если сравнивать два числа, созданные из литералов, то все OK: Trace.Assert(1.0 == 1.0); Более того, наличие ошибок округления зависит от того, какие именно операции производятся. Далеко не всякая операция вообще может задействовать округление. В общем, всем курить: http://docs.sun.com/source/806-3568/ncg_goldberg.html