Основные знания C ++ для успешного прохождения собеседования по C ++
Особенности
- Переполнение
- Преобразование между десятичным и двоичным числами
- Ошибки представления и округления чисел с плавающей запятой
- Побитовые операции, битовые квартиры, битовые маски
- Ключевое слово Extern и ссылка, статическое ключевое слово
- Неявные и явные преобразования типов
- Инициализация и присвоение структур
- Ключевое слово auto
- Инициализация массива
- Строка в стиле C
- Различия между указателями и фиксированными массивами
- Адрес массива, передача массива функции, передача ссылки на массив функции
- Указатель на постоянную переменную и константный указатель
- Ссылка и указатели
- Указатель на массив, указатель на многомерный массив, массивы указателей
- Передача аргументов функциям
- Указатели на функции
Оглавление
- Вступление
- Основы C ++
- Функции и файлы
- Переменные и основные типы данных
- Операторы
- Переменная область видимости и другие типы
- Массивы, строки, указатели и ссылки
- Функции
Вступление
Машинный язык
- Ограниченный набор инструкций, которые ЦП может понять напрямую, называется машинным кодом (например, _1 _) (или машинным языком или набор инструкций ).
- У разных ЦП разные наборы инструкций, инструкции, написанные для одного типа ЦП, не могли использоваться на ЦП, который не использовал один и тот же набор инструкций. Это означало, что программы обычно нельзя было переносить.
Язык ассемблера
- В языке ассемблера каждая инструкция идентифицируется коротким сокращением (а не набором битов), и могут использоваться имена и другие числа.
- Программа сборки должна быть переведена на машинный язык с помощью ассемблера, прежде чем она может быть выполнена компьютером.
- Язык ассемблера по-прежнему не очень переносим - программа, написанная на ассемблере для одного процессора, скорее всего, не будет работать на оборудовании, использующем другой набор инструкций.
Языки высокого уровня
- Большинство языков можно компилировать или интерпретировать. Хорошее сравнение преимуществ компиляторов и интерпретаторов можно найти здесь.
- Скомпилированный код обычно быстрее, чем интерпретируемый код, потому что выполняемые инструкции обычно предназначены только для самой программы, а не для самой программы плюс накладные расходы интерпретатора.
Система компиляции
Связывание объектных файлов и библиотек
- Сначала компоновщик берет все объектные файлы, сгенерированные компилятором, и объединяет их в единую исполняемую программу.
- Во-вторых, компоновщик также может связывать файлы библиотеки. Либо стандартная библиотека, либо другие библиотеки.
- В-третьих, компоновщик обеспечивает правильное разрешение всех межфайловых зависимостей.
- Динамическое связывание и статическое связывание
Структура памяти программы c / c ++
- Стек: компилятор автоматически выделяет и освобождает, сохраняет значения параметра функции, локальной переменной, вызовов функций. Операция аналогична стеку в структуре данных.
- Куча. Куча - это сегмент, в котором обычно выполняется динамическое выделение памяти. Обычно он выпускается программистом, если программист не выпускает его, он может быть выпущен ОС при выходе из программы.
- Неинициализированный сегмент данных (BSS): храните глобальные и статические переменные, которые имеют арифметический 0 - инициализированы ядром или не инициализированы. например ,
static int i;
- Инициализированный сегмент данных (данные): обычно называется сегментом данных. Сегмент данных - это часть виртуального адресного пространства программы, которая содержит глобальные, статические, буквальные, внешние переменные, инициализированные программистом.
- Текст: также известный как сегмент кода или просто текст, это один из разделов программы в объектном файле или в памяти, который содержит исполняемые инструкции. Обычно он доступен только для чтения.
#include "stdio.h" int a = 0;//Initialized data segment char *p1;//Uninitialized data segment,Zero Initialized, nullptr
void main(void) { int b;//stack, undefined Behavior, may be Initialized to anything
char s[] = "abc";//stack char *p2;//stack, can point to any address char *p3 = "123456";//123456/0:Initialized data segment, p3: stack static int c = 0;//Initialized data segment p1 = (char *)malloc(10); //10 bytes: Heap p2 = (char *)malloc(20); //20 bytes: Heap strcpy(p1, "123456");//123456/0: Initialized Data Segment,compiler may optimize it to store it at the same location where p3 points to. }
Основы C ++
Назначение и инициализация переменных
C ++ поддерживает три основных способа инициализации переменной, третий предпочтительнее.
- Копировать инициализацию
int width = 5; // copy initialization of value 5 into variable width
- Прямая инициализация
int width( 5 ); // direct initialization of value 5 into variable width
- Единая инициализация в C ++ 11
int width{ 5 }; // brace (uniform) initialization of value 5 into variable width int width{}; // zero initialization to value 0
Неинициализированные переменные и неопределенное поведение
- Неинициализированная переменная - это переменная, которой программа не присвоила значение (обычно посредством инициализации или присвоения). Использование значения, хранящегося в неинициализированной переменной, приведет к неопределенному поведению.
- Неопределенное поведение - это результат выполнения кода, поведение которого не определяется языком. Результатом может быть что угодно, в том числе и то, что ведет себя правильно.
Функции и файлы
Возвращаемые значения функции
- В спецификации C ++ явно указано, что функция main должна возвращать int. Некоторые компиляторы позволят вам недействительно указать тип возвращаемого значения void (они неявно вернут 0, если вы это сделаете), но вам не следует полагаться на это.
- Неспособность вернуть значение из функции с ненулевым типом возврата (отличным от main) приведет к неопределенному поведению.
- Функция может возвращать вызывающей стороне только одно значение при каждом вызове.
Предупреждение о порядке оценки аргументов функции
- Спецификация C ++ не определяет, будут ли вызовы функций оценивать аргументы слева направо или справа налево. Позаботьтесь о том, чтобы не вызывать функции, если порядок аргументов имеет значение. например
someFunction(a(), b()); // a() or b() may be called first
.
Введение в локальный охват
- локальные переменные создаются при входе в функцию и уничтожаются в обратном порядке создания при выходе из функции.
Форвардное заявление
- Форвардное объявление позволяет нам сообщить компилятору о существовании идентификатора до фактического определения идентификатора.
- Чтобы написать предварительное объявление для функции, мы используем оператор объявления, называемый прототипом функции. например
int add(int x, int y); // function prototype includes return type, name, parameters, and semicolon. No function body! int add(int, int); // valid function prototype
- Если сделано предварительное объявление и функция вызывается, но программа никогда не определяет функцию, программа будет компилироваться нормально, но компоновщик будет жаловаться, что не может разрешить вызов функции. Если функция никогда не вызывается, программа компилируется и работает нормально.
- Определение фактически реализует (для функций или типов) или создает экземпляр (для переменных) идентификатора. Требуется определение, чтобы удовлетворить компоновщик. Если вы используете идентификатор без определения, компоновщик выдаст ошибку.
- Объявление - это инструкция, которая сообщает компилятору о существовании идентификатора и информации о его типе. Объявление - это все, что нужно компилятору.
- В C ++ все определения также служат объявлениями, обратное неверно: эти объявления, которые не являются определениями, называются чистыми объявлениями, например прототип функции, предварительные объявления для переменных и объявления типов.
- В случае объявления в памяти не резервируется место для какой-либо переменной. Определение означает, что помимо всего того, что делает объявление, в памяти дополнительно резервируется место.
- Примеры декларации
extern int a; // Declaring a variable a without defining it struct Example { int a; int b; }; // Declaring a struct int myFunc (int a, int b); // Declaring a function
- Примеры определения
int a; int b = 0; int myFunc (int a, int b) { return a + b; } struct Example example;
Значение переменной по умолчанию
- Объявленная переменная может быть инициализирована нулевым значением, инициализированным значением или инициализированной по умолчанию.
- Нулевая инициализация выполняется в следующих ситуациях:
- Ноль инициализируется для каждой именованной переменной со сроком хранения статического или локального потока, который не подлежит постоянной инициализации (начиная с C ++ 14), перед любой другой инициализацией.
- Ноль инициализируется как часть последовательности инициализации значения для типов, не являющихся классами, и для членов типов классов, инициализированных значением, которые не имеют конструкторов.
- Когда символьный массив инициализируется очень коротким строковым литералом, остальная часть массива инициализируется нулем.
static int a; //Zero Initialized int b; //Zero Initialized int c[3]; //Zero Initialized vector<int>d(3); //Zero Initialized int main() { int i; //Undefined Behavior, Might be Initialized to anything static int j; //Zero Initialized vector<int>v(3); //Zero Initialized, 0,0,0 int e[3] = {4}; //4,0,0 int f[3]; //Undefined Behavior, Might be Initialized to anything unordered_map<char, int>um; um['a']++; //um['a'] is 1 }
Программы с несколькими файлами кода
- Когда компилятор компилирует многофайловую программу, он может компилировать файлы в любом порядке. Кроме того, он компилирует каждый файл индивидуально, не зная, что находится в других файлах. например предварительное объявление для func необходимо, чтобы вызывающая сторона знала, что func определена в другом файле CPP.
Конфликты имен и пространство имен
- Большинство конфликтов имен возникают в двух случаях:
1. Два (или более) определения функции (или глобальной переменной) вводятся в отдельные файлы, которые компилируются в одну программу. Это приведет к ошибке компоновщика.
2. Два (или более) определения функции (или глобальной переменной) вводятся в один и тот же файл (часто через #include). Это приведет к ошибке компилятора.
- Пространство имен - это группа идентификаторов, которые используются для уменьшения вероятности конфликтов имен.
- Два способа использования пространства имен: явный квалификатор пространства имен (например, std :: cout), директива (например, использование пространства имен std;). Лучше всего использовать явный квалификатор пространства имен, поскольку любой идентификатор, который мы определяем, может конфликтовать с любым идентификатором с таким же именем в пространстве имен.
Перевод и препроцессор (Ссылка)
- Когда препроцессор запускается, он просматривает файл кода (сверху вниз) в поисках директив препроцессора. Директивы препроцессора (часто называемые просто директивами) - это инструкции, которые начинаются с символа # и заканчиваются новой строкой (НЕ точкой с запятой). Это могут быть # include и макроопределения.
- Объем
#define
- это весь исходный файл, даже если он определен в функции. Используйте#undef
, чтобы ограничить его объем. - Препроцессор никоим образом не модифицирует исходные файлы кода - скорее, все изменения текста, сделанные препроцессором, временно происходят в памяти при каждой компиляции файла кода.
Заголовочные файлы
- Основная цель файла заголовка - распространять объявления в файлы кода.
- Когда вы #include файла, содержимое включенного файла вставляется в момент включения. Это обеспечивает удобный способ извлечения объявлений из другого файла.
- Когда дело доходит до функций и объектов, файлы заголовков обычно содержат только объявления функций и объектов, а не определения функций и объектов (в противном случае может произойти нарушение одного правила определения).
std::cout
объявлен в заголовкеiostream
, но определен как часть стандартной библиотеки C ++, которая автоматически связывается с вашей программой на этапе компоновщика. - Используйте угловые скобки, чтобы включить файлы заголовков, поставляемые с компилятором. Используйте двойные кавычки для включения любых других файлов заголовков.
- Почему у
iostream
нет.h
расширения? комитет ANSI переместил все функции стандартной библиотеки в пространство имен std, чтобы помочь избежать конфликтов имен с определенными пользователем идентификаторами и был представлен новый набор файлов заголовков, которые используют те же имена, но не имеют расширения .h, так что старые программы, содержащие#include <iostream.h>
, не нужно переписывать, а новые программы можно#include <iostream>
.
защита заголовка
- Защита заголовков предназначена для того, чтобы содержимое данного файла заголовка не копировалось более одного раза в любой отдельный файл e, чтобы предотвратить дублирование определений. Форма такая:
#ifndef SOME_UNIQUE_NAME_HERE #define SOME_UNIQUE_NAME_HERE // your declarations (and certain types of definitions) here #endif or you can use the following, but it is not an official part of the C++ language, and not all compilers support it #pragma once
- Цель защиты заголовка - предотвратить получение файла кода более одной копии защищенного заголовка. По своей конструкции защитные элементы заголовка не предотвращают включение данного файла заголовка (однократно) в отдельные файлы кода. Это также может вызвать непредвиденные проблемы: два файла CPP могут получить копию определения одной и той же функции, и программа будет компилироваться, но компоновщик будет жаловаться на то, что ваша программа имеет несколько определений одного и того же идентификатора функции. "Подробнее".
- Повторяющиеся объявления допустимы, поскольку объявление может быть объявлено несколько раз без инцидентов, но даже если ваш файл заголовка состоит из всех объявлений (без определений), рекомендуется включить защиту жатки.
- Есть довольно много случаев, когда необходимо поместить определения, не связанные с функциями (в основном, не определяют функции в файлах заголовков) в файл заголовка. Например, C ++ позволит вам создавать свои собственные типы. Эти определяемые пользователем типы обычно определяются в файлах заголовков.
Переменные и основные типы данных
Обращение к памяти
- В современных компьютерах каждый бит не имеет своего адреса. Наименьшая адресуемая единица памяти называется байтом.
- Поскольку все данные на компьютере представляют собой просто последовательность битов, мы используем тип данных (часто называемый для краткости «типом»), чтобы сообщить нам, как интерпретировать содержимое памяти тем или иным образом.
Переменные размеры и оператор sizeof
- Размер переменной ограничивает объем информации, которую она может хранить - переменные, которые используют больше байтов, могут содержать более широкий диапазон значений.
- Размер данного типа данных зависит от компилятора и / или архитектуры компьютера.
- Типичный размер каждого типа:
bool: 1 bytes char: 1 bytes wchar_t: 4 bytes char16_t: 2 bytes char32_t: 4 bytes short: 2 bytes int: 4 bytes long: 8 bytes, at least 32 bits long long: 8 bytes, at least 64 bits float: 4 bytes double: 8 bytes long double: 16 bytes size_t: 8 bytes
Целые числа
- N-битовая переменная со знаком имеет диапазон от
-(2^(n-1))
до(2^(n-1))-1
. N-битовая беззнаковая переменная имеет диапазон от0
до(2^n)-1
. например
char (-2⁷ ~ 2⁷-1, which is -128 ~ 127) (1000,0000, 0111,1111) unsigned char (0~2⁸ -1, which is 0 ~ 255) (0000,0000, 1111,1111)
- Переполнение происходит, когда биты теряются из-за того, что переменной не выделено достаточно памяти для их хранения. например Если мы попытаемся поместить десятичное значение 21 (
10101
) в нашу 4-битную переменную, 4 крайних правых бита (0101
) войдут в переменную, а крайний левый (1
) просто потеряется. Теперь наша переменная содержит0101
, десятичное значение 5. - Результаты переполнения предсказуемы только для целых чисел без знака. Переполнение целых или нецелых чисел со знаком (например, чисел с плавающей запятой) может привести к разным результатам в разных системах.
- size_t гарантированно беззнаковый и имеет не менее 16 бит, но в большинстве систем будет эквивалентен ширине адреса приложения, которая составляет 32 бита в 32-битных приложениях и 64 бита в 64-битных приложениях. битовые приложения.
- Чтобы облегчить кроссплатформенную переносимость, C99 определил набор целых чисел фиксированной ширины (в заголовке
stdint.h
,#include <cstdint>
в C ++ 11), которые гарантированно имеют одинаковый размер в любой архитектуре. например:
- std :: int8_t и std :: uint8_t могут или не могут вести себя как символы. например
int8_t myint = 65; cout << myint; // most systems, it will print ‘A’ (treating myint as a char). However, on some systems, this may print 65 as expected.
- Три разных типа данных с плавающей запятой: float, double и long double.
- По умолчанию литералы с плавающей запятой по умолчанию имеют тип double. Суффикс
f
используется для обозначения литерала типа float.double y(5.0); float z(5.0f);
- Научная запись - удобное сокращение для краткого написания длинных чисел. Мы используем буквы «e» или «E» для обозначения части уравнения, умноженной на «10 в степени». Например,
1.2 x 10^4
будет записано как1.2e4
.5e-2
эквивалентно5 * 10^-2
, то есть5 / 10^2
или0.05
. - Цифры в мантиссе (часть перед E, например, 1.2 в 1.2e4) называются значащими цифрами. Количество значащих цифр определяет точность числа. Чем больше цифр в мантиссе, тем точнее число.
- точность числа с плавающей запятой определяет, сколько значащих цифр оно может представлять без потери информации.
cout
имеет точность по умолчанию 6, то есть предполагает, что все переменные с плавающей запятой имеют значение только до 6 цифр, и, следовательно, он будет усекать все после этого. например
float f = 9.87654321f; cout << f; //9.87654
- Числа с плавающей запятой часто имеют небольшие ошибки округления, даже если у числа меньше значащих цифр, чем точность. Часто они остаются незамеченными, потому что они такие маленькие, и потому, что числа обрезаются для вывода. Следовательно, сравнение чисел с плавающей запятой может не дать ожидаемых результатов. Выполнение математических операций над этими значениями приведет к увеличению ошибок округления.
double d{0.1}; std::cout << d << '\n'; // use default cout precision of 6, 0.1 std::cout << std::setprecision(17); std::cout << d << '\n'; //0.10000000000000001
- Представление поплавка в памяти. Экспоненты могут быть положительными или отрицательными, но вместо того, чтобы зарезервировать другой знаковый бит, они закодированы так, что
10000000
представляет0
, поэтому00000000
представляет-128
, а11111111
представляет127
.
0 10000010 11001001000011111100111 ^ ^ ^ | | | | | +--- significand = 0.7853975... | | | +------------------- exponent = 2 (130 - 128) | +------------------------- sign = 0 (positive) value= -1(sign) * 2(exponent) * (significand) value= -10 * 22 * 0.7853975... value= 3.14159...
Символы
- Несмотря на то, что тип данных char является целым числом (и, следовательно, следует всем нормальным целочисленным правилам), мы обычно работаем с символами иначе, чем с обычными целыми числами. Переменная типа char содержит 1-байтовое целое число. Однако вместо того, чтобы интерпретировать значение char как целое число, значение переменной char обычно интерпретируется как символ ASCII (0–127).
- Символ ASCII определяет конкретный способ представления английских символов (плюс несколько других символов) в виде чисел от 0 до 127 (называемых кодом ASCII или кодовая точка). Например, символ
‘a’
- это код97
.‘A’
- это код65
.
cout << 'a' - 'A' << endl; //32 char upper = 'B'; cout << (char)(upper + 'a' - 'A') << endl; //upper to lower, 'b'
- При использовании
cout
для печати символаcout
выводит переменную char как символ ASCII вместо числа. например
char ch(97); // even though we're initializing ch with an integer cout << ch; // a
- Чтобы вывести char как число вместо символа, используйте
static_cast<int>(ch)
. - Когда
cout
используется для вывода, вывод может быть буферизован. И‘\n’
, иstd::endl
переместят курсор на следующую строку. Вдобавокstd::endl
также будет гарантировать, что любой вывод из очереди действительно выводится перед продолжением.
Литералы
- По умолчанию буквальные константы с плавающей запятой имеют тип double. Чтобы преобразовать их в значение с плавающей запятой, следует использовать суффикс f (или F).
float f = 5.0f; // 5.0 has type float
. - Чтобы использовать восьмеричный литерал, префиксный литерал с
0
и префиксный литерал с0x
для использования шестнадцатеричного литра. В C ++ 14 мы можем назначать двоичные литералы с помощью префикса0b
: например:
int x = 012; // 0 before the number means this is octal std::cout << x; // 10 int x = 0xF; // 0x before the number means this is hexadecimal std::cout << x; // 15 int bin = 0b11; // assign binary 0000 0011 to the variable
Константы, constexpr и символьные константы
- Определение константной переменной без ее инициализации вызовет ошибку компиляции.
- Постоянные переменные могут быть инициализированы неконстантными значениями:
int age; cin >> age; const int usersAge (age); // usersAge can not be changed
- В C ++ 11 появилось новое ключевое слово constexpr, которое гарантирует, что константа должна быть константой времени компиляции. Идея состоит в том, чтобы потратить время при компиляции и сэкономить время во время выполнения. например
constexpr int
product(int
x, int
y){ return
(x * y); }
constexpr int maxNum { 30 };
- Символьная константа - это имя, присвоенное постоянному буквальному значению, которое может быть достигнуто с помощью
#define
илиconst
. Поскольку константы#define
не отображаются в отладчике и с большей вероятностью будут иметь конфликты имен, поскольку его область действия - весь файл, поэтомуconst
предпочтительнее. - Если данная символическая константа должна использоваться во всем вашем коде, вы можете создать файл заголовка для хранения констант и объявить пространство имен в файле, добавить константы внутри пространства имен и
#include
файл заголовка, где бы он вам ни понадобился.
Операторы
Приоритет операторов и ассоциативность
- Порядок, в котором операторы вычисляются в составном выражении, называется приоритетом оператора. Если два оператора с одинаковым уровнем приоритета соседствуют друг с другом в выражении, правила ассоциативности операторов сообщают компилятору, следует ли оценивать операторы слева направо (например,
+
) или из справа налево (например,=
).
Арифметические операторы
- Если один или оба операнда являются значениями с плавающей запятой, оператор деления выполняет деление с плавающей запятой. Деление с плавающей запятой возвращает значение с плавающей запятой, а дробь сохраняется. Например, 7,0 / 3 = 2,333, 7 / 3,0 = 2,333 и 7,0 / 3,0 = 2,333.
- Использование static_cast ‹› для деления с плавающей запятой целых чисел.
int x = 7; int y = 4; cout << "double / int = " << static_cast<double>(x) / y << "\n"; // 1.75
- До C ++ 11, если любой из операндов целочисленного деления отрицательный, компилятор может округлять в большую или меньшую сторону! Например, -5 / 2 может быть равно -3 или -2 (технически это -2,5), в зависимости от того, как компилятор выполняет округление. Однако большинство современных компиляторов усекают до 0 (поэтому -5 / 2 будет равно -2). Спецификация C ++ 11 изменила это, чтобы явно определить, что целочисленное деление всегда должно усекаться до 0 (или, проще говоря, дробный компонент отбрасывается, 0,5 в -2,5). Кроме того, спецификация C ++ 11 ужесточает это, так что
a % b
всегда разрешается в знакa
. - В C ++ нет оператора экспоненты, который в математике равен 70_. Чтобы сделать экспоненты в C ++, # включите заголовок
<cmath>
и используйте функциюpow()
.double x = std::pow(3.0, 4.0); // 3 to the 4th power
. Из-за ошибок округления в числах с плавающей запятой результатыpow()
могут быть неточными (немного меньше или больше, чем вы ожидали).
Операторы увеличения / уменьшения и побочные эффекты
- Считается, что функция или выражение имеют побочный эффект, если они изменяют какое-либо состояние (например, любую сохраненную информацию в памяти), вводят или выводят данные или вызывают другие функции, которые имеют побочные эффекты. Побочные эффекты могут привести к неожиданным результатам:
int x = 5; int value = add(x, ++x); // is this 5 + 6, or 6 + 6? It depends on what order your compiler evaluates the function arguments in int x = 1; x = x++; cout << x; // The above is undefined. // If the ++ is applied to x before the assignment, the answer will be 1, if not, it is 2.
Оператор-запятая
x, y
, вычисляет x, затем y, возвращает значение y. напримерint z = (++x, ++y); // increment x and y, z would be assigned the result of evaluating ++y
. Следует избегать оператора запятой, за исключением циклов for.
Логические операторы
- Если первый операнд для логического И ложь, второй операнд не нужно оценивать, и он возвращает ложь. Точно так же, если первый операнд для логического ИЛИ - истина, второй операнд не нуждается в оценке, и он возвращает истину. Это известно как оценка короткого замыкания, поэтому операторы, вызывающие побочные эффекты (++, - -), не должны использоваться в составных выражениях.
Преобразование десятичного числа в двоичное
- По сути, это включает в себя постоянное деление на
2
и запись остатков. Двоичное число строится в конце из остатков снизу вверх. например (148 в двоичном формате):
148 / 2 = 74 r0 74 / 2 = 37 r0 37 / 2 = 18 r1 18 / 2 = 9 r0 9 / 2 = 4 r1 4 / 2 = 2 r0 2 / 2 = 1 r0 1 / 2 = 0 r1 Writing all of the remainders from the bottom up: 1001 0100
Числа со знаком и дополнение до двух
- Целые числа со знаком обычно хранятся с использованием метода, известного как дополнение до двух. В дополнении до двух самый левый (наиболее значимый) бит используется как знаковый бит. 0 означает положительный результат, 1 - отрицательный.
- Отрицательные числа со знаком сохраняются в виде числа, обратного положительному числу плюс 1. Например, преобразуйте -5 в двоичное дополнение до двух:
Binary representation for 5: 0000 0101 Invert all of the bits: 1111 1010 Then we add 1: 1111 1011 To convert 1111 1011 back to integer: -128 + 64 + 32 + 16 + 8 + 2 + 1 = -5
- Компилятор использует тип переменной для преобразования базового двоичного представления обратно в ожидаемую форму. Таким образом, если тип переменной был целым числом без знака, он знал бы, что 1011 0100 был стандартным двоичным и должен быть напечатан как 180. Если бы переменная была целым числом со знаком, он знал бы, что 1011 0100 было закодировано с использованием дополнения до двух, и должно быть напечатано как -76.
- Преобразование чисел с плавающей запятой из / в двоичное.
Побитовые операторы
- Почему тип bool занимает 1 байт? Это связано с тем, что переменные нуждаются в уникальных адресах, а адрес памяти можно адресовать только в байтах. Bool использует 1 бит, а остальные 7 теряются. Основная цель побитового использования - экономия памяти.
- Есть 6-битные операторы манипуляции
- оператор сдвига влево (‹x = x << 1; можно написать
x <<= 1;
3 = 0011 3 << 1 = 0110 = 6 (3 * 2^1) 3 << 2 = 1100 = 12 (3 * 2^2) 3 << 3 = 1000 = 8 (1 bit is dropped)(should be 24 (3 * 2^3) if we use 8 bits) 12 = 1100 12 >> 1 = 0110 = 6 (12 / 2^1) 12 >> 2 = 0011 = 3 (12 / 2^2) 12 >> 3 = 0001 = 1 (1 bit is dropped)
- Оператор побитового НЕ (~) просто переворачивает каждый бит с 0 на 1 или наоборот. Результат побитового НЕ зависит от размера вашего типа данных.
Assuming 4 bits: 4 = 0100 ~4 = 1011 = 11 (decimal) Assuming 8 bits: 4 = 0000 0100 ~4 = 1111 1011 = 251 (decimal)
- Побитовое И (&) и побитовое ИЛИ (|) работают аналогично их логическим И и логическим ИЛИ. Однако вместо того, чтобы оценивать одно логическое значение, они применяются к каждому биту.
5 | 6: 0 1 0 1 // 5 0 1 1 0 // 6 ------- 0 1 1 1 // 7 5 & 6: 0 1 0 1 // 5 0 1 1 0 // 6 -------- 0 1 0 0 // 4
- XOR оценивается как истина (1), если один и только один из его операндов истинен (1). Если ни один из них или оба не верны, он оценивается как 0. Рассмотрим выражение
6 ^ 3
:
6 ^ 3: 0 1 1 0 // 6 0 0 1 1 // 3 ------- 0 1 0 1 // 5
- При работе с битовыми операторами используйте целые числа без знака в качестве результатов применения операторов побитового сдвига к знаку целое число зависит от компилятора.
- Битовые флаги: мы можем использовать побитовые операторы для установки, очистки и запроса отдельных битов в байте, рассматривая каждый как отдельное логическое значение. Эти отдельные биты называются битовыми флагами.
- Битовые маски. Принципы для битовых флагов могут быть расширены, чтобы включать, выключать, переключать или запрашивать сразу несколько битов за одну битовую операцию. Когда мы объединяем отдельные биты вместе с целью изменения их как группы, это называется битовой маской.
- Есть три способа определения битовых флагов:
// Define 8 separate bit flags (these can represent whatever you want) // C++14: const unsigned char option0 = 0b0000'0001; // represents bit 0 const unsigned char option1 = 0b0000'0010; // represents bit 1 ... const unsigned char option7 = 0b1000'0000; // represents bit 1 // C++11 or earlier: const unsigned char option0 = 0x1; // hex for 0000 0001 const unsigned char option1 = 0x2; // hex for 0000 0010 ... // or const unsigned char option0 = 1 << 0; // 0000 0001 const unsigned char option1 = 1 << 1; // 0000 0010 ...
- Есть четыре операции (установка, сброс, переключение, запрос) для битовых флагов.
- Установить и сбросить битовые флаги (с | и &, ~)
// If there are 8 options, use 8-bit value to hold our 8 options // Each bit in myflags corresponds to one of the options defined above unsigned char myflags = 0; // all bits turned off to start myflags |= option4; // turn option 4 on, myflags is 0001 0000 myflags &= ~option4; // turn option 4 off, myflags is 0000 0000
- Переверните битовые флаги (с XOR)
myflags ^= option4; // flip option4 from on to off, or vice versa myflags ^= (option4 | option5); // flip options 4 and 5 at the same time
- Битовые флаги запроса (с &)
if (myflags & option4) // option4 has constant value 00010000, the condition will be true if the 5th bits from leftmost is set to 1. cout << "myflags has option 4 set"; if (!(myflags & option5)) cout << "myflags does not have option 5 set";
- Битовые флаги в реальной жизни и чем они полезны?
- Когда у вас есть много наборов идентичных битовых флагов со 100
myflag
переменными, стоимость вашей памяти будет 108 (8 байтов для определения параметров и 1 байт для каждойmyflags
переменной) байтов вместо 800 байтов (100 x 8). - Представьте, что у вас есть функция, которая может принимать любую комбинацию из 32 различных вариантов:
// without bigflag someFunction(false, false, ..., true); // 32 parameters // with bigflag void someFunction(unsigned int options); someFunction(option10 | option32); // 1 parameter
- std :: bitset (#include ‹bitset›) может помогают нам управлять битовыми флагами. Bitset предоставляет четыре функции (проверка, установка, сброс, переворот). например
// Note that with std::bitset, our options correspond to bit indices, not bit patterns const int option0 = 0; const int option7 = 7; bitset<8> bits(0x2); // we need 8 bits, start with bit pattern 0000 0010 bits.set(option4); // set bit 4 to 1 (now we have 0001 0010) bits.flip(option5); // flip bit 5 (now we have 0011 0010) bits.reset(option5); // set bit 5 back to 0 (now we have 0001 0010) cout << "Bit 4 has value: " << bits.test(option4) << '\n'; // 1 cout << "All the bits: " << bits << '\n'; //00010010
- Чтобы изменить несколько бит за одну операцию, мы можем использовать битовую маску:
const unsigned int lowMask = 0xF; // bit mask to keep low 4 bits (hex for 0000 0000 0000 1111) cout << "Enter an integer: "; int num; cin >> num; //151 num &= lowMask; // remove the high bits to leave only the low bits cout << "The 4 low bits have value: " << num << '\n'; //7 151 is 1001 0111 in binary. lowMask is 0000 1111 in 8-bit binary. 1001 0111 & 0000 1111 = 0000 0111, which is 7 decimal.
Переменная область видимости и другие типы
Локальные переменные, область действия и продолжительность
- Переменные, определенные внутри функции, называются локальными переменными. Локальные переменные имеют автоматическую продолжительность, они создаются в момент определения и уничтожаются при выходе из блока.
- Хотя параметры функции не определены внутри блока, принадлежащего функции, в большинстве случаев можно считать, что они имеют область видимости блока.
- Переменные, объявленные вне функции, называются глобальными переменными. Глобальные переменные имеют статическую продолжительность, что означает, что они создаются при запуске программы и уничтожаются при ее завершении.
- Локальные переменные с тем же именем, что и глобальная переменная, скрывают глобальную переменную внутри блока, в котором объявлена локальная переменная. Однако оператор глобальной области
(::)
может использоваться, чтобы сообщить компилятору, что вы имеете в виду глобальную переменную. версия вместо локальной версии. например ,—-(::value);
- Внутренняя и внешняя связь через ключевые слова static и extern (см. Пример ниже), по умолчанию неконстантные переменные, объявленные вне функции, считаются внешними (используйте static, чтобы сделать их внутренними). Однако предполагается, что константные переменные, объявленные вне функции, являются внутренними (используйте extern, чтобы сделать их внешними).
static int g_x; // g_x is static, and can only be used within this file //A.cpp extern double g_y(9.8); // g_y is external, and can be used by other files, extern keyword can be omitted here. // Note: those other files will need to use a forward declaration to access this external variable //B.cpp extern double g_y; //forward declaration for g_y
- Как показано в примере выше, ключевое слово extern имеет разное значение в разных контекстах. В некоторых контекстах extern означает «предоставить этой переменной внешнюю связь» (A.cpp). В других контекстах extern означает «это предварительное объявление для внешней переменной, которая определена где-то еще» (B.cpp). Когда extern используется с переменной, она только объявляется, но не определена. Когда при инициализации объявляется переменная extern, она также используется как определение переменной.
- Функции имеют то же свойство связывания, что и переменные. По умолчанию функции всегда связаны с внешней связью, но могут быть настроены на внутреннюю привязку с помощью ключевого слова static. Для предварительных объявлений функций не требуется ключевое слово extern.
- Не внешние объекты и функции в разных файлах считаются разными сущностями, даже если их имена и типы идентичны.
- Если мы определим g лобальные символьные константы в файле заголовка, это может привести к длительному перестроению, если файл включен в несколько файлов. Мы можем избежать этой проблемы, превратив эти константы в глобальные переменные const и изменив файл заголовка так, чтобы он содержал только предварительные объявления переменных.
//constants.cpp: extern const double pi(3.14159); //actual global variables //constants.h: extern const double pi; //forward declarations only
Подробнее о глобальных переменных
- Некоторые причины использовать неконстантные глобальные переменные:
- Программа использует базу данных для чтения и записи данных.
- В программе есть журнал ошибок (или журнал отладки), куда вы можете вывести информацию об ошибках (или отладке).
- Звуковая библиотека
- Несколько советов по использованию неконстантной глобальной переменной:
- Префикс всех глобальных переменных с помощью «g_» и / или поместите их в пространство имен.
- Вместо того, чтобы разрешать прямой доступ к глобальной переменной, лучше «инкапсулировать» переменную. Во-первых, сделайте переменную статической, чтобы к ней можно было получить доступ только в объявленном файле. Во-вторых, предоставьте внешние глобальные «функции доступа» для работы с переменной.
- В-третьих, при написании автономной функции, использующей глобальную переменную, не используйте ее непосредственно в теле функции. Передайте его как параметр и используйте параметр.
- Структурируйте свой код так, чтобы предполагалось, что значение глобальной переменной может измениться. Относитесь к глобальному как можно больше как только для чтения
Статическое ключевое слово
См. Также ссылку здесь.
- Статические переменные: глобальные переменные, локальные переменные в функции, переменные в классе.
- При применении к глобальной переменной ключевое слово static определяет глобальную переменную как имеющую внутреннюю связь, что означает, что переменная не может быть экспортирована в другие файлы.
- При применении к локальной переменной ключевое слово static определяет локальную переменную как имеющую статическую продолжительность, что означает, что переменная будет создана только один раз, и не будет уничтожена до конца программы (а не до конца функции! ), он имеет такую же продолжительность с глобальными переменными.
- При применении к переменной в классе она инициализируется только один раз, так как им выделяется место в отдельном статическом хранилище, поэтому статические переменные в классе совместно используются объектами.
- Статические члены класса: объекты класса и функции в классе.
- При применении к объекту класса он ведет себя так же, как статическая локальная переменная. например:
static CMylass a;
- При применении к функции класса статическая функция-член не зависит от объекта класса. Его можно вызвать с помощью имени класса и оператора разрешения области видимости (например,
CMyClass::printMsg();
). Статическим функциям-членам разрешен доступ только к статическим элементам данных или другим статическим функциям-членам.
Сфера охвата, продолжительность и сводка по связи
Пространства имен
- По умолчанию глобальные переменные и обычные функции определены в глобальном пространстве имен. Можно использовать оператор разрешения области без какого-либо пространства имен (например,
::doSomething
), и он относится к глобальному пространству имен. Использование пользовательского пространства имен:
//foo.h namespace Foo { // This doSomething() belongs to namespace Foo int doSomething(int x, int y) { return x + y; } } //main.cpp cout << Foo::doSomething(4, 3);
- Разрешено объявлять блоки пространства имен в нескольких местах (либо в нескольких файлах, либо в нескольких местах в одном файле). Все объявления в блоке пространства имен считаются частью пространства имен.
Использование инструкции
- Разница между использованием объявления
using std::out;
и директивой использованияusing namespace std;
using std::out;
Если есть конфликт имен междуstd::cout
и каким-либо другим использованиемcout
,std::cout
будет предпочтительнее.using namespace std;
Если возникает конфликт имен междуstd::cout
и каким-либо другим использованиемcout
, компилятор пометит это как ошибку.
- Если внутри блока используется объявление using или директива using, оператор using применяется только внутри этого блока (он следует обычным правилам области видимости). Таким образом, лучше избегать операторов using вне функции (в глобальной области видимости).
Неявное преобразование типа (принуждение)
- Неявное преобразование типа происходит, когда компилятор ожидает значение одного типа, но получает значение другого типа.
- Цифровое продвижение - это преобразование меньшего типа (обычно целочисленного или с плавающей запятой) в более крупный аналогичный (целочисленный или с плавающей запятой) тип. Рекламные акции обычно включают расширение двоичного представления числа (например, для целых чисел добавление ведущих нулей).
long l(64); // widen the integer 64 into a long double d(0.12f); // promote the float 0.12 into a double
- Числовое преобразование - это преобразование большего типа в меньший или между разными типами. Преобразования требуют преобразования базового двоичного представления в другой формат.
double d = 3; // convert integer 3 to a double (between different types) short s = 2; // convert integer 2 to a short (from larger to smaller type)
- При оценке выражений компилятор разбивает каждое выражение на отдельные подвыражения. Арифметические операторы требуют, чтобы их операнды были одного типа. Для этого компилятор использует следующие правила:
- Если операнд является целым числом, которое уже, чем int, оно подвергается целочисленному преобразованию (как описано выше) в int или unsigned int.
- Если операнды по-прежнему не совпадают, компилятор находит операнд с наивысшим приоритетом и неявно преобразует другой операнд в соответствие.
double d(4.0); short s(2); cout << d + s; //6.0, double /*In this case, the short undergoes integral promotion to an int. However, the int and double still do not match. Since double is higher on the hierarchy of types, the integer 2 gets converted to the double 2.0, and the doubles are added to produce a double result.*/
Явное преобразование типа (приведение)
- Явное преобразование типа происходит, когда пользователь использует приведение типа для преобразования значения из одного типа в другой тип.
- В C ++ существует 5 различных типов приведения типов: приведения в стиле C, статические приведения, константные, динамические и переинтерпретированные.
int i1 = 10; int i2 = 4; float f = (float)i1 / i2; //C-style cast, convert i1 to float float f = float(i1) / i2; // C-style cast, with a more function-call like syntax float f = static_cast<float>(i1) / i2; //static_cast, convert i1 to float
- Резюме явного преобразования типа:
- Приведения типов в стиле C следует избегать, поскольку они не проверяются компилятором во время компиляции и могут удалить const.
- static_cast обеспечивает проверку типа во время компиляции, что затрудняет случайную ошибку. В большинстве этих случаев следует использовать static_cast вместо преобразования в стиле C.
- dynamic_cast выполняется во время выполнения, что особенно полезно в сочетании с полиморфными классами.
- Константные приведения и переинтерпретировать обычно следует избегать, поскольку они полезны только в редких случаях и могут быть вредными при неправильном использовании.
Введение в std :: string
- Вместо использования
cin >> str
для ввода текста используйтеgetline
для ввода текста.
std::cin >> name; // read characters up to the first whitespace std::getline(std::cin, name); // read a full line of text into name
- Используйте длину, чтобы получить длину строки:
myName.length()
.
Нумерованные типы
- Использование перечисления
// Define a new enumeration named Color enum Color { COLOR_RED, // assigned 0 COLOR_BLUE, // assigned 1 COLOR_WHITE, // assigned 2 COLOR_CYAN, // assigned 3 }; // end with a semicolon // Define a few variables of enumerated type Color Color paint = COLOR_WHITE; Color house(COLOR_BLUE); Color apple { COLOR_RED };
- Перечислимые типы полезны для документации кода и удобства чтения, когда вам нужно представить конкретный, предопределенный набор состояний. например коды ошибок.
- Классы Enum работают как перечисления, но обеспечивают большую безопасность типов, компилятор больше не будет неявно преобразовывать значения перечислителя в целые числа, что может предотвратить некоторые проблемы:
if (color == fruit) // The compiler will compare color and fruit as integers if color and fruit are of type enum
.
enum class Color // "enum class" defines this as a scoped enumeration instead of a standard enumeration { RED, // RED is inside the scope of Color BLUE }; Color color = Color::RED; // note: RED is not directly accessible any more, we have to use Color::RED
Определения типов и псевдонимы типов
- typedefs можно использовать, чтобы скрыть сведения о платформе. например int8_t будет 8-разрядным целым числом со знаком, int16_t - 16-разрядным целым числом со знаком, а int32_t - 32-разрядным целым числом со знаком. На машинах, где целые числа составляют всего 2 байта, INT
static int i;
BYTES может быть #defined
#ifdef INTstatic int i;
BYTES
typedef char int8_t;
typedef int int16_t;
typedef long int32_t;
#else
typedef char int8_t;
typedef short int16_t;
typedef int int32_t;
#endif
- Для простоты в C ++ введены псевдонимы типов, и они предпочтительны.
using distance_t = double; // define distance_t as an alias for type double which is functionally equivalent with: typedef double distance_t;
Структуры
- Объявления структур, в настоящее время память не выделяется.
struct Employee { short id; int age; double wage; };
- Определите переменную структуры, выделяется память.
Employee joe; cout << joe.age; // access member of struct
- Инициализация структур
// initializer list before C++11 Employee joe = { 1, 32, 60000.0 }; // joe.id = 1, joe.age = 32, joe.wage = 60000.0 Employee frank = { 2, 28 }; // frank.id = 2, frank.age = 28, frank.wage = 0.0 (default initialization) // uniform initialization in C++11 Employee joe { 1, 32, 60000.0 }; // joe.id = 1, joe.age = 32, joe.wage = 60000.0 Employee frank { 2, 28 }; // frank.id = 2, frank.age = 28, frank.wage = 0.0 (default initialization)
- Присваивание структурам
// Prior to C++11 Employee joe; joe.id = 1; joe.age = 32; joe.wage = 60000.0; // C++11 only Employee joe; joe = { 1, 32, 60000.0 };
- Размер структуры и выравнивание структуры данных. Обычно размер структуры является суммой размеров всех ее членов, но не всегда. Размер структуры будет по крайней мере таким же большим, как размер всех содержащихся в ней переменных. Но могло быть и больше. По соображениям производительности компилятор иногда добавляет пробелы в структуры (это называется заполнением). Подробнее о выравнивании структуры данных.
Ключевое слово auto
- Начиная с C ++ 11, ключевое слово auto может использоваться вместо типа переменной при инициализации для выполнения вывода типа. Другого использования ключевого слова auto, как правило, следует избегать.
auto d = 5.0; // 5.0 is a double literal, so d will be type double auto i = 1 + 2; // 1 + 2 evaluates to an integer, so i will be type int
- Работает только при инициализации переменной при создании. Переменные, созданные без значений инициализации, не могут использовать эту функцию (поскольку C ++ не имеет контекста, из которого можно определить тип).
- Ключевое слово auto нельзя использовать с параметрами функции, поскольку компилятор не может определить типы для параметров функции во время компиляции.
- В C ++ 14 ключевое слово auto было расширено, чтобы иметь возможность автоматически определять тип возвращаемого значения функции. Но это использование не рекомендуется. Потому что, в отличие от определений переменных, здесь нет контекста, который помог бы указать, какой тип возвращает функция.
Массивы, строки, указатели и ссылки
Массив
- При объявлении фиксированного массива длина массива (в квадратных скобках) должна быть константой времени компиляции. Обратите внимание, что неконстантные переменные или константы времени выполнения нельзя использовать, но некоторые компиляторы могут разрешать такие типы массивов (по причинам совместимости с C99).
// using a literal constant int array[5]; // Ok // using a macro symbolic constant #define ARRAY_LENGTH 5 int array[ARRAY_LENGTH]; // Syntactically okay, but don't do this // using a symbolic constant const int arrayLength = 5; int array[arrayLength]; // Ok // The following two can compile and run in many compilers, but not standard. // using a non-const variable int length; std::cin >> length; int array[length]; // length is not a compile-time constant! // not a compile-time constant int temp = 5; int array[temp]; // temp is not constant
- Массив без инициализации имеет неопределенные значения.
int array[5]; for (int i = 0; i < 5; ++i){ cout << array[i] << endl; // print any random numbers }
- Инициализацию одномерного массива можно выполнить с помощью списка инициализаторов. Если в списке меньше инициализаторов, чем может вместить массив, оставшиеся элементы инициализируются значением 0 (или любым другим значением, которое 0 преобразует для нецелого фундаментального типа - например, 0,0 для double). Это называется нулевой инициализацией. например
// initializer list int x[] = {1,2,3}; // x has type int[3] and holds 1,2,3 int y[5] = {1,2,3}; // y has type int[5] and holds 1,2,3,0,0 int array[5] = { }; // all to 0 int z[3] = {0}; // z has type int[3] and holds all zeroes, this is different from the uninitialized array // uniform initialization in C++11 int prime[5] { 2, 3, 5, 7, 11 }; // with memset int dp[n]; memset(dp, 0, sizeof(dp)); //set all elements of dp to 0
- Инициализация многомерного массива
// initialize with initializer list
int array[3][5] =
{
{ 1, 2, 3, 4, 5 }, // row 0 = 1, 2, 3, 4, 5
{ 6, 7, 8 }, // row 1 = 6, 7, 8, 0, 0
{ 11, 12, 13, 14 } // row 2 = 11, 12, 13, 14, 0
};
//can omit (only) the leftmost length specification
int a[][5] = {
{1, 2, 3, 4, 5},
{6, 7, 8, 9, 0}
};
int rows = sizeof(a) / sizeof(a[0]); // (2*5*4) / (5*4) = 2 rows
int cols = sizeof(a[0]) / sizeof(int); // (5*4) / 4 = 5 cols
// Three dimensional array
int x[2][3][2] =
{
{ {0,1}, {2,3}, {4,5} },
{ {6,7}, {8,9}, {10,11} }
};
int a[10][10]; // uninitialized
int a[10][10] = { {0} }; // all elements initialized to 0.
int a[10][10] = {1,2}; // test[0][0] ==1, test[0][1]==2, rest==0
// dynamic array
int** a = new int*[rowCount];
for(int i = 0; i < rowCount; ++i)
a[i] = new int[colCount];
- Сделайте индексы значимыми, используя перечисления.
enum StudentNames { KENNY, // 0 KYLE, // 1 MAX_STUDENTS // 2 }; int testScores[MAX_STUDENTS]; // allocate 3 integers testScores[KYLE] = 76;
- При передаче массивов функциям, поскольку копирование больших массивов может быть очень дорогостоящим, C ++ не копирует массив, когда массив передается в функцию. Вместо этого передается фактический массив, поэтому функция может изменять значения массива, если не используется
const
. - Оператор sizeof можно использовать с массивами, и он вернет общий размер массива (длина массива, умноженная на размер элемента). Обратите внимание, что из-за того, как C ++ передает массивы функциям, это не будет некорректно работать с массивами, которые были переданы функциям. например:
void printSize(int array[]){ std::cout << sizeof(array) << '\n'; // prints the size of a pointer, not the size of the array! }
- Определение длины фиксированного массива
// Prior to C++17sizeof(array) / sizeof(array[0]);
// C++17 #include <iterator> // for std::sizestd::size(array);
Сортировка массива
- Сортировка массива сравнительно дорога, и часто не стоит сортировать массив, чтобы ускорить поиск, если только вы не собираетесь искать в нем много раз.
- Чтобы поменять местами два элемента, мы можем использовать функцию
std::swap()
из стандартной библиотеки C ++, которая определена в заголовкеalgorithm
. По соображениям эффективностиstd::swap()
был перемещен в заголовокutility
в C ++ 11.
#include <algorithm> // for std::swap, use <utility> instead if C++11 int x = 2; int y = 4; std::swap(x, y); // swap the values of x and y
- Стандартная библиотека C ++ включает функцию сортировки
std::sort
.
#include <algorithm> // for std::sort int array[5] = {2,3,5,1,4}; std::sort(array, array+length);
Струны в стиле C
- Современный C ++ поддерживает два разных типа строк:
std::string
(как часть стандартной библиотеки) иC-style strings
(изначально, как унаследованный от C).std::string
реализован с использованием строк в стиле C. - Строка в стиле C - это просто массив символов, в котором используется нулевой терминатор. нулевой символ конца - это специальный символ (‘\ 0’, код ASCII 0), используемый для обозначения конца строки. В более общем смысле строка в стиле C называется строкой с завершающим нулем. например
char myString[] = “string”; //
a null terminator automatically added, length is 6+1 = 7;
- Строки в стиле C подчиняются всем тем же правилам, что и массивы, вы не можете присваивать им значения с помощью оператора присваивания (=) после создания.
- Чтобы использовать
std::cin
для ввода строки в стиле c, мы используемgetline
следующим образом:
char name[255]; // declare array large enough to hold 255 characters std::cin.getline(name, 255); // will read up to 254 characters into name
- C ++ предоставляет множество функций для управления строками в стиле C как часть библиотеки ‹cstring›.
// copy strcpy(dest, source); // length and size char name[20] = "Alex"; // use 4+1 characters. strlen(name); // 4 (without the null terminator). std::size(name) // 20 // strcat() -- Appends one string to another (dangerous) // strncat() -- Appends one string to another (with buffer length check) // strcmp() -- Compare two strings (returns 0 if equal) // strncmp() -- Compare two strings up to a specific number of characters (returns 0 if equal)
- Строк в стиле C следует избегать и как можно чаще использовать
std::string
.
Указатели
- Объявление указателя. Поместите звездочку рядом с именем переменной, поставьте звездочку возвращаемого значения указателя рядом с типом.
int *iPtr; int *iPtr2, *iPtr3; int* doSomething();
- Некоторое использование указателя
int var = 10; int *ptr = &var; cout << var << endl; //10 cout << *ptr << endl; //10 cout << &var << endl; //0x7fffa0757dd4, address of var cout << ptr << endl; //0x7fffa0757dd4, value of pointer == address of var it points to cout << &ptr << endl; //0x7fff98b499e8, address of pointer
- C ++ также не позволит вам напрямую назначать буквальные адреса памяти указателю.
double *dPtr = 0x0012FF7C; // not okay, treated as assigning an integer literal
. - Оператор адресации возвращает указатель, но не возвращает адрес своего операнда в виде литерала.
int x(4); std::cout << typeid(&x).name(); //int *, or Pi(pointer of int)
- Разыменование указателя мусора может привести к сбою вашего приложения.
- Размер указателя зависит от архитектуры, для которой скомпилирован исполняемый файл - 32-разрядный исполняемый файл использует 32-разрядные адреса памяти - следовательно, указатель на 32-разрядной машине составляет 32 бита (4 байтов). В 64-битном исполняемом файле указатель будет 64-битным (8 байтов). Это верно независимо от того, на что указывают. Подробнее о 32-битной и 64-битной.
Нулевые указатели
- Инициализируйте указатели нулевым значением, если вы не присваиваете им другое значение.
- Разыменование нулевого указателя также приводит к неопределенному поведению.
- Значение NULL определяется реализацией, но обычно определяется как целочисленная константа 0. Примечание. Начиная с C ++ 11, NULL можно определить как nullptr, вместо этого C ++ будет неявно преобразовывать nullptr на любой тип указателя.
int *ptr { nullptr }; // note: ptr is still an integer pointer, just set to a null value
nullptr
всегда является указателем.0
(также известный как NULL из C, соединенный с C ++) может вызвать неоднозначность в разрешении перегруженных функций, среди прочего:
f(int); //f(NULL)
f(foo *); //f(nullptr)
Указатели и массивы
- Имя фиксированного массива - это указатель, указывающий на первый элемент массива. В приведенном ниже примере массив (типа int [5]) неявно преобразуется в указатель (типа int *), и мы разыменовываем указатель, чтобы получить значение по адресу памяти, который он удерживает ( значение первого элемента массива).
int array[5] = { 9, 7, 5, 3, 1 }; cout << &array[0] << " " << array << endl; //both 0042FD5C cout << *array; //9 int *ptr = array; //the array decays into a pointer of type int *
- Различия между указателями и фиксированными массивами:
- Оператор sizeof () при использовании с фиксированным массивом sizeof возвращает размер всего массива, при использовании с указателем sizeof возвращает размер переменной указателя, это потому, что фиксированный массив знает какова длина массива, на который он указывает, указатель на массив - нет.
- оператор адреса (&), получение адреса массива возвращает указатель на весь массив. Этот указатель также указывает на первый элемент массива, но информация о типе отличается (& array имеет тип int (*) [5], поэтому не может передать его функции, где требуется int *). Принятие адреса указателя дает адрес памяти переменной указателя.
int array[] {1,2,3,4,5}; cout << typeid(array).name() << endl; //A5_i (array with 5 int elements) cout << typeid(&array).name() << endl; //P5_i (pointer of array with 5 int elements) cout << typeid(array[0]).name() << endl; //i (int) cout << typeid(&array[0]).name() << endl; //Pi (Pointer of int) cout << array << endl; //0x7ffeea289700 cout << &array << endl; //0x7ffeea289700
3. Операторы -, ++ не могут применяться к имени массива, пока он работает с указателем.
int a[] = {1,2,3}; int *p = a; //a contains the address of a[0] a++; //compile error: cannot increment value of type 'int [3]' p++; //ok, points to a[1] cout << a+1; //ok, address of a[1] cout << p+1; //ok, address of a[1]
- Передача массива в функцию. C ++ не копирует массив, когда массив передается в функцию. При передаче массива в качестве аргумента функции фиксированный массив превращается в указатель, и указатель передается функции.
void printSize(int *array) { // array is treated as a pointer here cout << sizeof(array) << '\n'; // prints the size of a pointer, not the size of the array! } int array[] = { 1, 1, 2, 3, 5, 8, 13, 21 }; printSize(array); // the array argument decays into a pointer here
- C ++ неявно преобразует параметры с помощью синтаксиса массива ([]) в синтаксис указателя (*). Это означает, что следующие два объявления функций идентичны. Обратите внимание, что имя массива - указатель на массив.
void func1(int a[], int n){ cout << "value of a in func: " << a << endl; } void func2(int *a, int n){ cout << "value of a in func1: " << a<< endl; } int a[] = {1,2,3}; cout << "value of a: " << a << endl; func1(a, 3); func2(a, 3); value of a: 0x7ffee117e78c value of a in func1: 0x7ffee117e78c value of a in func2: 0x7ffee117e78c
- Тот факт, что массивы распадаются на указатели при передаче в функцию, объясняет основную причину, по которой изменение массива в функции изменяет фактический переданный аргумент массива. Когда вызывается changeArray (), массив распадается на указатель, и значение этого указателя (адрес памяти первого элемента массива) копируется в параметр ptr функции changeArray (). Хотя значение в ptr является копией адреса массива, ptr по-прежнему указывает в фактическом массиве (а не в копии!). Следовательно, когда ptr разыменован, разыменовывается фактический массив!
// parameter ptr contains a copy of the array's address void changeArray(int *ptr){ *ptr = 5; // changing an element changes the actual array ptr = xxx; // this won't change the address of the array } int array[] = { 1,2,3 }; cout << array[0]; //1 cout << array; //0x7ffee8616710 changeArray(array); cout << array[0]; //5 cout << array; //0x7ffee8616710. this won't change
Арифметика указателя и индексация массива
- Если ptr указывает на целое число,
ptr + 1
- это адрес следующего целого числа в памяти (4 байта) после ptr. - Обобщая,
array[n]
то же самое, что*(array + n)
.
Строковые символьные константы в стиле C
- Три способа создания строки в стиле C, версия 1 - это фиксированный массив, значение которого можно изменить позже, версия 2 аналогична версии 1. В версии 3 обычно происходит то, что компилятор помещает строку «Alex \ 0» где-нибудь в постоянную память, а затем устанавливает указатель, указывающий на нее. Поскольку эта память может быть доступна только для чтения, рекомендуется убедиться, что строка является константной.
char version1[5] = {'A','l','e','x','\0'}; /
/ fixed array,\0 is explicitly specified char version2[] = "
Alex"; // \0 is added automatically const char *version3 = "
Alex"; //
pointer to symbolic constant,\0 is added automatically
- В результате того, что строковые литералы хранятся в фиксированном месте в памяти, строковые литералы имеют статическую продолжительность, а не автоматическую. Это означает, что когда мы используем строковые литералы, нам не нужно беспокоиться о проблемах с областью видимости. например «Alex» не будет уничтожен после завершения работы getName ().
const char* getName(){ return "Alex"; }
- std :: cout по-разному обрабатывает указатели разных типов. Если вы передадите ему указатель, не являющийся символом, он просто распечатает содержимое этого указателя (адрес, который удерживает указатель). Однако если вы передадите ему объект типа char * или const char *, он будет предполагать, что вы собираетесь напечатать строку. например
int nArray[5] = { 9, 7, 5, 3, 1 }; char cArray[] = "Hello!"; const char *name = "Alex"; cout << nArray << '\n'; // nArray will decay to type int*, 003AF738 cout << cArray << '\n'; // cArray will decay to type char*, Hello! cout << name << '\n'; // name is already type char*, Alex char c = 'Q'; cout << &c; //char*, print string until 0. may print something like Q╠╠╠╠╜╡4;¿■A
Динамическое выделение памяти с помощью новых и удаленных
- Большинство обычных переменных (включая фиксированные массивы) размещаются в части памяти, называемой стеком. Объем стековой памяти для программы обычно довольно невелик (скажем, 1 МБ).
- Динамическая память выделяется из гораздо большего пула памяти, управляемого операционной системой, который называется кучей. На современных машинах размер кучи может составлять гигабайты.
- Как статическое (глобальное, статическое), так и автоматическое (локальная переменная, параметры функции) распределение и освобождение происходит автоматически. В отличие от статической или автоматической памяти, сама программа отвечает за запрос и удаление динамически выделяемой памяти.
- Инициализация и удаление динамически выделяемых переменных и массивов
//single values int *ptr1 = new int; *ptr1 = 4; int *ptr2 = new int (5); // use direct initialization int *ptr3 = new int { 6 }; // use uniform initialization in C++11 cout << *ptr1 << endl; // 4 cout << *ptr2 << endl; // 5 cout << *ptr3 << endl; // 6 delete ptr1; delete ptr2; delete ptr3; //arrays int *array1 = new int[length];//length does not need to be constant array1[0] = 5; delete[] array1; int *array2 = new int[length](); // all to 0 int *array3 = new int[5] { 9, 7, 5, 3, 1 }; // initialize a dynamic array in C++11
- Динамические массивы должны быть объявлены с явной длиной:
int *dynamicArray1 = new int[] {1, 2, 3}; // not okay: implicit size for dynamic arrays int *dynamicArray2 = new int[3] {1, 2, 3}; // okay: explicit size for dynamic arrays
- Оператор delete не фактически ничего удаляет. Он просто возвращает указанную память обратно в операционную систему. После удаления необходимо установить указатель на
NULL
илиnullptr
, если только они не выходят из области видимости сразу после этого.
delete ptr; // return the memory pointed to by ptr to the OS ptr = NULL; // set ptr to be a null pointer (use nullptr instead of 0 in C++11)
- Удаление нулевого указателя не имеет никакого эффекта. Таким образом, вы можете писать
delete ptr;
независимо от того, указывает он на ноль или нет. - Динамически выделяемая память фактически не имеет области действия. То есть он остается выделенным до тех пор, пока он не будет явно освобожден или пока программа не завершится (и операционная система очистит его, если это сделает ваша операционная система).
- Утечки памяти происходят, когда ваша программа теряет адрес некоторого бита динамически выделяемой памяти, прежде чем вернуть его операционной системе. Программа не может его удалить, а ОС не может его использовать.
- Будьте осторожны, чтобы не разыменовать висячие или нулевые указатели.
Указатели и константы
- Указатель на постоянную переменную может указывать на непостоянную переменную, указатель на непостоянную переменную не может указывать на постоянную переменную.
int value = 5; // value is not constant const int *ptr = &value; // this is still okay *ptr = 6; // not ok const int value = 5; // value is const int *ptr = &value; // compile error: cannot convert const int* to int*
- Указатель на константную переменную (может изменять значение указателя, но не значение переменной через указатель) и константные указатели (могут изменять значение переменной через указатель, но не значение самого указателя)
// A pointer to a const variable int value = 5; // value is not constant int value2 = 6; const int *ptr = &value; *ptr = 6; //not ok ptr = &value2; //ok, ptr now points at some other const int // const pointer int value = 5; int *const ptr = &value; ptr = &value2; // not okay *ptr = 6; //ok, as value is non-constant variable // Const pointer to a const value, cannot change ptr and value via ptr. int value = 5; const int *const ptr = &value;
Справочные переменные
- Ссылка - это тип переменной C ++, которая действует как псевдоним для другого объекта или значения. Ссылка имеет тот же адрес, что и переменная. Ссылки должны быть инициализированы при создании. Ссылки реализуются внутри как указатели.
- l-значение - это значение, имеющее адрес (в памяти). r-значение относится к любому значению, которое может быть присвоено l-значению.
- Ссылки на неконстантные значения могут быть инициализированы только неконстантными l-значениями. Они не могут быть инициализированы постоянными l-значениями или r-значениями.
const int y = 7; int &ref2 = y; // not okay, y is a const l-value int &ref3 = 6; // not okay, 6 is an r-value
- После инициализации ссылку нельзя изменить для ссылки на другую переменную.
int &ref = value1; // okay, ref is now an alias for value1 ref = value2; // it assigns the value from value2 to value1
- В большинстве случаев массивы в стиле C при вычислении превращаются в указатели. Однако если массив в стиле C передается по ссылке, такого распада не происходит.
void printElements(int (&arr)[4]){ //need to explicitly define the array size int length{ sizeof(arr) / sizeof(arr[0]) };//ok } int arr[]{ 99, 20, 14, 80 }; printElements(arr); // Other ways to pass array, they are all treated as taking anint *
parameter, you can pass any size array to them.void foo(int *x); void foo(int x[100]); //this is ok even passing an address of void foo(int x[]);
- Ссылки против указателей, см. также здесь.
- Переменные, объявление, переназначение, адрес памяти (указатель имеет свой собственный адрес памяти и размер в стеке, тогда как ссылка использует тот же адрес памяти (с исходной переменной), но также занимает некоторое пространство в стек), нулевое значение, аргументы, когда использовать.
Для каждой петли
- В C ++ 11 появился новый тип цикла, называемый циклом for-each (также называемый циклом на основе диапазона).
for (element_declaration : array) statement; int fibonacci[] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 }; for (int number : fibonacci){ cout << number << ' '; } // or we can use below for (auto number : fibonacci) for (auto &element: fibonacci) //no value copy and can change the element for (const auto &element: fibonacci) //read-only, no copy
- Циклы For-each работают не только с фиксированными массивами, они работают со многими видами структур, подобных спискам, такими как векторы (например, std :: vector), связанные списки, деревья и карты.
- Чтобы выполнить итерацию по массиву, для каждого необходимо знать, насколько велик массив, что означает знание размера массива. Таким образом, он не работает с массивами, которые превратились в указатель. например
int sumArray(int array[])
.
недействительный указатель
- Указатель void может указывать на данные любого типа даже после инициализации. Перед разыменованием он должен быть явно приведен к другому типу указателя.
int value = 5; void *voidPtr = &value; int *intPtr = static_cast<int*>(voidPtr); cout << *intPtr << endl;
- указатели void могут быть установлены в значение
null
. Следует избегать удаления пустого указателя, указывающего на динамически выделяемую память, так как это может привести к неопределенному поведению.
Указатель на массив, указатель на многомерный массив, массивы указателей
- Указатель на массив (это указатель. Указывает на 0-й элемент и весь массив).
int arr[5] = { 1, 2, 3, 4, 5 }; int *p = arr; //p points to the 0th element of the array int (*ptr)[5]; //ptr points to the whole array which has 5 elementsint
(*ptr)[5] = &arr; //
ptr points to the whole array cout << p; //0x7fff4f32fd50 cout << ptr; //0x7fff4f32fd50 cout << ++p; //0x7fff4f32fd54 (4*1 = 4) cout << ++ptr; 0x7fff4f32fd64 (4*5 = 20)
2. Указатель на многомерный массив (он же указатель)
int arr[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }; // arr is a pointer to 0th element of arr, points to 0th 1-D array (5000) // arr+1 is a pointer to 1th element of arr, points to 1th 1-D array (5016) // arr+2 is a pointer to 2th element of arr, points to 2th 1-D array (5032) arr[i], *(arr + i) // identical arr[i][j], *(*(arr + i) + j) // identical
3. Массивы указателя (это массив)
// Two-dimensional dynamically allocated arrays int **array = new int*[10]; // allocate an array of 10 int pointers — these are rows. each pointer points to a pointer of int. for (int count = 0; count < 10; ++count) array[count] = new int[5]; // these are columns
Введение в std :: array и std :: vector
- Представленный в C ++ 11, std :: array обеспечивает функциональность фиксированного массива, которая не теряет своего значения при передаче в функцию. std :: array определяется в заголовке массива внутри пространства имен std.
- Представленный в C ++ 03, std :: vector обеспечивает функциональность динамического массива, которая обрабатывает собственное управление памятью. Это означает, что вы можете создавать массивы, длина которых устанавливается во время выполнения, без необходимости явно выделять и освобождать память с использованием нового и удаления.
#include <array> array<int, 3> myArray; // declare an integer array with length array<int, 5> myArray = { 9, 7, 5, 3, 1 }; // initialization list array<int, 5> myArray2 { 9, 7, 5, 3, 1 }; // uniform initialization // no need to specify length at initialization #include <vector> vector<int> array; vector<int> array2 = { 9, 7, 5, 3, 1 }; // initializer vector<int> array3 { 9, 7, 5, 3, 1 }; // uniform initialization
- С std :: array вы не можете опустить длину массива при предоставлении инициализатора, а с std :: vector вы можете.
- Всегда передавайте std :: array и std :: vector по ссылке или константной ссылке.
- Доступ к элементам массива для обоих из них может быть выполнен с помощью оператора [] (который не проверяет границы) или функции at () (которая выполняет проверку границ).
- Начиная с C ++ 11, вы также можете присваивать значения std :: vector, используя список инициализаторов.
vector<int>array; array = { 0, 1, 2, 3, 4 }; // okay, array length is now 5 array = { 9, 8, 7 }; // okay, array length is now 3 std::array<int, 3> arr; arr = {1,2,3}; //okay arr = {1,2,3,4}; //error
- Размер std :: vector можно изменить с помощью функции resize (int), а размер std :: array изменить нельзя.
std::vector<int> array { 0, 1, 2 }; array.resize(5); // set size to 5, 0,1,2,0,0 array.resize(2); // set size to 2, 0,1
Функции
- Есть три способа передачи аргументов функциям: передача по значению, передача по ссылке и передача по адресу.
- По умолчанию аргументы без указателя передаются по значению. Значение аргумента копируется в значение соответствующего параметра функции.
int foo(int a);
. - Передача по ссылке позволяет избежать копирования аргумента и позволить функции изменять переданные аргументы.
- Один из способов вернуть несколько значений - использовать ссылочные параметры.
- Неконстантные ссылки могут ссылаться только на неконстантные l-значения (например, неконстантные переменные).
- Используйте Передавать по константной ссылке, если аргументы не нужно изменять.
void foo(const int &x)
. - Можно передать указатель по ссылке, и функция полностью изменит адрес указателя.
void foo(int *&ptr){ // pass pointer by reference ptr = nullptr; // this changes the actual ptr argument passed in, not a copy }
5. Когда вам нужен доступ к информации о типе фиксированного массива, вы можете передать его по ссылке. например void printElements(int (&arr)[4])
.
- Передача аргумента по адресу включает передачу адреса переменной аргумента, а не самой переменной аргумента.
void foo(int *ptr){ *ptr = 6; } // Passing by const address, elements won't be modified void printArray(const int *array, int length){ ... }
- Адреса фактически передаются по значению. Когда вы передаете указатель на функцию по адресу, значение указателя (адрес, на который он указывает) копируется из аргумента в параметр функции. Несмотря на то, что сам адрес передается по значению, вы все равно можете разыменовать этот адрес, чтобы изменить значение аргумента.
- Ссылки обычно реализуются компилятором как указатели. Это означает, что за кулисами передача по ссылке по сути является просто передачей по адресу (с доступом к ссылке, выполняющей неявное разыменование). А передача по адресу на самом деле просто передает адрес по значению. C ++ действительно передает все по значению.
- Подобно передаче аргументов функции, функция может возвращать значение по значению, ссылке и адресу.
- С помощью возврата по значению вы можете возвращать переменные (или выражения), которые включают локальные переменные, объявленные внутри функции, не беспокоясь о проблемах с областью видимости.
- Возврат по адресу просто копирует адрес из функции вызывающей стороне, возврат по адресу выполняется быстро. Если вы попытаетесь вернуть адрес переменной, локальной для функции, ваша программа будет демонстрировать неопределенное поведение. например
int* doubleValue(int x) { int value = x * 2; return &value; // return value by address here } // value destroyed here
- Как и при передаче по адресу, значения , возвращаемые по ссылке, должны быть переменными. Как и при возврате по адресу, вы не должны возвращать локальные переменные по ссылке.
int& doubleValue(int x) { int value = x * 2; return value; // return a reference to value here } // value is destroyed here
- Std :: tuple (после C ++ 11) может использоваться для возврата нескольких значений.
std::tuple<int, double> returnTuple() // return a tuple that contains an int and a double { return std::make_tuple(5, 6.7); // use std::make_tuple() as shortcut to make a tuple }
Встроенные функции
- Встроенные функции используются, чтобы избежать накладных расходов на вызов функции, когда используются небольшие, часто используемые функции. Когда компилятор компилирует ваш код, все встроенные функции разворачиваются на месте, то есть вызов функции заменяется копией содержимого самой функции. Формат встроенной функции:
inline int min(int x, int y) { return x > y ? y : x; }
- Ключевое слово inline является только рекомендацией - компилятор может проигнорировать ваш запрос на встраивание функции. Современные компиляторы должны встраивать функции для вас по мере необходимости, поэтому нет необходимости использовать ключевое слово.
- На встроенные функции не распространяется правило, согласно которому у вас может быть только одно определение для каждой программы. Когда компоновщик связывает несколько файлов вместе, конфликта нет.
Перегрузка функций
- Перегрузка функций (проверяется во время компиляции) позволяет нам создавать несколько функций с одинаковым именем, если каждая функция отличается числом или типами параметры. Возвращаемое значение не учитывается при определении, является ли перегрузка отличной. например,
// different parameters. int add(int x, int y); // integer version double add(double x, double y); // floating point version int add(int x, int y, int z); // not allowed int getRandomValue(); double getRandomValue(); // not allowed void printValues(int x); void printValues(int x, int y=20);
- Соответствие функций:
- Сначала C ++ пытается найти точное совпадение.
void print(char *value); void print(int value); print(0); // exact match with print(int)
2. Если точное совпадение не найдено, C ++ пытается найти совпадение посредством продвижения.
void print(char *value); void print(int value); print('a'); // promoted to match print(int)
3. Если продвижение невозможно, C ++ пытается найти соответствие с помощью стандартного преобразования.
struct Employee; // defined somewhere else void print(float value); void print(Employee value); print('a'); // 'a' converted to match print(float)
4. Наконец, C ++ пытается найти соответствие через определяемое пользователем преобразование (класс).
- Если вызов функции соответствует нескольким кандидатам через стандартное преобразование или пользовательское преобразование, результатом будет неоднозначное совпадение. Неоднозначные совпадения считаются ошибкой времени компиляции.
- Разница между перегрузкой и переопределением: наследование, подпись функции, объем функций, поведение функций.
Указатели на функции
- Функции имеют свой собственный тип функции с l-значением, и, как и переменные, функции хранятся по назначенному адресу в памяти. Когда функция вызывается (через оператор ()), выполнение переходит к адресу вызываемой функции.
- Синтаксис указателя на функцию:
// fcnPtr is a pointer to a function that takes no arguments and returns an integer int (*fcnPtr)(); // fcnPtr is a const function pointer, we can't change the function it points to int (*const fcnPtr)(); // point to a function with one argument int (*fcnPtr)(int);
- Назначение функции указателю на функцию
int (*fcnPtr)() = foo; // fcnPtr points to function foo fcnPtr = goo; // fcnPtr now points to function goo, (only works for non-const function pointer)
- Вызов функции с помощью указателя функции
int foo(int x); int (*fcnPtr)(int) = foo; // assign fcnPtr to function foo (*fcnPtr)(5); // call function foo(5) through fcnPtr. // or fcnPtr(5); // call function foo(5) through fcnPtr.
- Функции обратного вызова: передайте функцию в качестве аргумента другой функции. Одно из самых полезных занятий с этим - сортировка. Эту функцию также можно установить как параметр по умолчанию. например
void selectionSort(int *array, int size, bool (*comparisonFcn)(int, int)); bool ascending(int x, int y); bool descending(int x, int y); selectionSort(array, 9, descending); void selectionSort(int *array, int size, bool (*comparisonFcn)(int, int) = ascending);
- Typedef указатель на функцию, чтобы определить typedef с именем «validateFcn», который является указателем на функцию, которая принимает два целых числа и возвращает логическое значение:
typedef bool (*validateFcn)(int, int);
- Использование std :: function в C ++ 11 для определения и сохранения указателя на функцию:
#include <functional> std::function<int()> fcnPtr = foo; //is identical to below: int (*fcnPtr)() = foo;
Стек и куча
- Переполнение стека происходит, когда вся память в стеке была выделена - в этом случае дальнейшие выделения начинают переполняться в другие разделы памяти.
Обработка ошибок и утверждение
- Если мы хотим немедленно завершить программу, можно использовать функцию exit, которая находится в ‹cstdlib›, для возврата в операционную систему кода ошибки:
exit(2);
. - cerr - это выходной поток (как и cout), который определен в ‹iostream› и предназначен специально для печати сообщений об ошибках.
std::cerr << “function printString() received a null parameter”;
. - Оператор assert - это макрос препроцессора, который оценивает условное выражение во время выполнения. Если условное выражение оценивается как ложное, отображается сообщение об ошибке и программа завершается.
assert(index >= 0 && index <= 9);
. - Чтобы отключить утверждения в рабочем коде, можно использовать #define NDEBUG. Все вызовы assert () будут игнорироваться до конца файла, в котором записано #define NDEBUG.
- static_assert предназначен для работы во время компиляции ,
static_assert(sizeof(long) == 8, “long must be 8 bytes”);
. - Обработка исключений: при возникновении ошибки она «выбрасывается». Если текущая функция не «улавливает» ошибку, у вызывающей функции есть шанс уловить ошибку. Если вызывающий абонент не обнаруживает ошибку, у вызывающего абонента есть шанс обнаружить ошибку. Ошибка постепенно перемещается вверх по стеку, пока она не будет обнаружена и обработана, или пока main () не сможет обработать ошибку. Если никто не обрабатывает ошибку, программа обычно завершается с ошибкой исключения.
Аргументы командной строки
- Форма main () для использования аргументов командной строки показана ниже. argc - это сокращение от arg ument c ount, а argv - от arg ument. v значения. Argc всегда будет не меньше 1. Каждый аргумент командной строки, предоставляемый пользователем, вызывает увеличение argc на 1.
int main(int argc, char *argv[]) // array of C-style strings, argv is array of pointers to char
, args[i] is of type char*
//or
int main(int argc, char **argv)
- Аргументы могут использоваться следующим образом: Аргумент 0 - это путь и имя текущей запущенной программы.
cout << "There are " << argc << " arguments:\n"; for (int count=0; count < argc; ++count) cout << count << " " << argv[count] << '\n';
- Аргументы командной строки всегда являются строками в стиле C и должны быть преобразованы в числа, если требуются числовые значения:
#include <sstream> stringstream convert(argv[1]); int myint; convert >> myint; //similar to cin
использованная литература
Понравилось? См. Также часть 2 этой серии.