--- language: c++ filename: learncpp-es.cpp contributors: - ["Steven Basart", "http://github.com/xksteven"] - ["Matt Kline", "https://github.com/mrkline"] - ["Geoff Liu", "http://geoffliu.me"] - ["Connor Waters", "http://github.com/connorwaters"] translators: - ["Gerson Lázaro", "https://gersonlazaro.com"] - ["Diego Ramirez", "https://DiddiLeija.github.io"] lang: es-es --- C++ es un lenguaje de programación de sistemas que, [de acuerdo a su inventor Bjarne Stroustrup](http://channel9.msdn.com/Events/Lang-NEXT/Lang-NEXT-2014/Keynote), fue diseñado para - ser un "mejor C" - soportar abstracción de datos - soportar programación orientada a objetos - soportar programación genérica Aunque su sintaxis puede ser más difícil o compleja que los nuevos lenguajes, es ampliamente utilizado, ya que compila instrucciones nativas que pueden ser directamente ejecutadas por el procesador y ofrece un estricto control sobre el hardware (como C), mientras ofrece características de alto nivel como genericidad, excepciones, y clases. Esta combinación de velocidad y funcionalidad hace de C ++ uno de los lenguajes de programación más utilizados. ```c++ //////////////////// // Comparación con C //////////////////// // C ++ es _casi_ un superconjunto de C y comparte su sintaxis básica para las // declaraciones de variables, tipos primitivos y funciones. // Al igual que en C, el punto de entrada de tu programa es una función llamada // main con un retorno de tipo entero. // Este valor sirve como código de salida del programa. // Mira http://en.wikipedia.org/wiki/Exit_status para mayor información. int main(int argc, char** argv) { // Los argumentos de la línea de comandos se pasan por argc y argv de la // misma manera que en C. // argc indica el número de argumentos, // y argv es un arreglo de strings de estilo C (char*) // representando los argumentos. // El primer argumento es el nombre con el que el programa es llamado. // argc y argv pueden omitirse si no te preocupan los argumentos, // dejando la definición de la función como int main () // Un estado de salida 0 indica éxito. return 0; } // Sin embargo, C ++ varía en algunas de las siguientes maneras: // En C++, los caracteres literales son caracteres sizeof('c') == sizeof(char) == 1 // En C, los caracteres literales son enteros sizeof('c') == sizeof(int) // C++ tiene prototipado estricto void func(); // función que no acepta argumentos // En C void func(); // función que puede aceptar cualquier número de argumentos // Use nullptr en lugar de NULL en C++ int* ip = nullptr; // Las cabeceras (headers) estándar de C están disponibles en C ++, // pero tienen el prefijo "c" y no tienen sufijo .h. #include <cstdio> int main() { printf("Hola mundo!\n"); return 0; } ////////////////////////// // Sobrecarga de funciones ////////////////////////// // C++ soporta sobrecarga de funciones // siempre que cada función tenga diferentes parámetros. void print(char const* myString) { printf("String %s\n", myString); } void print(int myInt) { printf("Mi entero es %d", myInt); } int main() { print("Hello"); // Corresponde a void print(const char*) print(15); // Corresponde a void print(int) } //////////////////////////////////// // Argumentos de función por defecto //////////////////////////////////// // Puedes proporcionar argumentos por defecto para una función si no son // proporcionados por quien la llama. void doSomethingWithInts(int a = 1, int b = 4) { // Hacer algo con los enteros aquí } int main() { doSomethingWithInts(); // a = 1, b = 4 doSomethingWithInts(20); // a = 20, b = 4 doSomethingWithInts(20, 5); // a = 20, b = 5 } // Los argumentos predeterminados deben estar al final de la lista de argumentos. void invalidDeclaration(int a = 1, int b) // Error! { } ///////////////////// // Espacios de nombre ///////////////////// // Los espacios de nombres proporcionan ámbitos separados para variable, función y // otras declaraciones. // Los espacios de nombres se pueden anidar. namespace First { namespace Nested { void foo() { printf("Esto es First::Nested::foo\n"); } } // fin del nombre de espacio Nested } // fin del nombre de espacio First namespace Second { void foo() { printf("Esto es Second::foo\n") } } void foo() { printf("Este es global: foo\n"); } int main() { // Incluye todos los símbolos del espacio de nombre Second en el ámbito // actual. Tenga en cuenta que simplemente foo() no funciona, ya que ahora // es ambigua si estamos llamando a foo en espacio de nombres Second o en // el nivel superior. using namespace Second; Second::foo(); // imprime "Esto es Second::foo" First::Nested::foo(); // imprime "Esto es First::Nested::foo" ::foo(); // imprime "Este es global: foo" } ///////////////// // Entrada/Salida ///////////////// // La entrada y salida de C++ utiliza flujos (streams) // cin, cout, y cerr representan a stdin, stdout, y stderr. // << es el operador de inserción >> es el operador de extracción. #include <iostream> // Incluir para el flujo de entrada/salida using namespace std; // Los streams estan en el espacio de nombre std (libreria estandar) int main() { int myInt; // Imprime a la stdout (o terminal/pantalla) cout << "Ingresa tu número favorito:\n"; // Toma una entrada cin >> myInt; // cout puede también ser formateado cout << "Tu número favorito es " << myInt << "\n"; // imprime "Tu número favorito es <myInt>" cerr << "Usado para mensajes de error"; } //////////////////// // Cadenas (Strings) //////////////////// // Las cadenas en C++ son objetos y tienen muchas funciones #include <string> using namespace std; // Las cadenas también estan en el nombre de espacio std string myString = "Hola"; string myOtherString = " Mundo"; // + es usado para concatenar. cout << myString + myOtherString; // "Hola Mundo" cout << myString + " Tu"; // "Hola Tu" // Las cadenas en C++ son mutables y tienen valor semántico. myString.append(" Perro"); cout << myString; // "Hola Perro" ////////////// // Referencias ////////////// // Además de punteros como los de C, // C++ tiene _references_. // Estos tipos de puntero no pueden ser reasignados una vez establecidos // Y no pueden ser nulos. // También tienen la misma sintaxis que la propia variable: // No es necesaria * para eliminar la referencia y // & (dirección) no se utiliza para la asignación. using namespace std; string foo = "Yo soy foo"; string bar = "Yo soy bar"; string& fooRef = foo; // Crea una referencia a foo. fooRef += ". Hola!"; // Modifica foo través de la referencia cout << fooRef; // Imprime "Yo soy foo. Hola!" // No trate de reasignar "fooRef". Esto es lo mismo que "foo = bar", y // foo == "Yo soy bar" // después de esta linea. fooRef = bar; const string& barRef = bar; // Crea una referencia constante a bar. // Como en C, los valores constantes (y punteros y referencias) no pueden ser // modificados. barRef += ". Hola!"; // Error, una referencia constante no puede ser modificada. // Sidetrack: Antes de hablar más sobre referencias, hay que introducir un // concepto llamado objeto temporal. Supongamos que tenemos el siguiente código: string tempObjectFun() { ... } string retVal = tempObjectFun(); // Lo que pasa en la segunda línea es en realidad: // - Un objeto de cadena es retornado desde tempObjectFun // - Una nueva cadena se construye con el objeto devuelto como argumento al // constructor // - El objeto devuelto es destruido // El objeto devuelto se llama objeto temporal. Los objetos temporales son // creados cada vez que una función devuelve un objeto, y es destruido en el // fin de la evaluación de la expresión que encierra (Bueno, esto es lo que la // norma dice, pero los compiladores están autorizados a cambiar este // comportamiento. Busca "return value optimization" para ver mas detalles). // Así que en este código: foo(bar(tempObjectFun())) // Suponiendo que foo y bar existen, el objeto retornado de tempObjectFun es // pasado al bar, y se destruye antes de llamar foo. // Ahora, de vuelta a las referencias. La excepción a la regla "en el extremo // de la expresión encerrada" es si un objeto temporal se une a una // referencia constante, en cuyo caso su vida se extiende al ámbito actual: void constReferenceTempObjectFun() { // ConstRef obtiene el objeto temporal, y es válido hasta el final de esta // función. const string& constRef = tempObjectFun(); ... } // Otro tipo de referencia introducida en C ++ 11 es específicamente para // objetos temporales. No se puede tener una variable de este tipo, pero tiene // prioridad en resolución de sobrecarga: void someFun(string& s) { ... } // Referencia regular void someFun(string&& s) { ... } // Referencia a objeto temporal string foo; someFun(foo); // Llama la función con referencia regular someFun(tempObjectFun()); // Llama la función con referencia temporal // Por ejemplo, puedes ver estas dos versiones de constructores para // std::basic_string: basic_string(const basic_string& other); basic_string(basic_string&& other); // La idea es que si estamos construyendo una nueva cadena de un objeto temporal // (que va a ser destruido pronto de todos modos), podemos tener un constructor // mas eficiente que "rescata" partes de esa cadena temporal. Usted verá este // concepto denominado "movimiento semántico". //////////////////////////////////////////// // Clases y programación orientada a objetos //////////////////////////////////////////// // Primer ejemplo de clases #include <iostream> // Declara una clase. // Las clases son usualmente declaradas en archivos de cabeceras (.h o .hpp) class Dog { // Variables y funciones de la clase son privados por defecto. std::string name; int weight; // Todos los miembros siguientes de este son públicos // hasta que se encuentre "private" o "protected". public: // Constructor por defecto Dog(); // Declaraciones de funciones de la clase (implementaciones a seguir) // Nota que usamos std::string aquí en lugar de colocar // using namespace std; // arriba. // Nunca ponga una declaración "using namespace" en un encabezado. void setName(const std::string& dogsName); void setWeight(int dogsWeight); // Funciones que no modifican el estado del objeto // deben marcarse como const. // Esto le permite llamarlas si se envía una referencia constante al objeto. // También tenga en cuenta que las funciones deben ser declaradas // explícitamente como _virtual_ para que sea reemplazada en las clases // derivadas. // Las funciones no son virtuales por defecto por razones de rendimiento. virtual void print() const; // Las funciones también se pueden definir en el interior // del cuerpo de la clase. // Funciones definidas como tales están entre líneas automáticamente. void bark() const { std::cout << name << " barks!\n"; } // Junto a los constructores, C++ proporciona destructores. // Estos son llamados cuando un objeto se elimina o está fuera del ámbito. // Esto permite paradigmas potentes como RAII // (mira abajo) // El destructor debe ser virtual si una clase es dervada desde el; // Si no es virtual, entonces la clase destructora derivada // no será llamada si el objeto se destruye a través de una referencia de // la clase base o puntero. virtual ~Dog(); }; // Un punto y coma debe seguir la definición de clase. // Las funciones de una clase son normalmente implementados en archivos .cpp. Dog::Dog() { std::cout << "Un perro ha sido construido\n"; } // Objetos (tales como cadenas) deben ser pasados por referencia // si los estás modificando, o referencia constante en caso contrario. void Dog::setName(const std::string& dogsName) { name = dogsName; } void Dog::setWeight(int dogsWeight) { weight = dogsWeight; } // Nota que "virtual" sólo se necesita en la declaración, no en la definición. void Dog::print() const { std::cout << "El perro es " << name << " y pesa " << weight << "kg\n"; } Dog::~Dog() { std::cout << "Adiós " << name << "\n"; } int main() { Dog myDog; // imprime "Un perro ha sido construido" myDog.setName("Barkley"); myDog.setWeight(10); myDog.print(); // imprime "El perro es Barkley y pesa 10 kg" return 0; } // imprime "Adiós Barkley" // Herencia: // Esta clase hereda todo lo público y protegido de la clase Dog class OwnedDog : public Dog { void setOwner(const std::string& dogsOwner); // Reemplaza el comportamiento de la función de impresión // de todos los OwnedDogs. Mira // http://en.wikipedia.org/wiki/Polymorphism_(computer_science)#Subtyping // para una introducción más general si no está familiarizado con el // polimorfismo de subtipo. // La palabra clave override es opcional, pero asegúrate de que estás // reemplazando el método de una clase base. void print() const override; private: std::string owner; }; // Mientras tanto, en el archivo .cpp correspondiente: void OwnedDog::setOwner(const std::string& dogsOwner) { owner = dogsOwner; } void OwnedDog::print() const { Dog::print(); // Llama a la función de impresión en la clase base Dog std::cout << "El perro es de " << owner << "\n"; // Imprime "El perro es <name> y pesa <weight>" // "El perro es de <owner>" } //////////////////////////////////////////// // Inicialización y sobrecarga de operadores //////////////////////////////////////////// // En C ++ se puede sobrecargar el comportamiento // de los operadores como +, -, *, /, etc. // Esto se hace mediante la definición de una función que es llamada // cada vez que se utiliza el operador. #include <iostream> using namespace std; class Point { public: // Las variables de la clase pueden dar valores por defecto de esta manera. double x = 0; double y = 0; // Define un constructor por defecto que no hace nada // pero inicializa el punto al valor por defecto (0, 0) Point() { }; // La siguiente sintaxis se conoce como lista de inicialización // y es la forma correcta de inicializar los valores de los miembros de la clase Point (double a, double b) : x(a), y(b) { /* No hace nada excepto inicializar los valores */ } // Sobrecarga el operador + Point operator+(const Point& rhs) const; // Sobrecarga el operador += Point& operator+=(const Point& rhs); // También tendría sentido añadir los operadores - y -=, // pero vamos a omitirlos por razones de brevedad. }; Point Point::operator+(const Point& rhs) const { // Crea un nuevo punto que es la suma de este y rhs. return Point(x + rhs.x, y + rhs.y); } Point& Point::operator+=(const Point& rhs) { x += rhs.x; y += rhs.y; return *this; } int main () { Point up (0,1); Point right (1,0); // Llama al operador + de Point // Point llama la función + con right como parámetro Point result = up + right; // Imprime "Result is upright (1,1)" cout << "Result is upright (" << result.x << ',' << result.y << ")\n"; return 0; } ///////////////////////// // Plantillas (Templates) ///////////////////////// // Las plantillas en C++ se utilizan sobre todo en la programación genérica, // a pesar de que son mucho más poderoso que los constructores genéricos // en otros lenguajes. Ellos también soportan especialización explícita y // parcial y clases de tipo estilo funcional; de hecho, son un lenguaje // funcional Turing-completo incrustado en C ++! // Empezamos con el tipo de programación genérica que podría estar // familiarizado. // Para definir una clase o función que toma un parámetro de tipo: template<class T> class Box { public: // En este caso, T puede ser usado como cualquier otro tipo. void insert(const T&) { ... } }; // Durante la compilación, el compilador realmente genera copias de cada // plantilla con parámetros sustituidos, por lo que la definición completa // de la clase debe estar presente en cada invocación. // Es por esto que usted verá clases de plantilla definidas // enteramente en archivos de cabecera. //Para crear una instancia de una clase de plantilla en la pila: Box<int> intBox; y puedes utilizarlo como era de esperar: intBox.insert(123); // Puedes, por supuesto, anidar plantillas: Box<Box<int> > boxOfBox; boxOfBox.insert(intBox); // Hasta C++11, había que colocar un espacio entre los dos '>'s, // de lo contrario '>>' serían analizados como el operador de desplazamiento // a la derecha. // A veces verás // template<typename T> // en su lugar. La palabra clave "class" y la palabra clave "typename" son // mayormente intercambiables en este caso. Para la explicación completa, mira // http://en.wikipedia.org/wiki/Typename // (sí, esa palabra clave tiene su propia página de Wikipedia). // Del mismo modo, una plantilla de función: template<class T> void barkThreeTimes(const T& input) { input.bark(); input.bark(); input.bark(); } // Observe que no se especifica nada acerca de los tipos de parámetros aquí. // El compilador generará y comprobará cada invocación de la plantilla, // por lo que la función anterior funciona con cualquier tipo "T" // que tenga un método 'bark' constante! Dog fluffy; fluffy.setName("Fluffy") barkThreeTimes(fluffy); // Imprime "Fluffy barks" 3 veces. // Los parámetros de la plantilla no tienen que ser las clases: template<int Y> void printMessage() { cout << "Aprende C++ en " << Y << " minutos!" << endl; } // Y usted puede especializar explícitamente plantillas // para código más eficiente. // Por supuesto, la mayor parte del mundo real que utiliza una especialización // no son tan triviales como esta. // Tenga en cuenta que usted todavía tiene que declarar la función (o clase) // como plantilla incluso si ha especificado de forma explícita todos // los parámetros. template<> void printMessage<10>() { cout << "Aprende C++ rapido en solo 10 minutos!" << endl; } printMessage<20>(); // Prints "Aprende C++ en 20 minutos!" printMessage<10>(); // Prints "Aprende C++ rapido en solo 10 minutos!" ///////////////////// // Manejador de excepciones ///////////////////// // La biblioteca estándar proporciona algunos tipos de excepción // (mira http://en.cppreference.com/w/cpp/error/exception) // pero cualquier tipo puede ser lanzado como una excepción #include <exception> #include <stdexcept> //Todas las excepciones lanzadas dentro del bloque _try_ pueden ser // capturados por los siguientes manejadores _catch_. try { // No asigne excepciones en el heap usando _new_. throw std::runtime_error("Ocurrió un problema"); } // Captura excepciones por referencia const si son objetos catch (const std::exception& ex) { std::cout << ex.what(); } ******************************************************************************** // Captura cualquier excepción no capturada por bloques _catch_ anteriores catch (...) { std::cout << "Excepción desconocida capturada"; throw; // Re-lanza la excepción } /////// // RAII /////// // RAII significa "Resource Acquisition Is Initialization" // (Adquisición de recursos es inicialización). // A menudo se considera el paradigma más poderoso en C++ // Y el concepto es simple: un constructor de un objeto // adquiere recursos de ese objeto y el destructor les libera. // Para entender cómo esto es útil, // considere una función que utiliza un identificador de archivo C: void doSomethingWithAFile(const char* filename) { // Para empezar, asuma que nada puede fallar. FILE* fh = fopen(filename, "r"); // Abre el archivo en modo lectura doSomethingWithTheFile(fh); doSomethingElseWithIt(fh); fclose(fh); // Cierra el manejador de archivos } // Por desgracia, las cosas se complican rápidamente por el control de errores. // Supongamos que fopen puede fallar, y que doSomethingWithTheFile y // DoSomethingElseWithIt retornan códigos de error si fallan. // (Excepciones son la mejor forma de manejar los fallos, // pero algunos programadores, especialmente los que tienen un fondo C, // estan en desacuerdo sobre la utilidad de las excepciones). // Ahora tenemos que comprobar cada llamado por fallos y cerrar el manejador // del archivo si se ha producido un problema. bool doSomethingWithAFile(const char* filename) { FILE* fh = fopen(filename, "r"); // Abre el archivo en modo lectura if (fh == nullptr) // El puntero retornado es nulo o falla. return false; // Reporta el fallo a quien hizo el llamado. // Asume que cada función retorna falso si falla if (!doSomethingWithTheFile(fh)) { fclose(fh); // Cierre el manejador de archivo para que no se filtre. return false; // Propaga el error. } if (!doSomethingElseWithIt(fh)) { fclose(fh); // Cierre el manejador de archivo para que no se filtre. return false; // Propaga el error. } fclose(fh); // Cierre el archivo. return true; // Indica que todo funcionó correctamente. } // Programadores C suelen limpiar esto un poco usando goto: bool doSomethingWithAFile(const char* filename) { FILE* fh = fopen(filename, "r"); if (fh == nullptr) return false; if (!doSomethingWithTheFile(fh)) goto failure; if (!doSomethingElseWithIt(fh)) goto failure; fclose(fh); // Cierre el archivo. return true; // Indica que todo funcionó correctamente. failure: fclose(fh); return false; // Propaga el error } // Si las funciones indican errores mediante excepciones, // las cosas son un poco más claras, pero pueden optimizarse más. void doSomethingWithAFile(const char* filename) { FILE* fh = fopen(filename, "r"); // Abrir el archivo en modo lectura if (fh == nullptr) throw std::runtime_error("No puede abrirse el archivo."); try { doSomethingWithTheFile(fh); doSomethingElseWithIt(fh); } catch (...) { fclose(fh); // Asegúrese de cerrar el archivo si se produce un error. throw; // Luego vuelve a lanzar la excepción. } fclose(fh); // Cierra el archivo } // Compare esto con el uso de la clase de flujo de archivos de C++ (fstream) // fstream utiliza su destructor para cerrar el archivo. // Los destructores son llamados automáticamente // cuando un objeto queda fuera del ámbito. void doSomethingWithAFile(const std::string& filename) { // ifstream es la abreviatura de el input file stream std::ifstream fh(filename); // Abre el archivo // Hacer algo con el archivo doSomethingWithTheFile(fh); doSomethingElseWithIt(fh); } // El archivo se cierra automáticamente aquí por el destructor // Esto tiene ventajas _enormes_: // 1. No importa lo que pase, // el recurso (en este caso el manejador de archivo) será limpiado. // Una vez que escribes el destructor correctamente, // es _imposible_ olvidar cerrar el identificador y permitir // fugas del recurso. // 2. Tenga en cuenta que el código es mucho más limpio. // El destructor se encarga de cerrar el archivo detrás de cámaras // Sin que tenga que preocuparse por ello. // 3. El código es seguro. // Una excepción puede ser lanzado en cualquier lugar de la función // y la limpieza ocurrirá. // Todo el código idiomático C++ utiliza RAII ampliamente para todos los // recursos. // Otros ejemplos incluyen // - Memoria usando unique_ptr y shared_ptr // - Contenedores (Containers) - la biblioteca estándar linked list, // vector (es decir, array con auto-cambio de tamaño), hash maps, etc. // Destruimos todos sus contenidos de forma automática // cuando quedan fuera del ámbito. // - Mutex utilizando lock_guard y unique_lock ///////////////////// // Cosas divertidas ///////////////////// // Aspectos de C ++ que pueden sorprender a los recién llegados // (e incluso algunos veteranos). // Esta sección es, por desgracia, salvajemente incompleta; // C++ es uno de los lenguajes con los que mas facil te disparas en el pie. // Tu puedes sobreescribir métodos privados! class Foo { virtual void bar(); }; class FooSub : public Foo { virtual void bar(); // Sobreescribe Foo::bar! }; // 0 == false == NULL (La mayoria de las veces)! bool* pt = new bool; *pt = 0; // Establece los puntos de valor de 'pt' en falso. pt = 0; // Establece 'pt' al apuntador nulo. Ambas lineas compilan sin error. // nullptr se supone que arregla un poco de ese tema: int* pt2 = new int; *pt2 = nullptr; // No compila pt2 = nullptr; // Establece pt2 como null. // Hay una excepción para los valores bool. // Esto es para permitir poner a prueba punteros nulos con if (!ptr), // pero como consecuencia se puede asignar nullptr a un bool directamente! *pt = nullptr; // Esto todavía compila, a pesar de que '*pt' es un bool! // '=' != '=' != '='! // Llama Foo::Foo(const Foo&) o alguna variante (mira movimientos semanticos) // copia del constructor. Foo f2; Foo f1 = f2; // Llama Foo::Foo(const Foo&) o variante, pero solo copia el 'Foo' parte de // 'fooSub'. Cualquier miembro extra de 'fooSub' se descarta. Este // comportamiento horrible se llama "Corte de objetos." FooSub fooSub; Foo f1 = fooSub; // Llama a Foo::operator=(Foo&) o variantes. Foo f1; f1 = f2; // Cómo borrar realmente un contenedor: class Foo { ... }; vector<Foo> v; for (int i = 0; i < 10; ++i) v.push_back(Foo()); // La siguiente línea establece el tamaño de v en 0, // pero los destructores no son llamados y los recursos no se liberan! v.empty(); v.push_back(Foo()); // Nuevo valor se copia en el primer Foo que insertamos // En verdad destruye todos los valores en v. // Consulta la sección acerca de los objetos temporales para la // explicación de por qué esto funciona. v.swap(vector<Foo>()); ``` Otras lecturas: * Una referencia del lenguaje hasta a la fecha se puede encontrar en [CPP Reference](http://cppreference.com/w/cpp). * Recursos adicionales se pueden encontrar en [[CPlusPlus]](http://cplusplus.com). * Un tutorial que cubre los conceptos básicos del lenguaje y la configuración del entorno de codificación está disponible en [TheChernoProject - C ++](https://www.youtube.com/playlist?list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FF).