首先,我们来明确指针概念,指针是用来储存地址的变量,大小是固定的4/8个字节,具体看平台。这是因为地址是物理的电线上产生的,32位机器----32根地址线-----每根地址线产生1/0的电信号,那么以32位平台为例,32个0/1组成的二进制序列就需要4个字节来储存,同理,可推测64位平台就需要8个字节来储存地址了。这就是指针大小为什么是4/8个字节的原因。
指针的类型中我们知道有一种指针类型为字符指针 char*。
我们常用的方法是,定义一个字符变量,用字符指针指向字符变量。如下代码:
int main()
{
char ch = 'a';
char* p = &ch;
*p = 'b';
printf("%c", ch);
return 0;
}
但我们今天介绍新的一种使用方式:
int main()
{
char arr[] = {"abcdef"};
const char* p = "abcdef";
printf("%s\n", p);
printf("%c", *p);
return 0;
}
大家想一想运行结果会是怎样的?运行结果显示如下:
于是,我们发现当字符指针指向常量字符串时,指向的其实是字符串的第一个地址,而当我们输出字符串时,又发现字符串在内存中是连续储存的。这里有一个问题:为什么const不是让p指向的地址不变,而是让const指向的字符串不能被改变?这个问题先留给大家去思考。
那我们来看一道面试题目:
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
const char* str3 = "hello bit.";
const char* str4 = "hello bit.";
if (str1 == str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if (str3 == str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
return 0;
}
那么根据我们上面内容的讲解,大家预测一下输出应该是什么呢?
大家答对了吗?因为我们的数组在内存中开辟不同的内存,而数组名又代表首元素地址,所以str1和str2肯定不相同,而我们常量字符串则被开辟在代码区中,所以指向的是同一个地址。
类比概念,整型数组是存放整型变量的数组,那指针数组是一个存放指针的数组。
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
//数组名为首元素地址,arr1相当于int*
int* arr[3] = { arr1,arr2,arr3 };//指针数组
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
此代码模拟使用指针数组对二维数组进行遍历,结果如下:
类比概念,整型指针是指向整型变量的指针,储存的是整型变量的地址;那么数组指针就是指向数组的指针,储存的是数组的地址。所以我们需要先来了解数组的地址。
我们知道通常情况下数组名代表数组首元素地址,除了两种特殊情况:一种是sizeof(arr),此时代表整个数组,另一种是&arr,代表整个数组的地址。以下代码详细进行解释:
int main()
{
int arr1[5] = { 1,2,3,4,5 };
printf("%p\n", arr1);
printf("%p\n", arr1+1);
printf("%p\n", &arr1[0]);
printf("%p\n", &arr1[0] + 1);
printf("%p\n", &arr1);
printf("%p\n", &arr1+1);
return 0;
}
从运行结果我们可以清楚地看到,数组名代表的就是首元素地址,所以当arr1+1时,相当于指针向后移动一个元素,整形数组,所以移动一个元素就是4个字节,所以从E8变为EC。而&arr1虽然和arr1指向位置一样,但当&arr1+1时,发现从E8变为FC,十进制则为232到252,增加了20个字节,也就是一个数组的大小。所以我们得出结论&arr1得到的是整个数组的地址。
那么我们知道&arr可以得到数组的地址后,我们再来看数组指针的使用。
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//数组的地址,储存到数组指针变量里
int(*p)[10] = &arr;
return 0;
}
我们来看一下对于一维数组打印,两种不同方式,从而加深对数组指针的了解。
第一种方法:使用首元素地址进行打印,也是常用方法。
int main()
{
int arr[5] = { 1,2,3,4,5 };
int* p = arr;
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
arr为首元素地址,我们把它存入整型指针里面,然后每次对p进行+i操作,相当于逐步遍历数组元素,再解引用得到数组值,后打印整个数组。方法思想:使用整型指针加法操作进行遍历。
第二种方法:使用数组指针进行打印。
int main()
{
int arr[5] = { 1,2,3,4,5 };
int(*p)[5] = &arr;
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d ", *((*p) + i));
}
return 0;
}
我们把整个数组的地址存在数组指针中,此时,数组指针就是指向数组的指针,储存的是数组的地址,所以要用&arr。但这里的不同是我们先对p进行*得到数组名也就是首元素地址,再进行遍历操作,后再解引用此时得到值。方法思想:使用数组指针进行对一维数组的遍历。
那么一维数组常见的使用方法就是这两种,那么我们思考一下二维数组的遍历?
二维数组的每一行可以理解为二维数组的一个元素,每一行又可以理解为一个一维数组。所以,二维数组其实是元素为一维数组的数组,那我们来思考一下如何利用数组指针对二维数组进行遍历?二维数组的数组名,为首元素地址,那么可以理解为为首行元素地址,也就是一维数组的地址,也就是数组的地址,所以需要使用数组指针。两种方式实现方法如下:
第一种方法,使用数组传参,直接进行打印
Print(int arr[][5], int m, int n)
{
int i = 0;
for (i = 0; i < m; i++)
{
int j = 0;
for (j = 0; j < n; j++)
{
printf("%d ", arr[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 };
Print(arr, 3, 5);
//传参:数组,数组行,数组列
return 0;
}
第二种方法,使用数组指针进行打印。
Print(int (*p)[5], int m, int n)
{
int i = 0;
for (i = 0; i < m; i++)
{
int j = 0;
for (j = 0; j < n; j++)
{
printf("%d ",*( * (p + i) + j));
//*p数组指针,指向数组的地址,对数组指针解引用得到首元素地址
//再对首元素地址+j得到二维数组的位置,再进行解引用得到二维数组元素
//printf("%p ", *(p + i));
//*(p + i)得到的是第一,二,三行数组的地址
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { 1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7 };
//数组指针,指向数组的地址,首元素地址即为第一行元素地址,为一个数组,所以使用数组指针。
Print(arr, 3, 5);
//传参:数组,数组行,数组列
return 0;
}
通过一维数组和二维数组的遍历,希望大家可以充分理解指针和数组的关系,包括数组指针和指针数组的具体应用,今天的内容补充就到这里,希望大家可以一起讨论!一起学好C语言。