Источники:
char c = '1'; // символ
bool b = true; // логическая переменная, принимает значения false и true
int i = 42; // целое число (занимает, как правило, 4 байта)
short int i = 17; // короткое целое (занимает 2 байта)
long li = 12321321312; // длинное целое (как правило, 8 байт)
long long lli = 12321321312; // длинное целое (как правило, 16 байт)
float f = 2.71828; // дробное число с плавающей запятой (4 байта)
double d = 3.141592; // дробное число двойной точности (8 байт)
long double ld = 1e15; // длинное дробное (как правило, 16 байт)
По умолчанию типы знаковые, необходимо использовать "unsigned" для получения беззнаковых типов.
unsigned int ui = 4294967295; // 2^32 - 1
Размер типа и минимальное-максимальное значение:
#include <limits> // необходимо для numeric_limits
int main() {
std::cout << "long long int: " << sizeof(long int) << "\n";
// для типа int:
std::cout << "minimum value: " << std::numeric_limits<int>::min() << "\n"
<< "maximum value: " << std::numeric_limits<int>::max() << "\n";
}
Размер long int на 64-разрядной машине составляет 8 байт, а на 32-разрядной машине 4 байта.
Типы с фиксированным размером, независящим от битности системы, из стандартной библиотеки:
#include <cstdint> int8_t / uint8_t int16_t / uint16_t int32_t / uint32_t int64_t / uint64_t
На некоторых аппаратных платформах операции с меньшей разрядностью, например uint16_t, могут занимать больше времени чем с аппаратным типом int. Чтобы выбрать тип необходимой разрядности, но не приводящий к снижению быстродействия, необходимо использовать определения std::int_fast#_t. Например, если на некоторой платформе:
то это будет говорить о том, что процессор (микроконтроллер) одинаково быстро работает с типами uint8_t и uint32_t, а вот с числами uint16_t работает медленнее. (Скорость зависит от выборки из памяти и выполнений операций в АЛУ - Арифметико-Логическом Устройстве). Поэтому вместо вычислений с типом uint16_t оптимальнее будет использовать тип uint32_t, который покрывает собой весь диапазон uint16_t.
Есть похожие определения std::int_least8_t, std::int_least16_t, std::int_least32_t которые вернут тип, покрывающий данный диапазон и поддерживающийся в заданной платформе. Например если аппаратура не поддерживает тип uint8_t, а поддерживает только uint16_t и выше, то std::int_least8_t вернет тип uint16_t.
(Источник - radioprog.ru/post - 4.6 – Целочисленные типы фиксированной ширины и size_t).
При работе с беззнаковыми типами можно использовать их переполнение. Но переполнение знаковых типов считается UB, undefined behavior - не гарантируется правильная работа программы.
// OK
unsigned int x = 0; // 0x0000_0000, на 64-битной платформе sizeof(x) == 4
unsigned int y = x - 1; // 0xFFFF_FFFF, 4294967295, то есть 2**32 - 1
unsigned int z = y + 1; // 0x0000_0000
// UB, Произведение a * a не помещается в 4 байта, так как оно больше 2^32
unsigned int a = 123456; // на 64-битной платформе sizeof(a) == 4
std::cout << a * a << "\n";
Для получения дробного результата при делении необходимо одну из переменных привести к вещественному типу.
int a = 7, b = 3;
int q = a / b; // 2
int r = a % b; // 1
int c = 6, d = 4;
double q = static_cast<double>(c) / d; // 1.5
Автоматический вывод типа переменной:
auto x = 42; // int
auto pi = 3.14159; // double
// НО для строк, выберется тип const char *, а не std::string!
auto s = "hello"
Отличная статья, объясняющая зоопарк с разрядностью типов int, long и т.д. - PVS-Studio Blog: Что такое size_t и ptrdiff_t
// содержит стандартные потоки cin, cout, cerr
// для языка С они в stdio.h
#include <iostream>
// Считывание переменных через cin
//При наборе данных на клавиатуре значения должны быть разделены:
// пробел, \n, \t
int64_t a, b;
char operation;
std::cin >> a >> operation >> b;
// Считывание строки без спецсимволов: пробел, \n, \t
std::string name;
std::cin >> name;
// Считывание строки включающей спецсимволы
std::string line;
std::getline(std::cin, line);
for (char symbol : line) {
std::cout << symbol << "\t" << static_cast<int>(symbol) << "\n"; // symbol and ascii code
}
// Считывание пока не закончатся данные
// При вводе данных не из файла, а с клавиатуры можно сымитировать конец ввода комбинацией клавиш:
// Ctrl+D в Linux и macOS или
// Ctrl+Z в Windows.
int sum = 0;
int x;
while (std::cin >> x) {
sum += x;
}
std::cout << sum << "\n";
// или
std::string name;
while (std::getline(std::cin, name)) {
std::cout << "Hello, " << name << "!\n";
}
Подробнее: prog-cpp.ru: Поточный ввод-вывод в C++
std::stod("123.0"); // 123
std::stod("0.123"); // 0.123
std::stod("123.4 with chars"); // 123.4
std::stod("chars 1.2"); // исключение std::invalid_argument
string str1 = "123.4 with chars";
string str2 = " 123.4";
size_t ptr1 = -1;
size_t ptr2 = -1;
auto m1 = std::stod(str1, &ptr1); // m1 = 123.4 ptr1 = 5
auto m2 = std::stod(str2, &ptr2); // m2 = 123.4 ptr2 = 16
std::cout << std::fixed << std::setprecision(1) << m1;
std::this_thread::sleep_for(std::chrono::milliseconds(500));
QVector<uint8_t> table_lin(8);
std::iota(table_lin.begin(), table_lin.end(), 0);
// table_lin: 0, 1, 2, 3, 4, 5, 6, 7
QVector<uint8_t> table(8);
std::fill(table.begin(), table.end(), 3);
// table: 3, 3, 3, 3, 3, 3, 3, 3
// CASE 1:
int indexOfWidgetRegistered(QWidget *widg)
{
auto p_match = std::find_if(m_regWidgets.begin(), m_regWidgets.end(),
[widg](Widg_RegItem *item){
return (item->widg == widg);
}
);
if (p_match != m_regWidgets.end())
return p_match - m_regWidgets.begin();
else
return -1;
}
// CASE 2:
FormItem *MainWindow::getFormItemBySender(QAction *action)
{
QList<FormItem*>::iterator match_item = std::find_if(m_formItems.begin(), m_formItems.end(),
[action](FormItem *item){
return (item->action == action);
}
);
if (match_item != m_formItems.end())
return *match_item;
else
return nullptr;
}
// CASE 3:
const QString c_addrNames[addrCount] = {
"CtrlSw1",
"CtrlSw2",
"CtrlHV",
"CtrlOffs1",
"CtrlOffs2",
"CtrlSw3",
...
}
QString rd_name = read from somewhere;
auto pmatch_name = std::find_if(std::begin(c_addrNames), std::end(c_addrNames),
[rd_name](const QString &name){
return name == rd_name;
});
if (pmatch_name != std::end(c_addrNames)) {
i = pmatch_name - std::begin(c_addrNames);
// index ready for next operations
}
struct UID_RegItem {
Param_UIH *uih;
QLabel *lblName;
QLabel *lblUnits;
bool updNames;
UID_RegItem(Param_UIH *_uih, QLabel *_lblName, QLabel *_lblUnits, bool _updNames)
: uih{_uih}
, lblName{_lblName}
, lblUnits{_lblUnits}
, updNames{_updNames}
{}
};
int UI_ParamEventListener::indexOfRegWidget(const std::list<UID_RegItem> &items, const QWidget *widg)
{
if (widg == nullptr)
return -1;
const auto p_match = std::find_if(items.begin(), items.end(),
[widg](const UID_RegItem &item){
return (item.uih->widget() == widg);
}
);
if (p_match != items.end())
return std::distance(items.begin(), p_match);
else
return -1;
}
uint32_t m_values[addrCount]; uint32_t fill_value = 0; std::fill(m_values, m_values + addrCount, fill_value);
QVector<float> m_x;
QVector<float> m_y;
QRectF m_boundingRect;
size_t m_pntCount;
const auto [minY, maxY] = std::minmax_element(m_y.constBegin(), m_y.constBegin() + m_pntCount);
m_boundingRect.setCoords(m_x.constFirst(), *minY, m_x.constLast(), *maxY);
The example removes all (key, value) pairs where the key and the value are the same.
QMutableMapIterator<QString, QString> i(map);
while (i.hasNext()) {
i.next();
if (i.key() == i.value())
i.remove();
}
Исходный Map:
typedef int32_t uid_t;
struct Par_RegItem {
// fields ...
};
QMap<uid_t, Par_RegItem*> m_regParams;
Неправильный вариант 1 - container-anti-pattern:
for (auto uid : m_regParams.keys()) {
Par_RegItem *item = m_regParams[uid];
}
Warning:
allocating an unneeded temporary container [clazy-container-anti-pattern]
Recommended:
for (auto i : hash.values()) {} // Iterate the hash directly instead: for (auto i : hash) {}
Неправильный вариант 2 - range-loop-detach:
for (auto hash : m_regParams) {
Par_RegItem *item = hash; // hash это и есть //"Par_RegItem *"//
}
Warning:
c++11 range-loop might detach Qt container (QMap) [clazy-range-loop-detach]
Recommended: Fix it by marking the container const, or, since Qt 5.7, use qAsConst():
for (auto i : qAsConst(list)) { ... }
Видимо так:
for (auto hash : qAsConst(m_regParams)) {
Par_RegItem *item = hash;
}
Итератор по Key and Value:
for (auto iter = m_regParams.constBegin(); iter != m_regParams.constEnd(); ++iter) {
Par_RegItem *item = iter.value();
uid_t uid = iter.key();
}
Под впечатлением от "Habr: Inline variables".
Константы и переменные объявленные (и определенные) в *.h файле без externt имеют внутреннее связывание. Т.е. эти переменные и константы получают свою копию в каждой единице трансляции. Они дублируются (занимают память) в каждый *.obj файл при компиляции *.cpp в которых подключен данный *.h файл.
При использовании externt к переменной, "ссылка" на эту внешнюю переменную выносится наружу *.obj файла и затем вместо "ссылки" линкер подставляет реальный адрес данной переменной, из той единицы трансляции в которой она определена.
Поэтому при создании констант до стандарта С++17, чтобы избежать дублирования, необходимо было определять константы в *.cpp файле, а объявлять их с externt в *.h файле.
// consts.h
namespace NT {
namespace KeysFTDI {
extern const char * const SerialNum;
extern const char * const BuffLenTX;
extern const char * const BuffLenRX;
extern const char * const TimeoutTx_ms;
extern const char * const TimeoutRx_ms;
extern const char * const Latency_ms;
}
}
// consts.cpp
namespace NT {
namespace KeysFTDI {
const char * const SerialNum = "FTDI/SerialNum";
const char * const BuffLenTX = "FTDI/BuffLenTX";
const char * const BuffLenRX = "FTDI/BuffLenRX";
const char * const TimeoutTx_ms = "FTDI/TimeoutTx_ms";
const char * const TimeoutRx_ms = "FTDI/TimeoutRx_ms";
const char * const Latency_ms = "FTDI/Latency_ms";
}
} // NT
constexpr - здесь не делает связывание внешним! Константы дублируются!
Начиная со стандарт С++17 можно использовать директиву inline для указания внешнего связывания для переменной определенной в *.h файле. Т.е. компилятор сам соберет все inline переменные / константы и расположит их в одной единице трансляции, из которой потом линкер подставит реальные адреса этих переменных/констант при сборке *.obj файлов.
Термин inline теперь разрешает множественное определение одной и той-же сущности для каждой единицы трансляции.
// consts.h
namespace NT {
namespace KeysFTDI {
inline const char * const SerialNum = "FTDI/SerialNum";
inline const char * const BuffLenTX = "FTDI/BuffLenTX";
inline const char * const BuffLenRX = "FTDI/BuffLenRX";
inline const char * const TimeoutTx_ms = "FTDI/TimeoutTx_ms";
inline const char * const TimeoutRx_ms = "FTDI/TimeoutRx_ms";
inline const char * const Latency_ms = "FTDI/Latency_ms";
}
}
Со статическими полями классов происходит все аналогично. На примере массива обработчиков некоего класса-парсера, до С++17 необходимо:
// Parser.h
<code>
class Parser
{
private:
// Тип функции - обработчика
typedef bool (*ParseHandler) (uint16_t *rx_data, uint16_t rx_count);
// Функции обработчики протокола входных данных
static bool parseMess(uint16_t *rx_data, uint16_t rx_count);
static bool parseMessEx(uint16_t *rx_data, uint16_t rx_count);
static bool parseFunc(uint16_t *rx_data, uint16_t rx_count);
static bool parseReadBuff(uint16_t *rx_data, uint16_t rx_count);
static bool parseOscData(uint16_t *rx_data, uint16_t rx_count);
static bool parseOscReconf(uint16_t *rx_data, uint16_t rx_count);
// Массив функций-обработчиков
static ParseHandler parseHandlers[PC_ACK_COUNT];
...
}
// Parser.cpp
Parser::ParseHandler Parser::parseHandlers[PC_ACK_COUNT] {
&Parser::parseMess,
&Parser::parseMessEx,
&Parser::parseFunc,
&Parser::parseReadBuff,
&Parser::parseOscData,
&Parser::parseOscReconf
};
Начиная с С++17 теперь можно так:
class Parser
{
private:
// Тип функции-обработчика
typedef bool (*ParseHandler) (uint16_t *rx_data, uint16_t rx_count);
// Методы-обработчики протокола входных данных
static bool parseMess(uint16_t *rx_data, uint16_t rx_count);
static bool parseMessEx(uint16_t *rx_data, uint16_t rx_count);
static bool parseFunc(uint16_t *rx_data, uint16_t rx_count);
static bool parseReadBuff(uint16_t *rx_data, uint16_t rx_count);
static bool parseOscData(uint16_t *rx_data, uint16_t rx_count);
static bool parseOscReconf(uint16_t *rx_data, uint16_t rx_count);
// Массив методов-обработчиков
static inline ParseHandler parseHandlers[PC_ACK_COUNT] {
&Parser::parseMess,
&Parser::parseMessEx,
&Parser::parseFunc,
&Parser::parseReadBuff,
&Parser::parseOscData,
&Parser::parseOscReconf
};
...
}
Функция-метод класса и Функция-метод объекта:
class ShuttersCtrl
{
public:
enum DeviceStatus_e {
DevReady,
DevDisconnected,
DevInitializing,
DevFinalizing,
// not for use
DeviceStatusCount
};
// метод класса
static const char * statusStrClass(DeviceStatus_e status) {
return strDeviceStatus[status];
}
// метод объекта
const char * statusStr(DeviceStatus_e status) const {
return strDeviceStatus[status];
private:
DeviceStatus_e devStatus_{DevDisconnected};
static inline const char * const strDeviceStatus[DeviceStatusCount] =
{"Ready", "Disconnected", "Loading...", "Finalizing..." };
};
// вызов метода класса
const char *text = ShuttersCtrl::statusStrClass(ShuttersCtrl::DevReady);
// вызов метода объекта
ShuttersCtrl *ctrl = new ShuttersCtrl();
const char *text = ctrl->statusStrClass(ShuttersCtrl::DevReady);
Заменяем С-шный #define на C++ шный inline constexpr:
// example.h
#define MY_CONST_1 1
inline constexpr int MY_CONST_2 = 2; // Default choice
inline const std::string MY_STR = "2"; // If not a literal type
struct A {
static constexpr int n = 5; // Default choice; implicitly inline
static inline const std::string s = "6"; // If not a literal type
};
// example.cpp
constexpr int MY_CONST_2 = 2; // Default choice; implicitly static
const std::string s4 = "4"; // If not a literal type; implicitly static
void f() {
static constexpr int n = 7; // Default choice
static const std::string s = "8"; // If not a literal type
}
Вместо статических методов-обработчиков описаных выше, можно сделать так (спасибо Stackoverflow):
#include <iostream>
#include <array>
#include <functional>
struct Foo {
std::array<std::function<void()>, 3> funArray; // Notice the change in signature to void()
Foo() {
funArray[0] = [&](){ fun1(); }; // Here & is catching this by reference and this lambda will always call fun1 on the current object.
funArray[1] = [&](){ fun2(); };
}
void fun1() {
std::cout << "fun1\n";
}
void fun2() {
std::cout << "fun2\n";
}
std::function<void()> getFunction(int i) {
return funArray[i];
}
};
int main() {
Foo foo;
foo.getFunction(0)(); // We get a function returned, so we need to call if by adding one more () at the end
auto storedFunction = foo.getFunction(1); // We can also store it
storedFunction(); // and call it later
}
QSplitter - это Widget который реализует "масштабирование" внутри себя своих child. Поэтому растягиваем QSplitter обычным Layout менеджером на всю форму в конструкторе. Задаем направление QSplitter - вертикальное или горизонтальное.
m_layout = new QVBoxLayout;
m_layout->setContentsMargins(0, 0, 0, 0);
m_layout->setSpacing(0);
setLayout(m_layout);
m_splitter = new QSplitter(this);
m_splitter->setChildrenCollapsible(false);
m_splitter->setOrientation(Qt::Vertical);
m_layout->addWidget(m_splitter);
Добавляем формы UiOscFrame в сплиттер по нажатию какой-нибудь кнопки Add обычным образом. Но с удалением формы из сплиттера есть нюансы. Надо вызвать Hide() для формы, чтобы место в сплиттере освободилось. Затем форму можно удалять. Для удобства создаваемые формы я храню в QList<UiOscFrame *> m_frames. Лог показывает, что m_splitter→count() показывает правильное количество виджетов в сплиттере при добавлении и удалении.
// Add Widget:
UiOscFrame *osc_frame = new UiOscFrame(this);
m_frames.append(osc_frame);
m_splitter->addWidget(osc_frame);
qDebug() << "Add SplitterChilds: " << m_splitter->count();
// Del Widget
UiOscFrame *osc_frame = m_frames.takeAt(m_frames.count() - 1);
osc_frame->hide();
delete osc_frame;
qDebug() << "Del SplitterChilds: " << m_splitter->count();
std::optional<int32_t> smPosItemLo(AppID app)
{
if (app > NT_SM_DLL::AppID_LaserExt)
return {};
int32_t value = ...
return {value};
}
auto posItmLo = smPosItemLo(app);
if (posItmLo.has_value()) {
ui->lbSelPos->setText(QString::number(*posItmLo));
}
static std::pair<int32_t, bool> getMeanRange()
{
int32_t retMeanRange = ...;
bool retIsValid = ...;
return {retMeanRange, retIsValid};
}
auto [meanRange, rangeValid] = getMeanRange(brd, sm);
int maskToBitInd(uint16_t mask)
{
int indOf1 = std::countr_zero(mask);
if (indOf1 == 16)) // 0 or 16 bits of zeros
return -1;
else
return indOf1;
}
countr_zero( 00000000 ) = 8
countr_zero( 11111111 ) = 0
countr_zero( 00011100 ) = 2
countr_zero( 00011101 ) = 0
Источник - Habr: Числа, которые должен знать каждый программист
| Обращение к кэшу L1 | 0.5 нс |
| Ошибка при предсказании условного перехода | 5 нс |
| Обращение к кэшу L2 | 7 нс |
| Открытие/закрытие мьютекса | 25 нс |
| Обращение к главной памяти | 100 нс |
| Сжатие 1 Кб быстрым алгоритмом | 3,000 нс |
| Пересылка 2Кб по сети со скоростью 1 Гб/с | 20,000 нс |
| Чтение 1 Мб последовательно из главной памяти | 250,000 нс |
| Передача сообщения туда/обратно в одном дата-центре | 500,000 нс |
| Произвольный доступ к жёсткому диску | 10,000,000 нс |
| Чтение 1 Мб последовательно с жёсткого диска | 20,000,000 нс |
| Передача пакета из Калифорнии в Нидерланды и обратно | 150,000,000 нс |
Класс объектов, которые должны быть созданы в массиве:
class Shutter : public QObject
{
Q_OBJECT
public:
enum Shutter_e {
Sh1, Sh2, Sh3, Sh4, Sh5, Sh6,
// not for use
ShutterCount
};
Shutter(Shutter_e sh, QObject *parent = nullptr);
// Нельзя explicit, чтобы объект мог создаваться неявно. Без прямого вызова конструктора.
//explicit Shutter(Shutter_e sh, QObject *parent = nullptr);
};
Объект в котором создается массив объектов:
class ShuttersCtrl : public QObject
{
Q_OBJECT
public:
explicit ShuttersCtrl(QObject *parent = nullptr);
private:
Shutter shutters_[Shutter::ShutterCount];
};
ShuttersCtrl::ShuttersCtrl(QObject *parent)
: shutters_{ {Shutter::Sh1},
{Shutter::Sh2},
{Shutter::Sh3},
{Shutter::Sh4},
{Shutter::Sh5},
{Shutter::Sh6}
}
{
}
typedef std::function<void(const uint8_t *data, uint16_t len)> ParseDataCallBack;
typedef std::function<void(bool connected)> ConnectedCallBack;
class SerialPort
{
public:
explicit SerialPort(const std::wstring &serial, uint16_t buffLen,
ParseDataCallBack fnParseRx,
ConnectedCallBack fnConnected);
// ...
}
//------------ Class receiver -------------
void MainWindow::openSerial(const QString &serial)
{
std::wstring serial_w = serial.toStdWString();
serial_ = new NT::SerialPort(serial_w, 64,
std::bind(&MainWindow::serialParseData, this, std::placeholders::_1, std::placeholders::_2),
std::bind(&MainWindow::serialConnected, this, std::placeholders::_1)
);
ui->grBoxActComPort->setTitle(serial);
}
void MainWindow::serialParseData(const uint8_t *data, uint16_t len)
{
// ...
}
void MainWindow::serialConnected(bool connected)
{
// ...
}
Поскольку при вызове метода класса первым параметром неявно передается указатель на объект, то в std::bind необходимо первым параметром указать this. Следом должны идти параметры, которые будут переданы в функцию явно при ее вызове. Эти параметры задаются через std::placeholders::_1 и т.д.
Например, когда объект SerialPort вызовет функцию fnConnected(state), то std::placeholders::_1 это значение state. Если бы serialConnected(), "присвоенный" в fnConnected(), был обычной функцией, то использовать std::placeholders::_1 было не нужно. Можно было бы просто сделать так: std::bind(&serialParseData).
Но т.к. serialConnecte dэто метод объекта, то в реальности происходит вызов: fnConnected(binded_object, state). Очередность параметров изменилась, первым вместо state идет binded_object. Поэтому надо явно указать в std::bind, что первым параметром будет this, т.е. binded_object. А вторым параметром будет тот, который указан первым при вызове функции, т.е. state.
std::placeholders::_1 и подобные используются для изменения очередности или количества параметров при вызове функции. Т.е. когда сигнатура функции не совпадает с сигнатурой вызова.
std::string wstrToStr(const std::wstring &wstr)
{
size_t len = wcstombs(nullptr, wstr.c_str(), 0) + 1;
char* buffer = new char[len];
std::wcstombs(buffer, wstr.c_str(), len);
std::string str(buffer);
delete[] buffer;
return str;
}
std::wstring strToWstr(const std::string &str)
{
std::wstring wstr{str.begin(), str.end()};
return wstr;
}
class DeviceStorage
{
public:
DeviceStorage(const wchar_t *cfgFileName);
...
};
class Device
{
public:
Device(std::unique_ptr<DeviceStorage> storage)
: storage_{std::move(storage)}
{}
private:
Device(const Device&) = delete;
Device& operator=(const Device&) = delete;
std::unique_ptr<DeviceStorage> storage_;
}
void main()
{
...
Device dev = new Device(std::make_unique<DeviceStorage>(cfgFileName))
...
}
void splitTextToStrs(const std::string& text, char delimiter, std::vector<std::string> &parts)
{
std::string token;
std::istringstream tokenStream(text);
while (std::getline(tokenStream, token, delimiter)) {
parts.push_back(token);
}
}