指针概念
- 变量的访问方式:直接访问,直接使用变量名进行访问;间接访问,通过指针来实现。
- 指针:如果一个变量专门用来存放内存地址,则称它为指针变量,简称地址。
- 指针格式:数据类型 *指针变量名 [=初始化地址值];
- 取址运算符:&,取出变量的内存地址,格式占位符:%p。
- 取值运算符:* ,取出指针指向的内存地址的数据值。也叫解引用。
// 声明一个int 变量
int num = 100;
// 声明一个指针变量
int *ptr = #
int * ptr = #
int* ptr = #
// 打印指针的值
printf("ptr 的值:%p\n", ptr);
// 打印指针的地址
printf("ptr 的地址:%p\n", &ptr);
// 打印指针指向的值
printf("ptr指向的值:%d\n", *ptr);
// 打印num 所占空间字节的大小
printf("num占空间大小:%zu\n", sizeof(num));
// 打印指针所占内存的大小
printf("ptr占空间大小:%zu\n", sizeof(ptr));
指针运算
指针加减整数
- 指针与整数的加减运算,表示指针所指向的内存地址的移动,加——向后移动,减——向前移动。指针移动多少,与指针指向的数据类型有关,int——4字节,double——8字节,float——1字节。
// 声明一个数组 数据连续存储
int arr[5] = {10, 20, 30, 40, 50};
int *ptr = &arr[0];
ptr += 3;
printf("ptr=%d\n", *ptr);
ptr -= 2;
printf("ptr=%d\n", *ptr);
指针自增自减
- 指针自增自减,本质上就是指针加减整数,自增地址后移,自减地址前移。
int arr[5] = {10, 20, 30, 40, 50};
int *ptr = &arr[0];
// 自增
ptr++;
printf("ptr=%d\n", *ptr);
// 自减
ptr--;
printf("ptr=%d\n", *ptr);
同类型指针相减
- 相同类型的指针可以进行减法运算,返回它们之间的距离,即间隔多少个数据单位,高位地址减去地位地址,返回真值,低位地址减去高位地址返回的是负值。结果是一个ptrdiff_t类型数据,是一个带符号的整数。格式占位符%td。
// 相同类型的指针相减 // 返回两个数据存储位置的相差个数
double a = 2.434;
double b = 32.55;
double *ptr1 = &a;
double *ptr2 = &b;
int result = ptr1 -ptr2;
printf("%td\n", result);
指针的比较运算
- 指针之间进行比较: < 、>、<=、>=、==等,比较的是各自指向的内存地址的大小,返回值是int类型整数1或0。
// 指针比较 返回值 1 —— true 0 —— false
// 比较的是各自指向的内存地址的大小,比较地址大小
double a = 3.24;
int num = 10;
double *ptr = &a;
int *ptr1 = #
int result = ptr > ptr1;
printf("%d\n", result);
指针和数组
数组名
- 数组名:一般会被隐式的转换位指向数组第一个元素的指针,有一点指针的特性
- 使用sizeof运算符,数组名得到的是整个数组的大小,指针得到的是本身的大小;
- 数组名不能进行自增、自减运算;
- 数组名的指向不可更改。
指针数组
- 指针数组:是一个数组,其中每个元素都是指针。
- 语法:数据类型 *指针数组名[长度];
// 指针数组 int *ptr[5];
int a = 10;
int b = 12;
int c = 13;
// 定义一个长度为三的指针数组
int *ptr[3];
ptr[0] = &a;
ptr[1] = &b;
ptr[2] = &c;
// 遍历数组
for (int i = 0; i < 3; i++) {
printf("指针数组的元素为:%p, 元素指向的值为:%d\n", ptr[i], *ptr[i]);
}
数组指针
- 数组指针:是一个指针,指向一个数组。数组指针指向的是整个数组的地址而不是第一个元素的地址。
- 语法:数据类型 (*数组指针名) [长度];
// 数组指针 是一个指针 int (*ptr)[4];
int arr[4] = {10, 20, 30, 40};
// 定义一个数组指针
int (*ptr)[4] = &arr; // &arr 指向整个数组的地址
// 二者数值相同
printf("arr指向的地址:%p\n", arr);
printf("ptr的地址:%p\n", ptr);
// 数组的值是第一个元素的地址
// 数组指针的值是整个数组的地址
printf("arr + 1指向的地址:%p\n", arr + 1);
printf("ptr + 1 的地址:%p\n", ptr + 1);
// 数组指针遍历数组
for (int i = 0; i < 4; i++) {
printf("数组指针ptr 的每个元素指向的值为:%d\n", (*ptr)[i]);
}
数组指针和数组名的区别
- 指向不同:数组名指向元素的首地址,数组指针指向数组的地址。
- 类型不同:数组的是arr[4],ptr的类型是int(*)[4];
- 能否再次赋值:数组名是个常量,是不可改变的,数组指针是可变的,可以指向不同的数组。
- 初始化:数组名不需要显示初始化,自动指向数组元素的首元素,数组指针需要显示初始化,指定它将指向的数组。
- 访问元素:数组名访问数组元素不需要解引用,数组指针通常需要解引用才能访问数组元素。
字符指针
- 字符指针:指向字符或字符串(字符数组),通常用于处理字符串。
- 字符数组名和字符指针表示字符串的区别
- 对字符数组只能对各个元素赋值,不能对字符数组名重新赋值。
- 字符指针式可变的,允许重新赋值,指向新的字符串。
char arr[] = "Hello";
printf("%s\n", arr); // Hello
// 字符指针
char *ptr = "World";
printf("%s\n", ptr); // OK
ptr = "OK";
arr[1] = 'a';
printf("%s\n", arr); // Hello
printf("%s\n", ptr);// OK
指针和函数
传递指针给函数
- 传地址或指针给函数;
- 传数组给函数——数组名本身代表数组首地址,本质就是传地址。
#include <stdio.h>
void fn(int *);
int main() {
// 给函数传递指针
int a = 100;
printf("a 的地址:%p, a 地址中存储的数据:%d\n", &a, a);
int *ptr = &a;
fn(ptr);
printf("函数运行后a 的地址:%p, a 地址中存储的数据:%d\n", &a, a);
fn(&a);
printf("函数运行后a 的地址:%p, a 地址中存储的数据:%d\n", &a, a);
return 0;
}
void fn(int *p) {
printf("p地址:%p,p = %d\n", p, *p);
*p += 1;
printf("p地址:%p,p = %d\n", p, *p);
}
// 传递数组
#include <stdio.h>
// 返回这个数组的平均数
double avg(double *arr, int length) {
double sum = 0;
for (int i = 0; i < length; i++) {
sum += arr[i];
}
return sum / length;
}
int main() {
// 给函数传递数组
double arr[3] = {10, 20, 30};
int length = sizeof(arr) / sizeof(double);
double result = avg(arr, length);
printf("result = %lf\n", result);
return 0;
}
指针函数——返回指针的函数
- 指针函数:函数的返回值是一个指针,本质上是一个函数。
- 语法:返回类型 *指针函数名(参数列表);
- 返回值:返回值不能指向局部变量,局部变量会在当前代码块结束后释放。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// 定义一个指针函数返回10哥随机数
int *fn () {
// 定义一个数组接收
static int arr[10];
// 设置随机数种子
srand(time(NULL));
for (int i = 0; i < 10; i++) {
arr[i] = rand();
}
return arr;
}
int main() {
int *array = fn();
for (int i = 0; i < 10; i++) {
printf("arr[%d]= %d\n", i + 1, array[i]);
}
return 0;
}
函数指针——指向函数的指针
- 函数指针:把函数的这个首地址赋予一个指针变量,通过指针变量就可以找到并调用该函数,这种指针就是函数指针。
- 语法:返回类型 (* 函数指针名)(参数列表);
- ()的优先级高于 *, 第一个括号不能省略。
#include <stdio.h>
// 函数 返回两整数中的最大值
int max(int a, int b) {
return a > b ? a : b;
}
int main() {
// 函数指针 就是一个指针 (指针类型)
int (*ptr)(int a, int b) = max;
printf("%d\n",(*ptr)(10, 20));
// 通过函数指针调用函数
int maxVal;
maxVal = (*ptr) (12, 32);
printf("%d\n", maxVal);
return 0;
}
回调函数
- 回调函数:一个通过函数指针调用的函数。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// 使用回调函数,实现返回一个随机数
int *fn(int *array, int length, int (*arr)()) { // int (*arr)() 调用了 rand() 函数
for (int i = 0; i < length; i++) {
array[i] = (*arr)();
}
return array;
}
int main() {
int arr[10];
int length = sizeof(arr) / sizeof(int);
// 设置随机种子
srand(time(NULL));
fn(arr, length, rand);
for (int i = 0; i < 10; i++ ) {
printf("%d\n", arr[i]);
}
return 0;
}
多级指针
- 指向指针的指针是一种多级间接寻址的形式。
- 声明多级指针时,使用多个 * 老表示指针的级别。
- 初始化指针时,需逐级给指针赋值,确保每个指针指向正确的目标。
- 解引用多级指针时,需要根据指针的级别使用适当数量的 * 解引用。
// 多级指针
int a = 100;
// 一级指针
int *ptr = &a;
// 二级指针
int **ptr2 = &ptr;
// 三级指针
int ***ptr3 = &ptr2;
printf("一级指针 = %d,二级指针 = %d, 三级指针 = %d\n", *ptr, **ptr2, ***ptr3);
空指针
- 赋为NULL值的指针称为空指针,NULL一个值为0的宏常量。
// 空指针
int *p = NULL;
int num = 34;
p = #
printf("p = %p \n", p);
printf("p = %d \n", *p);
野指针
- 野指针:指针指向的位置是不可知的,随机性、不正确、没有明确限制。
- 野指针的成因:
- 指针使用前未初始化,值是随机的。
- 指针越界访问。
- 指针指向已释放的空间。
- 避免野指针
- 初始化时没有明确的地址赋值,为指针变量赋一个NULL。
- 小心指针越界。
- 避免返回指向局部变量的指针。
指针总结:
变量定义 | 类型表示 | 含义 |
int i | int | i是一个整型变量。 |
int *p | int * | p 是一个指向整型数据的指针 |
int a[5] | int[5] | a 是一个5个元素的整型数组。 |
int *p[4] | int *[4] | p 是一个指针数组,每个元素都是指向整型数据的指针。 |
int (*p)[4] | Int (*)[4] | p 是一个数组指针,指向一个4个元素的整型数组。 |
int f() | Int () | f是一个返回整型数据的函数 |
int *f() | int *() | f是一个指针函数,返回指向整数数据的指针。 |
int (*p)() | int (*)() | p 是一个函数指针,指向一个返回整型数据的函数。 |
int **p | int ** | p是一个指向指针的指针。 |