指针的再认识(进阶(1))
文章目录
一、指针是什么?
- 指针是内存中一个最小单元的编号,也就是地址。
- 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量。
- 指针的大小是固定的4/8个字节(32位/64位平台)。
- 指针是有类型的。
指针和指针的类型
1.指针的类型
在这里,我们知道变量有不同的类型,例如整型,浮点型等,那么指针有没有类型呢?
char *pc = NULL;
int *pi = NULL;
short *ps = NULL;
long *pl = NULL;
float *pf = NULL;
在这里我们可以看到其实:
char* 类型的指针是为了存放 char 类型变量的地址。
short* 类型的指针是为了存放 short 类型变量的地址。
int* 类型的指针是为了存放 int 类型变量的地址。
** NULL**
指针,标准定义了NULL指针,它作为一个特殊的指针变量,表示不指向任何东西,为了测试一个指针变量是否为NULL,可以将其与零值进行比较(这是因为一种源代码约定)。
2.指针的解引用
指针的类型决定了,对指针解引用的时候有多大的权限(就是能够操作几个字节)。比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。看下列一段代码:
#include <stdio.h>
int main()
{
int n = 0x11223344;
char *pc = (char *)&n;//强制类型转换
int *pi = &n;
*pc = 0; //重点在调试的过程中观察内存的变化。
*pi = 0; //重点在调试的过程中观察内存的变化。
return 0;
}
(char *)&n强制类型转换,将(int *)&n的类型转换为char *,通过监视可以看到,pc,pi 的地址相同的。
我们再看这个代码会有什么问题?
int *a
*a = 20;
创建了一个名为a的指针变量,后面的语句将20存储在a所指向的内存位置,但是a究竟指向哪里的?我们只是声明了这个变量但是并没有对其进行初始化,所以我们也就不知道20这个值将存储到哪里。这就是野指针,野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。这是造成野指针的原因之一,另一个重要的原因便是指针越界访问,例如:
#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;
}
下面将介绍本文的“重头戏”
二、字符指针
我们知道一种指针类型为字符指针 char*
int main()
{
char ch = 'w';
char* pc = &ch;//char*是pc的类型
*pc = 'b';
printf("%c ", ch);
const char* p = "abcdef"; //把字符串首字符a的地址赋值给p
char arr[] = "abcdef"; //把字符串放在数组
*p = 'w'; //const修饰 改不了
printf("%s\n", p);
return 0;
}
这里容易产生误解,误以为是把字符串abcdef放入了字符指针p中,然而事实上是将常量字符串的首字符a的地址赋给了指针p。
没关系我们再来看下列这串代码
int main()
{
char arr1[] = "hello";
char arr2[] = "hello";
const char* P1 = "hello";
const char* P2 = "hello";
if (arr1 == arr2)
printf("arr1 == arr2\n");
else
printf("arr1 != arr2\n");
if (P1 == P2)
printf("P1 == P1\n");
else
printf("P1 != P2\n");
}// P1 P2指向同一块地址
我们发现P1和P2指向的是同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。
三、指针数组
指针数组强调的是数组 ,是用来存放指针的数组。
首先我们看一个例子:
int* arr1[6];//存放整型指针的数组
表示数组arr1中存放的是int* 型的整型指针
char* arr2[5];//存放字符指针的数组`
表示数组arr2中存放的是char*型的字符指针
看下面这个例子:
我们创建3个一维数组,通过指针数组放入arr1,arr2,arr3三个数组首元素的地址,parr是存放指针的数组。所以我们就模拟了一个二维数组(这个数组的地址不是连续的,大家的自行去验证)
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 3,4,5,6,7 };
int arr3[] = { 4,5,6,7,8 };
int* parr[3] = { 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));
//*(p+i)-->p[i]
//printf("%d ", parr[i][j]);
}
printf("\n");
}
return 0;
}
内存布局:
代码运行结果:
所以我们就模拟了一个二维数组(这个数组的地址不是连续的,大家可以自行去验证)
*(p+i)–>p[ i ]
printf("%d ", parr[ i ][ j ]);
此处 p+i 其实计算的是数组 parr 下标为 i 的地址,那我们就可以直接通过指针来访问数组。
四、数组指针
1.提出一个问题
数组指针是指针?还是数组?
答案是:指针。我们可以类比得出结果:
// 整型指针是用来存放整型的地址
// 字符指针是用来存放字符的地址
// 数组指针是用来存放数组的地址
也就是说,数组指针,就是指向数组的指针。
例如:
int (*p)[10];
p是数组指针,p可以指向一个数组,该数组有10个元素且每个元素的类型为int。
这里注意,[]的优先级要高于 * 号的,所以必须加上()来保证p先和*结合。
2.数组名
数组名通常表示的是数组首元素的地址
但是有2个意外!!!
<1> sizeof(数组名),这里的数组名表示真个数组,计算整个数组的大小,单位是字节
<2> &数组名,这里的数组名表示的依然是整个数组,&数组名 取出整个数组的地址
以下代码可供我们深入理解数组指针以及数组指针的正确用法:
其中print1函数是我们学过常规遍历数组的形式,而print2函数使用数组指针 **int (*p)[5]**作为参数,是指向一维数组的指针。
void print1(int arr[3][5], int r, int c)//遍历数组的形式
{
int i = 0;
for (i = 0; i < r; i++)
{
int j = 0;
for (j = 0; j < c; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
void print2(int (*p)[5],int r, int c)//指向一维数组的指针
{
int i = 0;
for (i = 0; i < r; i++)
{
int j = 0;
for (j = 0; j < c; j++)
{
//printf("%d ", *(*(p + i)+j));//*(p+i)--首元素的地址-->p[i]
printf("%d ", p[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };
print1(arr, 3, 5);
print2(arr, 3, 5);
return 0;
}
图解
在此处,可这样理解
printf("%d ", ((p + i)+j));//*(p+i)–首元素的地址–>p[i]
总结
通过本文,希望可以帮助大家进一步理解指针的内容,以及它们的使用场景,如有不足之处还望大佬指点!最后,点赞收藏加关注!!!