指针
1.什么是指针呢?
指针 = 内存地址
指针变量 :存储着内存地址的变量
2.指针变量定义格式
格式: 数据类型 * 变量名
- 数据类型要和变量的类型保持一致
- *标记
- 变量名:自己起的名字
3.指针的作用
利用* (解引用运算符)可以查询数据、存储数据
-
作用1:操作其他函数中的变量
-
作用2:函数返回多个值
示例2:
-
作用3:函数的结果和计算状态分开
-
作用4:方便操作数组和函数
指针的使用细节
-
指针变量的名字
注意 int* p = &a; 变量名是一个p而不是一个*p, * 只是一个标记
-
指针变量的数据类型要跟指向变量的类型保持一致
-
指针变量占用的大小,跟数据类型无关,跟编辑器有关
32位编辑器中 占用四个字节
64位编辑器中 占用八个字节
-
给指针变量赋值的时候,不能把一个数值赋值给指针变量
如下所示:
int a = 100; int* p = 500; // 赋值未分配空间的内存地址 上方是错误的代码
指针的作用细节
函数中变量的生命周期跟函数相关,函数结束了,变量也会消失,此时在其他函数中,就无法通过指针使用了
如果不想函数中的变量被回收,可以在变量前面加static关键字
示例
#include <stdio.h>
int* method();
int main()
{
// 调用method 函数并使用method函数中的变量a
int* p1 = method();
printf("hello world\n");
printf("hello world\n");
printf("hello world\n");
printf("hello world\n");
printf("hello world\n");
printf("hello world\n");
printf("%d", *p1); // 不能打印的,因为method函数结束以后,那么该函数中所有的变量也会随之消失
return 0;
}
int* method()
{
static int a = 10;// 此时变量一直保存到程序结束
return &a;
}
4.指针的使用
1.查询数据
通过指针存储的内存地址 获取数据
// 示例
#include <stdio.h>
int main()
{
int a = 10;
// 定义指针的时候 *只是一个标记表示当前是一个指针 里面记录的是内存地址
int* p = &a;
// 打印出来的时候我们用到的*是解引用运算符 表示通过后面的内存地址去获取数据
printf("%d\n",*p); // 10
return 0;
}
2.存储数据
格式: *指针名 = 数据值;
例如:
#include <stdio.h>
int main()
{
int a = 10;
// 先给指针指向变量
int* p = &a;
// 再进行修改数据
*p = 200;
printf("%d",&p);
return 0;
}
高级指针
指针的类型是什么意思?
数据类型 * 变量名 = 内存地址
// 数据类型要和变量的类型保持一致
// * 就是一个标记
// 变量名也叫指针名就是我们自己起的名字
指针中数据类型的作用:获取字节数据的个数
1.指针的运算
-
指针+1或者指针-1是什么意思?
把指针中记录的内存地址,往后或往前移动一个步长
-
什么是步长?跟什么有关
跟数据类型有关
windows64操作系统:
char:移动一个字节
short:移动两个字节
int:移动四个字节
long:移动四个字节
long long:移动八个字节
示例:
#include <stdio.h>
int main()
{
// c语言指针步长问题
int a = 10;
int* p = &a;
printf("%p\n", p);
printf("%p\n", p + 1);
printf("%p\n", p + 2);
printf("%p\n", p - 1);
return 0;
}
指针运算中有意义的操作和无意义的操作
-
有意义的操作:
指针跟整数进行加、减操作(每次移动n个步长)
指针跟指针进行减操作(间隔步长)
-
无意义的操作:
指针跟指针进行乘除操作
原因:此时指针指向不明
指针跟指针进行加、乘、除操作
指向不明的指针
野指针:指针指向的空间未分配
悬空指针:指针指向的空间已分配,但是被释放了
#include <stdio.h>
int* method();
int main()
{
/*
野指针:指针指向的空间未分配
悬空指针:指针指向的空间已分配,但是被释放了
*/
// 野指针:指针指向的空间未分配
int a = 10;
int* p1 = &a;
printf("%p\n", p1);
printf("%d\n", *p1);
// 野指针
int* p2 = p1 + 10;
printf("%p\n", p2);
printf("%d\n", *p2);
// 悬空指针:指针指向的空间已分配,但是被释放了
printf("----------------------------------\n");
int* p3 = method();
printf("%p\n", p3);
printf("%d\n", *p3);
return 0;
}
int* method()
{
int a = 10;
int *p1 = &a;
// 返回值一般都是不带*的变量名
return p1;
}
尽量都不要去使用,因为一旦使用了,获取到的数据有可能是错误的。
没有类型的指针(void)
特殊类型:
void * p 不表示任何类型
特点:无法获取数据,无法计算,但是可以接收任意地址
指针两个数据进行转换的案例
#include <stdio.h>
int main()
{
// 一个指针数据进行交换的小案例
long long a = 100;
long long b = 200;
// 这里函数中的4表示是四个字节的数据,我们 也可以这么写 double 4 long long 8
swap(&a, &b, 8);
printf("a = %lld, b = %lld", a, b);
return 0;
}
void swap(void* p1,void* p2,int len)
{
char* pc1 = p1;
char* pc2 = p2;
char temp = 0;
for(int i = 10;i < len ;i ++)
{
temp = *pc1;
*pc1 = *pc2;
*pc2 = temp;
pc1++;
pc2++;
}
}
2.二级指针和多级指针
指向指针的指针我们叫做二级指针
指针数据类型:跟指向空间中,数据的类型是保持一致的
作用:二级指针可以操作一级指针记录的地址
二级指针最终的格式可以这么写
int ** 指针名
// 数据类型必须和一级指针数据类型保持一致
printf("%d",*pp);// 表示获取变量中存取a的内存地址 **pp 表示解引用获取数据
// 一个星表示获取一级里面存取的内存地址,再加一个星表示从一级指针中获取数据然后再获取一级指针中的数据
示例:修改变量a中存储的数据
#include <stdio.h>
int main()
{
int a = 10;
int* p = &a;
int** pp = &p;
printf("%p\n", &a);
printf("%p\n", p);
printf("%p\n", *pp);
**pp = 20;
printf("%d\n", a);
return 0;
}
3.数组和指针
获取数组首地址的两种方式
#include <stdio.h>
int main()
{
int arr[] ={1,2,3,4,5,6};
int* p1 = arr[0];
int* p2 = arr;
return 0;
}
数组指针的细节
-
arr参与运算的时候,会退化为第一个元素的指针
-
示例
#include <stdio.h> int main() { int arr[] = {1,2,3,4,5}; int* p = arr;// 会退化 它会从1 走到2 int* p2 = &arr;// 不会退化 它会直接走完整个数组 是用数据类型再乘以数组的长度 return 0; }
什么是数组指针?
指向数组的指针就叫数组指针
数组指针的细节
sizeof参与计算的时候,不会退化,arr还是整体
&arr获取地址的时候,不会退化
步长:数据类型*数组长度
二维数组
概念:把多个小数组,放到一个大的数组当中
二维数组的定义格式
//第一种定义方式
数据类型 arr[m][n] = // m:二维数组的长度
// n: 一维数组的长度
{
{1,2,3,4....},
{1,2,3,4....},
{1,2,3,4....}
};
// 第二种定义方式
int arr1[5] = {1,2,3,4,5};
int arr2[5] = {1,2,3,4,5};
int* arr[2] = {arr1,arr2};
二维数组(索引操作)
二维数组的第一种定义格式以及遍历方式 如下:
示例
#include <stdio.h>
int main()
{
// 二维数字的第一种定义以及便利方式
// 定义数组
int arr[3][5] =
{
{1,2,3,4,5},
{11,22,33,44,55},
{111,222,333,444,555},
};
// 循环嵌套进行遍历
for (int i = 0; i < 3; i++)
{
for (int j = 0;j < 5;j++)
{
// 进行遍历
printf("%d ", arr[i][j]);
}
// 打印换行
printf("\n");
}
return 0;
}
二维数组进行遍历的第二种方式
示例如下:
#include <stdio.h>
int main()
{
// 二维数组第二种定义格式 事先把一维数组定义完毕 再放到二维数组中
// 创建一维数组
int arr1[5] = { 1,2,3,4,5 };
int arr2[7] = { 1,2,3,4,5,6,7 };
int arr3[9] = { 1,2,3,4,5,6,7,8,9 };
// 计算出每个数组的长度
int len1 = sizeof(arr1) / sizeof(int);
int len2 = sizeof(arr2) / sizeof(int);
int len3 = sizeof(arr3) / sizeof(int);
// 创建一个新的二维数组 把长度存入二维数组
int len[3] = { len1,len2,len3 };
// 存入指针中的二维数组中
int* arr[3] = { arr1,arr2,arr3 };
// 数组遍历
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < len[i]; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
二维数组(指针操作)
数据类型* 指针名 = arr
数组指针的数据类型:要跟数组内部元素的类型保持一致
二维数组里面存储的是一维数组
// 示例
int (*p)[5] = arr;
示例:
#include <stdio.h>
int main()
{
// 定义一个3x5的二维数组
int arr[3][5] =
{
{1, 2, 3, 4, 5},
{11, 22, 33, 44, 55},
{111, 222, 333, 444, 555}
};
// 声明一个指向包含5个整数的数组的指针,并初始化为指向arr的首地址
int (*p)[5] = arr;
// 外层循环遍历每一行
for (int i = 0; i < 3; i++)
{
// 内层循环遍历当前行中的每个元素
for (int j = 0; j < 5; j++)
{
// 使用指针算术访问当前行中的第j个元素并打印
printf("%d ", *(*p + j));
}
// 打印换行符,以便下一行的元素从新的一行开始打印
printf("\n");
// 将指针p移动到下一行的起始位置
p++;
}
return 0;
}
获取指针的方式不同:
示例代码:
#include <stdio.h>
int main()
{
int arr1[5] = { 1,2,3,4,5 };
int arr2[5] = { 11,22,33,44,55 };
int arr3[5] = { 111,222,333,444,555};
// 把三个一维数组的内存地址,再放到二维数组中
int* arr[3] = { arr1,arr2,arr3 };
// 获取指针
int** p = arr;
// 便利数组
for (int i = 0;i < 3;i++)
{
for (int j = 0;j < 5;j++)
{
// 解引用分解数组里面的内容 然后打印输出
// 因为第一次获取的是一维数组的内存地址
// 两个*表示获取两回内存地址
printf("%d ", *(*p + j));
}
printf("\n");
// 移动指针
p++;
}
return 0;
}
数组指针和指针数组
-
数组指针:指向数组的指针
作用:方便的操作数组中的各种数据
#include <stdio.h> int main() { // 举例:int* p = arr; 步长为:int(四个字节) // 举例: int(*p)[5] = &arr;步长为:int乘5(20个字节) return 0; }
如果数组指针不写括号,那么就很指针数组是一致的了
-
指针数组:存放指针的数组
作用:用来存放指针
举例:int* p[5],这个数组里面存着int类型的指针
#include <stdio.h> int main() { // 举例:int* p[5],这个数组里面存放int类型的指针 int arr1[5] = {1,2,3,4,5}; int arr2[5] = {6,7,8,9,0}; int* arr[2] = {arr1,arr2}; return 0; }
4.函数和指针
函数指针
格式:返回值类型(*指针名)(形参列表)
作用:利用函数指针,可以动态的调用函数
示例:
#include <stdio.h>
void method1();
int method2(int num1, int num2);
int main()
{
void (*p1)() = method1;
p1();
int (*p2)(int, int) = method2;
int num = p2(1100, 20);
printf("%d\n", num);
return 0;
}
void method1()
{
printf("method1\n");
}
int method2(int num1, int num2)
{
printf("method2\n");
return num1 + num2;
}
综合案例:
小型计算机:用户录入三个数字,前两个表示要计算的数字,后面一个表示要调用函数的数字
#include <stdio.h>
//int number(int num1, int num2, int num3);
int addition(int num1, int num2);
int subtraction(int num1, int num2);
int multiplication(int num1, int num2);
int division(int num1, int num2);
int main()
{
// 定义一个数组去装四个函数的指针
// 函数指针数组
int(*p[4])(int, int) = { addition,subtraction ,multiplication,division };
// 用户键盘录入三个数字
printf("请输入两个数字前两个表示参与计算的数数字\n");
int num1;
int num2;
scanf_s("%d %d", &num1, &num2);
int choose;
printf("请输入一个数字表示调用的函数1:加法,2减法,3乘法,4除法\n");
scanf_s("%d", &choose);
// 根据用户的选择来调用不同的函数
int result = (p[choose - 1])(num1,num2);
printf("结果是%d", result);
return 0;
}
// 加法
int addition(int num1, int num2)
{
return num1 + num2;
}
// 减法
int subtraction(int num1, int num2)
{
return num1 - num2;
}
// 乘法
int multiplication(int num1, int num2)
{
return num1 * num2;
}
// 除法
int division(int num1, int num2)
{
return num1 / num2;
}