本人0基础开始学编程,我能学会的,你也一定可以,学会多少写多少。
下载安装请从官网入手,社区版本即可,这里主要使用的软件是VS2019,图标如下。
上一篇:从0开始学c语言-26-操作符练习、指针练习、调试作业_阿秋的阿秋不是阿秋的博客-优快云博客
应有的基础:从0开始学c语言-20-指针与地址、指针类型、野指针、指针运算_阿秋的阿秋不是阿秋的博客-优快云博客
从0开始学c语言-21-指针和数组、二级指针、指针数组_阿秋的阿秋不是阿秋的博客-优快云博客
目录
1. 字符指针
我们常见的字符指针是这样用的。
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0; }
实际上还可以这样用
int main()
{
const char* pstr = "hello bit.";//这里是把一个字符串放到pstr指针变量里了吗?
printf("%s\n", pstr);
return 0; }
不信你就看这个文章学习小发现-02-char和char*和char[ ]*_阿秋的阿秋不是阿秋的博客-优快云博客
那么我们看看这道题,练习一下你懂了没有。
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; }
输出结果是
"str3 and str4 are same\n"
虽然str1和str2的字符串内容一样,但是两者的地址不同,是两块独立的空间。
str3和str4指向的常量字符串内容相同,str3和str4都只是保存首字符的地址,所以只开发了一块空间来储存这个字符串,让str3和str4指向同一个地址。
所以要清楚字符指针保存的什么。
2. 指针数组和数组指针
区分
实际上,我们已经学过指针数组。
int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组
上面这是存放指针的数组,叫指针数组,本质是数组。
int *p1[10];
int (*p2)[10];
那么你看看这俩谁是数组指针呢?
很明显,第一个是存放int*指针的指针数组。
第二个才是我们要学的数组指针,本质是指针。
* 和 [ ] 的优先结合顺序
int (*p)[10];
//解释:p先和*结合,说明p是一个指针变量,然后指针指向的是一个大小为10个整型的数组。所以p是一个
指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。
也就是说,如果没有把*p用括号括起来,那p首先会和 [ ] 来组合为一个数组,就像下面这样。
int *p1[10];
int **p[10];
因为没有把*p括起来,所以这两个都是指针数组,第一个存放int*,第二个存放int**.
&arr和arr
int arr[10]={0};
实际上,如果你打印&arr和arr都会是同一个地址,那么他们两个的意思会一样吗?
不一样!
访问范围
我画图示意一下。
arr和&arr都指向首元素的地址,但是一次访问的范围不同。
数组的标志便是指针的标志
我们在监视窗口中监视到的arr类型是int[10],似乎arr应该代表整个数组才对。
但实际上我想说的是,监视窗口中arr数组名存放的是地址,前面我们说过,其实指针就是有指向性的地址,那么我们就可以把数组名变为指针来运用,但是指针会指向谁该如何判断呢?
我们从类型来下手,可以看到arr的类型是int[10],我们又说
指针就是有指向性的地址,数组名存放的是地址,那么我们就可以把数组名变为指针来运用 |
也就是说,数组的标志便是指针的标志
就像int*指针一样,我们是去掉*来判断int*指针指向谁
同样的道理,我们去掉 [10],这便是数组名arr指向的对象类型,是int。
又因为数组名保存的是首元素的地址,便指向了第一个int类型的元素。
同样的道理,我们来看&arr
可以看到&arr的类型是int[10]*
我们去掉*,便是&arr指向的对象类型,是int[10]
又因为&arr保存的是首元素的地址,于是便和arr一样,指向了同一个地址,但是arr和&arr指向的范围(对象类型)不同。
现在可以更深刻的理解这个图了把?
+1步长和二级指针(&arr)
+1步长
我们又知道,指针类型会决定指针+1的步长,
int*指向int,步长就是int。char*指向char,步长就是char。
arr的类型是int[10] ,&arr是int[10]*
所以arr每次会跳过一个int,&arr则会跳过int[10]
我们又发现arr和&arr指向的范围(对象类型)便是+1的步长。
二级指针(&arr)——这部分属于是我自己的思考,慎读
我们又说数组的标志便是指针的标志,那么实际上
arr的类型是int[10] ——>10个( int*) ——>arr就是(1个int* )——>步长是int*
&arr的类型便是int[10]* ——> (10个 int*)*——>&arr就是(10个 int*)—>步长是10个 int*
因为我们通常认为*才是指针的标志呀,便可以像上面这样转换
所以我们在判断步长的时候,去掉类型的最后一个符号
1·如果是*,那么剩下的就是步长
例如:int[10]* - int[10] -10个int*
2·如果是[ ],那么剩下的加上*
例如:int[10] - int*
所以实际上我们的数组应该是这样才对
也就是说arr这个数组,存放了10个指针,每个指针都有自己的独立空间,于是arr数组名的步长就是存放的一个指针的大小。
但那样画不利于理解,下面这个好些,这时候arr是指向int的,arr本身是int*的指针,这便是数组。
而&arr这个二级指针就会是这样子,存放了10个指针,每个指针都有自己的独立空间,但是&arr相当于一个围墙,把他们圈在了一个共同空间内,于是&arr的步长就会是arr整个数组的大小。
(被同一颜色划线的空间代表&arr)
为了更好理解一些,我们画成这样,
所以今后你见到数组的时候会不会更加理解呢?
数组实际上就是同一类型指针的集合,指向了同一类型的元素,因为数组保存了每个元素的地址,而每个地址在运用的时候会有指向性,有指向性的地址就是指针。所以数组名本身就是指针。
就像我们一开始推导的那样
arr的类型是int[10] ——>10个( int*) ——>arr就是(1个int* )——>步长是int*
&arr的类型便是int[10]* ——> (10个 int*)*—>&arr就是(10个 int*)—>步长是10个 int*
更直接一些
arr的类型是int[10] ——>指向10个int——>arr本身是int*指针
&arr的类型便是int[10]* ——> 指向10个int*——>&arr是int[10]*指针
那么为什么数组要用[ ]来访问呢?
[ ] 和 * 的联系
实际上早已经在下面这篇文章里说过[ ]和*的联系,不过还不够深刻,有了上面的理解,我们能够更加理解他俩的关系。
从0开始学c语言-21-指针和数组、二级指针、指针数组_阿秋的阿秋不是阿秋的博客-优快云博客
int arr[10]={0,1,2,3,4,5,6,7,8,9};
既然我们认为数组名本身就是指针,那么arr[0],arr[1]之类的又代表什么呢?
我先给上结论
arr[0]=*(arr+0)
arr[1]=*(arr+1)
arr[2]=*(arr+2)
也就是说,arr[ i ] =*(arr+i )
注:arr[ i ] 和*(arr+i ) 类型是int , arr+i 的类型是int*。
arr[ i ] 和*(arr+i ) 相当于 arr 这个指针向后挪动 i 步长后访问住户。
总结
判断 数组和指针 指向对象类型
1·数组的标志便是指针的标志
2·指针去掉*来判断指针指向谁
3·数组去掉[ ]来判断数组名指向谁
实际上数组名是指针:
arr的类型是int[10] ——>指向10个int——>arr本身是int*指针
&arr的类型便是int[10]* ——> 指向10个int*——>&arr是int[10]*指针
本质上数组就是同一类型指针的集合,指向了同一类型的元素,数组名本身就是指针。
判断步长的两种理解方式:
1——判断+1步长的,arr和&arr指向的范围(对象类型)便是+1的步长。
arr的类型是int[10] ,指向int,步长int(跳过一个int元素)
&arr是int[10]*,指向int[10],步长int[10](跳过10个int元素)
2——我们在判断步长的时候,去掉类型的最后一个符号
1·如果是 *,那么剩下的就是步长
例如:int[10]* - int[10]
2·如果是[ ],那么剩下的加上*
例如:int[10] - int*
步长结论:arr数组名的步长就是本身指针类型的大小,&arr的步长就会是arr整个数组的大小。
补充结论:arr[ i ] 和*(arr+i ) 相当于 arr 这个指针向后挪动 i 步长后访问住户。
实际运用:
我们说int[10]类型是指向了10个int,数组名本身是int*指针
那么当看到指针数组的时候,你还能理解吗?
int *p1[10];
int **p[10];
int[10]类型是指向了10个int,数组名本身是int*指针
int *p1[10]当中p1的类型是int*[10],那么类比上面
int*[10]就是指向了10个int*类型的元素,数组名本身是int**指针。
剩下的你会把?不说咯。
现在回到数组指针
数组指针的使用
既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
//但是我们一般很少这样写代码
return 0; }
*p的另外一种理解
前面我们说数组名实际上就是指针,我们观察一下数组指针的书写方式
int arr[10] = {0};
int (*p)[10] = &arr;
很像数组的书写方式吧?一定记得把*p括起来!
我们就可以把*p理解为数组名,*p的类型就是int[10],而int[10]又代表指向10个int,数组名本质是int*指针。那么就可以把*p=arr。
那p又会是什么类型呢?*p的类型是int[10],去掉*p的*后,p的类型就是int[10]*。
&arr的类型也是int[10]*,那么p=&arr。
现在我们监视窗口看看是否符合我们的推论。
*p=arr 符合!
数组名本质是int*指针。*p=arr。 符合!
p=&arr。符合!
那么赋值过程就会是这样的代码
int main()
{
int arr[10] = { 0 };
int(*p)[10] = &arr; //类型int[10]*
//p=&arr 数组地址
int i = 0;
for (i=0;i<10;i++)
{
printf("%d\n", *((*p) + 1));
//*p=arr 首元素地址
}
return 0;
}
二维数组
int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};
我是这样看待这个数组的,
arr是二维数组名,它的类型是int[3][5],指向3个 int[5]类型的 一维数组,arr数组名本身是首元素的地址,二维数组的首元素是二维数组的第一行,也就是说二维数组名arr本身是int[5]*类型的数组指针。
那么我们在传递arr这个二维数组到某一个函数中去,应该用数组指针进行接收。就像下面这样。
void print_arr2(int (*arr)[5])
或者好理解一些,就是这样。
void print_arr1(int arr[3][5]) //[3]可省略
这里给上一些我学习时候的思考过程
int main()
{
int arr[3][4] = { {1,2},3 ,2 };
//&arr int[3][4]*
//如何用指针存二维数组
int(*p3)[4] = arr;
//p3 int[4]* 和arr作为指针的时候类型一样
//把arr首元素的地址存到p3中,也就是第一行
// arr=p3 arr表示第一行地址(相当于指针指向第一行的一维数组
// *arr=*p3 代表第一行 类型为int[4]
// *arr+1=*p3+1 代表第一行的第二个元素 类型int*
// arr+1=p3+1 代表下一行 类型int[4]*
//*(arr+1)=*(p3+1) 代表下一行 类型int[4]
//函数传参以这个为基准
int(*p4)[3][4] = &arr; //整个数组
// p4=&arr int[3][4]*
// *p4=*(&arr) int[3][4] 代表arr整个数组
// *p4+1=*(&arr)+1 下一行 int[4]* 此时的加一把arr当做一维数组
// p4+1=&arr+1 加了一整个数组大小的地址 int[3][4]*
//*(p4+1)=*(&arr+1) 加了一整个数组大小的地址 int[3][4]
return 0;
}
还有利用二维数组传参如何赋值的代码
二维数组的数组名是首元素的地址
首元素:第一行
*(p+1)表示某一行的地址
*(*(p+i)+j)就是某一行的某一个元素的地址
void test(int(*p)[4], int a, int b)
{
//p接收第一行地址的数组指针 int[4]*
//*p代表第一行数组的地址和元素 int[4]
int i = 0;
int j = 0;
//printf("%p ", p+1);
// //p+1指向下一行的数组指针 int[4]*
//printf("%p ", *p+1);
// // *p+1代表第一行的下一个元素 int* 只包含一个元素
//printf("%p ", *(p+1));
// // *(p+1)下一行的地址和元素 int[4]
//printf("%p ", *(p+1)+1);
// // *(p+1)+1下一行的第二个元素 int* 只包含一个元素
//其他则包含一行的元素
/*for (i = 0; i < 3; i++)
{
for (j = 0; j < 4; j++)
{
printf("%d ",*(*(p+i)+j));
}
printf("\n");
}*/
}
int main()
{
int arr[3][4] = { {1,2},3 ,2 };
test(arr, 3, 4);
//二维数组传过去的是第一行的地址
//相当于你传过去了int[4]类型的一维数组
//所以用一维数组指针接收
return 0;
}
我觉得这里的思考过程实际上和一维数组的思考过程是相通的,便不想过多解释。
区分运用的练习
int (*parr3[10])[5];
先自己思考好每个的类型,和代表的意思。
int arr[5]; —— 指向int
arr是数组名,类型int[5],指向5个int,数组名本质类型是int*,指向int的指针。
int *parr1[10]; —— 指向int*
parr1是数组名,类型int*[5],指向5个int*,数组名本质类型是int* *,指向int*类型的指针。
int (*parr2)[10]; —— *parr2指向int ,parr2指向int[10]
*parr2是数组名,类型int[10],指向5个int,数组名本质类型是int*,指向int的指针。
parr2是指针变量,类型int[10]*,指向int[10],指向数组的指针。
int (*parr3[10])[5]; —— *parr3[10] 指向int,parr3[10]指向int[5],parr3指向int[5]*
*parr3[10] 是数组名,类型int[5],指向5个int,数组名本质类型是int*,指向int的指针。
在前面学习的时候我们知道,[ ]的结合性高于*,所以*parr3[10]相当于*(parr3[10]),所以
去掉*后的parr3[10] 的类型就是int[5]*,是指向数组的指针。
parr3[10] 把[10]去掉,parr3是指针数组,类型int[5]*[10],指向int[5]*类型,本质是int[5]**指针,指向int[5]*的指针。
总结:
1·在 int 和 [ ] 之间的是数组名,剩下的就是数组名的类型,数组名本质是指针。
2·*p中的*代表这是一个指针变量,p的类型是在*p原来类型后加*便是p指针变量的类型。
3·[ ]的结合性高于*,所以 *p[10] = *(p[10]),p[10]中的[10]代表这是一个存放10个同类型元素的数组,p的类型就是在p[10]原本的类型后加[10]就可以了。