【C语言】从零了解指针(同时详细说明地址让你更容易了解指针)

本文深入介绍了C语言中的指针,包括如何定义指针变量,指针的作用如变量地址、交换值、返回多个值,以及指针与数组、const关键字的结合使用。同时,文章讨论了指针运算和常见错误,如未初始化的指针使用。通过对这些概念的解析,读者能更好地理解和掌握C语言中指针的运用。
摘要由CSDN通过智能技术生成

一、从零了解指针

  • 就是存放地址的 变量
  • 无论指向什么类型,所有的指针的大小都是一样的4个字节。(32位的平台上)。
  • 且指向不同类型的指针不能相互赋值。

怎么定义指针变量 ?

  • int p; 或者 int p;

    int* p; 和 int *p; 两者意思相同

int* a,b,c;	//	代表只有a是指针变量,而b,c变量只是普通的整型变量;
int *a,*b,*c; //  则是a,b,c  三个都是指针变量;
  • 例如:
int i ; 
int* p = &i ; //发生了什么???

image-20220505235351506

值得注意的一点
  • 定义了指针变量后。比如: int *a; //定义的是a为指针变量

  • 要用到的时候也是a ,而不是*a。

    详细后面会说到。

变量的地址与变量的值

  • 我个人认为学习下面的知识点得明白变量的地址变量的值有什么区别。
  • 先假设一下,内存就是酒店;酒店里有很多房间,其中房间号就是:内存地址;房间里的人就是:储存的数据;
  • 以下面为例:

​ 定义一个变量a,变量a的地址假设为0x123,就当作房间号是0x123

​ 赋给a值,就相当于人。就当作变量的值

  • int a = 10;  //定义一个变量a ,将10赋值给a
    
  • 假设变量的地址为0x123

image-20220506224028893

用指针作为参数

  • 指针作为变量,在被调用时得到某个变量的地址
  • 在函数里可以通过这个指针访问到函数外面的 i(如何操作看下面的《访问指针》)

访问指针

  • 访问(读、写)-------访问即我可以获取它,也可以修改它。

  • *是一个单目运算符(不是乘号的意思)。用来 访问指针的值所表示的地址上的变量的值(访问指针所指向变量的值

  • 可以做左值,也可以做右值

  • 比如,int i = 6; int* p = &i;

    则p的值就是 i 的地址。而我用 *p,则是访问p的值所表示的地址上的变量的值(即我可以获得 i 的值,或者我可以修改 i 的值。

    也可以将*p看成一个整体,当做整数。

void f(int* p);
int main() {
	int i=6;
	printf("&i = %p\n", &i);	//输出i的地址
	f(&i);
	return 0;
}
void f(int* p) {	//指针作为参数的函数
	printf("p  = %p\n",p);  //输出p的地址(其实就是i的地址)
    printf("*p = %d\n",*p);	//6  输出*p,即p所指向变量的值(也就是i的值6)
    *p = 60; //修改i的值为60。 上面有说为什么可以修改。
    
}

二、指针有什么用

交换两个变量的值

为什么要用指针交换?因为不用指针交换你传入到方法参数后你得到的只是传过来变量的值,你如果得到的是它的值的话,你就不能够交换它的那个的值。但是如果它传入的是变量的地址的话。那就可以。

void swap(int* pa, int* pb);
int main() {
	int a = 11;
	int b = 88;
	swap(&a, &b);	//将a和b的地址传入方法swap。使得里面参数可以访问ab。
	printf(" a = %d,b = %d", a, b);  //输出a=88,b=11。交换成功。
	return 0;
}
//swap 英文是交换的意思。
void swap(int* pa, int* pb) {   //这里执行交换变量的值
	int t;
	t = *pa;
	*pa = *pb;
	*pb = t;
}

题外话:

int a ;
int b ;
a = b = 100;   //意思是 a 等于100; 而 b 也等于100;

函数返回多个值

由于函数不能返回多个值,这个时候用指针可以返回多个值。

因为将目标变量(min和max)的地址传入参数,并将最小值和最大值传给min和max。

从而实现了返回多个值的目的。

注:数组也行。

#include <stdio.h>
void minmax(int a[], int len, int* min, int* max);
int main() {
	//一个数组,现在求它的最大值和最小是
	int a[] = {1,2,3,4,5,6,7,1,234,34,2,1,45,32,3,32,4,5,567,23,0,4,34}; 
	int min, max; //最小值,最大值
	minmax(a,sizeof(a)/sizeof(a[0]),&min,&max );
    //在minmax函数里已经通过循环获得最小值和最大值并将其赋给min和max。
    //所以这里输出min和max的结果是0,567.
	printf("min = %d\nmax = %d\n",min,max); //0,567

}
void minmax(int a[], int len, int* min, int* max) {
	int i;
	*min = *max = a[0];  //*min 与*max的值都是为a[0]
	for (i = 1; i < len; i++) {
		if (a[i] < *min) {
			*min = a[i];
		}
		if (a[i] > *max) {
			*max = a[i];
		}
	}
}

有一种情况是这样的:

  • 函数本身返回运算的状态,结果通过指针返回(就是函数和指针分开返回)
  • 一般是让函数返回特殊的不属于有效范围的值,来表示出错(0 或者 -1)
  • 但是当任何数值都有可能是有效数值的话。就得分开返回了。

注:这个可以用异常解决,我也不知道为啥要列出这个情况。但是还是了解下比较好。

#include <stdio.h> 


int main() {
	int a = 10;
	int b = 2;
	int c;
    //如果if括号里没判断符号的话,则默认为真。啥意思,比如if(a)。则是如果a为真则执行,否则不执行。
	if (divide(a, b, &c)) {  //传入参数的同时进行判断,如果返回的是0则不会执行这条if。
		printf("%d / %d = %d\n",a,b,c);
	}


	return 0;
}

int divide(int a ,int b ,int *result) {
	int ret = 1;  //作为返回的值
	if (b == 0) return ret = 0;   //如果b == 0;则a/0,程序必然运行不了。
	else {
		*result = a / b;
	}
	return ret;
}

指针常见的错误

  • 定义了指针变量,还没有指向任何变量,就直接使用指针,这是错了。

    比如:

int *p;
//这个时候指针变量p,是啥都没有的。虽然我们可以把*p当成一个整数,但是我们不能直接给它赋值
//*p = 10; 这是错的。

//原因一: 我们创建了这个指针变量 p。它没有存放地址(也就是还没指向任何变量)。这个时候它啥都没有。你就给他赋一个值。这是错的。
//因为它连地址都没有,你就赋不了值。好比你要寄快递给一个乞丐,乞丐居无定所,连家都没有,所以你就寄不了了。差不多的道理。

//原因二:然后还有个说法是这个这个指针变量p,你给*p赋值的话,是可以的,但是p是没有一个明确的值的,如果把它当作地址的话,它是随便指向一个地方的。这时候你给它赋值。那个地址又碰巧不能写的话,你程序依旧不能运行。(它的意思就是你可能这样写,不会报错,但是总有一次会运行不了。但是我在vs上每次都运行不了。反正记住不能这样做就行了)
(无论哪种说法都是说明不能犯这种错误)

数组与指针

函数参数里的数组

**结论:**函数参数表中的数组,实际上就是指针,但是我们可以用数组的运算符 [] 进行运算

  • 我们可以做个实验:
void  jkl(int a[]);
int main() {
	int a[] = {1,2,5,2,3,5,9};
	printf("传入前 a[0] = %d\n",a[0]);    //结果是1
	jkl(a);  //这里将a传入
	printf("传入后 a[0] = %d\n",a[0]);	 //结果是10000;
	return 0;
}

void  jkl(int a[]) {
	a[0] = 10000;
}
  • 可得数组可以和指针一样 ,而且想到前面要用sizeof获取数组的个数的时候,不能直接获取,而单个数组单元获取是4个字节,和指针一样大。(所以有没有一种可能是数组的本质就是指针呢)

  • 我们再做一个实验,将 jkl 的参数改成指针。并将传入的数组当成指针

​ 看看结果能否和参数为数组的时候一样。

void  jkl(int a[]);
int main() {
	int a[] = {1,2,5,2,3,5,9};
	printf("传入前 a[0] = %d\n",a[0]);    //结果是1
	jkl(a);  //这里将a传入
	printf("传入后 a[0] = %d\n",a[0]);	 //结果是10000;
	return 0;
}

//这里参数改成指针,但是输出和上面为数组的时候一样
void  jkl(int *a) {	 
	a[0] = 10000;
}
  • 事实证明:数组传入指针变量参数时,我们可以将其当作数组来使用
  • 所以函数参数表中的数组,实际上就是指针,但是我们可以用数组的运算符 [] 进行运算
数组变量是特殊的指针
  • 因为数组变量本身表达地址,所以我们要取数组地址的时候不需要 & 符号

  • 但是数组的**单元(就是数组[0],数组[1]之类的)**表达的是变量,要获取其地址还是需要用 & 符号的;

    int  arr[100]; //这是一个数组
    int *p ;  //定义一个指针变量
    p = arr;  //直接就可以将地址给p了,不用&arr
    
    • **需要注意的一点是:**上面直接将数组地址给p,给的是首地址.不是首元素地址

​ **数组的首地址:**表示整个数组的地址。

​ **数组的首元素地址:**表示数组的首个元素的地址。即数组[0]的地址

指针变量可以当作数组
  • 指针当作数组,指针指向的变量当作数组的首元素 p[0]

啥意思呢??

看代码就懂了:

	int jjj[100] = { 1000,12 };
	int kkk = 100; //定义一个整数 100;
	int* p = &kkk;  //定义一个指针变量,并获取kkk的地址;
	printf("*p = %d \n",*p);  //很明显 *p 等于100
	//我们现在将指针当作数组输出;
	printf("p[0] = %d\n",p[0]); //输出的也是 100
	//不仅可以当作数组输出,还可以修改其的值
	p[0] = 999;  //将其改成999
	printf("p[0] = %d\n",p[0]); // 999
	printf("kkk = %d\n",p[0]); // 999

  • 反过来也成立。可以将数组当成指针来使用。同样只有首元素起作用
	int j[100] = { 1000,12,213,432 };
	printf("j[0] = %d\n", j[0]); //1000
	printf("j[0] = %d\n", *j);  //1000  用*j输出依旧是1000
	//用*j 改首元素,改成999
	*j = 999;
	printf("j[0] = %d\n", j[0]); //999
	printf("j[0] = %d\n", *j);  //999 
  • 虽然我感觉这个知识点没啥用,但是还是学一下好,起码以后看到不至于不知道啥意思。

指针与const

  • const 在星号*前面则表示,指针所表示的东西不可修改。

  • 可这样写

  • int const *p;//和下面的意思一样

  • const int*p;

    啥意思呢?看代码

    	int i = 100;
    	int const* p = &i;
    	printf("i  = %d\n", i);  //这个时候指针所表示的值已经固定了。等于100
    	//如果我们这个时候试图改变i的值
    	*p = 999; //直接报错。
    	//但是你可以直接用 i 改变
    	i= 888printf("i  = %d\n", i); //888
    
  • const 在星号*后面,则表示指针不可修改

  • int *const p ;

    看代码:

    	int i = 100;
    	int a = 299;
    	int *const p = &i;
    	printf("p = %p\n", p);//输出p(实际上就是i的地址)
    	printf("i = %p\n", &i);//输出i的地址
    	p = &a;	//现在要试图给p修改成a的地址,这个时候你的编译器就会报错
    
    
const数组:
  • const int arr[] ={1,2,3,4};
  • 则数组变量已经是const的指针了,并且const表示的是,数组里的每个单元都是const int.
  • 所以必须通过初始化进行赋值.
将非const的值转换成const的(在结构体会用到)

顾名思义,就将非const的值转化成const,保证传进来的参数的值不会变。

const的也可以转成const,不影响。

什么时候需要这样做?

  • 当要传递的参数的类型比地址大的时候,这是常见的手段:既能用比较少的字节数传递值给参数,又能避免函数对外面的变量的修改。(后面会学)
void f(const int* a){
    int c = 888;//定义一个整型为888
    //zhe时候我们不想让它的值发生改变
    f(&c);  //将c的地址传给函数参数,直接转化为const
    
}

指针运算

  • 如果给指针加上**(运算符,+,+=,-,-=)**

  • 指针+1(单元往后挪一位)

  • 结果会是输出首元素的后一个单元的数值(注意:这里不一定非要是首元素,看你怎么用);

  • 注意:无论是double还是char的数组都是如此;

  • 	int arr[] = { 1,2,3,4,5 };
    	int* p = arr;
    	printf("arr[0] = %d\n",arr[0]);//  1
    	printf("arr[0] = %d\n",*p);//  1
    	//这时候如果我们给指针加一会怎样?
    	printf("arr[1] = %d\n", *(p+1));//  2,很明显输出的就是0后面的一个单元了
    
    
  • 如果指针相减会发生什么?

  • 先说结果:相减他们地址的十六进制(地址的最后两位),会相减(直接化成十进制再相减也行)。然后化成十进制再/4。就是输出的整数结果。

    	int a[] = { 1,2,3,4,5 };
    	int* p1 = &a[1];
    	int* p2 = &a[2];
    	printf("p1 = %p\n",p1);//地址是十六进制的cc,转化成十进制就是204
    	printf("p2 = %p\n",p2);//       d0,转化后208
    	printf("p2-p1 = %d",p1-p2);//204-208/4 = -1;
    

**{p++,指的是 (p++),但是因为++的优先级比星号高,所以可以不用写括号}

指针比较

  • < ,<= ,== ,> ,>= ,!= 。都是可以对指针做的。比较他们在内存中的地址。

0地址(了解一下就行)

  • 任何程序都有零地址

  • image-20220508171205624

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值