持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第18天,点击查看活动详情
引言
翻到了在加入掘金很久之前在其他平台写的c语言指针的学习的文章,接下来学习一下c指针
指针的概念
指针是一种数据类型,是一个变量在内存中所对应单元的地址。指针是用来存放地址的量。
(几个比较重要的概念:数组的首地址是数组名a的地址。数组首个元素的地址是a[0],比如一个数组a[10],系统会给a[0]~a[9]分配存储地址。
| a[0] | a[1] | a[2] | a[3] | a[4] | a[5] | a[6] | a[7] | a[8] | a[9] |
指针会根据你的指令指向某个地址。
如何定义一个指针?
指针类型(int,float,double.......) *p;
这个时候p就会成为一个指针变量。然后接下来让p指向对应的地址。
*指针必须指向地址。
*p和p的区别
我们有时候会看到
#include<stdio.h>
int main()
{
int a[10];
for(int i=0;i<10;i++)
scanf("%d",&a[i]);//为数组中十个元素赋值,比如输入1 2 3 4 5 6 7 8 9 10
int *p=&a[3];
printf("%d",*p);
}
//输出4(就是a[3])
有人可能会奇怪,我们知道**p是指针应该是指向一个地址,p指向的才是地址中的内容。*
(举个例子:一个瓶子,里面装的是水,瓶子就相当于地址,水就相当于地址中的内容,所以指针指向的是装水瓶子(地址),而不是里面的水(内容))
这里看上去是*p指向地址,但是在定义里,int *p是指区别于普通变量的指针变量p的意思。
所以本质上还是p指向地址,*p表示其指向的内容。
关于如何辨别到底什么是地址,什么是元素的问题?
数组的首地址是数组名a的地址。 我们要理解性的去记忆.
例1
以一下程序为例子。
#include<stdio.h>
int main()
{
int a[10]={1,2,3,4,5,6,7,8,9,10};
int *p,*q,*m,*n;//定义四个指针变量。
p=a;//根据数组的首地址是数组名a的地址,a是一个地址,所以此时p->a[0]
q=a+1;//a是一个地址(即a[0]的地址),a+1是地址加1,就是a[1]的地址,所以q指向a[1]
m=&a[0];//这一条语句和p=a一样,m也指向a[0],因为a是地址,a[0]是数组元素,不是地址,
//所以应该取地址符
n=&a[1];//和q那条语句等价,参考上一行的注释
printf("%d\n%d\n%d\n%d",*p,*q,*m,*n);
}
指向函数的指针
int (*p)();//定义一个指向函数的指针,该函数的返回值为整形数据。
int *f();//定义一个返回值为指针的函数,该指针指向一个整形数据。
注: 在c语言中,()的优先级大于,所以指针变量名外部必须用括号,否则指针变量名先与后面的()结合。**
更深刻的理解指针的作用
一段关于函数的代码:
#include<stdio.h>
hanshu(int x,int y)
{
return(x>y)?x:y;//两个值x,y,如果x>y为真,输出第二个,即x。如果为假,输出第三个,即y。
}
int main()
{
int (*p)();
int a,b,c,d,e;
scanf("%d,%d,%d",&a,&b,&d);
c=(*p)(a,b);
e=(*p)(c,d);
printf("%d %d",c,e);
}
这样就可以用指针调用函数了。
即:c=(*p)(a,b)等价于c=hanshu(a,b)
指针的基本运算
众所周知,指针是地址,所以指针之间的运算就是地址间的运算。
*******只有当两个指针指向用类型的数据元素时,才能进行关系运算!!!! *****************
当p和q指向同类型的数据元素时:
p<q:如果p地址小于q地址,表达式为1(真),否则为0(假)
p>q:同理比较地址。
p==q:单等是赋值,双等是判断。如果p地址和q地址相等,即p,q指向同一个元素的地址时,
表达式为1(真),否则表达式为0(假)
p!=q判断p,q地址是否相等,即p,q指向不同元素的地址时,
表达式为1(真),否则表达式为0(假)
*****任何指针p与NULL进行p==NULL或p!=NULL运算均有意义。
*****p==NULL指的是p为空的时候成立
*****p!=NULL指的是p不为空的时候成立。
一道小问题:
#include<stdio.h>
int fun(char *x)
{
char *y=x;
while(*y++);
return y-x-1;
}
int main()
{
char c[]="UESTC.EDU.CN";
printf("%d",fun(c+2));
return 0;
}
首先分析主函数,输出的是fun(c+2)
看fun函数,指针x指向c+2(字符串末尾系统会自动补一个’\0‘,而'\0'和0完全等价,实际程序中可以写0或'\0'。但是这俩和\0不一样,\本身是转义字符,转的是后面的0,所以\0实际上是0的ASCII码,即48)而指针y指向x,相当于指针y和x都指向c[2]的地址。
| U | E | S | T | C | . | E | D | U | . | C | N | '\0' |
^
|
x和y
正常的while循环后面无; 控制的是后面的一条语句,但是这里while后面有一个; 说明循环在while内部循环,直到循环体内部=0的时候跳出循环。即y指向字符串中的最后一位'\0'时为止。
此时y指向c[13],x->c[2],根据指针的运算关系,fun函数中return一个y-x-1即13-2-1=10。
指针和二维数组(行指针)
1、
a[0],a[1],a[2]..........是一堆数组名,在C语言中数组名代表数组的首地址,因此a[0]代表第0行一维数组中第0列元素的地址。即&a[0][0];所以,a[1]的值就是&a[1][0];
由此,a[0]+1在二维数组中就是第0行第一列的元素的地址了。(就是把第一行数组a[0]看作一个一维数组的首地址。)
由前面我们知道,a[0]等价于*(a+0),a[1]等价于*(a+1),所以a[0]+1等价于*((a+0)+1),都是&a[0][1],因此a[0][1]本身的值也就是*(a[0]+1)或*((a+0)+1)。也就是a[i]和(a+1)是等价的。
从形式上看,可以认为a[i]是第i个元素。但是,如果a是一个一维数组的数组名,那么实际上来看,a[i]就是数组a中第i个元素中的内容。在这里a[i]是有物理地址的,是占内存单元的。如果a是一个二维数组,则a[i]是一个一维数组名,它本身不占内存单元。他只是一个地址,也就是这个二维数组里的dii+1行的首地址。同样的道理,a+1是地址,也就是a[1]。而*(a+1)等价于a[1]。
所以:a[i][j]的存储地址是 首地址+i*N+j。编辑
(图片来自c语言程序设计(第2版)清华大学出版社)
所以行指针的问题要牢记这是行指针!!!!
关于二级指针:
看下列一段代码
int a[10]={1,2,3,4,5,6,7,8,9,10}, *p=&a[1];
int *q=p;
这里看上去指针p指向a[1]指针q指向指针p,但其实这段代码所表达的是指针p,q都指向a[1]。而不是q指向p。
那么我们如何表示一个指向指针的指针呢?
答案是:二级指针。
看这段代码:
int a[10]={1,2,3,4,5,6,7,8,9,10}, *p=&a[1];
int **q=p;
这是二级指针q便是指向p的指针了。
三个小练习
1、
- 编写程序:输入 10 个整数,将其中最小的数与第一个数对换,将其中最大的 数与最后一个数对换。
编程要求: 程序中要求有三个函数: (1)输入 10 个数; (2)进行处理(交换); (3)输出处理后的 10 个数
#include <stdio.h>
#define n 10
void spp(int *a)
{
int m1,m2,i;
int t1,t2;
i=0;
t1=*a;
t2=*a;
m1=0;
m2=0;
for(;i<10;i++)
{
if(*(a+i)>t1)
{
t1=*(a+i);
m1=i;
}
if(*(a+i)<t2)
{
t2=*(a+i);
m2=i;
}
}
*(a+m1)=*(a+9);
*(a+9)=t1;
*(a+m2)=*a;
*a=t2;
}
int main()
{
int a[n];
int i;
for(i=0;i<n;i++)
{
scanf("%d",&a[i]);
}
spp(a);
int j;
for(j=0;j<n;j++)
printf("%d,",a[j]);
return 0;
}
2、
- 写一函数,将一个字符串逆置。
●编程要求: (1)要求使用指针作为函数参数; (2)在主函数中输入字符串,并输出逆置后的字符串。
#include<stdio.h>
#include<string.h>
void xinxi(char *p,int n)
{
int i;
char t;
for(i=0;i<(n/2);i++)
{
t=*(p+i);
*(p+i)=*(p+n-i);
*(p+n-i)=t;
}
puts(p);
}
int main()
{
char a[10];
gets(a);
int n;
n=strlen(a);
xinxi(a,n);
return 0;
}
3、
- 编写函数,实现两个串的连接。(不用strcat)
●编程要求: (1)要求连接函数的参数和返回值类型均为指针类型; (2)要求在主函数中输入连接前的两个字符串,连接后,在主函数中输出连接后的字符 串。
#include<stdio.h>
#include<stdlib.h>
void xx(char *p,char *q)
{
int i;
while(*q!=0)
q++;
while(*p!=0)
{
*q=*p;
q++;
p++;
}
*q=0;
}
int main()
{
char a[10];
char b[10];
scanf("%s",&a);
scanf("%s",&b);
xx(a,b);
printf("%s",b);
}
暂时想到的就这些,还有什么问题可以在下面留言,大家一起交流。本文有什么问题也欢迎各位大佬指正。