Red de conocimiento informático - Computadora portátil - ¿Cuál es la diferencia entre C y C?

¿Cuál es la diferencia entre C y C?

------------------------------------------- ----- -------------------------------------

Piensa del estándar C como nuevo idioma

Aprender el estándar C como nuevo idioma

Autor Bjarne Stroustrup

Traductor Chen Wei

Don' Considerar C como un nuevo lenguaje. Es el siguiente lenguaje después de C. Simplemente hazle esta pregunta al padre de C.

Nota de Hou Jie: Este artículo es un artículo publicado en la revista "Programmer" de Beijing en 2001/04. La traducción es fluida y técnica.

El traductor, Sr. Chen Wei, y el responsable de la revista "Programmer", Sr. Jiang Tao, acordaron reproducirlo aquí para beneficio de los lectores de Taiwán. Muchas gracias.

Nadie puede reimprimir este artículo sin el consentimiento del Sr. Chen Wei y del Sr. Jiang Tao.

------------------------------------------- ----- -------------------------------------

C /Diario del usuario de C, mayo de 1999

Aprendizaje del estándar C como nuevo idioma

por Bjarne Stroustrup

------------ ----- --------------------------------------------- ----- -------------

Importar

Para obtener las mayores ventajas del estándar C [Material de referencia 1], debemos repensar El estilo de escritura del programa C. Una forma de repensar esto es pensar en cómo se debe aprender (y educar) C. ¿Qué técnicas de programación debemos enfatizar? ¿Qué parte del idioma deberíamos aprender primero? ¿Qué parte del código real queremos resaltar?

Este artículo compara varios programas C simples, algunos escritos en un estilo moderno (usando la biblioteca estándar) y otros escritos en un estilo C tradicional. Las lecciones aprendidas de estos simples ejemplos todavía tienen implicaciones importantes para programas más amplios. En términos generales, este artículo aboga por tratar C como un lenguaje de nivel superior que se basa en la abstracción para brindar simplicidad y elegancia sin perder la eficiencia de un estilo de nivel inferior.

Todos queremos que los programas sean fáciles de escribir, ejecutar correctamente, fáciles de mantener y que tengan una eficiencia aceptable. Esto significa que debemos usar C (o cualquier otro lenguaje) de la manera más cercana a este ideal. Sospecho que la comunidad C aún no ha sido capaz de digerir las facilidades que ofrece el estándar C; repensar cómo usamos C podría conducir a algunas mejoras importantes que conducirían a estos ideales. Este artículo se centra en un estilo de programación que se centra en aprovechar al máximo las funciones admitidas por el Estándar C, en lugar de esas funciones en sí.

La principal clave de mejora es reducir el tamaño y la complejidad del código que escribimos mediante el uso de bibliotecas. A continuación demostraré y cuantificaré estas reducciones en algunos ejemplos sencillos. Ejemplos sencillos de este tipo podrían aparecer en cualquier curso de introducción a C.

Al reducir el tamaño y la complejidad, reducimos el tiempo de desarrollo, reducimos los costos de mantenimiento y reducimos los costos de prueba. Otro punto importante es que mediante el uso de librerías de programación también podemos simplificar el aprendizaje de C. Para programas pequeños y estudiantes que sólo quieren obtener buenas calificaciones, esta simplificación debería ser suficiente.

Sin embargo, los programadores profesionales son extremadamente exigentes en materia de eficiencia. Sólo cuando no se sacrifica la eficiencia podemos esperar mejorar nuestro estilo de programación para satisfacer las estrictas demandas de datos y respuestas instantáneas de los servicios y empresas modernos. Para ello muestro un resultado de medición que demuestra que la reducción de la complejidad no pierde eficiencia. Finalmente, analizo las implicaciones de esta perspectiva para el aprendizaje y la educación C.

Complejidad

Considere una pregunta que es muy adecuada como segundo ejercicio en un curso de lenguajes de programación (Traducción: el primer ejercicio es, por supuesto, "hola mundo"):

Envía un mensaje "Por favor ingresa tu nombre"

Lee el nombre

Envía "Hola lt;namegt;"

En el estándar C, el la respuesta obvia es:

#includelt;iostreamgt; // Obtener las funciones de E/S estándar

#includelt;stringgt; // Obtener las funciones de cadena estándar

p>

int main()

{

// Obtener acceso a la biblioteca estándar

usando el espacio de nombres std

cout lt; ; lt; "Ingrese su nombre: \n";

nombre de cadena;

cin gt; " name '\n'

}

Para un verdadero principiante, tenemos que explicar toda la arquitectura. ¿Qué es principal()? ¿Qué hace #incluir? ¿Para qué se utiliza el uso? Además, tenemos que entender todas las pequeñas reglas, como el significado de \n, dónde agregar un punto y coma, etc.

Sin embargo, el concepto del componente principal de este programa es muy simple, y la única diferencia entre este y la descripción textual del tema es la representación. Debemos aprender esta notación, que es muy simple: cadena es una cadena (cadena), cout es una salida (dispositivo de salida), lt;lt; es un operador que usamos para escribir en el dispositivo de salida┅, etc.

A modo de comparación, aquí se muestra la solución tradicional estilo C [Nota 1]:

#includelt; stdio hgt // Obtener funciones de E/S estándar

<. p>int main()

{

const int max = 20; // La longitud máxima del nombre es 19

char name[max]; /p>

printf("Ingrese su nombre: \n");

// Lee los caracteres en el nombre

scanf( "s" , nombre

printf( "Hola s\n" , nombre);

return 0;

}

Obviamente, la lógica principal ha cambiado ligeramente. - sólo un poco - y es un poco más complicado que la versión de estilo C, ya que tenemos que tener en cuenta matrices y notaciones extrañas. El principal problema es que esta sencilla solución estilo C tiene poco valor.

Si alguien ingresa un nombre con más de 19 (19 es el 20 especificado anteriormente menos 1, donde el 1 se usa para finalizar la cadena de estilo C), el programa fallará.

Algunas personas piensan que este tipo de producto inferior en realidad no causará daño, siempre y cuando se utilice alguna solución adecuada "que se describe más adelante". Sin embargo, aun así, la línea controvertida es sólo "aceptable" y no alcanza el nivel de "buena". Lo ideal sería no exponer a un usuario novato a un programa que sea propenso a fallar.

¿Cómo puede comportarse este programa estilo C como un programa estilo C? Primero, podemos usar scanf apropiadamente para evitar el desbordamiento de la matriz:

#includelt; stdio hgt // Obtener funciones de E/S estándar

int min()

<. p>{

const int max 20;

char nombre [max];

printf( "Ingrese su nombre: \ n"); /p>

scanf( "19s", nombre); // Lee hasta 19 caracteres

printf( "Hola s\n", nombre

return 0); ;

}

No existe una forma estándar de usar el tamaño del búfer simbólico directamente en la cadena de formato scanf, así que tengo que hacer algo como lo anterior. Usar constantes literales enteras de esa manera. Ese es un mal estilo y una bomba de tiempo para el mantenimiento futuro. El siguiente es un enfoque de nivel experto, pero es realmente difícil de explicar a los principiantes:

char fmt[10]

// Generar una cadena de formato, como usar un simple s posible causará desbordamiento

sprintf(fmt, "ds", max-1);

// Lee como máximo un máximo de 1 caracteres en el nombre.

scanf(fmt, name);

Además, este programa cortará los caracteres que "excedan el tamaño del búfer". Sin embargo, queremos que la cadena crezca como entrada.

Para hacer esto, tenemos que bajar la abstracción a un nivel inferior y tratar con caracteres individuales:

#includelt;stdio.hgt;

#includelt;ctype.

#includelt;stdlib.hgt;

void quit()

{

// Escribir mensaje de error y salir

fprintf( stderr, "memoria agotada\n");

salir (1

}

int main()

{

int max= 20;

// Búfer de configuración:

char* nombre = (char*) malloc( max);

if (name ==0) quit();

printf( "Ingrese su nombre: \n");

// Omitir espacios en blanco iniciales caracteres

while (true) {

int c = getchar();

if (c = EOF) break; // Fin del archivo

if (!isspace(c)) {

ungetc (c, stdin

break;

}

}

int i = 0;

mientras (verdadero) {

int c = getchar();

if (c) == '\n' || c == EOF) {

// Agrega el carácter final 0 al final

nombre[i] = 0; p> break;

}

nombre[i] = c;

if (i == max-1) { // El búfer está lleno

max = max max;

nombre = (char*) realloc(nombre, max);

if (nombre = = 0) quit(); /p>

}

itt;

}

printf( "Hola s\n", nombre);

free(name); // Liberar memoria

return 0;

}

En comparación con la versión anterior, esta La versión es significativamente más compleja. Agregar un proceso de "omitir el carácter de espacio en blanco inicial" me hace sentir un poco culpable porque no mencioné explícitamente este requisito en la descripción del título. Sin embargo, "omitir los espacios en blanco iniciales" es un comportamiento normal y se realizará en otras versiones más adelante.

Algunas personas pueden pensar que este ejemplo no es tan malo. Los programadores de C más experimentados y los programadores de C probablemente (con suerte) hayan escrito algunas de estas cosas en una aplicación real. Incluso podríamos pensar que si no puedes escribir un programa como ese, no eres un programador profesional. Sin embargo, piense en la carga conceptual que estas cosas imponen a los principiantes. El programa anterior utiliza siete funciones estándar de C diferentes, maneja la entrada a nivel de caracteres a un nivel muy trivial, usa punteros y maneja el almacén gratuito en sí (anotación: generalmente el montón). Para poder usar realloc, tengo que usar malloc (en lugar de new). Esto nos lleva al tema de la conversión de tamaño y tipo [Nota 2]. En un programa tan pequeño, ¿cuál es la mejor manera de abordar posibles problemas de agotamiento de la memoria? La respuesta no es obvia. Sólo estoy haciendo algo aquí para evitar que esta discusión degenere en otro tema no relacionado. Las personas que están acostumbradas a los métodos de estilo C deben considerar cuidadosamente qué método puede formar una buena base para una enseñanza más profunda y una aplicación práctica final.

En resumen, para resolver el problema simple original, además del núcleo del problema en sí, tengo que introducir bucles, pruebas, tamaño del espacio de almacenamiento, indicadores, transformaciones y gestión explícita de archivos gratuitos. espacio. Y este estilo de programación está plagado de oportunidades de error. Gracias a mi larga experiencia, pude evitar errores obvios de uno en uno o errores de configuración de la memoria. También cometí el error típico de principiante cuando me enfrenté a transmisiones de E/S: leer en un carácter (en lugar de un int) y olvidarme de verificar el EOF. En los días previos a la biblioteca estándar C, no es sorprendente que muchos profesores no pudieran deshacerse de estas cosas sin valor y dejarlas a un lado para enseñarlas más tarde. Desafortunadamente, muchos estudiantes también simplemente notan que este estilo inferior es "lo suficientemente bueno" para escribir más rápido que sus hermanos de estilo C. Desarrollan un hábito difícil de romper y dejan un reguero de errores.

El último programa estilo C tiene 41 líneas, mientras que el programa estilo C equivalente tiene sólo 10 líneas. Después de deducir la estructura básica, la proporción es 30:4. Más importante aún, las líneas estilo C no sólo son más cortas, sino que su esencia también es más fácil de entender. La cantidad de líneas y la complejidad conceptual del estilo C y las versiones estilo C es difícil de medir objetivamente, pero creo que la versión estilo C tiene una ventaja de 10:1.

Eficiencia

Para un programa tan trivial como el ejemplo anterior, la eficiencia no es un problema. Cuando nos enfrentamos a este tipo de programas, la simplicidad y la seguridad (de tipo) son los puntos clave. Sin embargo, los sistemas reales a menudo constan de componentes que dan mucha importancia a la eficiencia. Para tales sistemas, la pregunta es "¿Podemos permitir niveles más altos de abstracción?"

Considere este tipo de programa centrado en la eficiencia. He aquí un ejemplo sencillo:

Leer en un número desconocido de elementos

Realizar alguna acción en cada elemento

Realizar alguna acción en todos los elementos

Lo mejor que se me ocurre Un ejemplo simple y claro es un Programa que calcula la media y la mediana de una serie de números de punto flotante de doble precisión desde un dispositivo de entrada. La siguiente es la solución tradicional de estilo C:

// Solución de estilo C:

#includelt; stdlib.hgt; hgt ;

// Una función de comparación que qsort() utilizará más adelante.

int comparar (const void* p, const void* q)

{

registro doble p0 = * (doble* )p;

registrar doble q0 = * (doble*)q;

si (p0 gt; q0) devuelve 1;

si (pO lt; qO) devuelve -1;

devuelve 0;

}

void quit()

{

fprintf(stderr, "memoria agotada \n");

exit(1);

}

int main(int argc, char*argv[])

{

int res = 1000; // Cantidad de configuración inicial

char* file = argv[2];

double* buf= (double* ) malloc(sizeof(double) * res);

if (buf == 0) quit();

doble mediana = 0

doble media = 0;

int n = 0;

ARCHIVO* fin = fopen(archivo, "r"); // Abrir archivo para entrada (lectura)

doble d;

while (fscanf(fin, "lg", amp; d) == 1) {

if(n == res) {

res = res;

buf = (doble*) realloc(buf, sizeof(doble) * res);

if (buf == 0) quit() ;

}

buf[n ] = d;

// Hay una tendencia a errores de redondeo

media = (n== 1 ) ? d: media (d-media)/n;

}

qsort(buf, n, sizeof(double), comparar

if); (n) {

int mid=n/2;

mediana = (n2) ? buf[mid] : (buf[mid-1] buf[mid])/ 2

}

printf( "número de elementos=d, mediana=g, media=g\n",

n, mediana, media);

free(buf);

}

Los siguientes son comunes

Solución C:

// Solución utilizando la biblioteca estándar de C:

#include lt; vectorgt;

#include lt; p>#include lt;algorithmgt;

usando el espacio de nombres std;

main(int argc, char*argv[])

{

char* archivo = argv[2];

vectorlt; buf;

doble mediana = 0; p>

fstream fin(archivo, ios::in);

doble d;

while (fin gt; gt; d) {

buf.push_back(d);

media = (buf.size() == 1)?

d: media (d-media)/buf.size(); /p>

}

sort(buf.begin(), buf.end());

if (buf.size()) {

int mid = buf.size() /2

mediana =

(buf.size() 2) ?

buf[mid] : (buf[mid-1] buf[mid] )/2;

}

cout lt; "número de elementos = " lt;

lt;lt; ", mediana = " lt;lt; mediana lt;lt; ", media = "

lt;lt; media '\n ' ;

}

El tamaño de estos dos programas ya no es tan diferente como el ejemplo anterior (43:24, las líneas en blanco no se cuentan). Excluyendo los mismos elementos que no se pueden eliminar, como la declaración de main () y el cálculo del valor intermedio (*** 13 líneas), la diferencia en el número de líneas entre los dos es 20:11. El bucle crítico "Input and Save" y las acciones de clasificación se acortan significativamente en los programas de estilo C (la diferencia en el número de líneas del bucle "Input and Save" es 9:4, y la diferencia en el número de líneas del bucle la acción de clasificación es 9:1). Es más, en la versión C, la lógica contenida en cada línea es mucho más simple y las posibilidades de hacerlo bien son ciertamente mucho mayores.

Una vez más, la gestión de la memoria se implementa metafóricamente en programas estilo C; cuando se agregan elementos con push_back, el vector crece automáticamente. Los programas estilo C utilizan realloc para la gestión explícita de la memoria. El constructor de vectores y la función push_back que aparecen en los programas de estilo C eliminarán las operaciones malloc y realloc en los programas de estilo C, así como el seguimiento del "tamaño de memoria asignado".

En los programas de estilo C, confío en el manejo de excepciones para registrar el agotamiento de la memoria. En programas de estilo C, pruebo explícitamente para evitar posibles problemas de agotamiento de la memoria.

No sorprende que la versión C sea más fácil de corregir. Generé esta versión estilo C a partir de la versión estilo C cortando y pegando. Olvidé incluir el algoritmo; dejé n fuera y olvidé usar buf.size; además, mi compilador no admite la directiva de uso en local, lo que me obligó a moverlo fuera de main. Después de corregir estos cuatro errores, el programa puede ejecutarse correctamente.

Para un principiante, qsort es muy extraño. ¿Por qué hay que dar el número de elementos? (Porque la matriz no sabe cuántos elementos tiene por sí sola) ¿Por qué tienes que dar el doble de tamaño? (Porque qsort no sabe que está clasificando en dobles). ¿Por qué tienes que escribir esa fea función que compara dobles? (Porque qsort requiere un puntero a una función, ya que no conoce el tipo de elementos que está ordenando). ¿Por qué la función de comparación utilizada por qsort acepta argumentos const void* en lugar de argumentos char*? (Porque qsort puede ordenar valores que no son cadenas) ¿Qué significa void*? ¿Qué significa agregar const delante? (Hmm, volveremos a eso más adelante). Es difícil explicar estos problemas a un principiante sin que se le llenen los ojos de lágrimas. En comparación, es mucho más fácil explicar sort(v.begin(), v.end()): "La clasificación simple (v) es relativamente simple, pero a veces queremos ordenar una determinada parte del contenedor. Entonces, un método más El enfoque general es especificar el rango de operación de clasificación".

Para comparar la eficiencia, primero tuve que decidir cuántas entradas harían que la comparación de eficiencia fuera significativa. Como sólo toma menos de medio segundo usar este programa para 50.000 entradas, elegí comparar 500.000 entradas y 5.000.000 de entradas. Los resultados se muestran en la Tabla 1.

Tabla 1/ Lectura, clasificación y salida de números de punto flotante

Antes y después de la optimización

Relación C C C/C C C Relación C/C

p>

500.000 datos 3,5 6,1 1,74 2,5 5,1 2,04

5.000.000 datos 38,4 172,6 4,49 27,4 126,6 4,62

El número clave es la proporción. Una proporción mayor que 1 indica que la versión estilo C es más rápida. Las comparaciones entre lenguajes, bibliotecas y estilos de programación son notoriamente complicadas, así que no saque conclusiones radicales basadas en estas pruebas simples. Estos ratios son promedios de los resultados de varias ejecuciones en diferentes máquinas. Los diferentes entornos de ejecución del mismo programa difieren en menos del 1 por ciento. También ejecuté una versión estrictamente compatible con ISO C de mi programa estilo C y, como era de esperar, no hubo diferencia de rendimiento.

Esperaba que los programas estilo C fueran un poco más rápidos. Después de examinar diferentes implementaciones del compilador de C, encontré variaciones sorprendentes en los resultados de ejecución. A veces, la versión estilo C supera a la versión estilo C para tamaños de datos pequeños. Sin embargo, el objetivo de este ejemplo es que podemos proporcionar un mayor nivel de abstracción y una mejor protección contra errores que las técnicas conocidas actualmente.

El compilador de C que utilicé era común y barato, no un juguete de laboratorio. Por supuesto, los compiladores que afirman proporcionar una mayor eficiencia también se aplicarán a este resultado.

No es raro encontrar personas que estén dispuestas a pagar una proporción de 3, 10 o incluso 50 por conveniencia y una mejor protección contra errores. Pero cuando sumas esos beneficios y agregas dos o cuatro veces la velocidad, es bastante espectacular y convincente. Estos números deben ser los valores mínimos que un proveedor de biblioteca C esté dispuesto a aceptar. Para comprender dónde se gasta el tiempo, realicé algunas pruebas adicionales (consulte la Tabla 2).

Tabla 2 / Leer números en coma flotante y ordenarlos. Para comprender el costo de las acciones de entrada, agregué una función "generar" para generar valores aleatorios.

500.000 datos:

Antes de la optimización Después de la optimización

Relación C C C/C C C Relación C/C

Leer datos leer 2,1 2,8 1,33 2,0 2.8 1.4

Generar datos generar 0.6 0.3 0.5 0.4 0.3 0.75

Leer y ordenar leer amp; ordenar 3.5 6.1 1.75 2.5 5.1 2.04

Generar y ordenar generar & sort 2,0 3,5 1,75 0,9 2,6 2,89

5.000.000 de datos:

Antes y después de la optimización

C C C/C ratio C C C/C

Leer datos leer 21,5 29,1 1,35 21,3 28,6 1,34

Generar datos generar 7,2 4,1 0,57 5,2 3,6 0,69

Leer y ordenar leer y ordenar 38,4 172,6 4,49 27,4 126,6 4,62

Generar y ordenar generar y ordenar 24.4 147.1 6.03 11.3 100.6 8.9

Por supuesto, "leer" es solo leer datos, "leer y ordenar" simplemente lee los datos y los clasifica, ninguno de ellos produce ningún producción. Para tener una mejor idea de los costos de entrada, se utiliza "generar" para generar valores aleatorios en lugar de leer datos del dispositivo de entrada.

En otros ejemplos y con otros compiladores, esperaría que la E/S de flujo C fuera ligeramente más lenta que stdio. De hecho, este es el caso cuando una versión anterior de este programa usaba cin en lugar de file stream. En algunos compiladores de C, la E/S de archivos es mucho más rápida que cin, al menos en parte debido al mal manejo de la conexión entre cin y cout. Sin embargo, los números anteriores muestran que las E/S estilo C pueden ser tan eficientes como las E/S estilo C.

Cambiar estos programas para que lean y ordenen números enteros en lugar de números de punto flotante no cambia la eficiencia relativa, aunque nos sorprenderá descubrir que este cambio no funciona bien para programas de estilo C. En realidad, es muy simple (solo dos cambios, los programas estilo C requieren 12 cambios). Esto es un buen augurio para la facilidad de mantenimiento. La diferencia que presenta la prueba "generar" muestra el costo de la configuración. Un vector más push_back debería ser tan rápido como una matriz más malloc/free, pero no lo es. La razón de esto es que es difícil eliminar llamadas a "inicializadores que no hacen nada" durante el proceso de optimización. Afortunadamente, el costo de la configuración casi siempre es eclipsado por el costo de los insumos que crean los requisitos de configuración. En cuanto a ordenar, como se esperaba, es mucho más rápido que qsort. La razón principal es que la acción de comparación en ordenar es una expansión en línea, mientras que qsort debe llamar a una determinada función.

Es realmente difícil elegir un ejemplo que pueda ilustrar bien la cuestión de la eficiencia. La opinión que recibí de mis colegas es que leer y comparar "valores numéricos" no es lo suficientemente realista, sino que se deben leer y ordenar "cadenas". Entonces escribí el siguiente programa:

#includelt;vectorgt;

#includelt;fstreamgt;

#includelt;algorithmgt;

#includelt; stringgt;

usando el espacio de nombres std;

int main(int argc, char* argv[])

{

char* file = argv[2]; //El nombre del archivo de entrada

char* ofile = argv[3] //El nombre del archivo de salida

vectorlt; stringgt;

fstream fin (archivo, ios::in

cadena d;

buf.push_back (d);

sort(buf.begin(), buf.end());

fstream fout (ofile, ios: out);

copy(buf.begin(), buf.end(),

ostream_iteratorlt; stringgt; (fout, "\n")); p>}

Lo reescribí en formato C e intenté optimizar la lectura de caracteres. La versión de estilo C funciona muy bien, incluso frente a una versión de estilo C optimizada y ajustada manualmente que elimina la copia de cadenas. Para pequeñas cantidades de resultados, no hay una diferencia significativa, pero para grandes cantidades de datos, sort nuevamente supera a qsort debido a sus mejores líneas integradas, consulte la Tabla 3.

Tabla 3/ Lectura, clasificación y salida de cadenas

C C C/C

Relación C, eliminar

Acción de copia de cadena optimizada

Relación C/C

500.000 datos 8,4 9,5 1,13 8,3 0,99

2.000.000 datos 37,4 81,3 2,17 76,1 2,03

I Usé 2 millones de cadenas porque no tenía suficiente memoria principal para contener 5 millones de cadenas sin causar paginación.

Para saber dónde se gasta el tiempo, también ejecuté el programa omitiendo deliberadamente la clasificación (consulte la Tabla 4). Las cadenas que preparé eran relativamente cortas (un promedio de siete caracteres).

Tabla 4/Lectura y salida de cadena: omitiendo deliberadamente ordenar

C C C/C

Relación C, eliminar

cadena

Relación C/C después de la optimización de la acción de copia

500.000 datos 2,5 3,0 1,2 2 0,8

2.000.000 datos 9,8 12,6 1,29 8,9 0,91

Tenga en cuenta que string es un tipo perfecto definido por el usuario y es solo parte de la biblioteca estándar. Si podemos ganar eficiencia y elegancia usando cadenas, también podemos ganar eficiencia y elegancia usando otros tipos definidos por el usuario.

¿Por qué debería hablar de eficiencia en un artículo sobre estilo de programación y enseñanza? Porque el estilo de programación y las técnicas que enseñamos deben servir a problemas del mundo real. C fue creado para su uso en sistemas a gran escala y sistemas donde la eficiencia está estrictamente regulada. Así que creo que sería escandaloso si un cierto enfoque de la educación en C llevara a la gente a utilizar estilos y técnicas de programación que sólo fueran efectivos en programas de juguetes, y que frustrarían a la gente y los abandonarían. Los resultados de las mediciones anteriores muestran que si su estilo C se basa en gran medida en programación genérica y tipos concretos para proporcionar un código más simple y con mayor "seguridad de tipos", su eficiencia puede ser tan buena como El estilo C tradicional tiene sus ventajas y desventajas. Se pueden obtener resultados similares en estilos orientados a objetos.

Existen diferencias dramáticas en el rendimiento de las diferentes implementaciones de bibliotecas estándar, lo cual es un tema importante. Para un programador que decide depender en gran medida de bibliotecas estándar (o bibliotecas populares no estándar), es importante que el estilo de programación que adopte sea al menos aceptable en diferentes sistemas. Me horroricé al descubrir que mi programa de prueba era dos veces más rápido que el estilo C en un sistema, pero sólo la mitad de rápido en otro sistema. Si el factor de variación entre sistemas supera 4, los programadores no deberían aceptarlo. Hasta donde puedo decir, esta variabilidad no se debe a factores fundamentales, por lo que se debe lograr coherencia en la eficiencia sin esfuerzos demasiado exagerados por parte del implementador de la biblioteca. Usar una biblioteca mejor optimizada es probablemente la forma más sencilla de mejorar la percepción y el rendimiento real del Estándar C. Sí, los implementadores de compiladores trabajan duro para suavizar las pequeñas diferencias de eficiencia entre los compiladores. Sospecho que en términos de eficiencia, los implementadores de la biblioteca estándar tienen el mayor impacto;

Obviamente, la simplificación lógica y de programación que ofrece la solución de estilo C anterior en comparación con la solución de estilo C se puede lograr a través de la biblioteca estándar de C. ¿Es esta comparación poco realista o injusta? No me parece. Una de las características clave de C es que su soporte de biblioteca es sofisticado y eficiente.

Las ventajas demostradas por los sencillos programas descritos anteriormente se pueden mantener en cualquier campo de aplicación, siempre que exista una biblioteca de programas sofisticada y eficiente. El desafío para la comunidad C es expandir el campo para que los programadores comunes puedan disfrutar de estos beneficios. En otras palabras, debemos diseñar e implementar bibliotecas sofisticadas y eficientes para más áreas de aplicación, y hacer que estas bibliotecas se utilicen ampliamente.

Aprendizaje de C

Ni siquiera un programador profesional puede aprender el lenguaje completo desde el principio antes de empezar a utilizarlo. Los lenguajes de programación deben aprenderse por partes, mediante pequeños ejercicios para probar sus funciones. Por eso siempre aprendemos un idioma con un enfoque de dominio gradual. La verdadera pregunta no es "¿Debería aprender primero parte del idioma?" sino "¿Qué parte del idioma debería aprender primero?"

La respuesta tradicional a esta pregunta es "Aprender primero un subconjunto de C". que sea compatible con C". Pero desde el punto de vista de lo que he pensado, esa no es una buena respuesta. Este tipo de aprendizaje puede llevar a centrarse prematuramente en detalles de bajo nivel. También oscurece las cuestiones de estilo y diseño de programación al obligar a los estudiantes a enfrentar muchas dificultades técnicas prematuramente, suprimiendo así muchas cosas interesantes. Dos ejemplos anteriores en este artículo ilustran este punto. C tiene mejor soporte de biblioteca, mejor notación y mejor verificación de tipos, lo que sin duda es un voto en contra del enfoque "C primero". Sin embargo, tenga en cuenta que no pretendo ser "puro