指针
内存地址,在计算机内存中,每个存储单元都有一个唯一的地址(内存编号)。
指针和指针变量,指针是一种特殊的变量类型,用于存储内存空间的地址,指针类型实质就是一个内存的“地址”,指针变量就是存储这个地址的变量,指针可间接操作存储在内存中变量的值。
指针变量的定义和使用
-
指针也是一个数据类型,指针变量也是一种变量。
-
指针变量指向谁,就把谁的地址赋值给指针变量。
-
语法格式:类型 * 变量 OR 类型 * 指针变量 = &变量;这里的 * 可以靠近类型、放在类型和变量中间、靠近变量。
-
- & 叫取地址,返回操作数的内存地址
- * 叫解引用,指操作指针所指向的变量的值
- 在定义变量时,* 号表示所声明的变量为指针类型,指针变量要保存某个变量的地址,指针变量的类型比这个变量的类型多一个 *
- 在指针使用时,* 号表示操作指针所指向的内存空间。
#include <stdio.h>
int main()
{
// 定义一个int类型的变量,同时赋值为10
int a = 10;
// 第一种:定义一个指针类型后初始化,指针类型只能接受地址
int *p = &a;
// 第二种:定义一个指针类型后未初始化
int *p;
p = &a; // p 是指针变量,在此初始化
return 0;
}
指针间接修改变量的值
指针变量指向谁,就把谁的地址赋值给指针变量,通过指针间接修改变量的值。
#include <stdio.h>
int main()
{
int x = 10;
int *p = &x;
printf("p = %p\n", p);
printf("&x = %p\n", &x);
printf("a = %d\n", x);
*p = 20;
printf("x = %d, *p = %d\n", x, *p);
int **ptr = &p; // 指向一个指针使用 **
printf("p = %p\n", &p); // 0000000c95bffad8
printf("ptr = %p\n", ptr); // 0000000c95bffad8
printf("**ptr = %d\n", **ptr); // 20
return 0;
}
const 修饰的指针变量
从左往右看,跳过类型关键字,看修饰哪个字符,如果是 *,说明指针指向的内存不能改变,如果是指针变量,说明指针的指向不能改变,指针的值不能修改。
#include <stdio.h>
int main()
{
int a = 1;
int b = 2;
// p1 可以改,*p1不能改
const int *p1 = &a; // 等价于 int const *p1 = &a;
// p1 = &b; // ok
// *p1 = 555; // err
// p2 不能修改,*p2可以修改
int *const p2 = &a;
// p2 = &b; //err
// *p2 = 555; // ok
// p3 和 *p 都不能改
const int *const p3 = &a;
// p3 = &b; // err
// *p3 = 555; // err
return 0;
}
指针大小
使用 sizeof() 测量指针的大小,得到的总是:4 或 8,sizeof()测的是指针变量指向存储地址的大小。32位平台,所有的指针(地址)都是 32 位( 4 字节),64 位平台,所有的指针(地址)都是 64 位( 8 字节)。
#include <stdio.h>
int main()
{
int *p1;
int **p2;
char *p3;
char **p4;
printf("sizeof(p1) = %llu\n", sizeof(p1));
printf("sizeof(p2) = %llu\n", sizeof(p2));
printf("sizeof(p3) = %llu\n", sizeof(p3));
printf("sizeof(p4) = %llu\n", sizeof(p4));
printf("sizeof(double *) = %llu\n", sizeof(double *));
return 0;
}
指针步长
指针步长指的是通过指针进行递增或递减操作时,指针所指向的内存地址相对于当前地址的偏移量。指针的步长取决于所指向的数据类型,如果是 int,在 32位上是内存地址加 4。
指针加 n 等于指针地址加上 n 个 sizeof(type) 的长度,指针减 n 等于指针地址减去 n 个 sizeof(type) 的长度。
#include <stdio.h>
int main()
{
char ch;
char *p1 = &ch;
printf("p1:%p, p1+1: %p\n", p1, p1 + 1); // 步长为1字节
int a;
int *p2 = &a;
printf("p2:%p, p2+1: %p\n", p2, p2 + 1); // 步长为4字节
double d;
double *p3 = &d;
printf("p3:%p, p3+1: %p\n", p3, p3 + 1); // 步长为8字节
return 0;
}
野指针和空指针
指针变量也是变量,是变量就可以任意赋值,任意数值赋值给指针变量没有意义,因为这样的指针就成了野指针。指针指向的区域是未知(操作系统不允许操作此指针指向的内存区域),当直接赋值数值时会出现意想不到的问题。
野指针不会直接引发错误,操作野指针指向的内存区域才会出问题,为了标志某个指针变量没有任何指向,可赋值为 NULL。NULL 是一个值为 0 的宏常量。
#include <stdio.h>
int main()
{
int *p;
p = 0x123456; // 指针指向的内存地址不确定,会导致意想不到的结果
*p = 5; // 改变一个未知地址的内存空间,系统不允许操作,将无法执行后面的代码
printf("*p = %d\n", *p);
return 0;
}
多级指针
C语言允许有多级指针存在,在实际的程序中一级指针最常用,其次是二级指针。二级指针就是指向一个一级指针变量地址的指针。
#include <stdio.h>
int main()
{
int x = 10;
int *p1 = &x;
int **p2 = &p1;
int ***p3 = &p2;
printf("%d\n", x);
printf("============================ \n");
printf("%d\n", *p1);
printf("%d\n", **p2);
printf("%d\n", ***p3);
printf("============================ \n");
printf("&p1 = %p\n", &p1);
printf("&p2 = %p\n", &p2);
printf("&p3 = %p\n", &p3);
printf("============================ \n");
printf("p2 = %p\n", p2);
printf("p3 = %p\n", p3);
}
函数参数传址
传值是指将参数的地址传递给函数,函数内部可以通过改地址访问原变量,并对其进行修改。
#include <stdio.h>
void func(int *m, int *n)
{
// 函数内部交换 2 个指针指向内存的值
int temp = *m;
*m = *n;
*n = temp;
printf("函数内部 *m = %d, *n = %d\n", *m, *n);
}
int main()
{
int a = 10;
int b = 20;
// 调用函数,地址传递
func(&a, &b);
printf("函数外部 a = %d, b = %d\n", a, b);
return 0;
}
函数指针
一个函数在编译时被分配一个入口地址,这个地址就称为函数的指针,函数名代表函数的入口地址。函数指针是指针,指向函数的指针。语法格式:返回值类型 (*函数指针变量)(形参列表)。函数指针变量的定义,其中返回值、形参列表需要和指向的函数匹配。
#include <stdio.h>
void func(int a)
{
printf("a = %d\n", a);
}
int main()
{
// 函数指针变量的定义,同时初始化
void (*p1)(int a) = func;
// 通过函数指针变量调用函数
p1(10);
// 先定义函数指针变量,后面再赋值
void (*p2)(int);
p2 = func;
// 通过函数指针变量调用函数
p2(20);
return 0;
}
回调函数
函数指针变量做函数参数,这个函数指针变量指向的函数就是回调函数。回调函数可以增加函数的通用性,在不改变原函数的前提下增加新功能。
#include <stdio.h>
int calc(int a, int b, int (*p)(int, int))
{
int res = p(a, b);
}
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int main()
{
int t1 = calc(1, 2, add);
printf("%d\n", t1);
int t2 = calc(10, 5, sub);
printf("%d\n", t2);
return 0;
}
© shiyd.RSS