Comenzando con C++

Hola Mundo

Este programa imprime Hello World! en el flujo de salida estándar:

#include <iostream>

int main()
{
    std::cout << "Hello World!" << std::endl;
}

Véalo [en vivo en Coliru] (http://coliru.stacked-crooked.com/a/ba766ad8ca2fae56).

Análisis

Examinemos cada parte de este código en detalle:

  • #include <iostream> es una directiva de preprocesador que incluye el contenido del archivo de encabezado estándar de C++ iostream.

    iostream is a standard library header file that contains definitions of the standard input and output streams. These definitions are included in the std namespace, explained below.

    The standard input/output (I/O) streams provide ways for programs to get input from and output to an external system – usually the terminal.

  • int main() { ... } define una nueva función llamada main. Por convención, la función principal se llama al ejecutar el programa. Solo debe haber una función principal en un programa C++, y siempre debe devolver un número del tipo int.

    Here, the int is what is called the function’s return type. El valor devuelto por la función principal es un código de salida.

    By convention, a program exit code of 0 or EXIT_SUCCESS is interpreted as success by a system that executes the program. Any other return code is associated with an error.

    If no return statement is present, the main function (and thus, the program itself) returns 0 by default. In this example, we don’t need to explicitly write return 0;.

    All other functions, except those that return the void type, must explicitly return a value according to their return type, or else must not return at all.

  • std::cout << "¡Hola mundo!" << std::endl; imprime “¡Hola mundo!” al flujo de salida estándar:

    • std is a namespace, and :: is the scope resolution operator that allows look-ups for objects by name within a namespace.

      There are many namespaces. Here, we use :: to show we want to use cout from the std namespace. For more information refer to Scope Resolution Operator - Microsoft Documentation.

    • std::cout is the standard output stream object, defined in iostream, and it prints to the standard output (stdout).

    • << is, in this context, the stream insertion operator, so called because it inserts an object into the stream object.

      The standard library defines the << operator to perform data insertion for certain data types into output streams. stream << content inserts content into the stream and returns the same, but updated stream. This allows stream insertions to be chained: std::cout << "Foo" << " Bar"; prints “FooBar” to the console.

    • "Hello World!" is a character string literal, or a “text literal.” The stream insertion operator for character string literals is defined in file iostream.

    • std::endl is a special I/O stream manipulator object, also defined in file iostream. Inserting a manipulator into a stream changes the state of the stream.

      The stream manipulator std::endl does two things: first it inserts the end-of-line character and then it flushes the stream buffer to force the text to show up on the console. This ensures that the data inserted into the stream actually appear on your console. (Stream data is usually stored in a buffer and then “flushed” in batches unless you force a flush immediately.)

      An alternate method that avoids the flush is:

      std::cout << "Hello World!\n";
      

      where \n is the character escape sequence for the newline character.

    • The semicolon (;) notifies the compiler that a statement has ended. All C++ statements and class definitions require an ending/terminating semicolon.

Comentarios

Un comentario es una forma de colocar texto arbitrario dentro del código fuente sin que el compilador de C++ lo interprete con ningún significado funcional. Los comentarios se utilizan para dar una idea del diseño o método de un programa.

Hay dos tipos de comentarios en C++:

Comentarios de una sola línea

La secuencia de doble barra diagonal // marcará todo el texto hasta una nueva línea como comentario:

int main()
{
   // This is a single-line comment.
   int a;  // this also is a single-line comment
   int i;  // this is another single-line comment
}

Comentarios de estilo C/bloque

La secuencia /* se usa para declarar el inicio del bloque de comentarios y la secuencia */ se usa para declarar el final del comentario. Todo el texto entre las secuencias de inicio y finalización se interpreta como un comentario, incluso si el texto es una sintaxis válida de C++. Estos a veces se denominan comentarios de “estilo C”, ya que esta sintaxis de comentarios se hereda del lenguaje predecesor de C++, C:

int main()
{
   /*
    *  This is a block comment.
    */
   int a;
}

En cualquier comentario de bloque, puedes escribir lo que quieras. Cuando el compilador encuentra el símbolo */, finaliza el comentario del bloque:

int main()
{
   /* A block comment with the symbol /*
      Note that the compiler is not affected by the second /*
      however, once the end-block-comment symbol is reached,
      the comment ends.
   */
   int a;
}

El ejemplo anterior es un código C++ (y C) válido. Sin embargo, tener /* adicional dentro de un comentario de bloque puede generar una advertencia en algunos compiladores.

Los comentarios en bloque también pueden comenzar y terminar dentro de una sola línea. Por ejemplo:

void SomeFunction(/* argument 1 */ int a, /* argument 2 */ int b);

Importancia de los comentarios

Al igual que con todos los lenguajes de programación, los comentarios brindan varios beneficios:

  • Documentación explícita del código para que sea más fácil de leer/mantener
  • Explicación del propósito y la funcionalidad del código.
  • Detalles sobre la historia o el razonamiento detrás del código
  • Colocación de derechos de autor/licencias, notas de proyectos, agradecimientos especiales, créditos de contribuyentes, etc. directamente en el código fuente.

Sin embargo, los comentarios también tienen sus inconvenientes:

  • Deben mantenerse para reflejar cualquier cambio en el código.
  • Los comentarios excesivos tienden a hacer que el código sea menos legible

La necesidad de comentarios se puede reducir escribiendo un código claro y autodocumentado. Un ejemplo simple es el uso de nombres explicativos para variables, funciones y tipos. La factorización de tareas lógicamente relacionadas en funciones discretas va de la mano con esto.

Marcadores de comentarios utilizados para deshabilitar el código

Durante el desarrollo, los comentarios también se pueden usar para deshabilitar rápidamente partes del código sin eliminarlo. Esto suele ser útil para fines de prueba o depuración, pero no es un buen estilo para nada más que ediciones temporales. Esto a menudo se denomina “comentar”.

Del mismo modo, mantener versiones antiguas de un fragmento de código en un comentario con fines de referencia está mal visto, ya que desordena los archivos y ofrece poco valor en comparación con la exploración del historial del código a través de un sistema de control de versiones.

El proceso de compilación estándar de C++

El código de programa ejecutable de C++ generalmente lo produce un compilador.

Un compilador es un programa que traduce el código de un lenguaje de programación a otra forma que es (más) directamente ejecutable para una computadora. Usar un compilador para traducir código se llama compilación.

C++ hereda la forma de su proceso de compilación de su lenguaje “principal”, C. A continuación se muestra una lista que muestra los cuatro pasos principales de compilación en C++:

  1. El preprocesador de C++ copia el contenido de cualquier archivo de encabezado incluido en el archivo de código fuente, genera código de macro y reemplaza las constantes simbólicas definidas usando #define con sus valores.
  2. El archivo de código fuente expandido producido por el preprocesador C++ se compila en lenguaje ensamblador apropiado para la plataforma.
  3. El código ensamblador generado por el compilador se ensambla en el código objeto apropiado para la plataforma.
  4. El archivo de código de objeto generado por el ensamblador está vinculado junto con los archivos de código de objeto para cualquier función de biblioteca utilizada para producir un archivo ejecutable.
  • Nota: algunos códigos compilados están vinculados entre sí, pero no para crear un programa final. Por lo general, este código “vinculado” también se puede empaquetar en un formato que otros programas pueden usar. Este “paquete de código utilizable empaquetado” es a lo que los programadores de C++ se refieren como una biblioteca.

Muchos compiladores de C++ también pueden fusionar o desfusionar ciertas partes del proceso de compilación para facilitar o realizar un análisis adicional. Muchos programadores de C++ usarán diferentes herramientas, pero todas las herramientas generalmente seguirán este proceso generalizado cuando estén involucradas en la producción de un programa.

El siguiente enlace amplía esta discusión y proporciona un buen gráfico para ayudar. 1: http://faculty.cs.niu.edu/~mcmahon/CS241/Notes/compile.html

Función

Una función es una unidad de código que representa una secuencia de sentencias.

Las funciones pueden aceptar argumentos o valores y devolver un solo valor (o no). Para usar una función, se usa una llamada de función en valores de argumento y el uso de la llamada de función en sí se reemplaza con su valor de retorno.

Cada función tiene una firma de tipo: los tipos de sus argumentos y el tipo de su tipo de retorno.

Las funciones están inspiradas en los conceptos del procedimiento y la función matemática.

  • Nota: las funciones de C++ son esencialmente procedimientos y no siguen la definición exacta ni las reglas de las funciones matemáticas.

Las funciones a menudo están destinadas a realizar una tarea específica. y se puede llamar desde otras partes de un programa. Una función debe declararse y definirse antes de que se la llame en otra parte de un programa.

  • Nota: las definiciones de funciones populares pueden estar ocultas en otros archivos incluidos (a menudo por conveniencia y reutilización en muchos archivos). Este es un uso común de los archivos de encabezado.

Declaración de función

Una declaración de función declara la existencia de una función con su nombre y firma de tipo al compilador. La sintaxis es la siguiente:

int add2(int i); // The function is of the type (int) -> (int)

En el ejemplo anterior, la función int add2(int i) declara lo siguiente al compilador:

  • El tipo de retorno es int.
  • El nombre de la función es add2.
  • El número de argumentos de la función es 1:
  • El primer argumento es del tipo int.
  • El primer argumento será referido en el contenido de la función por el nombre i.

El nombre del argumento es opcional; la declaración de la función también podría ser la siguiente:

int add2(int); // Omitting the function arguments' name is also permitted.

De acuerdo con la regla de una definición, una función con una determinada firma de tipo solo se puede declarar o definir una vez en una base de código C++ completa visible para el compilador de C++. En otras palabras, las funciones con una firma de tipo específica no se pueden volver a definir; solo se deben definir una vez. Por lo tanto, lo siguiente no es válido en C++:

int add2(int i);  // The compiler will note that add2 is a function (int) -> int
int add2(int j);  // As add2 already has a definition of (int) -> int, the compiler
                  // will regard this as an error.

Si una función no devuelve nada, su tipo de retorno se escribe como vacío. Si no toma parámetros, la lista de parámetros debe estar vacía.

void do_something(); // The function takes no parameters, and does not return anything.
                     // Note that it can still affect variables it has access to.

Llamada de función

Se puede llamar a una función después de haberla declarado. Por ejemplo, el siguiente programa llama a add2 con el valor 2 dentro de la función main:

#include <iostream>

int add2(int i);    // Declaration of add2

// Note: add2 is still missing a DEFINITION.
// Even though it doesn't appear directly in code,
// add2's definition may be LINKED in from another object file.

int main()
{
    std::cout << add2(2) << "\n";  // add2(2) will be evaluated at this point,
                                   // and the result is printed.
    return 0;  
}

Aquí, add2(2) es la sintaxis para una llamada de función.

Definición de función

Una definición de función* es similar a una declaración, excepto que también contiene el código que se ejecuta cuando se llama a la función dentro de su cuerpo.

Un ejemplo de una definición de función para add2 podría ser:

int add2(int i)       // Data that is passed into (int i) will be referred to by the name i
{                     // while in the function's curly brackets or "scope."
                    
    int j = i + 2;    // Definition of a variable j as the value of i+2.
    return j;         // Returning or, in essence, substitution of j for a function call to
                      // add2.
}

Sobrecarga de funciones

Puede crear múltiples funciones con el mismo nombre pero diferentes parámetros.

int add2(int i)           // Code contained in this definition will be evaluated
{                         // when add2() is called with one parameter.
    int j = i + 2;
    return j;
}

int add2(int i, int j)    // However, when add2() is called with two parameters, the
{                         // code from the initial declaration will be overloaded,
    int k = i + j + 2 ;   // and the code in this declaration will be evaluated
    return k;             // instead.
}

Ambas funciones se llaman con el mismo nombre add2, pero la función real que se llama depende directamente de la cantidad y el tipo de parámetros en la llamada. En la mayoría de los casos, el compilador de C++ puede calcular qué función llamar. En algunos casos, el tipo debe indicarse explícitamente.

Parámetros predeterminados

Los valores predeterminados para los parámetros de función solo se pueden especificar en las declaraciones de función.

int multiply(int a, int b = 7); // b has default value of 7.
int multiply(int a, int b)
{
    return a * b;               // If multiply() is called with one parameter, the
}                               // value will be multiplied by the default, 7.

En este ejemplo, se puede llamar a multiply() con uno o dos parámetros. Si solo se proporciona un parámetro, b tendrá un valor predeterminado de 7. Los argumentos predeterminados deben colocarse en los últimos argumentos de la función. Por ejemplo:

int multiply(int a = 10, int b = 20); // This is legal 
int multiply(int a = 10, int b);      // This is illegal since int a is in the former

Llamadas de función especial - Operadores

Existen llamadas a funciones especiales en C++ que tienen una sintaxis diferente a name_of_function(value1, value2, value3). El ejemplo más común es el de los operadores.

Ciertas secuencias de caracteres especiales que el compilador reducirá a llamadas de función, como !, +, -, *, % y << y muchas más. Estos caracteres especiales normalmente se asocian con el uso que no es de programación o se usan por motivos estéticos (por ejemplo, el carácter + se reconoce comúnmente como el símbolo de adición tanto dentro de la programación C++ como en las matemáticas elementales).

C++ maneja estas secuencias de caracteres con una sintaxis especial; pero, en esencia, cada aparición de un operador se reduce a una llamada de función. Por ejemplo, la siguiente expresión de C++:

3+3

es equivalente a la siguiente llamada de función:

operator+(3, 3)

Todos los nombres de funciones de operadores comienzan con operator.

Mientras que en el predecesor inmediato de C++, C, a los nombres de las funciones del operador no se les pueden asignar diferentes significados al proporcionar definiciones adicionales con firmas de tipos diferentes, en C++, esto es válido. “Ocultar” definiciones de funciones adicionales bajo un nombre de función único se conoce como sobrecarga de operadores en C++, y es una convención relativamente común, pero no universal, en C++.

Visibilidad de prototipos de funciones y declaraciones

En C++, el código debe declararse o definirse antes de su uso. Por ejemplo, lo siguiente produce un error de tiempo de compilación:

int main()
{
  foo(2); // error: foo is called, but has not yet been declared
}

void foo(int x) // this later definition is not known in main
{
}

Hay dos formas de resolver esto: poner la definición o declaración de foo() antes de su uso en main(). Aquí hay un ejemplo:

void foo(int x) {}  //Declare the foo function and body first

int main()
{
  foo(2); // OK: foo is completely defined beforehand, so it can be called here.
}

Sin embargo, también es posible “declarar hacia adelante” la función colocando solo una declaración de “prototipo” antes de su uso y luego definiendo el cuerpo de la función más adelante:

void foo(int);  // Prototype declaration of foo, seen by main
                // Must specify return type, name, and argument list types
int main()
{
  foo(2); // OK: foo is known, called even though its body is not yet defined
}

void foo(int x) //Must match the prototype
{
    // Define body of foo here
}

El prototipo debe especificar el tipo de devolución (void), el nombre de la función (foo) y los tipos de variables de la lista de argumentos (int), pero NO se requieren los nombres de los argumentos .

Una forma común de integrar esto en la organización de los archivos fuente es crear un archivo de encabezado que contenga todas las declaraciones prototipo:

// foo.h
void foo(int); // prototype declaration

y luego proporcione la definición completa en otro lugar:

// foo.cpp --> foo.o
#include "foo.h" // foo's prototype declaration is "hidden" in here
void foo(int x) { } // foo's body definition

y luego, una vez compilado, vincule el archivo de objeto correspondiente foo.o al archivo de objeto compilado donde se usa en la fase de enlace, main.o:

// main.cpp --> main.o
#include "foo.h" // foo's prototype declaration is "hidden" in here
int main() { foo(2); } // foo is valid to call because its prototype declaration was beforehand.
// the prototype and body definitions of foo are linked through the object files

Se produce un error de “símbolo externo no resuelto” cuando la función prototipo y llamada existen, pero la función cuerpo no está definida. Estos pueden ser más complicados de resolver ya que el compilador no informará el error hasta la etapa final de vinculación, y no sabe a qué línea saltar en el código para mostrar el error.

Preprocesador

El preprocesador es una parte importante del compilador.

Edita el código fuente, recortando algunos bits, cambiando otros y agregando otras cosas.

En los archivos fuente, podemos incluir directivas de preprocesador. Estas directivas le dicen al preprocesador que realice acciones específicas. Una directiva comienza con un # en una nueva línea. Ejemplo:

#define ZERO 0

La primera directiva de preprocesador que encontrará es probablemente la

#include <something>

directiva. Lo que hace es tomar todo algo y lo inserta en su archivo donde estaba la directiva. El programa hola mundo comienza con la línea

#include <iostream>

Esta línea agrega las funciones y los objetos que le permiten usar la entrada y salida estándar.

El lenguaje C, que también usa el preprocesador, no tiene tantos archivos de encabezado como el lenguaje C++, pero en C++ puede usar todos los archivos de encabezado C.


La próxima directiva importante es probablemente la

#define something something_else

directiva. Esto le dice al preprocesador que a medida que avanza en el archivo, debe reemplazar cada ocurrencia de algo con algo_más. También puede hacer cosas similares a las funciones, pero eso probablemente cuente como C++ avanzado.

algo_más no es necesario, pero si define algo como nada, entonces, fuera de las directivas del preprocesador, todas las apariciones de algo desaparecerán.

Esto realmente es útil, debido a las directivas #if, #else y #ifdef. El formato para estos sería el siguiente:

#if something==true
//code
#else
//more code
#endif

#ifdef thing_that_you_want_to_know_if_is_defined
//code
#endif

Estas directivas insertan el código que está en el bit verdadero y eliminan los bits falsos. esto se puede usar para tener bits de código que solo se incluyen en ciertos sistemas operativos, sin tener que volver a escribir todo el código.