指针进阶
关于指针的部分基础概念:
- 指针就是变量,用来存放地址,地址唯一标识一块内存空间。
- 32位平台指针的大小固定为4个字节,64位平台固定为8个字节。
- 指针是有类型,指针的类型决定了指针加减整数的步长,指针解引用操作的时候的权限。
这篇博客主要介绍指针数组和数组指针及传参。
先看一段代码:
int main()
{
char str1[] = "hello word";
char str2[] = "hello word";
const char* str3 = "hello word";
const char* str4 = "hello word";
if (str1 == str2)
printf("str1 == str2");
else
printf("str1!=str2");
if (str3 == str4)
printf("str3 == str4");
else
printf("str3!=str4");
return 0;
}
//输出结果为
//str1!=str2str3 == str4
这段代码展示了,基本类型变量str1
和str2
在内存中单独开辟空间来存放hello word
,而指针变量str3
和str4
指向的是常量字符串,不能修改(如下面代码,将指针变量str4
进行修改),因此在内存中只会存在一份。
int main()
{
char str1[] = "hello word";
char str2[] = "hello word";
const char* str3 = "hello word";
const char* str4 = "hello word";
const char* str4 = "h";
if (str1 == str2)
printf("str1 == str2");
else
printf("str1!=str2");
if (str3 == str4)
printf("str3 == str4");
else
printf("str3!=str4");
return 0;
}
修改指针变量str4
的执行结果:
指针数组
指针数组本质上就是数组中存放的是指针也就是地址。如何使用,请看代码:
int main()
{
int a = 5;
int b = 5;
int c = 5;
int* arr[3] = { &a, &b, &c };
return 0;
}
通过int* e = &a;
可以创建指针变量,int* arr[3];
表示创建存放整型指针的数组其中有3个元素(每个元素的类型是int*
也就是整型指针)。再看一个例子,打印出三个数组中元素:
int main()
{
int a[] = { 1, 2, 3, 4, 5 };
int b[] = { 2, 3, 4, 5, 6 };
int c[] = { 3, 4, 5, 6, 7 };
int* arr[] = {a, b, c}; //数组名字就相当于数组首元素地址,相当于把abc数组首元素地址传给arr
int i = 0;
for (i = 0; i < 3;i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
//arr[i]是内存地址,加j表示地址加j,会跳到下一个元素的地址。
printf("%d ",*(arr[i]+j));
//也可以这样子printf("%d ",arr[i][j]); arr[i][j]相当于上面,[j]可以理解为+j再解引用
}
}
return 0;
}
上述代码执行结果:
数组指针
数组指针,是指针中存放的是数组的地址。下面代码以int
类型和double
以及double*
类型创建了数组指针。
int main()
{
int arr[10] = { 1, 2, 3, 4, 5 };
int(*pa)[10] = &arr; //去除数组的地址
//parr就是数组指针-其中存放的是数组的地址
double* d[5]; //数组中的每个元素是double*
double* (*pd)[5] = &d;
return 0;
}
int (*pa)[10] = &arr ;
中int
表示数组的类型,*pa
表示其是一个指针,[10]
表示指针指向的是一个包含10个元素的数组。
数组指针和指针的区别
int main()
{
int arr[10] = { 0 };
//值一样,但是类型不一样
printf("%p\n",arr); //首元素地址 —— 操作长度不一致,可以打印加一看看结果
//00F3FA90
printf("%p\n",&arr); //数组的地址
//00F3FA90
printf("%p\n", arr + 1);
//00F3FA94
printf("%p\n", &arr + 1);
//00F3FAB8
//指针
int* p = arr; //指针指向首元素
//数组指针
int (*p2)[10] = &arr; //取数组的地址,首先(*p2)表示其是指针,[10]表示其指向为一个含有10个元素的数组,类型为int
printf("%p\n", p);
//00F3FA90
printf("%p\n", p+1); //跳过一个元素的4个字节
//00F3FA94
printf("%p\n", p2);
//00F3FA90
printf("%p\n", p2+1); // 跳过整个数组
//00F3FAB8
return 0;
}
arr
表示数组首元素的地址,&arr
显示的是首元素地址但其表示整个数组地址,因此打印的一样,整型数组每个元素都是4个字节,因此arr+1
其表示下一个元素的地址,因此是00F3FA90+4=00F3FA94
(16进制)。整个数组有10个元素,每个元素是4个字节,&arr+1
地址变化为00F3FA90+40=00F3FAB8
。int* p = arr;
创建指针指向的是首元素地址,p+1
之后地址变化为4个字节,int (*p2)[10] = &arr;
表示数组指针p2+1
会跳过整个数组和&arr
一致。
数组名是数组首元素的地址的两个例外
sizeof(数组名)
——数组名表示整个数组,计算的是整个数组大小,单位是字节。&数组名
——表示取出的是整个数组的地址。
数组指针的使用:
通过数组指针来打印一维数组中的元素:
int main()
{
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9,10};
int i = 0;
for (i = 0;i < 10;i++)
{
printf("%d ", *((*pa)+i));
// *pa就相当于arr,pa是整个数组的地址,*pa对其解引用,
// 表示拿到了整个数组相当于arr也就是首元素的地址,通常用于二维数组
}
return 0;
}
数组指针并不常用来处理一维数组。
使用数组指针打印二维数组中的元素:
void print(int (*p)[5], int r, int c) //数组指针接收,arr是第一个元素的地址,第一个元素是一个数组,因此接收的指针为数组指针,加1时可以直接跳过此数组,也就是直接跳过这一行
{
int i = 0;
int j = 0;
for (i = 0;i < r;i++)
{
for (j = 0;j < c;j++)
{
printf("%d ", (*(p+i)+j));
// p存放的是数组地址,p+1之后,会直接跳过一个数组,到第二行,p+i解引用后指的是第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;
}
print
函数传递arr
表示数组中首元素(是数组)的地址,也就是{1,2,3,4,5}
的地址,也是1的地址,通过数组指针的形式接收参数,p+1
后就跳过真个数组,地址从第一个数组到第二个数组,通过这种操作可以达到换行的目的。
传参
一维数组传参:
void test(int arr[])
{}
void test(int arr[10])
{}
void test(int *arr)
{}
void test2(int* arr2[])
{}
void test2(int* *arr2)
{}
int main()
{
int arr[10] = { 0 };
int* arr2[10] = { 0 }; //同样arr2指的也是首元素地址。
test(arr);
test2(arr2);
return 0;
}
二维数组传参以及一些错误示范:
//自定义函数中行可以省,列必须有。
void test1(int arr[3][5])
{}
void test1(int arr[][5])
{}
//这里的5不能省,少了会报错,如下图。
void test1(int (*arr)[5])
{}
//错误示范
void test1(int (*arr)[])
{}
void test1(int **arr)
{}
void test1(int *arr) //首元素是数组地址,因此得用数组指针
{}
int main()
{
int arr[3][5] = { 0 };
test1(arr);
return 0;
}