c和指针

 
初学者服 是我的帖子的宗旨。我也是个初学者 ( 强调 了无数遍了 ), 我以我的理解把初学者 懂的 西用浅 言写出来。由于小学 时语 文没学好 , 所以竭尽全力也未必能达到 个目的。尽力而 吧。
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 型。
法的角度看 , 你只要把指 声明 句里的指 名字去掉 , 剩下的部分就是 个指 型。 是指 本身所具有的 型。 看看例一中各个指
(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 。指 所指向的 型。
当你通 访问 所指向的内存区 , 所指向的 型决定了 编译 器将把那片内存区里的内容当做什 来看待。 法上看 , 你只 把指 声明 句中的指 名字和名字左 的指 声明符 * 去掉 , 剩下的就是指 所指向的 型。例如 :
(1)int *ptr; // 所指向的 型是 int
(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
所指向的内存区就是从指 所代表的那个内存地址 , sizeof( 所指向的 ) 的一片内存区。以后 , 们说 一个指 XX, 就相当于 说该 指向了以 XX 首地址的一片内存区域;我 们说 一个指 指向了某 内存区域 , 就相当于 说该 这块 内存区域的首地址。
所指向的内存区和指 所指向的 型是两个完全不同的概念。在例一中 , 所指向的 型已 有了 , 但由于指 针还 未初始化 , 所以它所指向的内存区是不存在的 , 或者 是无意 的。
以后 , 遇到一个指 , 应该问问 : 个指 型是什 ? 指向的 型是什 ? 指向了哪里 ?
4 本身所占据的内存区。
本身占了多大的内存 ? 你只要用函数 sizeof( ) 一下就知道了。在 32 位平台里 , 本身占据了 4 个字 度。 本身占据的内存 个概念在判断一个指 表达式是否是左 值时 很有用。
第二章。指 的算 运算
可以加上或减去一个整数。指 这种 运算的意 和通常的数 的加减运算的意 是不一 的。 例如 :
例二 :
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 始的四个字
可以用一个指 和一个循 来遍 一个数 , 看例子 :
例三 :
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;
个例子中 ,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,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)
是指 表达式
由于指 表达式的 果是一个指 , 所以指 表达式也具有指 所具有的四个要素 : , 所指向的 , 指向的内存区 , 自身占据的内存。
好了 , 当一个指 表达式的 果指 明确地具有了指 自身占据的内存的 , 个指 表达式就是一个左 , 就不是一个左
在例七中 ,&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);
上例中 , 一般而言数 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));
上例中 ,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', 等等。
下面 总结 一下数 的数 名的 问题 。声 明了一个数 TYPE array[n], 名称 array 就有了两重含 : 第一 , 它代表整个数 , 它的 型是 TYPE [n] ;第二 , 它是一个指 , 型是 TYPE*, 指向的 型是 TYPE, 也就是数 组单 元的 , 指向的内存区就是数 0 , 自己占有 独的内存区 , 注意它和数 0 元占据的内存区是不同的。 是不能修改的 , array++ 的表达式是 错误 的。
在不同的表达式中数 array 可以扮演不同的角色。
在表达式 sizeof(array) , array 代表数 本身 , 这时 sizeof 函数 出的是整个数 的大小。
在表达式 *array ,array 扮演的是指 , 因此 个表达式的 果就是数 0 元的 sizeof(*array) 出的是数 组单 元的大小。
表达式 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( ) 出的都是 象自身的 型的大小 , 而不是 的什 么类 型的大小。
第六章。指 型的
可以声明一个指向 象的指
例十一 :
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
从格式上看倒是与通 针访问结 构成 的不正 方法的格式一
所有的 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 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 的一切属性都没有被修改。
一个函数如果使用了指 形参 , 在函数 句的 参和形参的 程中 , 也会 生指 针类 型的 转换
例十五 :
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 当作一个地址来看待。
上面 强调 a 代表一个合法的地址 , , 在你使用 ptr , 就会出 非法操作 错误
想想能不能反 , 把指 指向的地址即指 当作一个整数取出来。完全可以。下面的例子演示了把一个指 当作一个整数取出来 , 然后再把 个整数当作一个地址 赋给 一个指 :
例十六 :
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 的高地址方向的三个字 也改 了。 三个字 是干什 ? 只有 编译 程序知道 , 而写程序的人是不太可能知道的。也 许这 三个字 里存 了非常重要的数据 , 许这 三个字 里正好是程序的一条代 , 而由于你 , 三个字 被改 , 会造成崩 性的 错误
再来看一例 : 例十八 :
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 所指向的存 是不安全的。 至于 , 合例十七来想一想 , 应该 会明白的
 

你可能感兴趣的:(C++,c,struct,basic,float,fun)