相信对于学习过C语言的人来说指针一直是一个难点。其实C语言也就只有这么一个难点,攻破了这个难点,要做的就是不断实践了,毕竟想好学好一门语言不实践是不行的。
C语言中指针类型
-
**int p; ** //这是一个普通的整型变量
-
*int p; //p是一个返回整型数据的指针
-
int p[ ]; // p是一个整型数据组成的数组
-
*int p[ ]; //从p开始,先与
[]
结合,因为其优先级比*
高 ,所以p是一个整型数组,然后再与*
结合,所以p是一个由返回整型数据的指针所组成的数组 -
*int (p)[ ]; //这里p与
*
结合为一个指针,因为有了()
,改变了优先级,所以p是一个指向由整型数据组成的数组指针 -
**int **p; ** //这是一个二级指针,但我们可以这么来看。
*p
是一个一级指针,而**p
是一个一维指针,而这个一维指针里面的元素又是一维指针的指针,这样看起来这个指针就变得比二级指针更容易了 -
int p(int); //这是一个返回值为整型数据的函数
-
*int (p)(int); //p先与
*
结合,说明p是一个指针,并且是指向一个整型函数的指针
以上就是我们平时经常用到的一些类型,当然还包括char``double
等,这里只是用int
型进行说明。
指针说明
指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。
指针类型
从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。
(1)*int p; //指针的类型是int*
(2)*char p; //指针的类型是char *
(3)**int p; //指针的类型是int **
(4)*int (p)[]; //指针的类型是int(*)[]
(5)**int (p)[]; //指针的类型是int*(*)[]
指针所指向的类型
“指针的类型”和"指针所指向的类型"是两个概念,区分它们是学好指针的关键之一。
当你通过指针来访问指针过指向的内存区时,指针所指向的类型据顶了编译器把那片内存区里的内容当做什么来看待。
从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*
去掉,剩下的就是指针所指向的类型。例如:
(1)*int p; //指针所指向的类型是int
(2)*char p; //指针所指向的类型是char
(3)**int p; //指针所指向的类型是int *
(4)*int (p)[]; //指针所指向的类型是int()[]
(5)**int (p)[]; //指针所指向的类型是int*()[]
指针的值(指针所指向的内存区或地址)
指针的值是指针本身存储的数值,这个数值将被编译器当做一个地址,而不是一个一般的数值。 指针多指向的内存区就是从指针的值所代表的那个内存开始,长度为sizeof(指针所指向的类型)的一片内存区。我们说一个指针的值是xx,就相当于说该指针指向了以xx为首地址的一片内存区;说一个指针指向了某块内存取域,就相当于说该指针的值是这块内存区域的首地址。
指针本身所占据的内存片
指针本身占据了多大内存,用sizeof(指针的类型)测一下就知道了。
指针的算术运算
指针可以加上或减去一个整数。指针的这种运算的意义和通常的数值的加减运算的意义是不一样的,以单元为单位。例如:
char a[20];
int *p=(int *)a;
p++;
指针p的类型是int *
,它指向的类型是int,它被初始化为指向整型变量a。由于地址是用字节做单位,且一般32位程序中int为4个字节,所以p++后,p所指向的地址由原来的变量a的地址向高地址方向增加了4个字节。即原来p是指向数组a 的第0号单元开始的四个字节,此时指向了数组a 中从第4号单元开始的四个字节。
再来一个例子:
char c[20]="you_are_a_good_boy";
int *p=(int *)a;
p+=5;
在上面例子中,p加上了5,编译器是这样处理的:将指针p的值加上5乘上sizeof(int),在32位程序中就是加上了5乘4=20。所以就是p所指向的地址由原来的地址向高地址方向移动了20字节。
两个指针不能进行加法运算,但是可以进行减法运算,但必须类型相同,一般用在数组方面。
运算符&和*
&
是取地址运算符,*
是间接运算符
&a
的运算结果是一个指针,指针指针所指向的地址就是a的地址。
*p
的运算结果是p所指向的东西。这个东西得类型是p指向的类型,它所占用的地址是p所指向的地址。
指针表达式
一个表达式的结果如果是一个指针,那么这个表达式就叫指针表达式。例:
pa=&a; //&a 是一个指针表达式。
Int **p=&pa; //&pa 是一个指针表达式。
*p=&b; //*p 和&b 都是指针表达式。
由于指针表达式的结果是一个指针,所以指针表达式也具有指针的四个要素:**指针的类型、指针所指向的类型、指针指向的内存区、指针自身占据的内存。
指针和数组的关系
数组的数组名其实也可以看作一个指针。
int a[2]={0,1},b;
b=a[0]; //也可写成:b=*a;
b=a[1]; //也可写成: b=*(a+1);
指针和结构类型的关系
可以声明一个指向结构类型对象的指针。**例:
struct cl
{
int a,b,c
};
struct cl cn={20,30,40};
// 声明结构对象cn并初始化
struct cl *p=&cn;
//声明一个指向结构对象b的指针,它的类型是cl *,它指向的类型是cl
int *ps=(int *)&cn;
//声明一个指向结构对象cn的指针,它被指向的类型p是不同的。
-
怎么通过指针p来访问cn的三个成员变量?
p->a; //指向运算符,或者(*p).a
p->b;
p->; -
如何通过指针ps来访问cn的三个成员变量?
-
ps; //访问cn成员a;
-
(ps+1); //访问cn成员b;
-
(ps+2); //访问cn成员c;
注意所有C/C++编译器在排列数组的单元时,总是把各个数组单元存放在连续的存储区里,单元和单元之间没有空隙。但在存放解构对象的各个成员时,在某些编译环境下,可能会需要字对齐或其他的什么对齐,需要在相邻的两个成员之间加若干个“填充字节”这就导致各个成员之间可能有若干个字节的空隙。所以即使*ps
访问到结构对象的第一个成员变量a,也不能保证*(ps+1)
就能访问到结构成员b。
指针和函数的关系
可以把一个指针声明成为一个指向函数的指针。**例:
int fun(char *);
inta;
char str[]="abcdefghijklmn";
a=fun(str);
int fun(char *s)
{
int num=0;
for(int i=0;;)
{
num+=*s;s++;
}
return num;
}
这个例子中的函数fun统计一个字符串中各个字符的ASCII码值之
和。 前面说了, 数组的名字也是一个指针。 在函数调用中, 当把 str
作为实参传递给形参 s 后, 实际是把 str 的值传递给了 s, s 所指向的
地址就和 str 所指向的地址一致,但是 str 和 s 各自占用各自的存储空
间。 在函数体内对 s 进行自加 1 运算, 并不意味着同时对 str 进行了自
加 1 运算。
指针类型转换
指针类型转换和数据类型的转换是一样的,在变量前进行强制转换。
指针的安全问题
很多时候我们买的代码在语法上没有错误,但是在逻辑上或者其他的地方有错。在使用指针的时候,程序员常犯的错误就是自己写了一个指针,最后却不知道这个指针指向了哪里。所以在使用指针的时候,程序员必须要清楚自己写的指针指向了哪里,在用指针访问数组的时候,也要注意不要超出了数组的低端和高端界限,以免出现错误。
本文是笔者看一些资料所总结,如有不妥,还请多多指出。