::: tip 指针的概念: 1.指针是一个用来存放地址的变量,地址唯一标识一块内存空间。 2.指针的大小是固定的4/8个字节( 32位平台/64位平台)。 3.指针的类型决定了指针的±整数的步长,指针解引用操作时能操作内存空间的大小。 4.指针的运算。 :::
字符指针
两种用法:
- 一般用法
int main() { char a = 'w'; char* p = &a; *p = 'w'; return 0; }
- 特殊
char* p = "abcdefg";
上述代码的含义是将字符串中首字符的地址赋给p。 常量字符串abcdefg赋给指针变量p时,该字符串加上\0共有8个字节,超出指针p大小,所以其实是将第一个字节a的地址赋给p。
printf("%c\n",*p); //a
指针p中存放的字符串首字符地址,*p解引用可以得到a
printf("%s\n",p); //abcdefg
从p中存放的地址处开始打印一个字符串,即从a的地址出向后打印一个字符串(遇到字符串结尾的\0打印停止)。
::: warning 上述代码中的char* p = "abcdefg";准确写法如下:
const char* p = "abcdefg";
“abcdefg”是一个常量字符串,不能通过指针解引用的方式修改,加入const可以在编译时就提示报错。 :::
题目
int main()
{
char arr1[] = "abcdefg";
char arr2[] = "abcdefg";
if (arr1 == arr2)
{
printf("haha");
}
else
{
printf("hehe");
}
return 0;
}
hehe
上述代码中创建两个数组占据内存中两个不同的空间,数组名是数组首元素地址,所以arr1和arr2不相等。
int main()
{
const char* p1 = "abcdefg";
const char* p2 = "abcdefg";
if (p1 == p2)
{
printf("haha");
}
else
{
printf("hehe");
}
return 0;
}
haha
上述代码第3行和第4行中的"abcdefg"是常量字符串,完全一致且不可修改,出于节省内存空间,两个常量字符串"abcdefg"只存储了一个,p1和p2都指向了同一块内存空间的起始位置,即p1 == p2。
指针数组
指针数组是用来存放指针的数组。
int main()
{
int a = 10;
int b = 20;
int c = 30;
int d = 40;
int* p[] = { &a,&b,&c,&d };
return 0;
}
指针数组的使用场景
int main()
{
int arr1[] = { 1,2,3,4,5,6 };
int arr2[] = { 2,3,4,5,6,7 };
int arr3[] = { 3,4,5,6,7,8 };
int* parr[] = { arr1,arr2,arr3 };
int i = 0;
int j = 0;
for (i = 0; i < 3; i++)
{
//printf("%d ", *parr[i]); 得到每个数组的首元素1、2、3
for (j = 0; j < 6; j++)
{
printf("%d ", *(parr[i]+j)); //数组首元素地址向后偏移
}
printf("\n");
}
return 0;
}
1 2 3 4 5 6
2 3 4 5 6 7
3 4 5 6 7 8
上述代码中第14行先通过parr[i]找到了指针数组中每一个元素,即arr1,arr2,arr3,数组名是数组首元素的地址,每次+j遍历出数组中所有的元素的地址,最后解引用打印数组中所有的元素。
数组指针
::: warning 数组指针是能够指向数组的指针。 arr - 首元素地址 &arr[0] - 首元素地址 &arr - 数组地址 ::: &arr是数组地址,不能使用int整型接收,应该使用int( * p)[]的数组指针形式接收。 ::: tip int * p[10]中[]的优先级高于 * ,p先和[]结合变为数组,整体是一个存放指针的数组。如果 * 要和p先结合,需要加圆括号。 *p是指针,其余的int [10]是数组类型,结合起来int( * p)[10]是数组指针( * p指向数组[10],数组中每个元素类型是int)。 :::
数组指针的用法
数组指针一般用在二维数组以上,一维数组的使用时比较繁琐。
int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
数组名arr除了取地址&和sizeof以外都表示数组的首元素地址。 ::: tip 二维数组的首元素需要先把二维数组当作一个一维数组,每一行当作一维数组的一个元素,即arr[3][5]中有三个元素。 二维数组的首元素地址是第一行一维数组的地址。 二维数组第一行是一个一位数组,其中有5个int类型的元素。 :::
void Print2(int(*p)[5], int x, int y) //使用指针形式接收
//数组的地址应该使用数组指针接收。
//数组指针指向的一维数组是二维数组的第一行,共有5个整型元素。
{
//p是指向第一行数组的指针,p+1跳过一个数组(5个元素),p每次+1指向下一行
int i = 0;
for (i = 0; i < x; i++)
{
int j = 0;
for (j = 0; j < y; j++)
{
//方法1
//printf("%d ", *(*(p + i) + j));
//p + 1跳过i行指向下标为i的一行,解引用后找到i行
//*(p + i)取得i行的一维数组名,即i行首元素的地址
//*(p + i) + j取得该行下标为j的元素地址
//*(*(p + i) + j)对该地址解引用取得i行j列的元素
//方法2
//printf("%d ", (*(p + i))[j]);
//p + 1跳过i行指向下标为i的一行,解引用后找到i行
//*(p + i)取得i行的一维数组名
//(*(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); //数组传参
printf("\n");
Print2(arr, 3, 5); //数组传参
//数组名arr是首元素地址,首元素是第一行,arr代表第一行的地址。
//第一行是一个一维数组,即传到函数Print中的是一个一维数组的地址。
return 0;
}
数组指针形式的几种打印写法
int main()
{
int arr[] = { 1,2,3,4,5,6 };
int* p = arr;
int i = 0;
for (i = 0; i < 6; i++)
{
printf("%d ", *(arr + i));
printf("%d ", *(p + i));
printf("%d ", arr[i]); //以arr为起始地址访问下标为i的元素
printf("%d ", p[i]); //以p为起始地址访问下标为i的元素
}
return 0;
}
上述代码中的四种打印方式的结果完全相同。 int * p = arr;表示p和arr是等价的。
arr[i] == *(arr + i) == p[i] == *(p + i)
由上可得,上述代码的Print()函数还有以下两种打印形式:
printf("%d ", *(p[i] + j));
printf("%d ", *(p[i][j]);
::: warning 处理二维数组的思路: 将二维数组想象成一维数组,此时二维数组的首元素地址是第一行元素的地址。 第一行元素组成一个一维数组,地址可以存在一个指向该一维数组的指针中。此时数组可以写成指针形式,并可以用该指针访问二维数组。 :::
参考视频:C语言从入门到进阶 第35节指针详解(2) https://www.bilibili.com/video/BV1oi4y1g7CF?p=35&spm_id_from=pageDriver&vd_source=df7bbd0bde58584ffdbf01858a3d03c6
小结1
几个概念:
int arr[5];
//arr是一个5个元素组成的整型数组
int* parr1[10];
//parr1是一个10个元素组成的指针数组,每一个元素的类型是int*
//parr1优先和[10]结合组成数组([]的优先级高)
int(*parr2)[10];
//parr2是一个数组指针,该指针指向一个有10个int类型元素的数组
int(*parr3[10])[5];
//parr3优先和[10]结合组成数组,
//parr3是数组名,[10]是数组个数,其余int(*)[5]是类型。
//int(*)[5];类似于int(*parr2)[10]的形式,即该类型也是指向数组的指针类型。
//可以认为parr3是一个有10个元素的数组,每一个元素是一个数组指针,
//该数组指针指向一个包含5个int类型元素的数组。
int(*parr3[10])[5];的存储如下图: