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:
-
Leer un valor(es)
-
Imprimir un valor(es)
-
Crear una variable con un valor
-
Cambiarle el valor a una variable
-
Condicionar una instrucción
-
Repetir una instrucción
-
Hacer una subtarea
-
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
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
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:
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.
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
):
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:
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
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).
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.
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).
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).
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()
.
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).
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.
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:
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.
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;
}
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
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.
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;
}
1
2
3
4
5
6
#ifndef TESTSTRING_H
#define TESTSTRING_H
int testString();
#endif // TESTSTRING_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
#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;
}
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
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:
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.
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.
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
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)
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
1
#include "Common.h"
La clase Question
se encarga de cargarse de un archivo, imprimirse, y efectuar la pregunta al usuario.
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
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:
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
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:
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:
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()
.
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
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).
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
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.
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
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.
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
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.
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
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:
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
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.
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:
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.
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
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.
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
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.
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
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.
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
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
.
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
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.
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
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:
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
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):
-
Variable: un espacio para almacenar un único valor.
-
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.
-
Arreglo: una región continua de la memoria para almacenar varios valores (variables) del mismo tipo.
-
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:
-
De memoria continua: arreglos, matrices, arreglos multimensionales.
-
De memoria discontinua: pilas, colas, listas, árboles, grafos.
-
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
.
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;
}
1
2
3
4
5
6
#ifndef TESTARRAY_H
#define TESTARRAY_H
int testArray();
#endif // TESTARRAY_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
#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.
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
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++.
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:
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.
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. |