1. Programación procedimental

1.1. Entrada y salida

1.1.1. Número de la apuesta

El siguiente programa pregunta una cantidad de dinero a apostar, luego imprime con formato los números en la salida estándar de forma amigable.

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

int main(void)
{
	const int lucky_number = 46;
	const short day = 15;

	double money = 0.0;
	scanf("%lg", &money);
	printf("Apueste %.2lf colones al %d este domingo %hd\n", money, lucky_number, day);

	return 0;
}

Si copia el código anterior en un archivo bet.c puede compilarlo y ejecutar el programa con los comandos siguientes. Los textos precedidos por $ son comandos escritos por el usuario, y # son comentarios:

# Compilar y generar ejecutable llamado bet
$ cc -g -Wall -Wextra bet.c -o bet

# Correr el programa llamado bet
$ ./bet

1.1.2. Formatear la lista de clase

eMatrícula genera listas de clase con exceso de espacio en blanco. El siguiente es un ejemplo con datos ficticios. Supóngase que el archivo se llama lista.txt.

1
2
3
4
5
6
7
8
9
B62423 \t 8 \t  RIOS JUAREZ BLANCA ROSA  \t
\t\t
\t\t
B82342 \t 8.19 \t RODRIGUEZ GOMEZ IGNACIO  \t
\t\t
\t\t
B83472 \t 9.71 \t BRENES SOTO ANA IRIS  \t
\t\t
\t\t

El siguiente programa lee una lista de clase generada por eMatricula y la imprime con formato. El programa ignora el espacio en blanco redundante generado por eMatricula.

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

int main(void)
{
	char id[6 + 1];
	char name[65];
	double ppm = 0.0;
	while ( scanf("%6s %lf %64[^\n]", id, &ppm, name) == 3 )
		printf("%6s %5.2lf %64s\n", id, ppm, name);

	return 0;
}

Si copia el código anterior en un archivo format.c, este comando lo compila en un ejecutable:

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

Al invocar el programa con la lista.txt como entrada estándar, produciría una salida:

$ ./format < lista.txt
B62423  8.00                                        RIOS JUAREZ BLANCA ROSA
B82342  8.19                                        RODRIGUEZ GOMEZ IGNACIO
B83472  9.71                                           BRENES SOTO ANA IRIS

1.1.3. Archivos nombrados

La siguiente variación del programa que formatea la lista de clase puede recibir un nombre de archivo por argumento de línea de comandos. Si se omite, supone que los datos se proveerán en la en 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
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
	char id[6 + 1];
	char name[65];
	double ppm = 0.0;
	bool is_named_file = false;

	//FILE* input = fopen("input001.txt", "r");
	FILE* input = stdin;
	if ( argc >= 2 )
	{
		input = fopen(argv[1], "r");
		if ( ! input )
		{
			fprintf(stderr, "format2: error: could not open %s\n", argv[1]);
			return 1; // EXIT_FAILURE
		}
		is_named_file = true;
	}

	while ( fscanf(input, "%6s %lf %64[^\n]", id, &ppm, name) == 3 )
		fprintf(stdout, "%6s %5.2lf %64s\n", id, ppm, name);

	//if ( argc >= 2 )
	if ( is_named_file )
		fclose(input);

	return EXIT_SUCCESS;
}

El programa se puede invocar de las siguientes dos formas

# Lee los datos del archivo lista.txt
$ ./format2 lista.txt
B62423  8.00                                        RIOS JUAREZ BLANCA ROSA
B82342  8.19                                        RODRIGUEZ GOMEZ IGNACIO
B83472  9.71                                           BRENES SOTO ANA IRIS

# Lee los datos de la entrada estándar
$ ./format2 < lista.txt
B62423  8.00                                        RIOS JUAREZ BLANCA ROSA
B82342  8.19                                        RODRIGUEZ GOMEZ IGNACIO
B83472  9.71                                           BRENES SOTO ANA IRIS

# Si se da un nombre de archivo que no existe
$ ./format2 jiji.txt
format2: error: could not open jiji.txt

1.2. Expresiones y condicionales

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

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

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

  1. Leer un valor(es)

  2. Imprimir un valor(es)

  3. Crear una variable con un valor

  4. Cambiarle el valor a una variable

  5. Condicionar una instrucción

  6. Repetir una instrucción

  7. Hacer una subtarea

  8. Definir una subtarea

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

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

1.2.1. Eliminar la nota más baja

Enunciado del problema

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

% Rubro

30%

10 laboratorios

20%

10 quices

50%

3 exámenes

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

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

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

Entrada de ejemplo:

2

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

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

 

Salida de ejemplo:

Grupo 1:
B18019 55.27 57.54

Grupo 2:
B50908 67.37 70.19
B68901 70.04 72.51

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

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

Algoritmo parcial del Grupo 04

 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
main:
	Read group count

	Repeat group_index for group count
		//for ( int group_index = 0; group_index < group_count; ++group_index )
		Read group number
		Read student_count

		Repeat student_index for student_count
			Read student_id
			Create lab_grades as an array of 10 real numbers
			Read lab_grades
			...
			Read professor_grade
			Create lab_lowest_grade as the result of calculating the lowest grade
			Create quiz_lowest_grade as the result of calculating the lowest grade
			Create lab_sum as the sum of all labs
			Create right_lab_grade as (lab_sum - lab_lowes_grade)/9
			...

Calculate the lowest grade:
	Create min as positive infinite
	For each grade in grades
		If grade is lower than min then
			Change min as grade
	Return min

1.3. Indirección, arreglos y matrices

1.3.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
#include <stdio.h>

// Subroutine declaration or Function prototype
void swap(long long* value1, long long* value2);

// main:
int main(void)
{
	// Read min, max
	long long min = 0, max = 0;
	if ( scanf("%lld %lld", &min, &max) == 2 )
	{
		// If min > max then
		if ( min > max )
		{
			// Swap min with max
			//.   1000. 1008      min==5, &min==1000
			swap( &min, &max ); // Arguments
		}

		// Repeat index from min to max inclusive do
		for ( long long index = min; index <= max; ++index )
		{
			// Print index
			printf("%lld%c", index, (index == max ? '\n' : ' ') );
		}
	}
	else
		return 1;

	return 0;
}

// Swap <value1> with <value2>:
//.                  1000               1008
void swap(long long* value1, long long* value2) // Params: DataType varName = initValue
{
	// Create a copy of value1
	//                          = *1000.
	// value1=1000 &value1==1016 *value1==5
	const long long value1_copy = *value1; // desreferencing
	// Assign value1 as value2
	// 1000 := 1008
	*value1 = *value2;
	// Assign value2 as the copy of value1
	// 1008 := 1000
	*value2 = value1_copy;
}

1.3.2. Leer e imprimir el arreglo

Lee un arreglo de números flotantes de doble precisión y lo imprime en la salida estándar.

Versión 1: Crea el arreglo en memoria de pila. Produce una vulnerabilidad de desbordamiento de pila (stack overflow) y es un código que no debería usarse en producció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
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

/**
	@brief Read @a value_count doubles from stdin and store them in values array
	@param value_count Number of elements to be read from stdin
	@param values Array of elements. It must not be NULL
	@return An error code, 0 for success
*/
int read_values(const size_t value_count, double values[value_count]);

/**
	...
*/
void print_values_index(const size_t value_count, const double values[value_count]);


int main(void)
{
	size_t value_count = 0;
	if ( scanf("%zu", &value_count) == 1 )
	{
		double values[value_count];
		if ( read_values(value_count, values) == EXIT_SUCCESS )
			print_values_index(value_count, values);
	}
	else
		return 1;

	return 0;
}

int read_values(const size_t value_count, double values[value_count])
{
	for ( size_t index = 0; index < value_count; ++index )
		if ( scanf("%lf", &values[index]) != 1 )
			return EXIT_FAILURE;

	return EXIT_SUCCESS;
}

void print_values_index(const size_t value_count, const double values[value_count])
{
	for ( size_t index = 0; index < value_count; ++index )
		printf("%zu: %lf\n", index, values[index]);
}

Versión 2: Crea el arreglo en memoria dinámica, pero introduce una fuga de memoria. Tampoco debe usarse en producció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
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

/**
	@brief Read @a value_count doubles from stdin and store them in values array
	@param value_count Number of elements to be read from stdin
	@param values Array of elements. It must not be NULL
	@return An error code, 0 for success
*/
int read_values(const size_t value_count, double values[value_count]);

/**
	...
*/
void print_values_index(const size_t value_count, const double values[value_count]);


int main(void)
{
	size_t value_count = 0;
	if ( scanf("%zu", &value_count) == 1 && errno == 0 )
	{
		double* values = (double*) malloc( value_count * sizeof(double) );
		if ( values == NULL )
			return (void) fprintf(stderr, "error: not enough memory\n"), 2;

		if ( read_values(value_count, values) == EXIT_SUCCESS )
			print_values_index(value_count, values);
	}
	else
		return 1;

	return 0;
}

int read_values(const size_t value_count, double values[value_count])
{
	for ( size_t index = 0; index < value_count; ++index )
		// &values[index] == &2000[0]
		if ( scanf("%lf", &values[index]) != 1 )
			return EXIT_FAILURE;

	return EXIT_SUCCESS;
}

void print_values_index(const size_t value_count, const double values[value_count])
{
	for ( size_t index = 0; index < value_count; ++index )
		if ( index == value_count - 1 )
			printf("%zu: %lf\n", index, values[index]);
}
array leak

Versión 3: Crea el arreglo en memoria dinámica y lo libera. Puede usarse en producció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
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

/**
	@brief Read @a value_count doubles from stdin and store them in values array
	@param value_count Number of elements to be read from stdin
	@param values Array of elements. It must not be NULL
	@return An error code, 0 for success
*/
int read_values(const size_t value_count, double values[]);

/**
	...
*/
void print_values_index(const size_t value_count, const double* values);
void print_values_pointer(const size_t value_count, const double* values);


int main(void)
{
	size_t value_count = 0;
	if ( scanf("%zu", &value_count) == 1 && errno == 0 )
	{
		double* values = (double*) calloc( value_count, sizeof(double) );
		if ( values == NULL )
			return (void) fprintf(stderr, "error: not enough memory\n"), 2;

		if ( read_values(value_count, values) == EXIT_SUCCESS )
			print_values_pointer(value_count, values);

		free(values);
	}
	else
		return 1;

	return 0;
}

int read_values(const size_t value_count, double values[])
{
	for ( size_t index = 0; index < value_count; ++index )
		// &values[index] == &2000[0]
		if ( scanf("%lf", &values[index]) != 1 )
			return EXIT_FAILURE;

	return EXIT_SUCCESS;
}

void print_values_index(const size_t value_count, const double* values)
{
	for ( size_t index = 0; index < value_count; ++index )
		if ( index == value_count - 1 )
			printf("%zu: %lf\n", index, values[index]);
}

void print_values_pointer(const size_t value_count, const double* values)
{
	for ( size_t index = 0; index < value_count; ++index )
		if ( index == 0 || index == value_count - 1 )
			//                          *(2000   + 2 * sizeof(const double))
			//                          *(2000   + 2 * 8)
			//                          *(2000   + 16)
			//                          *(2016)
			printf("%zu: %lf\n", index, *(values + index) );
	// pointer[index] == *(pointer + index)
}

1.3.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
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
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

/**
	@brief Read @a value_count doubles from stdin and store them in values array
	@param value_count Number of elements to be read from stdin
	@param values Array of elements. It must not be NULL
	@return An error code, 0 for success
*/
int read_values(const size_t value_count, double values[]);

/**
	...
*/
void print_values_index(const size_t value_count, const double* values);
void print_values_pointer(const size_t value_count, const double* values);
void** create_matrix(size_t row_count, size_t col_count, size_t element_size);
void free_matrix(const size_t row_count, void** matrix);
void free_matrix_double(const size_t row_count, double** matrix);
double** create_matrix_double(size_t row_count, size_t col_count);
void free_matrix_double(const size_t row_count, double** matrix);
int read_matrix_double(const size_t row_count, const size_t col_count, double matrix[row_count][col_count]);
int read_matrix_double_ptr(const size_t row_count, const size_t col_count, double** matrix);
void print_matrix_double(const size_t row_count, const size_t col_count, double** matrix);


// Used as a draft
int main_double(void)
{
	size_t row_count = 0, col_count = 0;
	if ( scanf("%zu %zu", &row_count, &col_count) == 2 && errno == 0 )
	{
		double** values = create_matrix_double(row_count, col_count);
/*
		double values[row_count][col_count];
		double** values_ptr = (double**)values;

		row_count = 3;

		double array[row_count]; // 1016
		sizeof(array) == 24

		//                  1016
		double* array_ptr = array;
		sizeof(array_ptr) == 8
		array_ptr = array_ptr + 2; // 1016 + 2*sizeof(double) == 1016 + 16 == 1032
		*array_ptr = 3.14;

		array = 1032;

		const double* const array_ptr2 = array;
		array_ptr2 = array_ptr2 + 2;
*/

		if ( values == NULL )
			return (void) fprintf(stderr, "error: not enough memory\n"), 2;

		if ( read_matrix_double_ptr(row_count, col_count, values) == EXIT_SUCCESS )
			print_matrix_double(row_count, col_count, values);

		free_matrix_double(row_count, values);
	}
	else
		return 1;

	return 0;
}

int main(void)
{
	size_t row_count = 0, col_count = 0;
	if ( scanf("%zu %zu", &row_count, &col_count) == 2 && errno == 0 )
	{
		double** values = (double**) create_matrix(row_count, col_count, sizeof(double));
		if ( values == NULL )
			return (void) fprintf(stderr, "error: not enough memory\n"), 2;

		if ( read_matrix_double_ptr(row_count, col_count, values) == EXIT_SUCCESS )
			print_matrix_double(row_count, col_count, values);

		free_matrix(row_count, (void**)values);
	}
	else
		return 1;

	return 0;
}

void** create_matrix(size_t row_count, size_t col_count, size_t element_size)
{
	void** matrix = (void**) calloc( row_count, sizeof(void*) );
	if ( matrix == NULL )
		return NULL;

	for ( size_t row = 0; row < row_count; ++row )
		if ( ( matrix[row] = calloc( col_count, element_size ) ) == NULL )
			return free_matrix(row_count, matrix), NULL;

	return matrix;
}

void free_matrix(const size_t row_count, void** matrix)
{
	if ( matrix )
		for ( size_t row = 0; row < row_count; ++row )
			free( matrix[row] );

	free(matrix);
}


double** create_matrix_double(size_t row_count, size_t col_count)
{
	double** matrix = (double**) calloc( row_count, sizeof(double*) );
	for ( size_t row = 0; row < row_count; ++row )
		matrix[row] = (double*) calloc( col_count, sizeof(double) );
	return matrix;
}

void free_matrix_double(const size_t row_count, double** matrix)
{
	for ( size_t row = 0; row < row_count; ++row )
		free( matrix[row] );
	free(matrix);
}

int read_matrix_double(const size_t row_count, const size_t col_count, double matrix[row_count][col_count])
{
	for ( size_t row = 0; row < row_count; ++row )
		for ( size_t col = 0; col < col_count; ++col )
			if ( scanf("%lf", &matrix[row][col]) != 1 )
				return EXIT_FAILURE;

	return EXIT_SUCCESS;
}

int read_matrix_double_ptr(const size_t row_count, const size_t col_count, double** matrix)
{
	for ( size_t row = 0; row < row_count; ++row )
		for ( size_t col = 0; col < col_count; ++col )
			if ( scanf("%lf", &matrix[row][col]) != 1 )
				return EXIT_FAILURE;

	return EXIT_SUCCESS;
}

void print_matrix_double(const size_t row_count, const size_t col_count, double** matrix)
{
	for ( size_t row = 0; row < row_count; ++row )
		for ( size_t col = 0; col < col_count; ++col )
			printf("%6.2lf\n", *(*(matrix + row) + col));
}
matrix

1.3.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.4. Cadenas de caracteres

1.4.1. Imprimir argumentos en línea de comandos

Imprime todos los argumentos dados por el usuario al invocar el programa en la línea de comandos.

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

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

	return 0;
}

1.4.2. Encontrar la extensión

Programa que recibe nombres de archivo en los argumentos de línea de comandos y para cada uno de ellos encuentra la extensión y la imprime. El algoritmo para buscar la extensión de archivo se implementa en dos variantes: con indexación y con arimé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
#include <stdio.h>

/**
@brief Convert a text file containing floating point numbers to its binary form
@param input_filename The path and name of the file to be converted
@return An error code, 0 for success
*/
int process_file(const char* input_filename);
int find_extension_index(const char* filename);
const char* find_extension_ptr(const char* filename);

//int main(int argument_count, char* argument_vector[])
int main(int argc, char* argv[])
{
	for ( int arg_index = 1; arg_index < argc; ++arg_index )
		process_file( argv[arg_index] );

	return 0;
}

int process_file(const char* input_filename)
{
	int extension_index = find_extension_index(input_filename);
	printf("extension idx: [%s]\n", &input_filename[extension_index]);
	printf("extension idx: [%s]\n", input_filename + extension_index);

	const char* extension = find_extension_ptr(input_filename);
	printf("extension ptr: [%s]\n", extension);

	return 0; // Success
}

//size_t find_extension_index(size_t count, double* values);
int find_extension_index(const char* filename)
{
	int last_dot = -1, index = 0;
	while ( filename[index] )
	{
		if ( filename[index] == '.' )
			last_dot = index;
		++index;
	}

	return last_dot == -1 ? -1 : last_dot + 1;
}

const char* find_extension_ptr(const char* filename)
{
	const char* last_dot = NULL;
	while ( *filename )
	{
		if ( *filename == '.' )
			last_dot = filename;

		++filename; // filename++; filename += 1; filename = filename + 1;
	}

	return last_dot ? last_dot + 1 : last_dot;
}
valconv2

2. Programación orientada a objetos

2.1. Clases y objetos

2.1.1. Espacios de nombres

Un programa en C es un programa en C++:

main.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <stdio.h>

/*
#include <thirdparty.h>

typedef double main;

int main;
*/

int main(void)
{
	printf("Hello world C\n");
	return 0;
}
main.cpp
1
2
3
4
5
6
7
#include <stdio.h>

int main(void)
{
	printf("Hello world C\n");
	return 0;
}

Por tanto se puede compilar con C++ un programa en C, mientras tenga extensión .cpp:

gcc -g -Wall -Wextra -std=c18   main.c   -o main_c
g++ -g -Wall -Wextra -std=c++17 main.cpp -o main_cpp

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/la informático/a para ver la computadora como una caja de cristal y no una caja mágica. El siguiente código muestra una potencial representación de los objetos del ejemplo de fracciones usando registros y funciones libres (con punteros hacia los registros). Para cada ejemplo se muestra una pareja de archivos, el original en C++ y su correspondiente adptación a C.

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

#include "Fraction.hpp"

int main()
{
	Fraction fr1;
	fr1.read();
	printf("Your fraction: ");
	fr1.print();
	putchar('\n');

	Fraction fr2;
	printf("Fraction2: ");
	fr2.print();
	putchar('\n');

	Fraction fr3(3, 15);
	printf("Fraction3: ");
	fr3.print();
	putchar('\n');

	Fraction fr4{-4};
	printf("Fraction4: ");
	fr4.print();
	putchar('\n');
}
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
#include <stdio.h>

#include "Fraction.h"

int main()
{
	Fraction fr1;
	Fraction_ctor(&fr1);
	Fraction_read(&fr1);
	printf("Your fraction: ");
	Fraction_print(&fr1);
	putchar('\n');

	Fraction fr2;
	Fraction_ctor(&fr2);
	printf("Fraction2: ");
	Fraction_print(&fr2);
	putchar('\n');

	Fraction fr3;
	Fraction_ctor_ll_ll(&fr3, 3, 5);
	printf("Fraction3: ");
	Fraction_print(&fr3);
	putchar('\n');
//	fr3.numerator = 3;
//	fr3.setDenominator(5);

	Fraction fr4;
	Fraction_ctor_ll(&fr4, -4);
	printf("Fraction4: ");
	Fraction_print(&fr4);
	putchar('\n');
}

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.

Fraction.hpp
 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
#ifndef FRACTION_H
#define FRACTION_H

// \frac{a}{b}: a, b \epsilon \mathbb{Z}, b\neq 0

typedef long long FractionPart;

//struct Fraction // PDO=Pure Data Object
class Fraction
{
  private:
	FractionPart numerator = 0;
	FractionPart denominator;

  public:
	Fraction(FractionPart numerator = 0, FractionPart denominator = 1)
		: numerator(numerator)
		, denominator{denominator ? denominator : 1}
	{
		/*
		int x;
		x = 1;
		*/
		// These are assignments not initialization
		// this->numerator = 0;
		// this->denominator = 1;
		this->simplify();
	}
	void read();
	void print()
	{
		std::cout << /*this->*/numerator << '/' << this->denominator;
	}
	void simplify();
	FractionPart greatestCommonDivisor()
	{
		return greatestCommonDivisor(this->numerator, this->denominator);
	}
	static FractionPart greatestCommonDivisor(FractionPart a, FractionPart b);
};

#endif // FRACTION_H
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
#ifndef FRACTION_H
#define FRACTION_H

// \frac{a}{b}: a, b \epsilon \mathbb{Z}, b\neq 0

typedef long long FractionPart;

//struct Fraction // PDO=Pure Data Object
typedef struct
{
	FractionPart numerator;
	FractionPart denominator;
} Fraction;

void Fraction_ctor_ll_ll(Fraction* this, FractionPart numerator, FractionPart denominator);
void Fraction_ctor_ll(Fraction* this, FractionPart numerator);
void Fraction_ctor(Fraction* this);
void Fraction_read(Fraction* this);
void Fraction_print(Fraction* this);
void Fraction_simplify(Fraction* this);
FractionPart Fraction_greatestCommonDivisor(Fraction* this);
/*static*/ FractionPart Fraction_greatestCommonDivisor_ll_ll(FractionPart a, FractionPart b);

/*
extern "C"
{

	FractionPart min(FractionPart a, FractionPart b)
	{
		return a < b ? a : b;
	}

} // extern "C"
*/

#endif // FRACTION_H

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:

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

#include "Fraction.h"

void Fraction::read()
{
	std::cin >> this->numerator;
	std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '/');
	std::cin >> this->denominator;
	this->simplify();
}

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

	// x /= exp
	// x = x / (exp)
	FractionPart greatestCommonDivisor = Fraction::greatestCommonDivisor(this->numerator, this->denominator);
	this->numerator /= greatestCommonDivisor;
	this->denominator /= greatestCommonDivisor;
}

FractionPart Fraction::greatestCommonDivisor(FractionPart a, FractionPart b)
{
	if ( a <= 0 )
		a = -a;
	if ( b <= 0 )
		b = -b;

	// Source: https://en.wikipedia.org/wiki/Euclidean_algorithm
    while ( b )
    {
		FractionPart t = b;
		b = a % b;
		a = t;
	}
	return a;
}
Fraction.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
#include <stdio.h>

#include "Fraction.h"

void Fraction_ctor_ll_ll(Fraction* this, FractionPart numerator, FractionPart denominator)
{
	this->numerator = numerator;
	this->denominator = denominator ? denominator : 1;
	Fraction_simplify(this);
}

void Fraction_ctor_ll(Fraction* this, FractionPart numerator)
{
	this->numerator = numerator;
	this->denominator = 1;
	Fraction_simplify(this);
}

void Fraction_ctor(Fraction* this)
{
	this->numerator = 0;
	this->denominator = 1;
	Fraction_simplify(this);
}

// void Fraction::read()
void Fraction_read(Fraction* this)
{
	scanf("%lld/%lld", &this->numerator, &this->denominator);
	// std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '/');
	Fraction_simplify(this);
}

void Fraction_print(Fraction* this)
{
	printf("%lld", this->numerator);
	printf("%c", '/');
	printf("%lld", this->denominator);
}

// void Fraction::simplify()
void Fraction_simplify(Fraction* this)
{
	if ( this->denominator < 0 )
	{
		this->numerator *= -1;
		this->denominator *= -1;
	}

	// x /= exp
	// x = x / (exp)
	//FractionPart greatestCommonDivisor = Fraction_greatestCommonDivisor(this);
	FractionPart greatestCommonDivisor = Fraction_greatestCommonDivisor_ll_ll(this->numerator, this->denominator);
	this->numerator /= greatestCommonDivisor;
	this->denominator /= greatestCommonDivisor;
}

FractionPart Fraction_greatestCommonDivisor(Fraction* this)
{
	return Fraction_greatestCommonDivisor_ll_ll(this->numerator, this->denominator);
}

/*static*/ FractionPart Fraction_greatestCommonDivisor_ll_ll(FractionPart a, FractionPart b)
{
	if ( a <= 0 )
		a = -a;
	if ( b <= 0 )
		b = -b;

	// Source: https://en.wikipedia.org/wiki/Euclidean_algorithm
    while ( b )
    {
		FractionPart t = b;
		b = a % b;
		a = t;
	}
	return a;
}

2.2. Sobrecarga de operadores

2.2.1. Calculadora fraccional

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

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

#include "Fraction.h"

int main()
{
	Fraction fr1;
	fr1.read();
	std::cout << "Your fraction: ";
	fr1.print();
	std::cout << std::endl;

	Fraction fr2;
	std::cout << "Fraction2: ";
	fr2.print();
	std::cout << std::endl;

	Fraction fr3(3, 15);
	std::cout << "Fraction3: ";
	fr3.print();
	std::cout << std::endl;
//	fr3.numerator = 3;
//	fr3.setDenominator(5);

	Fraction fr4{-4};
	std::cout << "Fraction4: ";
	fr4.print();
	std::cout << std::endl;

	Fraction sum = fr1 + fr3;
//	Fraction sum = fr1.operator+(fr3);
//	Fraction sum = fr1.add(fr3);
	std::cout << "Sum: ";
	sum.print();
	std::cout << std::endl;

//	std::cin >> fr1;
//	std::cout << fr1 << std::endl;
}
Fraction.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#ifndef FRACTION_H
#define FRACTION_H

// \frac{a}{b}: a, b \epsilon \mathbb{Z}, b\neq 0

typedef long long FractionPart;

//struct Fraction // PDO=Pure Data Object
class Fraction
{
  private:
	FractionPart numerator = 0;
	FractionPart denominator;

  public:
	Fraction(FractionPart numerator = 0, FractionPart denominator = 1)
		: numerator(numerator)
		, denominator{denominator ? denominator : 1}
	{
		/*
		int x;
		x = 1;
		*/
		// These are assignments not initialization
		// this->numerator = 0;
		// this->denominator = 1;
		this->simplify();
	}
	void read();
	void print()
	{
		std::cout << /*this->*/numerator << '/' << this->denominator;
	}
	void simplify();
	FractionPart greatestCommonDivisor()
	{
		return greatestCommonDivisor(this->numerator, this->denominator);
	}
	static FractionPart greatestCommonDivisor(FractionPart a, FractionPart b);

	Fraction add(Fraction other);
	Fraction operator+(Fraction other);
};

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

#include "Fraction.h"

void Fraction::read()
{
	std::cin >> this->numerator;
	std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '/');
	std::cin >> this->denominator;
	this->simplify();
}

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

	// x /= exp
	// x = x / (exp)
	FractionPart greatestCommonDivisor = Fraction::greatestCommonDivisor(this->numerator, this->denominator);
	this->numerator /= greatestCommonDivisor;
	this->denominator /= greatestCommonDivisor;
}

FractionPart Fraction::greatestCommonDivisor(FractionPart a, FractionPart b)
{
	if ( a <= 0 )
		a = -a;
	if ( b <= 0 )
		b = -b;

	// Source: https://en.wikipedia.org/wiki/Euclidean_algorithm
    while ( b )
    {
		FractionPart t = b;
		b = a % b;
		a = t;
	}
	return a;
}

Fraction Fraction::add(Fraction other)
{
	Fraction result;
	result.numerator = this->numerator * other.denominator + this->denominator * other.numerator;
	result.denominator = this->denominator * other.denominator;
	result.simplify();
	return result;
}

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