¿Como venimos hasta acá? ¿TPs... Como les fue?
¿Donde están las variables? En la memoria RAM, que se organiza en "celdas". Cada una de estas "celdas" tiene un posición que nos permite encontrar nuestra variable.
Lo que vemos a la izquierda serán posiciones, y en las "celdas" de la derecha guardaremos variables.
Vamos a copiar las variables para que las use la funcion.
Vamos a decirle a la funcion donde están las variables, para que la función opere directamente sobre ellas.
Cuando entramos a una funcion se crea un área de memoria que contiene copias de los valores pasados como argumentos y también variables locales de la función. A este área de memoria con la cuál trabaja la función se denomina pila o stack.
int a=100;
int b=100;
int *p=&a;
int *q=&a;
/* O también:
* int *q=p;
*/
int *r=&b;
En decimal:
a: 100, b: 100
Como hexa:
a: 64, b: 64
Como hexa:
p: 7ffc54ca3898, q: 7ffc54ca3898, r: 7ffc54ca389c
Como punteros:
p: 0x7ffc54ca3898, q: 0x7ffc54ca3898, r: 0x7ffc54ca389c
Direccion de los punteros:
&p: 0x7ffc54ca38a0, &q: 0x7ffc54ca38a8, &r: 0x7ffc54ca38b0
p
igual a q
: Por qué apuntan a lo mismo.&p
distinta de &q
: Por qué son distintas variables.
#include <stdio.h>
void proc(int valor) {
printf("2: %d\n", valor);
valor = 10;
printf("3: %d\n", valor);
}
int main(void) {
int valor = 7;
printf("1: %d\n", valor);
proc(valor);
printf("4: %d\n", valor);
}
int x=4;
int *px=&x;
/* Donde:
* *px es - Es una variable puntero a un entero */
/* &x es - Direccion de x */
int **ppx=&px;
/* Donde:
* &px es - Direccion de px */
/* **ppx es - Es una variable puntero a un puntero a un entero */
tipo_de_variable_apuntada *nombre_del_puntero;
int *p;
char *pc;
Ejemplo:
/* Declaro y creo en memoria una variable entera */
int v1, v2 = 10 ;
/* Declaro y creo un puntero a un entero.
* Contendrá la dirección de una variable entera
*/
int *pint ;
/* Inicializo el puntero con la dirección de memoria de la
* variable. Donde está el puntero se guardó la dirección
* de la variable entera
*/
pint = &v2;
* puntero devuelve el valor de lo
que está siendo apuntado por puntero
Ejemplo:
int n = 10, m;
int *p;
p = &n;
m = *p;
En el ejemplo...
Dada la declaración de las siguientes variables y su representación en memoria ilustrada:
int a, b, *p, *q;
Complete la siguiente tabla con los valores que tomarán las variables y lo apuntado por sus contenidos en las columnas que se lo solicita.
Cuando no sea posible indicar el valor señálelo con el signo: ?
Las letras α, β, χ, etc. simbolizan las direcciones de memoria en las que se encuentran ubicadas las variables declaradas anteriormente.
int a, b, *p, *q, **r, **s;
p=q=NULL; r=s=NULL;
a=9; b=4;
p=&a; q=&b;
r=&p; s=&q;
**r=*q;
// ...mas... (ver enunciado)
void fn(char *s);
Ejemplo:
int mifunc(int a, int b) {
a++;
b=b+2;
return a+b;
}
Ver correr: func_por_valor.c
Ejemplo:
int mifunc(int *a, int *b) {
(*a)++;
(*b)=(*b)+2;
return (*a)+(*b);
}
Ver correr: func_por_referencia.c y func_por_referencia_v2.c
scanf("%i", &a);
a = input("%i")
En las máquinas actuales de 64 bits se manejan punteros de 48 bits. Esto es por que con eso ya pueden direccionar 256 terabyte de memoria cosa que les pareció suficiente a los fabricantes de procesadores (Wikipedia X86-64 Virtual address space y StackOverflow Q&A). De esta manera los fabricantes se ahorran los transitores de esos últimos 16 bits.
Por esto los punteros que veremos con %p serán de
48 bits o 6 bytes. Pero si hacemos un sizeof() nos va a decir 8 bytes. La arquitectura puede direccionar 64bits/8bytes, pero el hardware se queda con los 48bits/6bytes inferiores.
Ejemplo:
int vec[20];
Ejemplo:
int edades[5] = {17, 19, 21, 20, 18};
int edades[ ] = {17, 19, 21, 20, 18};
int A[50];
char A[100];
int A[10] = {2, 5, 8, 100, 1, 2, 100, 5, 5, 5}
int A[100] = {25, 5, 100, 25, 5}
char A[10] = {'a', 'z', 'E', 'e', 65, '\x41', '@', 'U', '*', '\n'}
int A[] = {5, 10, 2, 15, 20}
A[5] = 200;
int i, B[100];
for (i=0; i < 100; i++)
printf("%d\n", B[i]);
int i, C[20];
for (i=0; i < 20; i++)
/* scanf() requiere puntero: & */
scanf("%d", &C[i]);
float promedioArray (int D[ ], int num_elementos) {
long prom = 0;
int i;
for (i=0; i < num_elementos; i++)
prom = prom + D[i];
return (prom / num_elementos);
}
Se pueden bajar una versión completa (con llamado y main) se adjunta usando además casting para la conversión long/int y int/float.
Importante: A una función, además de pasarle la dirección de comienzo del array, hay que pasarle los límites del array. El lenguaje C no comprueba los limites de los arreglos.
También pueden ver un ejemplo de arreglos/vectores y notas.
#include <stdio.h>
int main(void){
int vec[3]={};
}
Este código es invalido en C, pero valido en C++.
En C++ inicializa el vector con ceros.
Dependiendo de los modificadores que le pasamos al compilador, vemos o no el error.
$ gcc -pedantic -o prueba arreglo_con_llaves.c
Código que compila en gcc pero es invalido en C, equivale a un aplazo en el final. Se lo considera un error de concepto.
Son importantes en este sentido las evoluciones del estándard de C: C99, C11, C17 y C2x (sin terminar)
En el archivo modificadores_gcc.txt se pueden ver modificadores de gcc para ver/evitar estos tipo de confusiones.
#include <stdio.h>
void main(void) {
char c;
printf("Ingrese un caracter: ");
scanf("%c", &c);
printf("Usted ingreso el caracter: \'%c\' \
cuyo codigo ASCII es %d en decimal y %x hexa.\n", c, c, c);
}
Ver correr: codigo_ascii.c
Recordemos la diferencia entre representación e interpretación...
#include <stdio.h>
void main(void) {
int i, j;
int c;
printf("\nTabla ASCII\n");
printf("Sume el valor de la fila con el de la columna para obtener el codigo\n\n");
printf(" 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15\n\n");
for (i = 2; i < 8; i++) {
printf("%4d", i*16);
for(j = 0; j < 16; j++) {
c = 16*i + j;
printf("%2c ", c);
}
printf("\n\n");
}
printf("Nota: Solo imprimimos la parte baja (primeros 128 códigos) \n\
y evitamos los primeros 32 que no son imprimibles\n\n");
}
Ver correr: tabla_ascii.c
En muchos lenguajes existe el tipo de dato string,
pero C en un intento de simplificación (y refuerzo
del concepto de punteros) no lo tiene.
char s[5] = "Hola";
char s[15] = "Hola";
char s[5] = {'H', 'o', 'l', 'a', NULL};
Otra formas de declarar strings:
char *pS = "Hola";
Esto falla por que en pS[i] no se puede escribir mas tarde
ya que *pS apunta a un: const string
#include <stdio.h>
int main(void) {
char *pS = "ABCD";
printf("Ingrese el string a grabar: ");
scanf("%s", pS);
}
Resultado: Segmentation fault
#define N 50
char str[N];
char str[50] = "Este es el contenido inicial";
char str[] = "El tamaño se autodetermina";
scanf("%s", &str[0]);
scanf("%s", str); /* En caso de vectores str==&str */
printf("%s\n", str);
scanf() para strings no tiene proteccion por tamaño...
#define N 5
#include <stdio.h>
int main(void) {
char str[N];
scanf("%s", &str[0]);
// Tambien pueden &str[0] hacerlo str
printf("%s\n", str);
}
El teclado es stdin, y la pantalla es stdout...
#define N 5
#include <stdio.h>
int main(void) {
char str[N] = "ABCD";
// Protegido por tamaño del string
fgets(str, N, stdin);
printf("%s\n", str);
}
¿Por qué esto es importante?
¡Seguridad en el código que ejecutamos a ataques!
while((c = getchar()) != '\n' && c != EOF);