/* Example program. */
#include <stdio.h>
int main(void)
{
printf("Hello world\n");
return 0;
}
Sistemas embebidos
Introducción.
Tipos de datos y constantes.
Variables.
Operadores y expresiones.
Control de flujo.
Preprocesador y macros.
Funciones y bibliotecas.
Uso de memoria y punteros.
Estructuras de datos.
Creado por Dennis Ritchie a partir de 1972.
Publicación de The C Programming Language en 1978.
Publicación del estándar ANSI C en 1983.
Publicación de The C Programming Language (ANSI C) en 1988.
Revisiones del estándar ANSI C (C89, C90, C11, C17, C2x…)
Actualmente, TIOBE index.
/* Example program. */
#include <stdio.h>
int main(void)
{
printf("Hello world\n");
return 0;
}
Un programa contiene comentarios, funciones y variables.
Una función contiene comentarios, variables y sentencias. → Definición
Una función acepta una lista de argumentos y devuelve un resultado. → Declaración
La declaración de funciones suele realizarse mediante la inclusión de archivos de cabecera (.h).
Función printf:
int printf(const char *format, ...)
El formato es una cadena de texto en la que se incluyen especificadores de conversión precedidos de %.
Algunos especificadores de conversión comunes:
d Entero con signo u Entero sin signo x Entero hexadecimal f Real coma flotante c Carácter de texto s Cadena de texto
auto | double | int | struct |
break | else | long | switch |
case | enum | register | typedef |
char | extern | return | union |
const | float | short | unsigned |
continue | for | signed | void |
default | goto | sizeof | volatile |
do | if | static | while |
signed char Carácter con signo (8 bits). unsigned char Carácter sin signo (8 bits, byte). signed short int Entero corto con signo (16 bits). unsigned short int Entero corto sin signo (16 bits, word). signed int Entero con signo (16 bits*). unsigned int Entero sin signo (16 bits*). signed long int Entero largo con signo (32 bits*). unsigned long int Entero largo sin signo (32 bits*, dword). signed long long int Entero extra largo con signo (64 bits). unsigned long long int Entero extra largo sin signo (64 bits, qword). float Real coma flotante (32 bits, precisión simple). double Real coma flotante (64 bits, precisión doble). long double Real coma flotante (80 bits*, precisión extendida). void Vacío.
(*) Depende de la arquitectura del procesador
Solución alternativa <stdint.h> |
Una expresión precedida por un tipo de datos entre paréntesis, causa la conversión del valor de la expresión al tipo solicitado.
Sintaxis:
(tipo_dato)expresión;
Ejemplo:
int x, y; double d; d = sqrt((double)x*x + (double)y*y);
Expresiones que se evalúan a un valor constante.
Tipos de constantes utilizadas habitualmente:
Carácter de texto como 'A'.
Carácter de texto especial como '\n', '\r', '\t'.
Cadena de texto como "Hola".
Valor entero como 1234
Valor real como 123.4 o 1234e-1
Valor entero en hexadecimal como 0x2A0F
Constantes simbólicas:
#define PI 3.1416
Constantes enumeradas
enum months { JAN = 1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC };
Los valores son enteros con signo.
Si no se especifica explícitamente, al primer elemento se le asigna el valor 0.
Contenedores para almacenar valores en memoria.
Declaración de variables:
modificador tipo_dato nombre_variable = valor_inicial;
Reglas aplicadas al nombre de las variables:
Sólo puede contener los caracteres a-z, A-Z, 0-9 y _.
No puede empezar por un 0-9.
Se distingue entre mayúsculas y minúsculas.
No se pueden utilizar palabras reservadas.
Ámbito (global o local).
Modificadores (extern, register, static, volatile, const).
Tabla de conversión de ºC a ºF (versión en coma fija) ⇒
#include <stdio.h>
int main(void)
{
int fahr, celsius;
fahr = 0;
while (fahr <= 300) {
celsius = (5 * (fahr - 32)) / 9;
printf("%d\t%d\n", fahr, celsius);
fahr = fahr + 20;
}
return 0;
}
Tabla de conversión de ºC a ºF (versión en coma flotante) ⇒
#include <stdio.h>
int main(void)
{
float fahr, celsius;
fahr = 0;
while (fahr <= 300) {
celsius = (5 * (fahr - 32)) / 9;
printf("%f\t%f\n", fahr, celsius);
fahr = fahr + 20;
}
return 0;
}
+ - * / %
&& || !
== != > >= < <=
& | ^ << >> ~
++ --
?:
Prioridad de los operadores en la evaluación de expresiones:
* / %
+ -
<< >>
> >= < <=
== !=
&
^
|
&&
||
= += -= *= /= %= &= ^= |= <<= >>=
Las sentencias son expresiones terminadas en ;.
Los bloques agrupan varias sentencias entre { }.
Un bloque se comporta a efectos prácticos como una sentencia.
Se pueden declarar variables locales dentro de un bloque.
Estructura de control para toma de decisiones simples.
Sintaxis:
Con una sentencia:
if (expresión) sentencia; else sentencia;
Con un bloque:
if (expresión) { sentencias; } else { sentencias; }
Determinar a qué semestre del año pertenece un mes ⇒
#include <stdio.h>
enum months {
JAN = 1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC
};
int main(void)
{
enum months month;
month = DEC;
if (month >= JUL) {
printf("Segundo semestre\n");
} else {
printf("Primer semestre\n");
}
return 0;
}
Estructura de control para toma de decisiones múltiples.
Sintaxis:
Con una sentencia:
if (expresión) sentencia; else if (expresión) sentencia; else if (expresión) sentencia; else sentencia;
Con un bloque:
if (expresión) { sentencias; } else if (expresión) { sentencias; } else if (expresión) { sentencias; } else { sentencias; }
Determinar a qué trimestre del año pertenece un mes ⇒
#include <stdio.h>
enum months {
JAN = 1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC
};
int main(void)
{
enum months month;
month = DEC;
if (month >= OCT) {
printf("Cuarto trimestre\n");
} else if (month >= JUL) {
printf("Tercer trimestre\n");
} else if (month >= APR) {
printf("Segundo trimestre\n");
} else {
printf("Primer trimestre\n");
}
return 0;
}
Estructura de control para selección de casos múltiples.
Sintaxis:
switch (expresión) { case constante1: sentencias; break; case constante2: sentencias; break; default: sentencias; break; }
Los valores de los casos son constantes enteras con signo.
Mostrar el mes del año como una cadena de texto ⇒
#include <stdio.h>
enum months {
JAN = 1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC
};
int main(void)
{
enum months month;
month = DEC;
switch (month) {
case JAN: printf("Enero\n"); break;
case FEB: printf("Febrero\n"); break;
case MAR: printf("Marzo\n"); break;
case APR: printf("Abril\n"); break;
case MAY: printf("Mayo\n"); break;
case JUN: printf("Junio\n"); break;
case JUL: printf("Julio\n"); break;
case AUG: printf("Agosto\n"); break;
case SEP: printf("Septiembre\n"); break;
case OCT: printf("Octubre\n"); break;
case NOV: printf("Noviembre\n"); break;
case DEC: printf("Diciembre\n"); break;
}
return 0;
}
Estructura de control para repetir una sentencia un número de veces determinado por tres expresiones: inicio, condición e incremento.
Sintaxis:
Con una sentencia:
for (expresión_inicio; expresión_condición; expresión_incremento) sentencia;
Con un bloque:
for (expresión_inicio; expresión_condición; expresión_incremento) { sentencias; }
Tabla de conversión de ºC a ºF (versión en coma flotante) ⇒
#include <stdio.h>
int main(void)
{
float fahr, celsius;
for (fahr = 0; fahr <= 300; fahr += 20) {
celsius = (5 * (fahr - 32)) / 9;
printf("%f\t%f\n", fahr, celsius);
}
return 0;
}
Cuidado con las sentencias y los bloques ⇒
#include <stdio.h>
int main(void)
{
int n, i;
n = 10;
if (n > 0)
for (i = 0; i < n; i++)
if ((i & 1) != 0)
printf("%d\n", i);
else
printf("n es negativo.\n");
return 0;
}
Estructura de control para repetir una sentencia mientras se cumpla una expresión evaluada antes de la primera iteración del bucle.
Sintaxis:
Con una sentencia:
while (expresión) sentencia;
Con un bloque:
while (expresión) { sentencias; }
Un bucle For:
for (expresión_inicio; expresión_condición; expresión_incremento) { sentencias; }
Equivale al siguiente bucle While que utiliza las mismas tres expresiones del bucle For:
expresión_inicio; while (expresión_condición) { sentencias; expresión_incremento; }
Estructura de control para repetir una sentencia mientras se cumpla una expresión evaluada tras la primera iteración del bucle.
Sintaxis:
Con una sentencia:
do sentencia; while (expresión);
Con un bloque:
do { sentencias; } while (expresión);
La sentencia Break permite salir de un bucle For, While o Do-While igual que de un Switch.
Sintaxis:
while (expresión_while) { sentencias; if (expresión_break) break; sentencias; }
La sentencia Continue salta a la siguiente iteración de un bucle For, While o Do-While.
Sintaxis:
while (expresión_while) { sentencias; if (expresión_continue) continue; sentencias; }
Ejemplo de utilización de Break/Continue ⇒
#include <stdio.h>
int main(void)
{
int i;
for (i = 0; i <= 10; i++) {
if (i == 5) {
break;
}
printf("%d ", i);
}
printf("\n");
for (i = 0; i <= 10; i++) {
if (i == 5) {
continue;
}
printf("%d ", i);
}
printf("\n");
return 0;
}
Salto incondicional a una etiqueta.
Sintaxis:
sentencias; goto etiqueta; sentencias; etiqueta: sentencias;
No se recomienda su uso salvo en algunos casos muy concretos.
Ejemplo de utilización de Goto/Label:
for (i = 0; i < n; i++)
for (j = 0; j < m; j++)
if (a[i] == b[j])
goto found;
/* not found */
...
goto done;
found:
/* found */
...
done:
Alternativa, código equivalente sin utilizar Goto-Label:
found = 0;
for (i = 0; (i < n) && !found; i++)
for (j = 0; (j < m) && !found; j++)
if (a[i] == b[j])
found = 1;
if (found) {
/* found */
...
} else {
/* not found */
...
}
Conceptualmente es un paso independiente previo a la compilación.
El preprocesador evalua las líneas que empiezan por #.
Los usos más frecuentes del preprocesador son:
Inclusión de archivos #include.
Macros de sustitución #define.
Compilación condicional #ifdef, #if.
Generalmente se utiliza #include al principio del programa para incluir los archivos de cabecera (.h) con la declaración de las funciones de las bibliotecas utilizadas.
Sintaxis:
#include <nombre.h> #include "nombre.h"
Generalmente se utiliza #define para definir macros de sustitución (constantes simbólicas).
Sintaxis:
#define nombre reemplazo
También es posible definir macros de sustitución que acepten argumentos:
Ejemplo:
#define max(A, B) ((A) > (B) ? (A) : (B)) #define min(A, B) ((A) < (B) ? (A) : (B))
Se puede eliminar la definición de un macro de sustitución con #undef.
La compilación condicional se utiliza para incluir código de forma selectiva en función de condiciones evaluadas en el momento de la compilación.
Sintaxis:
#if expresión ... #else ... #endif
Ejemplo:
#if (DEBUG > 0) #define DEBUG_PRINT(error) printf("Error %d\n", error) #else #define DEBUG_PRINT(error) #endif
Permiten dividir una tarea larga en varias tareas más pequeñas.
Permiten su reutilización. → Bibliotecas.
Aceptan argumentos de entrada y permiten devolver un resultado.
Para poder utilizar una función primero hay que declararla o definirla.
Sintaxis:
tipo_dato nombre_función(argumentos) { declaraciones; sentencias; return resultado; }
Tabla de conversión de ºC a ºF (versión en coma flotante) ⇒
#include <stdio.h>
float convert_celsius(float fahr)
{
return (5 * (fahr - 32)) / 9;
}
int main(void)
{
float fahr;
for (fahr = 0; fahr <= 300; fahr += 20) {
printf("%f\t%f\n", fahr, convert_celsius(fahr));
}
return 0;
}
Habitualmente las funciones se declaran en archivos de cabecera (.h) y se definen en archivos de código (.c).
Las funciones más utilizadas se agrupan en bibliotecas para facilitar su reutilización.
Cadenas de texto constantes como "Hola".
Uso de memoria de una cadena de texto (formato ASCIIZ o null-terminated):
'H' | 'o' | 'l' | 'a' | 0 |
Declaración de cadenas de texto variables:
char nombre_cadena[longitud_cadena + 1] = "texto";
Ejemplo de utilización de cadenas de texto ⇒
#include <stdio.h>
#include <string.h>
int main(void)
{
char text[10];
int i;
printf("%ld\n", sizeof(text));
strcpy(text, "uno");
printf("(%ld) %s\n", strlen(text), text);
strcat(text, " dos");
printf("(%ld) %s\n", strlen(text), text);
i = 0;
while (text[i] != 0) {
printf("%c", text[i]);
i++;
}
printf("\n");
return 0;
}
Permiten crear conjuntos de datos homogéneos unidimensionales (vectores) o multidimensionales (matrices).
Sintaxis:
tipo_dato nombre_vector[elementos] = {valores}; tipo_dato nombre_matriz[filas][columnas] = {{valores_fila1}, {valores_fila2}...};
Los índices de los elementos de una matriz siempre empiezan en cero.
Ejemplo de utilización de matrices (vector y matriz 2-D) ⇒
#include <stdio.h>
enum months {
JAN = 1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC
};
const int month_days[12 + 1] = {
0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
int main(void)
{
enum months month;
int i, j;
int matrix[3][5] = { { 1, 2, 3, 4, 5},
{ 6, 7, 8, 9, 10},
{11, 12, 13, 14, 15} };
/* Vector */
month = DEC;
printf("%d días\n", month_days[month]);
/* Array */
for (i = 0; i < 3; i++) {
for (j = 0; j < 5; j++) {
printf("%2d ", matrix[i][j]);
}
printf("\n");
}
return 0;
}
Un puntero es una variable que contiene la dirección de memoria de otra variable.
El operador & devuelve la dirección de una variable.
El operador * devuelve el valor de la variable a la que apunta el puntero.
Ejemplo de utilización de punteros ⇒
#include <stdio.h>
int main(void)
{
int *a, b;
b = 25;
a = &b;
printf("%d %d\n", *a, b);
*a += 5;
printf("%d %d\n", *a, b);
return 0;
}
Internamente, las matrices son realmente punteros ⇒
#include <stdio.h>
int main(void)
{
char text[] = "Hola";
char *p;
int i;
/* Array */
i = 0;
while (text[i] != 0) {
printf("%c", text[i]);
i++;
}
printf("\n");
/* Pointer as array */
p = text;
i = 0;
while (p[i] != 0) {
printf("%c", p[i]);
i++;
}
printf("\n");
/* Pointer as pointer */
p = text;
while (*p != 0) {
printf("%c", *p++);
}
printf("\n");
return 0;
}
Cuando se llama a una función todos los argumentos se pasan por valor.
La función trabaja con una copia del valor de los argumentos independiente de la variable original.
Si se desea que la función modifique el valor de los argumentos, es necesario pasarlos por referencia (como punteros).
Ejemplo de paso de argumentos a una función por valor y por referencia ⇒
#include <stdio.h>
void swap_val(int x, int y)
{
int temp;
temp = x;
x = y;
y = temp;
}
void swap_ref(int *px, int *py)
{
int temp;
temp = *px;
*px = *py;
*py = temp;
}
int main(void)
{
int a = 5, b = 9;
swap_val(a, b);
printf("%d %d\n", a, b);
swap_ref(&a, &b);
printf("%d %d\n", a, b);
return 0;
}
Afortunadamente no se utilizan mucho en la práctica.
Se leen de dentro hacia afuera (no de izquierda a derecha):
int *daytab[13] daytab: array[13] of pointer to int int (*daytab)[13] daytab: pointer to array[13] of int int *f(); f: function returning pointer to int int (*pf)(); pf: pointer to function returning int char (*(*x())[])() x: function returning pointer to array[] of pointer to function returning char char (*(*x[3])())[5] x: array[3] of pointer to function returning pointer to array[5] of char
El uso de memoria estático reserva el espacio de memoria al compilar el programa.
El uso de memoria dinámico permite solicitar espacio de memoria de tamaño variable en tiempo de ejecución.
Biblioteca estándar <stdlib.h>.
void *malloc(size_t size); void *calloc(size_t num, size_t size); void free(void *ptr );
Permiten agrupar varias variables de tipos distintos bajo un sólo nombre para facilitar su manejo.
Sintaxis:
struct nombre_estructura { tipo_dato variable; ... }; struct nombre_estructura nombre_variable = {valores};
Para acceder a las variables de una estructura se utiliza nombre_variable.variable.
En el caso de un puntero a una estructura podemos utilizar (*nombre_puntero).variable o, la notación alternativa, nombre_puntero->variable.
Ejemplo de utilización de estructuras ⇒
#include <stdio.h>
struct point {
int x;
int y;
};
int main(void)
{
struct point pt = {100, 200};
struct point *p;
/* Structure */
printf("(%d, %d)\n", pt.x, pt.y);
pt.x += 10;
pt.y += 20;
printf("(%d, %d)\n", pt.x, pt.y);
/* Pointer to structure */
p = &pt;
(*p).x += 10;
p->y += 20;
printf("(%d, %d)\n", (*p).x, p->y);
return 0;
}
Permite crear tipos de datos nuevos (pseudónimos de tipos de datos existentes).
Sintaxis:
typedef tipo_dato nombre_nuevo;
Ejemplo:
typedef struct { int x; int y; } point; point pt1, pt2;
Ejemplo de utilización de estructuras en funciones ⇒
#include <stdio.h>
typedef struct {
int x;
int y;
} point;
point addpoints(point pt1, point pt2)
{
pt1.x += pt2.x;
pt1.y += pt2.y;
return pt1;
}
int main(void)
{
point pt1 = {100, 200};
point pt2 = { 10, 20};
point pt3 = { 0, 0};
printf("(%d, %d) (%d, %d) (%d, %d)\n", pt1.x, pt1.y,
pt2.x, pt2.y,
pt3.x, pt3.y);
pt3 = addpoints(pt1, pt2);
printf("(%d, %d) (%d, %d) (%d, %d)\n", pt1.x, pt1.y,
pt2.x, pt2.y,
pt3.x, pt3.y);
return 0;
}
Ejemplo de utilización de estructuras con matrices unidimensionales ⇒
#include <stdio.h>
typedef enum {
JAN = 1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC
} months;
typedef struct {
int days;
char name[15];
} month_entry;
const month_entry month_list[12 + 1] = {
{ 0, ""},
{31, "Enero"}, {28, "Febrero"}, {31, "Marzo"},
{30, "Abril"}, {31, "Mayo"}, {30, "Junio"},
{31, "Julio"}, {31, "Agosto"}, {30, "Septiembre"},
{31, "Octubre"}, {30, "Noviembre"}, {31, "Diciembre"}
};
int main(void)
{
months month;
for (month = JAN; month <= DEC; month++) {
printf("%s %d días\n", month_list[month].name,
month_list[month].days);
}
return 0;
}
Estructura mp_obj_str_t utilizada en MicroPython para almacenar cadenas de texto:
typedef struct _mp_obj_str_t {
mp_obj_base_t base;
size_t hash;
// len == number of bytes used in data, alloc = len + 1 because (at the moment) we also append a null byte
size_t len;
const byte *data;
} mp_obj_str_t;
Justifica el comportamiento distinto de las cadenas de texto en C y Python:
|
|