变量在内存中的存储
-
不同类型的变量在内存中占据不同的字节空间
- int 占据连续的 4 个字节
- double 占据连续的 8 个字节
- float 占据连续的 4 个字节
- char 占据连续的 1 个字节
-
内存中存储数据的最小基本单位是字节
- 每一个字节都有一个内存地址,这个地址是一个十六进制的数
-
声明一个变量,在内存中是从高字节向低字节分配连续的指定字节空间
- int num = 10;
-
任何数据在内存中都是一起二进制的补码形式存储的
- 低位存储在低字节 高位存储在高字节
-
变量的值:存储在变量中的数据,叫做变量的值
-
变量的地址:一个变量是有一个或者多个字节组成的,组成这个变量的低字节地址,就是这个变量的地址
-
如何取出变量的值
- int num = 10;
- 直接写上变量的名字就可以取到变量的值
-
如何取出变量的地址
- 使用&运算符,&变量名,这个表达式的结果就是这个变量的地址
-
什么是指针?
- 变量的地址就是指针,指针就是变量的地址
指针变量
-
不管是什么东西,首先它是一个变量
- int变量,是存储 int 变量的
- 那么指针变量就是专门存储指针的一个变量,专门用来存储另一个变量的地址
-
那么我们就说这个指针变量指向了另外一个变量
-
这么做的好处
- 访问一个变量的方式分为两种
- 直接访问
- int num = 10;
- Num = 20; // 直接访问这个num 变量
- 间接访问
- 可以通过指针变量找到这个指针变量指向的变量
- 通过指针变量就可以间接的访问这个指向的变量,通过指针遍历可以间接的访问指针变量指向的另外一个变量
-
如果声明1 个专门用来存储地址的指针变量呢?
-
我们之前虚席的普通变量是如何声明的
- 数据类型 变量名; int num;
-
声明指针变量的语法
-
数据类型* 指针变量的名称;
-
int* p1;
-
代表声明了 1 个指针变量,这个指针遍历的名字叫做p1,这个指针遍历的类型是 int* 读作 int 指针
-
这个*代表 这个变量不是 1 个普通变量,而是 1 个专门用来存储地址的指针变量,这个 p1 指针变量中只能存储地址
-
int * p1
-
double* p1
-
float* p1
-
char* p1
-
指针的类型:有哪些普通的类型就可以有那些指针的类型
-
-
声明的注意
-
*的位置,可以与数据类型放在一起,也可以和指针变量名挨在一起,也可以单独写中间
-
指针遍历用来存储另外 1 个变量的地址
-
但是 1 个指针变量并不是可以存储任意类型的变量地址,而是有限定的,只能存储和这个指针类型相同的普通变量的地址
-
int* p1; p1 变量中只能 存储 int 变量的地址
-
doublep1; p1 变量中只能 存储 double变量的地址
-
float* p1; p1 变量中只能 存储 float 变量的地址
-
char* p1; p1 变量中只能 存储 char 变量的地址
-
否则就会出现一些乱七八糟的东西
-
-
指针变量的初始化
-
指针遍历用来存储另外一个变量的地址
- 所以我么不能直接复制 1 个费地址类型的常量数据
- 也不可以直接复制 1 个变量给指针
- 指针只能存储辞职,不是用来存储常量,变量的
-
指针用来存储另外一个变量的地址,并且指针可以存储另外一个变量的地址,这个变量的类型是限定的
-
正确的初始化步骤
- 先取出变量的地址
- 使用&取地址运算符就可以取出变量的地址
- 要打印地址,使用格式控制符号%p
int num = 10;
int* nums = # // 把num 变量的地址赋值给 int* nums
所以我们说nums 指针变量的值就是 num 变量的地址
那么我们就说nums 指向了 num 变量
- 指针变量只能存储和指针变量相同的普通变量的地址,否则就会出问题
float num = 10.1f;
int* nums = # // 把num 变量的地址赋值给 int* nums
- 如果直接写变量名,操作的就是这个变量,你可以为这个变量赋值或者取值
- &变量名:其实这个是一个边大师&是一个运算符,叫做取地址运算符
- 这个表达式的结果是这个变量的地址
- 指针变量在内存中也有一个地址,因为指针变量也是一个变量
- 所以我们也可以使用&符号取出指针变量的地址
// 指针初始化
int age = 10;
int* p1 = &age;
printf("age的地址%p
",&age); // 0x7ffeefbff44c
printf("p1的值是%p
",p1); // 0x7ffeefbff44c
printf("age 的值是%i
",age); // 10
printf("*p1 的值是%i
",*p1); // 10 *p1 的意思就是找到p1 存储的地址所对应的变量值,所以*p1 是一个值,p1 是变量的地址
指针变量的使用
-
指针变量中存储的是另外一个普通变量的地址
- 这么做的好处:在于可以使用指针简介的操作指针指向的变量
-
Pointer 建议大家在声明直指针变量的时候以 p 开头
-
操作变量的方式
- 直接访问
int num = 10; num = 20; 直接操作这个变量
-
间接访问
-
当一个指针指向了另外一个变量的时候,那么就可以通过指针来简介操作这个变量
-
操作变量的两种方式:取值,赋值
-
使用指针间接的操作指针指向的变量
- *指针变量名; 这个就是拿到了指针变量指向变量的值
// 格式:*指针变量名; 代表这个指针指向的变量
int num = 10;
int * p1 = #
// *p1 代表 p1 指针指向的变量,也就是 num
// *p1 完全等价于 num
*p1 = 100;
// 将 100 赋值给 p1 指针指向的变量
printf("查看 num 的值:%i
",num);
printf("查看 *p1 的值:%i
",*p1);
// 但是你不能直接将 100 赋值给 p1
// p1 = 100; // 错误的,p1 是一个指针变量,只能存储地址
- 注意
- *指针变量 就完全代表指针指向的变量
- 所以通过这种方式为指针指向的变量赋值的时候,数据类型不同的时候会做自动类型转换
使用指针变量的时候注意的问题
-
批量声明
int *p1 ,*p2, *p3; // 声明三个指针变量 int *p1,p2,p3; // 此时只有 p1 是指针变量,p2,p3 是 int 类型
-
野指针
- 我们声明一个指针遍历如果没有为其初始化那么这个时候这个指针变量中是有值的,垃圾值,随机数,这个时候这个指针变量就有可能指向 1 块随机的空间
- 这个空间有可能没有被使用,也有可能其他人在用,也有可能系统再用,这个使用去访问指针变量的时候就会报错,这样的指针我们叫做野指针
int * p1; printf("*p1 = %i ",*p1);
-
NULL 值
- 我们声明一个指针,如果不初始化这个指针这个指针就是一个野指针,指向了一个随机空间,这时候如果使用这个空间指向的值会出问题
- 所以我们建议大家声明一个指针变量以后,最好为其初始化,如果没有变量的地址初始化给这个指针变量那就初始化一个 NULL 值
- NULL 值代表这个指针变量不指向任何空间
- 这个 NULL 值完全等价于 0,也可以付给指针变量为 0
int *p1 = NULL; if(NULL == 0){ printf("等价的吧"); }
- 如果一个指针变量的值是 NULL 值,这个时候通过指针变量去访问指向的变量的时候 100%报错
int num = 100; int *p1 = NULL; //初始化为 NULL 值 p1 = # // 后续在给 p1 赋值一个变量的地址 printf("*p1 = %i ",*p1);
多个指针指向同一个变量
- 多个指针指向同一个变量,操作任一一个指针都会修改变量的值
int num = 10;
int* p1 = #
int* p2 = p1;
*p1 = 100;
printf("*p2的值是:%i
",*p2);
指针的作用
- 指针的作用:通过指针简介的操作指针指向的变量
- 当函数的参数类型是 int,char,doube,float 的时候,这个时候是值传递
- 在函数的内部去修改形参变量的值,对实参变量没有丝毫的影响
- 当函数的参数类型是数组的时候,这个时候参数传递是地址传递
void test(int arr[],int len){
arr[0] = 100;
}
int main(){
int arr[3] = {1,2,3};
test(arr);
printf("%i
",arr[0]);
}
- 指针是一种新的数据类型
- 指针也可以作为函数的参数
- 直接将指针的声明挡在小括号中
- 当我们调用的这个函数的时候,如果这个函数的参数是指针,那么就要传递一个相同类型的指针变量
void test1(int *p1){
printf("p1 的值:%p
",p1);
printf("test1函数中没修改前,*p1的值:%i
",*p1);
*p1 = 1000;
printf("test1函数中修改后,*p1的值:%i
",*p1);
}
int main(){
int num = 10;
printf("main函数中 numi 变量的地址:%p
",&num);
test1(&num);
printf("main 函数中 num的值:%i
",num);
return 0;
}
什么时候需要将指针作为函数的参数
- 函数只能返回一个值,我们需要返回多个值的时候
- 解决方案:使用指针作为函数的参数,让调用者将自己的变量的地址传递给函数,在函数内部修改变量的值
- 当函数需要返回多个数据的时候就可以使用指针返回
int getNumMaxMin1(int arr[],int leight){
int max = arr[0];
int min = arr[0];
for (int i = 1 ; i < leight; i++) {
if(max <= arr[i]){
max = arr[i];
}else{
min = arr[i];
}
}
return max,min; // 这是一个逗号表达式,只会返回 min,所以要是想返回两个需要 使用指针
}
// 让调用者传递两个变量的地址
// 让调用者自己准备两个变量,然后将这两个变量的地址给我
// 函数内部可以通过指针直接修改调用者的变量的值
void getNumMaxMin(int arr[],int leight,int * max,int* min){
*max = arr[0];
*min = arr[0];
for (int i = 1 ; i < leight; i++) {
if(*max <= arr[i]){
*max = arr[i];
}else{
*min = arr[i];
}
}
}
int main(){
// 函数需要返回多个值的时候
// 找出数组中的最大值和最小值
int arr[] = {22,33,4,45,6,7,33,1,111,888,00,88,66,05,3,42};
int max = 0;
int min = 0;
int leight = sizeof(arr) / sizeof(arr[0]);
getNumMaxMin(arr, leight, &max, &min);
printf("max = %i
",max);
printf("min = %i
",min);
return 0;
}
指针为什么需要分类型
- 指针的类型:int* double* float* char*
- 指针变量是一个变量既然是一个变量就是要在内存中占有字节数,那么指针变量在内存中占据多少个字节数呢?
int* pnum = NULL;
int len = sizeof(pnum);
printf("%i
",len); // 不管是哪个类型的都是占据 8 个字节
- 通过指针间接的操作指针指向的变量的方式
int num = 10;
int * p1 = #
// p1这个指针变量中存储的是 num 变量的地址,也就是 num 变量低字节的地址
// 通过这个 p1 指针其实只能找到这个地址的字节
// 这个时候,通过 p1 指针找到这个字节操作的时候,是操作几个字节空间呢?
// 操作多少个字节是根据指针的类型来决定的
- 指针变量的类型决定了,通过这个指针找到字节以后,连续操作多少个字节空间
- int* 操作 4 个字节
- double* 操作8 个字节
- float* 操作 4 个字节
- char* 操作 1 个字节
- 如果指针的类型不和指向的变量类型相同的话,那么通过指针无法正确的操作变量.不然的话如果一个 char*指针指向的是一个 int 类型的变量,那么只会操作一个字节,那么就与这个变量不符,而应该操作四个字节
多级指针
- 一级指针:一个指针变量中存储的是一个普通变量的地址,像这样的指针,我们就称之为一级指针(也就是我们之前学的那些)
- 二级指针:一个指针变量中存储的是一级指针变量的地址,那么久称之为二级指针
-
三级指针:一个指针变量中存储的是二级指针变量的地址,那么就称之为三级指针
-
声明二级指针
- 声明一级指针:数据类型* 指针名
- 声明二级指针:数据类型** 指针名
int num = 10;
int* p1 = #// 一级指针
int** p2 = &p1; // 把一级指针的地址传递给 p2,p2 就是一个二级指针
int*** p3 = &p2; // 把二级指针的地址传递给 p3,p3 就是一个三级指针
- 初始化
- 使用取地址运算符,拿到对应的指针变量的地址,赋值给指针变量就可以了
printf("打印 num 的值是%i
",num);
printf("打印 num 的地址是%p
",&num);
printf("打印 p1 变量的值是%p
",p1);
printf("打印 p1 变量的地址是%p
",&p1);
printf("打印 p2 变量的值是%p
",p2);
printf("打印 p2 变量的地址是%p
",&p2);
指针与整数之间的加减法
- 指针可以和整数进行加减运算
// 指针与整数的加减法
int num = 10;
int num2 = 20;
int* p1 = &num2;
int* p2 = p1+1;
printf("p1 的地址是%p
",&p1);
printf("p2的地址是%p
",&p2);
printf("看看*p2 的值%i
",*p2);
- 指针+1 or -1
- 并不是在指针地址的基础之上加一个字节的地址,而是在这个指针地址的基础之上加一个单位变量占用的字节数
- int* 那么+1 就是加了四个字节
- float* 那么+1 就是加了四个字节
- double* 那么+1 就是加了 8 个字节
- char* 那么+1 就是加了 1 个字节
指针与数组
- 一维数组在内存中是连续的空间
- int arr[3] = {10,20,30}
- 一维数组的地址
- 是数组的低字节地址,数组名代表数组的地址,数组第 0 个元素的地址
- 数组的地址 = 数组名 == 数组的第 0 个元素的地址 == 数组的低字节地址
int arr[] = {1,2,3,45,6};
int * p1 = arr[0];
int * p2 = arr;
// p1 与 p2 是等价的
- 一维数组的元素本质是一个普通变量
- int arr[3] = {10,20,30}
- 这数组有三个元素,每一个元素其实就是一个int普通变量
- 思考:能否定义一个指针指向数组的元素
- 我们可以声明一个指针指向数组的元素,
int arr[] = {1,20,30,40,50,60};
int *p1 = &arr[0];
int *p2 = arr; // 此时p1 与 p2 是等价的
printf("*p1的值是:%i
",*p1);
printf("*p1+1的值是:%i
",*p1+1);
printf("*(p1+1)的值是:%i
",*(p1+1));// p1 是数组第 0 个元素的地址,根据指针加减法+1,int 类型等于增加四个字节,那么就指向了数组的第二个元素,
printf("p1+1的地址是:%p
",p1+1);
printf("数组的第二个的地址是:%p
",&arr[1]);
printf("arr+1的地址是:%p
",arr+1);
- 使用指针遍历数组的值
- 使用指针变量数组的第一种方式
// 使用指针遍历数组
int arr[] = {1,20,30,40,50,60};
int *p1 = &arr[0];
int len = sizeof(arr)/sizeof(arr[0]);
for (int i = 0; i < len; i++) {
printf("使用数组遍历数组的第%i 个值是:%i
",i,*(p1+i)); // p1 指向数组第 0 个元素地址,地址加
}
- 使用指针变量数组的第二种方式
// 使用指针遍历数组(第二种)
int arr[] = {1,20,30,40,50,60};
int len = sizeof(arr)/sizeof(arr[0]);
for (int i = 0; i < len; i++) {
printf("使用数组遍历数组的第%i 个值是:%i
",i,*(arr+i));
}
- 使用指针变量数组的第三种方式
int len = sizeof(arr)/sizeof(arr[0]);
for (int i=0; i<len; i++) {
printf("使用数组遍历数组的第%i 个值是:%i
",i,*(p1++)); //注意这块就不能使用数组名++了,因为数组的地址是不能修改的,只能先定义一个指针变量指向第 0 个元素的地址
}
// 需要重新将数组的第 0 个元素地址赋值给 p1
p1 = &arr[0];
for (int i=0; i<len; i++) {
printf("使用数组遍历数组的第%i 个值是:%i
",i,*(p1++)); //注意这块就不能使用数组名++了,因为数组的地址是不能修改的,只能先定义一个指针变量指向第 0 个元素的地址
} // 再次遍历的时候就会出现错误了,因为 p1 的指向已经不在数组内了,已经超过数组的长度了,
数组作为函数参数的本质
-
在声明这个参数数组的时候,他并不是去创建一个数组,而是去创建一个用来存储地址的指针变量,如果我们为函数写了一个数组作为参数,其实编译器在编译的时候,已经把这个数组换成了指针
-
所以在声明参数的时候,不是创建一个数组,而是创建一个存储数组地址的指针变量,所以我们通过 sizeof 计算参数数组得到的永远是 8
-
所以我们的函数如果带了一个参数,这个参数是一个数组,建议不要写数组了,直接写一个指向数组的指针
中括号的本质(实际还是操作的指针)
- 指针变量后面可以使用中括号,在中国话中写上下标来访问数据
- P1(n) 前提是 p1 是一个指针变量,完全等价于*(p1+n)
- 只要是指针都可以使用中括号下标,就相当于访问
// 中括号的本质
int num[] = {10,20,30,40,50,60};
num[0] = 100; // num[0] == *(num+0)
num[1] = 200; // num[1] == *(num+1)
存储指针的数据
- 存储 int 数据的数据
- int arr[3]
- 这个 arr 数组是不是就可以存储 3 个 int 类型的数组
- 如果一个数组是用来存储指针类型的数据的话,那额这个数组就叫做存储指针的数据
- 格式:
- 元素类型* 数组名[数组长度]
- int* arr[3]
- 这个 arr 数组的元素类型是 int* 是 int 指针,
- 所以这个数组可以存储 int 指针数据,最多存储 3 个
// 指针数组
int num = 10;
int num2 = 20;
int num3 = 30;
int* p1 = #
int* p2 = &num2;
int* p3 = &num3;
int* arr[3] = {p1,p2,p3};
for (int i = 0; i<3; i++) {
printf("指针数组的第%i个值为:%p
",i,arr[i]);
}
- 如果你有多个指针数据,那么就可以将多个指针数据存储在数组中
int arr[3] = {1,2,3};
int* parr[3] = {arr,&arr[1],&arr[2]}; // int 指针数组存储三个 int 类型的地址,第一个是 arr 数组第 0 个元素地址,第二个值的地址,第三个值的地址
*(parr[1]) = 200; // *号记住就是取地址所指向的变量值,parr[1]的地址是 arr 数组的第二个变量的地址,那么*一下就是这个变量值
printf("此时看看 arr 数组中的第二个元素的值改变了没有:%i
",arr[1]);//200,改变了
指针与指针之间的加减法
- 指针与指针之间可以做减法元素,结果是一个 long 类型的数据
- 代表两个指针变量之间相差多少个单位变量
- 绝大多数情况下,我们用来判断数组的两个元素之间相差多少个元素
int arr[] = {10,20,30,40,50,60,70};
int* p1 = &arr[2];
int* p2 = &arr[5];
long result = p2-p1;
printf("result = %li
",result); // 3
- 如果参与减法运算的两个指针不指向同一个数组,结果就会有问题
- 原酸原理:
- 1,先求出两个指针的差
- 2,每一个指针变量对应的普通变量的占用字符
- 3,相除
- 指针与指针之间只能做减法运算,加法,乘法,除法不能
指针与指针之间的比较运算
- 指针与指针之间可以做比较匀速
- (>,<,>=,<=,!=)这些运算都可以做的
// 指针与指针之间的比较运算
int num = 10;
int num1 = 20;
int* p1 = #
int* p2 = &num1;
int result = p1 > p2;
printf("看看是不是大于哦:%i
",result);// 1
- 他可以判断两个指针的变量地址,谁在高字节,谁在低字节
- 也可以用== ,!=来判断两个指针指向的地址是不是同一个地址,是不是同一个变量