Регулярные выражения предназначены для обработки относительно простых текстов, которы
задаются регулярными языками. Регулярные выражения со времени своего появления сильн
усложнились, особенно в Perl, реализация регулярных выражений в котором является вдохновение
для остальных языков и библиотек, но регулярные выражения всё ещё плохо приспособлен
(и вряд ли когда-либо будут) для обработки сложных языков типа HTML. Сложность обработк
HTML заключается ещё и в очень сложных правилах обработки невалидного кода, которы
достались по наследству от первых реализаций времён рождения Интернета, когда никаки
стандартов не было и в помине, а каждый производитель браузеров нагромождал уникальны
и неповторимые возможности.
Итак, в общем случае регулярные выражения — не лучший кандидат для обработки HTML
Обычно разумнее использовать специализированные парсеры HTML.
CsQuery
Лицензия: MIT
Один из современных парсеров HTML для .NET. В качестве основы взят парсер validator.n
для Java, который в свою очередь является портом парсера из движка Gecko (Firefox)
Это гарантирует, что парсер будет обрабатывать код точно так же, как современные браузеры.
API черпает вдохновение у jQuery, для выбора элементов используется язык селекторо
CSS. Названия методов скопированы практически один-в-один, то есть для программистов
знакомых с jQuery, изучение будет простым.
Обладает высокой производительностью. На порядки превосходит HtmlAgilityPack+Fizzle
по скорости на сложных запросах.
CQ cq = CQ.Create(html);
foreach (IDomObject obj in cq.Find("a"))
Console.WriteLine(obj.GetAttribute("href"));
Если требуется более сложный запрос, то код практически не усложняется:
CQ cq = CQ.Create(html);
foreach (IDomObject obj in cq.Find("h3.r a"))
Console.WriteLine(obj.GetAttribute("href"));
HtmlAgilityPack
Лицензия: Ms-PL
Самый старый, и потому самый популярный парсер для .NET. Однако возраст не означае
качество, например, уже пять лет (!!!) висит незакрытым критический баг Incorrect parsin
of HTML4 optional end tags, который приводит к некорректной обработке тегов HTML, закрывающи
теги для которых опциональны. В API присутствуют странности, например, если ничего н
найдено, возвращается null, а не пустая коллекция.
Для выбора элементов используется язык XPath, а не селекторы CSS. На простых запроса
код получается более-менее удобочитаемый:
HtmlDocument hap = new HtmlDocument();
hap.LoadHtml(html);
HtmlNodeCollection nodes = hap.DocumentNode.SelectNodes("//a");
if (nodes != null)
foreach (HtmlNode node in nodes)
Console.WriteLine(node.GetAttributeValue("href", null));
Однако если нужны сложные запросы, то XPath оказывается не очень приспособленны
для имитации CSS селекторов:
HtmlDocument hap = new HtmlDocument();
hap.LoadHtml(html);
HtmlNodeCollection nodes = hap.DocumentNode.SelectNodes(
"//h3[contains(concat(' ', @class, ' '), ' r ')]/a");
if (nodes != null)
foreach (HtmlNode node in nodes)
Console.WriteLine(node.GetAttributeValue("href", null));
Fizzler
Лицензия: LGPL
Надстройка к HtmlAgilityPack, позволяющая использовать селекторы CSS.
HtmlDocument hap = new HtmlDocument();
hap.LoadHtml(html);
foreach (HtmlNode node in hap.DocumentNode.QuerySelectorAll("h3.r a"))
Console.WriteLine(node.GetAttributeValue("href", null));
AngleSharp
Лицензия: BSD (3-clause)
Новый игрок на поле парсеров. В отличие от CsQuery, написан с нуля вручную на C#
Также включает парсеры других языков.
API построен на базе официальной спецификации по JavaScript HTML DOM. В некоторы
местах есть странности, непривычные для разработчиков на .NET (например, при обращени
к неверному индексу в коллекции будет возвращён null, а не выброшено исключение; ест
свой отдельный класс Url; пространства имён очень гранулярные, даже базовое использовани
библиотеки требует три using и т. п.), но в целом ничего критичного.
Из других странностей — библиотека тащит за собой Microsoft BCL Portability Pack
Поэтому, когда подключите AngleSharp через NuGet, не удивляйтесь, если обнаружите подключенным
три дополнительных пакета: Microsoft.Bcl, Microsoft.Bcl.Build, Microsoft.Bcl.Async.
Обработка HTML простая:
IHtmlDocument angle = new HtmlParser(html).Parse();
foreach (IElement element in angle.QuerySelectorAll("a"))
Console.WriteLine(element.GetAttribute("href"));
Она не усложняется, и если нужна более сложная логика:
IHtmlDocument angle = new HtmlParser(html).Parse();
foreach (IElement element in angle.QuerySelectorAll("h3.r a"))
Console.WriteLine(element.GetAttribute("href"));
Regex
Страшные и ужасные регулярные выражения. Применять их нежелательно, но иногда возникае
необходимость, так как парсеры, которые строят DOM, заметно прожорливее, чем Regex
они потребляют больше и процессорного времени, и памяти.
Если дошло до регулярных выражений, то нужно понимать, что вы не сможете построит
на них универсальное и абсолютно надёжное решение. Однако если вы хотите парсить конкретны
сайт, то эта проблема может быть не так критична.
Ради всего святого, не надо превращать регулярные выражения в нечитаемое месиво
Вы не пишете код на C# в одну строчку с однобуквенными именами переменных, так и регулярны
выражения не нужно портить. Движок регулярных выражений в .NET достаточно мощный, чтоб
можно было писать качественный код.
Например, вот немного доработанный код для извлечения ссылок из вопроса:
Regex reHref = new Regex(@"(?inx)
]*
href \s* = \s*
(? ['""] )
(? [^""]+ )
\k
[^>]* >");
foreach (Match match in reHref.Matches(html))
Console.WriteLine(match.Groups["url"].ToString());
Я хочу вернуться к более раннему коммиту. Как мне это сделать?
Вот что показывает команда git log:
$ git log
commit dddddd
Author: Me
Date: Thu Nov 4 18:59:41 2010 -0400
Add buzz
commit cccccc
Author: Me
Date: Thu Nov 4 05:13:39 2010 -0400
Add fizz
commit bbbbbb
Author: Me
Date: Thu Nov 4 00:55:06 2010 -0400
Add bar
commit aaaaaa
Author: Me
Date: Wed Nov 3 23:56:08 2010 -0400
Add foo
Ответы
Ответ 1
Этот вопрос можно понять по-разному:
Что значит вернуться или откатиться: просто посмотреть, изменить содержимое рабоче
области, изменить историю Git?
Что именно откатить: рабочую область (worktree), индекс (область подготовки коммита
staging area), текущую ветку, удаленную ветку?
К какой позиции откатить: к индексу, к последнему коммиту, к произвольному коммиту?
Обозначим начальную ситуацию на следующей схеме:
(i) (wt)
A - B - C - D - ? - ?
↑
master
(HEAD)
A, B, C, D — коммиты в ветке master.
(HEAD) — местоположение указателя HEAD.
(i) — состояние индекса Git. Если совпадает c (HEAD) - пуст. Если нет - содержи
изменения, подготовленные к следующему коммиту.
(wt) — состояние рабочей области проекта (working tree). Если совпадает с (i) — не
неиндексированных изменений, если не совпадает — есть изменения.
↑ обозначает коммит, на который указывает определенная ветка или указатель.
Вот решения, в зависимости от задачи:
1. Временно переключиться на другой коммит
Если вам нужно просто переключиться на другой коммит, чтобы, например, посмотрет
на его содержимое, достаточно команды git checkout:
git checkout aaaaaa
(wt)
(i)
A - B - C - D
↑ ↑
(HEAD) master
Сейчас репозиторий находится в состоянии «detached HEAD». Чтобы переключиться обратно
используйте имя ветки (например, master):
git checkout master
2. Переключиться на коммит и продолжить работу с него
Если вы хотите продолжить работу с другого коммита, вам понадобится новая ветка
Можно переключиться и создать ее одной командой:
git checkout -b имя-новой-ветки aaaaaa
(wt)
(i)
A - B - C - D
↑ ↑
new master
(HEAD)
3. Удалить изменения в рабочей области и вернуть ее к состоянию как при последне
коммите.
Начальное состояние:
(i) (wt)
A - B - C - D - ? - ?
↑
master
(HEAD)
3.1 Безопасно — с помощью кармана (stash)
3.1.1 Только неиндексированные
Можно удалить прикарманить только те изменения, которые еще не были индексирован
(командой add):
git stash save --keep-index
Конечное состояние:
(wt)
(i)
A - B - C - D - ? ?
↑ ↑
master stash{0}
(HEAD)
3.1.2 Индексированные и нет
Эта команда отменяет все индексированные и неиндексированные изменения в рабоче
области, сохраняя их в карман (stash).
git stash save
Конечное состояние:
(wt)
(i)
A - B - C - D ?
↑ ↑
master stash{0}
(HEAD)
Восстановление несохраненных изменений: легко и просто.
git stash apply
Если stash совсем не нужен, его можно удалить.
# удалить последнюю запись кармана
git stash drop
Подробнее про использование stash.
После этого восстановить изменения всё ещё можно, но сложнее: How to recover a droppe
stash in Git?
3.2 Опасный способ
Осторожно! Эта команда безвозвратно удаляет несохраненные текущие изменения и
рабочей области и из индекса Если они вам все-таки нужны, воспользуйтесь git stash.
Восстановление несохраненных изменений: неиндексированные потеряны полностью, н
вы можете восстановить то, что было проиндексировано.
Здесь мы будем использовать git reset --hard
Выполняем:
git reset --hard HEAD
Конечное состояние:
(wt)
(i)
A - B - C - D - х - х
↑
master
(HEAD)
4. Перейти к более раннему коммиту в текущей ветке и удалить из нее все последующи
(неопубликованные)
Осторожно! Эта команда переписывает историю Git-репозитория. Если вы уже опубликовал
(git push) свои изменения, то этот способ использовать нельзя (см. почему). Используйт
вариант из пункта 5 (git revert).
4.1 При этом сохранить изменения в индекс репозитория:
git reset --soft bbbbbb
После этого индекс репозитория будет содержать все изменения от cccccc до dddddd
Теперь вы можете сделать новый коммит (или несколько) на основе этих изменений.
(wt)
(i)
A - B - C - D
↑
master
(HEAD)
4.2 Сохранить изменения в рабочей области, но не в индексе.
git reset bbbbbb
Эта команда просто перемещает указатель ветки, но не отражает изменения в индекс
(он будет пустым).
(i) (wt)
A - B - C - D
↑
master
(HEAD)
4.3 Просто выбросить изменения.
Осторожно! Эта команда безвозвратно удаляет несохраненные текущие изменения. Есл
удаляемые коммиты не принадлежат никакой другой ветке, то они тоже будут потеряны.
Восстановление коммитов: Используйте git reflog и этот вопрос чтобы найти и восстановит
коммиты; иначе сборщик мусора удалит их безвозвратно через некоторое время.
Восстановление несохраненных изменений: неиндексированные потеряны полностью, н
вы можете восстановить то, что было проиндексировано.
Начальное состояние:
(i) (wt)
A - B - C - D - ? - ?
↑
master
(HEAD)
Выполняем:
git reset --hard bbbbbb
Конечное состояние:
(wt)
(i)
A - B - C - D - х - х
↑
master
(HEAD)
5. Отменить уже опубликованные коммиты с помощью новых коммитов
Воспользуйтесь командой git revert. Она создает новые коммиты, по одному на кажды
отменяемый коммит. Таким образом, если нужно отменить все коммиты после aaaaaa:
# можно перечислить отменяемые коммиты
git revert bbbbbb cccccc dddddd
# можно задать диапазон от более раннего к более позднему (новому)
git revert bbbbbb..dddddd
# либо в относительных ссылках
git revert HEAD~2..HEAD
# можно отменить коммит слияния, указывая явным образом номер предка (в нашем пример
таких нет):
git revert -m 1 abcdef
# после этого подтвердите изменения:
git commit -m'детальное описание, что и почему сделано'
Восстановление: Если revert-коммит оказался ошибочным, используйте этот ответ.
Пытаюсь делать вот так, но ничего не получается:
var result = "";
someInput.onchange = function() {
result = someInput.value;
};
$.get("someapi", function (data) {
result = data.foo;
});
some.api.call(42, function (data) {
result = data.bar;
});
someDiv.textContent = result;
Почему-то в someDiv ничего не отображается.
Ответы
Ответ 1
Дождались! ES2017 8-ая редакция.
Внесено описание для функций с модификатором async, и использование await
Пример уже работает в хроме:
(async function() {
var data = await fetch('https://jsonplaceholder.typicode.com/users');
console.log(await data.json());
})();
ES2015
В данном стандарте введено понятие функции-генератора - функции которая может передат
управление из середины и затем вернуться в то же место. Обычно их используют для получени
последовательностей
function* foo(){
yield 1;
yield 2;
while(true) yield 3;
}
Данная функция возвращает итератор для последовательности 1,2,3,3,3,..., которы
может быть проитерирован. Хотя это интересно и само по себе, но есть один специфически
случай.
Если получаемая последовательность - это последовательность действий, а не чисел
мы можем приостановить функцию всякий раз запуская действие и ждать результата, прежд
чем вернуться к выполнению функции. Таким образом получаем не последовательность чисел
а последовательность будущих значений: т.е. обещаний.
Это несколько сложнее, но очень мощный трюк позволяет нам писать асинхронный ко
в синхронном режиме. Есть несколько "запускальщиков", которые делают это. Для пример
будет использован Promise.coroutine из Bluebird, но есть и другие упаковщики, как с
или Q.async.
var foo = coroutine(function*(){
var data = yield fetch("/echo/json"); // обратите внимание на yield
// код здесь будет выполнен после получения ответа на запрос
return data.json(); // data здесь определена
});
Этот метод тоже возвращает обещание, которое может быть использовано в других сопрограммах
Например:
var main = coroutine(function*(){
var bar = yield foo(); // ожидаем окончания нашей сопрограммы она вернет обещание
// код ниже выполнится когда будет получен ответ от сервера
var baz = yield fetch("/api/users/"+bar.userid); // зависит от результата возвращенног
функцией foo
console.log(baz); // выполнится когда завершатся оба запроса
});
main();
ES2016 (ES7) Недалекое будущее
В стандартах есть намеки на введение новых ключевых слов async, await позволивши
бы сделать работу с обещаниями более простой.
async function foo(){
var data = await fetch("/echo/json"); // обратите внимание на await
// код тут выполнится только после выполнения запроса
return data.json(); // data определена
}
Но пока это просто зарезервированные слова и неизвестно попадут ли они в следующи
стандарт и когда будут реализации.
На данный момент для их использования можно воспользоваться сборщиками, например Babel.
частичный перевод данного ответа
Ответ 2
Проблема в том, что в коде нет операции ожидания. Ни подписка на событие, ни AJAX-вызов
ни даже вызов API не ждут поступления данных - а сразу же передают управление дальше
Поэтому строка someDiv.textContent = result; выполняется ДО того, как переменная resul
получит значение!
Способов сделать это присваивание после получения значения - несколько.
Способ 0 - переместить присваивание внутрь
Возможно, этот способ выглядит как-то глупо - но он решает задачу и наиболее прос
в понимании. Если ваше приложение достаточно простое - то так и надо делать. Смотрите:
someInput.onchange = function() {
someDiv.textContent = someInput.value;
};
$.get("someapi", function (data) {
someDiv.textContent = data.foo;
});
some.api.call(42, function (data) {
someDiv.textContent = data.bar;
});
someDiv.textContent = "";
В данном случае я вообще избавился от переменной result.
Недостаток у данного способа ровно 1 - отсутствует разбиение на слои. Данные обрабатываютс
там же, где и получаются. Если вы чувствуете, что ваши скрипты становятся при использовани
такого способа все менее понятными, или вам приходится писать одно и то же в нескольки
местах - надо переходить к другим способам.
Способ 0+ - вынесение присваивания в именованную функцию.
Простейшая модификация прошлого способа, позволяющая избавиться от дублирования кода.
someInput.onchange = function() {
setResult(someInput.value);
};
$.get("someapi", function (data) {
setResult(data.foo);
});
some.api.call(42, function (data) {
setResult(data.bar);
});
setResult("");
function setResult(result) {
someDiv.textContent = result;
}
Напомню, что в js объявления функций "поднимаются на верх", т.е. объявленной в само
низу функцией setResult можно пользоваться где угодно. Это позволяет начинать скрип
не с объявления 100500 функций - а с того кода, который непосредственно начнет выполняться.
Такой способ неплохо подходит для небольших скриптов, которые не разбиты на модули.
Проблема макаронного кода
Иногда, асинхронный запрос делается в одном модуле или его части, а получить ег
результат надо в другой. Прямое использование способа 0+ приводит к коду, который называю
"макаронным":
// модуль 1
function getResult() {
$.get("someapi", function (data) {
setResult(data.foo);
});
}
// модуль 2
function someFunc() {
getResult();
}
function setResult(result) {
someDiv.textContent = result;
}
Обращаю внимание: someFunc вызывает getResult, которая вызывает setResult. В итог
два модуля вызывают друг друга. Это и есть макаронный код.
Для борьбы с таким кодом и предназначены способы ниже.
Способ 1 - обратные вызовы ("колбеки", callbacks)
Добавим той функции, которая делает запрос, параметр callback, куда будем передават
функцию, получающую ответ:
function getResult(callback) {
$.get("someapi", function (data) {
callback(data.foo);
});
}
Теперь такую функцию можно вызвать вот так:
getResult(function(result) {
someDiv.textContent = result;
})
Или вот так:
getResult(setResult);
function setResult(result) {
someDiv.textContent = result;
}
Способ 2 - обещания ("промизы", promises)
Обещание в js - это шаблон программирования, обозначающий значение, которого сейча
нет, но предполагается, что оно будет в будущем.
Имеется несколько реализаций обещаний. Основной сейчас являются ES6 Promises, он
поддерживаются современными браузерами кроме IE. (Но для тех браузеров, которые их н
поддерживают, есть куча полифилов).
Создаются обещания вот так:
function getResult(N) {
return new Promise(function (resolve, reject) {
some.api.call(N, function (data) {
resolve(data.bar);
});
});
}
Также в качестве обещания можно использовать JQuery Deferred:
function getResult(N) {
var d = $.Deferred();
some.api.call(N, function (data) {
d.resolve(data.bar);
});
return d.promise();
}
Или Angular $q:
function getResult(N) {
var d = $q.defer();
some.api.call(N, function (data) {
d.resolve(data.bar);
});
return d.promise;
}
Кстати, Angular $q можно использовать и подобно es6 promise:
function getResult(N) {
return $q(function (resolve, reject) {
some.api.call(N, function (data) {
resolve(data.bar);
});
});
}
В любом случае, использование такой функции getResult будет выглядеть одинаково:
getResult(42).then(function (result) {
someDiv.textContent = result;
});
Или же можно использовать новый синтаксис async/await, описанный в ответе ниже от Grundy
Обращаю внимание, что здесь я для примера взял именно some.api.call, но не событи
или ajax-вызов - и это не случайно!
Дело в том, что обещание может быть выполнено (resolved) только 1 раз, а большинств
событий происходят несколько раз. Поэтому использовать обещания для того же onchange
- нельзя.
Что же до ajax-вызова - то надо помнить, что он УЖЕ возвращает обещание! А потом
все способы выше в комбинации с ним будут выглядеть смешными. Все делается гораздо проще:
function getResult() {
return $.get("someapi")
.then(function (data) {
return data.foo;
});
}
Кстати, здесь тоже можно было использовать async/await
На случай если вы запутались в коде выше, вот его "развернутая" версия:
function getResult() {
var q1 = $.get("someapi");
var q2 = q1.then(function (data) {
return data.foo;
});
return q2;
}
Тут все просто. Сам по себе вызов $.get возвращает обещание, которое при выполнени
будет содержать прищедшие с сервера данные.
Далее мы создаем для него продолжение, которое обработает эти данные (достанет поле foo).
Ну и потом это продолжение (которое тоже является обещанием) мы и возвращаем.
Способ 3 - наблюдаемые значения (observables) в Knockout
Обычно про Knockout вспоминают как про библиотеку для двусторонней привязки данны
к виду - но ее возможности могут пригодиться и при решении подобных задач.
Можно сделать так. Для начала, заведем наблюдаемое значение:
var result = ko.observable("");
Это значение можно менять по событию:
someInput.onchange = function() {
// вызов result с параметром устанавливает значение равным параметру
result(someInput.value);
};
И теперь можно выполнять некоторый блок кода каждый раз когда это значение меняется:
ko.computed(function() {
// вызов result без параметров возвращает текущее значение
someDiv.textContent = result();
});
Функция, переданная в ko.computed, будет вызвана каждый раз, когда ее зависимост
изменятся.
PS код выше приведен как пример ручной работы с наблюдаемыми значениями. Но имейт
в виду, что в Knockout есть более простые способы для работы с содержимым элементов DOM:
var vm = {
result: ko.observable()
};
ko.applyBindings(vm);
Способ 3.1 - наблюдаемые значения (observables) в MobX
Тут все почти так же, как и в knockout. В примере ниже я использую синтаксис ES201
и старше, потому что библиотека подразумевает использование новых средств языка:
import { observable, autorun } from 'mobx';
var result = observable("");
someInput.onchange = () => {
result.set(someInput.value);
};
autorun(() => someDiv.textContent = result.get());
Однако, обычно в MobX используются классы, а не одиночные obervable:
class ViewModel {
@observable result = "";
}
var vm = new ViewModel();
someInput.onchange = () => {
vm.result = someInput.value;
};
autorun(() => someDiv.textContent = vm.result);
Ответ 3
Код с асинхронными функциями можно исполнять синхронно используя альтернативный J
движок nsynjs
Если асинхронная функция возвращает promise
то просто вызываем функцию, а значение промиса получаем через свойство data:
function synchronousCode() {
var getURL = function(url) {
return window.fetch(url).data.text().data;
};
var url = 'https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js';
console.log('received bytes:',getURL(url).length);
};
nsynjs.run(synchronousCode,{},function(){
console.log('synchronousCode done');
});
Если асинхронная функция вызывает callback
Шаг 1. Оборачиваем асинхронную функцию в nsynjs-обертку (либо в промис):
var ajaxGet = function (ctx,url) {
var res = {};
var ex;
$.ajax(url)
.done(function (data) {
res.data = data;
})
.fail(function(e) {
ex = e;
})
.always(function() {
ctx.resume(ex);
});
return res;
};
ajaxGet.nsynjsHasCallback = true;
Шаг 2. Помещаем логику в функцию, как если бы логика исполнялась синхронно
function process() {
console.log('got data:', ajaxGet(nsynjsCtx, "data/file1.json").data);
}
Шаг 3. Исполняем функцию через nsynjs
nsynjs.run(process,this,function () {
console.log("synchronous function finished");
});
Nsynjs будет последовательно исполнять код функции, останавливаясь и дожидаясь результат
вызовов всех аснихронных функций.
Какие книги могут пригодиться для обучения?
1. Stack Overflow на русском
2. Stack Overflow на английском
Это вопрос предназначен для того, чтобы в единственном общем ответе собрать ссылк
на эталонные вопросы по источникам. Метки пишем в алфавитном порядке. Если некоторы
вопрос является эталонным для нескольких меток, то повторяем ссылку для каждой из них.
Преследуются сразу две цели. Во-первых, люди смогут использовать его для выявлени
похожих технологий. Во-вторых, его будет удобно использовать в качестве списка эталонны
вопросов при необходимости закрыть вопрос про литературу как дубликат.
Помимо ссылок на чужие подборки материалов и литературы, можно создавать новые, н
забыв указать метку.
Ответы
Ответ 1
Stack Overflow на русском
Книги и учебные ресурсы по фундаментальным знаниям и навыкам разработчика
android Книги и учебные ресурсы по Android
c# Книги и учебные ресурсы по C#
c++ Книги и учебные ресурсы по С++
c++ Где взять стандарт C++?
htmlcss Книги и учебные ресурсы по HTML и CSS
java Книги и учебные ресурсы по Java
javascript Книги и учебные ресурсы по JavaScript
kotlin Книги и учебные ресурсы по Kotlin
linux Книги и учебные ресурсы по Linux
php Книги и учебные ресурсы по PHP
python Книги и учебные ресурсы по Python
r Книги и учебные ресурсы по языку R
svg Книги и учебные материалы по SVG
sqlnosql Книги и учебные ресурсы по проектированию БД: SQL && NoSQL
unity3d Книги и учебные ресурсы по Unity3D
алгоритмы Книги по теме "Алгоритмы"
безопасность Источники по безопасному (Secure) программированию
машинное-обучение Книги и учебные ресурсы по машинному обучению
тестирование Книги и другие учебные материалы по тестированию
Ответ 2
Stack Overflow на английском
What is the single most influential book every programmer should read
Language Books/Tutorials for popular languages
node.js: How do I get started with Node.js
javascript: Best resources to learn JavaScript
d3.js: Good books for learning D3.js
c++: The Definitive C++ Book Guide and List
Допустим есть это:
private int a { get; set; }
Какой в этом смысл, если я могу сделать так:
public int a;
Ответы
Ответ 1
Смотрите, какие есть преимущества у свойства перед полем.
Если ваше свойство определено так:
public int A { get; set; }
— то непосредственных выгод, конечно, нету. Но выгоды придут позже.
Вы можете навесить свою логику на запись и считывание значения. Применений може
быть море. Например, вы хотите посчитать, сколько раз считывалось значение:
private int a;
private int readcount_a = 0;
public int A
{
get { readcount_a++; return a; }
set { a = value; }
}
Вы можете сделать триггер на изменение поля:
class Data : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int a;
public int A
{
get { return a; }
set
{
if (a == value)
return;
a = value;
RaisePropertyChanged();
}
}
private void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Вы можете залогировать все изменения поля:
public int A
{
get { return a; }
set
{
Trace.TraceInformation("Changing value of a from {0} to {1}", a, value);
a = value;
}
}
Вы можете навесить проверку значения на корректность при записи, или ленивую инициализаци
при чтении.
class Data
{
private string a = null;
public string A
{
get
{
return a ?? (a = LazyComputeInitialA());
}
set
{
if (value == null)
throw new ArgumentException(nameof(A) + " cannot be null", nameof(A));
a = value;
}
}
}
В конце-концов, вы можете не расходовать память на значение, если в большинстве случае
оно одинаковое (как это сделано у DependencyProperty):
class Data
{
static Dictionary aValues = new Dictionary();
public int A
{
get
{
int result;
if (aValues.TryGetValue(this, out result))
return result;
else
return -1; // default value
}
set
{
aValues[this] = value;
}
}
}
Вы хотите сделать разную степень видимости у геттера и сеттера, так что наприме
только ваш класс и его наследники смогут установить значение, а считать смогут все
(Это, пожалуй, самое лучшее применение свойств, на мой вкус.)
public int A { get; protected set; }
Вы можете вообще не определять setter, и возвращать какую-то высчитанную вами величину:
public int A
{
get { return b + c; }
}
(впрочем, такое можно сделать и при наличии сеттера). Например, вы можете предоставлят
данные в разных форматах:
public double Radians { get; set; }
public double Degrees
{
get { return Radians * 180.0 / Math.PI; }
set { Radians = value * Math.PI / 180.0; }
}
Вы можете установить брейкпойнт на запись или чтение свойства! Брейкпойнты на запис
или чтение данных в отладчике управляемого кода Visual Studio пока (по крайней мер
до текущей на данный момент версии Visual Studio 2017, версия 15.7) не поддерживаются.
Вы можете задать наличие свойства в интерфейсе, в отличие от поля:
interface ISupportsA
{
int A { get; }
}
class Data : ISupportsA
{
public int A { get; set; }
}
Вы можете объявить свойство виртуальным! То есть вы сможете переопределить поведени
свойства в классах-наследниках. Попробуйте-ка сделать такое с полем.
Свойства не хуже полей в том смысле, что вы можете заставить свойство работать так
будет это просто поле (public int A { get; set; }), но вы не сможете поле заставит
работать как свойство. То есть практически всегда лучше «наружу» выставлять свойство
а не поле.
XML-сериализация и WPF-овский Binding работает лишь со свойствами, но не с полями
Да, это можно считать ошибкой во фреймворке, но фактически это так.
Но не излишни ли свойства в языке? Кажется, что вместо свойства можно определит
просто две функции:
class Data
{
private int a;
public int GetA() { return a; }
public int SetA(int a) { this.a = a; }
}
Ответ на это таков.
Во-первых, одно свойство вместо двух функций представляет собой логическую группу
В хорошем языке вы говорите то, что думаете. На самом деле вы предоставляете пользовател
«переменную» A с дополнительной, часто невидимой снаружи семантикой. Значит, и выглядет
она должна как одна переменная, чтобы пользователи класса думали в тех же терминах
что и вы.
Во-вторых, это читаемость текста. Сравните код со свойствами:
player.car.speed++;
и без них:
getPlayer().getCar().setSpeed(getPlayer().getCar().getSpeed()+1);
Что легче воспринимается?
Справедливости ради, нужно отметить и недостатки свойств по сравнению с полями.
Свойства нельзя использовать как out/ref-параметр, поля можно.
Доступ к полям очень быстр, а вот доступ к свойствам может быть медленным, если ко
внутри геттера/сеттера медленный. Однако, медленный сеттер или (ещё хуже) геттер считаютс
порочной практикой, их рекомендуется избегать, чтобы не разрушать ментальную модел
«переменная с небольшим довеском».
Доступ к свойству может выбросить исключение или зависнуть, в то время как боле
простые поля ведут себя крайне просто. Конечно, правильно написанное свойство не буде
зависать, а исключения я бы порекомендовал выбрасывать только в случаях, когда пользовател
класса нарушил контракт на доступ к полю.
Ещё одно тонкое отличие свойства от поля состоит в том, что геттер возвращает ва
копию значения, в то время как при работе с полем вы получаете доступ непосредственн
к переменной. При работе со полями reference-типов (то есть, тип которых есть класс
практической разницы нет, так как работа с объектом по копии ссылки не отличается о
работы по оригиналу ссылки. Разница, однако, есть, когда поле является изменяемой структуро
(хотя, сами по себе изменяемые структуры — плохая идея). Пример случая, когда это важно
во фрагменте кода ниже.
Часто считают, что можно для начала объявить данные как поле, а потом, при необходимости
«превратить» его в свойство. Это лишь отчасти верно: при этом вы теряете бинарную совместимость
Код, который использовал ваш класс, должен быть перекомпилирован, так как на уровн
скомпилированного кода обращение к полю и к свойству — не одно и то же. Кроме того
смысл кода может поменяться, приводя к тонким ошибками. Пример из статьи по ссылке выше:
using System;
struct MutableStruct
{
public int Value { get; set; }
public void SetValue(int newValue) { Value = newValue; }
}
class MutableStructHolder
{
public MutableStruct Field;
public MutableStruct Property { get; set; }
}
class Test
{
static void Main(string[] args)
{
MutableStructHolder holder = new MutableStructHolder();
// Меняет значение holder.Field
holder.Field.SetValue(10);
// Получает *копию* holder.Property и изменяет её
holder.Property.SetValue(10);
Console.WriteLine(holder.Field.Value); // 10
Console.WriteLine(holder.Property.Value); // 0
}
}
Кстати, согласно Википедии, геттер и сеттер правильно называть акцессор и мутато
соответственно. Вы об этом знали? [Хотя, MSDN пишет просто «методы доступа».]
Ответ 2
Я думаю, что use-case'ов, в которых полезно делать именно поле, довольно таки много
Попробую привести вам один пример, поясняющий мотивацию использования свойства вместо поля.
Представим, что вы разрабатываете библиотеку классов. И реализовали класс как-то так:
class SuperClass
{
public int a; // То есть, решили, что поля достаточно.
}
Вашей библиотекой пользуются другие разработчики (то есть, клиенты по отношению
вашему коду). И вот оказывается, что вы хотите добавить некую логику чтения или запис
a (например, считывание значения из конфига). Полем здесь не обойдешься - делаем свойство:
class SuperClass
{
public int a { get { /*какая-то логика*/ } set { /*какая-то логика*/ } }
}
Вы поставили библиотеку клиентам и оп-па... Их код больше не работает с вашим. Он
не могут просто поставить обновление в виде вашей библиотеки, им придется пересобрат
свои приложения, потому что поля a больше нет. Есть свойство, но не поле - внешний интерфей
класса изменился.
Код постоянно меняется, везде потенциально может появиться новая логика. Описанног
неудобства не случилось бы, если бы вы сразу объявили свойство.
Общий смысл примерно таков: используйте свойства даже когда это просто запись и чтени
из поля, тогда внешний интерфейс класса не изменится, даже если вы измените логику работ
с этим полем.
Ответ 3
Использование свойства вместо поля является реализацией принципа инкапсуляции - одног
из принципов объектно ориентированого программирования.
Ответ 4
Плюсов очень много.
Но важен тот, который приносит пользу в нужный момент.
Эта вещь мне принесла пользу.
Был класс, ну допустим с A. Этот класс использовали из кучи мест. Даже не знаеш
из каких. А обновить надо сейчас, через 15 минут. Поиск использования показывается 110
позиций, а надо поменять только там, где в него что-то пишем.
Делаем из public int A; ->
public int A {get; private set;}
И любое присвоение выпадет списком ошибок.
Ответ 5
В дополнение к подробнейшему ответу @VladD
Еще удобно инициализировать:
class Filter {
public int ID { get; set;}
public string Name { get; set;}
}
Filter filter = new Filter { ID = 15, Name = "Vasya" };
Ответ 6
смысл есть, если связываешь скрипты к примеру или хочешь видеть значение в инспекторе
если приват-не видно паблик-видно
Нередко встречается код со строкой "use strict";. Например, библиотеки часто начинаются с этого:
(function () {
"use strict";
Что значит "use strict", и зачем его использовать?
На других языках: English • Español
Ответы
Ответ 1
"use strict"; (перевод: «использовать строгий») - это установка, которая заставляе
код обрабатываться в строгом режиме. Без этой установки код обрабатывается в неограниченно
режиме.
Строгий режим был введён в ECMAScript 5, и старые браузеры (IE9 и младше) его н
поддерживают. То есть, не обращают внимания на установку по умолчанию и всё обрабатываетс
в неограниченном режиме.
Зачем использовать "use strict";?
В строгом режиме:
некоторые ошибки можно найти быстрее,
более опасные и не полезные черты JavaScript либо запрещены, либо приводят к ошибке.
Как использовать "use strict";?
Чтобы включить строгий режим в целом скрипте, надо поставить установку "use strict"
или 'use strict'; в начало скрипта.
"use strict";
// код здесь обрабатывается в строгом режиме
Чтобы включить строгий режим в функции, надо поставить установку в начало кода функции.
// код здесь обрабатывается в неограниченном режиме
function f() {
"use strict";
// код здесь обрабатывается в строгом режиме
}
// код здесь обрабатывается в неограниченном режиме
В чём различие между строгим режимом и неограниченным режимом?
В строгом режиме:
нельзя присваивать значение в неопределённую переменную (спецификация §11.13.1)
В неограниченном режиме создается глобальная переменная.
(function() {
"use strict";
x = 5; // ReferenceError: x is not defined
})();
x = 5; // Создает глобальную переменную x
Также нельзя присваивать значение в свойство данных только для чтения.
(function() {
"use strict";
window.undefined = 5; // TypeError: Cannot assign to read only
})(); // property 'undefined' of [object Object]
window.undefined = 5; // Ничего не делает
нельзя использовать инструкцию with (спецификация §12.10).
(function() {
"use strict";
with(Object) {} // SyntaxError: Strict mode code may not include a with statement
})();
в ES5 нельзя определить повторные свойства в литерале объекта (спецификация §11.1.5).
(function() {
"use strict";
var x = {
a: 1,
a: 2
}; // SyntaxError: Duplicate data property in object literal
})(); // not allowed in strict mode
var x = {
a: 1,
a: 2
}; // x равно {a: 2}
нельзя определить повторные формальные параметры функции (спецификация §13.1, §15.3.2).
function f(a, a) {
"use strict";
} // SyntaxError: Strict mode function may not have duplicate parameter names
function f(a, a) {
return a;
}
f(1,2); // возвращает 2
изменения объекта arguments не изменяют аргументы (спецификация §10.6).
function f(x) {
"use strict";
arguments[0] = 5;
return x;
}
f(10); // возвращает 10
function f(x) {
arguments[0] = 5;
return x;
}
f(10); // возвращает 5
delete приводит к ошибке, если аргумент - не изменяемое свойство объекта (спецификаци
§11.4.1).
(function f(x) {
"use strict";
var y = 4;
delete f; // SyntaxError: Delete of an unqualified identifier in strict mode.
delete x; // SyntaxError: Delete of an unqualified identifier in strict mode.
delete y; // SyntaxError: Delete of an unqualified identifier in strict mode.
delete window.undefined; // TypeError: Cannot delete property
})(); // 'undefined' of [object Object]
(function f(x) {
var y = 4;
delete f; // Возвращает false
delete x; // Возвращает false
delete y; // Возвращает false
delete window.undefined; // Возвращает false
})();
eval не может инстанциировать переменные и функции в контексте вызова (спецификаци
§10.4.2).
(function () {
"use strict";
eval("var x = 5");
return x; // ReferenceError: x is not defined
})();
(function () {
eval("var x = 5");
return x;
})(); // Возвращает 5
this не преобразуется в объект, а если значение this - undefined или null, то н
преобразуется в глобальный объект (спецификация §10.4.3).
function f() {
"use strict";
return this;
};
f.call(4); // возвращает 4
f.call(null); // возвращает null
f.call(undefined); // возвращает undefined
function f() {
return this;
};
f.call(4); // возвращает [object Number]
f.call(null); // возвращает [object global]
f.call(undefined); // возвращает [object global]
eval и arguments - нельзя изменить или использовать в качестве имени (спецификаци
§11.4.4, §11.4.5, §11.13, §12.2.1, §12.10, §12.14.1, §13.1).
function eval(arguments) { // SyntaxError: Unexpected eval or arguments in stric
mode
"use strict";
eval = "5"; // SyntaxError: Unexpected eval or arguments in stric
mode
++eval; // SyntaxError: Unexpected eval or arguments in stric
mode
arguments++; // SyntaxError: Unexpected eval or arguments in stric
mode
try {
var arguments = 5; // SyntaxError: Unexpected eval or arguments in stric
mode
} catch(eval) {} // SyntaxError: Unexpected eval or arguments in stric
mode
return arguments.eval;
}
function eval(arguments) {
eval = "5";
++eval;
arguments++;
try {
var arguments = 5;
} catch(eval) {}
return arguments.eval;
}
eval(); // возвращает 5
нельзя использовать argument.caller и arguments.callee (спецификация §13.2).
(function f() {
"use strict";
arguments.caller; // TypeError: 'caller', 'callee', and 'arguments' propertie
may not be accessed on strict mode functions or the arguments objects for calls to them
arguments.callee; // TypeError: 'caller', 'callee', and 'arguments' propertie
may not be accessed on strict mode functions or the arguments objects for calls to them
f.arguments; // TypeError: 'caller', 'callee', and 'arguments' propertie
may not be accessed on strict mode functions or the arguments objects for calls to them
})();
больше слов, зарезервированных для использования в будущем (спецификация §7.6.1.2).
(function () {
"use strict";
var implements, let, private, public, yield, interface, package, protected, static;
})(); // SyntaxError: Unexpected strict mode reserved word
нельзя использовать литералы восьмеричной СС (спецификация B.1.1, B.1.2).
(function () {
"use strict";
return 010 + // SyntaxError: Octal literals are not allowed in strict mode.
"\077"; // SyntaxError: Octal literals are not allowed in strict mode.
})();
(function () {
return 010 + "\077";
})(); // возвращает "8?"
Ответ 2
Если в двух словах то 'use strict' этим ты указываешь что используешь ES6 (ECMAScript6)
самый последний стандарт JavaScript, который отличается синтаксисом от предыдущего (ES5).
Столкнулся с такой задачей на собеседовании:
Написать программу, которая печатает в консоли фразу Hello world
И всё бы ничего, если бы после этого стояла точка. Но фраза продолжается:
при условии, что функция main() данной программы выглядит следующим образом:
int main()
{
return 0;
}
Ответы
Ответ 1
Ответ очень прост. Их даже несколько. Самый простой, основанный на свойстве глобальны
статических объектов.
int printHelloWorld() { printf("Hello World"); return 0; }
static int testVar = printHelloWorld();
int main ()
{
return 0;
}
Это, если С стиль. С++ стиль: создаем класс, в конструкторе добавляем вывод, буде
вывод до main, в деструкторе - после.
Ответ 2
Используем препроцессор.
#define return puts("Hello World"); return
int main ()
{
return 0;
}
Не такой красивый вариант как со статикой, но тоже работает.
Ответ 3
True programmer напишет это в hex редакторе в машинных кодах. Даже функция main не нужна.
BB 11 01 B9 0D 00
B4 0E 8A 07 43 CD
10 E2 F9 CD 20 48
65 6C 6C 6F 2C 20
57 6F 72 6C 64 21
Немного исправим, воспользовавшись примером выше для соответствию заданию.
using namespace std;
#include
class hello{
public:
hello()
{
ofstream hello;
hello.open ("hello.com");
hello << "»..№..ґ.Љ.CН.вщН Hello, World!";
hello.close();
system("hello.com");
}put;
int main(){
return 0;
}
Вуаля мы получили программу на языке С++ которая возвращает .com файл, затем запускае
его, после чего этот файл через BIOS прерывание выводит на экран Hello, world! При это
наша программа имеет пустую функцию main и не обращается напрямую к стандартном вывод
в командную строку. Работает только в DOS command promt.
Ответ 4
Статические переменные и дефайны не обязательны:
#include
int s = printf("Hello world\n");
int main()
{
return 0;
}
Ответ 5
Вот ещё способ:
#include
void hello()
{
puts("Hello, World!");
}
#pragma data_seg(".CRT$XIY")
void(*pinit)()=&hello;
int main()
{
return 0;
};
Только оказалось, что он только в дебаге работает.
ПРАВКА:
А ещё можно подменить точку входа:
#pragma comment(linker, "/ENTRY:Main") //Вместо прагмы можно использовать парамет
командной строки
#include //CRT с его printf и puts (не говоря уже о cout) мы, к сожалению
потеряли, поэтому придётся использовать средства ОС
int main(void)
{
return 0;
}
void Main()
{
WriteConsoleA(GetStdHandle(STD_OUTPUT_HANDLE), "Hello, World!\n", 14, nullptr
nullptr);
}
Кстати, у меня эта программка занимает 656 байт после того, как я немного похимичи
с ключами компиляции.
Ответ 6
#include
class hello {
public:
hello(){ std::cout << "Hello, world!\n"; }
} put;
int main(){
return 0;
}
Помимо многих предложенных ранее вариантов с выводом текста до выполнения main, предлож
вариант с выводом после выполнения main:
#include
#include
void hw() {
puts("Hello world");
}
int v = atexit(hw);
int main() {
return 0;
}
Ответ 9
gcc поддерживает атрибут "constructor", который помещает функцию в init-секцию ELF-файла
Что, в свою очередь, означает, что эта функция вызовется перед функцией main. То есть
то, что нам нужно.
void __attribute__((constructor)) init {
printf("Hello world!");
}
Ответ 10
$ cat hellower.cpp
#include "helper.cpp"
int main(void) {
return 0;
}
второй файл:
$ cat helper.cpp
#include
int hello_helper() {
printf("Hello World\n");
return 0;
}
static int a = hello_helper();
Лаунч:
$ g++ hellower.cpp
$ ./a.out
Hello World
Ответ 11
Первый файл (строго соответствует условию задачи):
$ cat main.cpp
int main(void)
{
return 0;
}
Второй файл:
$ cat hello.cpp
#include
int hello_helper() {
printf("Hello World\n");
return 0;
}
static int a = hello_helper();
Makefile для компилятора g++:
$ cat makefile
TARGET = hello
PREFIX = /usr/local/bin
.PHONY: all clean install uninstall
all: $(TARGET)
clean:
rm -f $(TARGET) *.o
main.o: main.cpp
g++ -c -o main.o main.cpp
hello.o: hello.cpp
g++ -c -o hello.o hello.cpp
$(TARGET): main.o hello.o
g++ -o $(TARGET) main.o hello.o
install:
cp $(TARGET) $(PREFIX)/$(TARGET)
uninstall:
rm -f $(PREFIX)/$(TARGET)
Ответ 12
Вот в стиле C++
#include
class StaticOutput
{
static int print();
static int out;
};
int StaticOutput::out = StaticOutput::print();
int StaticOutput::print()
{
std::cout << "Hello World!" << std::endl;
return 0;
}
int main()
{
return 0;
}