为
初学者服
务
。
这
是我的帖子的宗旨。我也是个初学者
(
强调
了无数遍了
),
我以我的理解把初学者
觉
得
难
懂的
东
西用浅
显
的
语
言写出来。由于小学
时语
文没学好
,
所以竭尽全力也未必能达到
这
个目的。尽力而
为
吧。
指
针
是
c
和
c++
中的
难
点和重点。我只精通
dos
下的
basic
。
c
语
言的其它各
种
特性
,
在
basic
中都有
类
似的
东
西。只有指
针
,
是
baisc
所不具
备
的。指
针
是
c
的灵魂。
我不想重
复
大多数
书
上
说
得很清楚的
东
西
,
我只是把我看
过
的
书
中
说
得不清楚或没有
说
,
而我又
觉
得我理解得有点道理的
东
西写出来。我的目的是
:
通
过
写
这
些
东
西
,
把我
脑
袋中
关
于
c
的模糊的知
识
清晰化。
第一章。指 针 的概念
第一章。指 针 的概念
指
针
是一个特殊的
变
量
,
它里面存
储
的数
值
被解
释
成
为
内存里的一个地址。
要搞清一个指
针
需要搞清指
针
的四方面的内容
:
指
针
的
类
型
,
指
针
所指向的
类
型
,
指
针
的
值
或者叫指
针
所指向的内存区
,
还
有指
针
本身所占据的内存区。
让
我
们
分
别说
明。
先声明几个指
针
放着做例子
:
例一
:
(1)int *ptr; (2)char *ptr; (3)int **ptr; (4)int (*ptr)[3]; (5)int *(*ptr)[4];
(1)int *ptr; (2)char *ptr; (3)int **ptr; (4)int (*ptr)[3]; (5)int *(*ptr)[4];
1
。
指
针
的
类
型。
从
语
法的角度看
,
你只要把指
针
声明
语
句里的指
针
名字去掉
,
剩下的部分就是
这
个指
针
的
类
型。
这
是指
针
本身所具有的
类
型。
让
我
们
看看例一中各个指
针
的
类
型
:
(1)int *ptr; //
指
针
的
类
型是
int *
(2)char *ptr; //
指
针
的
类
型是
char *
(3)int **ptr; //
指
针
的
类
型是
int **
(4)int (*ptr)[3]; //
指
针
的
类
型
是
int(*)[3]
(5)int *(*ptr)[4]; // 指 针 的 类 型是 int *(*)[4]
怎 么样 ? 找出指 针 的 类 型的方法是不是很 简单 ?
(5)int *(*ptr)[4]; // 指 针 的 类 型是 int *(*)[4]
怎 么样 ? 找出指 针 的 类 型的方法是不是很 简单 ?
2
。指
针
所指向的
类
型。
当你通
过
指
针
来
访问
指
针
所指向的内存区
时
,
指
针
所指向的
类
型决定了
编译
器将把那片内存区里的内容当做什
么
来看待。
从
语
法上看
,
你只
须
把指
针
声明
语
句中的指
针
名字和名字左
边
的指
针
声明符
*
去掉
,
剩下的就是指
针
所指向的
类
型。例如
:
(1)int *ptr; //
指
针
所指向的
类
型是
int
(2)char *ptr; // 指 针 所指向的的 类 型 是 char
(3)int **ptr; // 指 针 所指向的的 类 型是 int *
(4)int (*ptr)[3]; // 指 针 所指向的的 类 型是 int()[3]
(5)int *(*ptr)[4]; // 指 针 所指向的的 类 型是 int *()[4]
(2)char *ptr; // 指 针 所指向的的 类 型 是 char
(3)int **ptr; // 指 针 所指向的的 类 型是 int *
(4)int (*ptr)[3]; // 指 针 所指向的的 类 型是 int()[3]
(5)int *(*ptr)[4]; // 指 针 所指向的的 类 型是 int *()[4]
在指
针
的算
术
运算中
,
指
针
所指向的
类
型有很大的作用。
指
针
的
类
型
(
即指
针
本身的
类
型
)
和指
针
所指向的
类
型是两个概念。当你
对
C
越来越熟悉
时
,
你会
发现
,
把与指
针搅
和在一起的
"
类
型
"
这
个概念分成
"
指
针
的
类
型
"
和
"
指
针
所指向的
类
型
"
两个概念
,
是精通指
针
的
关键
点之一。我看
了不少
书
,
发现
有些写得差的
书
中
,
就把指
针
的
这
两个概念
搅
在一起了
,
所以看起
书
来前后矛盾
,
越看越糊涂。
3
。
指
针
的
值
,
或者叫指
针
所指向的内存区或地址。
指 针 的 值 是指 针 本身存 储 的数 值 , 这 个 值 将被 编译 器当作一个地址 , 而不是一个一般的数 值 。在 32 位程序里 , 所有 类 型的指 针 的 值 都是一个 32 位整数 , 因 为 32 位程序里内存地址全都是 32 位 长 。
指 针 的 值 是指 针 本身存 储 的数 值 , 这 个 值 将被 编译 器当作一个地址 , 而不是一个一般的数 值 。在 32 位程序里 , 所有 类 型的指 针 的 值 都是一个 32 位整数 , 因 为 32 位程序里内存地址全都是 32 位 长 。
指
针
所指向的内存区就是从指
针
的
值
所代表的那个内存地址
开
始
,
长
度
为
sizeof(
指
针
所指向的
类
型
)
的一片内存区。以后
,
我
们说
一个指
针
的
值
是
XX,
就相当于
说该
指
针
指向了以
XX
为
首地址的一片内存区域;我
们说
一个指
针
指向了某
块
内存区域
,
就相当于
说该
指
针
的
值
是
这块
内存区域的首地址。
指
针
所指向的内存区和指
针
所指向的
类
型是两个完全不同的概念。在例一中
,
指
针
所指向的
类
型已
经
有了
,
但由于指
针还
未初始化
,
所以它所指向的内存区是不存在的
,
或者
说
是无意
义
的。
以后
,
每
遇到一个指
针
,
都
应该问问
:
这
个指
针
的
类
型是什
么
?
指
针
指向的
类
型是什
么
?
该
指
针
指向了哪里
?
4
。
指
针
本身所占据的内存区。
指 针 本身占了多大的内存 ? 你只要用函数 sizeof( 指 针 的 类 型 ) 测 一下就知道了。在 32 位平台里 , 指 针 本身占据了 4 个字 节 的 长 度。 指 针 本身占据的内存 这 个概念在判断一个指 针 表达式是否是左 值时 很有用。
第二章。指 针 的算 术 运算
指 针 本身占了多大的内存 ? 你只要用函数 sizeof( 指 针 的 类 型 ) 测 一下就知道了。在 32 位平台里 , 指 针 本身占据了 4 个字 节 的 长 度。 指 针 本身占据的内存 这 个概念在判断一个指 针 表达式是否是左 值时 很有用。
第二章。指 针 的算 术 运算
指
针
可以加上或减去一个整数。指
针
的
这种
运算的意
义
和通常的数
值
的加减运算的意
义
是不一
样
的。
例如
:
例二 :
1 。 char a[20]; 2 。 int *ptr=a; 3 。 ptr++;
例二 :
1 。 char a[20]; 2 。 int *ptr=a; 3 。 ptr++;
在上例中
,
指
针
ptr
的
类
型
是
int*,
它指向的
类
型是
int,
它被初始化
为
指向整形
变
量
a
。
接下来的第
3
句中
,
指
针
ptr
被加了
1,
编译
器是
这样处
理的
:
它把指
针
ptr
的
值
加上了
sizeof(int),
在
32
位程序中
,
是被加上了
4
。
由于地址是用字
节
做
单
位的
,
故
ptr
所指向的地址由原来的
变
量
a
的地址向高地址方向增加了
4
个字
节
。
由于
char
类
型的
长
度是一个字
节
,
所以
,
原来
ptr
是指向数
组
a
的第
0
号
单
元
开
始的四
个字 节 , 此 时 指向了数 组 a 中从第 4 号 单 元 开 始的四个字 节 。
我 们 可以用一个指 针 和一个循 环 来遍 历 一个数 组 , 看例子 :
个字 节 , 此 时 指向了数 组 a 中从第 4 号 单 元 开 始的四个字 节 。
我 们 可以用一个指 针 和一个循 环 来遍 历 一个数 组 , 看例子 :
例三
:
int array[20]; int *ptr=array; // 此 处 略去 为 整型数 组赋值 的代 码 。
for(i=0;i<20;i++)
{
(*ptr)++;
ptr++ ;
}
这 个例子将整型数 组 中各个 单 元的 值 加 1 。由于 每 次循 环 都将指 针 ptr 加 1, 所以 每 次循 环 都能 访问 数 组 的下一个 单 元。再看例子 :
int array[20]; int *ptr=array; // 此 处 略去 为 整型数 组赋值 的代 码 。
for(i=0;i<20;i++)
{
(*ptr)++;
ptr++ ;
}
这 个例子将整型数 组 中各个 单 元的 值 加 1 。由于 每 次循 环 都将指 针 ptr 加 1, 所以 每 次循 环 都能 访问 数 组 的下一个 单 元。再看例子 :
例四
:
1 。 char a[20]; 2 。 int *ptr=a; 3 。 ptr+=5;
1 。 char a[20]; 2 。 int *ptr=a; 3 。 ptr+=5;
在
这
个例子中
,ptr
被加上了
5,
编译
器是
这样处
理的
:
将指
针
ptr
的
值
加上
5
乘
sizeof(int),
在
32
位程序中就是加上了
5
乘
4=20
。由于地址的
单
位是字
节
,
故
现
在的
ptr
所指向的地址比起加
5
后的
ptr
所指向的地址来
说
,
向高地址方向移
动
了
20
个字
节
。在
这
个例子中
,
没加
5
前的
ptr
指向数
组
a
的第
0
号
单
元
开
始的四个字
节
,
加
5
后
,ptr
已
经
指向了数
组
a
的合法范
围
之外了。
虽
然
这种
情况在
应
用上会出
问题
,
但在
语
法上却是可以的。
这
也体
现
出了指
针
的灵活性。
如果上例中
,ptr
是被减去
5,
那
么处
理
过
程大同小异
,
只不
过
ptr
的
值
是被减去
5
乘
sizeof(int),
新的
ptr
指向的地址将比原来的
ptr
所指向的地址向低地址方向移
动
了
20
个字
节
。
总结
一下
,
一个指
针
ptrold
加上一个整数
n
后
,
结
果是一个新的指
针
ptrnew,ptrnew
的
类
型和
ptrold
的
类
型相同
,ptrnew
所指向的
类
型和
ptrold
所指向的
类
型也相同。
ptrnew
的
值
将比
ptrold
的
值
增加了
n
乘
sizeof(ptrold
所指向的
类
型
)
个字
节
。
就是
说
,ptrnew
所指向的内存区将比
ptrold
所指向的内存区向高地址方向移
动
了
n
乘
sizeof(ptrold
所指向的
类
型
)
个字
节
。
一个指
针
ptrold
减去一个整数
n
后
,
结
果是一个新的指
针
ptrnew,ptrnew
的
类
型和
ptrold
的
类
型相同
,ptrnew
所指向的
类
型和
ptrold
所指向的
类
型也相同。
ptrnew
的
值
将比
ptrold
的
值
减少了
n
乘
sizeof(ptrold
所指向的
类
型
)
个字
节
,
就是
说
,ptrnew
所指向的内存区将比
ptrold
所指向的内存区向低地址方向移
动
了
n
乘
sizeof(ptrold
所指向的
类
型
)
个字
节
。
第三章。运算符
&
和
*
这
里
&
是取地址运算符
,*
是
...
书
上叫做
"
间
接运算符
"
。
&a
的运算
结
果是一个指
针
,
指
针
的
类
型是
a
的
类
型加个
*,
指
针
所指向的
类
型是
a
的
类
型
,
指
针
所指向的地址嘛
,
那就是
a
的地址。
*p
的运算
结
果就五花八
门
了。
总
之
*p
的
结
果是
p
所指向的
东
西
,
这
个
东
西有
这
些特点
:
它的
类
型是
p
指向的
类
型
,
它所占用的地址是
p
所指向的地址。
例五
:
int a=12; int b; int *p; int **ptr;
p=&//&a 的 结 果是一个指 针 , 类 型是 int*, 指向的 类 型是 int, 指向的地址是 a 的地址。
*p=24;//*p 的 结 果 , 在 这 里它的 类 型是 int, 它所占用的地址是 p 所指向的地址 , 显 然 ,*p 就是 变 量 a 。
ptr=&//&p 的 结 果是个指 针 , 该 指 针 的 类 型是 p 的 类 型加个 *, 在 这 里是 int** 。 该 指 针 所指向的 类 型是 p 的 类 型 , 这 里是 int* 。 该 指 针 所指向的地址就是指 针 p 自己的地址。
*ptr=&b//*ptr 是个指 针 ,&b 的 结 果也是个指 针 , 且 这 两个指 针 的 类 型和所指向的 类 型是一 样 的 , 所以用 &b 来 给 *ptr 赋值 就是毫无 问题 的了。
**ptr=34;//*ptr 的 结 果是 ptr 所指向的 东 西 , 在 这 里是一个指 针 , 对这 个指 针 再做一次 * 运算 , 结 果就是一个 int 类 型的 变 量。
int a=12; int b; int *p; int **ptr;
p=&//&a 的 结 果是一个指 针 , 类 型是 int*, 指向的 类 型是 int, 指向的地址是 a 的地址。
*p=24;//*p 的 结 果 , 在 这 里它的 类 型是 int, 它所占用的地址是 p 所指向的地址 , 显 然 ,*p 就是 变 量 a 。
ptr=&//&p 的 结 果是个指 针 , 该 指 针 的 类 型是 p 的 类 型加个 *, 在 这 里是 int** 。 该 指 针 所指向的 类 型是 p 的 类 型 , 这 里是 int* 。 该 指 针 所指向的地址就是指 针 p 自己的地址。
*ptr=&b//*ptr 是个指 针 ,&b 的 结 果也是个指 针 , 且 这 两个指 针 的 类 型和所指向的 类 型是一 样 的 , 所以用 &b 来 给 *ptr 赋值 就是毫无 问题 的了。
**ptr=34;//*ptr 的 结 果是 ptr 所指向的 东 西 , 在 这 里是一个指 针 , 对这 个指 针 再做一次 * 运算 , 结 果就是一个 int 类 型的 变 量。
第四章。指
针
表达式。
一个表达式的最后 结 果如果是一个指 针 , 那 么这 个表达式就叫指 针 表达式。
下面是一些指 针 表达式的 例子 :
一个表达式的最后 结 果如果是一个指 针 , 那 么这 个表达式就叫指 针 表达式。
下面是一些指 针 表达式的 例子 :
例六
:
int a,b;
int array[10];
int *pa;
pa=&//&a 是一个指 针 表达式。
int **ptr=&//&pa 也是一个指 针 表达式。
*ptr=&//*ptr 和 &b 都是指 针 表达式。
pa=array;
pa++;// 这 也是指 针 表达式。
int a,b;
int array[10];
int *pa;
pa=&//&a 是一个指 针 表达式。
int **ptr=&//&pa 也是一个指 针 表达式。
*ptr=&//*ptr 和 &b 都是指 针 表达式。
pa=array;
pa++;// 这 也是指 针 表达式。
例七
:
char *arr[20];
char **parr=arr;// 如果把 arr 看作指 针 的 话 ,arr 也是指 针 表达式
char *str;
str=*parr;//*parr 是指 针 表达式
str=*(parr+1);//*(parr+1) 是指 针 表达式
str=*(parr+2);//*(parr+2) 是指 针 表达式
由于指 针 表达式的 结 果是一个指 针 , 所以指 针 表达式也具有指 针 所具有的四个要素 : 指 针 的 类 型 , 指 针 所指向的 类 型 , 指 针 指向的内存区 , 指 针 自身占据的内存。
char *arr[20];
char **parr=arr;// 如果把 arr 看作指 针 的 话 ,arr 也是指 针 表达式
char *str;
str=*parr;//*parr 是指 针 表达式
str=*(parr+1);//*(parr+1) 是指 针 表达式
str=*(parr+2);//*(parr+2) 是指 针 表达式
由于指 针 表达式的 结 果是一个指 针 , 所以指 针 表达式也具有指 针 所具有的四个要素 : 指 针 的 类 型 , 指 针 所指向的 类 型 , 指 针 指向的内存区 , 指 针 自身占据的内存。
好了
,
当一个指
针
表达式的
结
果指
针
已
经
明确地具有了指
针
自身占据的内存的
话
,
这
个指
针
表达式就是一个左
值
,
否
则
就不是一个左
值
。
在例七中
,&a
不是一个左
值
,
因
为
它
还
没有占据明确的内存。
*ptr
是一个左
值
,
因
为
*ptr
这
个指
针
已
经
占据了内存
,
其
实
*ptr
就是指
针
pa,
既然
pa
已
经
在内存中有了自己的位置
,
那
么
*ptr
当然也有了自己的位置。
第五章。数
组
和指
针
的
关
系
如果 对 声明数 组 的 语 句不太明白的 话 , 请 参 阅 我前段 时间贴 出的文章 << 如何理解 c 和 c++ 的 复杂类 型声明 >> 。
数 组 的数 组 名其 实 可以看作一个指 针 ? 聪吕 ?
例八 :
int array[10]={0,1,2,3,4,5,6,7,8,9},value;
value=array[0];// 也可写成 :value=*array;
value=array[3];// 也可写成 :value=*(array+3);
value=array[4];// 也可写成 :value=*(array+4);
如果 对 声明数 组 的 语 句不太明白的 话 , 请 参 阅 我前段 时间贴 出的文章 << 如何理解 c 和 c++ 的 复杂类 型声明 >> 。
数 组 的数 组 名其 实 可以看作一个指 针 ? 聪吕 ?
例八 :
int array[10]={0,1,2,3,4,5,6,7,8,9},value;
value=array[0];// 也可写成 :value=*array;
value=array[3];// 也可写成 :value=*(array+3);
value=array[4];// 也可写成 :value=*(array+4);
上例中
,
一般而言数
组
名
array
代表数
组
本身
,
类
型是
int [10],
但如果把
array
看做指
针
的
话
,
它指向数
组
的第
0
个
单
元
,
类
型是
int *,
所指向的
类
型是数
组单
元的
类
型即
int
。因此
*array
等于
0
就一点也不奇怪了。同理
,array+3
是一个指向数
组
第
3
个
单
元的指
针
,
所以
*(array+3)
等于
3
。其它依此
类
推。
例九
:
char *str[3]={ "Hello,this is a sample!","Hi,good morning.","Hello world" };
char s[80] ;
strcpy(s,str[0]);// 也可写成 strcpy(s,*str);
strcpy(s,str[1]);// 也可写成 strcpy(s,*(str+1));
strcpy(s,str[2]);// 也可写成 strcpy(s,*(str+2));
char *str[3]={ "Hello,this is a sample!","Hi,good morning.","Hello world" };
char s[80] ;
strcpy(s,str[0]);// 也可写成 strcpy(s,*str);
strcpy(s,str[1]);// 也可写成 strcpy(s,*(str+1));
strcpy(s,str[2]);// 也可写成 strcpy(s,*(str+2));
上例中
,str
是一个三
单
元的数
组
,
该
数
组
的
每
个
单
元都是一个指
针
,
这
些指
针
各指向一个字符串。把指
针
数
组
名
str
当作一个指
针
的
话
,
它指向数
组
的第
0
号
单
元
,
它的
类
型是
char**,
它指向的
类
型是
char *
。
*str 也是一个指 针 , 它的 类 型是 char*, 它所指向的 类 型是 char, 它指向的地址是字符串 "Hello,this is a sample!" 的第一个字符的地址 , 即 'H' 的地址。
str+1 也是一个指 针 , 它指向数 组 的第 1 号 单 元 , 它的 类 型是 char**, 它指向的 类 型是 char * 。
*(str+1) 也是一个指 针 , 它的 类 型是 char*, 它所指向的 类 型是 char, 它指向 "Hi,good morning." 的第一个字符 'H', 等等。
*str 也是一个指 针 , 它的 类 型是 char*, 它所指向的 类 型是 char, 它指向的地址是字符串 "Hello,this is a sample!" 的第一个字符的地址 , 即 'H' 的地址。
str+1 也是一个指 针 , 它指向数 组 的第 1 号 单 元 , 它的 类 型是 char**, 它指向的 类 型是 char * 。
*(str+1) 也是一个指 针 , 它的 类 型是 char*, 它所指向的 类 型是 char, 它指向 "Hi,good morning." 的第一个字符 'H', 等等。
下面
总结
一下数
组
的数
组
名的
问题
。声
明了一个数
组
TYPE array[n],
则
数
组
名称
array
就有了两重含
义
:
第一
,
它代表整个数
组
,
它的
类
型是
TYPE [n]
;第二
,
它是一个指
针
,
该
指
针
的
类
型是
TYPE*,
该
指
针
指向的
类
型是
TYPE,
也就是数
组单
元的
类
型
,
该
指
针
指向的内存区就是数
组
第
0
号
单
元
,
该
指
针
自己占有
单
独的内存区
,
注意它和数
组
第
0
号
单
元占据的内存区是不同的。
该
指
针
的
值
是不能修改的
,
即
类
似
array++
的表达式是
错误
的。
在不同的表达式中数
组
名
array
可以扮演不同的角色。
在表达式 sizeof(array) 中 , 数 组 名 array 代表数 组 本身 , 故 这时 sizeof 函数 测 出的是整个数 组 的大小。
在表达式 sizeof(array) 中 , 数 组 名 array 代表数 组 本身 , 故 这时 sizeof 函数 测 出的是整个数 组 的大小。
在表达式
*array
中
,array
扮演的是指
针
,
因此
这
个表达式的
结
果就是数
组
第
0
号
单
元的
值
。
sizeof(*array)
测
出的是数
组单
元的大小。
表达式 array+n( 其中 n=0,1,2,.... 。 ) 中 ,array 扮演的是指 针 , 故 array+n 的 结 果是一个指 针 , 它的 类 型是 TYPE*, 它指向的 类 型是 TYPE, 它指向数 组 第 n 号 单 元。故 sizeof(array+n) 测 出的是指 针类 型的大小。
表达式 array+n( 其中 n=0,1,2,.... 。 ) 中 ,array 扮演的是指 针 , 故 array+n 的 结 果是一个指 针 , 它的 类 型是 TYPE*, 它指向的 类 型是 TYPE, 它指向数 组 第 n 号 单 元。故 sizeof(array+n) 测 出的是指 针类 型的大小。
例十
:
int array[10];
int (*ptr)[10];
ptr=&
上例中 ptr 是一个指 针 , 它的 类 型是 int (*)[10], 他指向的 类 型是 int [10], 我 们 用整个数 组 的首地址来初始化它。在 语 句 ptr=&array 中 ,array 代表数 组 本身。
本 节 中提到了函数 sizeof(), 那 么 我来 问 一 问 ,sizeof( 指 针 名称 ) 测 出的究竟是指 针 自身 类 型的大小呢 还 是指 针 所指向的 类 型的大小 ? 答案是前者。例如 :
int (*ptr)[10];
则 在 32 位程序中 , 有 :
sizeof(int(*)[10])==4
sizeof(int [10])==40
sizeof(ptr)==4
实际 上 ,sizeof( 对 象 ) 测 出的都是 对 象自身的 类 型的大小 , 而不是 别 的什 么类 型的大小。
int array[10];
int (*ptr)[10];
ptr=&
上例中 ptr 是一个指 针 , 它的 类 型是 int (*)[10], 他指向的 类 型是 int [10], 我 们 用整个数 组 的首地址来初始化它。在 语 句 ptr=&array 中 ,array 代表数 组 本身。
本 节 中提到了函数 sizeof(), 那 么 我来 问 一 问 ,sizeof( 指 针 名称 ) 测 出的究竟是指 针 自身 类 型的大小呢 还 是指 针 所指向的 类 型的大小 ? 答案是前者。例如 :
int (*ptr)[10];
则 在 32 位程序中 , 有 :
sizeof(int(*)[10])==4
sizeof(int [10])==40
sizeof(ptr)==4
实际 上 ,sizeof( 对 象 ) 测 出的都是 对 象自身的 类 型的大小 , 而不是 别 的什 么类 型的大小。
第六章。指
针
和
结
构
类
型的
关
系
可以声明一个指向
结
构
类
型
对
象的指
针
。
例十一 :
struct MyStruct
{ int a;
int b;
int c;
}
MyStruct ss={20,30,40};// 声明了 结 构 对 象 ss, 并把 ss 的三个成 员 初始化 为 20,30 和 40 。
MyStruct *ptr=&// 声明了一个指向 结 构 对 象 ss 的指 针 。 它的 类 型是 MyStruct*, 它指向的 类 型是 MyStruct 。
例十一 :
struct MyStruct
{ int a;
int b;
int c;
}
MyStruct ss={20,30,40};// 声明了 结 构 对 象 ss, 并把 ss 的三个成 员 初始化 为 20,30 和 40 。
MyStruct *ptr=&// 声明了一个指向 结 构 对 象 ss 的指 针 。 它的 类 型是 MyStruct*, 它指向的 类 型是 MyStruct 。
int *pstr=(int*)&//
声明了一个指向
结
构
对
象
ss
的指
针
。但是它的
类
型和它指向的
类
型和
ptr
是不同的。
请问
怎
样
通
过
指
针
ptr
来
访问
ss
的三个成
员变
量
?
答案
: ptr->a; ptr->b; ptr->c;
又
请问
怎
样
通
过
指
针
pstr
来
访问
ss
的三个成
员变
量
?
答案
: *pstr
;
//
访问
了
ss
的成
员
a
。
*(pstr+1);//
访问
了
ss
的成
员
b
。
*(pstr+2)//
访问
了
ss
的成
员
c
。
呵呵
,
虽
然我在我的
MSVC++6.0
上
调
式
过
上述代
码
,
但是要知道
,
这样
使用
pstr
来
访问结
构成
员
是不正
规
的
,
为
了
说
明
为
什
么
不正
规
,
让
我
们
看看怎
样
通
过
指
针
来
访问
数
组
的各个
单
元
:
例十二
:
int array[3]={35,56,37};
int *pa=array;
通 过 指 针 pa 访问 数 组 array 的三个 单 元的方法是 :
*pa;// 访问 了第 0 号 单 元
*(pa+1);// 访问 了第 1 号 单 元
*(pa+2);// 访问 了第 2 号 单 元
int array[3]={35,56,37};
int *pa=array;
通 过 指 针 pa 访问 数 组 array 的三个 单 元的方法是 :
*pa;// 访问 了第 0 号 单 元
*(pa+1);// 访问 了第 1 号 单 元
*(pa+2);// 访问 了第 2 号 单 元
从格式上看倒是与通
过
指
针访问结
构成
员
的不正
规
方法的格式一
样
。
所有的
C/C++
编译
器在排列数
组
的
单
元
时
,
总
是把各个数
组单
元存放在
连续
的存
储
区里
,
单
元和
单
元之
间
没有空隙。但在存放
结
构
对
象的各个成
员时
,
在某
种编译环
境下
,
可能会需要字
对齐
或双字
对齐
或者是
别
的什
么对齐
,
需要在相
邻
两个成
员
之
间
加若干
"
填充字
节
",
这
就
导
致各个成
员
之
间
可能会有若干个字
节
的空隙。
所以
,
在例十二中
,
即使
*pstr
访问
到了
结
构
对
象
ss
的第一个成
员变
量
a,
也不能保
证
*(pstr+1)
就一定能
访问
到
结
构成
员
b
。因
为
成
员
a
和成
员
b
之
间
可能会有若干填充字
节
,
说
不定
*(pstr+1)
就正好
访问
到了
这
些填充字
节
呢。
这
也
证
明了指
针
的灵活性。要是你的目
的就是想看看各个
结
构成
员
之
间
到底有没有填充字
节
,
嘿
,
这
倒是个不
错
的方法。
通
过
指
针访问结
构成
员
的正确方法
应该
是象例十二中使用指
针
ptr
的方法。
第七章。指
针
和函数的
关
系
可以把一个指
针
声明成
为
一个指向函数的指
针
。
int fun1(char*,int);
int (*pfun1)(char*,int);
pfun1=fun1;
int a=(*pfun1)("abcdefg",7);// 通 过 函数指 针 饔煤 ?
可以把指 针 作 为 函数的形参。在函数 调 用 语 句中 , 可以用指 针 表达式来作 为 实 参。
int fun1(char*,int);
int (*pfun1)(char*,int);
pfun1=fun1;
int a=(*pfun1)("abcdefg",7);// 通 过 函数指 针 饔煤 ?
可以把指 针 作 为 函数的形参。在函数 调 用 语 句中 , 可以用指 针 表达式来作 为 实 参。
例十三
:
int fun(char*);
int a;
char str[]="abcdefghijklmn";
a=fun(str);
int fun(char*s)
{ int num=0;
for(int i=0;i
{ num+=*s;s++; }
return num;
)
int fun(char*);
int a;
char str[]="abcdefghijklmn";
a=fun(str);
int fun(char*s)
{ int num=0;
for(int i=0;i
{ num+=*s;s++; }
return num;
)
这
个例子中的函数
fun
统计
一个字符串中各个字符的
ASCII
码值
之和。前面
说
了
,
数
组
的名字也是一个指
针
。在函数
调
用中
,
当把
str
作
为实
参
传递给
形参
s
后
,
实际
是把
str
的
值传递给
了
s,s
所指向的地址就和
str
所指向的地址一致
,
但是
str
和
s
各自占用各自的存
储
空
间
。在函数体内
对
s
进
行自加
1
运算
,
并不意味着同
时对
str
进
行了自加
1
运算。
第八章。指
针类
型
转换
当我
们
初始化一个指
针
或
给
一个指
针赋值时
,
赋值
号的左
边
是一个指
针
,
赋值
号的右
边
是一个指
针
表达式。在我
们
前面所
举
的例子中
,
绝
大多数情况下
,
指
针
的
类
型和指
针
表达式的
类
型是一
样
的
,
指
针
所指向的
类
型和指
针
表达式所指向的
类
型是一
样
的。
例十四
:
1 。 float f=12.3;
2 。 float *fptr=&
3 。 int *p;
在上面的例子中 , 假如我 们 想 让 指 针 p 指向 实 数 f, 应该 怎 么 搞 ? 是用下面的 语 句 吗 ?
p=&
不 对 。因 为 指 针 p 的 类 型是 int*, 它指向的 类 型是 int 。表达式 &f 的 结 果是一个指 针 , 指 针 的 类 型是 float*, 它指向的 类 型是 float 。两者不一致 , 直接 赋值 的方法是不行的。至少在我的 MSVC++6.0 上 , 对 指 针 的 赋值语 句要求 赋值 号两 边 的 类 型一致 , 所指向的 类 型也一致 , 其它的 编译 器上我没 试过 , 大家可以 试试 。 为 了 实现 我 们 的目的 , 需要 进 行 " 强 制 类 型 转换 ":
p=(int*)&
如果有一个指 针 p, 我 们 需要把它的 类 型和所指向的 类 型改 为 TYEP* 和 TYPE, 那 么语 法格式是 : (TYPE*)p ;
这样强 制 类 型 转换 的 结 果是一个新指 针 , 该 新指 针 的 类 型是 TYPE*, 它指向的 类 型是 TYPE, 它指向的地址就是原指 针 指向的地址。而原来的指 针 p 的一切属性都没有被修改。
一个函数如果使用了指 针 作 为 形参 , 那 么 在函数 调 用 语 句的 实 参和形参的 结 合 过 程中 , 也会 发 生指 针类 型的 转换 。
1 。 float f=12.3;
2 。 float *fptr=&
3 。 int *p;
在上面的例子中 , 假如我 们 想 让 指 针 p 指向 实 数 f, 应该 怎 么 搞 ? 是用下面的 语 句 吗 ?
p=&
不 对 。因 为 指 针 p 的 类 型是 int*, 它指向的 类 型是 int 。表达式 &f 的 结 果是一个指 针 , 指 针 的 类 型是 float*, 它指向的 类 型是 float 。两者不一致 , 直接 赋值 的方法是不行的。至少在我的 MSVC++6.0 上 , 对 指 针 的 赋值语 句要求 赋值 号两 边 的 类 型一致 , 所指向的 类 型也一致 , 其它的 编译 器上我没 试过 , 大家可以 试试 。 为 了 实现 我 们 的目的 , 需要 进 行 " 强 制 类 型 转换 ":
p=(int*)&
如果有一个指 针 p, 我 们 需要把它的 类 型和所指向的 类 型改 为 TYEP* 和 TYPE, 那 么语 法格式是 : (TYPE*)p ;
这样强 制 类 型 转换 的 结 果是一个新指 针 , 该 新指 针 的 类 型是 TYPE*, 它指向的 类 型是 TYPE, 它指向的地址就是原指 针 指向的地址。而原来的指 针 p 的一切属性都没有被修改。
一个函数如果使用了指 针 作 为 形参 , 那 么 在函数 调 用 语 句的 实 参和形参的 结 合 过 程中 , 也会 发 生指 针类 型的 转换 。
例十五
:
void fun(char*);
int a=125,b;
fun((char*)&a);
void fun(char*s)
{ char c;
c=*(s+3);*(s+3)=*(s+0);*(s+0)=c;
c=*(s+2);*(s+2)=*(s+1);*(s+1)=c;
}
}
void fun(char*);
int a=125,b;
fun((char*)&a);
void fun(char*s)
{ char c;
c=*(s+3);*(s+3)=*(s+0);*(s+0)=c;
c=*(s+2);*(s+2)=*(s+1);*(s+1)=c;
}
}
注意
这
是一个
32
位程序
,
故
int
类
型占了四个字
节
,char
类
型占一个字
节
。函数
fun
的作用是把一个整数的四个字
节
的
顺
序来个
颠
倒。注意到了
吗
?
在函数
调
用
语
句中
,
实
参
&a
的
结
果是一个指
针
,
它的
类
型是
int *,
它指向的
类
型是
int
。形参
这
个指
针
的
类
型是
char*,
它指向的
类
型是
char
。
这样
,
在
实
参和形参的
结
合
过
程中
,
我
们
必
须进
行一次从
int*
类
型到
char*
类
型的
转换
。
结
合
这
个例子
,
我
们
可以
这样
来想象
编译
器
进
行
转换
的
过
程
:
编译
器先构造一个
临时
指
针
char*temp,
然后
执
行
temp=(char*)&a,
最后再把
temp
的
值传递给
s
。所以最后的
结
果是
:s
的
类
型是
char*,
它指向的
类
型是
char,
它指向的地址就是
a
的首地址。
我
们
已
经
知道
,
指
针
的
值
就是指
针
指向的地址
,
在
32
位程序中
,
指
针
的
值
其
实
是一个
32
位整数。那可不可以把一个整数当作指
针
的
值
直接
赋给
指
针
呢
?
就象下面的
语
句
:
unsigned int a;
TYPE *ptr;//TYPE 是 int,char 或 结 构 类 型等等 类 型。
a=20345686;
ptr=20345686;// 我 们 的目的是要使指 针 ptr 指向地址 20345686( 十 进 制 )
ptr=a;// 我 们 的目的是要使指 针 ptr 指向地址 20345686( 十 进 制 )
编译 一下吧。 结 果 发现 后面两条 语 句全是 错 的。那 么 我 们 的目的就不能达到了 吗 ? 不 , 还 有 办 法 :
unsigned int a;
TYPE *ptr;//TYPE 是 int,char 或 结 构 类 型等等 类 型。
a= 某个数 , 这 个数必 须 代表一个合法的地址;
ptr=(TYPE*)a ; // 呵呵 , 这 就可以了。
严 格 说 来 这 里的 (TYPE*) 和指 针类 型 转换 中的 (TYPE*) 还 不一 样 。 这 里的 (TYPE*) 的意思是把无符号整数 a 的 值 当作一个地址来看待。
unsigned int a;
TYPE *ptr;//TYPE 是 int,char 或 结 构 类 型等等 类 型。
a=20345686;
ptr=20345686;// 我 们 的目的是要使指 针 ptr 指向地址 20345686( 十 进 制 )
ptr=a;// 我 们 的目的是要使指 针 ptr 指向地址 20345686( 十 进 制 )
编译 一下吧。 结 果 发现 后面两条 语 句全是 错 的。那 么 我 们 的目的就不能达到了 吗 ? 不 , 还 有 办 法 :
unsigned int a;
TYPE *ptr;//TYPE 是 int,char 或 结 构 类 型等等 类 型。
a= 某个数 , 这 个数必 须 代表一个合法的地址;
ptr=(TYPE*)a ; // 呵呵 , 这 就可以了。
严 格 说 来 这 里的 (TYPE*) 和指 针类 型 转换 中的 (TYPE*) 还 不一 样 。 这 里的 (TYPE*) 的意思是把无符号整数 a 的 值 当作一个地址来看待。
上面
强调
了
a
的
值
必
须
代表一个合法的地址
,
否
则
的
话
,
在你使用
ptr
的
时
候
,
就会出
现
非法操作
错误
。
想想能不能反
过
来
,
把指
针
指向的地址即指
针
的
值
当作一个整数取出来。完全可以。下面的例子演示了把一个指
针
的
值
当作一个整数取出来
,
然后再把
这
个整数当作一个地址
赋给
一个指
针
:
例十六
:
int a=123,b;
int *ptr=&
char *str;
b=(int)ptr;// 把指 针 ptr 的 值 当作一个整数取出来。
str=(char*)b;// 把 这 个整数的 值 当作一个地址 赋给 指 针 str 。
int a=123,b;
int *ptr=&
char *str;
b=(int)ptr;// 把指 针 ptr 的 值 当作一个整数取出来。
str=(char*)b;// 把 这 个整数的 值 当作一个地址 赋给 指 针 str 。
好了
,
现
在我
们
已
经
知道了
,
可以把指
针
的
值
当作一个整数取出来
,
也可以把一个整数
值
当作地址
赋给
一个指
针
。
第九章。指
针
的安全
问题
看下面的例子 :
例十七 :
char s='a';
int *ptr;
ptr=(int*)&
*ptr=1298 ; 指 针 ptr 是一个 int* 类 型的指 针 , 它指向的 类 型是 int 。 它指向的地址就是 s 的首地址。 在 32 位程序中 ,s 占一个字 节 ,int 类 型占四个字 节 。最后一 条 语 句不但改 变 了 s 所占的一个字 节 , 还 把和 s 相 临 的高地址方向的三个字 节 也改 变 了。 这 三个字 节 是干什 么 的 ? 只有 编译 程序知道 , 而写程序的人是不太可能知道的。也 许这 三个字 节 里存 储 了非常重要的数据 , 也 许这 三个字 节 里正好是程序的一条代 码 , 而由于你 对 指 针 的 马 虎 应 用 , 这 三个字 节 的 值 被改 变 了 , 这 会造成崩 溃 性的 错误 。
看下面的例子 :
例十七 :
char s='a';
int *ptr;
ptr=(int*)&
*ptr=1298 ; 指 针 ptr 是一个 int* 类 型的指 针 , 它指向的 类 型是 int 。 它指向的地址就是 s 的首地址。 在 32 位程序中 ,s 占一个字 节 ,int 类 型占四个字 节 。最后一 条 语 句不但改 变 了 s 所占的一个字 节 , 还 把和 s 相 临 的高地址方向的三个字 节 也改 变 了。 这 三个字 节 是干什 么 的 ? 只有 编译 程序知道 , 而写程序的人是不太可能知道的。也 许这 三个字 节 里存 储 了非常重要的数据 , 也 许这 三个字 节 里正好是程序的一条代 码 , 而由于你 对 指 针 的 马 虎 应 用 , 这 三个字 节 的 值 被改 变 了 , 这 会造成崩 溃 性的 错误 。
让
我
们
再来看一例
:
例十八
:
1 。 char a;
2 。 int *ptr=&
3 。 ptr++;
4 。 *ptr=115;
1 。 char a;
2 。 int *ptr=&
3 。 ptr++;
4 。 *ptr=115;
该
例子完全可以通
过编译
,
并能
执
行。但是看到没有
?
第
3
句
对
指
针
ptr
进
行自加
1
运算后
,ptr
指向了和整形
变
量
a
相
邻
的高地址方向的一
块
存
储
区。
这块
存
储
区里是什
么
?
我
们
不知道。有可能它是一个非常重要的数据
,
甚至可能是一条代
码
。而第
4
句竟然往
这
片存
储
区里写入一个数据
,
这
是
严
重的
错误
。所以在使用指
针时
,
程序
员
心里必
须
非常清楚
:
我的指
针
究竟指向了哪里。
在用指
针访问
数
组
的
时
候
,
也要注意不要超出数
组
的低端和高端界限
,
否
则
也会造成
类
似的
错误
。
在指
针
的
强
制
类
型
转换
:ptr1=(TYPE*)ptr2
中
,
如果
sizeof(ptr2
的
类
型
)
大于
sizeof(ptr1
的
类
型
),
那
么
在使用指
针
ptr1
来
访问
ptr2
所指向的存
储
区
时
是安全的。如果
sizeof(ptr2
的
类
型
)
小于
sizeof(ptr1
的
类
型
),
那
么
在使用指
针
ptr1
来
访问
ptr2
所指向的存
储
区
时
是不安全的。
至于
为
什
么
,
读
者
结
合例十七来想一想
,
应该
会明白的
。