En informática , la programación imperativa es un paradigma de programación de software que utiliza instrucciones que cambian el estado de un programa . De la misma manera que el modo imperativo en los lenguajes naturales expresa comandos, un programa imperativo consiste en comandos que la computadora debe ejecutar. La programación imperativa se centra en describir cómo funciona un programa paso a paso, [1] en lugar de en descripciones de alto nivel de sus resultados esperados.
El término se utiliza a menudo en contraste con la programación declarativa , que se centra en lo que el programa debe lograr sin especificar todos los detalles de cómo el programa debe lograr el resultado. [2]
La programación procedimental es un tipo de programación imperativa en la que el programa se construye a partir de uno o más procedimientos (también denominados subrutinas o funciones). Los términos se utilizan a menudo como sinónimos, pero el uso de procedimientos tiene un efecto dramático en cómo aparecen los programas imperativos y cómo se construyen. La programación procedimental pesada, en la que los cambios de estado se localizan en los procedimientos o se restringen a argumentos explícitos y retornos de los procedimientos, es una forma de programación estructurada . Desde la década de 1960, la programación estructurada y la programación modular en general se han promovido como técnicas para mejorar la capacidad de mantenimiento y la calidad general de los programas imperativos. Los conceptos detrás de la programación orientada a objetos intentan extender este enfoque.
La programación procedimental podría considerarse un paso hacia la programación declarativa. Con solo mirar los nombres, argumentos y tipos de retorno de los procedimientos (y los comentarios relacionados), un programador puede determinar qué se supone que debe hacer un procedimiento en particular, sin mirar necesariamente los detalles de cómo logra su resultado. Al mismo tiempo, un programa completo sigue siendo imperativo, ya que fija en gran medida las instrucciones que se ejecutarán y su orden de ejecución.
El paradigma de programación utilizado para crear programas para casi todas las computadoras generalmente sigue un modelo imperativo. [nota 1] El hardware de las computadoras digitales está diseñado para ejecutar código de máquina , que es nativo de la computadora y generalmente está escrito en estilo imperativo, aunque existen compiladores e intérpretes de bajo nivel que utilizan otros paradigmas para algunas arquitecturas, como las máquinas Lisp .
Desde esta perspectiva de bajo nivel, el estado del programa se define por el contenido de la memoria, y las instrucciones son instrucciones en el lenguaje de máquina nativo de la computadora. Los lenguajes imperativos de nivel superior utilizan variables y declaraciones más complejas, pero siguen el mismo paradigma. Las recetas y las listas de verificación de procesos , si bien no son programas de computadora , también son conceptos familiares que son similares en estilo a la programación imperativa; cada paso es una instrucción, y el mundo físico contiene el estado. Dado que las ideas básicas de la programación imperativa son conceptualmente familiares y están incorporadas directamente en el hardware, la mayoría de los lenguajes de computadora son de estilo imperativo.
Las sentencias de asignación , en el paradigma imperativo, realizan una operación sobre la información ubicada en la memoria y almacenan los resultados en la memoria para su uso posterior. Los lenguajes imperativos de alto nivel, además, permiten la evaluación de expresiones complejas , que pueden consistir en una combinación de operaciones aritméticas y evaluaciones de funciones , y la asignación del valor resultante a la memoria. Las sentencias de bucle (como en los bucles while , do while y for ) permiten que una secuencia de sentencias se ejecute varias veces. Los bucles pueden ejecutar las sentencias que contienen un número predefinido de veces, o pueden ejecutarlas repetidamente hasta que se cumpla alguna condición. Las sentencias de ramificación condicional permiten que una secuencia de sentencias se ejecute solo si se cumple alguna condición. De lo contrario, las sentencias se omiten y la secuencia de ejecución continúa desde la sentencia siguiente. Las sentencias de ramificación incondicional permiten que una secuencia de ejecución se transfiera a otra parte de un programa. Estas incluyen el salto (llamado goto en muchos lenguajes), el cambio y la llamada a un subprograma, subrutina o procedimiento (que generalmente regresa a la siguiente sentencia después de la llamada).
En los inicios del desarrollo de los lenguajes de programación de alto nivel , la introducción del bloque permitió la construcción de programas en los que un grupo de instrucciones y declaraciones podían tratarse como si fueran una sola instrucción. Esto, junto con la introducción de subrutinas , permitió expresar estructuras complejas mediante descomposición jerárquica en estructuras procedimentales más simples.
Muchos lenguajes de programación imperativos (como Fortran , BASIC y C ) son abstracciones del lenguaje ensamblador . [3]
Los primeros lenguajes imperativos fueron los lenguajes de máquina de los ordenadores originales. En estos lenguajes, las instrucciones eran muy simples, lo que facilitaba la implementación del hardware pero dificultaba la creación de programas complejos. FORTRAN , desarrollado por John Backus en International Business Machines (IBM) a partir de 1954, fue el primer lenguaje de programación importante que eliminó los obstáculos que presentaba el código de máquina en la creación de programas complejos. FORTRAN era un lenguaje compilado que permitía variables con nombre, expresiones complejas, subprogramas y muchas otras características ahora comunes en los lenguajes imperativos. Las siguientes dos décadas vieron el desarrollo de muchos otros lenguajes de programación imperativa de alto nivel importantes. A finales de los años 1950 y 1960, se desarrolló ALGOL para permitir que los algoritmos matemáticos se expresaran más fácilmente e incluso sirvió como lenguaje de destino del sistema operativo para algunos ordenadores. MUMPS (1966) llevó el paradigma imperativo a un extremo lógico, al no tener ninguna declaración en absoluto, basándose puramente en comandos, incluso hasta el punto de hacer que los comandos IF y ELSE fueran independientes entre sí, conectados solo por una variable intrínseca llamada $TEST. COBOL (1960) y BASIC (1964) fueron ambos intentos de hacer que la sintaxis de programación se pareciera más al inglés. En la década de 1970, Pascal fue desarrollado por Niklaus Wirth , y C fue creado por Dennis Ritchie mientras trabajaba en Bell Laboratories . Wirth pasó a diseñar Modula-2 y Oberon . Para las necesidades del Departamento de Defensa de los Estados Unidos , Jean Ichbiah y un equipo de Honeywell comenzaron a diseñar Ada en 1978, después de un proyecto de 4 años para definir los requisitos para el lenguaje. La especificación se publicó por primera vez en 1983, con revisiones en 1995, 2005 y 2012.
La década de 1980 vio un rápido crecimiento en el interés por la programación orientada a objetos . Estos lenguajes eran de estilo imperativo, pero añadían características para soportar objetos . Las dos últimas décadas del siglo XX vieron el desarrollo de muchos de estos lenguajes. Smalltalk -80, concebido originalmente por Alan Kay en 1969, fue lanzado en 1980, por el Centro de Investigación Xerox Palo Alto ( PARC ). Basándose en conceptos de otro lenguaje orientado a objetos, Simula (que se considera el primer lenguaje de programación orientado a objetos del mundo , desarrollado en la década de 1960), Bjarne Stroustrup diseñó C++ , un lenguaje orientado a objetos basado en C. El diseño de C++ comenzó en 1979 y la primera implementación se completó en 1983. A finales de la década de 1980 y 1990, los lenguajes imperativos notables que se basaban en conceptos orientados a objetos fueron Perl , lanzado por Larry Wall en 1987; Python , lanzado por Guido van Rossum en 1990; Visual Basic y Visual C++ (que incluía Microsoft Foundation Class Library (MFC) 2.0), publicados por Microsoft en 1991 y 1993 respectivamente; PHP , publicado por Rasmus Lerdorf en 1994; Java , por James Gosling ( Sun Microsystems ) en 1995, JavaScript , por Brendan Eich ( Netscape ), y Ruby , por Yukihiro "Matz" Matsumoto, ambos publicados en 1995. .NET Framework (2002) de Microsoft es imperativo en su núcleo, al igual que sus principales lenguajes de destino, VB.NET y C# que se ejecutan en él; sin embargo, F# de Microsoft , un lenguaje funcional, también se ejecuta en él.
FORTRAN (1958) se presentó como "El sistema de traducción de fórmulas matemáticas de IBM". Fue diseñado para cálculos científicos, sin funciones de manejo de cadenas . Junto con declaraciones , expresiones y sentencias , admitía:
Tuvo éxito porque:
Sin embargo, otros proveedores que no eran IBM también escribieron compiladores Fortran, pero con una sintaxis que probablemente no sería compatible con el compilador de IBM. [4] El Instituto Nacional Estadounidense de Estándares (ANSI) desarrolló el primer estándar Fortran en 1966. En 1978, Fortran 77 se convirtió en el estándar hasta 1991. Fortran 90 admite:
COBOL (1959) significa "lenguaje común orientado a los negocios". Fortran manipulaba símbolos. Pronto se comprendió que los símbolos no tenían por qué ser números, por lo que se introdujeron las cadenas. [5] El Departamento de Defensa de los EE. UU. influyó en el desarrollo de COBOL, y Grace Hopper fue una de las principales contribuyentes. Las instrucciones eran similares a las del inglés y verbosas. El objetivo era diseñar un lenguaje para que los gerentes pudieran leer los programas. Sin embargo, la falta de instrucciones estructuradas impidió este objetivo. [6]
El desarrollo de COBOL estuvo muy controlado, por lo que no surgieron dialectos que requirieran estándares ANSI. Como consecuencia, no se modificó durante 15 años hasta 1974. La versión de los años 1990 sí introdujo cambios importantes, como la programación orientada a objetos . [6]
ALGOL (1960) significa "lenguaje ALGOrítmico". Tuvo una profunda influencia en el diseño de lenguajes de programación. [7] Surgió de un comité de expertos en lenguajes de programación europeos y estadounidenses, utilizaba notación matemática estándar y tenía un diseño estructurado legible. Algol fue el primero en definir su sintaxis utilizando la forma Backus–Naur . [7] Esto dio lugar a compiladores dirigidos por sintaxis . Añadió características como:
Los descendientes directos de Algol incluyen Pascal , Modula-2 , Ada , Delphi y Oberon en una rama. En otra rama están C , C++ y Java . [7]
BASIC (1964) significa "Código de instrucción simbólica para todo uso para principiantes". Fue desarrollado en el Dartmouth College para que lo aprendieran todos sus estudiantes. [8] Si un estudiante no pasaba a un lenguaje más potente, seguiría recordando Basic. [8] Se instaló un intérprete de Basic en los microordenadores fabricados a finales de los años 70. A medida que la industria de los microordenadores crecía, también lo hacía el lenguaje. [8]
Basic fue pionero en la sesión interactiva . [8] Ofrecía comandos del sistema operativo dentro de su entorno:
Sin embargo, la sintaxis básica era demasiado simple para programas grandes. [8] Los dialectos más recientes añadieron estructura y extensiones orientadas a objetos. El Visual Basic de Microsoft todavía se utiliza ampliamente y produce una interfaz gráfica de usuario . [9]
El lenguaje de programación C (1973) recibió su nombre porque el lenguaje BCPL fue reemplazado por B , y AT&T Bell Labs llamó a la siguiente versión "C". Su propósito era escribir el sistema operativo UNIX . [10] C es un lenguaje relativamente pequeño, lo que facilita la escritura de compiladores. Su crecimiento reflejó el crecimiento del hardware en la década de 1980. [10] Su crecimiento también se debió a que tiene las facilidades del lenguaje ensamblador , pero utiliza una sintaxis de alto nivel . Agregó características avanzadas como:
C permite al programador controlar en qué región de la memoria se almacenarán los datos. Las variables globales y las variables estáticas requieren la menor cantidad de ciclos de reloj para almacenarse. La pila se utiliza automáticamente para las declaraciones de variables estándar . La memoria del montón se devuelve a una variable de puntero desde la malloc()
función.
main()
función. [12] Las variables globales son visibles para main()
todas las demás funciones en el código fuente.main()
, otras funciones o dentro de {
}
delimitadores de bloques son variables locales . Las variables locales también incluyen variables de parámetros formales . Las variables de parámetros se encierran entre los paréntesis de las definiciones de funciones. [13] Proporcionan una interfaz a la función.static
prefijo también se almacenan en la región de datos globales y estáticos . [11] A diferencia de las variables globales, las variables estáticas solo son visibles dentro de la función o el bloque. Las variables estáticas siempre conservan su valor. Un ejemplo de uso sería la funciónint increment_counter(){ static int counter = 0; counter++; return counter;}
static
prefijo, incluidas las variables de parámetros formales, [15] se denominan variables automáticas [12] y se almacenan en la pila. [11] Son visibles dentro de la función o bloque y pierden su alcance al salir de la función o bloque.malloc()
función de biblioteca para asignar memoria de montón. [17] Llenar el montón con datos es una función de copia adicional. Las variables almacenadas en el montón se pasan de manera económica a las funciones mediante punteros. Sin punteros, todo el bloque de datos tendría que pasarse a la función a través de la pila.En la década de 1970, los ingenieros de software necesitaban soporte de lenguaje para dividir proyectos grandes en módulos . [18] Una característica obvia era descomponer proyectos grandes físicamente en archivos separados . Una característica menos obvia era descomponer proyectos grandes lógicamente en tipos de datos abstractos . [18] En ese momento, los lenguajes admitían tipos de datos concretos ( escalares ) como números enteros , números de punto flotante y cadenas de caracteres . Los tipos de datos concretos tienen su representación como parte de su nombre. [19] Los tipos de datos abstractos son estructuras de tipos de datos concretos, con un nuevo nombre asignado. Por ejemplo, una lista de números enteros podría llamarse .integer_list
En la jerga orientada a objetos, los tipos de datos abstractos se denominan clases . Sin embargo, una clase es solo una definición; no se le asigna memoria. Cuando se le asigna memoria a una clase, se la denomina objeto . [20]
Los lenguajes imperativos orientados a objetos se desarrollaron combinando la necesidad de clases y la necesidad de una programación funcional segura . [21] Una función, en un lenguaje orientado a objetos, se asigna a una clase. Una función asignada se denomina entonces método , función miembro u operación . La programación orientada a objetos consiste en ejecutar operaciones sobre objetos . [22]
Los lenguajes orientados a objetos admiten una sintaxis para modelar relaciones de subconjuntos/superconjuntos . En la teoría de conjuntos , un elemento de un subconjunto hereda todos los atributos contenidos en el superconjunto. Por ejemplo, un estudiante es una persona. Por lo tanto, el conjunto de estudiantes es un subconjunto del conjunto de personas. Como resultado, los estudiantes heredan todos los atributos comunes a todas las personas. Además, los estudiantes tienen atributos únicos que otras personas no tienen. Los lenguajes orientados a objetos modelan relaciones de subconjuntos/superconjuntos utilizando la herencia . [23] La programación orientada a objetos se convirtió en el paradigma de lenguaje dominante a fines de la década de 1990. [18]
C++ (1985) se llamó originalmente "C con clases". [24] Fue diseñado para expandir las capacidades de C agregando las facilidades orientadas a objetos del lenguaje Simula . [25]
Un módulo orientado a objetos se compone de dos archivos. El archivo de definiciones se denomina archivo de encabezado . A continuación, se muestra un archivo de encabezado de C++ para la clase GRADE en una aplicación escolar sencilla:
// grado.h // -------// Se utiliza para permitir que varios archivos de origen incluyan // este archivo de encabezado sin errores de duplicación. // Ver: https://en.wikipedia.org/wiki/Imperative_language/Include_guard // ---------------------------------------------- #ifndef GRADE_H #define GRADE_Hclase GRADE { public : // Esta es la operación del constructor. // ---------------------------------- GRADE ( const char letter ); // Esta es una variable de clase. // ------------------------- char letra ; // Esta es una operación miembro. // --------------------------- int grade_numeric ( const char letter ); // Esta es una variable de clase. // ------------------------- int numérico ; }; #endif
Una operación constructora es una función con el mismo nombre que el nombre de la clase. [26] Se ejecuta cuando la operación que llama ejecuta la new
declaración.
El otro archivo de un módulo es el archivo fuente . Aquí se muestra un archivo fuente de C++ para la clase GRADE en una aplicación escolar sencilla:
// calificación.cpp // --------- #include "calificación.h" GRADE :: GRADE ( const char letter ) { // Hacer referencia al objeto usando la palabra clave 'this'. // ---------------------------------------------- this -> letter = letter ; // Esto es cohesión temporal // ------------------------- this -> numeric = grade_numeric ( letter ); } int CALIFICACIÓN :: calificación_numérica ( const char letra ) { if ( ( letra == 'A' || letra == 'a' ) ) return 4 ; else if ( ( letra == 'B' || letra == 'b' ) ) return 3 ; else if ( ( letra == 'C' || letra == 'c' ) ) return 2 ; else if ( ( letra == 'D' || letra == 'd' ) ) return 1 ; else if ( ( letra == 'F' || letra == 'f' ) ) return 0 ; else return -1 ; }
Aquí hay un archivo de encabezado C++ para la clase PERSONA en una aplicación escolar simple:
// persona.h // -------- #ifndef PERSONA_H #define PERSONA_Hclase PERSONA { público : PERSONA ( const char * nombre ); const char * nombre ; }; #endif
Aquí hay un archivo fuente de C++ para la clase PERSONA en una aplicación escolar simple:
// persona.cpp // ---------- #include "persona.h" PERSONA :: PERSONA ( const char * nombre ) { this -> nombre = nombre ; }
Aquí hay un archivo de encabezado C++ para la clase ESTUDIANTE en una aplicación escolar simple:
// estudiante.h // --------- #ifndef ESTUDIANTE_H #define ESTUDIANTE_H#include "persona.h" #include "calificación.h" // Un ESTUDIANTE es un subconjunto de PERSONA. // -------------------------------- class ESTUDIANTE : public PERSONA { public : ESTUDIANTE ( const char * nombre ); ~ ESTUDIANTE (); CALIFICACIÓN * calificación ; }; #endif
Aquí hay un archivo fuente de C++ para la clase ESTUDIANTE en una aplicación escolar simple:
// estudiante.cpp // ----------- #include "estudiante.h" #include "persona.h" ESTUDIANTE :: ESTUDIANTE ( const char * nombre ) : // Ejecuta el constructor de la superclase PERSONA. // ------------------------------------------------- PERSONA ( nombre ) { // No hay nada más que hacer. // ------------------- } ESTUDIANTE ::~ ESTUDIANTE () { // desasignar la memoria de la calificación // para evitar fugas de memoria. // ------------------------------------------------- eliminar esto -> calificación ; }
A continuación se muestra un programa de controlador para demostración:
// estudiante_dvr.cpp // --------------- #include <iostream> #include "estudiante.h" int main ( void ) { ESTUDIANTE * estudiante = new ESTUDIANTE ( "El Estudiante" ); estudiante -> calificación = new CALIFICACIÓN ( 'a' ); std :: cout // Observe que el estudiante hereda el nombre de PERSONA << estudiante -> nombre << ": Calificación numérica = " << estudiante -> calificación -> numérico << " \n " ; // desasignar la memoria del estudiante // para evitar fugas de memoria. // ------------------------------------------------- delete student ; devuelve 0 ; }
Aquí hay un makefile para compilar todo:
# makefile # -------- todos : student_dvr limpio : rm student_dvr *.oestudiante_dvr : estudiante_dvr . cpp grado . o estudiante . o persona . o c++ estudiante_dvr.cpp grado.o estudiante.o persona.o -o estudiante_dvr grado.o : grado .cpp grado .h c ++ -c grado.cpp estudiante.o : estudiante . cpp estudiante . h c++ -c estudiante. cpp persona.o : persona . cpp persona . h c++ -c persona.cpp