Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1676190
  • 博文数量: 585
  • 博客积分: 14610
  • 博客等级: 上将
  • 技术积分: 7402
  • 用 户 组: 普通用户
  • 注册时间: 2008-05-15 10:52
文章存档

2013年(5)

2012年(214)

2011年(56)

2010年(66)

2009年(44)

2008年(200)

分类: C/C++

2009-04-28 11:57:11

C语言指针问题快速解惑


1、
int a[3] = {2,5,8};
int* p = a;
把数组名赋值给指针表示将数组的首元素的地址赋予此指针。
2、
int a[3] = {2,5,8};
int* p = &a[0];
a[0]是a数组的首元素,而&则是取地址运算符,所以“ &a[0]”取得的同样是a数组的首元素的地址,因此这段代码的含义和代码段是一致的。
3、
char * c1 = "Hello";
char c2[6] = "World";
这两句不都是声明一个字符串吗?有什么区别吗?
"Hello"是一个字符串,然后让char指针c1指向这个字符串的首元素的地址。
第二句是声明一个长度为6的char类型数组,并且设置数组的初始值为"World"。注意末尾还有一个隐藏的“\0”,所以长度是6,而不是5。

4、数组指针的加减运算
对于指向数组的指针变量可以进行加减运算,比如对于指向数组的指针p,p++、p--、p+2等都是合法的。这里的加减运算并不是针对数组元素的,而是针对指针位置的,比如p当前指在首元素上,那么p++后就指在第二个元素上;比如p当前指在第5个元素上,那么p=p-2后,p就指在第3个元素上。
例子:
char * c1 = "Hello";
printf("%c\n",*c1);//输出H 
c1++;
printf("%c\n",*c1);//输出e

5、指针之间的运算
      两个指针之间可以进行减法运算,只有指向同一个数组的两个指针之间进行减法运算才有意义,而指向不同数组的两个指针之间进行减法运算则没有意义。为什么呢?
    指针其实就是内存中的地址,两个指针的减法运算计算的就是两个内存地址之间的元素的个数,比如整数指针p1指向内存地址2008H,整数指针p2内存地址2000H,而整数占4个字节,所以p1-p2的结果就是(2008H-2000H)/4=2,也就是 p1和p2 之间相差 2 个元素。很显然两个指针进行加法运算或者两个指向不同变量的指针进行减法运算都是没有意义的。
例子:
int a1[5]={3,5,6,8,2};
int* p1 = a1;//p1是指向第0个元素的地址 
int* p2 = &a1[3];//p2指向的是第3个元素的地址 
p1++;//p1指向了第1个元素 
printf("%d",p2-p1);//输出2

6、指针之间的大小比较
指向同一个数组的两个指针之间进行大小的比较是有意义的。比较结果为地址高低的比较:
例子:
int a1[5]={3,5,6,8,2};
int* p1 = a1;
int* p2 = &a1[3];
p1++;
printf("%d\n",p2 
p1=p1+2;//p1在数组内向高位移动两位 
printf("%d\n",p2==p1);//输出1 ,因为p1和p2的位相等

7、数组做为参数传递给函数
可以将数组做为传递给函数,比如下面的代码就是将传入输入的每个元素乘以2:
void makeDoule(int arr[],int len)
{
int i=0;
for(i=0;i
{
  arr[i]= arr[i]*2;
}
}
int main(int argc, char *argv[])
{
int a1[5]={3,5,6,8,2};
int i = 0;
int len=sizeof(a1)/sizeof(int);
makeDoule(a1,len);
for(i=0;i
{
  printf("%d ",a1[i]);
}
}
运行结果:
6 10 12 16 4传递给makeDoule函数的是a1,我们知道,一个数组的名字其实就是指向数组首元素的地址。所以makeDoule函数得到的arr就是数组a1的指针,那么在函数内部对arr进行操作的话就会改变a1数组。
所以makeDoule函数也可以写成下面的样子,这两个函数是等价的:
void makeDoule(int* arr,int len)
{
int i=0;
for(i=0;i
{
  arr[i]= arr[i]*2;
}
}
当然写成下面的形式更符合指针的使用习惯,因此推荐这种用法:
void makeDoule(int* arr)
{
int i=0;
for(i=0;i<5;i++)
{
  //arr+i指向的值设置为arr+i指向的值的二倍。
  *(arr+i)= *(arr+i)*2;
}
}
有的同学可能会问,为什么makeDoule函数还需要传递数组的长度len呢?makeDoule函数这样写不就行了吗?
void makeDoule(int* arr)
{
int i=0;
for(i=0;i<5;i++)
{
  arr[i]= arr[i]*2;
}
}
这样写在这个函数中是没有问题的。但是这个makeDoule函数不一定为只为a1服务,函数的最重要的特征是可以复用,也就是可以被其他地方调用,如果我们想用makeDoule函数为另外一个长度为20的数组进行“每个元素乘以2”的操作,那么将数组长度5写死在函数中就会有问题了。
那么为什么不在函数内部计算数组的长度呢?省得还传递一个len参数
void makeDoule(int arr[])
{
int i=0;
int len = sizeof(arr)/sizeof(int);
for(i=0;i
{
  arr[i]= arr[i]*2;
}
}
int main(int argc, char *argv[])
{
int a1[5]={3,5,6,8,2};
int i = 0;
int len=sizeof(a1)/sizeof(int);
makeDoule(a1);
for(i=0;i
{
  printf("%d ",a1[i]);
}
}
运行以后程序竟然输出了:6 5 6 8 2
只有第一个元素被“乘以2”。为什么呢?
运行下面的程序试一试:
void test(int arr[])
{
printf("%d\n",sizeof(arr)/sizeof(int));
}
int main(int argc, char *argv[])
{
int a1[5]={3,5,6,8,2};
printf("%d\n",sizeof(a1)/sizeof(int));
test(a1);
}
运行结果竟然是:
5
1为什么在main函数中计算数组的大小是5,在test函数中计算数组arr的大小就变成了1了呢?在C语言中可以通过sizeof的方式取得一个数组的尺寸,这是我们已经知道的。但是一旦把这个数组传递给函数的时候,到了函数内部使用的就是指向这个数组的指针了,虽然在test函数中arr声明的是数组,但是这里的arr只是一个指针而已了,arr本质上只是一个int类型的指针,而int类型的指针的大小是4,所以sizeof(arr)/sizeof(int)的结果就是1。这点是经常容易犯错的,需要特别注意。如果对指针还有什么不清楚的,可以到这个网址查询指针的学习资料:  ,指针是比较不好理解的,因此学习过程中要多试验,多思考,不要气馁。8、多维数组的指针
设有一个二维数组int a[3][4]={{0,1,2,3},{4,5,6,7},{8,9,10,11}} 
(1)C语言允许把一个二维数组分解为多个一维数组来处理。因此数组 a 可分解为三个一维数组,即 a[0],a[1],a[2]。每一个一维数组又含有四个元素。例如 a[0]数组,含有 a[0][0],a[0][1],a[0][2],a[0][3]四个元素。
(2)从二维数组的角度来看,a 是二维数组名,a 代表整个二维数组的首地址,也是二维数组 0 行的首地址。

特别注意a+1表示第1行的首地址,而不是第0行第第1列的地址,这是初学者最容易犯错的地方。
同样a[1]也是第1行一维数组的数组名和首地址。
所以a+1、*(a+1)、[1]是等价的。
(4)在二维数组中不能把&a[i]理解为元素 a[i]的地址,因为a[i]不是一个数组元素,a[i]是一种地址计算方法,它本身就表示数组 a 第 i 行首地址。所以&a[i]和 a[i]是等价的。这一点也是初学者最容易犯错的地方。
(5)从上边的分析我们得知a[0]+1是 a[0]的 1 号元素首地址,由此可得出 a[i]+j 则是一维数组 a[i]的 j 号元素首地址,它等于&a[i][j]。
由 a[i]=*(a+i)得 a[i]+j=*(a+i)+j。由于*(a+i)+j 是二维数组 a 的 i 行 j 列元素的首地址,所以,该元素的值等于*(*(a+i)+j)。
理解了下面的算法也就理解了多维数组的指针问题:
int main(int argc, char *argv[])
{
int a[3][4]={{0,1,2,3},{4,5,6,7},{8,9,10,11}};
printf("%d\n",*(*(a+1)+2));
printf("%d\n",*(a[1]+2));
}
程序输出如下:
6
6两个表达式都输出a[1][2]的值。*(a+1)则表示二维数组a的第1行,也就是等价于a[1]。a[1]、*(a+1)都表示数组的第1行。所以*(a[1]+2))和*(*(a+1)+2)都表示数组的第1行的第2个元素的值。
有的同学会问了,既然“*(a+1)”和“a+1”是等价的,为什么“printf("%d\n",*((a+1)+2)))”输出结果是错误的呢?
编 译器在编译“*((a+1)+2)))”的时候会把“(a+1)+2”优化成“a+3”,因此“*((a+1)+2)))”就变成了“*(a+3)”,也 就是a数组第3个一维数组的首地址,显然这个只是一个地址,并不是我们想像中的a[1][2]的值,所以输出了一个非常大的数。为了避免编译器的这种误 解,建议大家表示“二维数组a的第1行”的时候用a[1]或者*(a+1),而尽量不要用(a+1)因为很容易出错。

9、函数指针
    在C语言中,一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址。我们可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使该指针变量指向该函数。然后通过指针变量就可以找到并调用这个函数。我们把这种指向函数的指针变量称为“函数指针变量”。 
函数指针变量定义的一般形式为: 
函数的返回值的类型  (*指针变量名)(参数列表); 
其中“参数列表”可以省略,不过建议明确标明“参数列表”。
例子:
void PrintIt(int i)
{
printf("%d\n",i);
}
int main(int argc, char *argv[])
{
int i = 0;
int arr[5] = {3,5,8,2,1};
void (*myAction)(int);
myAction = PrintIt;
for(i=0;i
{
  myAction(arr[i]);
[/i]}
}
上面的程序遍历数组arr的所有元素,并且打印每个元素。
有的同学会说,这样做有什么意义吗?把“myAction(arr)”直接换成“PrintIt(arr)”不就得了吗?
这么替换在这里是非常合理,也是非常正确的,但是有一天我发现很多地方都要遍历数组做不同的事情,为了避免每次都写for循环,我将遍历数组的功能抽取到一个单独的公共函数中完成
void eachItem(int* pArray,int len,void (*action)(int))
{
int i = 0;
for(i=0;i
{
  //调用函数指针
  action(*(pArray+i));
}
}
注意函数eachItem的最后一个参数为函数指针类型。
这样main函数就可以简化成如下的样子了:
void PrintIt(int i)
{
printf("%d\n",i);
}
int main(int argc, char *argv[])
{
int arr[5] = {3,5,8,2,1};
int len = sizeof(arr)/sizeof(int);
eachItem(arr,len,PrintIt);
}
以后在其他的地方想对int数组做其他处理,那么只要写不同的函数就可以了,比如说要将数组中的奇数输出:
void PrintOdd(int i)
{
if((i%2)==1)
{
  printf("%d是奇数\n",i);
} 
}
在main函数中如下调用即可:
eachItem(arr,len,PrintOdd);
可以看到通过函数指针,将循环遍历算法和具体的处理算法隔离了,实现了代码的复用。
函数指针在编程中有非常多的应用。函数指针有时候又被称为回调,在事件机制、模板算法等场合有着广泛应用,因此一定要掌握好。MFC、STL等流行的框架中都大量的应用了函数指针,在的《C语言也能干大事》系列在线教学中也将进一步讲解函数指针的更生动的应用。
附录:本小节用到的代码:
void PrintIt(int i)
{
printf("%d\n",i);
}
void PrintOdd(int i)
{
if((i%2)==1)
{
  printf("%d是奇数\n",i);
} 
}
void eachItem(int* pArray,int len,void (*action)(int))
{
int i = 0;
for(i=0;i
{
  action(*(pArray+i));
}
}
int main(int argc, char *argv[])
{
int arr[5] = {3,5,8,2,1};
int len = sizeof(arr)/sizeof(int);
eachItem(arr,len,PrintIt);
eachItem(arr,len,PrintOdd);
}


10、指针数组、指针的指针。
指针数组的经典用途就是声明字符串数组:
  static char *name[]={"Monday", 
                        "Tuesday", 
                        "Wednesday", 
                        "Thursday", 
                        "Friday", 
                        "Saturday", 
                        "Sunday"}; 
这两部分请同学们参考教材。


11、结构指针
对结构的访问一般形式为: 
结构变量.成员名
    (*结构指针变量).成员名 
或为: 
    结构指针变量->成员名 
    应该注意(*pstu)两侧的括号不可少,因为成员符“.”的优先级高于“*”。
12、结构指针变量作函数参数
允许用结构变量作函数参数进行整体传送。但是这种传送要将全部成员逐个传送,特别是成员为数组时将会使传送的时间和空间开销很大,严重地降低了程序的效率。因此最好的办法就是使用指针,即用指针变量作函数参数进行传送。
13、动态存储分配
注:“C语言中不允许动态数组”是C89标准中的规定,所以TC、VC6等C89等老的编译器会有这个问题,新的C99中已经不存在这个问题。
C语言中不允许动态数组类型。例如下面的代码是错误的: 
int n; 
scanf("%d",&n); 
int a[n]; 
但是在实际的编程中,往往会发生这种情况,即所需的内存空间取决于实际输入的数据。对于这种问题,用数组的办法很难解决。为了解决上述问题,C语言提供了一些内存管理函数,这些内存管理函数可以按需要动态地分配内存空间,也可把不再使用的空间回收。
(1)分配内存空间函数 malloc
调用形式: 
  (类型说明符*)malloc(size) 
  功能:在内存的动态存储区中分配一块长度为"size"字节的连续区域。函数的返回值为该区域的首地址。
(2)释放内存空间函数 free 
调用形式: 
free(void*ptr); 

13、动态存储分配
C语言中不允许动态数组类型。例如下面的代码是错误的: 
int n; 
scanf("%d",&n); 
int a[n]; 
这不是说代码是错的嘛!但是在实际应用中,我用了很多次了,而没有用到malloc。
例如:
#include
int main(int argc, char *argv[])
{
        int i,n; 
        scanf("%d",&n); 
        int a[n];
        for(i=0;i                scanf("%d",&a[i]);
        }
                for(i=0;i                printf("%d ",a[i]);
        }
        return 0;
}
这个程序编译,运行都没有问题啊!这是怎么回事呢?



阅读(920) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~