c语言中,指针的故事

关于指针的二三事儿


  • 指针就是地址,是C语言的精髓。
  • 指针是灵活的,有时候也会是灾难的。
  • 学会,掌握指针,从这里做起。
这里主要引用了网络上的文章,《C指针详解(经典,非常详细)》,作者未知。
在此,感谢网络中无私奉献的大神们,大牛们!!

(1)识别

看指针,先从标识符看起,按照优先级结合,找出指针的类型,指向类型等。
直接和标识符相连的,表示不同的意义。
[] ==> 数组。
() ==> 函数
* ==> 指针

优先级
[],()优先级是最高的。
*优先级次高。
所以,标识符先和[]、()匹配。

数组指针,指针数组,函数指针,指针函数。

(2)四要素

指针类型,
将标识符去掉就是指针的类型。

指针指向的类型,
将标识符和相邻的*去掉就是指针所指向的类型。

指针的值 或者 叫指针所指向的内存区,
指针的值是XX,就相当于说该指针指向了以XX为首地址的一片内存区域;
指针指向某段内存,就相当与说该指针的值是该段内存的首地址。

这里注意,指针指向的类型和指针所指向的内存区是不同的。因为指针所指向的类型声明之后,如果没有初始化,那么它就没有指向的内存区。所以在使用指针的时候,要注意初始化。

int *a; //此时知道a是指针,类型:int *, 指向类型: int。
//但是这时没有指针的值,因为没有指向的区域。
//虽然会有值,但是是随机值,在程序运行中很危险。
//所以要初始化, 或者指向, 或者 = NULL,保证程序健壮性。

指针本身所占据的内存区
指针也是变量,也会占据内存。
通过&取地址运算符可以获取指针变量的地址,sizeof()运算符可以获取指针变量占据内存的大小。
在32位系统中,指针都是32位的,也就是4个字节。

(3)指针的算数运算

指针可以加上或减去一个整数

一个指针ptrold加(减)一个整数n后,结果是一个新的指针ptrnew。
ptrnew的类型和ptrold的类型相同,ptrnew所指向的类型和ptrold所指向的类型也相同。
ptrnew的值将比ptrold的值增加(减少)了n乘sizeof(ptrold所指向的类型)个字节。

重点理解的例子:

char s[80] = "Good morning.";
char *p = s;
char **ptr = &p;

printf("%c\n", **ptr);
ptr++;
printf("%c\n", **ptr);

问,输出的都是什么?

/// ptr++ 之前
ptr --->p---> s[80]

/// ptr++之后
ptr --->×_×p---> s[80]
--->pxx---> unknown


ptr的值,比p的地址 大 4(字节),这里用pxx表示。而pxx指向的内容,是未知的,随机的。


指针和指针运算

两个指针不能进行加法运算,没意义。
两个指针可以进行减法运算,通常在数组里面应用。结果 = 下标的差,具体 = (指针值的差) / sizeof(指针指向类型)

注意指针必须是同类型指针才可以做减法运算。

运算符 & *
& ==> 取地址运算符,获得变量的地址。得到一个指针,指向类型是变量的类型。

* ==> 取值运算符, 取得指针的值,也就是指向的内容。内容多样,可以是变量,指针,函数等。

指针表达式

如果一个表达式的值是指针,那么就是指针表达式。
所以也要看四要素。指针类型,指向类型,指针的值,本身所占的内存。

当一个指针表达式的结果指针已经明确地具有了指针自身占据的内存的话,这个指针表达式就是一个左值,否则就不是一个左值。
例子:
int a = 10, b;
int array[10] = {0};
int *pa = &a;
int **ptr = &pa;
*ptr = &b;
pa = array;
pa++;

例子中, &a这个表达式不是左值。因为这个指针没有占据内存。*ptr是左值,因为这个指针占据内存,就是pa。

如果没有 int **ptr = &pa; 的话, 那么 *ptr也不是左值。也就是初始化的问题。


什么是左值??  可以放在等式左边的值,是左值。

http://blog.csdn.net/u012480384/article/details/44495899


(4)指针和数组名

int a[10];
数组名代表数组本身,类型是int [10](例子)。
数组名还可以看成指针, 这时,类型是int *,指向数组第一个元素 a[0]。*(a + 3) 表示 a[3]。

字符串相当于是一个数组,在内存中以数组的形式储存,只不过字符串是一个数组常量,内容不可改变,且只能是右值.如果看成指针的话,他即是常量指针,也是指针常量.

声明了一个数组TYPEarray[n],则数组名称array就有了两重含义:
第一,它代表整个数组,它的类型是TYPE[n];
第二,它是一个常量指针,该指针的类型是TYPE*,该指针指向的类型是TYPE,也就是数组单元的类型,该指针指向的内存区就是数组第0号单元,该指针自己占有单独的内存区,注意它和数组第0号单元占据的内存区是不同的。该指针的值是不能修改的,即类似array++的表达式是错误的。

数组指针和数组名有区别。

int (*ptr)[10] = a; /// !# 编译有错误
int (*ptr)[10] = &a; /// ptr是指针,指向一个数组。
/// 所以需要对a取地址,此时 a 代表数组,不是指针。
int a0 = (*ptr)[3]; /// a[3],(*ptr) 相当于 数组名 a。可以按照数组名来使用。
int a00 = *((*ptr) + 3); ///a[3]

不同情况下,数组名意义不同。
int a[10];
int x = sizeof(a); ///这里 代表整个数组, 可以获得数组大小(字节)。
int y = sizeof(*a); ///这里 是指针,指向a[0],可以获得数组中元素的大小(字节)。
int z = sizeof(a + 5); ///这里 是指针,表达式是指针表达式,结果也是一个指针,
///可以获得指针的大小(字节)。

实际上,sizeof(对象)测出的都是对象自身的类型的大小,而不是别的什么类型的大小。


(5)指针类型转换

这里主要是 指针的强制类型转换。

(a)
OType * OldPtr;
NType * NewPtr = (NType *) OldPtr;
强制转换的结果也是一个指针,类型是NType *, 指向类型 NType,指向的内存是 原指针指向的内存,即值相等。而原指针 OldPtr的属性都没变!!!
对于计算机来说,就是以不同的视角来看待这段内存的内容。

例子:
int a = 10;
int *pa = &a;
char *sptr = (int *)pa; ///强制转换
例子中,*pa是 int型。而*sptr是 char型,这个字节就是(*pa)四个字节中的第一个字节。
利用这个特性可以提取int型的每个字节。如下例:

/// 将int型变量的四个字节颠倒。
void func_re(char * s)
{
char c;
c = *(s + 0); *(s + 0) = *(s + 3); *(s + 0) = c;
c = *(s + 1); *(s + 1) = *(s + 2); *(s + 2) = c;
}

int a = 10;
printf("0x%x \n", a); /// a == 0x 00 00 00 0A
func_re((char *)&a); /// 强制转换
printf("0x%x \n", a); ///输出16进制 a == 0x 0A 00 00 00

(b)
可以把指针的值当作一个整数取出来,也可以把一个整数值当作地址赋给一个指针。
一个整数不能赋值给一个指针。可以通过强制转换赋值。

int * ptr = 1241; /// 编译不通过
int a = 10;
int *ptr = &a;
int addr_a = (int) ptr; /// 将地址 转换为 整数,这里用ptr的值,即 a 的地址(&a)。
int *pptr = (int *)addr_a; ///将整数 转换为 地址


(6)指针安全问题

在使用指针时,程序员心里必须非常清楚:我的指针究竟指向了哪里。
不能让指针"飞了"


下面的例子:

char c = 'a';
int *ptr = (int *)&c;
//(!1)
*ptr = 1123;
ptr++;
//(!2)
*ptr = 115;


//(!)下面的语句都是不安全的。
(!1)因为 c 是 1byte, int 是 4byte。
如果使用 *ptr,就会使用 c 的内容和 c 下面3个字节的内容,作为一个int 来使用。这时的数据就是未知的,随意的。
(!2)同理。


在指针的强制类型转换:ptr1=(TYPE*)ptr2中,
如果sizeof(ptr2的类型)大于sizeof(ptr1的类型),那么在使用指针ptr1来访问ptr2所指向的存储区时是安全的。
如果sizeof(ptr2的类型)小于sizeof(ptr1的类型),那么在使用指针ptr1来访问ptr2所指向的存储区时是不安全的。
结合上面的例子就可以理解了。
切记, 使用指针,千万要知道指针指向哪里!!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值