C语言指针攻略

入门指针

内存的地址——计算机中所有的数据都必须放在内存中,为了正确地访问这些数据,必须为每个字节都编上号码。而这些编号就是地址,也就是指针。

怎么输出一个指针呢?

#include <stdio.h> 
int main()
{
	int a = 100; 
    char str[20] = "I love C"; 
    printf("%#X, %#X\n", &a, str); //%p也可
    return 0;  
} 

定义并使用它!

如果一个变量(假设为c)存储了一份数据的指针,我们就称它为指针变量(假设为p)。 术语称, p 指向了 c或者说 p 是指向变量 c 的指针。

//定义普通变量 
float a = 11.1, b = 22.2; 
char c = '#', d = '!';
//定义指针变量
float *p1 = &a; 
char *p2 = &c;
//修改指针变量的值
p1 = &b; p2 = &d; 

小伙伴们有木有发现,定义它的时候需要*,而再次使用他的时候不用再加上*了!非常省劲~

指针不仅能取得地址,还能获得数据!

其实特别简单,还需要使用的时候加上*即可,像这样!

1. #include <stdio.h> 
2.  
3. int main()
4. {
5.     int a = 15; 
6.     int *p = &a;
7.     printf("%d, %d\n", a, *p); 
8.     return 0;
9. }
10.//输出的a和*p都是15 

(终于加上行号了,O(∩_∩)O哈哈~)我们捋一捋哈定义加*,访问地址不加*,访问数据加*。

C语言谜题之 * 和 &

假设有一个 int 类型的变量 a,pa 是指向它的指针,那么*&a 和&*pa 分别是什么意思呢? (会考选择题哦~~)

*&a 就是a,怎么理解呢? a距离&比较近先运算,&a得到地址,根据第3条,再加上星号就是访问数据啦。

&*pa ,这次星号比较近,可以理解为&(*pa),*先取得 pa 指向的数据(等价于 a),在加上&就是地址啦。
第一个等价于 a,第二个等价于pa

指向数组的指针

进阶指针

1.数组指针

也就是指向数组的指针。大家都知道如果定义数组arr

int arr[] = { 2000, 15, 10, 11, 252 }; 
int *p = arr;

在以上例子中,arr、p、&arr[0]三者是等价的,他们都指向第0个元素。有同学认为arr本身就是一个指针,其实更准确来说arr被转换成了一个指针

arr在什么时候会转换为指针呢?

  1. 对数组的引用arr[i]在编译时被编译器改写成*(a+i)的形式。
  2. 数组作为函数形参,一个数组被编译器修改成指向数组第一个元素的指针。

有了数组指针,我们就有两种方案访问数组元素

  • 使用下标
    也就是采用 arr[i] 的形式访问数组元素。如果 p 是指向数组 arr 的指针,那么也可以使用 p[i] 来访问数组元素, 它等价于 arr[i]
  • 使用指针
    也就是使用 *(p+i) 的形式访问数组元素。另外数组名本身也是指针,也可以使用 *(arr+i) 来访问数组元素,它等价 于 *(p+i)。

关于数组指针的谜题

假设 p 是指向数组 arr 中第 n 个元素的指针,那么 *++p、(*p)++ 分别是什么意思呢?

*++p 等价于 *(++p),会先进行 ++p 运算,使得 p 的值增加,指向下一个元素,整体上相当于 *(p+1),所以会 获得第 n+1 个数组元素的值。
(*p)++ 就非常简单了,会先取得第 n 个元素的值,再对该元素的值加 1。假设 p 指向第 0 个元素,并且第 0 个元素的值为 99,执行完该语句后,第 0 个元素的值就会变为 100。

2.字符串指针

字符串一共有两种表示方法:

  1. 字符数组
char str[] = "http://c.biancheng.net"; 
printf("%s\n", str); 
  1. 指针指向字符串(本节讲解)
char *str = "http://c.biancheng.net"; 
printf("%s\n", str); 

并且两者还有区别:它们最根本的区别是在内存中的存储区域不一样,字符数组存储在全局数据区或栈区,第二种形式的字符串存储在常量区。全局数据区和栈区的字符串(也包括其他数据)有读取和写入的权限,而常量区的字符串(也包括其 他数据)只有读取权限,没有写入权限。 什么意思呢?请看以下代码:

1. #include <stdio.h>
2. int main(){ 
3. 		char *str = "Hello World!";
4.      str = "I love C!";  //正确 
5.      str[3] = 'P';  //错误,常量不能读入  
6.      return 0; 
7. } 

下面是C语言数组

#include<stdio.h>
int main(){
	char str[20] = "c.biancheng.net";
	char *s1 = str;
	char *s2 = str+2;
	char c1 = str[4];
	char c2 = *str;
	char c3 = *(str+4);
	char c4 = *str+2;
	char c5 = (str+1)[5];
	int num1 = *str+2;
	long num2 = (long)str;
	long num3 = (long)(str+2);
}
s1c.biancheng.net
s2biancheng.net
c1a
c2c
c3a
c4e
c5c
num1101
num22686736
num32686738

3.指针变量作为函数参数

1. int max(int intArr[], int len){ 
2.     int i, maxValue = intArr[0];  //假设第0个元素是最大值
3.     for(i=1; i<len; i++){
4.          if(maxValue < intArr[i]){ 
5.              maxValue = intArr[i]; 
6.          }
7. 		}
8.      return maxValue;
9.  } 
10.  

int intArr[]都不会创建一个数组出来,编译器也不会为它们分配内存,实际的数组是不存在的,它们最终还是会转换为 int *intArr 这样的指针。这就意味着,两种形 式都不能将数组的所有元素“一股脑”传递进来,大家还得规规矩矩使用数组指针。 因为 intArr 仅仅是一个指针,而不是真正的数组,所以必须要额外增加一个参数来传递数组长度。

4.指针作为函数返回值

#include <stdio.h> 
int *func(){ int n = 100;
	return &n;  
} 
int main(){    
	int *p = func(), n;    
	printf("c.biancheng.net\n");     
	n = *p;    
	printf("value = %d\n", n);     
	return 0; 
} 

输出结果:
c.biancheng.net
value = -2
可以看到,现在 p 指向的数据已经不是原来 n 的值了,它变成了一个毫无意义的甚至有些怪异的值。该段代码仅仅是在 *p 之前增加了一个函数调用(printf()),这一细节的不同却导致运行结果有天壤之别,究竟是为什么呢?

前面我们说函数运行结束后会销毁所有的局部数据,这个观点并没错,大部分C语言教材也都强调了这一点。但是, 这里所谓的销毁并不是将局部数据所占用的内存全部抹掉,而是程序放弃对它的使用权限,弃之不理,后面的代码可以随意使用这块内存。对于上面的两个例子,func() 运行结束后 n 的内存依然保持原样,值还是 100,如果使用及时也能够得到正确的数据,如果有其它函数被调用就会覆盖这块内存,得到的数据就失去了意义。

高级指针

1.二级指针

如果一个指针指向的是另外一个指针,我们就称它为二级指针,或者指向指针的指针。 在这里插入图片描述

int a =100; 
int *p1 = &a; 
int **p2 = &p1; //二级指针

2.空指针NULL以及void指针

简单来说,void是实实在在的指针,NULL是空指针

你记得malloc函数吗?

char *str = (char*)malloc(sizeof(char)*30);
char *str = NULL;

动态内存分配函数malloc()的返回值就是void*类型,所以用到了强制类型转换(char *),这就是一个实实在在的例子。NULL在C语言中表示空指针,不指向任何数据。

3.指针数组

如果一个数组中的所有元素保存的都是指针,那么我们就称它为指针数组。

1. #include <stdio.h> 
2.  int main(){ 
3.      int a = 16, b = 932, c = 100; 
4.      //定义一个指针数组 
5.      int *arr[3] = {&a, &b, &c};//也可以不指定长度,直接写作 int *parr[] 
6.      //定义一个指向指针数组的指针 
7.      int **parr = arr; 
8.      printf("%d, %d, %d\n", *arr[0], *arr[1], *arr[2]); 
9.      printf("%d, %d, %d\n", **(parr+0), **(parr+1), **(parr+2));. 
10.     return 0; 
11. //结果为 
12. //16,932,100
13. //16,932,100
14. } 

其中,parr+i 表示第 i 个元素的地址, * (parr+i) 表示获取第 i 个元素的值(该元素是一个指针), **(parr+i) 表示获取第 i 个元素指向的数据。

4.二维数组指针

也就是指向二维数组的指针。

int (*p)[4] = a; 

括号中的*表明 p 是一个指针,它指向一个数组,数组的类型为 int [ 4 ] ,这正是 a 所包含的每个一维数组的类型。

[ ]的优先级高于*,( )是必须要加的,如果赤裸裸地写作 int *p[4],那么应该理解为 int *(p[ 4 ]),p 就成了一个指针数组,而不是二维数组指针。

对指针进行加法(减法)运算时,它前进(后退)的步长与它指向的数据类型有关,p 指向的数据类型是 int [4], 那么 p+1 就前进 4×4 = 16 个字节,p-1 就后退 16 个字节,这正好是数组 a 所包含的每个一维数组的长度。也 就是说,p+1 会使得指针指向二维数组的下一行,p-1 会使得指针指向数组的上一行。

*(p+1)+1 表示第 1 行第 1 个元素的地址。如何理解呢?

*(p+1)单独使用时表示的是第 1 行数据,放在表达式中会被转换为第 1 行数据的首地址,也就是第 1 行第 0 个 元素的地址,因为使用整行数据没有实际的含义,编译器遇到这种情况都会转换为指向该行第 0 个元素的指针; 就像一维数组的名字,在定义时或者和 sizeof、& 一起使用时才表示整个数组,出现在表达式中就会被转换为指向数组第 0 个元素的指针。

在( * (p+1)+1)的基础上加星,就表示第 1 行第 1 个元素的值。很明显,增加一个 * 表示取地址上的数据。

5.指针数组和二维数组指针的区别

int *(p1[5]);  //指针数组,可以去掉括号直接写作 int *p1[5]; 
int (*p2)[5];  //二维数组指针,不能去掉括号 

6.函数指针

即指向函数的指针。
一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址,这和数组名非常类似。我们可以把函数的这个首地址赋予一个指针变量,使指针变量指向函数所在的 内存区域,然后通过指针变量就可以找到并调用该函数。这种指针就是函数指针。

int max(int a,int b){
	//功能
}
int main(){
	int (*pmax)(int a,int b) = max;//定义函数指针
	maxval = (*pmax)(x,y);//调用
}

总结

int *p;p 可以指向 int 类型的数据,也可以指向类似 int arr[n] 的数组。
int **p;p 为二级指针,指向 int * 类型的数据。
int (*p)();p 是一个函数指针,指向原型为 int func() 的函数。
int (*p)[n];p 为二维数组指针。
int *p[n];p 为指针数组。[ ] 的优先级高于 *,所以应该理解为 int *(p[n]);
int *p();p 是一个函数,它的返回值类型为 int *。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看rEADME.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看rEADME.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

HTUer的编程之路

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

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

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

打赏作者

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

抵扣说明:

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

余额充值