• XSS.stack #1 – первый литературный журнал от юзеров форума

Я изучаю C++. Нужен код ревью.

joe

HDD-drive
Пользователь
Регистрация
15.01.2024
Сообщения
20
Реакции
1
Занимаюсь изучением C++ около 2-3 месяцев, неплохо знаю Python.

Поставил себе задачу написать программу, которая посредством выбора (1 | 2) переводит вводимое число либо из 10 системы счисления в 2, либо наоборот. Вышел вот такой код:

C++:
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>

#define BINARY 2

int main(int argc, char* argv[]) {
    int number;
    int i{ 0 }; int sum{ 0 };
    int* numptr = &number;
    int* sumptr = &sum; int* iptr = &i;
    
    short signed choice;

    std::vector<int> bin{};

    std::cin >> choice;
    std::cin >> number;
    
    switch (choice) {
        case 1:
            while (*numptr) {
                bin.push_back(*numptr % 2);
                *numptr /= 2;
            }

            std::reverse(bin.begin(), bin.end());

            for (int i = 0; i < bin.size(); i++) {
                std::cout << bin[i];
            }

            break;
        case 2:
            while (*numptr) {
                bin.push_back(((*numptr) % 10) * pow(BINARY, i));

                *numptr /= 10; i++;
            }
            for (int i = 0; i < bin.size(); i++) {
                *sumptr += bin[i];
            }

            std::cout << sum << std::endl;
            break;
    }

    return 0;
}

Пожалуйста, укажите на мои ошибки, к примеру с указателями, переменными, расположением ключевых слов в коде и т.п.
Спасибо!
 
Пожалуйста, обратите внимание, что пользователь заблокирован
1) указатели тут не нужны
2) нет дефолта в свитче
3) вектор слишком жирно для такого. У тебя размер известен ( sizeof(number) * 8 ). То есть либо укажи сразу сколько памяти вектору выделять, либо используй умные указатели для создания дин. массива.
Твой вектор в самом худшем случае будет хранить дату размером sizeof(int) * 8 * sizeof(int). То есть 128 БАЙТ. Ничего не смущает?) У тебя число изначально 4 байта, а ты его в 128 разложил)))
Так что от вектор и дин массива тоже надо избавиться в пользу std::bitset<sizeof(int) * 8> или перейти на чистые бинарные операции.
4) Чтобы просуммировать все элементы массива/иттерируемого объекта, для объектов которого реализован оператор сложения, ты можешь использовать std::accumulate из <numeric>.
5) нет смысла писать signed. short и так знаковый до того момента, пока ты не написал unsigned short :)
6) для перевода в двоичную систему счисления вовсем необязательно прибегать к операции деления. Можно получить доступ напрямую к битам.
7) подумай о том, что число из двоичной системы может быть, допустим таким: "100000000001001000100". Как ты понимаешь, оно не влезет в int, хотя при переводе в десятичную систему счисления это 1049156.
Двоичное представление 1049156 никуда не влезет, кроме строки. (либо реализации чего-то для работы с большими числами)
А если ты изначально запихнул в строку, то зачем вообще перевод из двоичной в десятичную, если можно юзать stoi, в котором можно указать базу.

C++:
#include <iostream>
#include <string>

enum class BASES{
    to_binary = 1,
    to_decimal
};

int main()
{
    const int binary_constant = 2;

    std::string number_str = "";
    short choice = 0;

    std::cin >> choice;
    std::cin >> number_str;

    switch (static_cast<BASES>(choice))
    {
    case BASES::to_binary:
    {
        int number = std::stoi(number_str); // stoi еще придется в try catch обернуть. Ну либо юзать atoi и проверить заранее, что строка != "0"
        for (int i = sizeof(number) * 8 - 1; i >= 0; --i)
        {
            std::cout << ((number & (1 << i)) ? 1 : 0);
        }
        std::cout << std::endl;
        break;
    }
    case BASES::to_decimal:
        std::cout << std::stoi(number_str, nullptr, binary_constant);
        break;
    default:
        std::cout << "Unknown choice" << std::endl;
        break;
    }

    return 0;
}

вывод двоичного числа, начиная с единицы можно сделать так:
C++:
case BASES::to_binary:
    {
        auto find_last_true_bit = [](int num) -> int
        {
            int pos = -1;
            for (int i = sizeof(num) * 8 - 1; i >= 0; --i)
            {
                pos = ((num & (1 << i)) ? i : pos);
            }
            return pos;
        };
        int number = std::stoi(number_str); // stoi еще придется в try catch обернуть. Ну либо юзать atoi и проверить заранее, что строка != "0"
        int end_bit = find_last_true_bit(number);
        if(end_bit == -1)
        {
            std::cout << 0 << std::endl;
            break;
        }
        for (int i = end_bit; i >= 0; --i)
        {
            std::cout << ((number & (1 << i)) ? 1 : 0);
        }
        std::cout << std::endl;
        break;
    }

ну это так, на быструю руку. Не претендую на самый лучший вариант, но мне кажется, что так норм


upd:
в строку, наверное, стоит записывать только если попал в кейс с to_decimal. В to_binary стоит сразу в int писать.
 
1) указатели тут не нужны
2) нет дефолта в свитче
3) вектор слишком жирно для такого. У тебя размер известен ( sizeof(number) * 8 ). То есть либо укажи сразу сколько памяти вектору выделять, либо используй умные указатели для создания дин. массива.
Твой вектор в самом худшем случае будет хранить дату размером sizeof(int) * 8 * sizeof(int). То есть 128 БАЙТ. Ничего не смущает?) У тебя число изначально 4 байта, а ты его в 128 разложил)))
Так что от вектор и дин массива тоже надо избавиться в пользу std::bitset<sizeof(int) * 8> или перейти на чистые бинарные операции.
4) Чтобы просуммировать все элементы массива/иттерируемого объекта, для объектов которого реализован оператор сложения, ты можешь использовать std::accumulate из <numeric>.
5) нет смысла писать signed. short и так знаковый до того момента, пока ты не написал unsigned short :)
6) для перевода в двоичную систему счисления вовсем необязательно прибегать к операции деления. Можно получить доступ напрямую к битам.
7) подумай о том, что число из двоичной системы может быть, допустим таким: "100000000001001000100". Как ты понимаешь, оно не влезет в int, хотя при переводе в десятичную систему счисления это 1049156.
Двоичное представление 1049156 никуда не влезет, кроме строки. (либо реализации чего-то для работы с большими числами)
А если ты изначально запихнул в строку, то зачем вообще перевод из двоичной в десятичную, если можно юзать stoi, в котором можно указать базу.
Спасибо огромное за разбор!
У меня пару вопросов:
1) А когда нужны указатели?
2) Оператор default является необязательным, зачем он?
3) Что такое чистые бинарные операции?

Обратите внимания, что я новичок.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
8) Вынеси код из main в отдельный класс/в отдельные функции, которые будут вызываться из main.
9) Программа должна возвращать значение, отличное от нуля, если она отработала с ошибкой (например некорректный ввод). Ноль возвращается только в случае успеха.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
2) Оператор default является необязательным, зачем он?
он является необязательным, но в случае, когда туда может попасть что-то не то, он обязателен)))
3) Что такое чистые бинарные операции?
битовые*. Я ошибся. Это называется битовые операции. Гугли битовые операции c++. Ну и вспоминай, наверное, школьную информатику/матлогику/дискретку или где это еще могут проходить.
1) А когда нужны указатели?
У них много применений.
Указатель это просто число. Адрес, который указывает на память с какой-то информацией. (если это конечно не нулевой указатель)
Как минимум указатели помогают избегать копирования составных типов данных (структур и классов).
Дам тебе яркий пример даже

C++:
#include <iostream>
#include <vector>

size_t count_even_numbers(std::vector<int> nums){ // <-- тут ошибка
    size_t res = 0;
    for(size_t i = 0; i < nums.size(); ++i){
        if(!(nums[i] & 1))
            ++res;
    }
    return res;
}

int main()
{
    std::vector<int> numbers = {1,2,3,4,5};
    std::cout << count_even_numbers(numbers) << std::endl;

    return 0;
}

Представь, что вектор numbers содержит 268435456 чисел. То есть занимает 1 гб памяти.
При вызове count_even_numbers он скопируется в новый вектор nums. И твоя программа будет занимать 2 гб памяти до тех пор, пока не завершится функция.
Получается 2 проблемы: много памяти сожрало, ушло время на копирование гигабайта памяти.

Тут можно применить указатель, но в c++ в таких ситуациях применяют ссылки. Код сигнатуры функции count_even_numbers должен начать выглядеть так:
C++:
size_t count_even_numbers(const std::vector<int>& nums); // константная ссылка, потому что мы не меняем nums

Указатели в обычном понимании нужны скорее всего только при динамической аллокации памяти и при полиморфизме. В остальных случаях скорее всего можно обойтись ссылками.
Я думаю, что тебе рано еще говорить про полиморфизм у классов, потому что скорее всего ты про них еще не знаешь))


upd:
вообще есть рекомендация, что сырые указатели не надо использовать в C++. Следуй ей))
 
Последнее редактирование:
8) Вынеси код из main в отдельный класс/в отдельные функции, которые будут вызываться из main.
9) Программа должна возвращать значение, отличное от нуля, если она отработала с ошибкой (например некорректный ввод). Ноль возвращается только в случае успеха.
До классов пока не дошёл) Спасибо за ответ!
 
можно юзать промт


Код:
Act as a world-class software engineer specializing in code review. Given the following context, criteria, and instructions, conduct a code review for a C++ program.

## Context
A team of software developers has completed a C++ program.

## Approach
Conduct a line-by-line review of the code to ensure that best practices are followed, the code is efficient, and potential bugs or vulnerabilities are identified. Pay particular attention to memory management, error handling, security, and compliance with C++ coding standards.

## Response Format
Provide a detailed report with identified issues, suggestions for improvement, and specific examples where applicable.

## Instructions
1. Analyze the program's structure, readability, and modularity.
2. Evaluate variable naming, comments, and documentation for clarity and consistency.
3. Check for memory leaks, buffer overflows, and other common C++ pitfalls.
4. Identify potential optimizations and refactoring opportunities.
5. Verify that the code adheres to the team's coding style guidelines.
6. Flag any security vulnerabilities or potential compliance risks. Code :

Выдаст такой лог
Код:
### Code Review Report

#### 1. Program Structure and Readability:
- **Structure**: The program consists of a single `main()` function, which makes it straightforward. However, for larger projects, it's advisable to modularize the code into functions or classes.
- **Readability**: The code is generally readable due to its simplicity. However, some parts could be improved for clarity, especially within the switch-case block.

#### 2. Variable Naming, Comments, and Documentation:
- **Variable Naming**:
  - `number_str` is a bit vague. Consider renaming it to something more descriptive like `input_number_str`.
  - `choice` could be named `conversion_choice` or something similar to indicate its purpose more explicitly.
- **Comments**: The comment about `stoi` and error handling is helpful, but it should be addressed in the code rather than just commented.
- **Documentation**: There's no documentation provided. Adding brief comments or function/class documentation can improve code understanding.

#### 3. Memory Management and Error Handling:
- **Memory Management**: There are no dynamic memory allocations, so memory management isn't an issue in this code.
- **Error Handling**: Error handling is minimal. As mentioned in the comment, using `stoi` without proper error handling can lead to runtime exceptions. It's recommended to handle potential exceptions using try-catch blocks.

#### 4. Optimization and Refactoring:
- **Optimization**:
  - In the binary-to-decimal conversion case, the `sizeof(number) * 8 - 1` calculation is not optimal. It would be better to use `std::numeric_limits<int>::digits - 1` to get the number of bits in an integer type.
- **Refactoring**:
  - Consider refactoring the conversion logic into separate functions for better modularity and readability.
  - The switch-case block could be extracted into a separate function, enhancing code clarity.

#### 5. Coding Style Guidelines:
- The code mostly adheres to C++ coding standards, but it lacks consistency in variable naming and could benefit from more descriptive names.

#### 6. Security and Compliance:
- **Security**:
  - The code doesn't directly expose any security vulnerabilities. However, error handling should be improved to prevent potential runtime exceptions.
- **Compliance**:
  - There are no specific compliance risks evident in this code.

#### Overall Comments:
- The code fulfills its basic purpose but lacks robust error handling and could benefit from better modularity.
- Improvements in variable naming, comments, and error handling are recommended to enhance maintainability and reliability.
- Consider incorporating suggestions for optimization and refactoring to improve performance and readability.

### Suggestions for Improvement:
1. Refactor the code into functions or classes to improve modularity.
2. Enhance error handling, particularly around input conversion using `stoi`.
3. Improve variable naming for clarity and consistency.
4. Consider optimizing the binary-to-decimal conversion logic for efficiency.
5. Add comments/documentation to clarify the code's purpose and usage.
 
Может быть я опоздал, но хочу сказать такую вещь, что начинать изучение плюсов и сразу же использовать на начальных этапах vector, string - плохая манера.

const char* и new(для работы с массивами) надо начинать
 
Пожалуйста, обратите внимание, что пользователь заблокирован
привет
Этот код написан на C++ и представляет собой простую программу для преобразования чисел в двоичную систему счисления или для вычисления суммы цифр числа, если выбрано другое основание.

Однако, у него есть несколько проблем:

  1. В случае выбора основания 2 (case 2), вы используете неправильную формулу для вычисления бинарного значения цифры. Вместо этого нужно использовать деление и остаток от деления на 2.
  2. Переменная i в основной функции не инициализируется нулем, что может привести к неопределенному поведению при использовании.
  3. Вместо использования указателей для переменных number, i и sum, проще и безопаснее использовать простые переменные.
  4. Не обрабатываются случаи, когда вводится отрицательное число или введен некорректный выбор.
Ниже приведен исправленный код:
Код:
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>

#define BINARY 2

int main() {
    int number;
    int sum = 0;
    int choice;

    std::vector<int> bin;

    std::cout << "Enter your choice (1 for binary, 2 for sum of digits): ";
    std::cin >> choice;

    if (choice == 1 || choice == 2) {
        std::cout << "Enter a number: ";
        std::cin >> number;

        switch (choice) {
            case 1:
                while (number) {
                    bin.push_back(number % 2);
                    number /= 2;
                }

                std::reverse(bin.begin(), bin.end());

                for (int i = 0; i < bin.size(); i++) {
                    std::cout << bin[i];
                }
                break;
            case 2:
                while (number) {
                    sum += number % 10;
                    number /= 10;
                }
                std::cout << "Sum of digits: " << sum << std::endl;
                break;
        }
    } else {
        std::cout << "Invalid choice!" << std::endl;
    }

    return 0;
}
Этот исправленный код исправляет перечисленные выше проблемы и должен правильно выполнять требуемые операции.

pss ИИ бот исправил код
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх