13.0.0 指针
13.1.0 指针的使用
13.1.1 变量的值域和变量的地址
复习一下变量在内存中的存储:
- 不同类型的变量在内存中占据不同的字节空间
int 4
double 8
float 4
char 1
- 内存中存储数据的最小基本单位是字节
每一个字节都有一个内存地址,这个地址是一个十六进制的数
- 声明一个变量,在内存中是从高字节向低字节分配连续的指定字节数的空间
- 任何数据在内存中都是以其二进制的补码形式存储的,低位存储在低字节,高位存储在高字节
- 变量的值:存储在变量中的数据,叫做变量的值
- 变量的地址:变量是由一个或者多个字节组成的,组成这个变量的低字节的地址就是这个变量的地址
- 如何取出变量的值:
直接写上变量的名字就可以取到变量中的值
- 如何取出变量的地址:
使用&运算符,&变量名;这个表达式的结果就是这个变量的地址
%p 格式控制符输出变量的地址
什么是指针?
变量的地址就是指针。
13.1.2 指针变量
指针变量就是专门用来存储地址的变量,专门用来存储另外一个变量的地址。
我们可以这么说,这个指针变量指向了那另一个变量。
使用指针变量的好处是什么?
可以通过指针变量找到这个指针变量指向的变量,通过指针变量就可以间接的访问这个指向的变量。
如何声明一个指针变量?
- 语法格式:数据类型* 指针变量的名称;
例如:int* p1;
这代表声明了1个指针变量,这个指针变量的名字是p1,这个指针变量的类型是int*,读作“int指针”。
这个*代表这个变量不是一个普通变量,而是一个用来专门存储地址的指针变量,这个p1指针变量中只能存储地址。
- 指针的类型:有哪些普通的类型就可以有哪些指针的类型。
- *的位置:可以和数据类型在一起,也可以和指针变量名放在一起,也可以单独写在中间。
int* p1;
int *p1;
int * p1;
以上形式都是允许的。
- 指针变量是用来存储另外一个变量的地址,但是1个指针变量并不是可以存储任意类型的变量的地址,而是有规定的。
一个指针变量只能存储和这个指针类型相同的普通变量的地址。
如果不遵守这个规定,则会出现一些意想不到的问题。
13.1.3 指针变量的初始化
因为指针变量是用来存储另外一个变量的地址的,所以我们不能直接赋值1个非地址类型的常量数据给指针变量,也不可以直接赋值1个变量给指针变量。并且指针可以存储的另外一个变量的地址这个变量的类型是固定的。
所以:
正确的初始化步骤是:
- 1.先取出变量的地址:
使用取地址运算符& 就可以取出变量的的地址,要打印地址使用格式控制符%p
- 2.将取出来的变量的地址赋值给指针变量
int num =10;
int* p1 = #
这个p1指针变量的值就是num变量的地址,我们就说p1指针指向了num变量。
注意:
- 指针变量只能存储和指针变量类型相同的普通变量的地址,否则就会出问题
- 如果直接写变量名,操作的就是这个变量;如果加上&变量名;操作的就是这个变量的地址
- 指针变量在内存中也有一个地址,所以在指针变量的前面加上&也可以取出指针变量的地址。例如int* p1; &p1得到的就是p1这个指针的地址。
- 注意如果想要打印一个指针变量的值,应该使用%p占位符。
例如:printf(“%pn”,p1); 这打印的是p1的值也就是p1指向的普通变量的地址
而printf(“%pn”,&p1); 这打印的是p1这个指针变量自己的地址。
13.1.4 指针变量的使用
声明指针变量的好处就是可以间接的操作指针指向的变量。
如何使用指针间接操作指针指向的变量:
语法格式:*指针变量名; 代表这个指针指向的变量
例如:int num = 10;
int* p1 = #
*p1 = 100;
这个时候*p1完全等同于变量num,num被重新赋值为100。
这个时候我们就可以通过这种方式间接的为指针指向的变量赋值或者取值。
注意:
因为*指针变量名完全等同于这个指针指向的变量,所以通过这种方式为指针指向的变量赋值的时候,数据类型如果不同,会做自动类型转换。
13.1.5 使用指针变量的注意事项
- 指针变量也可以批量声明
int *p1,*p2,*p3;
注意不能写成int *p1,p2,p3; 这样的话p2和p3不是指针变量,会变成int类型的普通变量。
野指针
- 我们声明一个指针变量的时候,如果没有为其初始化,这个时候这个指针变量中是有值的,这个值是一个垃圾值,是一个随机地址,所指向的地址是不固定的。这个时候这种指针变量被称为野指针。
13.1.6 NULL值
我们声明一个指针变量,如果不为其初始化,那么这个指针就是一个野指针。在这个时候,如果通过这个指针取访问其指向的地址空间的时候,是相当危险的。可能会造成程序的崩溃。
所以,建议在声明一个指针变量以后,最好马上对其进行初始化,如果没有变量的地址可以初始化这个指针变量,那么就将其初始化为NULL值。
NULL值代表指针变量不指向内存中的任何地址。NULL和0可以完全等价,所以也可以将0赋值给指针变量。
如果一个指针变量的值是NULL,这个时候通过指针变量取访问指向的变量的时候100%报错。
13.1.7 多个指针指向同一个变量
int num = 10;
int* p1 = #
int* p2 = p1;
*p1 = 100;
那么这个时候*p2的值也是100。
p1和p2指针都指向了num变量,这个时候无论是通过*p1还是*p2都是在访问num变量。
这种形式叫做多个指针指向同一个变量。
13.2.0 指针的应用
13.2.1 指针作为函数的参数
指针也可以作为函数的参数。操作时可以直接将指针变量声明在函数的小括号内即可。
例如:void test(int* p1);
当我们调用一个函数的时候,如果函数的参数时一个指针变量,那么我们就必须要为这个指针传递一个和指针类型相同的普通变量的地址。
我们知道,一般普通变量作为形参的函数,在函数操作结束后,如果不返回结果值的话,函数内部的形参变量会被系统收回,不会改变实参的值。
但是,对于指针变量作为参数的函数,在函数的内部去访问参数指针指向的变量的时候,其实访问的就是实参变量。
所以这样做就可以做到在函数的内部修改实参变量的值。
那么什么时候要使用指针变量作为函数的参数呢?
我们知道,在C语言中,一个函数只能返回一个值,不能同时返回多个数据。
那么我们怎么做可以做到返回多个值呢?这个时候可以使用指针变量。
使用指针作为函数的参数,让调用者将自己的变量的地址传递到函数的内部,函数的内部通过指针就可以修改实参变量的值,达到返回多个值的效果。
例如:
通过写一个函数,得到一个数组的最大值和最小值。
void getMaxandMin(int arr[],int len,int* pMax,int* pMin)
{
int max = INT32_MIN;
int min = INT32_MAX;
for(int i = 0;i < 0;i++)
{
if(arr[i] > max)
{
max = arr[i];
}
if(arr[i] < min)
{
min = arr[i];
}
}
*pMax = max;
*pMin = min;
}
int main()
{
int arr[] = {
20,56,23,41,14,74,89,100,120,235,41};
int max = 0;
int min = 0;
int len = sizeof(arr)/sizeof(arr[0]);
getMaxandMin(arr,len,&max,&min);
printf(“Max = %dn Min = %dn”,max,min);
}
这样做就可以做到同时将最大值和最小值返回给main函数中的普通变量。
13.2.2 指针为什么要分类型
指针变量也是一个变量,既然是变量那么肯定就在内存中占有字节数。
那么指针变量在内存中占据多少个字节呢?
通过sizeof函数计算,会发现无论指针是什么类型,在内存中都是占据8个字节。
既然指针都是占据8个字节,那么为什么指针还要分类型呢?
举个例子:
int num = 10;
int* p1 = #
p1指针变量存储的是num变量的地址,也就是num变量的低字节的地址。
通过这个p1指针其实只能找到这个地址的字节。
这个时候,通过p1指针找到这个字节,但是操作的时候指针能够操作多少个字节的空间呢?
操作多少个字节空间是根据指针的类型来决定的。
所以指针变量的类型,决定了通过这个指针找到地址的字节后,连续操作多少个字节空间。
int* 就连续操作4个字节
double* 就连续操作8个字节
float* 就连续操作4个字节
char* 就连续操作1个字节
如果定义了一个int类型的普通变量,而指向它的指针变量是char类型的话,那么指针变量所能操作的字节数只有1个字节,超出的部分无法正确使用,最后导致得到的普通变量的值就会发生意想不到的奇怪结果。
所以指针的类型如果不和指向的变量的类型相同的话,那么通过指针就无法正确的操作指向的变量。
13.2.3 多级指针
一级指针:一个指针变量存储的是一个普通变量的地址,这样的指针就叫做一级指针。
二级指针:一个指针变量存储的是一个一级指针变量的地址,这样的指针就叫做二级指针。
三级指针:一个指针变量存储的是一个二级指针变量的地址,这样的指针就叫做三级指针。
以此类推,还可以有四级指针等……
如何声明多级指针呢?
声明一级指针的语法是:数据类型* 指针名;
声明二级指针的语法是:数据类型** 指针名;
声明三级指针的语法是:数据类型*** 指针名;
以此类推……
注意:
一级指针只能存储普通变量的地址;
二级指针只能存储一级指针变量的地址;
三级指针只能存储二级指针变量的地址;
n级指针只能存储n-1级指针变量的地址。
否则就会出现问题。
初始化:
使用取地址符&,拿到对应的指针变量的地址,赋值给指针变量就可以了。
例子:
int num = 10;
int* p1 = #
int** p2 = &p1;
int*** p3 = &p2;
13.2.4 二级指针的使用
使用指针的时候,在指针变量名前面有几个*就代表其指向的几级变量。
例如:
*指针变量名 代表这个指针指向的变量
**指针变量名 这个指针至少要是一个二级指针,代表这个指针指向的指针指向的那个变量
<