目录
2-5 一道为了区分栈区和字符常量区&&字符数组和字符指针的面试题:
3-3这才是指针数组的正确使用方法!【指针数组模拟打印二维数组】
4-5 这才是数组指针的正确使用方法捏【数组指针模拟打印二维数组】
1.初阶指针内容回顾
1.内存被划分为小的内存单元,每个内存单元都有一个编号,这个内存编号就是所谓的地址,也被叫做指针(内存编号=地址=指针)。
2.指针变量是一个变量,存放的是地址,地址能够唯一标识一块内存单元。
3.指针变量大小是固定的4/8个字节(32/64位平台上)。
4.指针变量类型决定了(1)指针在+-整数时的跳过多少个字节;(2)指针在解引用的时候访问的权限。
2.字符指针
2-1 字符指针长什么样?
int main()
{
//代码一:
char ch = 'a';
char* p1 = &ch;
printf("字符'a'的地址:>%p\n", p1);
printf("对指针解引用得到的是指针指向的那个目标:>%c\n", *p1);
printf("\n\n\n");
//代码二:
char* p2 = "hello world";
printf("字符'h'的地址:>%p\n", p2);
printf("对指针解引用得到的是指针指向的那个目标:>%c\n", *p2);
printf("%s\n", p2);
return 0;
}
2-2 误区:
误以为代码2中p2指针变量是存放着字符串,实际上p2所指向的是字符串"abcdef”中首个字符'a'的地址(原因有两点:1.指针变量在x86位(32位机器下)是4个字节,但是"abcdef“有7个字符,一个指针只能存放一个地址;2.通过指针解引用打印出'a'),同时因为字符串"abcdef"在内存(字符常量区)中的空间是连续的,所以只要拿到字符串首个元素'a'的地址就可以访问到整个字符串的内容。
2-3 代码一和代码二的异同:
1. 同:
(1)指针类型:p1和p2都是字符指针变量,都是存放的是字符a的地址。
(2)打印字符'a:打印出'a'都是通过拿到字符指针变量内存放的地址进行解引用,得到的都是指针变量所指向的那个变量。
2. 异
(1)字符'a'的存储位置:代码1中的'a'存放在栈区,代码2中的'a'存放在字符常量区(通过下方截图可以证明)
2-4 关于字符常量区:
对于上图的解释3:
- 既然位于字符常量区的"abcdef"是不允许修改的
那么在p2指向这块内存空间的时候就会产生隐患,一旦通过解引用试图修改就会造成程序的运行时错误,程序瘫痪;
- 因此使用const修饰(也就是const char* p2="abcdef")来阻止对指针解引用试图修改的行为 ,及时给出编译时错误,程序压根编译不通过。
2-5 一道为了区分栈区和字符常量区&&字符数组和字符指针的面试题:
int main()
{
const char* ptr1 = "abcdef";
const char* ptr2 = "abcdef";
char arr1[] = "abcdef";
char arr2[] = "abcdef";
if (ptr1 == ptr2)
{
printf("ptr1==ptr2\n");//bingo
}
else
{
printf("ptr1!=ptr2\n");//error
}
if (arr1 == arr2)
{
printf("arr1==arr2\n");//error
}
else
{
printf("arr1!=arr2\n");//bingo
}
return 0;
}
解释:
3.指针数组
其实下面要讲的指针数组和数组指针都是一种类型(类似整型,double,float)
3-1 指针数组长什么样捏?
//整型数组
int arr1[5]={1,2,3,4,5};//存放整型的数组
//字符数组
char arr2[5]={'a','b','c,''d','\0'};//存放字符的数组
//指针数组:指针是修饰,数组是名词,主角
int* arr3[5]={&arr1[0],&arr1[1],&arr1[2] ,&arr1[3] ,&arr1[4]};//存放指针的数组
//指针数组的意义:看着食之无味,弃之可惜
//实际上作用大着捏,我们指针数组的意义和普通数组的意义类似,
//都是对方便对相同类型元素(在这里的类型元素是指针)统一处理:比如修改和打印
3-2 初级使用(或者说给你看一下基本使用):
int main()
{
//简单用法:
int a = 10;
int b = 20;
int c = 30;
//没有数组指针的情况
int* p1 = &a;
int* p2 = &b;
int* p3 = &c;
//有数组指针的情况
int* arr[3] = { &a,&b,&c };
for (int i = 0; i < 3; i++)
{
printf("%d\n", *(arr[i]));
}
return 0;
}
3-3这才是指针数组的正确使用方法!【指针数组模拟打印二维数组】
这和arr[3][5]的区别在于arr[3][5]在内存中中每一个元素的地址都是连续的,而指针数组模拟的二维数组这种方式的地址不是连续的。
int main()
{
int arr1[5] = { 1,2,3,4,5 };
int arr2[5] = { 2,3,4,5,6 };
int arr3[5] = { 3,4,5,6,7 };
int* arr[3] = { arr1,arr2,arr3 };
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
//printf("%d\t",arr[i][j]);//way1
//printf("%d\t", *(arr[i] + j));//way2
printf("%d\t",*(*(arr+i)+j);//way3
//实际上way1和way2在编译器翻译过程中会自动转换为way3
}
printf("\n");
}
return 0;
}
4. 数组指针
int main()
{
//整型指针-指向整型的指针-存放整型变量的地址
int a = 10;
int* pa = &a;
//整型指针-指向字符的指针-存放字符变量的地址
char ch = 'w';
char* pc = &ch;
//数组指针-指向数组的地址,存放数组变量的地址
int arr[5] = { 1,2,3,4,5 };
int(*p)[5]=&arr;//注意这个5不能省略,因为这个5是指针指向的那个数组的类型
}
一个指针只能存放一个地址,只不过这个地址是整个数组的地址
4-1 区分取地址数组名和数组名(老生常谈了)
4-1-1 sizeof(数组名):求的是该类型所定义的变量在内存中所占空间的大小,单位是字节。
int main()
{
int arr1[5] = { 1,2,3,4,5 };
printf("%d\n", sizeof(arr1));//32位平台下:20字节 64位平台下:20字节//类型;int[5]数组
printf("%d\n", sizeof(arr1+0));//32位平台下:4字节 64位平台下:8字节//已经不是单独的sizeof(数组名)//类型:int*指针
printf("%d\n", sizeof(arr1[0]));//32位平台下:4字节 64位平台下:8字节//类型:int*指针
return 0;
}
4-1-2 &数组名,在没有+-整数时虽然地址是相同的,但是意义不同。
4-2 辨析数组指针和指针数组
图图我就省略喽!😁😁😁
int main()
{
//我们知道:去掉变量名剩下的部分就是变量所属的类型
//(这里可以通过调试得以验证以下猜想)
//整型数组,数组类型为int [5],从左往右念就是整型数组
//特别注意:数组索引中的元素个数5也是类型的一部分
//所以int [4]和int [5]所属的是不同的类型
int arr[5] = { 1,2,3,4,5 };
//p1先和方块5(也就是[5])结合,所以整体来看p1是一个数组
//既然是数组我们关心的就是数组呢存放的是什么?
//数组内有五个元素,每个元素的类型是int* 类型
//所以变量p1的类型为int* [5],从左往右念就是指针数组
int* p1[5] = { arr,&arr[1] ,&arr[2], &arr[3], &arr[4] };
//p2先和*结合,所以整体来看p2是一个指针
//既然是指针,我们关心的就是指针指向的是什么?
//指针指向的是一个数组,数组内有五个元素,每个元素时int类型
//所以变量p2的类型为int[5]*,从左往右念就是数组指针
int(*p2)[5] = &arr;
return 0;
}
4-3 学会了?来看一个小测试题
4-4 来看一个脱裤子放屁的代码【看一看数组指针的使用】
void Print1(int arr[], int sz)
{
printf("Print1:\n");
for (int i = 0; i < sz; i++)
{
printf("%d\t", arr[i]);
}
}
void Print2(int* arr, int sz)
{
printf("Print2:\n");
for (int i = 0; i < sz; i++)
{
printf("%d\t", arr[i]);
}
}
void Print3(int(*p)[5],int sz)
{
printf("Print3:\n");
for (int i = 0; i < sz; i++)
{
printf("%d\t", *((*p)+i));
//printf("%d\t", (*p)[i]);
}
}
int main()
{
int arr[5] = { 1,2,3,4,5 };
int sz = sizeof(arr) / sizeof(arr[0]);
//写一个函数打印数组内容
//测试1:
Print1(arr, sz);
printf("\n");
//测试2:
Print2(arr, sz);
printf("\n");
//测试3:
Print3(&arr,sz);
return 0;
}
4-5 这才是数组指针的正确使用方法捏【数组指针模拟打印二维数组】
指针数组虽然可以int(*p)[3]=&arr;其中arr是一个一维数组,但是这样太鸡肋了,
还不如直接int*p arr;
指针数组真正的使用场景是留给二维数组传数组名时,形参用指针数组接收。
void Print1(int arr[3][5], int row, int col)
{
printf("Print1:\n");
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf("%d\t", arr[i][j]);
}
printf("\n");
}
}
void Print2(int(*p)[5], int row, int col)
{
printf("Print2:\n");
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf("%d\t", (* (p + i))[j]);
//printf("%d\t", *(*(p+i)+j));
//printf("%d\t",p[i][j]);
//p+i是第i行,也就是第i行整个数组的地址
//*(p+i)也就是对整个数组的地址解引用,就是第i行的数组名,也就是第i行首元素的地址
//*(p+i)+j就是第i行第j列元素的地址
//*(*(p+i)+j)也就是对第i行第j列元素的地址解引用,就是第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} };
//测试1:
Print1(arr, 3, 5);
printf("\n");
//测试2:
Print2(arr, 3, 5);
printf("\n");
return 0;
}
上面目前讲的基本还是数组指针指向的是一个一维数组
其实数组指针还是可以指向一个二维数组:
int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
int(*p)[3][5]=&arr;
完结,撒花,等等,要不来一个小测试?
5 测试题和规律总结
测验1:
如果int(*arr[3])[5];
问题:怎么理解这个代码?
答案:数组指针数组
测验2:那么指针数组指针?
我总结的一点小规律
如果光要给上面这个东西命名的话,只用关注*和[]的出现次序排列就可以了
比如;数组指针数组:[]*[]
指针数组指针*[]*
然后再把[]存的内容和*指向的东西往里面填进去就可以了