[c++11] ¿Como pasar n argumentos a una función?

Iniciado por carl0s_47, 8 Marzo 2017, 05:07 AM

0 Miembros y 1 Visitante están viendo este tema.

carl0s_47

Que tal,

Es mi primer pregunta acá, se un poco de C y estoy intentando aprender a programar C++ "moderno" de forma autodidacta y es mi primer proyecto "útil", así que estaré atento a consejos para mejorarlo xD.

Quiero diseñar una GUI (con Qt) con la cual pueda mandarle datos a un microcontrolador por puerto serial, con estos datos se van a configurar varios registros de un sensor que esta conectado al microcontrolador.

El sensor tiene varios registros, cada registro tiene varios campos y cada campo tiene varios valores   que cambian la configuración del sensor, en la siguiente imagen se pueden ver el registro CONFIG_0 y sus campos NUM_TX y TX_FREQ_DIV:



Para representar cada campo del registro utilice un std::map, la llave (key) es el número que se le escribirá al campo en el registro (por ejemplo de 0 a 7 en TX_FREQ_DIV) y el valor (value) es la descripción de cada llave ("Divide by 2", "Divide by 4", etc.), el contenido de cada uno de estos campos los agrego a un combobox de Qt.

Cada registro del sensor puede tener un número diferente de campos, hay registros con dos campos y otros con 3, 4, etc., así que hice una clase básica:

m_register.h
Código (cpp) [Seleccionar]

#ifndef M_REGISTER_H
#define M_REGISTER_H

#include <QString>
#include <map>

class m_register
{
    QString m_Name;
    std::map<int, QString> m_Field_1;
    std::map<int, QString> m_Field_2;
    std::map<int, QString> m_Field_3;

public:
    m_register(QString name) : m_Name(name) {}
    QString getName(void) const;
    void setField_1(const std::map<int, QString> field);
    std::map<int, QString> getField_1(void) const;
    void setField_2(const std::map<int, QString> field);
    std::map<int, QString> getField_2(void) const;
    void setField_3(const std::map<int, QString> field);
    std::map<int, QString> getField_3(void) const;
};

#endif // M_REGISTER_H


m_register.cpp
Código (cpp) [Seleccionar]

#include "m_register.h"

QString m_register::getName() const
{
    return m_Name;
}

void m_register::setField_1(const std::map<int, QString> field)
{
    m_Field_1 = field;
}

std::map<int, QString> m_register::getField_1() const
{
    return m_Field_1;
}

void m_register::setField_2(const std::map<int, QString> field)
{
    m_Field_2 = field;
}

std::map<int, QString> m_register::getField_2() const
{
    return m_Field_2;
}

void m_register::setField_3(const std::map<int, QString> field)
{
    m_Field_3 = field;
}

std::map<int, QString> m_register::getField_3() const
{
    return m_Field_3;
}


Se ve que los métodos setField y getField de cada campo hacen lo mismo, mi duda es si es posible hacer una sola función que acepte n argumentos (n campos) y cree el mismo número de variables de tipo std::map.
¿Podría inicializar un vector sin especificar cuantos elementos contiene y especificar su tamaño en el constructor de m_register?.

He visto que para métodos con número de argumentos variables utilizan ... (olvide el nombre de esos tres puntos) pero no termino de entenderlos, ¿funcionan como argc y argv?

Así va mi GUI xD:


El grupo de arriba es de una prueba que hice sin la clase y el de abajo es una variable de tipo m_register.

Saludos

class_OpenGL

Yo tampoco sé el nombre de los tres puntos, pero el tema va así:

Existe una librería de C (por lo que también está para C++) que se llama cstdarg. Esta es la que te permite pasar n parámetros. Cuando pasas un número indeterminado de parámetros, la función no sabe cuántos parámetros hay o de qué tipo son, además no sabe donde comienzan en memoria por lo que esas tres cosas se deberían indicar con un parámetro de la función. Por ejemplo, voy a hacer una función que acepte n parámetros y que haga la media aritmética:

Código (cpp) [Seleccionar]
#include <iostream>
#include <cstdarg>

// La función solo aceptará doubles, así que el tipo viene implícito en la función.
// La dirección de memoria de los parámetros la sabes gracias al último parámetro
// (el que hay antes de los ...). En este caso, num_elementos. Esto implica que al menos
// necesitar un parámetro en la función (en este caso, num_elementos).
double MediaAritmetica(int num_elementos, ...) {
va_list parametros;
double parametro_actual;
double media;
int i;

va_start(parametros, num_elementos); // Inicializamos 'parametros' usando el último parámetro.

for(i = 0; i < num_elementos; i++) {
parametro_actual = va_arg(parametros, double); // Obtenemos el siguiente parámetro, de tipo double.
media += parametro_actual;
}

media /= num_elementos;
va_end(parametros); // No sé muy bien por qué, pero esta línea es necesaria.

return media;
}

int main() {
double media;

media = MediaAritmetica(4, 3.2, 1.1, 4.3, 2.0);

std::cout << media << std::endl;

return 0;
}

Programador aficionado. Me quiero centrar en programar videojuegos. La API que uso para crearlos es OpenGL

MAFUS

Lo más sensato sería que representaras el sensor en tu código, así, incluso, te sería más fácil depurar.

C, y por ende C++ son lenguajes creados para hablar con la máquina pero muchas veces, sobre todo quien trabaja a más alto nivel olvida o desconoce ese potencial del lenguaje. En tu caso deberías usar los bitfields.

En tu ejemplo, para representar el registro CONFIG_0 podrías hacer así:


#include <stdio.h>
#include <stdlib.h>

typedef enum {
    DIVIDE_BY_2,
    DIVIDE_BY_4,
    DIVIDE_BY_8,    /* Default value */
    DIVIDE_BY_16,
    DIVIDE_BY_32,
    DIVIDE_BY_64,
    DIVIDE_BY_128,
    DIVIDE_BY_256
} prescaler;

struct {
    unsigned TX_FREQ_DIV : 3;
    unsigned NUM_TX : 5;
} CONFIG_0;

/* Aquí otros registros
struct {
    unsigned <campo> : <tamaño en bits>;
    unsigned <campo> : <tamaño en bits>;
    ...
} <nombre del registro>

...

*/

int main() {
    CONFIG_0.TX_FREQ_DIV = DIVIDE_BY_8;
    CONFIG_0.NUM_TX = 5;
   
    puts("CONFIG_0 register status");
    puts("------------------------");
    printf("TX_FREQ_DIV : %u\n", CONFIG_0.TX_FREQ_DIV);
    printf("NUM_TX      : %u\n", CONFIG_0.NUM_TX);
}

ivancea96

Si pones un ejemplo del código en el que uses los métodos de la clase m_register, se me despejarían algunas dudas.
No estoy seguro de que realmente te interese una función variadic.
Si quieres comprimir los 3setters y los 3 getters en 1 setter y 1 getter, podrías meter los 3 maps en un array (o en una std::list si te interesa que sea una cantidad de maps variable (o std::vector, vaya, según necesidades)), y en los setters/getters, recibir como parámetro el índice del map.

Si aun quieres utilizar un método variadic, ver un ejemplo de uso sería muy explicativo.
Y por cierto, ¿qué valores almacenas exactamente en el map?

carl0s_47

Que tal,

Por ahora utilizo la clase solo para llenar de información a la ventana, la primer parte del constructor MainWindow le paso la información al grupo CONFIG_0, con la clase paso la información a CONFIG_0_c.

Código (cpp) [Seleccionar]

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QDebug>
#include <QString>

#include <map>

#include "m_register.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    ui->groupBox->setTitle("CONFIG_0");
    ui->label->setText("NUM_TX");
    ui->label_2->setText("TX_FREQ_DIV");

    const std::map<int, QString> NUM_TX{{0, "0 Pulses"}, {1, "1 Pulse"}, {2, "2 Pulses"}, {3, "3 Pulse"}, {4, "4 Pulses"}, {5, "5 Pulses"},
                                        {6, "6 Pulses"}, {7, "7 Pulse"}, {8, "8 Pulses"}, {9, "9 Pulse"}, {10, "10 Pulses"}, {11, "11 Pulses"},
                                        {12, "12 Pulses"}, {13, "13 Pulse"}, {14, "14 Pulses"}, {16, "16 Pulse"}, {17, "17 Pulses"}, {18, "18 Pulses"},
                                        {19, "19 Pulses"}, {20, "20 Pulse"}, {21, "21 Pulses"}, {22, "22 Pulse"}, {23, "23 Pulses"}, {24, "24 Pulses"},
                                        {25, "25 Pulses"}, {26, "26 Pulse"}, {27, "27 Pulses"}, {28, "28 Pulse"}, {29, "29 Pulses"}, {30, "30 Pulses"},
                                        {31, "31 Pulses"}, {32, "32 Pulse"}, {33, "33 Pulses"}, {34, "34 Pulse"}, {35, "35 Pulses"}};

    const std::map<int, QString> TX_FREQ_DIV{{0, "Divide by 2"}, {1, "Divide by 4"}, {2, "Divide by 8"}, {3, "Divide by 16"},
                                             {4, "Divide by 32"}, {5, "Divide by 64"}, {6, "Divide by 128"}, {7, "Divide by 256"}};

    for(auto const& it : NUM_TX){
        ui->comboBox->addItem(it.second);
    }

    for(auto const& it : TX_FREQ_DIV){
        ui->comboBox_2->addItem(it.second);
    }

    // Usando la clase

    m_register m_config_0{"CONFIG_0_c"};
    m_config_0.setField_1(NUM_TX);
    m_config_0.setField_2(TX_FREQ_DIV);

    ui->groupBox_2->setTitle(m_config_0.getName());
    ui->label_3->setText("NUM_TX_c");
    ui->label_4->setText("TX_DIV_DIV_c");

    for(auto const& it : m_config_0.getField_1()){
        ui->comboBox_3->addItem(it.second);
    }

    for(auto const& it : m_config_0.getField_2()){
        ui->comboBox_4->addItem(it.second);
    }

}

MainWindow::~MainWindow()
{
    delete ui;
}


Me falta añadir una variable que guarde la locación del campo (a partir de que bit empieza), entonces podría hacer un struct con el std::map y la posición, y en la clase m_register tener un vector de este struct.

Saludos

ivancea96

Ya que lso números son contiguos de 0 a N, en vez de un map, tal vez te interese utilizar un vector o una list.

A parte de eso, sobre el método variadic, lo dicho. Si puedes, pon un ejemplo de cómo usarías el método variadic con una cantidad indefinida de argumentos. No digo que lo hagas, sinó que lo utilices como si existiera para saber qué quieres hacer con él.