前面学了那么多 基本都会涉及到指针的知识 那今天 我们正式来学习c语言的魅力之一 指针
正如我们所知道的,每一个变量都有一个内存位置,每一个内存位置都定义了可使用 & 运算符访问的地址,它表示了在内存中的一个地址我们来看一段代码:
#include <stdio.h>
int main ()
{
int var_runoob = 10;
int *p; // 定义指针变量
p = &var_runoob;
printf("var_runoob 变量的地址: %p\n", p);
printf("var_runoob 变量: %d\n", *p);
return 0;
}
运行结果:
大家带着这个问题 输出 p 和输出*p的区别以及为什么?我们来学习指针
目录
一指针
1.1 概述
:指针也就是内存地址,指针变量是用来存放内存地址的变量。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明
基本格式:
type *var_name;
在这里,type 是指针的基类型,它必须是一个有效的 C 数据类型,var_name 是指针变量的名称。用来声明指针的星号 * 与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。以下是有效的指针声明
int *ip; /* 一个整型的指针 */
double *dp; /* 一个 double 型的指针 */
float *fp; /* 一个浮点型的指针 */
char *ch; /* 一个字符型的指针 */
所有实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,对应指针的值的类型都是一样的,都是一个代表内存地址的长的十六进制数。
不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同
1.2如何使用指针
概述:使用指针时会频繁进行以下几个操作:定义一个指针变量、把变量地址赋值给指针、访问指针变量中可用地址的值。这些是通过使用一元运算符 * 来返回位于操作数所指定地址的变量的值
示例:
#include <stdio.h>
int main ()
{
int var = 20; /* 实际变量的声明 */
int *ip; /* 指针变量的声明 */
ip = &var; /* 在指针变量中存储 var 的地址 */
printf("var 变量的地址: %p\n", &var );
/* 在指针变量中存储的地址 */
printf("ip 变量存储的地址: %p\n", ip );
/* 使用指针访问值 */
printf("*ip 变量的值: %d\n", *ip );
return 0;
}
运行结果:
所以我们根据定义来解决我们上述的问题
什么是指针 指针是地址
什么是指针变量 是存放地址的变量
我们重看定义 int* ip; 其中 int* 是指针类型 ip是变量的名字
我们可以这样理解 ip里面存放的是地址 *ip是将地址里面的内容进行解引用(即将地址转换为值)
所以我们在本节刚刚开始的代码 我们输出 p是地址 输出*p是变量的值
然后看上面的代码输出是不是就很简单了 什么时候输出的是地址 什么时候输出的是变量的值
1.3 NULL指针
在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针。
NULL 指针是一个定义在标准库中的值为零的常量
代码:为什么要这样说呢 因为当你定义了一个指针 却没指向一个地址 会变成一个野指针
野指针:野指针就是指向的内存地址是未知的(随机的,不正确的,没有明确限制的)
那为什么会有野指针呢?
1 指针未初始化:指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它所指的空间是随机的。
2 指针越界访问:指针指向的范围超出了合理范围,或者调用函数时返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放
3 指针释放后未置空:有时指针在free或delete后未赋值 NULL,便会使人以为是合法的。其实它们只是把指针所指的内存给释放掉,但并没有把指针本身忘记。此时指针指向的就是无效内存。释放后的指针应立即将指针置为NULL,防止产生“野指针
我们一定要避免野指针的存在
#include <stdio.h>
int main ()
{
int *ptr = NULL;
printf("ptr 的地址是 %p\n", ptr );
return 0;
}
1.3 指针的算术运算
概述:C 指针是一个用数值表示的地址。因此,您可以对指针执行算术运算。可以对指针进行四种算术运算:++、--、+、-。
假设 ptr 是一个指向地址 1000 的整型指针,是一个 32 位的整数,让我们对该指针执行下列的算术运算
ptr++
在执行完上述的运算之后,ptr 将指向位置 1004,因为 ptr 每增加一次,它都将指向下一个整数位置,即当前位置往后移 4 字节。这个运算会在不影响内存位置中实际值的情况下,移动指针到下一个内存位置。如果 ptr 指向一个地址为 1000 的字符,上面的运算会导致指针指向位置 1001,因为下一个字符位置是在 1001。
我们概括一下:
- 指针的每一次递增,它其实会指向下一个元素的存储单元。
- 指针的每一次递减,它都会指向前一个元素的存储单元。
- 指针在递增和递减时跳跃的字节数取决于指针所指向变量数据类型长度,比如 int 就是 4 个字节
这里 我们联想到之前的数组
1.3.1递增一个指针
我们喜欢在程序中使用指针代替数组,因为变量指针可以递增,而数组不能递增,数组可以看成一个指针常量。下面的程序递增变量指针,以便顺序访问数组中的每一个元素
示例:
#include <stdio.h>
const int MAX = 3;
int main ()
{
int var[] = {10, 100, 200};
int i, *ptr;
/* 指针中的数组地址 */
ptr = var;
for ( i = 0; i < MAX; i++)
{
printf("存储地址:var[%d] = %p\n", i, ptr );
printf("存储值:var[%d] = %d\n", i, *ptr );
/* 指向下一个位置 */
ptr++;
}
return 0;
}
运行结果:
可以看到我们通过对指针的++来遍历了数组
分析一下 这个语句 在之前我们说过数组名是数组的首地址 所以我们将数组的首地址赋值给指针ptr
然后我们通过解引用prt的地址(也就是数组的首地址)可以得到数组的首元素的值
然后通过指针++的特征 可以遍历数组 完成 输出数组每个元素的地址值和元素值
/* 指针中的数组地址 */
ptr = var;
再来分析一下: ptr++;
指针的++指向下一个元素的存储单元,因为数组的物理地址的连续的 所以下一个元素就是数组的下一个元素
同样的指针可以++ 自然也可以--
1.3.2指针的递减
同样地,对指针进行递减运算,即把值减去其数据类型的字节数
我们来看一下示例代码:
#include <stdio.h>
const int MAX = 3;
int main ()
{
int var[] = {10, 100, 200};
int i, *ptr;
/* 指针中最后一个元素的地址 */
ptr = &var[MAX-1];
for ( i = MAX; i > 0; i--)
{
printf("存储地址:var[%d] = %p\n", i-1, ptr );
printf("存储值:var[%d] = %d\n", i-1, *ptr );
/* 指向下一个位置 */
ptr--;
}
return 0;
}
运行结果就不带大家运行了 同理我们接着再来分析一下关键语句
ptr = &var[MAX-1];
将数组的最后一个元素的地址赋值给指针ptr 其中&是取地址
/* 指向下一个位置 */
ptr--;
指针的--指向上一个元素的存储单元,因为数组的物理地址的连续的 所以上一个元素就是数组的上一个元素
这里大家好好的思考一下 prt 和*ptr
这里给大家补充一道题:
代码:我们先来回顾一下数组名的不同使用地方代表了什么?
前面我们说个 数组名是数组的首地址 但是在俩个地方除外
1 是sizeof(数组名):这里数组名代表了整个数组
2 &数组名 :这里数组名也是代表了整个数组
所以我们来看代码 先是
/*声明一个指针 指向数组a的下一个4个字节大小的地址*/
int* ptr =(int*)(&a+1);
所以 *(ptr-1):就是数组的最后一个元素
#include <stdio.h>
int main ()
{
int a[5]={1,2,3,4,5};
int* ptr =(int*)(&a+1);
printf("%d %d\n",*(a+1),*(ptr-1));
return 0;
}
1.3.3指针的比较
概述:指针可以用关系运算符进行比较,如 ==、< 和 >。如果 p1 和 p2 指向两个相关的变量,比如同一个数组中的不同元素,则可对 p1 和 p2 进行大小比较
其实指针的比较就是比较指针指向的变量的大小 并不是单纯的去比较俩个指针的大小
比如我们指针指向的是数组的时候 比较指针的大小就是比较俩个数组元素的大小
来看一下代码:
#include <stdio.h>
const int MAX = 3;
int main ()
{
int var[] = {10, 100, 200};
int i, *ptr;
/* 指针中第一个元素的地址 */
ptr = var;
i = 0;
while ( ptr <= &var[MAX - 1] )
{
printf("存储地址:var[%d] = %p\n", i, ptr );
printf("存储值:var[%d] = %d\n", i, *ptr );
/* 指向上一个位置 */
ptr++;
i++;
}
return 0;
}
这里我们要注意 这个语句是比较数组元素地址的大小 因为数组是一块连续的内存 所以数组的最后一个元素在这一块内存的尾端 是大于前面数组的地址的
ptr <= &var[MAX - 1]
那我们想通过指针比较数组元素的大小怎么比呢?
示例:
#include <stdio.h>
int main ()
{
int var[] = {10, 100, 200};
int *ptr;
/* 指针中的数组地址 */
ptr = var;
if(*ptr<*(ptr+1))
{
printf("储存地址:%p\n",ptr);
printf("储存值:%d\n",*ptr);
}
return 0;
}
运行结果:
注意通过指针访问数组的时候一定是*(prt+1),而不是 *prt +1 那如果你不小心敲错了
我们来看一下 会输出什么?
#include <stdio.h>
int main ()
{
int var[] = {10, 100, 200};
int *ptr;
/* 指针中的数组地址 */
ptr = var;
printf("error=%d\n",*ptr+1);
return 0;
}
运行结果:
是11 实际上输出的结果是 *ptr的值加1
虽然不会报错 但是无法达到我们想要的效果
1.4指针数组
概述:C 指针数组是一个数组,其中的每个元素都是指向某种数据类型的指针。
指针数组存储了一组指针,每个指针可以指向不同的数据对象。
指针数组通常用于处理多个数据对象,例如字符串数组或其他复杂数据结构的数组
简单来说指针数组还是个数组 只是数组的元素是指针而已 指针的类型由数组的类型决定
基本格式:
type* name[len];
type*是指针数组的类型 name为指针数组的名字,len是数组的长度
示例:
int *ptr[MAX];
我们来通过代码来看
关键语句分析:ptr[i] = &var[i]; /* 赋值为整数的地址 */
将var数组元素的地址 赋值给ptr数组 因为ptr数组是指针数组 可以通过地址来解引用地址的内容
即数组元素的值
#include <stdio.h>
const int MAX = 3;
int main ()
{
int var[] = {10, 100, 200};
int i, *ptr[MAX];
for ( i = 0; i < MAX; i++)
{
ptr[i] = &var[i]; /* 赋值为整数的地址 */
}
for ( i = 0; i < MAX; i++)
{
printf("Value of var[%d] = %d\n", i, *ptr[i] );
}
return 0;
}
再看一个简单实例,我们首先声明了一个包含三个整数指针的指针数组 ptrArray,然后,我们将这些指针分别指向不同的整数变量 num1、num2 和 num3,最后,我们使用指针数组访问这些整数变量的值
代码:
#include <stdio.h>
int main ()
{
int* ptrarray[3];
int num1=1,num2=2,num3=3;
ptrarray[0]=&num1;
ptrarray[1]=&num2;
ptrarray[2]=&num3;
for(int i=0;i<3;i++)
{
printf("ptrarray[i]=%d\n",*ptrarray[i]);
}
return 0;
}
1.5指向指针的指针
概述:指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置
一个指向指针的指针变量必须如下声明,即在变量名前放置两个星号
示例:
int **var;
我们来看一下示例代码:
#include <stdio.h>
int main ()
{
int V;
int *Pt1;
int **Pt2;
V = 100;
/* 获取 V 的地址 */
Pt1 = &V;
/* 使用运算符 & 获取 Pt1 的地址 */
Pt2 = &Pt1;
/* 使用 pptr 获取值 */
printf("var = %d\n", V );
printf("Pt1 = %p\n", Pt1 );
printf("*Pt1 = %d\n", *Pt1 );
printf("Pt2 = %p\n", Pt2 );
printf("**Pt2 = %d\n", **Pt2);
return 0;
}
运行结果:
1.6传递指针给函数
概述:C 语言允许传递指针给函数,只需要简单地声明函数参数为指针类型即可
示例:
#include <stdio.h>
int max(int* p1,int* p2)
{
if(*p1>*p2)
{
return *p1;
}
else
{
return *p2;
}
}
int main ()
{
int a=1,b=2;
printf("max=%d\n",max(&a,&b));
return 0;
}
运行结果:
同样数组名也可以当实参传递给指针形参
代码:
#include <stdio.h>
int max(int* p1)
{
if(*p1>*(p1+1))
{
return *p1;
}
else
{
return *(p1+1);
}
}
int main ()
{
int a[2]={1,2};
printf("max=%d\n",max(a));
return 0;
}
1.7从函数返回指针
概述:,C 允许从函数返回指针,即函数的返回值是指针类型 所以 函数的类型也必须是指针
基本格式:
int * myFunction()
{
.
.
.
}
示例:
#include <stdio.h>
int* max(int* p1)
{
if(*p1>*(p1+1))
{
return p1;
}
else
{
return (p1+1);
}
}
int main ()
{
int a[2]={1,2};
int* ptr;
ptr=max(a);
printf("%d\n",*(ptr-1));
return 0;
}
这里指针的内容就结束了简单来说指针就是地址 我们通过对这个地址进行解引用 得到这个地址代表的值