目录
前言
一、指针和一维数组
二、指针和二维数组
三、数组指针
四、指针数组
六、二级指针
总结
前言
? ? ? ? 指针在C语言中是很重要的一部分,不会指针的不能说自己学过C语言,相信很多朋友都有学过了,现在我们一起进阶一下,学习指针数组,相信经过这次学习,同学们对指针和数组将会有更深刻的理解(当然了,大佬除外)。
一、指针和一维数组
?????? ??????? 数组名就是一个指针常量,可以读其值,而不能改其值。数组的内存存储方式为连续存储,各个成员地址是一个挨一个的,数组名就是这一块内存的首地址。
- 对于一个数组s,我们不仅可以通过s[i],访问他的成员,我们还可以以指针的方式访问其成员,如*(s+i)
- 数组名就是一个指针常量,可以直接把其值赋给指针,所以对于一个数组s,一个指针*p,可以p = s;可以p = s+n;此时可以p++,但不能s++,因为s是一个常量。
??????? 通过指针定义字符串有两个方法。
方法一:直接定义
char *p = "hello work";
方法二:借助数组定义
char str[1024];
char *p = str;
scanf("%s",p);
不能这样定义:
char *p;
scanf("%s",p);
解析:指针指向的内存地址必须是确定的,并且访问内存时不能越界。
下面是指针和数组的一些使用实例。
#include <stdio.h>
int main()
{
int s[5] = {10,20,30,40,50};
//对比s地址和s[0]地址,看看结果是不是一样的
printf("s = %p\n",s); //打印数组首地址
printf("&s[0] = %p\n",&s[0]);//打印s[0]地址
//s[1] s[2]
//通过数组和下标的形式访问数组成员的本质
//是指针“偏移“取值
printf("s[0] = %d,*(s+0) = %d\n",s[0],*(s+0));
printf("s[1] = %d,*(s+1) = %d\n",s[1],*(s+1));
printf("s[2] = %d,*(s+2) = %d\n",s[2],*(s+2));
printf("s[3] = %d,*(s+3) = %d\n",s[3],*(s+3));
printf("s[4] = %d,*(s+4) = %d\n",s[4],*(s+4));
//将数组的首地址赋值给指针P
int *p = s;
//赋值之后,下面的操作是等价的
//s[3] <==> *(s+3) <==> p[3] <==> *(p+3)
int i = 0;
for(i = 0;i < 5;i++){
printf("%d ",*(p+i)); //通过定义的指针访问数组成员
}
//指针是变量,可以被重新赋值
int *q = s;
for(i = 0;i < 5;i++){
printf("%d ",*q);
q++; //指针区别于数组,指针可以赋值,数组不能
}
printf("\n");
return 0;
}
二、指针和二维数组
?????? 二维数组数组名也是指针,但不能直接给一维指针赋值,因为对于一个二维数组s,s+1操作,地址偏移的不是一个数据类型,而是一行的长度乘一个数据类型。正确的赋值方式是指针p = *s或者p=**s,前者是行地址,后者是某个元素地址。
下面是使用例子及细节解析。
#include <stdio.h>
int main(int argc, const char *argv[])
{
int str[3][4] = {{1,2,3,4},
{5,6,7,8},
{9,10,11,12}};
//二维数组的数组名也是一个指针 是一个 行指针
//每次加1 相当于 偏移一行 ====== 1 * 列数 * 数据类型
//是地址偏移 也就是说 *(str+1) 相当于取了 第二行 首元素的地址
//对二维数组名 取 * 相当于给指针进行降维 降维成一维指针
//降维之后的地址每次 +1 相当于偏移一个数据类型
//如果想取到第二行的首元素 *(*(str+1)+0)
//str[0] <==> *(str+0)
//str[1] <==> *(str+1)
printf("%p %p\n",*(str+0), str[0]);
printf("%p %p\n",*(str+1), str[1]);
printf("%p %p\n",*(str+2), str[2]);
//str[i][j] <==> *(str[i]+j) <==> *(*(str+i)+j)
//二维数组的遍历
int i = 0;
int j = 0;
for(i = 0; i < 3; i++){
for(j = 0; j < 4; j++){
//printf("%d ",str[i][j]);
//printf("%d ",*(str[i]+j));
printf("%d ",*(*(str+i)+j));
}
printf("\n");
}
//二维数组的数组名 也是一个地址常量 是不能 赋值的
//str++; //错误的
//因为二维数组的数组名是个行指针 每次偏移 是偏移一整行
//已经超过了基本类型的长度
//所以不可以用普通的一级指针去保存二维数组的地址
// (保存是可以保存,但是不能按照二维数组一样操作)
//原因是,p+1 时,操作系统会认为你是想偏移一行,但是 int * 指针只能
//偏移4个字节,所以会有冲突,会报警告
//并且 二维数组名 str 可以做 **str ,而指针p 是不可以 **p 的
int *p = str;//可以保存 但是有警告
printf("%p\n",p);
printf("%d\n",*p);
printf("%d\n",*(p+1));//打印的不是 第二行的首地址 而值 第一行的第二个元素 !!!
//printf("%d\n",**p);//直接错误
printf("%p\n",str+1);
printf("%p\n",*(str+1));
//上面两种写法都是地址,且地址的值是一样的
//但是表达的含义是不一样的
//str+1 还是一个行指针(偏移是偏移一行的)
//*(str+1) 就是一个指针了(偏移只偏移一个数据类型)
return 0;
}
上面代码中这两行是要特别注意的,理解这两个,指针和二维数组的操作就很简单了。printf("%p\n",str+1); printf("%p\n",*(str+1));
三、数组指针
??????? 上面说过,二维数组是不能直接通过数组名向一维指针赋值的,但是可以直接给数组指针赋值的,数组指针本质上是一个指针,他指向一个数组(一般是二维的)。上面说到的赋值问题,本质上其实是数组和指针的维度问题。
- 我们可以通过 * 和 & 运算符处理维度问题,*表示降维,&表示升维,但&不能作用于指针。
- 二维数组不能通过数组名直接给一维指针赋值,主要是因为二维数组,数组名的维度比一维指针维度高,需要用*降维。
- 同样,一维数组也是不能通过数组名向数组指针赋值的,赋值前需要用&对数组名进行升维操作。
??????? 使用格式:
int (*p)[4] = NULL;
p = str;
或者
int (*p)[4] = str;
注意:
- ()是必须加的,否则变成指针数组了,[4]表示数组的列宽为4.
- 数组指针就是一个数组的指针,性质与数组名类型,对于指向一个二维数组a[m][n]的数组指针p,p++表示下一行首地址,指向一维数组的数组指针不允许这么操作,*(p+i)+j表示a[i][j]的地址。
操作实例
#include <stdio.h>
int main()
{
char str1[4] = "abcd";
char str2[3] [4] ={{'a','b','c','d'},
{'e','f','g','h'},
{'i','j','k','l'}, };
char *p;
char (*p2)[4] = NULL;
//1、str1 和p维度一致,可以直接赋值
p = str1;
//2、str2比p高一维,在赋值前需要进行降维操作
p = *str2;
//3、p2比str高一维,在赋值前需要进行升维操作
p2 = &str1;
//打印str1
printf("%s\n",*p2);
//4、str12和p2维度一致,可以直接赋值
p2 = str2;
//下面重点来讲一下数组指针
for(int i = 0;i <3;i++){
//注意,数组指针打印二维数组字符串时
//不会因为,某一行结束而停止打印
//而是一直打印到该字符串结束
//所以下面代码不可取
//printf("%s\n",*p2);
for ( int j = 0; j < 4; j++)
{
//遍历二维数组
printf("%c\n",*(*(p2 + i) + j));
}
}
return 0;
}
四、指针数组
??????? 指针数组本质上是一个数组,其中的元素都是指针,可以保存数组的地址,也可以是二维数组。
#include <stdio.h>
int main(int argc, const char *argv[])
{
//想存储一些字符串 "abcd" "abcdfr" "hello" "https://mp.csdn.net/mp_blog/creation/editor/120120753"
//存储在二维数组中的字符串行地址可以通过指针数组保存
char str[4][1024] = {"abcd","hello","world","https://mp.csdn.net/mp_blog/creation/editor/120120753"};
char *p[4];//定义一个指针数组,数组中每个元素都是一个 char * 指针
int i = 0;
for(i = 0; i < 4; i++){
p[i] = str[i];
}
for(i = 0; i < 4; i++){
printf("%s\n",p[i]);
}
//我们可以将字符串 单独存储,并也用指针数组访问
char s1[] = "hello";
char s2[] = "world";
char s3[] = "abcd";
char s4[] = "https://mp.csdn.net/mp_blog/creation/editor/120120753";
//printf("sizeof(p) = %ld\n",sizeof(p));//32 = sizeof(char *) * 4
p[0] = s1;
p[1] = s2;
p[2] = s3;
p[3] = s4;
printf("--------------------------\n");
for(i = 0; i < 4; i++){
printf("%s\n",p[i]);
}
return 0;
}
六、二级指针
??????? 指向指针的指针叫二维指针,一维指针不能指向一维指针,所以以下操作是错误的。
int *p,*q;
p = &q;
??????? 但可以这样操作
int **p,*q,arr[5];
p = &q;
??????? 注意:二级指针也不能这样操作
char str[10];
char **p;
p = &str;
七、练习
/***************************************************************
* 编码: 自己实现 atoi() 函数的功能
* atoi()函数的功能:
* 将字符串转换成整型数;
* atoi()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负号才开始做转换,
* 而再遇到非数字或字符串时('\0')才结束转化,并将结果返回(返回转换后的整型数)。
* 要求,运行时命令行直接传参
* ./a.out string
* *************************************************************/
#include <stdio.h>
#include <stdbool.h>
//注意:const类型给给非const类型赋值会报警告
//而非const类型给const类型赋值则不会
//所以为了迎合const char *argv[],把参数定义为const char *类型
int atoi(const char *p);
int main(int argc,const char *argv[])
{
// char arr[1024];
int add;
//const char *argv[]是一个数组指针,保存的是传入的参数
add = atoi(argv[1]);
printf("number = %d\n",add);
return 0;
}
int atoi(const char *p){
bool a = false,b = true,c = true,d[2] = {false,false},e = false;
int i = 0,p1=0;
while(1){
if(b){
if(*p == 43 || *p==45||(*p>47&&*p<58)){
b = false;
a = true;
c = false;
if(*p == 45) d[1] = true;
}
if(c) p++;
}
if(a){
if(*p == 43 || *p==45||(*p>47&&*p<58)){
if(*p == 45){
if(d[0]){
if(d[1]) return -p1;
else return p1;
}
p++;
}
if(*p>47&&*p<58){
p1 = p1*10 + *p - 48;
p++;
}
d[0] = true;
}
else{
if(d[1]) return -p1;
else return p1;
}
}
}
return p1;
}
总结
????????数组名就是一个指针常量,可以赋值给指针,但不能修改其地址,不同维度的指针不能互相赋值,但我们可以进行升降维操作,*表示降维,&表示升维,对于数组而言,str2、*str 和&str2 的值是一样的,但维度却是不一样的,使用时要注意。
|