1. Programación procedimental

1.1. Entrada y salida

1.1.1. Formatear la fecha

Programa que lee una fecha en formato YYYY/MM/DD y la imprime con ceros a la izquierda. El programa presenta un mensaje de error si la fecha ingresada no cumple este formato.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <stdio.h>

int main(void)
{
	int day = 20, month = 8;
	unsigned long long int year = 2019;
	if ( scanf("%04llu/%02d/%02d", &year, &month, &day) == 3 )
		printf("Date: %04llu/%02d/%02d\n", year, month, day);
	else
		fprintf(stderr, "invalid data\n");
	return 0;
}

Copie el código anterior en un archivo format_date.c y para compilar, puede usar este comando:

cc -g -Wall -Wextra format_date.c -o format_date

1.2. Expresiones y condicionales

Con esta sección se inicia el proceso de resolución de problemas, que consta de:

Análisis. Comprender el problema. Se recomienda rotular cada trozo de la entrada y salida con los nombres con que el cliente los llama. Como prueba, el estudiante debe estar seguro de que comprende qué debe hacer el programa y saber qué es cada trozo de información de la entrada y de la salida.

Diseño. Resolver el problema. Se recomienda al estudiante resolver el problema, traduciendo de la entrada a la salida, sin pensar en computadoras. Debe usar sus habilidades humanas y matemáticas. No se puede instruir una computadora si no se sabe resolver el problema como humano. Luego de resolver el problema como humano, escribir un diseño siguiendo las convenciones del paradigma de programación usado. Para el paradigma de programación procedimental consiste en escribir un algoritmo usando los ocho tipos de instrucciones:

  1. Leer un valor(es)

  2. Imprimir un valor(es)

  3. Crear una variable con un valor

  4. Cambiarle el valor a una variable

  5. Condicionar una instrucción

  6. Repetir una instrucción

  7. Hacer una subtarea

  8. Definir una subtarea

La fase de diseño es la más difícil del proceso, y la que más caracteriza al trabajo ingenieril. Una vez elaborado el algoritmo, se debe probar. El estudiante puede ejecutar el algoritmo paso a paso al pie de la letra, anotando en una tabla los valores de las variables, marcando con un cursor la entrada, y escribiendo los resultados en un texto de salida. Cuando el algoritmo resuelva el problema se pasa a la siguiente fase.

Implementación. Consiste en traducir instrucción por instrucción a un lenguaje de programación que se apegue al paradigma. Requiere mucho dominio del lenguaje de programación, y se considera la fase más fácil del proceso, a veces conocida como "codificación". Cuando el programa está implementado, se prueba contra usando algún mecanismo de pruebas de software (testing). En este curso se usa pruebas de caja negra apoyados por un juez automático en línea. El siguiente ejemplo recorre todas las fases del proceso descrito.

1.2.1. Eliminar la nota más baja

Enunciado del problema

Un coordinador de cátedra tiene a cargo varios grupos de un curso universitario. Al finalizar el ciclo lectivo, el coordinador ha recibido apelaciones de algunos estudiantes indicando que su profesor les ha asignado una nota injusta simplemente porque los "tienen entre ojos". El coordinador ha solicitado una copia del registro de notas a todos los profesores de su cátedra y debe verificar si la queja planteada por los estudiantes es veraz. Dado que el curso es colegiado, todos los profesores deben aplicar la misma evaluación, desglosada en la siguiente forma:

% Rubro

30%

10 laboratorios

20%

10 quices

50%

3 exámenes

Dado que un alumno podría ausentarse por una situación fortuita, la carta al estudiante especifica que se eliminará la nota más baja de cada estudiante en los laboratorios, y su nota más baja en los quices. Algunas quejas de los estudiantes se han hecho en referencia a este derecho, argumentando que su profesor eliminó la asignación con menor promedio general a todos los estudiantes y no a cada uno por aparte, o bien, no hizo la eliminación del todo para ciertos estudiantes.

Dado que son cerca de medio millar de estudiantes, el coordinador del curso agradecería un programa de computadora que lea los registros de notas y ayude a identificar si los profesores han aplicado correctamente la evaluación. En caso de existir error, el programa sería muy útil si pudiese indicar el grupo, el carné del estudiante, la nota que asignó el profesor y la nota correcta, con el fin de identificar los casos conflictivos y tomar las medidas del caso.

La entrada consta de un número entero en la primera fila que indica la cantidad de grupos que el coordinador tiene a cargo. Para cada grupo se especifican dos enteros: el número de grupo (que no necesariamente es secuencial, ya que a veces se cierran o abren grupos de acuerdo a la oferta y demanda de matrícula) y la cantidad de estudiantes matriculados en el grupo. Por cada estudiante hay una línea en el registro. La línea de un estudiante consta de su carné, las 10 notas de laboratorio, las 10 notas de los quices, las tres notas de los exámenes, y el promedio final del estudiante reportado por el profesor. Todas las notas están en base 100.

Entrada de ejemplo:

2

1 3
B45781  90 18 56 89 20 100 75 84 90 97   89 90 100 90 95 45 10 100 85 100   90  75 82   82.18
B18019  28 19 67 21  0 29  45 87 66 27   59 50  64 20 27  4 91 37  57  71   91  79 34   55.27
B09218  58 10 84 68 89 73  13 99 23 100  82 34  92 74  1 37 63 77  93   3   63  95 53   67.73

2 2                      
B50908  37 100 90 21 88 88 39 16 58  1   82  0  78 86  0 37 22  80 65  28   78  98 74   67.37
B68901  98  17 43  1 13 95 47 97 73 13   71 50  79 94 86 90 29  65 77  32   57 100 93   70.04

 

Salida de ejemplo:

Grupo 1:
B18019 55.27 57.54

Grupo 2:
B50908 67.37 70.19
B68901 70.04 72.51

  Como salida, se quiere que el programa imprima una línea encabezado indicando el número de grupo seguida de dos puntos. Luego cada uno de los estudiantes para los cuales el profesor reportó una nota incorrecta. Para cada estudiante se imprime su carné, la nota incorrecta que el profesor le asignó, y la nota correcta tras aplicar la evaluación oficial del curso. En caso de que el profesor haya reportado la nota correcta para todos los estudiantes de un grupo, sólo el encabezado del mismo se imprimirá.

Para los cálculos de las notas de los estudiantes, todas las evaluaciones del mismo tipo tienen el mismo peso. Por ejemplo, el quiz 1 y el quiz 7 tienen el mismo peso en la nota; el laboratorio 4 tiene el mismo peso que los demás laboratorios; y así para todos los tres tipos de evaluaciones.

Solución del Grupo 01

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#include <float.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>

// Count of labs evaluated in the course
#define LAB_COUNT 10
// Count of quizzes evaluated in the course
#define QUIZ_COUNT 10
// Count of exams evaluated in the course
#define EXAM_COUNT 3

// Function/Subroutine declaration
void process_group(void);
void calculate_print_wrong_grades(void);
void process_student(void);
double calculate_grade_average(const int grade_count, const bool should_remove_lowest_grade);
double aproximate_grade(const double grade);

// main:
int main()
{
    // Read group count
    int group_count = 0;
    // Repeat group index for each group
    if ( scanf("%d", &group_count) == 1 && group_count > 0 )
    {
        for ( int group_index = 0; group_index < group_count; ++group_index )
        {
            // Process group
            process_group();
            // If group index is not the last group
            if ( group_index < group_count - 1 )
                // Print empty line
                putchar('\n');
        }
    }
    else
        fprintf(stdout, "invalid data\n");

    return 0;
}

// Process group:
void process_group(void) // Subroutine definition
{
    // Read group number
    int group_number = 0;
    scanf("%d", &group_number);
    // Print group header, e.g: "Group 2:"
    printf("Grupo %d:\n", group_number);
    // Calculate and print wrong grades
    calculate_print_wrong_grades();
}

// Calculate and print wrong grades:
void calculate_print_wrong_grades(void)
{
    // Read student count
    int student_count = 0;
    scanf("%d", &student_count);
    // Repeat for each student
    for ( int student_index = 0; student_index < student_count; ++student_index )
    {
        // Process student
        process_student();
    }
}

// Process student:
void process_student(void)
{
    // Read student id
    char student_id[6 + 1];
    scanf("%6s", student_id);
    // Create lab average as the result of
        // Calculate average of 10 grades removing lowest grade
    const double lab_average = calculate_grade_average(LAB_COUNT, true);
    // Create quiz average as the result of
        // Calculate average of 10 grades removing lowest grade
    const double quiz_average = calculate_grade_average(QUIZ_COUNT, true);
    // Create exam average as the result of
        // Calculate average of 3 grades without removing lowest grade
    const double exam_average = calculate_grade_average(EXAM_COUNT, false);
    // Create expected student grade as
        // 0.3 * lab average + 0.2 * quiz average + .5 * exam average
    const double expected_student_grade = 0.3 * lab_average + 0.2 * quiz_average + .5 * exam_average;
    // Read professor student grade
    double professor_student_grade = 0.0;
    scanf("%lf", &professor_student_grade);
    // If expected student grade is different than professor student grade
    if ( aproximate_grade(expected_student_grade) != aproximate_grade(professor_student_grade) )
        // Print student id, professor student grade, expected student grade
        printf("%s %.2lf %.2lf\n", student_id, professor_student_grade, expected_student_grade);
}

// Calculate average of <grade count> grades <should remove lowest grade>:
double calculate_grade_average(int grade_count, const bool should_remove_lowest_grade)
{
    // Create sum as 0
    double sum = 0.0;
    // Create lowest grade as infinite
    double lowest_grade = DBL_MAX;

    // Repeat grade index in grade count
    for ( int grade_index = 0; grade_index < grade_count; ++grade_index )
    {
        // Read grade
        double grade = 0.0;
        scanf("%lf", &grade);
        // Set sum as sum + grade
        sum += grade;
        // If grade is less than lowest grade
        if ( grade < lowest_grade )
            // Set lowest grade to grade
            lowest_grade = grade;
    }

    // If should remove lowest grade
    if ( should_remove_lowest_grade )
    {
        // Set sum as sum - lowest grade
        sum -= lowest_grade;
        // Set grade count as grade count - 1
        --grade_count;
    }

    // Return sum / grade count
    return sum / grade_count;
}

double aproximate_grade(const double grade)
{
    return round(100.0 * grade) / 100.0;
}

1.3. Repetición: ciclos

Se realizó una clase magistral, con ejemplos teóricos elaborados en la pizarra.

1.4. Subrutinas

1.4.1. Desigualdad triangular

Enunciado del problema

Sus programas para ayudar a estudiantes de secundaria a revisar sus tareas de matemática se están haciendo populares, y usted está empezando a pensar en una aplicación para móviles. El próximo reto está en la geometría.

El profesor les da las tres medidas de los lados de un triángulo a los estudiantes y ellos deben determinar si esas medidas determinan un triángulo o no, el perímetro y el área del triángulo, su clasificación de acuerdo a las dimensiones de los lados (equilátero, isósceles, escaleno), y su clasificación de acuerdo a las dimensiones de los ángulos (rectángulo, acutángulo y obtusángulo). El programa debe realizar esas clasificaciones, como ocurre en los siguientes ejemplos:   Entrada de ejemplo:

5 5 5
3 4 5
1 2 3
1 2 2.9
4 5 -3
3 3 2

Salida de ejemplo:

a=5 b=5 c=5 P=15 A=10.8253 equilatero acutangulo
a=3 b=4 c=5 P=12 A=6 escaleno rectangulo
a=1 b=2 c=3 no es un triangulo
a=1 b=2 c=2.9 P=5.9 A=0.522727 escaleno obtusangulo
a=4 b=5 c=-3 datos no validos
a=3 b=3 c=2 P=8 A=2.82843 isosceles acutangulo

El profesor distribuyó la siguiente teoría a los estudiantes. Se puede saber si tres lados a, b, y c, todos positivos, determinan un triángulo si cumplen el principio de desigualdad triangular, el cual establece que un lado siempre debe medir menos que la suma de los otros dos. Este principio se puede resumir de tal forma que tres lados determinan un triángulo si cumplen:   \$a < b + c\$

\$a > |b - c|\$

El perímetro del triángulo se calcula como la suma de sus tres lados a, b, y c. El área del triángulo A se puede calcular a partir de su semiperímetro s con la fórmula de Herón:

\$s=\frac{a+b+c}{2}\$

\$A = \sqrt{s\left(s-a\right)\left(s-b\right)\left(s-c\right)}\$

Un triángulo es equilátero si todos sus lados miden lo mismo; isósceles si únicamente dos de sus lados miden lo mismo; y escaleno, si todos sus lados miden distinto.

Un triángulo es rectángulo si tiene un ángulo de 90°, obtusángulo si tiene un ángulo mayor a 90°, y acutángulo si todos sus ángulos son menores de 90°. Es fácil determinar si un triángulo es rectángulo con el teorema de Pitágoras. Para los otros dos se puede aplicar el mismo teorema recordando que las medidas de los ángulos son proporcionales a las medidas de los lados opuestos.

Solución del Grupo 01

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
#include <assert.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>

typedef enum
{
	EQUILATERAL_TRIANGLE,
	ISOSCELES_TRIANGLE,
	SCALENE_TRIANGLE,
} side_classification_t;

typedef enum
{
	ACUTE_TRIANGLE,
	RIGHT_TRIANGLE,
	OBTUSE_TRIANGLE,
} angle_classification_t;

static const char* const angle_texts[] =
{
	"acutangulo",
	"rectangulo",
	"obtusangulo",
};

// Function declarations
/**
	@brief Checks that the three sides are positive

	@details ...
	...
	@code
		are_sides_valid(1.0, 0.0, 2.0) == false
	@endcode
	more details...

	@param side1 Magnitude of a side of the triangle
	@param side2 Magnitude of another side of the triangle
	@param side3 Magnitude of last side of the triangle
	@return true if the three sides are positive
*/
bool are_sides_valid(const double side1, const double side2, const double side3);

bool do_sides_form_triangle(const double side1, const double side2, const double side3);
void classify_triangle(const double side1, const double side2, const double side3);
double calculate_perimeter(const double side1, const double side2, const double side3);
double calculate_area(const double side1, const double side2, const double side3);
double calculate_semiperimeter(const double side1, const double side2, const double side3);
side_classification_t classify_triangle_by_sides(const double side1, const double side2, const double side3);
angle_classification_t classify_triangle_by_angles(const double side1, const double side2, const double side3);
void find_hypotenuse_and_catheti(const double side1, const double side2, const double side3
	, double* hypotenuse, double* cathetus1, double* cathetus2);
void swap(double* value1, double* value2);
void print_side_classification(side_classification_t side_classification);
void print_angle_classification(angle_classification_t angle_classification);


// main:
int main(void)
{
	// Repeat while there are sides
	// Read side1, side2, side3
	double side1 = 0.0, side2 = 0.0, side3 = 0.0;
	while ( scanf("%lf %lf %lf\n", &side1, &side2, &side3) == 3 )
	{
		// Print side1, side2, side3
		printf("a=%lg b=%lg c=%lg ", side1, side2, side3);
		// If sides are valid then
		if ( are_sides_valid(side1, side2, side3) )
		{
			// If sides form a triangle
			if ( do_sides_form_triangle(side1, side2, side3) )
			{
				// Classify triangle
				classify_triangle(side1, side2, side3);
			}
			// Else
			else
			{
				// Print "no es un triangulo"
				fprintf(stdout, "no es un triangulo\n");
			}
		}
		// Else
		else
		{
			// Print "datos no validos"
			fprintf(stdout, "datos no validos\n");
		}
	}

	return 0;
}

// Are sides valid:
bool are_sides_valid(const double side1, const double side2, const double side3)
{
	// If side1 > 0 and side2 > 0 and side3 > 0
	if ( side1 > 0 && side2 > 0 && side3 > 0 )
	{
		// Return true
		return true;
	}
	// Else
	else
	{
		// Return false
		return false;
	}
}

// Do sides form a triangle:
bool do_sides_form_triangle(const double side1, const double side2, const double side3)
{
	// Return side1 < side2 + side3 and side1 > |side2 - side3|
	return side1 < side2 + side3 && side1 > fabs(side2 - side3);
}

// Classify triangle:
void classify_triangle(const double side1, const double side2, const double side3)
{
	// Calculate and print perimeter
	printf("P=%lg ", calculate_perimeter(side1, side2, side3) );
	// Calculate and print area
	printf("A=%lg ", calculate_area(side1, side2, side3) );
	// Create side classification with the result of classify triangle per sides
	side_classification_t side_classification = classify_triangle_by_sides(side1, side2, side3);
	// Print side classification
	print_side_classification(side_classification);
	putchar(' ');
	// Create angle classification with the result of classify triangle per angles
	angle_classification_t angle_classification = classify_triangle_by_angles(side1, side2, side3);
	// Print angle classification
	print_angle_classification(angle_classification);
	putchar('\n');
}

// Calculate perimeter:
double calculate_perimeter(const double side1, const double side2, const double side3)
{
	// return side1 + side2 + side3
	return side1 + side2 + side3;
}

// Calculate area:
double calculate_area(const double side1, const double side2, const double side3)
{
	// Create semiperimeter as result of calculate semiperimeter
	double semiperimeter = calculate_semiperimeter(side1, side2, side3);
	// Return area using the Heron formula and the semiperimeter
	return sqrt( semiperimeter * (semiperimeter - side1) * (semiperimeter - side2) * (semiperimeter - side3));
}

// Calculate semiperimeter:
double calculate_semiperimeter(const double side1, const double side2, const double side3)
{
	// Return the result of calculate the perimeter and divide by 2
	return calculate_perimeter(side1, side2, side3) / 2.0;
}

// Classify triangle per sides:
side_classification_t classify_triangle_by_sides(const double side1, const double side2, const double side3)
{
	// If side1 = side2 = side3 then
	if ( side1 == side2 && side2 == side3 )
	{
		// Return equilateral triangle
		return EQUILATERAL_TRIANGLE;
	}
	// Else if side1 ≠ side2 and side1 ≠ side3 and side2 ≠ side3 then
	if ( side1 != side2 && side1 != side3 && side2 != side3 )
	{
		// Return scalene triangle
		return SCALENE_TRIANGLE;
	}
	// Else
	else
	{
		// Return isosceles triangle
		return ISOSCELES_TRIANGLE;
	}
}

// Classify triangle per angles
angle_classification_t classify_triangle_by_angles(const double side1, const double side2, const double side3)
{
	// Find hypotenuse, cathetus1 and cathetus2
	double hypotenuse = 0.0, cathetus1 = 0.0, cathetus2 = 0.0;
	find_hypotenuse_and_catheti(side1, side2, side3, &hypotenuse, &cathetus1, &cathetus2);
	// If hypotenuse^2 = cathetus1^2 + cathetus2^2 then
	if ( hypotenuse * hypotenuse == cathetus1 * cathetus1 + cathetus2 * cathetus2 )
	{
		// Return right triangle
		return RIGHT_TRIANGLE;
	}
	// Else if hypotenuse^2 < cathetus1^2 + cathetus2^2 then
	else if ( hypotenuse * hypotenuse < cathetus1 * cathetus1 + cathetus2 * cathetus2 )
	{
		// Return acute triangle
		return ACUTE_TRIANGLE;
	}
	// Else
	else
	{
		// Return oblique triangle
		return OBTUSE_TRIANGLE;
	}
}

// Find hypotenuse, cathetus1 and cathetus2 (as reference values):
void find_hypotenuse_and_catheti(const double side1, const double side2, const double side3
	, double* hypotenuse, double* cathetus1, double* cathetus2)
{
	assert(hypotenuse);
	assert(cathetus1);
	assert(cathetus2);

	// Create hypotenuse with value side1
	*hypotenuse = side1;
	// Create cathetus1 with value side2
	*cathetus1 = side2;
	// Create cathetus2 with value side3
	*cathetus2 = side3;
	// If cathetus1 > hypotenuse then
	if ( *cathetus1 > *hypotenuse )
	{
		// Swap cathetus1 and hypotenuse
		swap(cathetus1, hypotenuse);
	}
	// If cathetus2 > hypotenuse then
	if ( *cathetus2 > *hypotenuse )
	{
		// Swap cathetus2 and hypotenuse
		swap(cathetus2, hypotenuse);
	}
}

void swap(double* value1, double* value2)
{
	assert(value1);
	assert(value2);

	double temp = *value1;
	*value1 = *value2;
	*value2 = temp;
}

void print_side_classification(side_classification_t side_classification)
{
	switch ( side_classification )
	{
		case EQUILATERAL_TRIANGLE: printf("equilatero"); break;
		case ISOSCELES_TRIANGLE: printf("isosceles"); break;
		case SCALENE_TRIANGLE: printf("escaleno"); break;

		default: assert(false); break;
	}
}

void print_angle_classification(angle_classification_t angle_classification)
{
	//assert(...);
	printf("%s", angle_texts[angle_classification]);
}

1.4.2. Bibliotecas estáticas

Los siguientes comandos resumen la forma de crear y compilar con bibliotecas estáticas y dinámicas de software

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# Static library
gcc -c -Wall -Wextra -std=c11 lib_file1.c -o lib_file1.o
gcc -c -Wall -Wextra -std=c11 lib_fileN.c -o lib_fileN.o
ar rs mylib.a lib_file1.o lib_fileN.o

# Static linking
gcc -c -Wall -Wextra -std=c11 main.c -o main.o
gcc    -Wall -Wextra -std=c11 main.o mylib.a -o myapp


# Dynamic library
gcc -c -fPIC -Wall -Wextra -std=c11 lib_file1.c -o lib_file1.o
gcc -c -fPIC -Wall -Wextra -std=c11 lib_fileN.c -o lib_fileN.o
gcc -shared lib_file1.o lib_fileN.o -o libmylib.so

# Dynamic linking
gcc -c -Wall -Wextra -std=c11 main.c -o main.o
gcc    -Wall -Wextra -std=c11 main.o libmylib.so -o myapp # or
gcc    -Wall -Wextra -std=c11 main.o -lmylib     -o myapp

Se agrega a la solución del problema de Desigualdad triangular un programa de pruebas unitarias (caja blanca) y una biblioteca estática (.a).

1.4.3. Desigualdad triangular con biblioteca estática

main.c:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <assert.h>
#include <stdio.h>

#include "triangle.h"

void classify_triangle(const double side1, const double side2, const double side3);
void print_side_classification(side_classification_t side_classification);
void print_angle_classification(angle_classification_t angle_classification);

// main:
int main(void)
{
	// Repeat while there are sides
	// Read side1, side2, side3
	double side1 = 0.0, side2 = 0.0, side3 = 0.0;
	while ( scanf("%lf %lf %lf\n", &side1, &side2, &side3) == 3 )
	{
		// Print side1, side2, side3
		printf("a=%lg b=%lg c=%lg ", side1, side2, side3);
		// If sides are valid then
		if ( are_sides_valid(side1, side2, side3) )
		{
			// If sides form a triangle
			if ( do_sides_form_triangle(side1, side2, side3) )
			{
				// Classify triangle
				classify_triangle(side1, side2, side3);
			}
			// Else
			else
			{
				// Print "no es un triangulo"
				fprintf(stdout, "no es un triangulo\n");
			}
		}
		// Else
		else
		{
			// Print "datos no validos"
			fprintf(stdout, "datos no validos\n");
		}
	}

	return 0;
}

// Classify triangle:
void classify_triangle(const double side1, const double side2, const double side3)
{
	// Calculate and print perimeter
	printf("P=%lg ", calculate_perimeter(side1, side2, side3) );
	// Calculate and print area
	printf("A=%lg ", calculate_area(side1, side2, side3) );
	// Create side classification with the result of classify triangle per sides
	side_classification_t side_classification = classify_triangle_by_sides(side1, side2, side3);
	// Print side classification
	print_side_classification(side_classification);
	putchar(' ');
	// Create angle classification with the result of classify triangle per angles
	angle_classification_t angle_classification = classify_triangle_by_angles(side1, side2, side3);
	// Print angle classification
	print_angle_classification(angle_classification);
	putchar('\n');
}

void print_side_classification(side_classification_t side_classification)
{
	switch ( side_classification )
	{
		case EQUILATERAL_TRIANGLE: printf("equilatero"); break;
		case ISOSCELES_TRIANGLE: printf("isosceles"); break;
		case SCALENE_TRIANGLE: printf("escaleno"); break;

		default: assert(false); break;
	}
}

void print_angle_classification(angle_classification_t angle_classification)
{
	//assert(...);
	printf("%s", angle_texts[angle_classification]);
}
Biblioteca de triángulos

triangle.h:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#ifndef TRIANGLE_H
#define TRIANGLE_H

#include <stdbool.h>

typedef enum
{
	EQUILATERAL_TRIANGLE,
	ISOSCELES_TRIANGLE,
	SCALENE_TRIANGLE,
} side_classification_t;

typedef enum
{
	ACUTE_TRIANGLE,
	RIGHT_TRIANGLE,
	OBTUSE_TRIANGLE,
} angle_classification_t;

static const char* const angle_texts[] =
{
	"acutangulo",
	"rectangulo",
	"obtusangulo",
};

// Function declarations
/**
	@brief Checks that the three sides are positive

	@details ...
	...
	@code
		are_sides_valid(1.0, 0.0, 2.0) == false
	@endcode
	more details...

	@param side1 Magnitude of a side of the triangle
	@param side2 Magnitude of another side of the triangle
	@param side3 Magnitude of last side of the triangle
	@return true if the three sides are positive
*/
bool are_sides_valid(const double side1, const double side2, const double side3);

bool do_sides_form_triangle(const double side1, const double side2, const double side3);
double calculate_perimeter(const double side1, const double side2, const double side3);
double calculate_area(const double side1, const double side2, const double side3);
double calculate_semiperimeter(const double side1, const double side2, const double side3);
side_classification_t classify_triangle_by_sides(const double side1, const double side2, const double side3);
angle_classification_t classify_triangle_by_angles(const double side1, const double side2, const double side3);
void find_hypotenuse_and_catheti(const double side1, const double side2, const double side3
	, double* hypotenuse, double* cathetus1, double* cathetus2);

#endif

triangle.c:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#include <assert.h>
#include <math.h>

#include "triangle.h"

// Declarations in .c are private
void swap(double* value1, double* value2);


// Are sides valid:
bool are_sides_valid(const double side1, const double side2, const double side3)
{
	// If side1 > 0 and side2 > 0 and side3 > 0
	if ( side1 > 0 && side2 > 0 && side3 > 0 )
	{
		// Return true
		return true;
	}
	// Else
	else
	{
		// Return false
		return false;
	}
}

// Do sides form a triangle:
bool do_sides_form_triangle(const double side1, const double side2, const double side3)
{
	// Return side1 < side2 + side3 and side1 > |side2 - side3|
	return side1 < side2 + side3 && side1 > fabs(side2 - side3);
}

// Calculate perimeter:
double calculate_perimeter(const double side1, const double side2, const double side3)
{
	// return side1 + side2 + side3
	return side1 + side2 + side3;
}

// Calculate area:
double calculate_area(const double side1, const double side2, const double side3)
{
	// Create semiperimeter as result of calculate semiperimeter
	double semiperimeter = calculate_semiperimeter(side1, side2, side3);
	// Return area using the Heron formula and the semiperimeter
	return sqrt( semiperimeter * (semiperimeter - side1) * (semiperimeter - side2) * (semiperimeter - side3));
}

// Calculate semiperimeter:
double calculate_semiperimeter(const double side1, const double side2, const double side3)
{
	// Return the result of calculate the perimeter and divide by 2
	return calculate_perimeter(side1, side2, side3) / 2.0;
}

// Classify triangle per sides:
side_classification_t classify_triangle_by_sides(const double side1, const double side2, const double side3)
{
	// If side1 = side2 = side3 then
	if ( side1 == side2 && side2 == side3 )
	{
		// Return equilateral triangle
		return EQUILATERAL_TRIANGLE;
	}
	// Else if side1 ≠ side2 and side1 ≠ side3 and side2 ≠ side3 then
	if ( side1 != side2 && side1 != side3 && side2 != side3 )
	{
		// Return scalene triangle
		return SCALENE_TRIANGLE;
	}
	// Else
	else
	{
		// Return isosceles triangle
		return ISOSCELES_TRIANGLE;
	}
}

// Classify triangle per angles
angle_classification_t classify_triangle_by_angles(const double side1, const double side2, const double side3)
{
	// Find hypotenuse, cathetus1 and cathetus2
	double hypotenuse = 0.0, cathetus1 = 0.0, cathetus2 = 0.0;
	find_hypotenuse_and_catheti(side1, side2, side3, &hypotenuse, &cathetus1, &cathetus2);
	// If hypotenuse^2 = cathetus1^2 + cathetus2^2 then
	if ( hypotenuse * hypotenuse == cathetus1 * cathetus1 + cathetus2 * cathetus2 )
	{
		// Return right triangle
		return RIGHT_TRIANGLE;
	}
	// Else if hypotenuse^2 < cathetus1^2 + cathetus2^2 then
	else if ( hypotenuse * hypotenuse < cathetus1 * cathetus1 + cathetus2 * cathetus2 )
	{
		// Return acute triangle
		return ACUTE_TRIANGLE;
	}
	// Else
	else
	{
		// Return oblique triangle
		return OBTUSE_TRIANGLE;
	}
}

// Find hypotenuse, cathetus1 and cathetus2 (as reference values):
void find_hypotenuse_and_catheti(const double side1, const double side2, const double side3
	, double* hypotenuse, double* cathetus1, double* cathetus2)
{
	assert(hypotenuse);
	assert(cathetus1);
	assert(cathetus2);

	// Create hypotenuse with value side1
	*hypotenuse = side1;
	// Create cathetus1 with value side2
	*cathetus1 = side2;
	// Create cathetus2 with value side3
	*cathetus2 = side3;
	// If cathetus1 > hypotenuse then
	if ( *cathetus1 > *hypotenuse )
	{
		// Swap cathetus1 and hypotenuse
		swap(cathetus1, hypotenuse);
	}
	// If cathetus2 > hypotenuse then
	if ( *cathetus2 > *hypotenuse )
	{
		// Swap cathetus2 and hypotenuse
		swap(cathetus2, hypotenuse);
	}
}

void swap(double* value1, double* value2)
{
	assert(value1);
	assert(value2);

	double temp = *value1;
	*value1 = *value2;
	*value2 = temp;
}
Programa de pruebas de la biblioteca

test.c:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>

#include "triangle.h"

void test_are_sides_valid(void);
void test_do_sides_form_triangle(void);

int main(void)
{
	test_are_sides_valid();
	test_do_sides_form_triangle();

	return 0;
}

void test_are_sides_valid(void)
{
	if ( ! (are_sides_valid(0.0, 0.0, 0.0) == false) )
		fprintf(stderr, "are_sides_valid(0.0, 0.0, 0.0) failed\n");
	if ( ! (are_sides_valid(-1.0, 1.0, 1.0) == false) )
		fprintf(stderr, "are_sides_valid(-1.0, 1.0, 1.0) failed\n");
	if ( ! (are_sides_valid(1.0, 1.0, 1.0) == true) )
		fprintf(stderr, "are_sides_valid(1.0, 1.0, 1.0) failed\n");
	if ( ! (are_sides_valid(0.000000000000001, 0.000000000000001, 0.000000000000001) == true) )
		fprintf(stderr, "are_sides_valid(0.000000000000001, 0.000000000000001, 0.000000000000001) failed\n");
}

void test_do_sides_form_triangle(void)
{
	if ( ! (do_sides_form_triangle(0.0, 0.0, 0.0) == false) )
		fprintf(stderr, "do_sides_form_triangle(0.0, 0.0, 0.0) failed\n");
	if ( ! (do_sides_form_triangle(3.0, 4.0, 5.0) == true) )
		fprintf(stderr, "do_sides_form_triangle(3.0, 4.0, 5.0) failed\n");
	if ( ! (do_sides_form_triangle(3.0, 4.0, 4.0) == true) )
		fprintf(stderr, "do_sides_form_triangle(3.0, 4.0, 5.0) failed\n");
}
Makefile

Makefile:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

.PHONY: all
all: triangle test

triangle: main.o triangle.a
	cc -Wall -Wextra -std=c11 main.o triangle.a -o triangle -lm

#main.o: main.c triangle.h
#	cc -c -Wall -Wextra -std=c11 main.c -o main.o

#triangle.o: triangle.c triangle.h
#	cc -c -Wall -Wextra -std=c11 triangle.c -o triangle.o

%.o: %.c triangle.h
	cc -c -Wall -Wextra -std=c11 $< -o $@

triangle.a: triangle.o
	ar rs $@ $^

test: test.o triangle.a
	cc -Wall -Wextra -std=c11 test.o triangle.a -o test -lm

.PHONY: clean
clean:
	rm -f triangle test *.o *.a

1.5. Indirección, arreglos y matrices

1.5.1. Imprimir el rango (punteros)

Programa que lee los extremos de un rango de los argumentos en la línea de comandos o de la entrada estándar e imprime los números en el rango. Si el rango está invertido, intercambia los dos extremos del rango. Utiliza indirección (punteros) para realizar el intercambio.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <stdio.h>
#include <stdlib.h>

void swap(long* value1, long* value2);

long range_min = 0;

int main(int argc, char* argv[])
{
	// for ( int index = 0; index < argc; ++index )
	// 	printf("%d: {%s}\n", index, argv[index]);

	long range_max = 0;

	if ( argc == 3 )
	{
		static char* error = NULL;
		range_min = strtol(argv[1], &error, 10);
		if ( error && *error )
			return (void)fprintf(stderr, "invalid range\n"), 1;
		range_max = strtol(argv[2], NULL, 10);
	}
	else
	{
		if ( scanf("%ld %ld", &range_min, &range_max) != 2 )
			return (void)fprintf(stderr, "invalid range\n"), 1;
	}

	if ( range_max < range_min )
		swap(&range_min, &range_max);

	//printf("[%ld, %ld]\n", range_min, range_max);
	for ( long index = range_min; index <= range_max; ++index )
		printf("%ld%c", index, index == range_max ? '\n' : ' ');

	return 0;
}

void swap(long* value1, long* value2)
{
	long value1_copy = *value1;
	*value1 = *value2;
	*value2 = value1_copy;
}

/*
void swap(long value1, long value2)
{
	long value1_copy = value1;
	value1 = value2;
	value2 = value1_copy;
}
*/

1.5.2. Mediana estadística (arreglos)

Versión 1: con alojamiento automático. El arreglo se crea en memoria de pila y es destruido automáticamente. Está limitado al tamaño de la pila, que es cerca de 8MB en los sistemas Linux.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <stdio.h>
#include <stdlib.h>

//void qsort(void* base, size_t num, size_t size, int (*compar)(const void*,const void*));
//int (*compare)(const void*, const void*);

int compare_doubles(const void* const value1, const void* value2);

// main:
int main(void)
{
	// Read value count
	size_t value_count = 0;
	if ( scanf("%zu", &value_count) != 1 )
		return 1;

	// Create an array of value count elements
	double values[value_count];
	// Read values into the array
	for ( size_t index = 0; index < value_count; ++index )
		if ( scanf("%lf", &values[index]) != 1 ) // values + index
			return 2;

	// Sort the array of values
	qsort( values, value_count, sizeof(double), compare_doubles );

	// If value count is odd
	double median = 0.0;
	if ( value_count % 2 )
	{
		// Create median as the value at the center of the array
		median = values[ value_count / 2 ];
	}
	// Else
	else
	{
		// Create median as the average of the two values at the center of the array
		median = (values[ value_count / 2 - 1 ] + values[ value_count / 2 ]) / 2.0;
	}
	// Print median
	printf("%.2lf\n", median);
}

int compare_doubles(const void* const value1, const void* const value2)
{
	// const double* ptr1 = (const double*)value1;
	// return *ptr1 - *(const double*)value2;
	return *(const double*)value1 - *(const double*)value2;
}

Versión 2: con alojamiento estático. El arreglo se crea en el segmento de datos, el cual existe antes de que se invoque cualquier subrutina y existe después de que se termine la ejecución de main(). Sin embargo, el tamaño exacto del arreglo se conoce sólo en tiempo de ejecución, cuando se invoca la subrutina main(), mientras que el tamaño del segmento de datos se establece en tiempo de compilación. Es decir, en tiempo en que se está ensamblando el ejecutable en disco. Por tanto, se debe escoger un tamaño máximo para el arreglo con una constante. El siguiente programa escoge 100 millones de valores de doble precisión flotante, pero no hay garantía de que el usuario no lo invoque con un archivo de datos de mayor tamaño, y en tal caso se caerá, dado que no realiza ninguna verificación.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <stdio.h>
#include <stdlib.h>

//void qsort(void* base, size_t num, size_t size, int (*compar)(const void*,const void*));
//int (*compare)(const void*, const void*);

int compare_doubles(const void* const value1, const void* value2);

#define MAX_VALUE_COUNT 100000000

// main:
int main(void)
{
	// Read value count
	size_t value_count = 0;
	if ( scanf("%zu", &value_count) != 1 )
		return 1;

	// Create an array of value count elements
	static double values[MAX_VALUE_COUNT];
	// Read values into the array
	for ( size_t index = 0; index < value_count; ++index )
		if ( scanf("%lf", &values[index]) != 1 ) // values + index
			return 2;

	// Sort the array of values
	qsort( values, value_count, sizeof(double), compare_doubles );

	// If value count is odd
	double median = 0.0;
	if ( value_count % 2 )
	{
		// Create median as the value at the center of the array
		median = values[ value_count / 2 ];
	}
	// Else
	else
	{
		// Create median as the average of the two values at the center of the array
		median = (values[ value_count / 2 - 1 ] + values[ value_count / 2 ]) / 2.0;
	}
	// Print median
	printf("%.2lf\n", median);
}

int compare_doubles(const void* const value1, const void* const value2)
{
	// const double* ptr1 = (const double*)value1;
	// return *ptr1 - *(const double*)value2;
	return *(const double*)value1 - *(const double*)value2;
}

Versión 3: con alojamiento dinámico. El arreglo se crea en el segmento de memoria dinámica (heap) con alguna de las funciones de biblioteca malloc(), calloc(), o realloc() en tiempo de ejecución. Durante la invocación de una de estas funciones, el proceso se detiene mientras el sistema operativo consigue memoria. Cuando lo logra retorna la dirección de memoria donde inician los bytes solicitados por el programador. Si el sistema operativo no puede conseguir la memoria solicitada, retornará la dirección de memoria 0, representada por la constante NULL. Una vez que el programador no requiera usar más la memoria dinámica, deberá liberarla con la función free(). De no hacerlo, provocará una fuga de memoria. El programa de análisis dinámico de código valgrind revisa por errores de acceso y fugas de memoria y se considera de uso obligatorio en el curso.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <stdio.h>
#include <stdlib.h>

//void qsort(void* base, size_t num, size_t size, int (*compar)(const void*,const void*));
//int (*compare)(const void*, const void*);

int compare_doubles(const void* const value1, const void* value2);

// main:
int main(void)
{
	// Read value count
	size_t value_count = 0;
	if ( scanf("%zu", &value_count) != 1 )
		return 1;

	// Create an array of value count elements
	double* values = (double*) malloc(value_count * sizeof(double));
	if ( values == NULL )
		return 2;

	// Read values into the array
	for ( size_t index = 0; index < value_count; ++index )
		if ( scanf("%lf", &values[index]) != 1 ) // values + index
			return 3;

	// Sort the array of values
	qsort( values, value_count, sizeof(double), compare_doubles );

	// If value count is odd
	double median = 0.0;
	if ( value_count % 2 )
	{
		// Create median as the value at the center of the array
		median = values[ value_count / 2 ];
	}
	// Else
	else
	{
		// Create median as the average of the two values at the center of the array
		median = (values[ value_count / 2 - 1 ] + values[ value_count / 2 ]) / 2.0;
	}

	free(values);

	// Print median
	printf("%.2lf\n", median);
}

int compare_doubles(const void* const value1, const void* const value2)
{
	// const double* ptr1 = (const double*)value1;
	// return *ptr1 - *(const double*)value2;
	return *(const double*)value1 - *(const double*)value2;
}

1.5.3. Imprimir la matriz

Lee una matriz de números flotantes de doble precisión y la imprime en la salida estándar. Muestra el trabajo básico de matrices: creación en memoria dinámica, recorrido, y eliminación. Provee funciones para crear y destruir matrices de cualquier tipo en C.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

double** create_matrix_double(const size_t rows, const size_t columns);
void** create_matrix(const size_t rows, const size_t columns, const size_t cell_size);
void destroy_matrix_double(double** matrix, const size_t rows);
void destroy_matrix(void** matrix, const size_t rows);
int read_matrix(const size_t rows, const size_t columns, double** matrix);
//void print_matrix(const size_t rows, const size_t columns, double matrix[rows][columns]);
void print_matrix(const size_t rows, const size_t columns, double** matrix);


int main(void)
{
	size_t rows = 0;
	size_t columns = 0;

	if ( scanf("%zu %zu\n", &rows, &columns) != 2 )
		return 1;

//	double** matrix = create_matrix_double(rows, columns);
	double** matrix = (double**) create_matrix(rows, columns, sizeof(double));
	if ( matrix == NULL )
		return (void)fprintf(stderr, "error: could not allocate memory for %zu rows and %zu columns\n", rows, columns), 2;

	if ( read_matrix(rows, columns, matrix) == EXIT_SUCCESS )
		print_matrix(rows, columns, matrix);

//	destroy_matrix_double(matrix, rows);
	destroy_matrix((void**)matrix, rows);

	return 0;
}

void** create_matrix(const size_t rows, const size_t columns, const size_t cell_size)
{
	void** matrix = calloc(rows, sizeof(void*));
	if ( matrix == NULL )
		return NULL;

	for ( size_t row = 0; row < rows; ++row )
		if ( ( matrix[row] = malloc( columns * cell_size ) ) == NULL )
			return destroy_matrix(matrix, rows), NULL;

	return matrix;
}

double** create_matrix_double(const size_t rows, const size_t columns)
{
	double** matrix = calloc(rows, sizeof(double*));
	if ( matrix == NULL )
		return NULL;

	for ( size_t row = 0; row < rows; ++row )
		if ( ( matrix[row] = malloc( columns * sizeof(double) ) ) == NULL )
			return destroy_matrix_double(matrix, rows), NULL;

	return matrix;
}

void destroy_matrix_double(double** matrix, const size_t rows)
{
	assert( matrix );

	for ( size_t row = 0; row < rows; ++row )
		free( matrix[row] );

	free( matrix );
}

void destroy_matrix(void** matrix, const size_t rows)
{
	assert( matrix );

	for ( size_t row = 0; row < rows; ++row )
		free( matrix[row] );

	free( matrix );
}

int read_matrix(const size_t rows, const size_t columns, double** matrix)
{
	for ( size_t row = 0; row < rows; ++row )
		for ( size_t column = 0; column < columns; ++column )
			//if ( scanf("%lg", &matrix[row][column]) != 1 )
			// if ( scanf("%lg", *(matrix + row) + column) != 1 )
			if ( scanf("%lg", matrix[row] + column) != 1 )
				return EXIT_FAILURE;

	return EXIT_SUCCESS;
}

void print_matrix(const size_t rows, const size_t columns, double** matrix)
{
	for ( size_t row = 0; row < rows; ++row )
		for ( size_t column = 0; column < columns; ++column )
			printf("%lg%c", matrix[row][column], column == columns - 1 ? '\n' : '\t');
}

1.5.4. Tipos de arreglos en C

Las matrices no existen en la memoria de la máquina, se construyen con arreglos de elementos y arreglos de indirección. El siguiente código muestra cómo crear diferentes tipos de arreglos en C:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void arrays();
void matrixes();
void multidimensional_arrays();

int main()
{
	srand( time(NULL) );

	arrays();
	matrixes();
	multidimensional_arrays();

	return 0;
}

void print_array(long double array[], size_t size)
{
	printf("\t{");
	for ( size_t index = 0; index < size; ++index )
		printf("%s %Lg", index ? "," : "", array[index] );
	printf(" }\n");
}

void arrays()
{
	// 1. Literal array (continuous)
	long double literal_array[] = {-1.0L, -0.5L, 0.0L, 0.5L, 1.0L};
	size_t literal_size = sizeof(literal_array) / sizeof(literal_array[0]);

	printf("literal_array:\n");
	print_array(literal_array, literal_size);
	printf("\tcount: %zu elements\n", literal_size);
	printf("\tsizeof: %zu bytes\n", sizeof(literal_array));


	// 2. Automatic array (continuous)
	size_t automatic_size = 3 + rand() % 8;
	long double automatic_array[automatic_size];
	for ( size_t index = 0; index < automatic_size; ++index )
		automatic_array[index] = index;

	printf("\nautomatic_array:\n");
	print_array(automatic_array, automatic_size);
	printf("\tcount: %zu elements\n", automatic_size);
	printf("\tsizeof: %zu bytes\n", sizeof(automatic_array));


	// 3. Dynamic allocated array (not Dynamic array)
	size_t dynamic_alloc_size = 3 + rand() % 8;
	long double* dynamic_alloc_array = (long double*)malloc(dynamic_alloc_size * sizeof(long double));
	for ( size_t index = 0; index < dynamic_alloc_size; ++index )
		dynamic_alloc_array[index] = index;

	printf("\ndynamic_alloc_array:\n");
	print_array(dynamic_alloc_array, dynamic_alloc_size);
	printf("\tcount: %zu elements\n", dynamic_alloc_size);
	printf("\tsizeof: %zu bytes (long double*)\n", sizeof(dynamic_alloc_array));
	free(dynamic_alloc_array);
}

void print_literal_matrix(size_t rows, size_t cols, long double matrix[rows][cols])
{
	for ( size_t row_index = 0; row_index < rows; ++row_index )
	{
		putchar('\t');
		for ( size_t col_index = 0; col_index < cols; ++col_index )
			printf("%5Lg ", matrix[row_index][col_index] );
		putchar('\n');
	}
}

void print_continous_matrix(long double* matrix, size_t rows, size_t cols)
{
	for ( size_t row_index = 0; row_index < rows; ++row_index )
	{
		putchar('\t');
		for ( size_t col_index = 0; col_index < cols; ++col_index )
			printf("%5Lg ", matrix[row_index * cols + col_index] );
		putchar('\n');
	}
}

void print_uncontinous_matrix(long double** matrix, size_t rows, size_t cols)
{
	for ( size_t row_index = 0; row_index < rows; ++row_index )
	{
		printf("\t{");
		for ( size_t col_index = 0; col_index < cols; ++col_index )
			printf("%5Lg ", matrix[row_index][col_index] );
		printf(" }\n");

	}
}

void literal_matrix()
{
	long double matrix[][3] =
	{
		{1.1L, 1.2L, 1.3L},
		{2.1L, 2.2L, 2.3L},
	};
	size_t cols = 3;
	size_t rows = sizeof(matrix) / cols / sizeof(matrix[0][0]);
	printf("\nliteral_matrix:\n");
	print_literal_matrix(rows, cols, matrix);
	printf("\tcount: %zu rows x %zu cols\n", rows, cols);
	printf("\tsizeof: %zu bytes\n", sizeof(matrix));
}

void automatic_matrix()
{
	// 2. Automatic matrix (continuous)
	size_t rows = 3 + rand() % 8;
	size_t cols = 3 + rand() % 8;
	long double matrix[rows][cols];
	for ( size_t row_index = 0; row_index < rows; ++row_index )
		for ( size_t col_index = 0; col_index < cols; ++col_index )
			matrix[row_index][col_index] = (row_index + 1) + (col_index + 1) / 10.0;

	printf("\nautomatic_matrix:\n");
	print_literal_matrix(rows, cols, matrix);
	printf("\tcount: %zu rows x %zu cols\n", rows, cols);
	printf("\tsizeof: %zu bytes\n", sizeof(matrix));
}

void continous_dynamic_allocated_matrix()
{
	// 3. Dynamic allocated matrix (continous) (not Dynamic matrix)
	size_t rows = 3 + rand() % 8;
	size_t cols = 3 + rand() % 8;
	long double* matrix = (long double*)malloc(rows * cols * sizeof(long double));
	for ( size_t row_index = 0; row_index < rows; ++row_index )
		for ( size_t col_index = 0; col_index < cols; ++col_index )
			matrix[row_index * cols + col_index] = (row_index + 1) + (col_index + 1) / 10.0;

	printf("\ndynamic_alloc_matrix:\n");
	print_continous_matrix(matrix, rows, cols);
	printf("\tcount: %zu rows x %zu cols\n", rows, cols);
	printf("\tsizeof: %zu bytes (long double*)\n", sizeof(matrix));
	free(matrix);
}

void uncontinous_dynamic_allocated_matrix()
{
	// 4. Dynamic allocated matrix (uncontinous) (not Dynamic matrix)
	size_t rows = 3 + rand() % 8;
	size_t cols = 3 + rand() % 8;

	// Allocate a vector of vectors
	long double** matrix = (long double**)malloc(rows * sizeof(long double*));
	for ( size_t row_index = 0; row_index < rows; ++row_index )
		matrix[row_index] = (long double*)malloc(cols * sizeof(long double));

	// Initialize values
	for ( size_t row_index = 0; row_index < rows; ++row_index )
		for ( size_t col_index = 0; col_index < cols; ++col_index )
			matrix[row_index][col_index] = (row_index + 1) + (col_index + 1) / 10.0;

	printf("\nuncontinous_dynamic_alloc_matrix:\n");
	print_uncontinous_matrix(matrix, rows, cols);
	printf("\tcount: %zu rows x %zu cols\n", rows, cols);
	printf("\tsizeof: %zu bytes (long double**)\n", sizeof(matrix));

	// Free each row first, then the vector of vectors
	for ( size_t row_index = 0; row_index < rows; ++row_index )
		free(matrix[row_index]);
	free(matrix);
}

void matrixes()
{
	literal_matrix();
	automatic_matrix();
	continous_dynamic_allocated_matrix();
	uncontinous_dynamic_allocated_matrix();
}


void multidimensional_arrays()
{
	// 3. multidimensional array
	long double multidimensional_array[2][3][4] =
	{
		{
			{11.1L, 11.2L, 11.3L, 11.4L},
			{12.1L, 12.2L, 12.3L, 12.4L},
			{13.1L, 13.2L, 13.3L, 13.4L},
		},
		{
			{21.1L, 21.2L, 21.3L, 21.4L},
			{22.1L, 22.2L, 22.3L, 22.4L},
			{23.1L, 23.2L, 23.3L, 23.4L},
		},
	};

	printf("\nmultidimensional_array:\n");
	printf("\tsizeof: %zu\n", sizeof(multidimensional_array));

	// To be done...
}

1.6. Cadenas de caracteres

1.6.1. Encontrar la extensión de archivo

Varias implementaciones de una subrutina que recibe un nombre de archivo en un arreglo de caracteres y retorna un puntero al carácter donde inicia la extensión del archivo. Algunas implementaciones usan indexación y otras aritmética de punteros.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <stdio.h>

const char* find_extension(const char* const filename);
const char* find_extension_v2(const char* const filename);
const char* find_extension_v3(const char* filename);
size_t str_len(const char* filename);

int main(void)
{
	char filename[32];
	//fgets(filename, 32, stdin);
	fscanf(stdin, "%31[^\n]", filename);
	printf("[%s]\n", filename);

	const char* extension = find_extension_v3(filename);
	printf("extension: [%s]\n", extension);

	return 0;
}

const char* find_extension_v3(const char* filename)
{
	const char* last_dot = NULL;
	while ( *filename )
	{
		if ( *filename == '.' )
			last_dot = filename;
		++filename;
	}
	return last_dot ? last_dot + 1 : NULL;
}

const char* find_extension_v4(const char* filename)
{
	const char* last_dot = NULL;
	for ( ; *filename; ++filename )
		if ( *filename == '.' )
			last_dot = filename;
	return last_dot ? last_dot + 1 : NULL;
}

const char* find_extension_v2(const char* const filename)
{
	const size_t npos = (size_t)-1; // non-position
	size_t last_dot_index = npos;
	size_t index = 0;
	while ( filename[index] )
	{
		if ( filename[index] == '.' )
			last_dot_index = index;
		++index;
	}

	if ( last_dot_index == npos )
		return NULL;
	return &filename[last_dot_index + 1];
}

const char* find_extension(const char* const filename)
{
	size_t index = str_len(filename) - 1;
	while ( filename[index] != '.' )
	{
		if ( index == 0 )
			return NULL;
		else
			--index;
	}
	return &filename[index + 1];
//	return filename + index + 1;
}

/*
	size_t index = 0;
	while ( filename[index] )
		++index;
*/

size_t str_len(const char* filename)
{
	size_t index = 0;
	while ( filename[index] )
		++index;
	return index;
}

1.6.2. Mediana con archivos binarios

La mediana hecha hasta el momento espera una entrada textual, donde el primer número indica la cantidad N de valores, seguido de N los valores flotantes. El siguiente es un ejemplo de entrada textual:

input000.txt
1
2
4
50 28 18 90 39

El siguiente programa convierte una entrada textual como la anterior en una entrada binaria. El resultado se imprimir en la salida estándar, por lo que es necesario re-abrir la salida estándar en modo binario, para evitar traducción automática de cambios de línea entre MS-DOS y Unix.

txt2bin.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>

int main()
{
	freopen(NULL, "wb", stdout);

	size_t value_count = 0;
	if ( fscanf(stdin, "%zu", &value_count) != 1 )
		return 1;

	fwrite(&value_count, sizeof(size_t), 1, stdout);

	double value = 0.0;
	for ( size_t index = 0; index < value_count; ++index )
	{
		if ( fscanf(stdin, "%lf", &value) != 1 )
			return 3;
		else
			fwrite(&value, sizeof(double), 1, stdout);
	}
}

La siguiente es el contenido correspondiente en binario al resultado de traducir la entrada textual anterior a binario. Los primeros 8 bytes corresponden al size_t con la cantidad de valores N y los subsecuentes 4 • 8 bytes corresponden a cada double:

$ od -t x1 input000.bin
0000000    04  00  00  00  00  00  00  00  00  00  00  00  00  00  49  40
0000020    00  00  00  00  00  00  3c  40  00  00  00  00  00  00  32  40
0000040    00  00  00  00  00  80  56  40
0000050

La siguiente versión de la mediana puede recibir los valores en la entrada estándar (textual), o un nombre de archivo como primer argumento de la línea de comandos. El programa detecta por la extensión del archivo si el contenido es textual (.txt) o binario (.bin):

median.c
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>

#include "extension.h"

typedef enum
{
	FILE_TYPE_TEXT,
	FILE_TYPE_BINARY,
} file_type_t;

int calculate_median(FILE* input, file_type_t file_type, double* median);
int compare_doubles(const void* const value1, const void* value2);
double* load_values(FILE* input, file_type_t file_type, size_t* value_count);
double* load_values_text(FILE* input, size_t* value_count);
double* load_values_binary(FILE* input, size_t* value_count);


// main:
int main(int argc, char* argv[])
{
	FILE* input = stdin;
	file_type_t file_type = FILE_TYPE_TEXT;

	if ( argc == 2 )
	{
		const char* extension = find_extension(argv[1]);
		if ( extension == NULL )
			return fprintf(stderr, "median: error: file not supported %s\n", argv[1]), 1;

		if ( str_icmp(extension, "txt") == 0 )
		{
			if ( (input = fopen(argv[1], "r")) == NULL )
				return fprintf(stderr, "median: error: could not open %s\n", argv[1]), 2;
		}
		else if ( str_icmp(extension, "bin") == 0 )
		{
			if ( (input = fopen(argv[1], "rb")) == NULL )
				return fprintf(stderr, "median: error: could not open %s\n", argv[1]), 3;
			file_type = FILE_TYPE_BINARY;
		}
		else
			return fprintf(stderr, "median: error: file not supported %s\n", argv[1]), 4;
	}

	double median = 0.0;
	int error = calculate_median(input, file_type, &median);
	if ( error == EXIT_SUCCESS )
		printf("%.2lf\n", median);

	if ( input != stdin )
		fclose(input);

	return EXIT_SUCCESS;
}

int calculate_median(FILE* input, file_type_t file_type, double* median)
{
	size_t value_count = 0;
	double* values = load_values(input, file_type, &value_count);
	if ( values == NULL )
		return EXIT_FAILURE;

	// Sort the array of values
	qsort( values, value_count, sizeof(double), compare_doubles );

	// If value count is odd
	assert(median);
	if ( value_count % 2 )
	{
		// Create median as the value at the center of the array
		*median = values[ value_count / 2 ];
	}
	// Else
	else
	{
		// Create median as the average of the two values at the center of the array
		*median = (values[ value_count / 2 - 1 ] + values[ value_count / 2 ]) / 2.0;
	}

	free(values);
	return EXIT_SUCCESS;
}

int compare_doubles(const void* const value1, const void* const value2)
{
	// const double* ptr1 = (const double*)value1;
	// return *ptr1 - *(const double*)value2;
	return *(const double*)value1 - *(const double*)value2;
}

double* load_values(FILE* input, file_type_t file_type, size_t* value_count)
{
	switch( file_type )
	{
		case FILE_TYPE_TEXT: return load_values_text(input, value_count);
		case FILE_TYPE_BINARY: return load_values_binary(input, value_count);
		default: assert(false); return NULL;
	}
}

double* load_values_text(FILE* input, size_t* value_count)
{
	// Read value count
	assert(value_count);
	if ( fscanf(input, "%zu", value_count) != 1 )
		return NULL;

	// Create an array of value count elements
	double* values = (double*) malloc(*value_count * sizeof(double));
	if ( values == NULL )
		return NULL;

	// Read values into the array
	for ( size_t index = 0; index < *value_count; ++index )
		if ( fscanf(input, "%lf", &values[index]) != 1 ) // values + index
			return free(values), NULL;

	return values;
}

double* load_values_binary(FILE* input, size_t* value_count)
{
	// Read value count
	assert(value_count);
	if ( fread(value_count, sizeof(size_t), 1, input) != 1 )
		return NULL;

	// Create an array of value count elements
	double* values = (double*) malloc(*value_count * sizeof(double));
	if ( values == NULL )
		return NULL;

	// Read values into the array
	// for ( size_t index = 0; index < *value_count; ++index )
	// 	if ( fread(&values[index], sizeof(double), 1, input) != 1 )
	// 		return free(values), NULL;

	if ( fread(values, sizeof(double), *value_count, input) != *value_count )
		return free(values), NULL;

	return values;
}

La mediana anterior usa una biblioteca estática de encontrar la extensión y comparar cadenas:

extension.h
1
2
3
4
5
6
7
#ifndef EXTENSION_H
#define EXTENSION_H

const char* find_extension(const char* filename);
int str_icmp(const char* text1, const char* text2);

#endif // EXTENSION_H
extension.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <assert.h>
#include <ctype.h>
#include <stddef.h>

#include "extension.h"

const char* find_extension(const char* filename)
{
	assert(filename);
	const char* last_dot = NULL;
	while ( *filename )
	{
		if ( *filename == '.' )
			last_dot = filename;
		++filename;
	}
	return last_dot ? last_dot + 1 : NULL;
}

// < 0 if text1 < text2
// = 0 if text1 = text2
// > 0 if text1 > text2
int str_icmp(const char* text1, const char* text2)
{
	assert(text1);
	assert(text2);

	while ( *text1 && *text2 && tolower(*text1) == tolower(*text2) )
		++text1, ++text2;
	return *text1 - *text2;
}

El siguiente Makefile ayuda a compilar el programa de la mediana que soporta archivos textuales y binarios. Incluye reglas para generar casos de prueba con el comando jot (disponible en sistemas basados en BSD).

Makefile
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
APPNAME=median
LIBNAME=extension
VALUECNT=10000000

.PHONY: all
all: $(APPNAME)

$(APPNAME): $(APPNAME).o $(LIBNAME).a
	cc -Wall -Wextra -std=c11 $^ -o $@

%.o: %.c
	cc -c -Wall -Wextra -std=c11 $< -o $@

$(LIBNAME).a: $(LIBNAME).o
	ar rs $@ $^

#test: test.o triangle.a
#	cc -Wall -Wextra -std=c11 test.o triangle.a -o test -lm

input%.txt:
	echo $(VALUECNT) > $@
	echo >> $@
	jot -r $(VALUECNT) -99999999999999.99999999999999 99999999999999.99999999999999 >> $@

input%.bin: input%.txt
	./txt2bin < $^ > $@

.PHONY: clean
clean:
	rm -f $(APPNAME) test *.o *.a

Los siguientes ejemplos de ejecución muestra un incremento en el rendimiento del cargado en archivos binarios respecto a los textuales. Además es más eficiente leer un arreglo con una única invocación a fread() que mútiples invocaciones en un ciclo. Debe tenerse en cuenta que el mayor consumo de CPU lo realiza el programa de la mediana ordenando el arreglo.

# Entradas con los mismos valores
$ ls -lh input004*
-rw-r--r--  1 jhc  staff    76M Oct  4 16:41 input004.bin
-rw-r--r--  1 jhc  staff   143M Oct  4 16:41 input004.txt

# Entrada textual
$ time ./median input004.txt
68154085993300.50           # Esta es la mediana

real    0m6.881s            # Duración total en segundos
user    0m6.749s            # Duración del programa
sys     0m0.109s            # Duración de solicitudes al OS

# Entrada binaria leída con un ciclo de fread()s
$ time ./median input004.bin
68154085993300.50

real    0m5.324s            # Incremento de 1.29 veces la velocidad
user    0m5.177s
sys     0m0.097s

# Entrada binaria leída con un único fread()
$ make
cc -c -Wall -Wextra -std=c11 median.c -o median.o
cc -Wall -Wextra -std=c11 median.o extension.a -o median

$ time ./median input004.bin
68154085993300.50

real    0m4.632s            # Incremento de 1.49 veces la velocidad
user    0m4.558s
sys     0m0.065s

1.7. Registros

1.7.1. Mediana con registros

La versión anterior de la mediana se implementa con funciones que reciben parámetros de salida, también conocidos como parámetros de retorno que son parámetros por referencia donde la subrutina regresa valores. Conforme el programa se vaya haciendo complejo, la cantidad de estos parámetros irá en incremento lo que hace al código difícil de mantener.

El siguiente es un "refactoring", es decir, un cambio en el código que no afecta la funcionalidad, pero que hace más fácil de mantener. Consiste en agrupar los parámetros comunes de las subrutinas en un registro (en inglés, record). Un registro de memoria es una región continua de la memoria que puede almacenar campos de diferente tipo. En C los registros de memoria se implementan con struct.

El siguiente código crea un tipo de datos para crear registros llamado median_t. Este registro almacena las variables comunes para las subrutinas que hacen el cálculo de la mediana. Cada subrutina recibe un puntero a este registro. De esta forma se simplifica el paso de parámetros, y evita tener que modificar las declaraciones si el número de campos incrementa en el tiempo.

median.c
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>

#include "extension.h"

typedef enum
{
	FILE_TYPE_TEXT,
	FILE_TYPE_BINARY,
} file_type_t;

typedef struct
{
	FILE* input;
	file_type_t file_type;
	double* values;
	size_t value_count;
	double result;
} median_t;


int calculate_median(median_t* median);
int compare_doubles(const void* const value1, const void* const value2);
int load_values(median_t* median);
int load_values_text(median_t* median);
int load_values_binary(median_t* median);


// main:
int main(int argc, char* argv[])
{
	median_t median = (median_t) {stdin, FILE_TYPE_TEXT, NULL, 0, 0.0};
//	return printf("%zu\n", sizeof(median_t));

	if ( argc == 2 )
	{
		const char* extension = find_extension(argv[1]);
		if ( extension == NULL )
			return fprintf(stderr, "median: error: file not supported %s\n", argv[1]), 1;

		if ( str_icmp(extension, "txt") == 0 )
		{
			if ( (median.input = fopen(argv[1], "r")) == NULL )
				return fprintf(stderr, "median: error: could not open %s\n", argv[1]), 2;
		}
		else if ( str_icmp(extension, "bin") == 0 )
		{
			if ( (median.input = fopen(argv[1], "rb")) == NULL )
				return fprintf(stderr, "median: error: could not open %s\n", argv[1]), 3;

			median.file_type = FILE_TYPE_BINARY;
		}
		else
			return fprintf(stderr, "median: error: file not supported %s\n", argv[1]), 4;
	}

	int error = calculate_median(&median);
	if ( error == EXIT_SUCCESS )
		printf("%.2lf\n", median.result);

	if ( median.input != stdin )
		fclose(median.input);

	return EXIT_SUCCESS;
}

int calculate_median(median_t* median)
{
	int error = load_values(median);
	if ( error )
		return error;

	// Sort the array of values
	qsort( (*median).values, median->value_count, sizeof(double), compare_doubles );

	// If value count is odd
	if ( median->value_count % 2 )
	{
		// Create median as the value at the center of the array
		median->result = median->values[ median->value_count / 2 ];
	}
	// Else
	else
	{
		// Create median as the average of the two values at the center of the array
		median->result = (median->values[ median->value_count / 2 - 1 ]
			+ median->values[ median->value_count / 2 ]) / 2.0;
	}

	free(median->values);
	median->values = NULL;
	return EXIT_SUCCESS;
}

int compare_doubles(const void* const value1, const void* const value2)
{
	// const double* ptr1 = (const double*)value1;
	// return *ptr1 - *(const double*)value2;
	return *(const double*)value1 - *(const double*)value2;
}

int load_values(median_t* median)
{
	switch( median->file_type )
	{
		case FILE_TYPE_TEXT: return load_values_text(median);
		case FILE_TYPE_BINARY: return load_values_binary(median);
		default: assert(false); return EXIT_FAILURE;
	}
}

int load_values_text(median_t* median)
{
	// Read value count
	if ( fscanf(median->input, "%zu", &median->value_count) != 1 )
		return 1;

	// Create an array of value count elements
	median->values = (double*) malloc(median->value_count * sizeof(double));
	if ( median->values == NULL )
		return 2;

	// Read values into the array
	for ( size_t index = 0; index < median->value_count; ++index )
		if ( fscanf(median->input, "%lf", &median->values[index]) != 1 ) // values + index
			return free(median->values), 3;

	return 0;
}

int load_values_binary(median_t* median)
{
	// Read value count
	if ( fread(&median->value_count, sizeof(size_t), 1, median->input) != 1 )
		return 1;

	// Create an array of value count elements
	median->values = (double*) malloc(median->value_count * sizeof(double));
	if ( median->values == NULL )
		return 2;

	if ( fread(median->values, sizeof(double), median->value_count, median->input) != median->value_count )
		return free(median->values), 3;

	return EXIT_SUCCESS;
}

1.7.2. Desigualdad triangular con biblioteca dinámica

Los archivos que componen un proyecto de software no deben estar en la raíz del proyecto, dado que se hará inmanejable. Los siguientes comandos asumen que el código fuente está ubicado en la carpeta src/, y crean carpetas para código objeto (build/), bibliotecas dinámicas (lib/), y ejecutables (bin/).

# Directories
mkdir -p build/
mkdir -p lib/
mkdir -p bin/

# Library (debug)
gcc -c -fPIC -Wall -Wextra -std=c11 src/triangle.c -o build/triangle-d.o
gcc -shared build/triangle.o -o lib/libtriangle-1.0.0-d.so

# Library (release)
gcc -DNDEBUG -c -fPIC -Wall -Wextra -std=c11 src/triangle.c -o build/triangle.o
gcc -DNDEBUG -shared build/triangle.o -o lib/libtriangle-1.0.0.so

# CLI
gcc -c -Wall -Wextra -std=c11 src/main.c -o build/main.o
gcc    -Wall -Wextra -std=c11 build/main.o lib/libtriangle-1.0.0.so -o bin/triangle

# Test
gcc -c -Wall -Wextra -std=c11 src/test.c -o build/test.o
gcc    -Wall -Wextra -std=c11 build/test.o lib/libtriangle-1.0.0.so -o bin/test

(Pendiente: Makefile reutilizable que genera los comandos anteriores)

2. Programación orientada a objetos

2.1. Clases y objetos

2.1.1. Desigualdad triangular con objetos

El siguiente es un programa híbrido procedimental-orientado a objetos. Un Triangle es un objeto con tres atributos (sus lados) y métodos que en lugar de recibir estos tres argumentos, los acceden a través de un parámetro oculto this. El main() se encarga de instanciar un objeto Triangle y darle órdenes como leerse o imprimirse, y hacerle preguntas, como determinar si es un triángulo válido o no (de acuerdo al principio de desigualdad triangular).

main.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <cassert>
#include <iostream>

#include "Triangle.h"

void classify_triangle(Triangle triangle);
void print_side_classification(SideClassification sideClassification);
void print_angle_classification(AngleClassification angleClassification);

// main:
int main()
{
	// Repeat while there are sides
	// Read side1, side2, side3
	Triangle triangle;
	while ( triangle.read() )
	{
		// Print side1, side2, side3
		triangle.print();
		// If sides are valid then
		if ( triangle.hasValidSides() )
		{
			// If sides form a triangle
			if ( triangle.isValid() )
			{
				// Classify triangle
				classify_triangle(triangle);
			}
			// Else
			else
			{
				// Print "no es un triangulo"
				std::cout << "no es un triangulo\n";
			}
		}
		// Else
		else
		{
			// Print "datos no validos"
			std::cout << "datos no validos" << std::endl;
		}
	}

	return 0;
}

// Classify triangle:
void classify_triangle(Triangle triangle)
{
	// Calculate and print perimeter
	std::cout << "P=" << triangle.calculatePerimeter() << ' ';
	// Calculate and print area
	std::cout << "A=" << triangle.calculateArea() << ' ';
	// Create side classification with the result of classify triangle per sides
	SideClassification sideClassification = triangle.classifyBySides();
	// Print side classification
	print_side_classification(sideClassification);
	std::cout << ' ';
	// Create angle classification with the result of classify triangle per angles
	AngleClassification angleClassification = triangle.classifyByAngles();
	// Print angle classification
	print_angle_classification(angleClassification);
	std::cout << '\n';
}

void print_side_classification(SideClassification sideClassification)
{
	switch ( sideClassification )
	{
		case EQUILATERAL_TRIANGLE: std::cout << "equilatero"; break;
		case ISOSCELES_TRIANGLE: std::cout << "isosceles"; break;
		case SCALENE_TRIANGLE: std::cout << "escaleno"; break;

		default: assert(false); break;
	}
}

void print_angle_classification(AngleClassification angleClassification)
{
	//assert(...);
	std::cout << angle_texts[angleClassification];
}

Las clases en C++ se declaran en un archivo .h o .hpp y aunque se pueden implementar sus métodos en ese archivo, no es una práctica recomendada (como es el caso del constructor abajo).

Triangle.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#ifndef TRIANGLE_H
#define TRIANGLE_H

typedef enum
{
	EQUILATERAL_TRIANGLE,
	ISOSCELES_TRIANGLE,
	SCALENE_TRIANGLE,
} SideClassification;

typedef enum
{
	ACUTE_TRIANGLE,
	RIGHT_TRIANGLE,
	OBTUSE_TRIANGLE,
} AngleClassification;

static const char* const angle_texts[] =
{
	"acutangulo",
	"rectangulo",
	"obtusangulo",
};

class Triangle
{
  private:
	double side1 = 0.0;
	double side2;
	double side3;

  public:
  	Triangle(double side1 = 0.0, double side2 = 0.0, double side3 = 0.0)
  		: side1(side1)
  		, side2{side2}
  		, side3{side3}
  	{
  		// This is NOT initialization
  		/*
  		side1 = 0.0; This is assignment
  		this->side2 = side2;
  		side3 = 0.0; */
  	}

	// Function declarations
	/**
		@brief Checks that the three sides are positive

		@details ...
		...
		@code
			are_sides_valid(1.0, 0.0, 2.0) == false
		@endcode
		more details...
		@return true if the three sides are positive
	*/
	bool hasValidSides() const;
	bool isValid() const;
	double calculatePerimeter() const;
	double calculateArea() const;
	double calculateSemiperimeter() const;

	bool read();
	void print() const;

  public:
	SideClassification classifyBySides() const;
	AngleClassification classifyByAngles() const;

	/**
		@param cathetus1 A valid pointer to the variable that will set as the first cathethus.
		This pointer must be not null.
	*/
	void findHypotenuseAndCatheti(double* hypotenuse, double* cathetus1, double* cathetus2) const;

};

#endif

Los métodos de la clase se implementan en su correspondiente archivo .cpp (otras extensiones comunes son .C y cxx). Para diferenciar una función libre f() de un método, se debe indicar que pertenece a la clase con el operador de resolución de alcance (scope resolution operator) de la forma MyClass::f().

Triangle.cpp
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#include <cassert>
#include <cmath>
#include <iostream>

#include "Triangle.h"

// Declarations in .c are private
void swap(double* value1, double* value2);

#if 0
// Free-function
bool read()
{
	return std::cin >> side1 >> side2 >> side3;
}
#endif

// Method
bool Triangle::read()
{
	return static_cast<bool>(std::cin >> this->side1 >> side2 >> side3);
}

void Triangle::print() const
{
	std::cout << "a=" << side1 << " b=" << side2 << " c=" << side3  << ' ';
}

// Are sides valid:
bool Triangle::hasValidSides()
{
	// If side1 > 0 and side2 > 0 and side3 > 0
	if ( side1 > 0 && side2 > 0 && side3 > 0 )
	{
		// Return true
		return true;
	}
	// Else
	else
	{
		// Return false
		return false;
	}
}

// Do sides form a triangle:
bool Triangle::isValid() const
{
	// Return side1 < side2 + side3 and side1 > |side2 - side3|
	return side1 < side2 + side3 && side1 > fabs(side2 - side3);
}

// Calculate perimeter:
double Triangle::calculatePerimeter()
{
	// return side1 + side2 + side3
	return side1 + side2 + side3;
}

// Calculate area:
double Triangle::calculateArea()
{
	// Create semiperimeter as result of calculate semiperimeter
	double semiperimeter = calculateSemiperimeter();
	// Return area using the Heron formula and the semiperimeter
	return sqrt( semiperimeter * (semiperimeter - side1) * (semiperimeter - side2) * (semiperimeter - side3));
}

// Calculate semiperimeter:
double Triangle::calculateSemiperimeter()
{
	// Return the result of calculate the perimeter and divide by 2
	return calculatePerimeter() / 2.0;
}

// Classify triangle per sides:
SideClassification Triangle::classifyBySides()
{
	// If side1 = side2 = side3 then
	if ( side1 == side2 && side2 == side3 )
	{
		// Return equilateral triangle
		return EQUILATERAL_TRIANGLE;
	}
	// Else if side1 ≠ side2 and side1 ≠ side3 and side2 ≠ side3 then
	if ( side1 != side2 && side1 != side3 && side2 != side3 )
	{
		// Return scalene triangle
		return SCALENE_TRIANGLE;
	}
	// Else
	else
	{
		// Return isosceles triangle
		return ISOSCELES_TRIANGLE;
	}
}

// Classify triangle per angles
AngleClassification Triangle::classifyByAngles()
{
	// Find hypotenuse, cathetus1 and cathetus2
	double hypotenuse = 0.0, cathetus1 = 0.0, cathetus2 = 0.0;
	findHypotenuseAndCatheti(&hypotenuse, &cathetus1, &cathetus2);
	// If hypotenuse^2 = cathetus1^2 + cathetus2^2 then
	if ( hypotenuse * hypotenuse == cathetus1 * cathetus1 + cathetus2 * cathetus2 )
	{
		// Return right triangle
		return RIGHT_TRIANGLE;
	}
	// Else if hypotenuse^2 < cathetus1^2 + cathetus2^2 then
	else if ( hypotenuse * hypotenuse < cathetus1 * cathetus1 + cathetus2 * cathetus2 )
	{
		// Return acute triangle
		return ACUTE_TRIANGLE;
	}
	// Else
	else
	{
		// Return oblique triangle
		return OBTUSE_TRIANGLE;
	}
}

// Find hypotenuse, cathetus1 and cathetus2 (as reference values):
void Triangle::findHypotenuseAndCatheti(double* hypotenuse, double* cathetus1, double* cathetus2)
{
	assert(hypotenuse);
	assert(cathetus1);
	assert(cathetus2);

	// Create hypotenuse with value side1
	*hypotenuse = side1;
	// Create cathetus1 with value side2
	*cathetus1 = side2;
	// Create cathetus2 with value side3
	*cathetus2 = side3;
	// If cathetus1 > hypotenuse then
	if ( *cathetus1 > *hypotenuse )
	{
		// Swap cathetus1 and hypotenuse
		swap(cathetus1, hypotenuse);
	}
	// If cathetus2 > hypotenuse then
	if ( *cathetus2 > *hypotenuse )
	{
		// Swap cathetus2 and hypotenuse
		swap(cathetus2, hypotenuse);
	}
}

void swap(double* value1, double* value2)
{
	assert(value1);
	assert(value2);

	double temp = *value1;
	*value1 = *value2;
	*value2 = temp;
}

2.1.2. Representación procedimental de los objetos

La máquina computacional no es orientada a objetos. El paradigma de programación de alto nivel más cercano a la máquina es el procedimental. Por tanto, el concepto de objeto es sólo comprendido por el compilador, quien debe traducirlo a constructos más primitivos. Comprender cómo los objetos pueden ser representados con constructos procedimientales es de utilidad para el informático para ver la computadora como una caja de cristal y no una caja máquina. El siguiente código muestra una potencial representación de los objetos del ejemplo de desigualdad triangular usando registros y funciones libres (con punteros hacia los registros).

main.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include <assert.h>
#include <stdio.h>

#include "Triangle.h"

static const char* const angle_texts[] =
{
	"acutangulo",
	"rectangulo",
	"obtusangulo",
};

void classify_triangle(Triangle triangle);
void print_side_classification(SideClassification sideClassification);
void print_angle_classification(AngleClassification angleClassification);

// main:
int main(void)
{
	// Repeat while there are sides
	// Read side1, side2, side3
	Triangle triangle;
	Triangle_Triangle_0(&triangle);

	while ( Triangle_read(&triangle) )
	{
		// Print side1, side2, side3
		triangle.print();
		// If sides are valid then
		if ( triangle.hasValidSides() )
		{
			// If sides form a triangle
			if ( triangle.isValid() )
			{
				// Classify triangle
				classify_triangle(triangle);
			}
			// Else
			else
			{
				// Print "no es un triangulo"
				std::cout << "no es un triangulo\n";
			}
		}
		// Else
		else
		{
			// Print "datos no validos"
			std::cout << "datos no validos" << std::endl;
		}
	}

	return 0;
}

// Classify triangle:
void classify_triangle(Triangle triangle)
{
	// Calculate and print perimeter
	std::cout << "P=" << triangle.calculatePerimeter() << ' ';
	// Calculate and print area
	std::cout << "A=" << triangle.calculateArea() << ' ';
	// Create side classification with the result of classify triangle per sides
	SideClassification sideClassification = triangle.classifyBySides();
	// Print side classification
	print_side_classification(sideClassification);
	std::cout << ' ';
	// Create angle classification with the result of classify triangle per angles
	AngleClassification angleClassification = triangle.classifyByAngles();
	// Print angle classification
	print_angle_classification(angleClassification);
	std::cout << '\n';
}

void print_side_classification(SideClassification sideClassification)
{
	switch ( sideClassification )
	{
		case SideClassification::EQUILATERAL: std::cout << "equilatero"; break;
		case SideClassification::ISOSCELES: std::cout << "isosceles"; break;
		case SideClassification::SCALENE: std::cout << "escaleno"; break;

		default: assert(false); break;
	}
}

void print_angle_classification(AngleClassification angleClassification)
{
	//assert(...);
	std::cout << angle_texts[angleClassification];
}

La conversión procedimental separa la estructura del comportamiento. La estructura (atributos) son representados con un registro (struct). El comportamiento es representado con funciones libres. Las declaraciones de funciones de la clase se convierten en declaraciones en el archivo de encabezado (.h), y a todos los métodos de instancia (no estáticos) se les agrega automáticamente un puntero en el primer parámetro hacia el registro donde debe actuar. A este puntero se le llama el parámetro oculto `this`, y es el que enlaza las funciones libres con su registro.

Triangle.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#ifndef TRIANGLE_H
#define TRIANGLE_H

typedef enum
{
	SideClassification_EQUILATERAL,
	SideClassification_ISOSCELES,
	SideClassification_SCALENE,

	SideClassification_UNKNOWN
} SideClassification;

typedef enum
{
	ACUTE_TRIANGLE,
	RIGHT_TRIANGLE,
	OBTUSE_TRIANGLE,

	UNKNOWN
} AngleClassification;

typedef struct
{
	double side1;
	double side2;
	double side3;
} Triangle;

void Triangle_Triangle_0(Triangle* this);
void Triangle_Triangle_1(Triangle* this, double side1);
void Triangle_Triangle_2(Triangle* this, double side1, double side2);
void Triangle_Triangle_3(Triangle* this, double side1, double side2, double side3);

// Function declarations
/**
	@brief Checks that the three sides are positive

	@details ...
	...
	@code
		are_sides_valid(1.0, 0.0, 2.0) == false
	@endcode
	more details...
	@return true if the three sides are positive
*/
bool Triangle_hasValidSides(Triangle* this);
bool Triangle_isValid(const Triangle* this);
double Triangle_calculatePerimeter(Triangle* this);
double Triangle_calculateArea(Triangle* this);
double Triangle_calculateSemiperimeter(Triangle* this);

bool Triangle_read(Triangle* this);
void Triangle_print(Triangle* this);

SideClassification Triangle_classifyBySides(Triangle* this);
AngleClassification Triangle_classifyByAngles(Triangle* this);

/**
	@param cathetus1 A valid pointer to the variable that will set as the first cathethus.
	This pointer must be not null.
*/
void Triangle_findHypotenuseAndCatheti(Triangle* this, double* hypotenuse, double* cathetus1, double* cathetus2);


#endif

Dado que en C no hay espacios de nombres ni sobrecarga de subrutinas, el compilador de C++ debe cambiar los nombres que el programador escogió por identificadores únicos. Los nombres escogidos no son estándar, sino que cada compilador escoge su propia nomenclatura, lo que hace al código objeto incompatible entre compiladores. A este proceso de cambio automático de nombres se le llama name mangling. En el siguiente ejemplo se usa la convención Clase_metodo() para los nombres de los métodos. Note cómo el compilador debe usar el puntero oculto this para poder acceder a los atributos de la forma this→attribute, en caso de que el programador no lo haya hecho:

Triangle.c
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
#include <assert.h>
#include <math.h>
#include <stdio.h>

#include "Triangle.h"

// Declarations in .c are private
void swap(double* value1, double* value2);

void Triangle_Triangle_0(Triangle* this)
{
	return Triangle_Triangle_3(this, 0.0, 0.0, 0.0);
}

void Triangle_Triangle_1(Triangle* this, double side1)
{
	return Triangle_Triangle_3(this, side1, 0.0, 0.0);
}

void Triangle_Triangle_2(Triangle* this, double side1, double side2)
{
	return Triangle_Triangle_3(this, side1, side2, 0.0);
}

void Triangle_Triangle_3(Triangle* this, double side1, double side2, double side3)
{
	this->side1 = side1;
	this->side2 = side2;
	this->side3 = side3;
}

// Method
bool Triangle_read(Triangle* this)
{
	return scanf("%lf%lf%lf", &this->side1, &this->side2, &this->side3) == 3;
}

void Triangle::print(Triangle* this)
{
	printf("%s", "a=");
	printf("%g", this->side1);
	printf("%s", " b=");
	// << this->side2 << " c=" << this->side3  << ' ';
}

// Are sides valid:
bool Triangle::hasValidSides(Triangle* this)
{
	// If side1 > 0 and side2 > 0 and side3 > 0
	if ( side1 > 0 && side2 > 0 && side3 > 0 )
	{
		// Return true
		return true;
	}
	// Else
	else
	{
		// Return false
		return false;
	}
}

// Do sides form a triangle:
bool Triangle_isValid(const Triangle* this)
{
	// Return side1 < side2 + side3 and side1 > |side2 - side3|
	return this->side1 < this->side2 + this->side3 && this->side1 > fabs(this->side2 - this->side3);
}

// Calculate perimeter:
double Triangle::calculatePerimeter(Triangle* this)
{
	// return side1 + side2 + side3
	return side1 + side2 + side3;
}

// Calculate area:
double Triangle_calculateArea(Triangle* this)
{
	// Create semiperimeter as result of calculate semiperimeter
	double semiperimeter = Triangle_calculateSemiperimeter(this);
	// Return area using the Heron formula and the semiperimeter
	return sqrt( semiperimeter * (semiperimeter - side1) * (semiperimeter - side2) * (semiperimeter - side3));
}

// Calculate semiperimeter:
double Triangle::calculateSemiperimeter(Triangle* this)
{
	// Return the result of calculate the perimeter and divide by 2
	return calculatePerimeter() / 2.0;
}

// Classify triangle per sides:
SideClassification Triangle::classifyBySides(Triangle* this)
{
	// If side1 = side2 = side3 then
	if ( side1 == side2 && side2 == side3 )
	{
		// Return equilateral triangle
		return SideClassification::EQUILATERAL;
	}
	// Else if side1 ≠ side2 and side1 ≠ side3 and side2 ≠ side3 then
	if ( side1 != side2 && side1 != side3 && side2 != side3 )
	{
		// Return scalene triangle
		return SideClassification::SCALENE;
	}
	// Else
	else
	{
		// Return isosceles triangle
		return SideClassification::ISOSCELES;
	}
}

// Classify triangle per angles
AngleClassification Triangle::classifyByAngles(Triangle* this)
{
	// Find hypotenuse, cathetus1 and cathetus2
	double hypotenuse = 0.0, cathetus1 = 0.0, cathetus2 = 0.0;
	findHypotenuseAndCatheti(&hypotenuse, &cathetus1, &cathetus2);
	// If hypotenuse^2 = cathetus1^2 + cathetus2^2 then
	if ( hypotenuse * hypotenuse == cathetus1 * cathetus1 + cathetus2 * cathetus2 )
	{
		// Return right triangle
		return RIGHT_TRIANGLE;
	}
	// Else if hypotenuse^2 < cathetus1^2 + cathetus2^2 then
	else if ( hypotenuse * hypotenuse < cathetus1 * cathetus1 + cathetus2 * cathetus2 )
	{
		// Return acute triangle
		return ACUTE_TRIANGLE;
	}
	// Else
	else
	{
		// Return oblique triangle
		return OBTUSE_TRIANGLE;
	}
}

// Find hypotenuse, cathetus1 and cathetus2 (as reference values):
void Triangle::findHypotenuseAndCatheti(Triangle* this, double* hypotenuse, double* cathetus1, double* cathetus2)
{
	assert(hypotenuse);
	assert(cathetus1);
	assert(cathetus2);

	// Create hypotenuse with value side1
	*hypotenuse = side1;
	// Create cathetus1 with value side2
	*cathetus1 = side2;
	// Create cathetus2 with value side3
	*cathetus2 = side3;
	// If cathetus1 > hypotenuse then
	if ( *cathetus1 > *hypotenuse )
	{
		// Swap cathetus1 and hypotenuse
		swap(cathetus1, hypotenuse);
	}
	// If cathetus2 > hypotenuse then
	if ( *cathetus2 > *hypotenuse )
	{
		// Swap cathetus2 and hypotenuse
		swap(cathetus2, hypotenuse);
	}
}

void swap(double* value1, double* value2)
{
	assert(value1);
	assert(value2);

	double temp = *value1;
	*value1 = *value2;
	*value2 = temp;
}

2.2. Sobrecarga de operadores

2.2.1. Calculadora fraccional

Muestra cómo sobrecargar operadores para realizar operaciones con fracciones, como sumas, restas. Introduce el concepto de referencia.

main.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>

#include "Fraction.h"

int main()
{
	Fraction fr1;
	fr1.read();
	Fraction fr2(3, -4);
//	Fraction fr4(-13);
//	Fraction fr4{-13};
	Fraction fr4 = -3;
//	Fraction fr3 = fr1.add(fr2).add(fr4);
//	Fraction fr3 = fr1 + fr2 + fr4;
//	Fraction fr3 = fr1.operator+(fr2).operator+(fr4);
//	Fraction fr3 = (fr1 + fr2).operator+(fr4);
	Fraction fr3 = fr1.operator+(fr2) + ((fr4));
	fr3.print();
	std::cout << std::endl;

//	Fraction fr5 = fr1 + fr2 * fr4;
	Fraction fr5 = 2 + fr1 + 3 + fr2 * 2;
//	Fraction fr5 = fr1.operator+(fr2).operator*(fr4);
	fr5.print();
	std::cout << std::endl;

	std::cout << "mcd(12,8) = " << Fraction::greatestCommonDivisor(12,8) << std::endl;
//	std::cout << "mcd(12,8) = " << fr4.greatestCommonDivisor(12,8) << std::endl;
}
Fraction.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#ifndef FRACTION_H
#define FRACTION_H


class Fraction
{
  private:
	long long numerator;
	long long denominator;

  public:
	/// Default constructor (ctor) and conversion constructor
	Fraction(const long long numerator = 0, const long long denominator = 1);
	Fraction add(const Fraction& other) const;
	Fraction operator+(const Fraction& other) const;
	Fraction operator*(const Fraction& other) const;

	bool read();
	void print();
	static long long greatestCommonDivisor(long long numerator, long long denominator);

  private:
	void simplify();
};

#endif // FRACTION_H
Fraction.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <iostream>
#include <cstdlib>

#include "Fraction.h"

Fraction::Fraction(const long long numerator, const long long denominator)
	: numerator{ numerator }
	, denominator{ denominator }
{
	this->simplify();
}

Fraction Fraction::add(const Fraction& other) const
{
	return Fraction{this->numerator * other.denominator + this->denominator * other.numerator
				, this->denominator * other.denominator};
}

Fraction Fraction::operator+(const Fraction& other) const
{
	return Fraction{this->numerator * other.denominator + this->denominator * other.numerator
				, this->denominator * other.denominator};
}

Fraction Fraction::operator*(const Fraction& other) const
{
	return Fraction{this->numerator * other.numerator
				, this->denominator * other.denominator};
}

bool Fraction::read()
{
	std::cin >> this->numerator >> this->denominator;
	this->simplify();
	return static_cast<bool>(std::cin);
}

void Fraction::print()
{
	std::cout << this->numerator << '/' << this->denominator;
}

void Fraction::simplify()
{
	long long gcd = Fraction::greatestCommonDivisor(numerator, denominator);
	numerator /= gcd;
	denominator /= gcd;

	if ( this->denominator < 0 )
	{
		this->numerator *= -1;
		this->denominator *= -1;
	}
}

long long Fraction::greatestCommonDivisor(long long numerator, long long denominator)
{
	numerator = abs(numerator);
	denominator = abs(denominator);

	while ( denominator )
	{
		long long denominator_copy = denominator;
		denominator = numerator % denominator;
		numerator = denominator_copy;
	}
	return numerator;
}

2.2.2. Clase String

Con esta clase se inicia la construcción de la biblioteca ecci. La clase String muestra sobrecarga de operadores como concatenación (+) y indexación ([]), administración de memoria dinámica con C++, la regla de los 5.

main.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include "TestArray.h"
#include "TestList.h"
#include "TestMap.h"
#include "TestString.h"

int main()
{
//	testString();
//	testArray();
//	testList();
	testMap();

	return 0;
}
TestString.h
1
2
3
4
5
6
#ifndef TESTSTRING_H
#define TESTSTRING_H

int testString();

#endif // TESTSTRING_H
TestString.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include "TestString.h"
#include "EcciString.h"

void printEcciString(const ecci::String& tmp)
{
//	tmp.print();
	(void)tmp;
}

void printChar(const ecci::String& string, size_t index)
{
	if ( index < string.getLength() )
		std::cout << "ch='" << string[index] << "'\n";
}

int testString()
{
	ecci::String s1;
//	String* s1bis = new String;
//	s1bis->~String();
//	delete s1bis;
	ecci::String s2{"Algo", 3};
	ecci::String s3("mas");
//	ecci::String s4 = s2 + s3;
//	s1 = 60;
//	s1 = ecci::String(60);
//	printEcciString(87);
//	printEcciString("87");
//	(s2 = s1) = s2;
//	s1 = s2;
	s2[2] = 't';
//	s1 = ecci::String(':') + s2 + ' ' + s3;
	s1 = ':' + s2 + ' ' + s3;
//	s1 = (ecci::String&&)s1;
//	s1 = std::move(s1);
//	int x = 2, y = 5;
//	(x = y = 0) = -2;
//	String s4 = s2 + ' ' + s3;
//	std::cout << s1.getText();
	std::cout << "s1='" << s1 << "'" << std::endl;
//	delete [] s1.text;
//	s1.free();
//	s1.~String();

//	FILE* file = fopen(s1, "r");
//	FILE* file = fopen(static_cast<const char*>(s1), "r");
	return 0;
}
EcciString.h
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
#ifndef STRING_H
#define STRING_H

#include <iostream>

#define implicit

namespace ecci
{

class String
{
  public:
	static const size_t npos = static_cast<size_t>(-1);

  private:
	size_t length;
	char* text;

  public:
	/// Default constructor (ctor)
	/// and Conversion constructor
	implicit String(const char* text = "", size_t length = npos);
	/// Conversion constructor from char
	implicit String(const char ch);
	/// Copy constructor
	String(const String& other);
	/// Move constructor
	String(String&& temp);
	/// Destructor (dtor)
	~String();
	/// Copy assignment operator
	String& operator=(const String& other);
	/// Move assignment operator
	String& operator=(String&& temp);

  public: // Accessors
	inline size_t getLength() const { return this->length; }
	inline const char* getText() const { return this->text; }
	inline const char* c_str() const { return this->text; }
	/// Conversion operator
//	explicit inline operator const char*() const { return this->text; }
	inline char& operator[](const size_t index) { return this->text[index]; }
	inline const char& operator[](const size_t index) const { return this->text[index]; }

  public: // Concatenation
	friend String operator+(const String& first, const String& second);
	friend String operator+(char first, const String& second);
#if 0
	friend String operator+(const String& first, char second);
	friend String operator+(const char* first, const String& second);
	friend String operator+(const String& first, const char* second);
	friend String operator+(short first, const String& second);
	friend String operator+(const String& first, short second);
	friend String operator+(int first, const String& second);
	friend String operator+(const String& first, int second);
	friend String operator+(long first, const String& second);
	friend String operator+(const String& first, long second);
	friend String operator+(long long first, const String& second);
	friend String operator+(const String& first, long long second);
	friend String operator+(unsigned short first, const String& second);
	friend String operator+(const String& first, unsigned short second);
	friend String operator+(unsigned int first, const String& second);
	friend String operator+(const String& first, unsigned int second);
	friend String operator+(unsigned long first, const String& second);
	friend String operator+(const String& first, unsigned long second);
	friend String operator+(unsigned long long first, const String& second);
	friend String operator+(const String& first, unsigned long long second);
	friend String operator+(float first, const String& second);
	friend String operator+(const String& first, float second);
	friend String operator+(double first, const String& second);
	friend String operator+(const String& first, double second);
	friend String operator+(long double first, const String& second);
	friend String operator+(const String& first, long double second);
#endif

  public:
	friend inline std::ostream& operator<<(std::ostream& out, const String& string)
	{
		return out << string.text;
	}

  private:
	/// Capacity constructor
	explicit String(size_t capacity);

  public:
  #ifdef TESTING
	static inline size_t getInstanceCount() { return instanceCount; }
  #endif

  private:
  #ifdef TESTING
	static size_t instanceCount;
  #endif
};


} // namespace ecci

#endif // STRING_H
EcciString.cpp
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#include <cstring>
#include <iostream>

#include "EcciString.h"

namespace ecci
{

  #ifdef TESTING
	size_t String::instanceCount = 0;
  #endif


#if 0
String::String()
	: length{0}
//	, text{nullptr}
//	, text{ static_cast<char*>( calloc(1, sizeof(char) ) ) }
	, text{ new char[1]() }
{
	std::cerr << "String()\n";
}
#endif

String::String(const char* text, size_t length)
	: length{ length == npos ? strlen(text) : length }
	, text{ new char[this->length + 1] }
{
	std::strncpy( this->text, text, this->length + 1);
	this->text[this->length] = '\0';
	std::cerr << "String(" << this->text << ", " << this->length << ")\n";

  #ifdef TESTING
	++instanceCount;
  #endif
}

String::String(const char ch)
	: length{ ch ? 1ull : 0 }
	, text{ new char[this->length + 1]() }
{
	this->text[0] = ch;

  #ifdef TESTING
	++instanceCount;
  #endif
}

String::String(size_t capacity)
	: length{ 0 }
	, text{ new char[capacity] }
{
	text[0] = '\0';
//	*text = '\0';
	std::cerr << "String[" << capacity << "]\n";

  #ifdef TESTING
	++instanceCount;
  #endif
}

String::String(const String& other)
	: length{ other.length }
	, text{ new char[this->length + 1] }
{
	std::strncpy( this->text, other.text, other.length + 1);
	std::cerr << "String{" << this->text << ", " << this->length << "}\n";

  #ifdef TESTING
	++instanceCount;
  #endif
}

String::String(String&& temp)
	: length{ temp.length }
	, text{ temp.text }
{
	temp.length = 0;
	temp.text = nullptr;
	std::cerr << "String&(" << this->text << ", " << this->length << "}\n";

  #ifdef TESTING
	++instanceCount;
  #endif
}

String::~String()
{
	std::cerr << "~String(" << (this->text ? this->text : "-deleted-") << ")\n";
	delete [] this->text;

  #ifdef TESTING
	++instanceCount;
  #endif
}

String& String::operator=(const String& other)
{
	if ( this != &other )
	{
		delete [] this->text;

		this->length = other.length;
		this->text = new char[this->length + 1];
		std::strncpy( this->text, other.text, other.length + 1);
	}
	std::cerr << "String=(" << this->text << ", " << this->length << ")\n";
	return *this;
}

String& String::operator=(String&& temp)
{
	if ( this != &temp )
	{
		delete [] this->text;

		this->length = temp.length;
		this->text = temp.text;

		temp.text = nullptr;
		temp.length = 0;
	}
	std::cerr << "String=&(" << this->text << ", " << this->length << ")\n";
	return *this;
}

String operator+(const String& first, const String& second)
{
	size_t newLength = first.length + second.length;
	String result(newLength + 1);
	std::strncpy(result.text, first.text, first.length);
	std::strncpy(result.text + first.length, second.text, second.length + 1);
	result.length = newLength;
	return result;
}

String operator+(char first, const String& second)
{
	size_t newLength = 1 + second.length;
	String result(newLength + 1);
	result.text[0] = first;
	std::strncpy(result.text + 1, second.text, second.length + 1);
	result.length = newLength;
	return result;
}

} // namespace ecci

2.3. Herencia y polimorfismo

La herencia es un mecanismo de la programación orientada a objetos que permite representar la relación "es un(a)" entre entidades, y reutilizar código. Se implementa en la máquina con redundancia controlada de registros.

El polimorfismo permite que un tipo de datos (llamado supertipo) pueda representar diferentes tipos de datos (subtipos) y que una operación en el supertipo se realice acorde a los subtipos de las entidades representadas. Se implementa en la máquina con tablas de indirección (punteros) a subrutinas polimórficas.

2.3.1. Trivia versión 1

Archivo de preguntas. Cada pregunta consta de un texto y una respuesta. Las preguntas se separan por una línea vacía en el archivo de datos:

trivia1.txt
Cuántos metros hay en un kilómetro?
1000

¿A cuántos metros cuadrados equivale una hectárea?
10000

Si a < b, y b < 0, entonces el valor de a es?
negativo

¿Cuántos lados tiene un triángulo rectángulo?
3

¿Cuál país actualmente es el mayor exportador mundial de piña?
Costa Rica

Después del Sol, la estrella más cercana a la Tierra está en la Constelación de:
Alfa Centauro

Según la leyenda, Tío Sam, la personificación nacional de Estados Unidos, fue de profesión un
carnicero

¿Cuál es el país que ha ganado más certámenes de Miss Universo?
Estados Unidos

La primera copa mundial de futbol en 1930 tuvo sede en este país:
Uruguay

Antónimo de: antónimo
sinónimo

Cuál fue la primera compañía en implementar el concepto de ventanas (windows)
Xerox

El punto de entrada del programa, la función main() se encarga de instanciar el controlador e indicarle que inicie la ejecución. Dado que sólo debe existir una única instancia del controlador Trivia, se usa el patrón singleton que crea la única instancia en el segmento de datos. Dado que el constructor de Trivia es privado, sólo la clase Trivia puede crear esta instancia.

main.cpp
1
2
3
4
5
6
#include "Trivia.h"

int main()
{
	return Trivia::getInstance().run();
}

La clase controladora Trivia contiene la colección de preguntas. Se escogió un arreglo por tener acceso aleatorio, útil para la aleatorización de las preguntas. El controlador carga las preguntas usando un archivo de C++ controlado por un objeto ifstream. Su interfaz es la misma que del objeto std::cin. La clase Question implementa los operadores de flujo operator>>() para lectura y operator<<() para impresión. De esta forma se facilita la lectura de las preguntas como cualquier otro tipo de datos.

Trivia.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#ifndef TRIVIA_H
#define TRIVIA_H

#include <string>
#include <vector>

#include "Common.h"
#include "Question.h"

/**
 * @brief The main controller of the game Trivia
 */
class Trivia
{
	DISABLE_COPY(Trivia);

  private:
	std::vector<Question> questions;
	int score = 0;

  public:
	/**
	 * @brief Impelements the Singleton pattern
	 * @return
	 */
	static Trivia& getInstance();
	/**
	 * Starts the execution of the game
	 * @return Exit code for the OS
	 */
	int run();
	bool askQuestion();

  private:
	Trivia();
	int loadQuestions();
	void printQuestions() const;
};

#endif // TRIVIA_H
Trivia.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <cstdlib>
#include <fstream>
#include <iostream>

#include "Trivia.h"

static const char* const filename = "trivia1.txt";

Trivia::Trivia()
{
}

Trivia& Trivia::getInstance()
{
	static Trivia trivia;
	return trivia;
}

int Trivia::run()
{
	srand( time(nullptr) + clock() );

	if ( int error = this->loadQuestions() )
		return error;

	while ( this->askQuestion() )
		;

	std::cout << "Final score " << this->score << std::endl;
	return 0;
}

bool Trivia::askQuestion()
{
	size_t random = static_cast<size_t>( rand() ) % this->questions.size();
	if ( this->questions[random].ask() )
		++this->score;

	std::cout << std::endl;
	return std::cin.good();
}

int Trivia::loadQuestions()
{
	std::ifstream input(filename);
	if ( ! input )
	{
		std::cerr << "Error: Trivia: could not open " << filename << std::endl;
		return 1;
	}

	Question question;
	while ( input >> question )
		this->questions.push_back( question );

	return 0;
}

void Trivia::printQuestions() const
{
	for ( const Question& question : this->questions )
		std::cout << question;
}

El encabezado Common.h contiene declaraciones que son comunes para todo el proyecto. Provee una macro para deshabilitar o hacer explícita las copias implícitas que implementa el compilador (regla de los 5)

Common.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#ifndef COMMON_H
#define COMMON_H

#define DECLARE_RULE5(Class, action) \
	Class(const Class& other) = action; \
	Class(Class&& other) = action; \
	Class& operator=(const Class& other) = action; \
	Class& operator=(Class&& other) = action

#define DISABLE_COPY(Class) \
	DECLARE_RULE5(Class, delete)

#endif // COMMON_H
Common.cpp
1
#include "Common.h"

La clase Question se encarga de cargarse de un archivo, imprimirse, y efectuar la pregunta al usuario.

Question.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#ifndef QUESTION_H
#define QUESTION_H

#include <iostream>
#include <string>

#include "Common.h"

class Question
{
  public:
	DECLARE_RULE5(Question, default);

  private:
	std::string text;
	std::string answer;

  public:
	Question();
	bool ask();
	friend std::istream& operator>>(std::istream& input, Question& question);
	friend std::ostream& operator<<(std::ostream& output, const Question& question);
};

#endif // QUESTION_H
Question.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <limits>

#include "Question.h"

Question::Question()
{
}

bool Question::ask()
{
	std::cout << this->text << std::endl;
	std::string playerAnswer;
	std::getline( std::cin, playerAnswer );
	bool hit = playerAnswer == this->answer;
	std::cout << (hit ? "Right!" : "Wrong!") << std::endl;
	return hit;
}

std::istream& operator>>(std::istream& input, Question& question)
{
	std::getline(input, question.text);
	std::getline(input, question.answer);
	input.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

	return input;
}

std::ostream& operator<<(std::ostream& output, const Question& question)
{
	output << '[' << question.text << ']' << std::endl;
	output << '{' << question.answer << '}' << std::endl;

	return output;
}

Un proyecto de Qt y un Makefile para construir la aplicación con este IDE o en línea de comandos:

Trivia.pro
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
TEMPLATE = app
QT -= core
CONFIG += console c++11
CONFIG -= qt app_bundle

SOURCES += main.cpp \
	Common.cpp \
	Question.cpp \
	Trivia.cpp

HEADERS += \
	Common.h \
	Question.h \
	Trivia.h
Makefile
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
CC=gcc
CXX=g++
FLAGS=-g -Wall -Wextra
CFLAGS=$(FLAGS) -std=c11
CXXFLAGS=$(FLAGS) -std=c++11
LIBS=

HEADERS=$(wildcard *.h)
SOURCES=$(wildcard *.c*)
COBJECTS=$(SOURCES:.c=.o)
OBJECTS=$(COBJECTS:.cpp=.o)
EXECUTABLE=$(shell basename `pwd`)

$(EXECUTABLE): $(OBJECTS)
	$(CXX) -o $@ $^ $(CFLAGS) $(LIBS)

%.o: %.cpp $(HEADERS)
	$(CXX) -c $< -o $@ $(CXXFLAGS)

%.o: %.c $(HEADERS)
	$(CC) -c -o $@ $< $(CFLAGS)

.PHONY: clean
clean:
	rm -rf $(EXECUTABLE) *.o *.dSYM *~

2.3.2. Trivia versión 2

Archivo de preguntas versión 2. Para poder comparar las preguntas numéricas como tales se agrega a cada pregunta su tipo en el archivo de datos:

trivia2.txt
numeric
Cuántos metros hay en un kilómetro?
1000

numeric
¿A cuántos metros cuadrados equivale una hectárea?
10000

textual
Si a < b, y b < 0, entonces el valor de a es?
negativo

numeric
¿Cuántos lados tiene un triángulo rectángulo?
3

textual
¿Cuál país actualmente es el mayor exportador mundial de piña?
Costa Rica

textual
Después del Sol, la estrella más cercana a la Tierra está en la Constelación de:
Alfa Centauro

textual
Según la leyenda, Tío Sam, la personificación nacional de Estados Unidos, fue de profesión un
carnicero

textual
¿Cuál es el país que ha ganado más certámenes de Miss Universo?
Estados Unidos

textual
La primera copa mundial de futbol en 1930 tuvo sede en este país:
Uruguay

textual
Antónimo de: antónimo
sinónimo

textual
Cuál fue la primera compañía en implementar el concepto de ventanas (windows)
Xerox

El punto de entrada del programa no cambia:

main.cpp
1
2
3
4
5
6
#include "Trivia.h"

int main()
{
	return Trivia::getInstance().run();
}

La clase controladora Trivia contiene la colección de preguntas, pero no se puede almacenar valores de diferentes tipos (preguntas numéricas y textuales) en un arreglo. Se almacenan punteros. En el cargado de las preguntas se lee primero el tipo de la pregunta y se crea un objeto acorde en la memoria dinámica y se agregan al arreglo de punteros a preguntas. Estas deben eliminarse en el destructor ~Trivia().

Trivia.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#ifndef TRIVIA_H
#define TRIVIA_H

#include <string>
#include <vector>

#include "Common.h"

// Forward declaration
class Question;

/**
 * @brief The main controller of the game Trivia
 */
class Trivia
{
	DISABLE_COPY(Trivia);

  private:
	std::vector<Question*> questions;
	int score = 0;

  public:
	/**
	 * @brief Impelements the Singleton pattern
	 * @return
	 */
	static Trivia& getInstance();
	/**
	 * Starts the execution of the game
	 * @return Exit code for the OS
	 */
	int run();
	bool askQuestion();

  private:
	Trivia();
	~Trivia();
	int loadQuestions();
	void printQuestions() const;
};

#endif // TRIVIA_H
Trivia.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#include <cstdlib>
#include <fstream>
#include <iostream>

#include "Trivia.h"
#include "Question.h"

static const char* const filename = "trivia2.txt";

Trivia::Trivia()
{
}

Trivia::~Trivia()
{
	for ( Question* question : this->questions )
		delete question;
}

Trivia& Trivia::getInstance()
{
	static Trivia trivia;
	return trivia;
}

int Trivia::run()
{
	srand( time(nullptr) + clock() );

	if ( int error = this->loadQuestions() )
		return error;

	while ( this->askQuestion() )
		;

	std::cout << "Final score " << this->score << std::endl;
	return 0;
}

bool Trivia::askQuestion()
{
	size_t random = static_cast<size_t>( rand() ) % this->questions.size();
	if ( this->questions[random]->ask() )
		++this->score;

	std::cout << std::endl;
	return std::cin.good();
}

#include "NumericQuestion.h"
#include "TextualQuestion.h"

int Trivia::loadQuestions()
{
	std::ifstream input(filename);
	if ( ! input )
	{
		std::cerr << "Error: Trivia: could not open " << filename << std::endl;
		return 1;
	}

	std::string questionType;
	while ( std::getline(input, questionType) )
	{
		if ( questionType == "numeric" )
		{
			NumericQuestion* question = new NumericQuestion();
			input >> *question;
			this->questions.push_back( question );
		}
		else if ( questionType == "textual" )
		{
			TextualQuestion* question = new TextualQuestion();
			input >> *question;
			this->questions.push_back( question );
		}
		else
			std::cerr << "Trivia: error: unknown question type: " << questionType << std::endl;
	}

	return 0;
}

void Trivia::printQuestions() const
{
	for ( const Question* question : this->questions )
		std::cout << *question;
}

El encabezado Common.h no cambia. Contiene declaraciones que son comunes para todo el proyecto. Provee una macro para deshabilitar o hacer explícita las copias implícitas que implementa el compilador (regla de los 5).

Common.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#ifndef COMMON_H
#define COMMON_H

#define DECLARE_RULE5(Class, action) \
	Class(const Class& other) = action; \
	Class(Class&& other) = action; \
	Class& operator=(const Class& other) = action; \
	Class& operator=(Class&& other) = action

#define DISABLE_COPY(Class) \
	DECLARE_RULE5(Class, delete)

#endif // COMMON_H
Common.cpp
1
#include "Common.h"

La clase Question se encarga de cargarse de un archivo, imprimirse, y efectuar la pregunta al usuario. Sus campos son ahora protegidos para que las clases hijas puedan accederlos. Del método ask() se separa la funcionalidad isRightAnswer() que compara la respuesta del jugador con la respuesta correcta. Este método isRightAnswer() se dice que es polimórfico, porque actúa diferente de acuerdo al tipo de objeto hijo, y se marca con la palabra reservada virtual que le indica al compilador agregar información adicional en el código objeto para producir el comportamiento polimórfico. El destructor debe marcarse también virtual para que se puedan destruir los objetos heredados completos a partir de punteros a la clase base.

Question.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#ifndef QUESTION_H
#define QUESTION_H

#include <iostream>
#include <string>

#include "Common.h"

class Question
{
  public:
	DECLARE_RULE5(Question, default);

  protected:
	std::string text;
	std::string answer;

  public:
	Question();
	virtual ~Question();
	bool ask();
	virtual bool isRightAnswer(const std::string& playerAnswer) const;
	friend std::istream& operator>>(std::istream& input, Question& question);
	friend std::ostream& operator<<(std::ostream& output, const Question& question);
};

#endif // QUESTION_H
Question.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <limits>

#include "Question.h"

Question::Question()
{
}

Question::~Question()
{
}

bool Question::ask()
{
	std::cout << this->text << std::endl;
	std::string playerAnswer;
	std::getline( std::cin, playerAnswer );
	bool hit = this->isRightAnswer(playerAnswer);
	std::cout << (hit ? "Right!" : "Wrong!") << std::endl;
	return hit;
}

bool Question::isRightAnswer(const std::string& playerAnswer) const
{
	return playerAnswer == this->answer;
}

std::istream& operator>>(std::istream& input, Question& question)
{
	std::getline(input, question.text);
	std::getline(input, question.answer);
	input.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

	return input;
}

std::ostream& operator<<(std::ostream& output, const Question& question)
{
	output << '[' << question.text << ']' << std::endl;
	output << '{' << question.answer << '}' << std::endl;

	return output;
}

La clase NumericQuestion se encarga de preguntas cuya respuesta es numérica y debe compararse como tal, es decir, ignorando ceros a la izquierda o espacios en blanco. Se agrega un atributo epsilon que permite al jugador errar por máximo este valor.

NumericQuestion.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#ifndef NUMERICQUESTION_H
#define NUMERICQUESTION_H

#include "Question.h"

class NumericQuestion : public Question
{
  protected:
	double epsilon = 0.5;

  public:
	NumericQuestion();
	virtual ~NumericQuestion() override;
	virtual bool isRightAnswer(const std::string& playerAnswer) const override;
};

#endif // NUMERICQUESTION_H
NumericQuestion.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <cmath>

#include "NumericQuestion.h"

NumericQuestion::NumericQuestion()
{
}

NumericQuestion::~NumericQuestion()
{
}

bool NumericQuestion::isRightAnswer(const std::string& playerAnswer) const
{
	return std::fabs(std::stod(playerAnswer) - std::stod(this->answer)) < epsilon;
}

La clase TextualQuestion realiza preguntas textuales. La comparación de las respuestas del jugador se realizan ignorando mayúsculas y minúsculas.

TextualQuestion.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#ifndef TEXTUALQUESTION_H
#define TEXTUALQUESTION_H

#include "Question.h"

class TextualQuestion : public Question
{
  public:
	TextualQuestion();
};

#endif // TEXTUALQUESTION_H
TextualQuestion.cpp
1
2
3
4
5
6
#include "TextualQuestion.h"

TextualQuestion::TextualQuestion()
{

}

Un proyecto de Qt y un Makefile para construir la aplicación con este IDE o en línea de comandos:

Trivia.pro
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
TEMPLATE = app
QT -= core
CONFIG += console c++11
CONFIG -= qt app_bundle

SOURCES += main.cpp \
	Common.cpp \
	NumericQuestion.cpp \
	Question.cpp \
	TextualQuestion.cpp \
	Trivia.cpp

HEADERS += \
	Common.h \
	NumericQuestion.h \
	Question.h \
	TextualQuestion.h \
	Trivia.h
Makefile
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
CC=gcc
CXX=g++
FLAGS=-g -Wall -Wextra
CFLAGS=$(FLAGS) -std=c11
CXXFLAGS=$(FLAGS) -std=c++11
LIBS=

HEADERS=$(wildcard *.h)
SOURCES=$(wildcard *.c*)
COBJECTS=$(SOURCES:.c=.o)
OBJECTS=$(COBJECTS:.cpp=.o)
EXECUTABLE=$(shell basename `pwd`)

$(EXECUTABLE): $(OBJECTS)
	$(CXX) -o $@ $^ $(CFLAGS) $(LIBS)

%.o: %.cpp $(HEADERS)
	$(CXX) -c $< -o $@ $(CXXFLAGS)

%.o: %.c $(HEADERS)
	$(CC) -c -o $@ $< $(CFLAGS)

.PHONY: clean
clean:
	rm -rf $(EXECUTABLE) *.o *.dSYM *~

2.3.3. Trivia versión 3

Archivo de preguntas versión 3. Incluye un nuevo tipo de prgunta: respuesta de selección única. El objetivo de esta versión es poder agregar el nuevo tipo de pregunta, tratando de hacer el mínimo de modificaciones en el resto del código del proyecto.

trivia3.txt
numeric
Cuántos metros hay en un kilómetro?
1000

single_choice
Quién tiene 4 estómagos?
2
Una gallina
Una vaca
El Botija
Una máquina tragamonedas

single_choice
Cuántos pies tiene un ciempiés?
4
100
74
104
Ninguna de las anteriores

numeric
¿A cuántos metros cuadrados equivale una hectárea?
10000

textual
Si a < b, y b < 0, entonces el valor de a es?
negativo

single_choice
¿Cómo se llama la supercomputadora que controla el Discovery 1 en "2001: Odisea del Espacio"?
2
TMA-1
HAL 9000
ENIAC
HANGAR 68

single_choice
¿El nombre de la sustancia "Nicotina" proviene de:?
1
Jean Nicot que introdujo la planta del tabaco en Francia
De sus componentes: nitrógeno e hidrógeno, y su efecto insecticida
De su descubridor: Francesco Nicota

numeric
¿Cuántos lados tiene un triángulo rectángulo?
3

single_choice
¿Cuál es considerado el animal más venenoso del mundo?
4
La serpiente marina
El alacrán
El sapo dorado
La avispa marina

single_choice
¿La fuerza de gravitación es inversamente proporcional a:?
3
la masa
el peso
la distancia
la constante de gravitación

single_choice
¿Por qué los teclados no están en orden alfabético?
2
Porque el inventor es de apellido Qwerty
Para evitar que los mazos de la máquina de escribir chocaran
Para competir con Dvorak
El proyecto que creó la máquina de escribir se denominaba Qwerty

single_choice
¿De dónde proviene el aguacate?
4
China
India
Australia
América Central

single_choice
¿Cuál de las siguientes afirmaciones es válida?
1
La avispa es más delgada que la abeja y no pierde el aguijón al picar, la abeja sí
La abeja es más delgada que la avispa y no pierde el aguijón al picar, la avispa sí
La abeja es un insecto, la avispa es un arácnido
Ninguna de las anteriores

single_choice
Antes de ser cantante, Julio Iglesias era portero de:
2
Barcelona
Real Madrid
Celta de Vigo
Saprissa

textual
¿Cuál país actualmente es el mayor exportador mundial de piña?
Costa Rica

single_choice
¿Cuál de los siguientes alimentos NO es un veneno para la babosa?
1
Hojas putrefactas
La cerveza
La sal
Compuestos de metaldehido

single_choice
La población de la India y China juntas representa
3
tres cuartas partes de la población mundial
la mitad de la población mundial
un tercio de la población mundial
un cuarto de la población mundial

single_choice
El tejido que hace a los ojos del gato y otros animales reflejar la luz se llama
2
Tejido fotoreflector
Tapete de luz
Captaluces
Halógenos

textual
Después del Sol, la estrella más cercana a la Tierra está en la Constelación de:
Alfa Centauro

single_choice
A partir de este invento de 1877 se pudo en la humanidad grabar y reproducir sonido con éxito:
1
El Fonógrafo de Thomas Alva Edison
El Gramófono de Emile Berliner
El casete de Fritz Pfleumer
La Victrola de RCA

single_choice
La Esvástica es un símbolo inventado por
3
Los Nazis
Los Vikingos
Los Hindúes
Los Egipcios

textual
Según la leyenda, Tío Sam, la personificación nacional de Estados Unidos, fue de profesión un
carnicero

single_choice
"Watson, ven aquí" es una frase célebre asociada al siguiente hecho
3
Descubrimiento del ADN
Aparición de Sherlock Holmes en la radio
Invención del teléfono
Película "Casa Blanca"

single_choice
¿Cuál de los siguientes campos NO es premiado con un Nobel?
3
Física
Química
Biología
Economía

textual
¿Cuál es el país que ha ganado más certámenes de Miss Universo?
Estados Unidos | USA | EEUU

textual
La primera copa mundial de futbol en 1930 tuvo sede en este país:
Uruguay

single_choice
El máximo goleador en la historia del fútbol con 1329 goles es
1
Arthur Friedenreic (Brasil)
Maradona (Argentina)
Pelé (Brasil)
El Chunche (Deporái)

single_choice
El puente más alto del mundo, de 402 metros, es
4
Puente Sutong en China
Golden Gate en Estados Unidos
Puente sobre el Río Virilla, Costa Rica
Puente Baluarte en México

single_choice
El primer sistema operativo de la historia de la informática fue creado por
4
Microsoft
IBM
Apple
General Motors
DARPA
American Airlines

textual
Antónimo de: antónimo
sinónimo

textual
Cuál fue la primera compañía en implementar el concepto de ventanas (windows)
Xerox

single_choice
Según Galileo Galilei el movimiento natural de los cuerpos es:
2
Uniformemente acelerado
Rectilíneo uniforme
Elíptico
Ondulatorio

El punto de entrada del programa no cambia:

main.cpp
1
2
3
4
5
6
#include "Trivia.h"

int main()
{
	return Trivia::getInstance().run();
}

La clase controladora Trivia, casi no cambia. Se le agrega el método createQuestion(type) que recibe un texto con el tipo de pregunta y retorna un puntero a un objeto pregunta correspondiente al tipo dado. El objeto se crea en la memoria dinámica. A este método se le llamará factory method. Contiene lógica de "switch" que se debe evitar a lo largo del programa. Para efectos del curso, este método es el único lugar en el proyecto que puede tener lógica dependiente del tipo de pregunta. De esta manera, en el proyecto sólo habrá un único "switch". Si se agregan varios, se convertirá en una pesadilla darle mantenimiento al código.

Trivia.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#ifndef TRIVIA_H
#define TRIVIA_H

#include <string>
#include <vector>

#include "Common.h"

// Forward declaration
class Question;

/**
 * @brief The main controller of the game Trivia
 */
class Trivia
{
	DISABLE_COPY(Trivia);

  private:
	std::vector<Question*> questions;
	int score = 0;

  public:
	/**
	 * @brief Impelements the Singleton pattern
	 * @return
	 */
	static Trivia& getInstance();
	/**
	 * Starts the execution of the game
	 * @return Exit code for the OS
	 */
	int run();
	bool askQuestion();

  private:
	Trivia();
	~Trivia();
	int loadQuestions();
	void printQuestions() const;
	static Question* createQuestion(const std::string& type);
};

#endif // TRIVIA_H
Trivia.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include <cstdlib>
#include <fstream>
#include <iostream>

#include "Trivia.h"
#include "Question.h"

static const char* const filename = "trivia3.txt";

Trivia::Trivia()
{
}

Trivia::~Trivia()
{
	for ( Question* question : this->questions )
		delete question;
}

Trivia& Trivia::getInstance()
{
	static Trivia trivia;
	return trivia;
}

int Trivia::run()
{
	srand( time(nullptr) + clock() );

	if ( int error = this->loadQuestions() )
		return error;

	while ( this->askQuestion() )
		;

	std::cout << "Final score " << this->score << std::endl;
	return 0;
}

bool Trivia::askQuestion()
{
	size_t random = static_cast<size_t>( rand() ) % this->questions.size();
	if ( this->questions[random]->ask() )
		++this->score;

	std::cout << std::endl;
	return std::cin.good();
}

int Trivia::loadQuestions()
{
	std::ifstream input(filename);
	if ( ! input )
	{
		std::cerr << "Error: Trivia: could not open " << filename << std::endl;
		return 1;
	}

	std::string questionType;
	while ( std::getline(input, questionType) )
	{
		Question* question = createQuestion(questionType);
		if ( question )
		{
			input >> *question;
			this->questions.push_back( question );
		}
		else
		{
			std::cerr << "Trivia: error: unknown question type: " << questionType << std::endl;
			return 1;
		}
	}

	return 0;
}

void Trivia::printQuestions() const
{
	for ( const Question* question : this->questions )
		std::cout << *question;
}

#include "NumericQuestion.h"
#include "TextualQuestion.h"
#include "SingleChoiceQuestion.h"

Question* Trivia::createQuestion(const std::string& type)
{
	if ( type == "numeric" )
		return new NumericQuestion();
	if ( type == "textual" )
		return new TextualQuestion();
	if ( type == "single_choice" )
		return new SingleChoiceQuestion();

	return nullptr;
}

El encabezado Common.h no cambia. Contiene declaraciones que son comunes para todo el proyecto. Provee una macro para deshabilitar o hacer explícita las copias implícitas que implementa el compilador (regla de los 5). Se agregó funciones para eliminar espacio en blanco antes y después de los textos (trim), y para cambiar un texto a minúsculas o mayúsculas. Se usan en TextualQuestion para comparar las respuestas.

Common.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#ifndef COMMON_H
#define COMMON_H

#include <string>

#define DECLARE_RULE5(Class, action) \
	Class(const Class& other) = action; \
	Class(Class&& other) = action; \
	Class& operator=(const Class& other) = action; \
	Class& operator=(Class&& other) = action

#define DISABLE_COPY(Class) \
	DECLARE_RULE5(Class, delete)

// Adapted from http://www.martinbroadhurst.com/how-to-trim-a-stdstring.html
static const char* const whitespace = " \t\n\v\f\r";
std::string& ltrim(std::string& str, const std::string& chars = whitespace);
std::string& rtrim(std::string& str, const std::string& chars = whitespace);
std::string&  trim(std::string& str, const std::string& chars = whitespace);

std::string& tolower(std::string& str);
std::string& toupper(std::string& str);

#endif // COMMON_H
Common.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include "Common.h"

#include <algorithm>

std::string& ltrim(std::string& str, const std::string& chars)
{
    str.erase(0, str.find_first_not_of(chars));
    return str;
}

std::string& rtrim(std::string& str, const std::string& chars)
{
    str.erase(str.find_last_not_of(chars) + 1);
    return str;
}

std::string& trim(std::string& str, const std::string& chars)
{
    return ltrim(rtrim(str, chars), chars);
}

std::string& tolower(std::string& str)
{
	std::transform(str.begin(), str.end(), str.begin(), std::tolower);
	return str;
}

std::string& toupper(std::string& str)
{
	std::transform(str.begin(), str.end(), str.begin(), std::toupper);
	return str;
}

La clase Question se encarga de cargarse de un archivo, imprimirse, y efectuar la pregunta al usuario. El nuevo tipo de pregunta SingleChoiceQuestion tiene formas distintas de cargarse y preguntarse. El cargado lo realizaba la función libre operator>>() que no puede comportarse polimóficamente por no tener parámetro oculto this. Por tanto, se cambia para que invoque un método load() que sí puede serlo. Del método ask() se había separado en la versión 2 la funcionalidad isRightAnswer(). Lo mismo se hace con print() dado que las SingleChoiceQuestion deben imprimir sus opciones.

Dado que todos los tipos de preguntas (numéricas, textuales, y de respuesta única) comparan la respuesta de forma diferente, el método isRightAnswer() se convierte en virtual puro (se le asigna la dirección de memoria 0). Esto provoca que la clase Question se convierta en abstracta. De una clase abstracta no se pueden construir objetos, lo cual tiene sentido, porque en este juego de Trivia "no tienen sentido las preguntas puras". Las clases hijas que no sobrescriban un método virtual puro continuarán siendo abstractas. Las clases que no tengan métodos virtuales puros serán concretas y podrán crear instancias.

Question.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#ifndef QUESTION_H
#define QUESTION_H

#include <iostream>
#include <string>

#include "Common.h"

class Question
{
  public:
	DECLARE_RULE5(Question, default);

  protected:
	std::string text;
	std::string answer;

  public:
	Question();
	virtual ~Question();
	virtual std::istream& load(std::istream& input, bool discardEmptyLine = true);
	virtual void print() const;
	bool ask();
	// Pure-virtual method
	virtual bool isRightAnswer(const std::string& playerAnswer) const = 0;
	friend std::istream& operator>>(std::istream& input, Question& question);
	friend std::ostream& operator<<(std::ostream& output, const Question& question);
};

#endif // QUESTION_H
Question.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <limits>

#include "Question.h"

Question::Question()
{
}

Question::~Question()
{
}

bool Question::ask()
{
	this->print();
	std::string playerAnswer;
	if ( ! std::getline( std::cin, playerAnswer ).good() )
		return false;
//	bool hit = this->vtable[3](playerAnswer);
	bool hit = this->isRightAnswer(playerAnswer);
	std::cout << (hit ? "Right!" : "Wrong!") << std::endl;
	return hit;
}

void Question::print() const
{
	std::cout << this->text << std::endl;
}

#if 0
bool Question::isRightAnswer(const std::string& playerAnswer) const
{
	return playerAnswer == this->answer;
}
#endif

std::istream& Question::load(std::istream& input, bool discardEmptyLine)
{
	std::getline(input, this->text);
	std::getline(input, this->answer);

	if ( discardEmptyLine)
		input.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

	return input;
}

std::istream& operator>>(std::istream& input, Question& question)
{
//	return question.vtable[1](input);
	return question.load(input);
}

std::ostream& operator<<(std::ostream& output, const Question& question)
{
	output << '[' << question.text << ']' << std::endl;
	output << '{' << question.answer << '}' << std::endl;

	return output;
}

La clase NumericQuestion se encarga de preguntas cuya respuesta es numérica y debe compararse como tal. No cambia respecto a la versión anterior.

NumericQuestion.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#ifndef NUMERICQUESTION_H
#define NUMERICQUESTION_H

#include "Question.h"

class NumericQuestion : public Question
{
  protected:
	double epsilon = 0.5;

  public:
	NumericQuestion();
	virtual ~NumericQuestion() override;
	virtual bool isRightAnswer(const std::string& playerAnswer) const override;
};

#endif // NUMERICQUESTION_H
NumericQuestion.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <cmath>

#include "NumericQuestion.h"

NumericQuestion::NumericQuestion()
{
}

NumericQuestion::~NumericQuestion()
{
}

bool NumericQuestion::isRightAnswer(const std::string& playerAnswer) const
{
	return std::fabs(std::stod(playerAnswer) - std::stod(this->answer)) < epsilon;
}

La clase TextualQuestion realiza preguntas textuales. Al hacer el método isRightAnswer() virtual puro en Question, tiene que sobrescribirlo. La implementación hace la comparación de las respuestas del jugador se realizan ignorando mayúsculas y minúsculas, y se elimina el espacio en blanco redundante antes y después de las respuestas. Se emplean las funciones reutilizables trim() y tolower() que se agregaron a Common.h/cpp.

TextualQuestion.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#ifndef TEXTUALQUESTION_H
#define TEXTUALQUESTION_H

#include "Question.h"

class TextualQuestion : public Question
{
  public:
	TextualQuestion();
	virtual bool isRightAnswer(const std::string& playerAnswer) const override;
};

#endif // TEXTUALQUESTION_H
TextualQuestion.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <cctype>

#include "TextualQuestion.h"
#include "Common.h"

TextualQuestion::TextualQuestion()
{

}

bool TextualQuestion::isRightAnswer(const std::string& playerAnswer) const
{
	std::string trimmedPlayerAnswer = playerAnswer;
	trim(trimmedPlayerAnswer);
	tolower(trimmedPlayerAnswer);

	std::string trimmedRightAnswer = this->answer;
	trim(trimmedRightAnswer);
	tolower(trimmedRightAnswer);

	return trimmedPlayerAnswer == trimmedRightAnswer;
}

La clase SingleChoiceQuestion tiene opciones (también conocidas como categorías o enumeraciones). El jugador debe seleccionar una de las opciones y responder con su número. Al sobrescribir el método virtual puro isRightAnswer(), realiza la comparación usando enteros. El cargado es distinto al resto de preguntas, dado que después de la respuesta vienen las opciones. El cargado invoca la versión de la clase base para reutilizar su código, lo cual es una buena práctica. Sin embargo, la versión de la clase base Question::load() consumía la línea en blanco después de la respuesta. Por tanto se agregó un parámetro discardEmptyLine que las clases hijas pueden ajustar de acuerdo a su necesidad. Al agregar este parámetro, se rompe el polimorfismo en todas las clases hijas. Gracias al marcador override en las clases hijas, el compilador advierte cuando el polimorfismo se rompe, lo que brinda al programador la oportunidad de corregirlo.

SingleChoiceQuestion.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#ifndef SINGLECHOICEQUESTION_H
#define SINGLECHOICEQUESTION_H

#include <string>
#include <vector>

#include "Question.h"

class SingleChoiceQuestion : public Question
{
  protected:
	std::vector<std::string> choices;

  public:
	SingleChoiceQuestion();
	virtual std::istream& load(std::istream& input, bool discardEmptyLine = false) override;
	virtual void print() const override;
	virtual bool isRightAnswer(const std::string& playerAnswer) const override;
};

#endif // SINGLECHOICEQUESTION_H
SingleChoiceQuestion.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include "SingleChoiceQuestion.h"

SingleChoiceQuestion::SingleChoiceQuestion()
{

}

std::istream& SingleChoiceQuestion::load(std::istream& input, bool discardEmptyLine)
{
	(void)discardEmptyLine;
	Question::load(input, false);

	std::string choice;
	while ( std::getline(input, choice) && ! choice.empty() )
		this->choices.push_back(choice);

	return input;
}

void SingleChoiceQuestion::print() const
{
	Question::print();
	for ( size_t index = 0; index < this->choices.size(); ++index )
		std::cout << "  " << index + 1 << ". " << this->choices[index] << std::endl;
}

bool SingleChoiceQuestion::isRightAnswer(const std::string& playerAnswer) const
{
	return std::stoll(playerAnswer) == std::stoll(this->answer);
}

Un proyecto de Qt y un Makefile para construir la aplicación con este IDE o en línea de comandos:

Trivia.pro
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
TEMPLATE = app
QT -= core
CONFIG += console c++11
CONFIG -= qt app_bundle

SOURCES += main.cpp \
	Common.cpp \
	NumericQuestion.cpp \
	Question.cpp \
	SingleChoiceQuestion.cpp \
	TextualQuestion.cpp \
	Trivia.cpp

HEADERS += \
	Common.h \
	NumericQuestion.h \
	Question.h \
	SingleChoiceQuestion.h \
	TextualQuestion.h \
	Trivia.h
Makefile
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
CC=gcc
CXX=g++
FLAGS=-g -Wall -Wextra
CFLAGS=$(FLAGS) -std=c11
CXXFLAGS=$(FLAGS) -std=c++11
LIBS=

HEADERS=$(wildcard *.h)
SOURCES=$(wildcard *.c*)
COBJECTS=$(SOURCES:.c=.o)
OBJECTS=$(COBJECTS:.cpp=.o)
EXECUTABLE=$(shell basename `pwd`)

$(EXECUTABLE): $(OBJECTS)
	$(CXX) -o $@ $^ $(CFLAGS) $(LIBS)

%.o: %.cpp $(HEADERS)
	$(CXX) -c $< -o $@ $(CXXFLAGS)

%.o: %.c $(HEADERS)
	$(CC) -c -o $@ $< $(CFLAGS)

.PHONY: clean
clean:
	rm -rf $(EXECUTABLE) *.o *.dSYM *~

3. Programación genérica

La máquina provee cuatro (realmente tres) formas de usar el almacenamiento (memoria):

  1. Variable: un espacio para almacenar un único valor.

  2. Indirección: un espacio para almacenar un número entero sin signo que es la dirección de algo almacenado en la memoria. Realmente es un caso especial de la forma anterior.

  3. Arreglo: una región continua de la memoria para almacenar varios valores (variables) del mismo tipo.

  4. Registro: una región continua de la memoria para almacenar varios valores (variables) que pueden tener tipos distintos.

Para almacenar varios valores se usan colecciones, que son estructuras de datos. Las estructuras pueden clasificarse en:

  1. De memoria continua: arreglos, matrices, arreglos multimensionales.

  2. De memoria discontinua: pilas, colas, listas, árboles, grafos.

  3. Híbridos de las dos anteriores.

3.1. Arreglo dinámico genérico

Se llama "arreglo dinámico" no sólo porque almacena elementos en la memoria dinámica, sino porque su tamaño puede crecer o decrecer en el tiempo. Es un objeto que internamente controla un arreglo, y puede cambiarlo por otro cuando se necesita más o menos elementos. El uso de plantillas permite que el compilador pueda crear clases Array en tiempo de compilación con diferentes tipos de datos. El programa de pruebas encuentra la mediana de valores double dados en la entrada estándar sin conocer la cantidad de ellos, y prueba almacenando cadenas de caracteres ecci::String.

main.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include "TestArray.h"
#include "TestList.h"
#include "TestMap.h"
#include "TestString.h"

int main()
{
//	testString();
//	testArray();
//	testList();
	testMap();

	return 0;
}
TestArray.h
1
2
3
4
5
6
#ifndef TESTARRAY_H
#define TESTARRAY_H

int testArray();

#endif // TESTARRAY_H
TestArray.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <iostream>

#include "TestArray.h"
#include "Array.h"
#include "EcciString.h"

int testMedian(void);
int testStringArray(void);

// main:
int testArray(void)
{
	testMedian();
	testStringArray();

	return 0;
}

ecci::Array<double> readArray(ecci::Array<double> values)
{
	// Read values into the array
	double value = 0.0;
	while ( std::cin >> value )
		values.append(value);

	return values;
}

int testMedian(void)
{
	ecci::Array<double> values;
	values = readArray(values);
//	values = values;

	// Sort the array of values
	values.sort();

	// If value count is odd
	double median = 0.0;
	if ( values.getCount() % 2 )
	{
		// Create median as the value at the center of the array
		median = values[ values.getCount() / 2 ];
	}
	// Else
	else
	{
		// Create median as the average of the two values at the center of the array
		median = (values[ values.getCount() / 2 - 1 ] + values[ values.getCount() / 2 ]) / 2.0;
	}

	// Print median
	std::cout << values.getCount() << " elements" << std::endl;
	std::cout << "median = " <<  median << std::endl;

	return 0;
}

int testStringArray(void)
{
	ecci::Array<ecci::String> texts;

	texts.append("text 1");
	texts.append("text 2");
	texts.append("text 3");

	for ( size_t index = 0; index < texts.getCount(); ++index )
		std::cout << index << ": " << texts[index] << std::endl;

	return 0;
}

La notación de las plantillas de C++ es compleja. Un template afecta la declaración que le sigue, que puede ser un registro (clase, estructura) o una subrutina. Todas las plantillas deben estar en el archivo de encabezado para que el compilador pueda expandirlas en tiempo de compilación.

Array.h
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#ifndef ARRAY_H
#define ARRAY_H

#include <cstddef>

namespace ecci
{

template <typename DataType>
class Array
{
  private:
	size_t capacity;
	size_t count;
	DataType* elements;

  public:
	Array();
	/// Copy constructor
	Array(const Array& other);
	/// Move constructor
	Array(Array&& other);
	/// Destructor
	~Array();
	/// Copy assignment operator
	Array& operator=(const Array& other);
	/// Move assignment operator
	Array& operator=(Array&& other);

  public:
	inline size_t getCount() const { return this->count; }
	bool append(const DataType& value);
	void sort();
	inline DataType& operator[](size_t index) { return this->elements[index]; }
	inline const DataType& operator[](size_t index) const { return this->elements[index]; }

  private:
	bool increaseCapacity();
};

} // namespace ecci

#include <algorithm>


namespace ecci
{

static const size_t INITIAL_CAPACITY = 10;
static const size_t INCREASE_FACTOR = 10;

template <typename DataType>
Array<DataType>::Array()
	: capacity{ INITIAL_CAPACITY }
	, count{ 0 }
	, elements{ new DataType[this->capacity]() }
{
}

template <typename DataType>
Array<DataType>::Array(const Array& other)
	: capacity{ other.capacity }
	, count{ other.count }
	, elements{ new DataType[this->capacity]() }
{
	for ( size_t index = 0; index < this->count; ++index )
		this->elements[index] = other.elements[index];
}

template <typename DataType>
Array<DataType>::Array(Array&& other)
	: capacity{ other.capacity }
	, count{ other.count }
	, elements{ other.elements }
{
	other.capacity = other.count = 0;
	other.elements = nullptr;
}

template <typename DataType>
Array<DataType>::~Array()
{
	delete [] this->elements;
}

template <typename DataType>
Array<DataType>& Array<DataType>::operator=(const Array& other)
{
	if ( this != &other )
	{
		if ( this->capacity != other.capacity )
		{
			delete [] this->elements;
			this->capacity = other.capacity;
			this->elements = new DataType[this->capacity]();
		}

		for ( this->count = 0; this->count < other.count; ++this->count )
			this->elements[this->count] = other.elements[this->count];
	}
	return *this;
}

template <typename DataType>
Array<DataType>& Array<DataType>::operator=(Array&& other)
{
	if ( this != &other )
	{
		delete [] this->elements;

		this->capacity = other.capacity;
		this->count = other.count;
		this->elements = other.elements;

		other.capacity = other.count = 0;
		other.elements = nullptr;
	}

	return *this;
}

template <typename DataType>
bool Array<DataType>::append(const DataType& value)
{
	if ( this->count == this->capacity )
		if ( ! this->increaseCapacity() )
			return false;

	this->elements[this->count++] = value;
	return true;
}

template <typename DataType>
void Array<DataType>::sort()
{
	std::sort(this->elements, this->elements + this->count);
}

template <typename DataType>
bool Array<DataType>::increaseCapacity()
{
	const size_t newCapacity = INCREASE_FACTOR * this->capacity;
	DataType* newElements = new DataType[newCapacity]();
	if ( newElements == nullptr )
		return false;
	for ( size_t index = 0; index < this->count; ++index )
		newElements[index] = this->elements[index];
	delete [] this->elements;
	this->elements = newElements;
	this->capacity = newCapacity;
	return true;
}


} // namespace ecci


#endif // ARRAY_H
Array.cpp
1
#include "Array.h"

3.1.1. Matrices en C++ [extra]

El siguiente ejemplo muestra plantillas para generar funciones que crean matrices de cualquier tipo en C++.

create_matrix.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <cstddef>

template <typename DataType>
void destroy_matrix(DataType** matrix, const size_t rows)
{
	if ( matrix )
		for ( size_t row = 0; row < rows; ++row )
			delete [] matrix[row];

	delete [] matrix;
}

template <typename DataType>
DataType** create_matrix(const size_t rows, const size_t columns)
{
	DataType** matrix = new DataType*[rows]();
	if ( matrix == nullptr )
		return nullptr;

	for ( size_t row = 0; row < rows; ++row )
	{
		if ( ( matrix[row] = new DataType[columns]() ) == nullptr )
		{
			destroy_matrix(matrix, rows);
			return nullptr;
		}
	}

	return matrix;
}

#include <iostream>

template <typename DataType>
void print_matrix(const char* name, DataType** matrix, const size_t rows, const size_t columns)
{
	if ( name )
		std::cout << name << ':' << std::endl;

	for ( size_t row = 0; row < rows; ++row )
		for ( size_t column = 0; column < columns; ++column )
			std::cout << matrix[row][column]
				<< (column < columns - 1 ? ' ' : '\n');
}

int main()
{
	bool**   matrix1 = create_matrix<bool>(3, 4);
	char**   matrix2 = create_matrix<char>(2, 5);
	double** matrix3 = create_matrix<double>(6, 2);

	print_matrix("matrix1", matrix1, 3, 4);
	print_matrix("matrix2", matrix2, 2, 5);
	print_matrix("matrix3", matrix3, 6, 2);

	destroy_matrix(matrix1, 3);
	destroy_matrix(matrix2, 2);
	destroy_matrix(matrix3, 6);
}

La plantilla Array puede usarse para crear matrices, que además se encarga de la destrucción de la memoria automáticamente. Lo mismo ocurre con la plantilla de clase std::vector de la biblioteca estándar de C++. El siguiente código muestra cómo crear una matriz de dimensiones rows y columns:

#include <vector>

std::vector<std::vector<bool>> matrix(rows, std::vector<bool>(columns));

3.2. Lista enlazada genérica

Las estructuras de datos de memoria discontinua usan registros, para combinar los valores que se quieren almacenar, junto con variables de indirección (punteros/referencias) para saber cuáles otros valores están relacionados. A estos registros se les llaman nodos. La cantidad de valores de indirección en cada nodo distinguen el tipo de estructura:

  • 1 referencia por nodo: pila, cola, lista.

  • 2 referencias por nodo: árboles binarios.

  • n referencias por nodo: árboles n-arios.

El siguiente ejemplo permite almacenar nombres en una lista doblemente enlazada:

TestList.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <string>

#include "TestList.h"
#include "List.h"

int testList()
{
	ecci::List<std::string> list;

	std::string name;
	while ( std::cin >> name )
		list.append( name );

//	list.print();
//	for ( size_t index = 0; index < list.getCount(); ++index )
//		std::cout << list[index] << std::endl;

	for (typename ecci::List<std::string>::Iterator itr = list.begin(); itr != list.end(); itr++)
		std::cout << '[' << *itr << ']' << std::endl;

	return 0;
}

List.h

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#ifndef LIST_H
#define LIST_H

#include <cstddef>

namespace ecci
{

template <typename DataType>
class List
{
  private:
	struct Node
	{
	  public:
		Node* previous;
		DataType data;
		Node* next;

	  public:
		explicit Node(const DataType& data, Node* previous = nullptr, Node* next = nullptr)
			: previous{previous}
			, data{data}
			, next{next}
		{
		}
	};

  private:
	size_t count;
	Node* first;
	Node* last;

  public:
	List()
		: count{0}
		, first{nullptr}
		, last{nullptr}
	{
	}

	~List()
	{
		clear();
	}

	bool append(const DataType& data)
	{
		Node* node = new Node(data, this->last);
		if ( node == nullptr )
			return false;

		if ( this->isEmpty() )
			this->first = this->last = node;
		else
			this->last = this->last->next = node;

		++this->count;
		return true;
	}

	inline size_t getCount() const
	{
		return this->count;
	}

	inline bool isEmpty() const
	{
		return this->count == 0;
	}

  #if 0
	void print() const
	{
		for (const Node* node = this->first; node; node = node->next)
			std::cout << '[' << node->data << ']' << std::endl;
	}
  #endif

	void clear()
	{
		while ( this->first )
		{
			Node* node = this->first->next;
			delete this->first;
			this->first = node;
		}

		this->count = 0;
		this->last = nullptr;
	}

  public:
	class Iterator
	{
	  private:
		Node* node;

	  public:
		explicit Iterator(Node* node = nullptr)
			: node{node}
		{
		}

		inline bool operator!=(const Iterator& other) const
		{
			return this->node != other.node;
		}

		inline DataType& operator*()
		{
			return node->data;
		}

		inline const DataType& operator*() const
		{
			return node->data;
		}

		inline Iterator& operator++()
		{
			this->node = this->node->next;
			return *this;
		}

		inline Iterator operator++(int)
		{
			Iterator original = *this;
			this->node = this->node->next;
			return original;
		}
	};

  public:
	inline Iterator begin()
	{
		return Iterator( this->first );
	}

	inline Iterator end()
	{
		return Iterator( nullptr );
	}
};

} // namespace ecci

#endif // LIST_H

3.3. Árboles

El árbol binario de búsqueda es un árbol con dos valores de indirección en cada nodo (árbol n-ario con n=2). Para asegurar que el acceso y la inserción/eliminación de los elementos sea en tiempo logarítmico impone una restricción: ordenamiento de acuerdo a algún criterio (típicamente la función matemática menor-que). La restricción establece que todos los elementos (nodos) que están a la izquierda de un elemento (nodo) E cumplen el criterio de ordenamiento (por ejemplo, ser menores), y todos los elementos que incumplen el criterio (por ejemplo, ser mayores o iguales) estarán a la derecha del elemento E.

Para realmente asegurar el acceso logarítmico, el árbol además debe estar balanceado, lo que es tema de cursos posteriores. El siguiente ejemplo cuenta ocurrencias de palabras leías de la entrada estándar.

TestMap.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
#include <string>
#include <vector>

#include "Map.h"
#include "TestMap.h"

typedef ecci::Map<size_t, std::vector<std::string>, ecci::GreaterThan<size_t>> TopWords;

int testMap()
{
	ecci::Map<std::string, size_t> occurrences;
//	ecci::Map<std::string, size_t> map2(occurrences);

//	const ecci::Map<std::string, size_t>& occ = occurrences;
//	size_t value = occ["de"];

	std::string word;
	while ( std::cin >> word )
	{
		++occurrences[word];
//		ecci::Map<std::string, size_t>::Iterator iterator = ocurrences.insert(word, 0);
//		++iterator.getValue();
	}
	for ( ecci::Map<std::string, size_t>::ConstIterator itr = occurrences.cbegin(); itr != occurrences.cend(); ++itr )
		std::cout << itr.getKey() << '\t' << itr.getValue() << std::endl;

	std::cout << "---------------------------\n";

	TopWords topWords;
	for ( ecci::Map<std::string, size_t>::ConstIterator itr = occurrences.cbegin(); itr != occurrences.cend(); ++itr )
		topWords[ itr.getValue() ].push_back( itr.getKey() );

	for ( TopWords::ConstIterator itr = topWords.cbegin(); itr != topWords.cend(); ++itr )
	{
		std::cout << itr.getKey() << '\t';
		const std::vector<std::string>& words = itr.getValue();
		for ( const std::string& word : words)
			std::cout << word << ' ';
		std::cout << std::endl;
	}

	return 0;
}

Map.h

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
#ifndef MAP_H
#define MAP_H

#include <cassert>
#include <cstddef>

#define DECLARE_RULE5(Class, action) \
	Class(const Class& other) = action; \
	Class(Class&& other) = action; \
	Class& operator=(const Class& other) = action; \
	Class& operator=(Class&& other) = action

#define DISABLE_COPY(Class) \
	DECLARE_RULE5(Class, delete)

namespace ecci
{

template <typename KeyType>
struct LessThan
{
	inline bool operator()(const KeyType& first, const KeyType& second) const
	{
		return first < second;
	}
};

template <typename KeyType>
struct GreaterThan
{
	inline bool operator()(const KeyType& first, const KeyType& second) const
	{
		return first > second;
	}
};

template <typename KeyType, typename ValueType, typename Comparator = LessThan<KeyType>>
class Map
{
	DISABLE_COPY(Map);

  private:
	struct Node
	{
		DISABLE_COPY(Node);

	  public:
		Node* parent;
		KeyType key;
		ValueType value;
		Node* left;
		Node* right;

	  public:
		Node(const KeyType& key, const ValueType& value = ValueType()
			, Node* parent = nullptr, Node* left = nullptr, Node* right = nullptr)
			: parent{parent}
			, key{key}
			, value{value}
			, left{left}
			, right{right}
		{
		}

		~Node()
		{
			delete this->left;
			delete this->right;
		}
	};

  private:
	size_t count;
	Node* root;

  public:
	Map()
		: count{0}
		, root{nullptr}
	{
	}

	~Map()
	{
		clear();
	}

	inline size_t getCount() const
	{
		return this->count;
	}

	inline bool isEmpty() const
	{
		return this->root == nullptr;
	}

	void clear()
	{
		delete this->root;
		this->root = nullptr;
		this->count = 0;
	}

	ValueType& operator[](const KeyType& key)
	{
		Node* node = this->insert(key);
		assert(node);
		return node->value;
	}

  #if 0
	const ValueType& operator[](const KeyType& key) const
	{
		Node* node = this->insert(key);
		assert(node);
		return node->value;
	}
  #endif

  public:
	class ConstIterator
	{
	  public:
		DECLARE_RULE5(ConstIterator, default);

	  private:
		Node* node;

	  public:
		explicit ConstIterator(Node* node = nullptr)
			: node{node}
		{
		}

		~ConstIterator() = default;

		inline bool operator!=(const ConstIterator& other) const
		{
			return this->node != other.node;
		}

		inline const KeyType& getKey() const
		{
			return this->node->key;
		}

		inline const ValueType& getValue() const
		{
			return this->node->value;
		}

		inline ConstIterator& operator++()
		{
			this->node = Map::findNextNode(this->node);
			return *this;
		}
	}; // class ConstIterator

	inline ConstIterator cbegin() const
	{
		return ConstIterator( this->findMinimum(this->root) );
	}

	inline ConstIterator cend() const
	{
		return ConstIterator( nullptr );
	}

  private:
	Node* insert(const KeyType& key, const ValueType& value = ValueType())
	{
		++this->count;

		if ( this->isEmpty() )
			return this->root = new Node(key, value);

		Comparator comparator;
		Node* current = this->root;
		while ( true )
		{
			if ( comparator(key, current->key) )
			{
				if ( current->left == nullptr )
					return current->left = new Node(key, value, current);
				else
					current = current->left;
			}
			else if ( comparator(current->key, key) )
			{
				if ( current->right == nullptr )
					return current->right = new Node(key, value, current);
				else
					current = current->right;
			}
			else
			{
				--this->count;
				return current;
			}
		}
	}

	static Node* findMinimum(Node* root)
	{
		Node* current = root;
		while ( current && current->left )
			current = current->left;
		return current;
	}

	static Node* findNextNode(Node* current)
	{
		Node* original = current;

		if ( current->right )
			return findMinimum(current->right);

		while ( current->parent && current == current->parent->right )
		{
			current = current->parent;
			if ( current->parent == nullptr )
				return nullptr;
		}

		if ( current->parent && current == current->parent->left )
			current = current->parent;

		return current == original ? nullptr : current;
	}
};

} // namespace ecci

#endif // MAP_H
Note
El tema de Herencia y polimorfismo es parte del Paradigma orientado a objetos, y por tanto se movió a la sección 2.3 de este documento.