数组指针
C语言中,数组名(不包括形参数组名)代表数组中的首元素的地址。
所谓数组元素的指针就是数组元素的地址。
一维数组
指针引用数组元素的表示方法:
(1)下标法,如a[i]
(2)指针法,如*(a+i) 或 *(p +i) 其中 a 是数组名,p是指向数组首元素的指针变量。
int a[] = {1,2,3,4,5};
//下标法
for(int i = 0;i < 5;i++)
printf("%d ",a[i]); //用数组下标法表示
printf("\n");
//指针法
for(int i = 0;i < 5;i++)
printf("%d ",*(a +i));//用数组名和元素序号计算元素地址
printf("\n");
int *p = a;//等价于int *p = &a[0];
for(;p < a + 5;p++)
printf("%d ",*p);//通过指针运算,每次指针加一个单位,等价于 *(p + 1)
printf("\n");
return 0;
二(多)维数组
二维数组可以理解成数组的数组,多维数组也是如此。
二维数组的指针表示方法:
| 表示形式 | 含义|
| --------- | -------- |
| a|二维数组名,指行,指向a[0]这一行的起始地址| $1,300 |
|a[0],*(a+0),*a| 指列,指向0行0列的元素地址|
| a+1,&a[1]|指行,第1行的起始地址(行列数从0开始)|
| a[1],*(a+1)|指列,1行0列的地址,就是a[1][0]的地址|
|a[1]+2,*(a+1)+2,&a[1][2]|指列,1行2列地址 |
|*(a[1]+2), ((a+1)+2),a[1][2]|指列,这是元素值
指向行的意思是,它+1加的是行数,从上往下加
指向列的意思是,它+1加的是列数,从左往右加
我们可以发现,在没有&符号的前提下,用下标法和带*号表示的,一定是一个指向列的,他们递增的时候增加的是列数。用数组名表示的,永远都是行数,比如:a+1,表示的是第(a+1)行的首元素地址,指向行, *(a+1)+2,虽然是指向列的,读作第(a+1)行第二列的元素地址, a+1的含义依旧是第(a+1)行。
可能会混肴的是:
&a[1] 和 &a[1][2]
前者一个下标表示第1行的元素地址,是指向行的
后者两个下标表示元素本身的地址
列与行的转换
指向行的指针前面加 * 号 ,变成指向列的指针
指向列的指针前面加 & 号 ,变成指向行的指针
a[1],*(a+1):指向列,读作:指向第1行0列
&a[1],&*(a+1):指向行,读作:指向第一行元素
a:指向行,读作:指向第0行
*a:指向列,读作:指向0行0列
指向行和指向列的地址有时候是相同的,但是仅仅是纯地址相同,他们所指向的对象是不一样的,指针的基类型是不一样的,指针会根据类型来判断+1所加的单元长度是多少。
字符数组
字符数组基本用法
C语言中只有字符变量,没有字符串变量。
char *string =“ I love China!”;
等价于
char *string;
string = “ I love China!”;定义了一个名叫string的char型指针,并且指向 “ I love China!” 的首元素地址。
普通的定义字符串:
%s格式输出,遇到 ‘\0’ 会结束。
复制字符数组时,要记得手动补充 ‘\0’ 为结束标志,不然会发生乱码,下面是一个例子
用字符数组的方式复制
#include<stdio.h>
int main()
{
char a[] = "I'm a boy!";
char b[20];
int i = 0;
for(;a[i] != '\0';i++)
b[i] = a[i];
//b[i] = '\0';//没有结束标志会乱码
for(i = 0;b[i] != '\0';i++)
printf("%c",b[i]);
return 0;
}
结果是这样的:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6d9QdPdm-1584805665553)($resource/1583998110(1)].jpg)
用指针的方法复制,只需要简单修改
#include<stdio.h>
int main()
{
char a[] = "I'm a boy!";
char b[20];
char *p1,*p2;
p1 = a;
p2 = b;
for(;*p1 != '\0';p1++,p2++)
*p2 = *p1;
*p2 = '\0';
//若想用指针表达,需要把指针重置回起点然后循环输出
printf("%s\n",b);
return 0;
}
两个字符数组长短不一致互相复制
短的接收长的,自动扩张;
长的接收短的,可以选择保留未覆盖部分,或者补充‘\0’进行全覆盖
char s[] ="hello";
char d[] = "java";
char *p3=s,*p4=d;
for(;*p4 != '\0';p4++,p3++)//没有复制'\0'
*p3 = *p4;
*p3 = '\0';
//因为hello比java多一个字符,所以这里如果想部分覆盖,不用补上'\0'
//如果希望完全覆盖,则需要复制完后补充一个'\0',以至于让%s输出时可以在结束
for(int i = 0;i<10;i++)
printf("%c",s[i]);
//或者直接用%s输出
printf("%s\n",s);
字符数组和字符指针变量
字符数组名是常量不能做运算
数值类型的数组名可以做运算
1.可以对字符指针变量赋值,但不能对数组名赋值。
2.可以在定义时对各元素赋初值,但是不能对数组整体赋值。
3.想从键盘输入一个字符串并令指针变量指向它,要先把该指针变量指向一个地址,不要空指向。
char message1[] = “hello”;
char *message2 = “hello”;
两个的初始化看上去很像,其实具有不同的含义。前者初始化一个字符数组,后者是一个真正的字符串常量,这个指针变量被初始化为指向这个字符串常量的存储位置,如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sbMS0PSh-1584805665556)($resource/265741b00dc151013b4e42e4c7eac38.jpg)]
char *a; //字符指针变量
a = “I love China”;//把首元素地址交给a
上面的 “I love China” 赋给 a 的不是字符串,而是他的首元素地址
char str[14];//字符数组
str[0] = ‘I’;//单个元素赋值,可以
str = “I love China”;//数组名时常量,不可以赋值
++char str[14];//在定义了字符数组的前提下
str[] = “I love China”;//不可以整体赋值++
上下两个是不等价的!
++char str[14] = “I love China”;//定义并初始化++
上面两个例子就是指针变量指向字符串更方便的理由:
使用字符数组时,只能采用在定义数组时就初始化或者逐个对元素复制的方法。
char *a;
scanf(“%s“,a);//代码执行时,不是输入一个值或他的地址给a,而是给a所指向的区域
a未指向任何地址,按照命令输入一个字符串到a的存储单元,然而a的指向时随机的,他可能指向空白区域,可能指向有数据的区域,破坏系统或程序数据。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-awlLX2hL-1584805665557)($resource/1584002739(1)].jpg)
char *a,str[10];
a = str;//确定指针指向
scanf(“%s“,a);//代码执行时,此时可以输入一个字符串到a所指向的一段存储单元。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CaayhgBC-1584805665558)($resource/1584003046(1)].jpg)
指向函数的指针
样例:
int (*p)(int , int);
p = max;
.
.
.
int max(int x,int y){…}
指向函数的指针与函数的匹配条件:
1返回类型匹配
2.有一个指向函数的指针(*p)//不要漏掉括号
3.参数类型匹配
4.给这个指针一个指向(指向相应匹配的函数名字,不必给出函数参数)
怎么调用指向函数的指针:
(*p)(所指函数参数的实参)
PS:
1.不要漏了星号,不然就是普通函数的意思
2.不要漏了指针变量的括号, * 号的优先级比()号要低,去掉(*p)的括号就变成了指针函数,该函数返回一个指针
3.指向函数的指针不能进行算术运算
使用指针调用函数的优点:
使程序更加简洁和专业!
下面使用指针变量调用函数的简单例子
#include<stdio.h>
int main()
{
int max(int ,int);
int (*p)(int, int);//设置指针的信息(返回值,函数参数类型)
int a,b,c;
p = max;//使p指向max
printf("please enter a and b:");
scanf("%d,%d",&a,&b);
c = (*p)(a,b);//等价于 c = max(a,b);
printf("a = %d\nb = %d\nmax = %d\n",a,b,c);
return 0;
}
int max(int x, int y)
{
int z;
if(x > y) z =x;
else z = y;
return(z);
}
指向函数的指针作为函数参数
样例:
(*x1) 指向函数 f1
(*x2) 指向函数 f2
void fun(int (*x1)(int),int(*x2)(int,int))
{
int a ,b,i = 3,j = 5;
a = (*x1)(i);
b = (*x2)(i,j);
}
分析上面的代码:
fun函数里有两个指向函数的指针变量,x1指向的函数有一个int参数,并且结果返回int值;x2指向的函数有两个int参数,并且返回int值。然后再fun方法里面使用这两个函数得到返回值赋给a和b。
为什么要这样用呢,直接调用f1和f2不行吗?
1.如果只是用到f1和f2两个函数,完全可以再fun里面直接调用,没有必要设指针变量x1和x2.
2.如果每次调用fun函数时,fun里面要调用的函数不是固定的,这次调用f1和f2,下次调用f3和f4。这时候用指针变量就比较方便了。只要每次调用fun函数时,给出不同的函数名作为实参即可(该函数需与指针的相关信息匹配才能传递),不必修改fun函数。这种方法复合结构化程序设计方法原则,是程序设计中经常使用的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uaF6sDQN-1584805665559)($resource/1584027957(1)].jpg)
上面的图结合下面的部分代码,简单说明说明情况下把指向函数的指针作为参数传递使用
if(n == 1) fun(a,b,max);
else if (n == 2) fun(a,b,min);
else if(n == 3) fun(a,b,add);
很清楚的看见,其实就是把函数作为参数传递下去了,当然这样写的前提就是这个指向函数的指针能匹配到这些不同的函数。
返回指针的函数
作为函数参数的多维数组
作为函数参数的多维数组名的传递方式和一维数组名相同——实际传递的是个指向数组第一个元素的指针。但是两者的区别是,多维数组每个元素本身是另外一个数组,编译器需要知道它的维数,以便为函数形参的下标值求值。
下面的两个例子说明他们的区别:
int vector[10]
…
func1(vector);
参数vector的类型是指向整形的指针,所以vector的原型可以是下面的两种
void func1(int *vec);
void func2(int vec[]);
作用于vec上面的指针运算把整型的长度作为他的调整因子。
我们来观察一个矩阵:
int matrix[3][10];
…
func2(matrix);
这里,matrix的类型是指向包含10个整形元素的指针,**func2的指针原型是什么样的呢?**可以使用下面两种:
void func2(int (* mat)[10]);
void func2(int mat[][10]);
在这个函数中,mat的第一个函数根据包含10个元素的整型数组的长度进行调整,接着第二个下标根据整型的长度进行调整,这和原先的matrix数组一样。
这里的关键在于编译器必须知道第二个及以后各维的长度才能对各下标进行求值,因此在原型中必须声明这些维的长度。第一维的长度并不重要,因为再计算下标值时用不到他。
在编写一维数组形参的函数原型时,你既可以把他写成数组的形式,也可以把它写成指针的形式。但是对于多维数组,只有第一维可以进行如此选择。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1yWs5EZV-1584805665561)($resource/1584167709(1)].jpg)
比如下面这个例子就是错误的:
void func2(int mat);
这个例子想把一维二维都写成指针,是不行的。实际上,这个例子把mat声明为一个指向整型指针的指针**,他和指向整型数组不是一回事。