C语言基础:指针(四)

C语言基础:指针(四)

指针是C语言的精华所在,也是C语言中最难,最重要的数据类型。指针灵活,使程序高效化,但往往概念复杂,难以理解,下面就是关于一些对指针的知识汇总。
C_指针



1. 指针与指针变量

1.1 指针的概念

指针是内存的地址,地址就像是要访问的存储单元的指示标,所以在高级语言中形象地称为指针。
指针

1.2 多级指针

指针不仅可以指向基本类型变量,还可以指向指针变量。指向指针型数据的指针变量称为指向指针的指针,或者称为多级指针。
二级指针的定义如下:

数据类型 **指针变量;

三级指针的定义如下:

数据类型 ***指针变量;

代码示例如下:

void main(){
	int a=10,*p,**pp;
	p=&a;
	pp=&p;
	printf("*p=%d\n",*p);
	printf("**pp=%d\n",**pp);
}

运算结果:

*p=10
**pp=10

:在实际应用中,一般只用二级指针。

1.3 变量的指针

&取地址运算符表示变量的指针,变量的指针就是变量的地址,即变量的首地址。变量的指针定义如下:

&变量名

1.4 指针变量

1.4.1 定义

若有一个变量专门来存储指针,这个变量称为指针变量,用*表示指针运算符或指向运算符、间接访问运算符。指针变量的定义如下:

数据类型标识符 *指针变量名;

指针变量

1.4.2 初始化值

定义了指针变量后,还需要赋予确定的地址值,只有将某一具体变量的地址赋给指针变量之后,指针变量才能指向确定的变量(内存单元)即定义指针并赋给初始值。

int a=20;
int *q=&a;
1.4.3 单目运算符(&和*)
  • &取地址运算符:运算对象必须是变量,可以是任何变量。
  • *指针运算符:运算对象必须是指针,可以是指针常量或指针变量。

代码示例如下:

int main(int argc, char* argv[]){
	int *p,x=5;
	p=&x;//在指针变量p中存储x的地址
	printf("%d\n",*(&x));//*(&x)<=>x
	printf("%d\n",x);
	printf("%d\n",*(&p));//*(&p)<=>p
	printf("%d",p);
	return 0;
}

运行结果:

5
5
1703720
1703720

2. 直接访问与间接访问

2.1 直接访问方式

直接按变量名或地址访问变量值的方式。

int x=10,y;
y=x;

:C语言编译系统在编译时会将变量名转化成相应的地址。

2.2 间接访问方式

利用变量的指针访问变量值的方式。这种方式是通过定义一种特殊的变量专门存放内存或变量的地址,然后根据该地址值再去访问相应的存储单元。

int x=10,y,*p; 
p=&x; 
y=*p; 
*p=20;

直接和间接访问


3. 指针运算

3.1 赋值运算

  1. 将变量地址值赋给指针变量,使指针指向变量。
	int a,*p; 
	a=10; 
	p=&a; 
  1. 相同类型的指针变量间的赋值。
	int *pa,*pb; 
	pa=pb; 
  1. 给指针变量赋空值。
	int *p; 
	p=0;//#include "stdio.h"	p=NULL;

代码示例如下:

void main(){
	int x,y,*p,*q;
	p=&x;//p——>x
	q=&y;//q——>y
	*p=10;//p——>x=10
	q=p;//q——>x=10
	*q=x+*p;//q——>y=10+10=20
	y=*p;
	printf("%d,%d\n",x,y);
}

运行结果:

20,20

3.2 算术运算

一个指针可以加、减一个整数n,但其结果不是指针值直接加或减n,而是与指针所指对象的数据类型有关。算术运算,也称为与整型的加减运算。代码示例如下:

int main(){
	int a[10]={1,2,3,4},*p;
	p=&a[0];//p=a;
	printf("%d\n",p);//地址值
	printf("%d\n",*p);//元素值,指向的内容
	p=p+2;
	printf("%d\n",p);
	printf("%d\n",*p);
	printf("%d\n",sizeof(int));
	return 0;
}

运行结果:

1703688
1
1703696
3
4

:在16位计算机中,整型int占2个字节,但在32位计算机中int则要占4个字节,在64位计算机中int仍然也占4个字节。

3.3 关系运算

关系运算,即相同类型指针的比较,指针间大小关系取决于地址大小。当指针指向同一个数组元素时,也可以用下标来比较大小。代码示例如下:

int main(){
	int a[10]={3,4,5,6,7};
	int *p,*q,x;
	p=&a[2];
	q=&a[4];
	x=(q>p)?1:0;
	printf("%d\n",x);
	return 0;
}

运行结果:

1

关系运算

:在数据结构中,数组的下标一般称为相对地址,指针称为绝对地址。

3.4 自增自减

自增自减,表示指针变量指向下一个或上一个基本类型单元。代码示例如下:

int main(){
	int a[4]={9,8,7,6};
	int *p;
	p=&a[2];
	printf("%d\n",p);
	p++;
	printf("%d\n",p);
	--p;
	printf("%d\n",p);
	return 0;
}

运行结果:

1703720
1703724
1703720

  1. p++++p表示把当前指针p向高地址移动一个基本类型单元,若p++为操作数,则先引用p再自增,而++p是先自增再引用。
  2. p----p表示把当前指针p向低地址移动一个基本类型单元。

3.5 指针相减

指针相减,即两个指针间相差的基本类型单元。代码示例如下:

int main(){
	int a[10]={3,4,5,6,7};
	int *p,*q,x;
	p=&a[2];
	q=&a[4];
	printf("%d\n",q-p);//相差的基本类型单元
	return 0;
}

运行结果:

2

3.6 用指针访问数组元素

例1:

int main(){
	int a[5]={1,2,3,4,5},*p;
	p=a;
	for(int i=0;i<5;i++)
		printf("%d\n",*(a+i));//printf("%d\n",*p++);
}

运行结果:

1
2
3
4
5

:公式*(a+i)等价于a[i]a+i等价于&a[i]

例2:

int main(){
	int a[4]={1,2,3,4};
	int *p;
	p=a;
	for(int i=0;i<=3;i++){
		printf("%d\n",*p);
		p++;
	}
	return 0;
}

运行结果:

1
2
3
4

例3:

int main(){
	char a[5]="abcd";//还包含一个结束字符"\0"
	char *p;
	p=a;
	for(int i=0;i<=4;i++){
		printf("%s\n",p);//必须是一个地址,从该地址往下打印
		p++;
	}
	return 0;
}

运行结果:

abcd
bcd
cd
d

4. 指针与函数

4.1 指针作为函数参数

利用指针类型的参数可以访问不在作用域范围内的数据,被调函数利用指针类型的参数可以向主调函数传递参数。C语言函数参数传递的本质:值传递(单向传递)。我们先来看看这个例子:

void swap(int a,int b){
	int t;
	t=a;
	a=b;
	b=t;//只把形参a和形参b的值进行了交换
}
int main(){
	int x=8;
	int y=6;
	swap(x,y);
	printf("%d,%d\n",x,y);
}

运行结果:

86

:大家是不是以为xy的值会交换,但它的赋值没有变化,这是为什么呢?

:C语言的参数传递只进行一次,就是在调用时把实参传给形参,并不会结束时形参会传递给实参,形参的任何改变不会影响到实参。

:那如何可以交换函数里xy的值呢?那我们就来用用指针试试。

例1:
指针变量作为形参,变量地址作为实参。

void swap(int *a,int *b){//指针变量作为形参
	int t;
	t=*a;
	*a=*b;
	*b=t;
}
int main(){
	int x,y;//变量地址作为实参
	scanf("%d,%d",&x,&y);
	swap(&x,&y);
	printf("%d,%d\n",x,y);
	return 0;
}

例2:
指针变量作为形参,指针变量作为实参。

void swap(int *p1,int *p2){//指针变量作为形参
	int t;
	t=*p1;
	*p1=*p2;
	*p2=t;
}
int main(){
	int x,y;
	int *pa,*pb;//指针变量作为实参
	scanf("%d,%d",&x,&y);
	pa=&x;//指针pa指向x,指针pb指向y
	pb=&y;
	swap(pa,pb);
	printf("%d,%d\n",x,y);
	return 0;
}

运行结果:

输入:8,6
输出:6,8

4.2 数组名作为函数参数

数组名即数组的首地址,即第一个元素的地址,形参写成数组的样子,等同于写成相应基类型的指针变量,且数组长度不要写。形参的数组其实和相应的实参数组是一回事,所以对形参数组元素的改变相当于对实参数组元素的改变。
例1:

void swap(int *p){
	int t;
	t=p[0];
	p[0]=p[2];
	p[2]=t;
}
int main(){
	int a[3]={1,3,5};
	swap(a);//数组名作为参数进行传递
	printf("%d\n",a[0]);
	printf("%d\n",a[2]);
}

运行结果:

5
1

例2:

int main(){
  int a[5]={1,2,4,6};
  int *p,*q;
  p=a;
  q=a+2;
  printf("%d %d\n",p[2],q[2]);//p[2]等价于a[2]或*(p+2),q[2]等价于a[4]或*(q+2)
}

运行结果:

4 0

4.3 字符(串)指针作为函数参数

代码示例如下:

int main(){
	char *p,*q,a[10]="abcd",b[100];
	p="abc";//a="abcd";是非法的
	scanf("%99s",b);
	printf("%s\n",p);
	printf("%s\n",a);
	printf("%s\n",b);
	return 0;
}

运行结果:

输入:abcdef
输出:
abc
abcd
abcdef

在C语言中,使用scanf("%s", b);来读取字符串时,如果b没有指向一个已分配的内存区域,那么scanf函数会尝试将输入的字符串写入一个随机的内存地址,这可能导致未定义的行为,包括数据损坏、程序崩溃,甚至更严重的安全问题。为了确保scanf函数安全地读取字符串,需要确保以下两点:

  • 必须指向一个已分配的内存区域:可以通过在栈上声明一个字符数组,或者使用malloc/calloc/realloc等函数在堆上动态分配内存来实现。
  • 避免缓冲区溢出:限制scanf读取的字符数,以防止它超过目标内存区域的大小。使用%Ns格式说明符来实现这一点,其中N是目标数组的大小减1(字符串末尾有结束字符\0)。

字符数组和字符指针

  • 区别
  1. 字符指针在程序的任何位置都可以直接用字符串进行赋初值,字符数组与普通数组一样,不能对其进行整体赋值,只能给数组里的每个元素进行赋值,而且字符数组名作为常量,不能对其赋值;
  2. 以字符数组作为形参,编译时候会为它分配若干存储单元,以指针作为形参的话,编译时候只会分配一个存储单元;
  3. 字符数组中每个元素都可以放一个字符,而字符指针变量存放字符串首地址。
  • 共同点
    都可以用来处理字符和字符串。

5. 指针与数组

5.1 指针数组

指针数组是指针变量的集合,即它的每一个元素都是指针变量,且都具有相同的存储类别和指向相同的数据类型。指针数组的定义如下:

类型标识符 *数组名[数组长度说明];
char *p[4]={"C","Program","is","excellent!"};
int *q[10];

指针数组

5.2 指向一维数组的指针

数组名是一个常量指针,它的值为该数组的首地址,即第一个元素的地址,指向数组的指针的定义方法与指向基本类型变量的指针定义方法是相同的。

int a[10]={1,3,5,7,9},*p;//p是指针变量
p=a;//a是常量指针,数组首地址
p=&a[0];//使p指针指向数组a,即将数组a的首地址赋给p

5.3 指向二维或多维数组的指针

指向数组元素的指针,定义如下:

类型标识符 *指针变量名=数组名[第几行];

指向一维数组(二维数组的一行)的指针,定义如下:

类型标识符 (*指针变量名)[元素个数];
int a[2][3] = {{1, 2, 3}, {4, 5, 6}}; 
int *p=a[0];//p现在指向a的第一行的第一个元素,即1
int (*p)[3]=a;//p现在指向a的第一行

int a[2][3];语句中怎么去理解a*aa[0]

aa的首地址,指向a数组的第一行;*a即第一行的一个元素的地址;a[0]即第一行一个元素的地址。

:如果这么一说,那么*(a+1)表示第二行的第一个元素的地址,*(a+1)+1表示第二行的第二个元素的地址。

例1:

int main(){
	int a[2][3]={{1,2,3},{4,5,6}};
	printf("%d\n",a[0]);
	printf("%d\n",a);
	printf("%d\n",a+1);
	printf("%d\n",*a);
	printf("%d\n",*(a+1));
	printf("%d\n",*(a+1)+1);//b[i][j]<=>*(*(b+i)+j)<=>*(b[i]+j)
	return 0;
}

运行结果:

1703704
1703704
1703716
1703704
1703716
1703720

例2:

int main(){
	int a[9]={1,2,3,4,5,6,7,8,9};
	int *p[3]={a,a+3,a+6};
	printf("%d\n",p[2][2]);
	printf("%d\n",*(p[2]+2));
	return 0;
}

运行结果:

9
9

二维数组


6. 指针与字符串

6.1 指向字符串的指针

指向字符串的指针即指向字符的指针。对于指向字符的指针,可以用来处理一个字符,也可以用来处理一个字符串。字符串指针变量中存放的是字符串的首地址,而绝不是字符串本身。字符(串)指针定义如下:

char *指针变量名;

代码示例如下:

int main(){
	char c='x',*p=&c,*q="abcd",*m;
	m="xyz";
	printf("%c,%c,%s,%s\n",*p,*q,q,q+1);
	return 0;
}

运行结果:

x,a,abcd,bcd

6.2 字符串数组

所谓字符串数组,是指数组中的每个元素都是一个存放字符串的数组。字符串数组可以用一个二维字符数组来表示。数组的第一个下标决定字符串的个数,第二个下标是字符串的最大长度('\0'占一个位置)。

char s[3][10]={"hello","c","program"};

字符串数组


——>以上内容是关于C语言指针的基础知识,希望对初学者或再次学习者有所帮助,基础打扎实,不怕风吹雨打! 如果以上内容有错误或者内容不全,望大家提出!我也会继续写好每一篇博文!

待续未完
——文优

欢迎观看和提问!!!

下一篇:C语言基础:结构体(五)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

文优

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值