🔥博客主页:小王又困了
📚系列专栏:C语言
🌟人之为学,不日近则日退
❤️感谢大家点赞👍收藏⭐评论✍️
目录
前言:指针是C语言中一个重要概念,也是C语言的一个重要特色。正确灵活的运用它,可以使程序简洁、高效,我们要深入学习和掌握指针。指针个概念比较复杂,使用时也比较灵活,所以我们在学习时要多思考,多练习,在实践中掌握它。接下来就让我们进入指针的学习。
一、指针是什么
想要弄清楚指针,必须先弄清楚数据在内存中是如何存储的,又是如何读取的。
如果在程序中定义一个变量,在对程序编译时,系统会给变量分配内存单元,编译系统会根据定义的变量类型,分配一定长度的空间。例如:为整型变量提供4个字节空间,为字符变量提供一个字节空间。
将内存分成一个一个小的单元,每个单元是一个字节,每一个字节都有一个编号,这就是“地址”
我们可以换一种理解方法,地址就相当于旅馆中的房间号,地址所标志的内存单元中存放的数据相当于旅馆房间中住的客人。例如:房间门口挂一个房间号2008,这个2008就是房间的地址,或者说2008指向该房间。因此,地址形象化为“指针”,意思就是通过它可以找到以它为地址的内存单元。
说明:我们对存储单元进行访问,我们除了要位置信息外,还要该数据的类型信息(例如:整形和单精度浮点型都是4个字节,但存储的方式不同),只有数据地址信息,而没有类型信息是无法对该数据进行存取的。
总结:指针就是地址,口语中说的指针通常指的是指针变量。
指针的大小在32位平台是4个字节,在64位平台是8个字节。
#include <stdio.h>
int main()
{
int a = 10; //在内存中开辟一块空间
int* p = &a; //这里我们对变量a,取出它的地址,可以使用&操作符。
//a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量中,p就是一个之指针变量。
//int* 是指针变量p的类型
return 0;
}
如果有一个变量专门存放另一个变量的地址(即指针),则称他为“指针变量”。上面p就是一个指针变量。指针变量就是地址变量,用来存放地址,指针变量的值就是地址(即指针)。
注意:区分“指针”和“指针变量”这两个概念。例如,可以说变量i的指针(即地址)是2000,而不能说i的指针变量是2000,指针是一个地址,而指针变量是存放地址的变量。
二、指针变量
怎么定义指针变量
类型名* 指针变量名
说明:在定义指针变量时要注意
(1).指针变量前面的“*”表示该变量为指针变量。
(2).在定义指针变量时必须指定基类型。一个变量的指针的含义包括两个方面,一是以存储单元编号表示的纯地址,一是它指向的存储单元的数据类型。
三、指针和指针类型
所有类型的指针大小都相同,为什么不用一个相同的类型呢?我们通过指针类型的意义,来解答这个疑问
指针类型的意义
int*的指针解引用访问4个字节
char*的指针解引用访问一个字节
结论:指针类型可以决定指针解引用的时候访问几个字节(指针的权限)
指针类型决定了指针+1操作时跳过的字节
整形指针跳过4个字节
字符指针跳过1个字节
四、野指针
概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
4.1野指针的成因
1.指针未初始化
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
2.指针越界访问
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
3.指针指向的空间释放
#include <stdio.h>
int* test()
{
int a = 100;
return &a;
}
int main()
{
int* p = test();
printf("%d\n", *p);
return 0;
}
野指针就像大街上的野狗,是危险的,那在我们编程中怎样避免写出野指针呢?
4.2如何规避野指针
1. 指针初始化
-
明确知道指针应该初始化为谁的指针,就直接初始化
- 不知道指针初始化为什么值,暂时初始化为NULL
#include <stdio.h>
int main()
{
int a = 10;
int* p = &a;
int* ptr = NULL;
return 0;
}
C++中NULL就是数字0
C语言中是将0强制转化为void* --本质还是0
2. 小心指针越界
3. 指针指向的空间释放,及时置NULL
4. 避免返回局部变量的地址
5. 指针使用之前检查有效性
五、指针运算
- 指针+- 整数
- 指针-指针
- 指针的关系运算
5.1 指针+- 整数
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
*p = i;
p++;
}
for(i = 0; i < 10; i++)
{
printf("%d ", *(p+i));
}
for(i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
5.2 指针-指针
指针-指针运算的前提条件是两个指针指向了同一块空间
应用:计算字符串的 长度
#include <stdio.h>
int my_strlen(char* s)
{
char* start = s;
while (*s != '\0')
{
s++;
}
return s - start;
}
int main()
{
char arr[] = "abcdef";
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
5.3 指针的关系运算
#define N_VALUES 5
float values[N_VALUES];
for(vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 0;
}
将代码简化,修改为:
#define N_VALUES 5
float values[N_VALUES];
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
*vp = 0;
}
这种写法在绝大部分的编译器上是可以顺利完成任务的,但是我们还是应该避免这样写,因为标准并不保证它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与 指向第一个元素之前的那个内存位置的指针进行比较。
可以拿p和p2比,但是不可以拿p和p1比
六、指针和数组
指针和数组虽然是不同的东西,但它们之间有着一些联系,接下来就让我带着大家学习两者之间的相同点和不同点。
根据上述结果我们可以得出数组名就是数组首元素的地址。
将数组视为指针,也催生除了数组和指针的密切关系
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr;//p存放的是数组首元素的地址
这里声明了数组arr和指针p。p初始化的值是arr,因为数值名arr会被解释为&arr[0],所以p中存放的值为&arr[0]。也就是说p会被初始化为指向数组arr的起始元素arr[0]的地址。
注意:p指向的是起始元素,不是整个数组。
6.1数组名不是首元素地址的情况
1. 作为sizeof运算符的操作数出现
sizeof(数组名) 不会生成指向起始元素的指针的长度,而是生成数组整体的长度。
2. 作为取地址操作符的操作数出现
&数组名 不是指向起始元素的地址的指针,二是指向数组整体的指针。
int a[5];
int b[5];
a=b; //错误
这样赋值,那么数组a的地址就会被改变。因此 ,赋值表达式的左操作数不能是数组。
“不可以使用赋值运算符改变指向数组起始元素的指针”
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr)/sizeof(arr[0]);
for(i=0; i<sz; i++)
{
printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p+i);
}
return 0;
}
6.2 通过指针引用数组元素
- 下标法:如a[i]的形式
- 指针法:如*(a+i)或*(p+i)。其中a是数组名,p是指向数组元素的指针变量,初始值p=a
(1)下标法
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9 };
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
(2)通过数组名计算数组元素地址,找出元素
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9 };
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *(arr + i));
}
return 0;
}
(3)用指针变量指向数组元素
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9 };
int* p = arr;
int i = 0;
for (p = arr; p < arr + 9; p++)
{
printf("%d ", *p);
}
return 0;
}
注意:在使用指针变量指向数组元素是要注意
1.可以通过改变指针变量的值指向不同的元素,如指针变量p来指向元素,用p++使p的值不断改变从而指向不同的元素。
2.要注意指针变量的当前值。
6.3以变量名和数组名作为函数参数的比较
实参类型 | 变量名 | 数组名 |
要求形参的类型 | 变量名 | 数组名或指针变量 |
传递的信息 | 变量的值 | 实参数组首元素的地址 |
通过函数调用能否改变实参的值 | 不能改变实参变量的值 | 能改变实参变量的值 |
注意:实参数组名代表的是一个固定的地址,或者说是指针变量,但形参数组名并不是一个固定的地址,而是按指针变量处理。
七、二级指针
int main()
{
int a = 10;
int* p = &a; //p是一级指针,指针变量也是变量,是变量就有地址,在内存上开辟空间
int** pp = &p; //pp是二级指针变量,二级指针变量是用来存放一级指针变量的地址
}
二级指针的运算有:
- *ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa
int a = 10;
int* p = &a;
int** pp = &p;
*pp=&a; //等价于 p = &a
- **ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a
**pp = 30;
//等价于*p = 30
//等价于a = 30
八、指针数组
8.1什么是指针数组
一个数组,若其元素均为指针类型数据,称为指针数组。也就是说,指针数组中的每一个元素都存放一个地址。下面定义一个指针数组:
int* p[4];
定义指针数组的形式
类型名 * 数组名[数组长度]
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,6 };
int* parr[] = { arr1,arr2,arr3 };
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ", *(parr[i] + j));
}
printf("\n");
}
return 0;
}
本次的内容到这里就结束啦。希望大家阅读完可以有所收获,同时也感谢各位读者的支持。文章有问题可以在评论区留言,博主一定认真认真修改,以后写出更好的文章。