Menú

Mostrar Mensajes

Esta sección te permite ver todos los mensajes escritos por este usuario. Ten en cuenta que sólo puedes ver los mensajes escritos en zonas a las que tienes acceso en este momento.

Mostrar Mensajes Menú

Mensajes - K-YreX

#481
Programación C/C++ / Re: Importante-Curioso
28 Agosto 2019, 15:44 PM
Una pila o stack es un contenedor LIFO (last in first out), es decir, que el último que entra es el primero que sale. Imagina para esto una "pila" de platos, tú los vas amontonando uno encima de otro y cuando vas a coger uno, coges el de arriba del todo. Puedes fijarte en el contenedor <stack> de la STL para C++ para crear una pila en C lo más parecida posible a lo que existe. Entonces necesitas un contenedor de datos ordenado (en tu caso un array) y las siguiente funciones por lo menos (más o menos ya las tienes):

T top(); // devuelve el ultimo elemento de la pila del tipo que sea (int/float/char/...)
void push(T elemento); // introduce un nuevo elemento
void pop(); // saca el ultimo elemento de la pila
bool empty(); // devuelve true/false dependiendo de si la pila esta vacia o no
int size(); // devuelve el numero de elementos que contiene la pila


Una cola o queue es lo contrario a la pila, un contenedor FIFO (first in first out) donde el primero que entra es el primero que sale. Imagina una cola de personas que se ponen en un cajero, el primero que llega es el primero que usa el cajero y se va. En C++ también existe una cola de la STL <queue> y tiene los siguiente elementos además de un contenedor ordenado (que en tu caso usarías un array):

T front(); // devuelve el elemento que antes se inserto en la cola
void pop(); // elimina el elemento que antes se inserto
T back(); // devuelve el elemento que se inserto el ultimo
// No existe un pop() para eliminar el ultimo ya que entonces no seria una cola ya que dejaria de ser FIFO
void push(T elemento); // introduce un nuevo elemento a la cola
bool empty(); // devuelve true/false dependiendo de si la cola esta vacia o no
int size(); // devuelve el numero de elementos que contiene la cola


Una lista o list es como una mezcla, es un contenedor que permite insertar y eliminar elementos tanto por delante como por detrás. Al igual que para las otras, en C++ tenemos el contenedor <list> de la STL que tiene además del propio contenedor de valores las siguientes funciones:

void push_back(T elemento); // inserta un elemento al final de la lista
void push_front(T elemento); // inserta un elemento al comienzo de la lista
void pop_back(); // elimina el ultimo elemento de la lista
void pop_front(); // elimina el primer elemento de la lista
T front(); // devuelve el primer elemento de la lista
T back(); // devuelve el ultimo elemento de la lista
bool empty(); // devuelve true/false dependiendo de si la lista esta vacia o no
int size(); // devuelve el numero de elementos de la lista



Ahora respecto a las mejoras de tu programa. Algunas buenas prácticas son:
  • Evitar el uso de variables globales siempre que se pueda
  • Evitar mostrar mensajes dentro de las funciones a no ser que la función se encargue justo de eso, de mostrar un mensaje (ej; un menú).
  • Las constantes en mayúsculas. Por ejemplo <max> -> <MAX>
    Supongo que si estás aprendiendo ahora, todavía no te habrán enseñado el uso de <struct> para agrupar variables o el paso de arrays como parámetros por eso lo usas de forma global y por separado <pila> y <tope>. Para el caso de esas dos variables lo dejaré pasar por lo dicho, aún no te habrán dicho cómo se hace de otra forma.
    Sin embargo, la variable <i> de la línea 10 es global y luego en las líneas 140 y 150 vuelves a declarar una variable <i> para cada función de forma local. En este caso te recomiendo borrar la de la línea 10 para evitar una variable global.
    También tienes en la línea 10 una variable <acum> que sólo usas dentro de la función <sumaPila()> y que contiene lo mismo que la variable <suma> que declaras dentro de la función. Entonces la variable <acum> sobra también.
    Otra cosa importante es el uso de funciones. Una función sirve para agrupar un grupo de código y poder reutilizarlo por lo que hay que crear funciones fáciles de reutilizar. Tú pregúntate que programa se entiende mejor, el primero o el segundo:

    int main(){
        int eleccion;
        menu(eleccion);
    }

    int main(){
        int eleccion = mostrarMenu();
        while(eleccion != -1){
            switch(eleccion){
                case 1:
                    pilaNueva();
                    break;
                case 2:
                    if(pilaVacia()) printf("La pila esta vacia");
                    else printf("La pila no esta vacia");
                    break;
                // y asi con todos que me canso de seguir...
            }
            eleccion = mostrarMenu(); // al acabar lo que sea volvemos a dar a elegir
        }
    }

    Como ves en el primer caso delegas TODO el trabajo a otra función <menu()> por lo que te ves en las mismas. El <main> no te dice nada y todo el código está en <menu()>. La cosa está en que leyendo el <main> entiendas lo que el programa hace pero no hace falta que entiendas cómo lo hace. En el segundo ejemplo, se ve que muestras un menú y ese menú devuelve una opción, luego según la opción ves lo que el programa hace (por ejemplo, crear una pila nueva) pero si quieres saber CÓMO es cuando tienes que ir a la función <pilaNueva()>. Así el código queda limpio pero a la vez comprensible.

    Paso de parámetros:
    Tú tienes que pasar una variable como parámetro a una función cuando necesites esa variable fuera de la función y el valor de esa variable condicione algo de la función.
    En tu función <menu()> recibes un parámetro <eleccion> que:
  • 1. No tiene ningún valor definido,
  • 2. No sirve para nada fuera de la función.
    Necesitas ese parámetro? NO.
    En la función <apilar()> sin embargo es al revés.
  • Objetivo de la función: insertar un elemento en la pila.
  • Parámetro: el elemento a insertar.
    Hasta aquí todo perfecto pero resulta que lo que luego haces en la función es insertar el elemento, pedir uno nuevo y sobreescribir el primero por el nuevo... Para qué mandas un elemento como parámetro si no lo usas para nada "productivo"??


    Respecto al resto de funciones:
    La función <menu()> hace demasiadas cosas y recibe parámetros innecesarios.
    Recomendación: hacer una función <int mostrarMenu()> que sólo muestre las opciones, de al usuario una a elegir y devuelva ese valor.

    La función <crearPilaVacia()> tiene que retornar un <bool> y no retorna nada. Por tanto lo que retorna es -1 que es el valor de la asignación de la función. Y -1 traducido a <bool> es siempre <true>. No dejes la tarea de adivinar el valor de retorno. Deja bien claro qué devuelve una función (si tiene que devolver algo, claro, yo en este caso lo veo innecesario...).
    Recomendacion: una función <void pilaNueva()> que lo único que haga sea poner el <tope> a -1.

    Las funciones <pilaVacia()> y <pilaLlena()> podrías ahorrártelas ya que conoces el tamaño de la pila <MAX> y el número de elementos <tope>. Pero tampoco es un problema grave, si prefieres usar esas funciones eres libre de hacerlo pero se pueden acortar:

    bool pilaVacia(){
        if(tope == -1){ // comprueba si lo de dentro del parentesis es true o false...
            return true; // ... si el parentesis vale true, devuelve true
        }
        else{ //... si el parentesis vale false...
            return false; // ... devuelve false
        }
    }
    // Al final lo que haces es devolver lo mismo que el resultado del parentesis
    // No sera mejor entonces devolver directamente el resultado del parentesis?
    bool pilaVacia(){
        return (tope == -1);
    }

    Recomendación: acortar ambas funciones como se ve en el ejemplo.

    La función <apilar()>, como antes te he explicado, no tiene fundamento lo que haces. Además como antes decía es mejor evitar mostrar mensajes dentro de las funciones.
    Recomendación: una función <bool apilar(int elemento)> que compruebe si se puede insertar el elemento. Si es posible, lo inserta y devuelve true y si no es posible no lo inserta y devuelve false. Así sin mostrar mensajes tú puedes saber si el elemento se insertó o no.

    Para la función <desapilar()> más de lo mismo. Se pueden evitar los mensajes haciendo lo mismo que en <apilar()>
    Recomendación: una función <bool desapilar()> que compruebe si la pila está vacía. Si está vacía devuelve false y si no está vacía, elimina el elemento (restar 1 a <tope>) y devuelve true. (No es necesario poner el elemento a 0 antes de eliminarlo).

    En la función <visualizarElementos()> se podría eliminar el mensaje que sirve de "titulillo" y dejar únicamente los elementos. En caso de querer que se vea el titulillo ese siempre puedes ponerlo antes de llamar a la función.

    printf("Los elementos de la pila son: ");
    visualizarElementos();

    Recomendación: dejar la función igual <void visualizarElementos()> pero quitando el primer mensaje.

    Y para la última función, <sumaPila()>, que también muestra los mensajes en pantalla tenemos que evitar esto.
    Recomendación: una función <int sumaPila()> que calcule la suma y en vez de mostrarla por pantalla, la devuelva. Entonces para mostrar la suma haríamos:

    printf("La suma de los elementos de la pila es: %d", sumaPila());



    Unos consejos extra:

    // Cuando se repite la misma variable justo antes y justo despues del = se puede acortar:
    suma += otro; // equivale a: suma = suma + otro
    resta -= otro; // equivale a: resta = resta - otro
    producto *= 2; // equivale a: producto = producto * 2
    // etc...
    // Para incrementar variables en una unidad se puede hacer:
    ++numero; // equivale a: numero = numero + 1
    numero++; // equivale a: numero = numero + 1
    // Para decrementar variables en una unidad se puede hacer:
    --numero; // equivale a: numero = numero - 1
    numero--; // equivale a: numero = numero - 1

    Y para los condicionales:

    // Poner:
    if(condicion == true){...}
    // ...es lo mismo que poner:
    if(condicion){...}
    // Al igual que para:
    if(condicion == false){...}
    // ...es lo mismo que poner:
    if(!condicion){...}
#482
Te pongo un caso más útil para que veas cómo se podrían usar los punteros a funciones. Imagina que vas a hacer un programa que ordene un array y quieres dar la posibilidad de ordenarlo ascendente y descendentemente. La primera opción que le vendría a cualquiera a la cabeza es esta:
Código (cpp) [Seleccionar]

void ordenarAscendente(int *numeros, int size){
    for(size_t i = 1; i < size; ++i)
        for(size_t j = 0; j < size-i; ++j)
            if(numeros[j] > numeros[j+1]){
                numeros[j] += numeros[j+1];
                numeros[j+1] = numeros[j] - numeros[j+1];
                numeros[j] -= numeros[j+1];
            }
}

void ordenarDescendente(int *numeros, int size){
    for(size_t i = 1; i < size; ++i)
        for(size_t j = 0; j < size-i; ++j)
            if(numeros[j] < numeros[j+1]){
                numeros[j] += numeros[j+1];
                numeros[j+1] = numeros[j] - numeros[j+1];
                numeros[j] -= numeros[j+1];
            }
}

Tenemos dos funciones que si ves siguen el mismo tipo de ordenamiento (un bubblesort muy simple :xD) pero una de forma ascendente y otra de forma descendente. Tampoco es demasiado trabajo pero imagina que queremos ordenar un array de 50 formas distintas, tendríamos que hacer 50 funciones iguales cambiando únicamente la condición de ordenamiento. Entonces podríamos hacer algo así:
Código (cpp) [Seleccionar]

bool mayor(int a, int b){
    return a > b;
}

bool menor(int a, int b){
    return a < b;
}

void ordenar(int *numeros, int size, bool (*orden)(int,int)){
    for(size_t i = 1; i < size; ++i)
        for(size_t j = 0; j < size-i; ++j)
            if(!orden(numeros[j], numeros[j+1])){
                numeros[j] += numeros[j+1];
                numeros[j+1] = numeros[j] - numeros[j+1];
                numeros[j] -= numeros[j+1];
            }
}

Y esto mismo lo podríamos hacer con otros tipos de ordenamiento. Es decir, hacemos una simple función que determine el tipo de orden a seguir y reutilizamos la función que tiene el algoritmo de ordenamiento.
Luego ya si nos venimos arriba le ponemos unos <template> a ese programa y ya podemos ordenar cualquier tipo de dato (primitivos/objetos) que tengan claro está los operadores que usemos sobrecargados (en este caso < y >). Tampoco quiero adelantarte contenidos, ya lo verás más adelante pero para que veas que conocer los recursos disponibles ayuda a ahorrar muchas líneas de código al programador.

PD: Ahí te he dejado también como curiosidad cómo intercambiar el valor de dos variables sin usar ninguna variable auxiliar.
#483
Citar
Creo que ya he visto todo de los punteros creo
Claro que no :xD :xD. Para trabajar a un nivel básico o incluso un poco avanzado ya tienes pero los punteros no son sólo lo que has visto. Como bien dices aunque un poco mezclado tienes por una parte los punteros de tipo <void> y por otra parte los punteros a funciones.


Los punteros de tipo <void> se usan para apuntar a un dato sin especificar el tipo de dato y así mediante estos punteros podemos pasar diferentes tipos de datos a una misma función. Esto da mucha libertad a la hora de trabajar con ello (pero también hay que saber cuándo de verdad compensa usarlos, de momento es mejor que no los uses). A la vez que da libertad también la quita ya que no podemos desreferenciar el puntero (mediante *) para trabajar con su valor sin antes hacer un <cast>.
Código (cpp) [Seleccionar]

void incrementar(void *puntero, int size){
    if(size == sizeof(char)){
        char *p_char = (char*)puntero;
        ++(*p_char);
    }
    if(size == sizeof(int)){
        char *p_int = (int*)puntero;
        ++(*p_int);
    }
}

int main(){
    char letra = 'a';
    int numero = 1;
    incrementar(&letra, sizeof(letra));
    incrementar(&numero, sizeof(numero));
    cout << letra << " - " << numero << endl;
}


Salida: b - 2



Por otro lado están los punteros a funciones. Esto sirve para pasarle a una función, otra función.
Código (cpp) [Seleccionar]

int sumar(int a, int b){
    return a + b;
}

int restar(int a, int b){
    return a - b;
}

int realizarOperacion(int (*operacion)(int,int), int a, int b){
    int resultado = (*operacion)(a,b);
    return resultado;
}

int main(){
    int a = 4, b = 1;
    int (*realizarResta)(int,int) = restar;
    int resultadoSuma = realizarOperacion(sumar, a, b);
    int resultadoResta = realizarOperacion(realizarResta, resultadoSuma, b);
}

Este es un ejemplo típico de punteros a funciones. Está claro que no parece necesario usarlos pero pueden darse casos en los que sí y entonces pues ahí está la opción de hacerlo así.


Podía haber hecho un último mensaje corto pero eso no es lo mío. Dejo un ejemplo de cada uno de estos y así podríamos decir que este tema del foro queda bastante completo en cuanto a punteros se refiere.
PD: Te recomiendo para tenerlo más a mano hacerte un programa (por ejemplo punteros.cpp o punterosExplicaciones.cpp) y ponerte ahí todo lo que has ido viendo que se puede o no se puede hacer y con algunos comentarios. Y en caso de que necesites recordar algo más en profundidad entonces ya puedes volver aquí y buscar este tema. Suerte :-X
#484
Citar
PD: Si lo he entendido bien esto:
Código (cpp) [Seleccionar]

void funcion(int**& p_m) // usando new
//seria equivalente a:
void funcion(int m[][100]

La diferencia radicaría en que en la primera se usa para crear una matriz dinámica y así ahorrar espacio en la memoria y en la segunda ya tienes el espacio reservado, no?
Respecto a esto, más que para qué se usa, es qué estoy haciendo con cada cosa. La primera cosa a tener en cuenta es, dicho de una forma poco exacta es que unos corchetes se pueden sustituir por un asterisco y un asterisco por unos corchetes (hablando de arrays como parámetros). Entonces tenemos estas opciones principalmente:
Código (cpp) [Seleccionar]

void funcion1(int **matriz); // pasamos la direccion de memoria del elemento [0][0]
void funcion2(int **&matriz); // pasamos la direccion de memoria que apunta a la direccion de memoria del elemento [0][0]
void funcion3(int matriz[][TAM]); // igual que funcion1() pero necesitamos indicar todas las dimensiones excepto la primera
void funcion4(int &matriz[][TAM]); // igual que funcion2() pero indicando las dimensiones como en la funcion3()

Luego claro está que podemos mezclar asteriscos y corchetes y decir "pues quito un par de corchetes y pongo un asterisco" o al revés. Recalco: lo de intercambiar asteriscos y corchetes es para arrays. Si queremos pasar un puntero que no es un array no podemos usar corchetes.
No puedo explicarte mucho más de esto ya que el tema de los arrays estáticos ha ido evolucionando mucho y no estoy muy puesto en ello pero ahora por ejemplo se permite crear un array estático indicando su tamaño en una variable cosa que en versiones anteriores no se podía y de ahí lo de declarar los tamaños usando <#define> (sobre todo en C) o usando una constante global al comienzo del programa <const int> (en C++).

Citar
PD2: las variables dinámicas no tienes nombre? o solo se les puede llamar a través del puntero del que se crean?
Claro que tienen nombre. El puntero. Lo puedes entender como si al hacer:
Código (cpp) [Seleccionar]

int miArray[5];

El compilador por dentro hiciese:
Código (cpp) [Seleccionar]

int *miArray = new int[5];

En el primer caso el nombre del array está claro. Y en el segundo? Pues es lo mismo, el nombre es el del puntero que lo está apuntando. Ya que con memoria dinámica se pueden hacer cosas como:
Código (cpp) [Seleccionar]

int *miArray = new int[10];
int *otroArray = new int[5];
// Queremos intercambiarlos pero en cuanto asignemos algo a uno de esos punteros, perderemos el array entonces:
int *auxiliar = miArray; // Ahora el primer array tiene dos nombres: <auxiliar> y <miArray>
miArray = otroArray; // Ahora hacemos que el puntero <miArray> apunte al mismo sitio que <otroArray>
otroArray = auxiliar; // Y ahora <otroArray> apunta al mismo sitio que <auxiliar> que era el array original al que apuntaba <miArray>

Otra duda típica en estos casos es: ¿y hago algún <delete> por ejemplo a <auxiliar>? Pues no. El delete no borra el puntero, borra la memoria dinámica a la que apunta ese puntero. Entonces si hacemos por ejemplo:
Código (cpp) [Seleccionar]

delete [] auxiliar;

<auxiliar> a dónde estaba apuntando? Al mismo array que <otroArray>. Pues entonces <otroArray> ya sólo es un puntero ya que el array que habíamos reservado, nos lo hemos cargado con ese <delete>.
#485
Programación C/C++ / Re: Importante-Curioso
27 Agosto 2019, 17:55 PM
Pues tu problema es porque estás introduciendo valores (opción 4) sin crear la pila vacía primero (opción 1).
Pero vamos que aparte de ese problema, ese código es muy mejorable (pero MUY MUCHO)... Tienes:
  • Variables globales
  • Variables sin inicializar
  • Variables repetidas
  • Parametros innecesarios
  • Funciones a las que les falta el <return>
  • Funciones mal utilizadas
    Como no sé si te interesa saber más sobre esto o no y no me apetece explicarme para nada, si quieres saber más detalles sobre el porqué del problema o algunos consejos para mejorar ese código, házmelo saber. AVISO: no te lo voy a dar todo ya hecho. Y si no lo necesitas, tema zanjado. :-X :-X
#486
Bueno, sigues teniendo un pequeño error. Fíjate que para mostrar la matriz haces:
Código (cpp) [Seleccionar]

for(int i = 0; i < filas; ++i){
    for(int j = 0; j < columnas; ++j)
        cout << *(*matriz + i) + j) << " ";
    cout << endl;
}

En cambio en la función <pedirDatos()> para almacenar los valores en la matriz haces:
Código (cpp) [Seleccionar]

for(int i = 0; i < columnas; ++i){
    for(int j = 0; j < filas; ++j)
        cout << *(*matriz + i) + j) << " ";
    cout << endl;
}

Por lo que este segundo bloque está mal. En matrices cuadradas no vas a notar la diferencia pero en matrices que no son cuadradas vas a tener errores de acceso a memoria.


Citar
PD: No se suponía que un puntero es un referencia al espacio de memoria? y que usando "cin>>puntero" te permite guardar el valor en ese espacio, por qué ahora al usar new hace falta pasarlo como puntero referencia y no lo englobaron en 1 sola categoría???, estas cosas son las que me matan  ;D ;D ;D.
Cuando pasas un puntero estás pasando su contenido que es la dirección de memoria del dato al que está apuntando.
Código (cpp) [Seleccionar]

int numero = 2; // pongamos que se guarda en 0x1 (voy a usar numeros cortos para que sea mas sencillo)
cout << "Numero vale: " << numero << " y su direccion de memoria original es: " << &numero << endl;
// Salida: Numero vale: 2 y su direccion de memoria original es: 0x1

// Pasamos la variable por referencia para pasar la direccion de memoria en la que esta guardado <numero> (0x1)
void incrementar(int &numero){ // pasamos 0x1
    ++numero;
    cout << "Numero vale: " << numero << " y su direccion de memoria ORIGINAL es: " << &numero << endl;
}
// Salida: Numero vale: 3 y su direccion de memoria ORIGINAL es: 0x1

int *p_numero = &numero; // pongamos que <p_numero> se guarda en 0x5 y dentro de 0x5 guarda la direccion de <numero> 0x1
// Pasamos un puntero por valor, entonces estamos haciendo una copia de su contenido pero su contenido ya es la direccion de memoria de <numero> 0x1
void incrementar(int *p_numero){ // a la funcion llega 0x1 y se guarda en una copia del puntero en 0x13 por ejemplo
    ++(*p_numero);
    cout << "Numero vale: " << *p_numero << " y su direccion de memoria ORIGINAL es " << p_numero << endl;
    cout << "La direccion de memoria del puntero es: " << &p_numero << endl;
}
// Salida: Numero vale: 4 y su direccion de memoria ORIGINAL es: 0x1
//         La direccion de memoria del puntero es: 0x13 // Aqui se ve la direccion de la copia, no 0x5

// Pasamos el puntero por referencia, entonces estamos pasando la direccion de memoria EN LA QUE ESTA EL PUNTERO (0x5), no a la que apunta (<numero>)
void incrementar(int *&p_numero){ // a la funcion llega 0x5 que sigue apuntando a 0x1
    ++(*p_numero);
    cout << "Numero vale: " << *p_numero << " y su direccion de memoria ORIGINAL es " << p_numero << endl;
    cout << "La direccion de memoria del puntero ORIGINAL es: " << &p_numero << endl;
}
// Salida: Numero vale: 5 y su direccion de memoria ORIGINAL es: 0x1
//          La direccion de memoria del puntero ORIGINAL es: 0x5
}


Una vez visto esto, el tema es que los operadores <new> y <delete> no actúan en la dirección a la que apunta el puntero, sino en la dirección en la que está el puntero. Por eso si quisieramos hacer <new> o <delete> en el ejemplo anterior tendríamos que hacerlo sobre la dirección de memoria 0x5. Si el puntero lo pasamos por valor, estamos haciendo un <new> sobre 0x13, pero la dirección 0x5 no se ha enterado de lo que le han hecho a 0x13.
Por eso en ese caso hay que pasar el puntero por referencia.

Citar
PD2: Donde aprendiste a programar por cierto?
Me metieron en este mundo en el instituto, me gustó y me quedé. Si quieres un consejo: no te quedes con que tu programa funciona después de cambiar tal cosa, investiga por qué funciona después de ese cambio. No hacen falta programas enormes para aprender, hasta el programa más tonto como el código que he puesto arriba sirve para ver cómo funcionan las direcciones de memoria. Después de 2 años de Ingeniería Informática te puedo asegurar que es mucho mejor aprender por libre. Existen recursos como internet de sobra para poder hacerlo y cada uno tiene su ritmo de aprendizaje, si te metes a aprender en un sistema estandarizado no vas a profundizar en nada y no te va a dar tiempo a practicar todo lo que veas y la programación se aprende programando, no leyendo libros y códigos ya hechos una y otra vez.
#487
Has hecho tantos cambios que estás mezclando <p_m> con <p_m2> en las funciones... :xD
Para evitar estas cosas es mejor usar nombres que se vean mejor y que se distingan mas facilmente. Y si te acostumbras a usar buenos nombres siempre al final los usarás hasta cuando estés haciendo programas de prueba.

Bueno el tema de matrices... Ibas bastante bien. Pero hay un detalle que te puse unos comentarios atrás... Voy a buscarlo:
Citar
Como te he comentado antes el paso de un puntero por referencia es muy aislado. Hasta donde yo he visto y además si estás empezando sólo lo necesitarás para pasar un puntero a una función y dentro de la función reservar memoria con <new> o bien pasar un array (es decir, un puntero al que ya le has hecho <new>) y liberar esa memoria con <delete>. Quitando esos casos, no creo que necesites pasar un puntero por referencia. Por lo que puedes olvidarte un poco de él hasta que veas memoria dinámica.

Entonces tienes dos opciones:
  • Opcion 1: Reservar memoria en el <main> y pasar el doble puntero por valor (como lo estás pasando ahora).
  • Opcion 2: Pasar el doble puntero por referencia y listo. Ya que has llegado hasta donde has llegado, te recomiendo esta segunda:
    Código (cpp) [Seleccionar]

    void pedirDatos(int**&, int&, int&);

    Con ese cambio ya estaría listo. A la hora de llamar a la función solo tienes que pasarle el nombre como estás haciendo (es decir, que no debes cambiar la llamada a la función del <main>).

    Y en el bucle para pedir la matriz de <pedirDatos()> tienes un pequeño error. Si trabajas siempre con: matriz[filas][columnas], entonces para mostrar la matriz el bucle externo maneja las filas y el interno las columnas (lo tienes al revés).
    Si ves que se te complica la aritmética de punteros, puedes usar indexación que es más cómodo de ver y más fácil de trabajar y cuando ya tengas más experiencia usar la aritmética de punteros:
    Código (cpp) [Seleccionar]

    cout << *(*(matriz + i) + j) << endl; // aritmetica de punteros
    cout << matriz[i][j] << endl; // indexacion
#488
Citar
Pero si lo uso con int me muestra la posición, por?
Código (cpp) [Seleccionar]

int nombre[] = {1,2,3}, *p_nombre2;
cout<<"Dir.1: "<<nombre[1]<<" | "<<p_nombre2<<endl;
//Salida: Dir.1: 2 | 0x6ffdf0
En este caso creo que no hay dudas. <p_nombre2> sería un puntero que apunta al mismo sitio que <nombre> aunque no se vea ahí (es decir, imagino que has omitido el <p_nombre2 = nombre> o <p_nombre2 = &nombre[0]> que es equivalente). Entonces al mostrar <nombre[1]> estamos mostrando el valor almacenado en la posición 1 del array y al mostrar <p_nombre2> estamos mostrando la dirección de memoria a la que está apuntando.

Citar
Si uso con char me muestra toda la cadena:
Código (cpp) [Seleccionar]

char nombre[] = {'a','b','c'}, *p_nombre2;
cout<<"Dir.1: "<<nombre[1]<<" | "<<p_nombre2<<endl;
//Salida: Dir.1: b | abc
Aquí es donde la cosa cambia, los arrays de <char> que se conocen también como cadenas estilo C porque existían en C y fueron heredadas por C++. Cuando tienes un array de <int> por ejemplo, para mostrarlo hacemos lo siguiente:
Código (cpp) [Seleccionar]

for(int i = 0; i < size; ++i)
    cout << arrayEnteros[i] << " ";

Pero imagina que tienes una cadena de <char> con un nombre y que quieres mostrar el nombre. Sería muy incómodo tener que hacer:
Código (cpp) [Seleccionar]

char nombre[] = "Pepe";
cout << "Tu nombre es: ";
for(int i = 0; i < 4; ++i)
    cout << nombre[i];

Entonces como las cadenas de <char> no se suelen usar sólo para almacenar valores aislados sino también palabras o frases, para hacerlo más cómodo el lenguaje permite hacer:
Código (cpp) [Seleccionar]

char nombre[] = "Pepe";
cout << "Tu nombre es: " << nombre << endl;

Y la máquina ya entiende que tiene que mostrar desde donde apunta <nombre> (&nombre[0]) hasta el caracter de fin de cadena '\0'. Cosa que con un array de enteros no podemos hacer. Entonces digamos que puedes entenderlo como que los arrays de <char> tienen sus excepciones respecto al resto.

Además en C++, al introducir la POO y con ello las clases y objetos, aparece la clase <string> que funciona como una cadena <char> pero sin necesidad de tener que reservar nosotros la memoria:
Código (cpp) [Seleccionar]

string nombre = "Pepe";
cout << "La primera letra de " << nombre << " es: " << nombre[0] << endl;

#489
Citar
Lo que no se es si luego al usar visual studio la forma de programar va a ser distinta a como funciona Dev C++
Visual Studio al igual que Dev C++ solo es un IDE como cualquier otro: Eclipse, NetBeans, CodeBlocks, etc. Pero al final C++ es C++, es decir, el lenguaje es el lenguaje y eso no cambia. Yo por ejemplo no uso ningún IDE, todos los proyectos los hago en un editor de texto (puedes usar desde un simple Bloc de Notas hasta un editor más especializado en programación como SublimeText o Atom que permiten tener muchas extensiones para facilitar el trabajo como si de un IDE se tratara) y los compilo desde la Terminal (en Linux) lo que equivaldría a hacerlo desde la Consola de Windows. Quiero decir que da igual dónde escribas tu código.

Citar
PD: Hice la carrera de electrónica y en el primer año di informática (muuuy básico) y llegamos hasta funciones, usábamos la librería <stdlib> para todo y usábamos printf o fprintf si era para una función y scanf, he visto que en algunos post hay gente que no usa <iostream> en C++ si se supone que es la estándar, por?
Normalmente cuando se trabaja a bajo nivel se usa mucho el lenguaje C que trabaja a más bajo nivel que C++. C se caracteriza por la librería <stdio.h> que contiene las funciones <printf()>, <scanf()>, <fprintf()>, etc mientras que C++ se caracteriza por <iostream> que engloba <cin>, <cout> entre otros.
C++ es como una expansión de C, por lo que todo lo que se podía hacer en C, se puede hacer en C++, pero en muchas ocasiones en C++ se crearon formas más sencillas de hacer las cosas al no ser de tan bajo nivel. Esto genera una gran confusión entre si estás aprendiendo C o C++. Un programa en C (fichero.c) lo puedes compilar como código fuente C (.c) o como código fuente C++ (.cpp), sin embargo, al revés, no. De ahí que muchas personas digan que programan en C++ y usen <stdio.h>, <stdlib.h>, etc. C++ también tiene sus propias versiones para estas librerías que tienen el mismo nombre quitando el <.h> del final y añadiendo una <c> al principio: <cstdio>, <cstdlib>, etc.

Citar
PD2: He visto que en C++ también se usa la programación orientada a objetos que se usa en unreal y que se parece a java. el C++ no es un lenguaje procedural? como puede ser orientado a objetos también?
El lenguaje C siempre ha sido uno de los más claros ejemplos de lenguaje procedural ya que permite dividir un problema en subproblemas que se resolverán mediante procedimientos (funciones). El lenguaje C++ se desarrolló a partir de C para hacer como una expansión de C que trabajase con el paradigma de programación orientada a objetos pero por ello no deja de ser un lenguaje procedural. Un lenguaje puede tener más de un paradigma de programación como es el caso.

PD: Te recomiendo una página que es cplusplus. Te dejo el enlace AQUÍ Ahí tienes un apartado a la izquierda de la página llamado <Reference> que contiene muchas librerías con su nombre en C y en C++. Además de otros archivos de cabecera como son los contenedores de la STL (muy útiles y cómodos de usar) y otros; y dentro de cada librería se encuentran sus funciones y constantes con explicaciones de uso y ejemplos. Está en inglés pero es muy fácil de entender y seguro que te saca de muchos apuros. Suerte. :-X
#490
Citar
No sabia que que cuando pasabas un vector a una función lo podías pasar como puntero con "*", hasta lo que he visto cuando se pasaba algo por referencia a una función se hacia con &
Cuando se pasa algo por referencia se pasa con &, eso es cierto. Pero un puntero no siempre se pasa por referencia.
Código (cpp) [Seleccionar]

void funcion1(int a); // variable por valor
void funcion2(int &a); // variable por referencia
void funcion3(int *pa); // puntero por valor
void funcion4(int *&pa); // puntero por referencia

Diría que esos son los tipos de parámetros que podemos usar más típicos. Luego claro está que podemos hacer conversiones de uno a otro. Por ejemplo es lo mismo pasar una variable por referencia que pasar un puntero a esa variable por valor. Todo depende de lo que nos guste más o lo que más nos convenga en cada momento: si estás trabajando todo el tiempo con variables y necesitas mandar una a una función y modificarla pero no puedes retornarla porque ya tienes un <return> (o cualquier otro motivo) pues la pasas por referencia (así ahorras crear un puntero y hacer que apunte a esa variable). En cambio si por un casual estás trabajando con punteros y quieres pasar el valor del puntero (es decir la variable a la que apunta) y modificarla pues para qué vas a crear otra variable y asignarle el valor del puntero para pasarla por referencia? En este caso te ahorras trabajo pasando el puntero directamente.

Como te he comentado antes el paso de un puntero por referencia es muy aislado. Hasta donde yo he visto y además si estás empezando sólo lo necesitarás para pasar un puntero a una función y dentro de la función reservar memoria con <new> o bien pasar un array (es decir, un puntero al que ya le has hecho <new>) y liberar esa memoria con <delete>. Quitando esos casos, no creo que necesites pasar un puntero por referencia. Por lo que puedes olvidarte un poco de él hasta que veas memoria dinámica.

Citar
y si querías pasar un puntero tenias que crearlo del mismo tipo que el vector, luego vincularlo al primer espacio de la memoria y luego ya se pasaba con "*". 
Esto no es necesario pero en algunas ocasiones se hace. Cuando tú creas un vector, el propio vector funciona como un puntero que apunta a la primera posición del vector.
Código (cpp) [Seleccionar]

int numeros[5] = {1,2,3,4,5};
// aqui podemos usar <numeros> como si fuera un puntero que apunta a &numeros[0]
// por lo tanto podemos hacer cosas como:
cout << *numeros << endl; // imprime: 1
for(size_t i = 0; i < 5; ++i)
    cout << *(numeros+i) << " " << endl; // imprime: 1 2 3 4 5


¿Y para qué sirve crear otro puntero? Bueno, imagina que tienes una función para recorrer todo un vector. Esta la podemos hacer de varias formas, por ejemplo:
Código (cpp) [Seleccionar]

void recorrerVector1(int *numeros, int longitud){
    for(int i = 0; i < longitud; ++i)
        cout << *(numeros+i) << endl;
}

void recorrerVector2(int *numeros, int longitud){
    for(int i = 0; i < longitud; ++i){
        cout << *numeros << endl;
        ++numeros;
    }
}

Con ambas vas a obtener el mismo resultado pero tienen una diferencia. La primera le va diciendo a <numeros> "imprime el valor que está i posiciones por delante de ti" mientras que la segunda le dice a <numeros> "imprime el valor que hay en tu posición 0 y muévete". La primera no tiene ningún inconveniente, sin embargo, la segunda sí. Si a la segunda función le pasamos el nombre del vector para que haga de puntero y avanzamos el puntero, los valores que vaya dejando atrás los perderemos para siempre. Un vector se puede encontrar en memoria siempre que tengamos un puntero apuntando a él (al comienzo de él), si perdemos el puntero, perdemos el vector. Para ello se crea otro puntero nuevo que sea el que se vaya moviendo mientras el original siempre apunta al comienzo.
Código (cpp) [Seleccionar]

int numeros[5] = {1,2,3,4,5};
int *puntero = numeros;
recorrerVector2(puntero, 5); // aqui no hay problemas porque aunque <puntero> se mueva, <numeros> siempre apunta al comienzo del vector
//en cambio si hacemos esto...
recorrerVector2(numeros, 5); // ... adios al vector. Ya no hay forma de volver a usarlo ni de liberarlo si fuera memoria dinamica


Citar
PD: Entonces lo que paso con: (char* nombre)
es la posición de memoria de la posición v[0] del vector?
o
es un puntero del vector que se llama nombre y esta en la posición v[0] del vector con su mismo nombre?.
Lo que pasas es lo primero que dices, la posición de memoria de la posición 0 del vector.
Citar
En el caso de (char& letra) que es un paso por referencia pasas la dirección de memoria también no? ya que puedes modificar la variable en otra funciones.
Exacto. Cuando pasas una variable por referencia siempre pasas la dirección de memoria en la que está almacenada esa variable.

Citar
Y  por qué no se usa (char& letra) como por ejemplo sí pasar un solo char como 'a'.
Respecto a esto voy a extenderme un poco más para intentar explicarlo del todo...
Este ha sido tu planteamiento de antes:
Código (cpp) [Seleccionar]

int pedirNombre(char& us){
int longitud;

cout<<"Digite su nombre: ";
cin.getline(us,30,'\n');
longitud = strlen(us);

return longitud;
}

¿Se puede hacer? ¿No se puede hacer? La respuesta es que sí, se puede hacer pero como todo, hay que hacerlo bien. Pasamos primero a un ejemplo más simple:
Código (cpp) [Seleccionar]

void funcion1(int a){
    cout << "La variable vale: " << a << " y su direccion de memoria es: " << &a << endl;
    // Se muestra la direccion de memoria de la copia
    // IMPORTANTE: el valor se obtiene con el nombre tal cual y la direccion con &
}

void funcion2(int &a){
    cout << "La variable vale: " << a << " y su direccion de memoria es: " << &a << endl;
    // Se muestra la direccion de memoria de la original
    // IMPORTANTE: el valor se obtiene con el nombre tal cual y la direccion con &
}

void funcion3(int *pa){
    cout << "La variable vale: " << *pa << " y su direccion de memoria es: " << pa << endl;
    // Se muestra la direccion de memoria de la original pero no de <pa> sino a la que apunta <pa>
    // IMPORTANTE: el valor se obtiene con * y la direccion con el nombre tal cual
}

// USO DE LAS FUNCIONES
int numero = 2;
funcion1(numero); // se pasa un <int>
funcion2(numero); // se pasa un <int> aunque el programa ya sabe que es por referencia
funcion3(numero); // ERROR: El programa espera una direccion de memoria (puntero) y le estamos pasando un <int>
funcion3(&numero); // Correcto: Le pasamos una direccion de memoria sin crear un puntero auxiliar (que tambien se podria hacer)

Lo que quiero que veas con esto es que tanto en el paso por valor como por referencia lo que pasamos es el nombre de la variable y eso que en el primer caso estamos copiando el valor y en el segundo caso estamos usando su dirección de memoria pero nosotros siempre pasamos siempre el nombre de la variable tal cual y dentro de las funciones usamos también el nombre de la variable tal cual para mostrarla. En cambio, en la tercera función no pasamos el nombre de la variable tal cual sino que pasamos su dirección de memoria y dentro de la función no usamos el nombre tal cual para mostrarla <pa> sino que usamos el operador de desreferencia (el asterisco).

Entonces volviendo a tu función. La forma más simple es esta:
Código (cpp) [Seleccionar]

int pedirNombre(char *nombre){
    cout << "Introduce nombre: ";
    cin.getline(nombre, 30, '\n');
    return strlen(nombre);
}

// USO DE LA FUNCION
int longitud = pedirNombre(nombre); // Como hemos visto antes tiene que recibir directamente la direccion de memoria, bien asi o bien asi:
int longitud = pedirNombre(&nombre[0]); // que es exactamente lo mismo


¿Se puede hacer usando el paso por referencia? Claro que se puede pero necesitamos hacer algunos cambios:
Código (cpp) [Seleccionar]

int pedirNombre(char &nombre){
    cout << "Introduce tu nombre: ";
    cin.getline(&nombre, 30, '\n');
    return strlen(&nombre);
}
// o bien:
int pedirNombre(char &nombre){
    char *puntero = &nombre;
    cout << "Introduce nombre: ";
    cin.getline(puntero, 30, '\n');
    return strlen(puntero);
}

// USO DE LA FUNCION
int longitud = pedirNombre(nombre[0]); // como hemos visto antes se pasa la variable y ya el programa sabe que tiene que usar su direccion de memoria


Lo importante es saber cuando se usa la dirección de memoria y cuando no pero también hay que saber cuando es necesario que lo indiquemos nosotros y cuando no. ¿Cuál usar? Pues cada uno la que más le guste pero lo ideal sería usar la más legible en cada momento y para vectores siempre va a ser más legible el paso de punteros que el de referencias.