目录
指针基础:指针就是地址,在c语言中指针与指针变量大部分情况下不做区分。指针变量在内存中通常以4/8个字节的空间存储,根据系统位数确定。
实例
int main()
{
int a = 10;
int* pa = &a;
printf("%d\n%d\n", sizeof(a), sizeof(pa));
return 0;
}
结果
4
8
在本机使用的64位系统中,指针变量的大小,即存放地址的变量向内存申请的大小为64bit位,8字节。
1.字符指针
实例
int main()
{
char arr[10] = "abcdef";
char* arr_p = arr; //数组名退化为整个数组的地址,即首元素的地址
printf("%s\n", arr_p);//打印指针变量会直接取出该指针下的内容
return 0;
}
结果
abcdef
注意:此处的数组名虽然退化为首元素的地址,能用相对应的指针变量接收,但体现的仍然是数组整体特征。
实例
int main()
{
const char* arr_p = "abcdef";
printf("%s\n", arr_p);
return 0;
}
“abcdef”在此处为const char型数组,既然是数组,在表达式中传递的仍然是该数组地址,但是相应的根据数据类型需要用 const char型指针变量接收该地址
结果
abcdef
实例
int main()
{
const char* st1 = "abc";
const char* st2 = "abc";
if (st1 == st2)
printf("1\n");
else
printf("0\n");
char st3[] = "abc";
char st4[] = "abc";
if (st3 == st4)
printf("1\n");
else
printf("0\n");
}
结果
1
0
该结果表明:
1)当内存储存一个字符串常量时,对同一常量设置不同的指针变量接收,该指针变量的指针都会指向该常量的地址。即编译器会先分配内存存储该常量,再根据该常量的地址设置接收地址的指针变量。
2)当设置不同数组存储同一字符串时,会首先根据数组开辟空间,然后再向该空间存储值,所以每创建一个数组,无论其储存的值是什么,都会在内存中开辟空间容纳该数组,所以即使数组内的内容相同,显然数组的地址不同。
2.数组指针
实例:遍历整数数组
int main()
{
int arr_1[10] = { 1,2,3 };
int arr_2[10] = { 11,12,13 };
int arr_3[10] = { 21,22,23 };
int* arr[5] = { arr_1,arr_2,arr_3 };
int i;
for (i = 0; i < 3; i++)
{
int j;
for (j = 0; j < 3; j++)
{
printf("%5d", *(arr[i] + j));//j的步长实际由arr[i]决定
}
printf("\n");
}
return 0;
}
结果
1 2 3
11 12 13
21 22 23
实现逻辑:
1)创建3个整数数组
2)创建一个指针数组接收3个整数数组的地址
3)遍历指针数组,解引用地址(arr【i】+j)。
4)当i= 0时,遍历arr_1数组。打印(arr【i】+j)地址处的数值。步长j由该该指针数组储存的数组类型决定。显然因为arr_1为整型数组,步长j为4个字节。
注意:arr[i]传递的是首元素的地址,步长由数组类型即所存储的元素大小决定。当把arr[i]改为&arr[i]时,指针就会出现越界访问。形成野指针。
实例
int main()
{
int arr_1[10] = { 1,2,3 };
int arr_2[10] = { 11,12,13 };
int arr_3[10] = { 21,22,23 };
int* arr[5] = { arr_1,arr_2,arr_3 };
int i;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 3; j++)
{
printf("%5s", *(&arr[i] + j));
}
printf("\n");
}
return 0;
}
结果
(null)
(null)(null)
-1785006136-1785006072-1785006008
-1785006072-1785006008 0
-1785006008 0 0
出现上述错误的原因很可能是本来应该预计指针访问4个字节,即一个整数的内存,但因为直接提取了整个数组的地址,指针类型错误,致使访问的内存为40个字节,打印出来的是整个数组串行所构成的整数,整体越界后刚好覆盖到第二个数组,再往后为空。
指针数组与数组指针
指针数组:储存指针的数组
int* arr[3] ={&a,&b,&c};
数组指针:指向该数组的指针
int(*arr_p)[]= &arr;
3.指针数组
数组指针的创建
该创建逻辑为:
1)创建一个指针
2)该指针指向一个拥有5个元素的数组
3)数组内元素为int型
数组名传参时传递的是首元素地址,步长为数组元素类型的字节。
数组指针的应用
实例1(数组名传递整个数组)
void print_1(int arr[3][4], int x, int y)//函数型参接收的是整个数组
{
int i = 0;
int j = 0;
for (i = 0; i < x; i++)
{
for (j = 0; j < y; j++)
{
printf("%4d", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int a[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
print_1(a, 3, 4);
return 0;
}
结果
1 2 3 4
5 6 7 8
9 10 11 12
注意:该函数传参时,数组名传递的是整个数组。数组名传参的类型和接收时定义的接受类型有关。
实例2(函数传递首元素地址)
void print_2(int(*p_arr)[4], int x, int y)//接收的为数组(一维数组)指针
{
int i = 0;
for (i = 0; i < x; i++)
{
int j = 0;
for (j = 0; j < y; j++)
{
printf("%4d", *(*(p_arr + i) + j));
}
printf("\n");
}
}
int main()
{
int a[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
print_1(a, 3, 4);//数组名传参时传递的是整个数组
print_2(a, 3, 4);//数组名传参时传递的是首元素(一维数组)的地址
return 0;
}
具体来看一下该数组指针是如何取得各元素的
1)向函数传递首元素(第一组一维数组)的地址
2)进入行循环
3)进入列循环
4)*(*(p_arr + i) + j)
p_arr为首行数组的地址,i的步长由该一维数组大小决定,因为解引用得到的是一个存放四个整型元素的数组,所以i的步长为16个字节,所以+i能改变行。
*(p_arr +i)就是解引用第i行的地址,得出的是整个第i行的内容,显然为一个一维数组。
*(*(p_arr + i) + j)第一次解引用得到i行的数组,第二次解引用得到的是i行数组下的第j个元素。j的步长由该数组元素决定,即4个字节。
综上可知
步长显然是由解引用后得到的内容确定,解引用得到一个数组,则步长为该数组的大小,解引用得到一个元素,步长为该元素的大小。
解引用数组名后得到的是该地址下的所有内容,但是在操作符传参时,传递的是数组名所代指的首元素地址,所以解引用能够顺利执行,即解引用解的是地址,得的是内容,传递过程的步长大小由解引用的内容决定。
*(p_arr + i)解出来的是整个数组,但在传参过程中该数组以数组名形式传递,数组名退化为了首元素的地址,即第i行首字节的地址。步长i由解引用的结果确定,为数组大小。
((p_arr + i) + j)解出来是*(p_arr + i)数组的第j个元素。
以上就是数组指针的一个应用。
printf("%4d", *(*(p_arr + i) + j));
显然也可以写作
printf("%4d", (*(p_arr + i))[j];
因为*(p_arr + i)解出来的是整个数组,该数组既可以再次解引用(数组退化为数组名),也可以直接用操作数组的方式操作该数组。
可以简单地理解为:
整个数组内容->数组名->数组内的首元素地址
1) 一个名为arr的数组,数组有5个int元素的数组
2)一个名为parr1的数组,数组有10个*元素(指针数组),*元素(指针)指向的元素为整型
3)一个名为parr2的指针,指针指向一个数组,该数组有10个int型元素
4)一个名为parr3的数组,该数组存有十个*元素(指针),*元素(指针)指向一个数组,该数组有5个元素,元素为整型。
4.数组传参
一维数组传参
void test_1(int arr[])//数组名传递一维数组,传值函数
{}
void test_1(int arr[10])//数组名传递大小确定的一维数组,传值函数
{}
void test_1(int* arr)//数组名传递地址(首元素),传址函数
{}
void test_2(int* arr[])//数组名传递指针数组,传址函数
{}
void test_2(int** arr)//数组名传递二级指针取值,传值函数
{}
int main()
{
int arr1[10] = { 0 };//整型数组
int* arr2[20] = { 0 };//指针数组
test_1(arr1);
test_2(arr2);
return 0;
}
二级指针,是指向另一个指向目标值的指针,也就是指向指针的指针。这个概念也叫做“多级间址”,或“多级间接地址(multiple indirection)”。普通指针的值是含预期值变量的地址。二级指针中,第一个指针含第二个指针的地址,第二个指针再指向含预期值的变量。
间接寻址的级数不受限制,但极少需要二级以上的间址。实际上,过深的间址难以理解,容易引起概念错误。作为指向指针的指针变量,必须这样声明,即通过在变量名的前面放置两个星号(**)来实现。
**为取值运算,取出的是 ->指向地址->指向内容
二维数组传参
void test(int arr[2][3])//创建与原二维数组大小相同的型参可接收
{}
void test(int arr[][])//未定义大小的二维数组无法接收
{}
void test(int arr[3][4])//创建行列不同的二维数组也无法接收
{}
void test(int arr[][4])//创建行不同列相同的数组可以接收,但不建议
{}
int main()
{
int arr[2][3] = { {1,2,3},{4,5,6} };
test(arr);
return 0;
}
注意:二维数组传值与一维数组有所不同,二维数组的传值函数,其型参必须与原二维数组的列数量保持一致。
标签:arr,int,数组名,地址,详解,数组,day18,指针 From: https://blog.51cto.com/u_15862591/5897181