
This course includes our updated coding exercises so you can practice your skills as you learn.
See a demo
El objetivo de la programación y su utilidad para resolver problemas tecnológicos.
Se hablará sobre las distintas partes que componen un ordenador (hardware y software)
Cuál es el rol del programador en todo esto.
Se define lo que se conoce como algoritmo y cuáles son sus características con algunos ejemplos para que el estudiante comprenda el concepto con facilidad.
Se discute sobre la importancia de los algoritmos en la programación y sobre el concepto de "entrada-algoritmo-salida" con respecto a los periféricos.
Se define qué es una variable.
Se discute sobre los diferentes tipos de dato que puede almacenar una varible.
Se introduce el concepto del tamaño de una variable.
Diferencia entre un dato variable y un dato constante.
Se muestran los componentes básicos de un diagrama de flujos.
Se presentan las tres estructuras básicas del paradigma de programación estructurada, y se realiza el diagrama de flujos correspondiente para cada uno.
Por último se discuten algunos ejemplos de algoritmos representados en diagramas de flujos usando las 3 estructuras de programación estructurada.
Video tutorial para descargar e instalar la IDE de Zinjai que utilizaremos a lo largo de este curso en Windows:
https://zinjai.sourceforge.net/index.php?page=descargas.php&os=w32
La versión para Linux también está disponible en la página oficial.
https://zinjai.sourceforge.net/index.php?page=descargas.php&os=lnx
Se presenta el entorno de programación (IDE) Zinjai y se crea el primer programa en lenguaje C utilizando la plantilla provista por la IDE.
Introducción de la funcion main.
Escritura de la primera instrucción para imprimir un mensaje en la consola.
Realización de saltos de linea en la consola.
Sintaxis para realizar comentarios de una sola linea.
Sintaxis para realizar comentarios de multiples lineas.
Declaración y asignación de variables en lenguaje C.
Presentación de los distintos tipos de datos (int, float y char).
Declaración de multiples variables usando el operador coma ",".
Declaración y asignación de constantes en lenguaje C.
Declaración de multiples constantes usando el operador coma ",".
Se profundiza sobre como utilizar la función printf() para imprimir en la consola.
Presentación de los caracteres de conversión para la impresión de una o varias variables asignadas.
Se muestra como utilizar la nueva función scanf() para poder recolectar información digitada por el usuario en la consola y almacenarla en variables.
Presentación de los operadores aritméticos del lenguaje C para realizar operaciones matemáticas entre variables (suma, resta, multiplicación, división, y módulo)
Concepto de precedencia en operaciones mixtas.
Utilización de paréntesis "()" para modificar la precedencia de una operación mixta.
Instrucciones de atajo para realizar operaciones airméticas sobre una misma variable:
+=
-=
*=
/=
Operadores incrementales y decrementales:
++
--
Introducción del bit como unidad fundamental de memoria
Cómo utilizar un conjunto de bits para almacenar información y se presenta el sistema binario para codificar esa información en un conjunto de bits.
Se define lo que es un byte de memoria y cómo conforman la memoria RAM de un ordenador.
Ejemplos de almacenamiento de números enteros y caracteres (código ASCII) en la memoria.
Se discute sobre los distintos espacios de memoria y rangos de valores que se pueden almacenar en una variable.
Se presentan los modificadores de tamaño de una variable entera:
short
long
Se presentan los modificados de signo de una variable entera:
signed
unsigned
Explicación de como se pueden mezclar los modificadores para adaptar una variable a las necesidades y posibilidades de nuestro código.
Introducción a la función sizeof para obtener el espacio que ocupa una variable en memoria.
Mención del tipo float y double, y el formato estándar IEEE 754.
Tabla de ayuda para saber el espacio y rango de almacenamiento de los distintos tipos de datos.
Se ponen en práctica los conceptos teóricos de modificadores de variables vistos en la clase anterior.
Utilización de la función sizeof aplicada a distintos tipos de dato.
Se presentan tajos para declarar variables con modificadores.
Se utiliza el casting para intercambiar valores entre dos variables con tipos de datos diferentes.
Prueba práctica donde se percibe mas detalladamente la relación entre los números enteros y el código ASCII.
Se discute sobre la diferencia entre un caracter numérico y un número entero.
Utilización de casting para realizar operaciones aritméticas con variables de tipo int, cuyo resultado es un númerp flotante o float.
Realizar un programa que permita al usuario ingresar el lado de un cuadrado desde la consola. Con este dato se pide calcular el perímetro y el área del cuadrado. Imprimirlos en pantalla.
El usuario debe ingresar por consola un entero. Se desea obtener el doble del entero consecutivo al que ingresó el usuario.
Imprimir el resultado en consola.
El usuario debe ingresar por consola un entero. Se desea obtener la mitad del entero anterior al que ingresó el usuario. Tener en cuenta números decimales.
Imprimir el resultado en consola.
El usuario del programa debe ingresar a través de la consola 3 notas.
A través de un programa en C, se debe calcular el promedio de esas 3 notas y guardarlo en una variable adecuada.
Luego se debe mostrar por consola el promedio del alumno.
El usuario debe ingresar 2 valores enteros signados ‘a’ y ‘b’ por consola. Se pide calcular un resultado usando la siguiente expresión matemática:
R = a^2 + b^2 + 2ab
Imprimir el resultado en pantalla.
En una empresa, los trabajadores tienen jornadas laborales de 8 horas diarias.
Escribir un programa que le pida al usuario digitar el sueldo por hora y luego imprima el salario mensual de un trabajador. Tomar 1 mes = 30 días.
Presentación de los seis operadores relacionales para realizar comparaciones entre variables:
>
>=
<
<=
==
!=
Se aplican los operadores relacionales presentados en la clase pasada para realizar comparaciones entre variables de tipo char.
Explicación de los resultados obtenidos a través del entendimiento del código ASCII.
Presentación de los tres operadores lógicos
Conjunción --> &&
Disyunción --> ||
Negación --> !
Se discute sobre la necesidad tomar decisiones en un programa y ramificar el código.
Sintaxis de la sentencia condicional if simple.
Utilización de operadores relacionales para obtener la condición de ejecución de la sentencia if simple.
Se escribe una sentencia condicional que también ejecuta un código cuando su condición no se cumple o es falsa utilizando la palabra reservada else.
Se discute sobre la necesidad de una sentencia que ejecute diversos fragmentos de código para distintas condiciones.
Utilización de las palabras "else if" para agregar condiciones a la sentencia condicional.
Se explica como las variables declaradas dentro de un bloque delimitado por llaves "{ }" solo pueden ser utilizadas dentro de ese mismo bloque.
Se muestra el error de compilación que surge cuando se intenta acceder a una variable fuera del bloque en el que fue declarada.
Presentación del operador ternario para resumir en una linea de código una sentencia condicional if doble que afecta al valor de una sola variable.
Sintaxis de la sentencia condicional switch.
Utilización de la sentencia switch para seleccionar ejecución código de acuerdo al valor de una variable.
Presentación de la palabra reservada break para finalizar la ejecución del caso seleccionado.
Presentación de la palabra reservada default para encapsular el código que se deberá ejecutar cuando no se presenta ninguno de los casos especificados en la sentencia switch.
En un colegio la nota mínima necesaria para aprobar es 6.
El usuario debe realizar un programa que le permita ingresar 3 notas por consola, calcular el promedio, y verificar usando sentencia de control condicional si el alumno está aprobado o no.
Si está aprobado se debe escribir en pantalla el mensaje:
"Estado academico del alumno
Aprobado con promedio igual a 'x'"
Caso contrario, se mostrará en pantalla:
"Estado academico del alumno
Desaprobado con promedio igual a 'x'
Para llegar al 6 el alumno necesita 'y' puntos mas"
Donde ‘x’ es el promedio del alumno, e ‘y’ es la cantidad de puntos más que necesitaba para llegar al 6. Es decir, si el alumno tiene un promedio de 4, necesitaba 2 puntos más para aprobar.
El usuario debe ingresar por consola un entero no signado. Se desea averiguar si el número es par o impar.
Imprimir en pantalla el resultado (par o impar).
El usuario debe ingresar por consola una letra. Se debe averiguar si la letra es mayúscula o minúscula.
Imprimir en pantalla el resultado (mayúscula o minúscula).
El usuario debe escribir un entero por la pantalla. Escribir un algoritmo que permita distinguir números positivos, negativos, o cero. Escribir en consola el resultado.
Escribir un programa que permita al usuario digitar su año de nacimiento. El programa debe verificar si el usuario es mayor o menor de edad.
Imprimir en pantalla el resultado (mayor o menor).
Una materia de una facultad consta de 5 notas, de las cuales se calcula un promedio. Hay 2 maneras de aprobar la materia:
Promedio total mayor o igual a 8
Promedio total mayor o igual a 6, más las últimas 2 notas mayor o igual a 8.
Imprimir en pantalla un mensaje que indique si el alumno aprobó o no la materia.
Escribir un programa que permita al usuario escribir un número del 1 al 7. Luego, escribir en consola el día de la semana correspondiente al número digitado, siendo lunes el día 1, y domingo el día 7.
Si el número digitado por el usuario está fuera de los rangos, escribir un mensaje de error.
Se habla sobre lo que es una estructura de control repetitiva o bucle.
Sintaxis de un bucle while.
Ejecución paso a paso de un contador utilizando un bucle while.
Se discute sobre la diferencia en la ejecución de un bloque while y un bloque do while.
Orden en la ejecución de un bloque while.
Sintaxis del bloque do while.
Orden en la ejecución un bloque do while.
Sintaxis ciclo for, distintas componentes de su definición.
Utilización del ciclo for como contador.
Contadores incrementales y decrementales con ciclo for.
Se utiliza y analiza el método de anidación de ciclos for.
Se explica que la cantidad total de ejecuciones del bloque interno de la anidación equivale a la multiplicación de la cantidad de repeticiones de ambos ciclos for.
Utilización de la palabra reservada break para manipular el fin de la ejecución de una sentencia de control repetitiva de los tres tipos vistos.
Ejemplo especifico con bucle while.
Utilización de la palabra reservada continue para omitir pasos de un ciclo o sentencia de control repetitiva de los tres tipos vistos.
Ejemplo especifico con bucle for.
El usuario debe ingresar por consola un número entero no signado. Escribir un algoritmo que dé como resultado el factorial de dicho entero.
Imprimir el resultado en consola.
En un registro empresarial se requiere que el usuario ponga su año de nacimiento. Los años de nacimiento permitidos son desde el 1890 hasta el 2005.
Escribir un programa que le pida al usuario digitar su año de nacimiento. Si el usuario escribe un año inválido, repetir la petición hasta que el año esté en los rangos correctos.
El usuario debe ingresar por consola la base y el exponente de una potencia que se desee calcular.
Escribir un programa en C que interprete estos dos datos y calcule la potencia.
Imprimir el resultado en pantalla.
Escribir un programa en C que le pida al usuario digitar dos números enteros que representen un rango de números.
El primer número ingresado debe siempre ser menor que el segundo número que se digita. Caso contrario, se deberá la operación de entrada del rango.
Se pide imprimir en pantalla la suma de todos los números enteros en el rango [n, m], inclusive ‘n’ y ‘m’.
Escribir un programa que muestre en pantalla la suma de todos los números pares en el rango [1, 100].
Escribir un programa que muestre en la consola todos los números impares del 100 hasta el 1 de manera descendente.
Desarrollar un programa en C que muestre en la consola el alfabeto en minúscula.
Sintaxis para declarar un arreglo.
Almacenamiento de varios datos del mismo tipo en un arreglo.
Qúe es la longitud de un arreglo.
Subíndices de un arreglo.
Indexación utilizando corchetes "[ ]" para acceder a los datos almacenados en un arreglo.
Utilización de un ciclo for como contador para iterar sobre todos los valores de una arreglo mediante la indexación con corchetes "[]".
Se muestra como utilizar un ciclo for junto con la función scanf para rellenar todos los elementos de un arreglo desde la consola con valores digitados por el usuario.
Definición de un arreglo.
Direcciones de memoria de los diversos elementos almacenados en un arreglo.
Relación entre la longitud del arreglo, el tipo de dato del arreglo, y la cantidad de espacio que ocupa en memoria.
Sintaxis de declaración de arreglos anidados o matrices.
Dimensiones de una matriz.
Identificación de las filas y columnas de una matriz.
Obtener valor de una matriz mediante la indexación utilizando corchetes "[ ]".
Utilizar dos ciclos for anidados para iterar sobre todos los elementos de una matriz.
Dado un arreglo de longitud 8 de tipo float rellenado por el usuario, escribir un algoritmo que permita calcular la suma de todos los elementos del arreglo.
Mostrar el resultado en consola.
Dado un arreglo de longitud 8 de tipo int, se debe escribir un algoritmo que permita invertir la posición de los elementos de un arreglo.
Mostrar el nuevo arreglo invertido en la consola.
En un desarrollo de una aplicación, se desea sumarle 1 a todos los elementos de un arreglo, pero se quiere guardar una copia del arreglo original.
Se pide que escriba un algoritmo que se encargue de copiar los 12 elementos del arreglo de enteros signados {1, 7, -2, 0, 2, -1, 5, 3, 3, 0, 1, -4} a un nuevo arreglo de misma longitud, con la diferencia que este nuevo arreglo debe tener incrementado en 1 a todos sus elementos.
Imprimir los elementos del nuevo arreglo en pantalla.
Dado el siguiente arreglo de longitud 25:
{‘a’, ‘B’, ‘c’, ‘d’, ‘E’, ‘f’, ‘G’, ‘H’, ‘I’, ‘j’, ‘K’, ‘L’, ‘n’, ‘o’, ‘P’, ‘q’, ‘R’, ‘s’, ‘t’, ‘u’, ‘V’, ‘W’, ‘x’, ‘Y’, ‘z’}
Contar la cantidad de letras mayúsculas en el interior del mismo.
Mostrar la cuenta en pantalla.
Dadas las siguientes matrices 3x3:
{{7, 97, 23},
{93, 74, 51},
{42, 99, 11}}
{{24, 28, 54},
{44, 11, 88},
{68, 40, 25}}
Escribir un algoritmo que calcule la suma matricial de ambas matrices.
Imprimir en consola la matriz resultado.
Dada una matriz 3x3 de tipo int rellenada por el usuario, escribir un algoritmo que calcule la suma de los elementos de la diagonal principal de la matriz.
Imprimir el resultado en consola.
Diferencia entre valor y dirección de memoria de una variable.
Utilización del operador (&) para obtener la dirección de memoria de una variable.
Concepto de puntero.
Tamaño ocupado por un puntero en la memoria.
Sintaxis para declarar e inicializar un puntero.
Acceder al valor de la variable a la que apunta el puntero usando el operador de desreferencia (*).
Se modifica el valor de la variable a la que apunta un puntero utilizando el operador de desreferencia (*).
Se muestra de forma gráfica como se almacena un puntero en la memoria.
Se explica la diferencia entre dirección de memoria almacenada en un puntero y la dirección de memoria en la que el puntero está almacenado.
Ejemplo de almacenamiento de un puntero de tipo char y tipo int.
Se muestra qué es lo que sucede en un puntero declarado que no ha sido inicializado, es decir nunca se le asignó una dirección de memoria de alguna variable.
Se introduce la constante NULL para inicializar punteros que queremos que apunten a "nada". Esto se realiza cuando queremos asignarle una dirección de memoria un tiempo más tarde en el código.
Se comparan las direcciones de memoria almaxenadas en un puntero nulo versus un puntero NO nulo.
Declaración de puntero que apunta al primer elemento de un arreglo.
Ejemplo de un puntero a un arreglo de tipo char y tipo int.
Imprimir el valor del primer elemento de un arreglo desreferenciando el puntero al arreglo.
Se muestra que el nombre del arreglo funciona como un "puntero" al primer elemento del arreglo.
Se discute sobre la similitud entre punteros y arreglos.
Obtener el valor de cualquier elemento del arreglo utilizando un puntero al primer elemento del arreglo mediante subíndices "[ ]".
Incrementar un puntero al primer elemento de un arreglo para obtener las direcciones de memoria de los demás elementos del arreglo.
Obtener los valores de todos los elementos del arreglo utilizando aritmética de punteros junto con el operador de desreferencia (*).
Sintaxis para definir una función de tipo void.
Prototipo de una función de tipo void.
Se explica como intercambiar información entre la funcion principal main y una función definida por el programador utilizando argumentos o parámetros.
Se discute que modificar el valor de las variables parámetros/argumentos de una función no modifica a las variables definidas en la función main.
Se explica que las variables declaradas dentro de una función solo pueden accederse dentro de esa función (Esto se discutirá en profundiad en la clase de Variables globales y locales dentro de esta sección).
Definición de funciones que no solo ejecutan instrucciones, sino que también devuelven un resultado, utilizando la palabra reservada return.
Se explica como recibir más de un argumento en una función.
Declaración de variables globales.
Utilización de variables globales para acceder a datos desde cualquier función del programa.
Diferencia de comportamiento con las variables locales.
Definición y prototipo de función que acepta un puntero como parámetro.
Se explica como intercambiar información entre la función main y una función definida por el programador utilizando parámetros de tipo puntero.
Se explica como se pueden utilizar parámetros de tipo puntero para que una función definida por el programador pueda aceptar un arreglo declarado en la función main.
Se discute sobre las diferencias entre la memoria global y la memoria stack.
Explicación más detallada de la forma en la cual se almacenan las variables globales y el tiempo de vida de las mismas.
Introducción del bloque stack relacionado a una función.
Explicación más detallada de la forma en la cual se almacenan las variables locales y el tiempo de vida de las mismas.
Secuencia desde la creación de un bloque stack (al llamar a una función) hasta la destrucción del mismo (fin de ejecución de la función).
Diferencias entre la resolución de problemas utilizando pensamiento iterativo y recursivo.
Cálculo paso a paso del factorial de un número utilizando recursividad.
Presentación del caso base de un algoritmo recursivo.
Se muestra en un programa en lenguaje C la construcción de una función recursiva para resolver el problema propuesto, identificando el caso base y el caso general.
Ejecución paso a paso de la función para poder percibir gráficamente la apilación de bloques stack en la memoria.
Definición de "stack overflow" (desbordamiento de pila), cuáles son sus causas y cómo resolverlo.
Construir una función llamada imprimir_fecha que reciba dos parámetros: mes y año.
La función debe mostrar en pantalla un mensaje con la fecha formateada. Ejemplos de funcionamiento:
>> imprimir_fecha(12, 2000);
"La fecha es diciembre del año 2000 D.C"
>> imprimir_fecha(1, 2010);
"La fecha es enero del año 2010 D.C"
>> imprimir_fecha(7, -5);
"La fecha es julio del año 5 A.C."
El IMC es un método de evaluación simple sobre el estado de peso de una persona. Su valor nos indica si nuestro peso es correcto en relación a nuestra altura.
La fórmula del IMC es:
IMC = peso / (altura^2)
Escribir una función que reciba 2 parámetros, peso en kg y altura en metros.
La función debe calcular y retornar el valor del IMC. Luego, de acuerdo a ese valor, Imprimir en la función main un mensaje sobre el estado del paciente en pantalla de acuerdo al valor del IMC.
Tabla de valores:
https://www.bienestarsaval.cl/assets_web/img/tabla-1.jpg
Escribir una función que transforme un carácter numérico a su valor entero.
En caso que el carácter recibido por el procedimiento no sea numérico, el resultado del entero debe ser -1.
Codificar una función que dado un arreglo de enteros de longitud 9 declarado en la función main: {0, 1, 2, 3, 4, 5, 6, 7, 8}
…retorne la dirección de memoria del último elemento del arreglo.
Imprimir la dirección en consola.
El producto interno entre dos vectores se puede definir como la suma del producto de las coordenadas correspondientes a los dos vectores.
Dado dos vectores:
(a, b, c) ; (d, e, f)
El producto interno se calcula mediante la operación:
PI = (a * d) + (b * e) + (c * f)
Se pide escribir una función que reciba como parámetros 2 vectores de tipo float de longitud 3 y retorne el producto interior de ambos.
Escribir una función recursiva en c que sea capaz de realizar la siguiente sumatoria hasta el n-ésimo índice (el cual se indicará como parámetro):
Sumar 2k + 1/2 desde k = 1 hasta 'n'
La función debe retornar el valor de la sumatoria. Imprimir en pantalla el resultado.
Sintaxis para declarar una cadena.
Inicializar una cadena con llaves { } al estilo de un arreglo.
Inicializar una cadena con comillas "".
Se presenta el caracter nulo para indicar el fin de una cadena.
Acceder y modificar los caracteres indiviuales de una cadena con subíndices [ ].
Omisión de la longitud de cadena al momento de la declaración e inicialización.
Utilización de la función fgets para leer y almacenar una cadena digitada por el usuario.
Impresión de una cadena utilizando el caracter de conversión "%s".
Utilización de un puntero al primer elemento para iterar sobre todos los caracteres de la cadena mediante aritmética de punteros.
Construcción de ciclo for con usando como condición de finalización el caracter nulo '\0'.
Explicación sobre las siguientes funciones para manipular cadenas:
strlen: devuelve la longitud de la cadena.
strcpy: copiar una cadena.
strcat: concatenar cadenas.
strrev: revertir los caracteres de una cadena.
Explicación sobre las siguientes funciones para manipular cadenas:
strlwr: convierte todos los caracteres de la cadena en caracteres minúscula.
strupr: convierte todos los caracteres de la cadena en caracteres mayúscula.
strcmp: comparación alfanumérica de cadenas.
Explicación sobre las siguientes funciones para manipular cadenas:
strchr: búsqueda de un caracter en la cadena de izquierda a derecha.
strrchr: búsqueda de un caracter en la cadena de derecha a izquierda.
strstr: búsqueda de una subcadena dentro de la cadena de izquierda a derecha.
Explicación sobre las siguientes funciones para trabajar con cadenas:
atoi: convertir una cadena que contiene un número entero a una variable entera.
atof: convertir una cadena que contiene un número con coma a una variable de tipo float.
Codificar una función que emule el funcionamiento del procedimiento de librería strrev.
Codificar una función que emule el funcionamiento de la función de librería strlen.
Codificar una función que emule el funcionamiento del procedimiento de librería strupr.
El curso ofrece una inmersión integral en uno de los lenguajes más fundamentales y poderosos en el ámbito de la programación. Diseñado para estudiantes y profesionales que buscan entender las raíces de la informática, este curso te dará una sólida comprensión de los conceptos necesarios para dominar y entender para qué sirve el lenguaje de programación C.
Ventajas del lenguaje C:
EFICIENCIA Y RENDIMIENTO
C es conocido por su eficiencia y rendimiento. Al ser un lenguaje de bajo nivel, permite un control directo sobre los recursos del sistema, lo que resulta en un código eficiente. Esto es particularmente importante en aplicaciones que requieren un rendimiento óptimo, como sistemas operativos, controladores de hardware y programas de bajo nivel.
PORTABILIDAD:
C es altamente portable, lo que significa que el código escrito en C puede ejecutarse en diferentes plataformas sin necesidad de modificaciones significativas. Esto se debe a que C proporciona un conjunto básico de características que son compatibles en varias arquitecturas de hardware. Muchos sistemas operativos están escritos en C.
FLEXIBILIDAD Y CONTROL:
C ofrece a los programadores un alto grado de control sobre el hardware y los recursos del sistema. Permite la manipulación directa de punteros y la gestión de memoria, lo que brinda flexibilidad y permite optimizar el código según las necesidades del programador. Esta capacidad de bajo nivel hace que C sea adecuado para desarrolladores que necesitan un control detallado sobre el hardware y el software de sus aplicaciones.
Este curso no solo ofrece conocimientos teóricos sólidos, sino también oportunidades prácticas para que consolides tus habilidades y te prepares para enfrentar desafíos de programación complejos.
Esencial para aquellos que buscan comprender los cimientos de otros lenguajes de programación modernos. Te sumergirás en conceptos avanzados, como punteros y recursión, desafiándote a aplicar conocimientos en proyectos prácticos y escenarios del mundo real.
Al finalizar, habrás adquirido una comprensión profunda de C, sentando las bases para una carrera exitosa en el desarrollo de software.
Al terminar este curso, podrás trabajar hábilmente con:
Herramientas básicas de la IDE Zinjal ✓
Algoritmos ✓
Diversos tipos de variables ✓
Diagramas de flujos ✓
Operadores aritméticos ✓
Operadores relacionales ✓
Operadores lógicos ✓
Sentencias de control condicional (if, else, switch) ✓
Sentencias de control repetitivas (while, do while, for) ✓
Arreglos y matrices ✓
Punteros ✓
Diversos tipos de funciones ✓
Memoria global y stack (recursión) ✓
Cadena de caracteres (string) ✓
Además, cada sección cuenta con una serie de ejercicios prácticos que son resueltos paso a paso antes de pasar a la siguiente sección.
¿Qué estás esperando para iniciar?
Tu instructor