指针的理解与定义
1.变量的访问形式
访问内存中变量存储的数据:直接访问(通过变量名来访问)和间接访问(通过首地址来访问)
地址==指针
变量:命名的内存空间,用于存放各种类型的数据。
变量名:变量名是给内存空间取的一个容易记忆的名字。
变量值:在变量单元中存放的数据值。
变量的地址:变量所使用的内存空间的地址,即 指针 。
指针变量:用一个变量来存储地址。我们可以通过访问指针变量达到访问内存中另一个变量数据的目的。
2.指针变量的定义
一般格式:数据类型 *变量名 [ = 初始地址值];
解释说明:
- 字符 * 用于告知系统这里定义的是一个指针变量,通常跟在类型关键字的后面。
-
指针变量的名字是 p,不是*p。
-
指针变量中只能存放地址,不要将一个整数(或任何其它非地址类型的数据)赋给一个指针变量。
同时声明两个指针变量:
// 正确
int * a, * b;
// 错误
int* a, b; //此时a是整数指针变量,而b是整数变量
int **p;
3.指针的应用场景
场景1:使用指针 访问变量 或者 数组的元素 ;
场景2:应用在数据结构中,构造节点。例如:
节点:数据+指针:指向下一个元素
指针的运算
指针作为一种特殊的数据类型可以参与运算,但与其他数据类型不同的是,指针的运算都是 针对内存中的地址来实现的。
1.取址运算符:&
取址运算符,使用“ & ”符号来表示。作用:取出指定变量在内存中的地址。
- 格式:&变量
举例:
int num = 10,*p1;
p1 = #
printf("%d",num);
printf("%p",pi)
说明:
1、在输出取址运算获得的地址时,需要使用“%p”作为格式输出符。
2、这里num的4个字节,每个字节都有地址,取出的是第一个字节的地址(较小的地址)。
指针变量赋值
- 赋值给一个指针变量:int *p = #
- 同时赋值两个指针变量;
int a = 10;
int *b, *c;
b = &a;
c = b;//a,b,c输出的地址相同
注意事项:
- 不能将任何其它非地址类型的数据赋给一个指针变量。
- 指针变量是“ 带类型的地址 ”。所以,一个指针变量只能指向同一个类型的变量,不能抛开类型随意赋值。
在没有对指针变量赋值时,指针变量的值是不确定的,可能系统会分配一个未知的地址,此时使用此指针变量可能会导致不可预料的后果甚至是系统崩溃。不要在给指针变量赋值之前使用此变量。可以在调用前,给指针变量赋初始值为0(或null),称为 空指针变量。
例题:通过指针变量修改内存中存储的数据。
方式1:
int num = 10,*p4;
p4 = #
printf("num1原值:%d\n",num);
printf("请输入要改变的值:");
scanf("%d",p4);//双引号中不能加其他的字符,会使结果出错。
printf("num1改变后的值:%d\n",num);
方式2:
int num = 10;
int *p = #
*p = 20;
printf("num = %d\n",num); //num = 20
2.取值运算符 :*
作用在指针变量上,取出地址代表的值。也称 ‘解引用符号’
* 指针表达式;使用方式:*p(p是指针变量)或 *& a(a:数据变量)
注意:“ * ”不同于定义指针变量的符号,这里是运算符。
举例1:
int main() {
int a = 2024;
int *p;
p = &a;
printf("%p\n",&a); //0000005cc43ff6d4
printf("%p\n",p); //0000005cc43ff6d4
printf("%d\n", *p); //2024
return 0;
}
举例2:通过指针变量修改指向内存地址位置上的值
int main() {
int num = 10;
int *p = #
*p = 20;
printf("num = %d\n",num); //num = 20
return 0;
}
举例3:定义指针变量 p1、p2,默认各自指向整数a、b;a、b从键盘输入。设计程序,使得 p1 指向其中的较大值,p2 指向其中的较小值 。
int a,b;
scanf("%d%d",&a,&b);
int *p1,*p2;
p1 = &a;
p2 = &b;
if(a<b){
int *t = p1;
p1 = p2;
p2 = t;
}
printf("%d%d",*p1,*p2);
“ &* ”和“ *& ” 的区别和含义:
int a = 10;
int *p;
p = &a;
- &*p 的含义是什么?——&*p 与 &a 相同,即变量a的地址。
- *&a 的含义是什么? ——*&a 和 *p 的作用是一样的,它们都等价于变量a。即 *&a 与 a 等价。
3. 指针的常用运算
指针本质上就是一个无符号整数,代表了内存地址。除了上面提到的取址运算外,指针还可以与整数加减、自增自减、同类指针相减运算等。但是规则并不是整数运算的规则。
a.指针与整数值的加减运算
格式:指针 +(-) 整数
作用:在一个连续的同类型数据区域内,通过与整数值的加减获取相应地址的值。
指针加减运算的解释:
- 指针与整数值的加减运算,表示指针所指向的内存地址的移动(加,向后移动;减,向前移动)。
- 指针移动的单位,与指针指向的数据类型有关。数据类型占据多少个字节,每单位就移动多少个字节。
举例1:
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int *p = &arr[1];
printf("p的地址为:%p,对应的值为%d\n", p, *p);
//p1的地址为:000000df21bff6e4,对应的值为2
printf("p+1=的地址为:%p,对应的值为%d\n", p + 1, *(p + 1));
//p1+1=的地址为:000000df21bff6e8,对应的值为3
printf("p-1=的地址为:%p,对应的值为%d\n", p - 1, *(p - 1));
//p1-1=的地址为:000000df21bff6e0,对应的值为1
return 0;
}
注意:只有 指向连续的同类型数据区域 ,指针加、减整数才有实际意义。
举例2: 定义一个一维数组,遍历其中的元素(用传统的方式 和 取地址的方式)
int array1[5] = {1,2,3,4,5};
//方式1:(传统方式)
for (int i = 0; i < 5; ++i) {
printf("%d",array1[i]);
}
printf("\n");
//方式2:(指针变量)
int *arr = array1;
for (int i = 0; i < 5; ++i) {
printf("%d",*(arr + i));
}
//方式3:比较运算符
for ( arr = array1; arr < array1 + 5; ++arr) {
printf("%d",*arr);
}
printf("\n");
b.指针的自增、自减运算
指针类型变量也可以进行自增或自减运算,如下:
p++ 、 p-- 、 ++p 、--p
例1:
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int *p1 = &arr[0];
int *p2 = &arr[3];
printf("p1的值为:%d\n", *p1); //1
printf("++p1的值为:%d\n", *(++p1)); //2
printf("p1的值为:%d\n", *p1); //2
return 0;
}
例2:
int a[5] = {10,20,30,40,50};
int *p = a; //p开始时指向数组a的首元素
printf("%d\n",*p++); //10 分析:由于++和*同优先级,结合方向自右而左,因此它等价于*(p++)
//先取值打印出来,然后在执行++的操作。
printf("%d\n",*p); //20
注意:自增和自减要分清楚 先赋值在自增(自减),还是先 自增(自减)再赋值。
c.同类指针相减运算
格式:指针 - 指针
注意:
- 相同类型的指针允许进行减法运算,返回它们之间的距离,即 相隔多少个数据单位(注意:非字节数) 。高位地址减去低位地址,返回的是正值;低位地址减去高位地址,返回的是负值。
- 返回的值属于 ptrdiff_t 类型,这是一个带符号的整数类型别名,具体类型根据系统不同而不同。这个类型的原型定义在头文件 stddef.h 里面。
例:
#include <stdio.h>
#include <stddef.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int *p1 = &arr[0];
int *p2 = &arr[3];
printf("p1的地址为:%d\n", p1); //497022544
printf("p2的地址为:%d\n", p2); //497022556
ptrdiff_t dist = p2 - p1;// 返回值数据类型:prtdiff_t
printf("p2-p1=%d\n", dist); //3 等同于 (497022556 - 497022544)/4 ==> 3
return 0;
}
- 两个指针相减,通常两个指针都是指向同一数组中的元素有意义。结果是两个地址之差除以数组元素的长度。
- 两个指针相加:是非法的,结果没有意义。
d. 指针间的比较运算
指针之间的比较运算,比如 ==、!= 、<、 <= 、 >、 >=。比较的是各自的内存地址的大小,返回值是整数 1 (true)或 0 (false)。
例:定义一个一维数组,遍历其中的元素(方式3:比较运算+自增)
for ( arr = array1; arr < array1 + 5; ++arr) {
printf("%d",*arr);
}
printf("\n");
例2:
int arr[5] = {1, 2, 3, 4, 5};
int *p1 = &arr[0];
int *p2 = &arr[3];
printf("%d\n",p1 > p2); //0
printf("%d\n",p1 < p2); //1
printf("%d\n",p1 == p2); //0
printf("%d\n",p1 != p2); //1
野指针
野指针:指针指向的位置不可知(随机性,不确定性,没有明确的限制)
野指针的成因及解决方法
情况1:指针使用前未初始化。
定义:指针变量在定义时如果未初始化, 其值是随机的 ,此时操作指针就是去访问一个不确定的地址,所以结果是不可知的。
错误的例子:
#include<stdio.h>
int main(){
int* p;
*p = 10;
return 0;
}
在没有给指针变量显式初始化的情况下,一系列的操作(包括修改指向内存的数据的值)也是错误的。如上述代码所示。
避免方法:
定义指针的时候,如果没有确切的地址赋值,为指针变量赋一个NULL 值是好的编程习惯。即;
int *p = NULL;
赋为 NULL 值的指针被称为 空指针 ,NULL 指针是一个定义在标准库 <stdio.h>中的值为零的常量 #define NULL 0
情况2:指针越界访问
定义: *p 访问的内存空间不在数组有效范围内,此时 *p 就属于非法访问内存空间,p为野指针。
错误的例子:定义一个长度为10的一维数组,使用指针访问数组的元素时超出了数组的范围。
int arr[10] = {0};
int *p = arr;
for (int i = 0; i <= 10; i++,p++) {
*p = i;
//当i=10时,越界。
避免方法: 小心指针越界,进行仔细检查
情况3:指针指向已经释放的空间
假如,定义了一个函数,函数的返回值是一个定义在函数内部局部变量的地址,(由于局部变量只在函数内有效,出了函数其内存空间就被释放,也就意味着 局部变量的地址编号不存在 )若将函数返回的地址赋值给外部的一个指针变量,导致指针变量获取到的地址是 无效 的。
例:
//定义了一个函数,函数的返回值是内部局部变量的地址
int *test() {
int a = 10;
return &a; //&a=0x0012ff40
}
//将上面函数的地址赋值给主函数中定义的一个指针变量
int main() {
int *p = test();
printf("%d", *p); //由于空间释放的原因,将不会输出值
return 0;
}
避免方法: 避免返回局部变量的地址。使用前要查看是否返回的是局部变量的地址。
其他避免出现野指针的方法:
1. 指针指向空间释放,及时置NULL
int a = 10;
int* pa = &a;
printf("%d\n", *pa);
pa = NULL; //把pa指针置成NULL
printf("%d\n",pa);
2.指针使用之前检查有效性
if (pa != NULL){
//进行使用
}
if (pa == NULL){
//不进行使用
}
二级指针(多重指针)
1.定义
记录一个指针地址的指针变量,即二级指针。
格式:数据类型 **指针名
举例:
int a = 10;
int *pa = &a; //pa是一级指针
int **ppa = &pa; //ppa是二级指针,类型为int **
解引用:
int var = 3000;
int *ptr = &var; // 一级指针指向 var
int **pptr = &ptr; // 二级指针指向 ptr
printf("Value of var: %d\n", var);
printf("Value of ptr: %d\n", *ptr); // 解引用一次
printf("Value of pptr: %d\n", **pptr); // 解引用两次
//上面三个输出的都是变量var的值,3000
2.二级指针的练习
举例1:使用malloc()函数创建一维数组。
int length = 5;
int *p1 = (int*)malloc(length * sizeof (int));//创建一个动态内存堆空间
free(p1);//释放空间。
举例2:使用malloc()函数创建二维数组。
步骤:
- 第一步:row和column;
- 第二步:初始化外层数组;
- 第三步:初始化内层数组,并给内存数组元素进行赋值。
int row,column;
printf("请输入行和列数:");
scanf("%d%d",&row,&column);
//初始化外层数组,二维数组的行数。使用二级指针加上malloc()函数
int **arr = (int**)malloc(row * sizeof(int*));//int*:每一个元素指向了一个一维数组
//初始化内层数组,并给元素赋值
for (int i = 0; i < row; ++i) {
arr[i] = (int*) malloc(column * sizeof (int));
//赋值
for (int j = 0; j < column; ++j) {
arr[i][j] = 1;
printf("%d",arr[i][j]);
}
printf("\n");
}
//使用完回收此数组
free(arr);
7万+

被折叠的 条评论
为什么被折叠?



