- Лаконичность и быстрота (почти как Ассемблер)
- Переносимость на различные архитектуры
- Набор низкоуровневых средств
- Прямой доступ к памяти
- Операции над битами
- Адресная арифметика
Си – универсальный язык программирования, считается удобным для системного программирования, хотя он удобен и для написания прикладных программ. Среди преимуществ языка Си следует отметить переносимость программ на компьютеры различной архитектуры и из одной операционной системы в другую, лаконичность записи алгоритмов, логическую стройность программ, а также возможность получить программный код, сравнимый по скорости выполнения с программами, написанными на языке ассемблера. Последнее связано с тем, что хотя Си является языком высокого уровня, имеющим полный набор конструкций структурного программирования, он также обладает набором низкоуровневых средств, обеспечивающих доступ к аппаратным средствам компьютера.
Программа на Cи состоит из программных единиц одного типа – функций. Аргументы могут передаваться функциям посредством копирования значений этих аргументов; при этом вызванная функция не может изменить фактический аргумент в вызывающей подпрограмме.
Возможен иной вариант – передача параметра по ссылке, когда явно передается указатель, т.е. адрес, при этом функция сможет изменить объект, на который ссылается указатель.
В Си предусмотрен ряд операций низкого уровня: прямой доступ к памяти, операции над битами данных и адресной арифметики.
Программы на языке Си компактны и гибки. Язык Си доверяет программисту и разрешает ему практически все; из-за этого Си нельзя считать языком надежного программирования, и вся ответственность за качество программы лежит на программисте, который должен знать особенности языка и его реализации. Программа, написанная на языке Си, состоит из операторов. Каждый оператор вызывает выполнение некоторых действий на соответствующем шаге выполнения программы.
При написании операторов применяются латинские прописные и строчные буквы, цифры и специальные знаки. К таким знакам, например, относятся: точка .
, запятая ,
, двоеточие :
, точка с запятой ;
и др. Совокупность символов, используемых в языке, называется алфавитом языка. Программы оперируют с различными данными, которые могут быть простыми и структурированными. Простые данные - это целые и вещественные числа, символы и указатели (адреса объектов в памяти). Целые числа не имеют, а вещественные имеют дробную часть. Структурированные данные - это массивы и структуры.
В своих программах вы не адресуете отдельные биты, а имеете дело с группами битов - байтами. Если представить байт как 8-разрядное целое число без знака, его биты соответствуют последовательным степеням 2
.
Однако компьютерам удобнее иметь дело со степенями 2
. Программисты часто используют шестнадцатеричную систему (16 = 2^4
) - особенно при работе с отдельными разрядами целых чисел. В качестве дополнительных цифр в шестнадцатеричной системе используются буквы а
, b
, с
, d
, е
и f
. Таким образом, счет в шестнадцатеричной системе выглядит так: 0 1 2 3 4 5 6 7 8 9 а b с d е f 10 11 ...
Числа, записанные в шестнадцатеричной системе, обозначаются префиксом 0х
.
Один байт всегда может быть представлен шестнадцатеричным числом из двух цифр (например, 3с
). По этой причине шестнадцатеричные числа удобны для работы с двоичными данными.
Побитовое И
(AND
, &
)
2
байта можно объединить поразрядной операцией И
для создания третьего байта. В этом случае бит третьего байта равен 1
только в том случае, если оба соответствующих бита первых двух байтов равны 1
.
0011
0101
----
0001
Часто используется для обнуления некоторой группы разрядов. Например:
n = n & 0177;
обнуляет в n
все разряды, кроме младших семи.
Побитовое ИЛИ
(OR
, |
)
Программа выдает результат объединения двух байтов поразрядной операцией ИЛИ
:
Hex: 03c0 | 0a90 = 0bd
Decimal: 0600 | 01690 = 0189
0011
0101
----
0111
Применяют для установки разрядов; так,
х = х | SET_ON;
устанавливает единицы в тех разрядах х
, которым соответствуют единицы в SET_ON
.
Побитовое исключающее ИЛИ
(сложение по модулю два, XOR
, ^
)
Исключающая операция ИЛИ
объединяет два байта и создает третий байт. Бит результата равен 1
в том случае, если ровно один из двух соответствующих битов входных байтов равен 1
.
0011
0101
----
0110
Побитовое отрицание (дополнение, унарный оператор НЕ
, NOT
, ~
)
Дополнением к существующему байту называется байт, биты которого находятся в противоположном состоянии: все нули заменяются единицами, а все единицы заменяются нулями.
0011
----
1100
Унарный оператор ~
поразрядно "обращает" целое т.е. превращает каждый единичный бит в нулевой и наоборот. Например:
х = х & ~077
обнуляет в х
последние 6
разрядов. Заметим, что запись х & ~077
не зависит от длины слова, и, следовательно, она лучше, чем х & 0177700
, поскольку последняя подразумевает, что х
занимает 16
битов. Не зависимая от машины форма записи ~077
не потребует дополнительных затрат при счете, так как ~077
— константное выражение, которое будет вычислено во время компиляции.
Сдвиг влево <<
При выполнении операции сдвига влево каждый бит смещается в направлении старшего бита. Биты, выходящие за пределы числа, теряются, а возникающие справа «пустоты» заполняются нулями.
Сдвиг вправо >>
Примеры битовых операций:
67 & 114 = 66
67 | 114 = 115
67 ^ 114 = 49
~67 = 188
Есть два способа обращения к функции – вызов по значению и вызов по ссылке. При вызове по значению создается копия аргумента и передается вызываемой функции. Изменение копии не влияют на значение оригинала в операторе вызова. Один из минусов этого способа – если передается большой элемент данных, то на его копирование может уйти дополнительное время.
Ссылочный параметр – это псевдоним аргумента.
int &count; //count является ссылкой на int.
В вызове функции достаточно указать имя переменной и она будет передана по ссылке, тогда упоминание в теле вызываемой функции переменной по имени ее параметра в действительности является обращением к исходной переменной в вызывающей функции и эта исходная переменная может быть изменена непосредственно вызываемой функцией. Для передачи больших объектов используется константный ссылочный параметр, чтобы обеспечить защиту параметра, как при вызове по значению, и в то же время избежать накладных расходов при передаче копии большого объекта:
const int &count;
Ссылка – это псевдоним для другой переменной и все операции, выполняемые с псевдонимом (ссылкой) выполняются на самом деле с истинной переменной. Псевдоним – другое имя переменной и для него не резервируется место в памяти. Если возвращение ссылки переменной объявлено в функции, то эта переменная должна быть объявлена внутри функции как статическая, иначе ссылка адресуется автоматической переменной, которая после выполнения функции уничтожается.
Указатель – переменная, которая содержит в качестве значения адрес памяти переменной, которая уже в свою очередь содержит значение. Т.о. имя переменной отсылает к значению прямо, а указатель – косвенно.
int y = 5;
int *yPtr;
yPtr = &y;
Теперь yPtr
указывает на y
.
Операция *
– операция косвенной адресации или операция разыменования.
Аргументы можно передавать в функцию тремя способами:
- Вызов по значению
- Вызов по ссылке с аргументами ссылками
- Вызов по ссылке с аргументами указателями
Указатели как и ссылки тоже можно использовать для модификации одного или более значений переменных в вызывающем операторе, или передавать указатели на большие объекты данных, чтобы избежать расходов, сопутствующих передаче объектов по значению.
const
с указателем значит, что значение переменной не изменяется.
Explanation 1:
if you pass a pointer to an object to your function, the function can only modify what the pointer is pointing to.
if you pass a pointer to a pointer to an object then the function can modify the pointer to point to another object.
In the case of NSError
, the function might want to create a new NSError
object and pass you back a pointer to that NSError
object. Thus, you need double indirection so that the pointer can be modified.
Explanation 2: A pointer to a pointer is a form of multiple indirection, or a chain of pointers. Normally, a pointer contains the address of a variable. When we define a pointer to a pointer, the first pointer contains the address of the second pointer, which points to the location that contains the actual value as shown below.
A variable that is a pointer to a pointer must be declared as such. This is done by placing an additional asterisk in front of its name. For example, the following declaration declares a pointer to a pointer of type int
: int **var;
When a target value is indirectly pointed to by a pointer to a pointer, accessing that value requires that the asterisk operator be applied twice, as is shown below in the example:
int main () {
int var;
int *ptr;
int **pptr;
var = 3000;
/* take the address of var */
ptr = &var;
/* take the address of ptr using address of operator & */
pptr = &ptr;
/* take the value using pptr */
printf("Value of var = %d\n", var);
printf("Value available at *ptr = %d\n", *ptr);
printf("Value available at **pptr = %d\n", **pptr);
return 0;
}
Value of var = 3000
Value available at *ptr = 3000
Value available at **pptr = 3000
with a regular parameter, say int
, you get a local copy
with a pointer parameter, say int*
, you can modify what it points to
with a double pointer parameter, say int**
, you can modify the pointer itself, i.e. 'repoint' it
In C:
- Returning the address of the first element of a local array has undefined behavior (at least dereferencing it later is). You may use output parameters, that is, pass two pointers, and set the values inside:
void Calculate(int x, int y, int* prod, int* quot) {
*prod = x*y;
*quot = x/y;
}
Usage:
int x = 10, y = 2, prod, quot;
Calculate(x, y, &prod, ")
- Another thing you could do is pack your data into a struct
typedef struct {
int prod;
int quot;
} product_and_quot;
product_and_quot Calculate(int x, int y) {
product_and_quot p = {x*y, x/y};
return p;
}
in Objective-C:
- Pointers
- (void)convertA:(float)a B:(float)b C:(float) intoX:(float *)xOut Y:(float *)yOut Z:(float)zOut {
*xOut = 3*a + b;
*yOut = 2*b;
*zOut = a*b + 4*c;
}
and call it like this:
float x, y, z;
[self convertA:a B:b C:c intoX:&x Y:&y Z:&z];
- Another way is to create a struct and return it:
struct XYZ {
float x, y, z;
};
- (struct XYZ)xyzWithA:(float)a B:(float)b C:(float)c {
struct XYZ xyz;
xyz.x = 3*a + b;
xyz.y = 2*b;
xyz.z = a*b + 4*c;
return xyz;
}
Call it like this:
struct XYZ output = [self xyzWithA:a B:b C:c];
- Double pointers with objects
If you want to return more than one new object, your function should take pointers to the object pointer, thus:
- (void)mungeFirst:(NSString **)stringOne andSecond:(NSString **)stringTwo {
*stringOne = [NSString stringWithString:@"foo"];
*stringTwo = [NSString stringWithString:@"baz"];
}
- Blocks
You can return two values with help of block:
- (void)getUIControlles:(void (^)(UITextField *objTextFiled, UIView *objView))completionBlock {
UITextField * textFiled = nil;
/*
do code here for textfiled
*/
UIView * viewDemo = nil;
/*
do code here for Uiview.
*/
completionBlock (textFiled, viewDemo);
}
- (void)testMethod {
// Call function with following way.
[self getUIControlles:^(UITextField *objTextFiled, UIView *objView) {
// objTextFiled = This is your textfiled object
// objView = This is your view object
}];
}
Существует четыре способа передачи в функцию указателя
- Неконстантный указатель на неконстантные данные
- Неконстнатный указатель на константные данные
- Константный указатель на неконстантные данные
- Константный указатель на константные данные
It's figuring out if n
is either 0
or an exact power of two.
It works because a binary power of two is of the form 1000...000
and subtracting one will give you 111...111
. Then, when you AND
those together, you get zero, such as with:
1000 0000 0000 0000
& 111 1111 1111 1111
==== ==== ==== ====
= 0000 0000 0000 0000
Any non-power-of-two input value will not give you zero when you perform that operation. For example, let's try all the 3-bit combinations:
<----- binary ---->
n n n-1 n&(n-1)
--- --- --- -------
0 000 111 000 *
1 001 000 000 *
2 010 001 000 *
3 011 010 010
4 100 011 000 *
5 101 100 100
6 110 101 100
7 111 110 110
You can see that only 0
and the powers of two (1
, 2
and 4
) result in a 000-false
bit pattern, all others are non-zero or true
.
See the full Bit Twiddling Hacks document for all sorts of other wonderful (or dastardly, depending on your viewpoint) hackery.