C语言指针总结

C语言中的精华是什么,那当然是指针,是C语言的难点部分。C是对底层操作非常方便的语言,而底层操作中用到最多的就是指针,

这成就了优秀的C程序的效率几乎和汇编语言程序一样高的功绩。

本文介绍C指针的一些基础和高级知识。关键好是多写代码,这样才能更好的理解C的精华--指针。

1.  指针的概念

指针是一种数据类型,与其它的数据类型不同的是指针是一种“用来存放地址值的”变量。

首先搞懂这几个概念:

指针类型、指针指向数据类型、指针指向的内存区、指针在内存中占空间大小.

如:int *pn;

指针类型: 去掉变量名,所剩就是指针类型,可知 pn 的指针类型为 int * ;

指针所指数据类型: 去掉变量名及 *, int 即为指针指向数据的类型;

指针指向的内存区:指针指向数据类型为多大,那么指针指向的内存区大小就为多少。

指针在内存中所占空间大小:在 32位的系统上, 指针在内存中始终是一个占了4个字节的数据类型。

再介绍下C里面特殊类型的指针,void指针:

void的意思就是“无值”或“无类型”。void指针一般称为“通用指针”或“泛指针”, 使用void指针可以很容易地把void指针转换成其它数据类型的指针.

分析指针,可以从变量名处起,根据运算符优先级结合,一步一步分析.

下面举例分析:

int p;//这是一个普通的整型变量

int *p;//首先从P处开始,先与*结合,所以说明P是一个指针,然后再与int结合,说明指针所指向的内容的类型为int.所以P是一个返回整型数据的指针

int p[3];//首先从P处开始,先与[]结合,说明P是一个数组,然后与int结合,说明数组里的元素是整型的,所以P是一个由整型数据组成的数组

int *p[3];//首先从P处开始,先与[]结合,因为其优先级比*,所以P是一个数组,然后再与*结合,说明数组里的元素是指针类型,然后再与int结合,说明指针

所指向的内容的类型是整型的,所以是一个由返回整型数据的指针所组成的数组

int (*p)[3];//首先从P处开始,先与*结合,说明P是一个指针然后再与[]结合("()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组,

然后再与int结合,说明数组里的元素是整型的.所以P是一个指向由整型数据组成的数组的指针

int **p;//首先从P开始,先与*结合,说明P是一个指针,然后再与*结合,说明指针所指向的元素是指针,然后再与int结合,说明该指针所指向的元素是整型数据.

所以P是一个返回指向整型数据的指针的指针

int p(int);//P处起,先与()结合,说明P是一个函数,然后进入()里分析,说明该函数有一个整型变量的参数然后再与外面的int结合,说明函数的返回值是一个

整型数据.所以P是一个有整型参数且返回类型为整型的函数

int (*p)(int);//P处开始,先与指针结合,说明P是一个指针,然后与()结合,说明指针指向的是一个函数,然后再与()里的int结合,说明函数有一个int型的参数,

再与最外层的int结合,说明函数的返回类型是整型,所以P是一个指向有一个整型参数且返回类型为整型的函数的指针

int *(*p(int))[3];//P开始,先与()结合,说明P是一个函数,然后进入()里面,int结合,说明函数有一个整型变量参数,然后再与外面的*结合,说明函数返回的

是一个指,,然后到最外面一层,先与[]结合,说明返回的指针指向的是一个数组,然后再与*结合,说明数组里的元素是指针,然后再与int结合,说明指针指向的

内容是整型数据.所以P是一个参数为一个整数且返回一个指向由整型指针变量组成的数组的指针变量的函数

2.指针的算术运算

有效的指针运算包括相同类型指针之间的赋值运算、指针同整数之间的加法或减法运算、指向相同数组中元素的两个指针间的减法或比较运算、将指针赋

值为0或指针与0之间的比较运算。其它形式的指针运算都是非法的,例如两个指针间的加法、乘法等。

指针可以进行加或减运算,例如:

pnew=pold + n

一个指针 pold()一个整数 n后,结果是一个新的指针pnew,其中pnew pold的类型相同及其所指向的类型也相同,pnew的值将比 pold的值增加(减少)

nsizeof(pold所指向的类型)个字节。即pnew=pold+n

即 pnew表示指针pold当前指向的对象之后第n个对象(pold所指类型的对象)的地址。

3. 运算符&*

&是取地址运算符,*...书上叫做“间接运算符”(c++叫解引用)。

&n的运算结果是一个指针,指针的类型是n的类型加个*,指针所指向的类型是n的类型,指针所指向的地址,那就是n的地址。

*p 的结果是 p所指向的东西。*p,它的类型是 p指向的类型,它所占用的地址是p所指向的地址。

int n=1;

p=&n;//&n的结果是一个指针,类型是int*,指向的类型是int,指向的地址是n的地址。

4. 指针表达式

一个表达式的最后结果如果是一个指针,那么这个表达式就叫指针表达式。

char *str[32];

char **pstr=str;//如果把str看作指针的话,str也是指针表达式

char *str;

str=*pstr;//*pstr是指针表达式

str=*(pstr+1);//*(pstr+1)是指针表达式

由于指针表达式的结果是一个指针,所以指针表达式也具有指针所具有的四个要素:指针的类型,指针所指向的类型,

指针指向的内存区,指针自身占据的内存。

5. 数组和指针的关系

数组的数组名其实可以看作一个指针。

定义一个数组int A[]={1,2, 3};

则数A就有了两重含义:

第一,它代表整个数组,它的类型是 int [3]

,它是一个常量指针,该指针的类型是int*,该指针指向的类型是 int,也就是数组单元的类型,即数组名所代表的就是该数组最开始的一个元素的地址,

(int *p = A 与 int *p = &A[0] 是等价的)。该指针指向的内存区就是数组第0号单元,该指针自己占有单独的内存区,注意它和数组第0号单元占据的内存区是不

同的。该指针的值是不能修改的,即类似 A++的表达式是错误的。

例如

int a[10];
a 和 &a 的值是一样的,但是绝对不等同,a是数组首元素地址,这里也就相当于一个指向 int 的指针, 即a所指类型为整型。
&a则是指向数组的指针(因为a的类型就是个一维数组int [10]),实际上就是 int (*p)[], 此时p指针所指数据类型为 int [10]

  int a[10];
    int *p = a;
    p = &a;    // 出错或警告,不兼容指针类型赋值,需要类型转换(int *)

    int (*p4)[10];
    p4 = &a;  // 这样就是可以的

此外数组在作为函数参数时,数组名将蜕化为指针

例如:

void fun(char arr[12]);

在编译时编译器会当成是:

void fun(char *arr);//你在这个函数中使用sizeof(arr)得到的值是4

6. 指针和结构类型的关系

struct My
{
    int a;
    int b;
};

假设我们定义了上面的结构体,同时定义结构体的结构对象并初始化,struct My ss={10,20};
那么我们如何通过指针ptr来访问ss的三个成员变量呢?
答案就是,我们先定义一个指向结构对象ss的指针,struct MyStruct *ptr=&ss;然后,使用指向运算符->便可实现对结构对象ss成员的访问。
ptr->a;//或者可以这们(*ptr).a,建议使用前者
ptr->b;

7. 指针和函数的关系

可以把一个指针声明成为一个指向函数的指针,从而通过函数指针调用函数。

例如:

int fun(char *,int);
int (*pfun)(char *,int);
pfun=fun;
int a=(*pfun)("abc", 3);

例中,定义了一个指向函数fun的指针pfun,把pfun作为函数的形参。把指针表达式作为实参,从而实现了对函数fun的调用。

在这里不得不说,C里面的回调函数。

回调函数:就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,

我们就说这是回调函数。

使用回调函数原因:

可以把调用者与被调用者分开。调用者不关心谁是被调用者,所有它需知道的,只是存在一个具有某种特定原型、某些限制条件(如返回值为int)的被

调用函数。

举例:

typedef int (*Fun)(char *p); // 为回调函数命名,类型命名为Fun,参数为char *p

int Afun(void *p)
{    // 方法 Afun,格式符合Fun 的格式
    printf("CallBack:%s!\n", (char*)p);
    return 0;
}

void call(char *p, int (*f)(void *))
{
    f(p);//回调
}
int main()
{
    call("123", Afun);
    return 0;
}

8. 指针类型转换

当我们初始化一个指针或给一个指针赋值时,赋值号的左边是一个指针,赋值号的右边是一个指针表达式,这就要求两边的类型一致,所指向的类型也一致,

如果不一致的话,需要进行强制类型转换。语法格式是:(TYPE *)p

这样强制类型转换的结果是一个新指针,该新指针的类型是TYPE *,它指向的类型是TYPE,它指向的地址就是原指针指向的地址。要注意的是,原来的指针

p的一切属性都没有被修改。另外,一个函数如果使用了指针作为形参,那么在函数调用语句的实参和形参的结合过程中,也必须保证类型一致,否则需要强

制转换。

9.指针应用简单举例

int (*a)[10] 和 int *a[10] 有什么区别 ??

int   *a[10]  :数组指针。数组a里存放的是10个int型指针

int   (*a)[10]  :a是指针,指向一个数组。此数组有10个int型元素


int   *a[10]

先找到声明符a,然后向右看,有[]说明a是个数组,再向左看,是int   *,说明数组中的每个元素是int   *。所以这是一个存放int指针的数组。

int (*a)[10]

先找到声明符a,被括号括着,先看括号内的(优先级高),然后向右看,没有,向左看,是*,说明a是个指针,什么指针?在看括号外面的,先向右看,有[] 是个数组,

说明a是个指向数组的指针,再向左看,是int,说明数组的每个元素是int。所以,这是一个指向存放int的数组的指针。

例:

int   *a[10];
int   (*b)[10];

printf("*a[10]:   %d\n ",   sizeof(a));
printf("(*b)[10]:   %d\n ",   sizeof(b));
 

结果是:

*a[10]:   40                     //说明a是一个数组名

(*b)[10]:   4                   //说明b是一个指针

10.右左法:应用分析指针的复杂申明

上面第9 说明了指针的简单应用,下面介绍复杂的应用。

右左法则:

“The right-left rule:Start reading the declaration from the innermost parentheses, go right, andthen go left. When you encounter parentheses, the direction

should be reversed.Once everything in the parentheses has been parsed, jump out of it. Continue tillthe whole declaration has been parsed.”

翻译后为:

首先从最里面的圆括号看起,然后往右看,再往左看。每当遇到圆括号时,就应该掉转阅读方向。一旦解析完圆括号里面所有的东西,就跳出圆括号。重复这个过

程直到整个声明解析完毕。

对于这句话应该是从未定义的标识符开始阅读,而不是从括号读起,之所以是未定义的标识符,是因为一个声明里面可能有多个标识符,但未定义的标识符只会有

一个。

如:

Int  (*f)(int n)

确定括号的含义是分析这个声明的重要步骤,这个声明有两个括号,每个括号作用不同。

首先找到未定义的标识符f,它向右是括号,然后向左,是 *, 即 f是个指针。然后向右为(int n), 这个括号为函数调用操作符。所以f是个指向函数的指针,然后再向左,

碰到int 即为“这个函数的返回类型为整型”,把上面的解析连起来即 f为一个包含参数int n的函数指针,它所指向的函数返回一个整型值。

int (*f[5])(int*p);

f右边是一个[]运算符,说明f是一个具有5个元素的数组,f的左边有一个*,说明f的元素是指针,要注意这里的*不是修饰 f的,而是修饰f[5]的,原因是[]运算符优先级比*

高,f先跟[]结合,因此*修饰的是f[5]。跳出这个括号,看右边,也是一对圆括号,说明f数组的元素是函数类型的指针,它所指向的函数具有int*类型的形参,返回值类

型为int。

int(*(*func)[5])(int *p);

func被一个圆括号包含,左边又有一个*,那么func是一个指针,跳出括号,右边是一个[]运算符号,说明func是一个指向数组的指针,现在往左看,左边有一个*号,

说明这个数组的元素是指针,再跳出括号,右边又有一个括号,说明这个数组的元素是指向函数的指针。总结一下,就是:func是一个指向数组的指针,这个数组的

元素是函数指针,这些指针指向具有int*形参,返回值为int类型的函数。

11.指针面试笔试题

下面介绍 一些 IT公司的关于C指针的笔试题:

2013搜狗2013年校园招聘研发类笔试试卷之C/C++类

下面程序执行结果为【说明:x86_64环境】

#include <iostream>
using namespace std;

int main()
{
	int a[4][4] = {
		{1, 2, 3, 4},
		{50, 60, 70, 80},
		{900, 1000, 1100, 1200},
		{13000, 14000, 15000, 16000}
	};
	
	int (*p1)[4] = a;
	int (*p2)[4] = &a[0];
	int *p3 = &a[0][0];
	
	printf("%d, %d, %d, %d\n", 
		*(*(a+1)-1),		 
		*(*(p1+3)-2)+1,		 
		*(*(p2-1)+16)+2,	 
		*(p3+sizeof(p1)-3)	
		);

	return 0;
}

答案:

4, 1101, 13002, 60

注意:在64位环境下,指针的大小是8字节,所以最后一个是60;当在32位环境下,最后一个应该是2.

分析根据9、10指针应用举例,我们可以很容易的知道p1是一个指向存放int [4]数组的指针。同理p2也是一个指向存放int [4]数组的指针,p3是一个指向整型的指针。

 我们知道在C里面其实二维数组也是个一维数组,只是这个一维数组中的类型是另外一个一维数组而已。如下图 二维数组int b[4][4]={{0}}.

      

   即b[4][4] 等价 b[int[4]]

因此上面的题目就比较好解决了, *(*(a+1)-1) 根据优先级关系 我们先看 (a+1) 即指向a[1], 即二维数组第二行的首部,然后 *解引用,此时*(a+1)为指向int 的指针,

*(a+1)此时指向 数组a元素 50 ,*(a+1)-1即 指向50的前一个元素即4(因为静态二维数组是在栈上分配的二维数组一个一块连续的内存块)。*(*(p1+3)-2)+1 也可以利

用同样的方法分析,容易得知值为1100+1 即 1101. 说下 *(*(p2-1)+16)+2 吧,和前面两个方法也基本一致,p2-1即指向数组a[-1] 然后解引用 为指向int 的指针,然后

*(p2-1)+16 即为指向 第四行的首元素 13000 所以最后结果为 13002.   *(p3+sizeof(p1)-3) 比较简单 注意在64位机器上 sizeof(p1)为8.


参考:

http://harborwan.blog.sohu.com/19248423.html

http://blog.csdn.net/megaboy/article/details/482771

<<C和指针>>


  • 11
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言,可以使用指针来访问寄存器。通过指针,我们可以直接操控寄存器,进行硬件级别的操作。一个常见的方法是使用强制类型转换和指针来实现访问MCU的寄存器。举个例子,我们可以通过以下方式定义一个寄存器的指针: #define DDRB (*(volatile unsigned char *)0x25) 这里,DDRB被定义为一个指向地址0x25的无符号字符型指针。通过解引用这个指针,我们可以像操作普通变量一样操作寄存器。例如,如果我们想读取或写入DDRB寄存器的值,我们可以使用"*"运算符来操作这个指针的值。 另外,对于单片机的特殊功能寄存器(SFR),在C语言环境下,我们可以使用两种方法进行访问。一种方法是使用指针,通过指针来直接访问寄存器的地址。另一种方法是使用#define指令,将寄存器地址定义为一个变量,然后通过这个变量来访问寄存器。例如,如果我们定义了一个指向地址0x25的指针,并将其命名为i,那么*(volatile unsigned char *)0x25就是一个固定的指针,而不是指针变量。如果我们使用#define i (*(volatile unsigned char *)0x25),那么i就是一个普通的unsigned char变量,只不过它的地址是固定的。通过这种方式,我们可以像操作普通变量一样操作寄存器。 总结起来,C语言可以使用指针来访问寄存器。通过定义一个指向寄存器地址的指针,我们可以直接操控寄存器,并进行硬件级别的操作。此外,我们还可以使用#define指令将寄存器地址定义为一个变量,然后通过这个变量来访问寄存器。这些方法使得C语言成为嵌入式开发的基础语言之一,方便了对硬件的控制。<span class="em">1</span><span class="em">2</span><span class="em">3</span><span class="em">4</span>

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值