C语言指针小结

指针应该算得上是C语言的精华,但也是难点。很多教程或者博客都有对其详细的讲解与分析。我这一节的内容,也是讲解指针,但我会尽量使用图解的方式,使大家很容易理解及掌握。

一、基本使用

先来看看下面的代码:

[objc] view plain copy

1. int i = 3;   

2. intint *p;      

3. p = &i;  

4.       

5. printf("i 存放的内容的值: %d, i 自己所在的地址: %p\n", i, &i);  

6.       

7. printf("p 存放的地址的值: %p; p 自己所在的地址: %p; p 存放的地址所指所存放内容的值: %d", p, &p, *p);  

8.       

9. return 0;  

变量i是int类型,所以存放的是int数据。

变量p是int *类型,所以存放的是指向int类型的地址。

这样说,似乎还是没有表达清楚,我使用下面的一张图进行说明:


1. int i = 3; 这句话执行完毕之后,变量i中的内容是3,假设 变量i本身的内存地址为 "0x6666"。

2. int *p; 只是为指针变量p申请了一块内存地址,假设它的内存地址为 "0x8888"。

3. p = &i; 表示将变量i的地址赋值给指针p所存放的内容,至此,就呈现出了上图的情形。

所以程序中的那两句打印结果就很明显了。

i 存放的内容的值: 3, i 自己所在的地址: 0x8000

p 存放的地址的值: 0x8000; p 自己所在的地址: 0x7000; p 存放的地址所指所存放内容的值: 3


二、 交换两个整数的值

示例代码如下:

[objc] view plain copy

1. void swap(intint *a, intint *b) {  

2.     int temp = *a;  

3.     *a = *b;  

4.     *b = temp;  

5. }  

6.   

7. int main(int argc, const charchar * argv[]) {  

8.       

9.     int a = 3, b = 5;  

10.       

11.     swap(&a ,&b);  

12.       

13.     printf("a: %d; b: %d", a ,b);  

14.       

15.     return 0;  

16. }  

当程序执行完 int a = 3, b = 5, 之后,假设a,b他们自己在内存中的地址分别为0x8000, 0x9000。如下图:


然后调用swap(&a, &b); 注意,这里传递的是变量a, b 本身的地址。而函数swap的接受形参int *a, int *b均是指针变量,用来接受传递过来的变量a, b的地址,即传递过来的变量a, b 和 函数 swap中的形式参数a, b 完全是两回事;为了以示区别,我将形参中的a, b 表达为 swap_a, swap_b。所以将变量a, b 的地址赋值给swap_a 和 swap_b 之后,内存中的地址分布大致如下(假设swap_a本身的地址为0x6000, swap_b本身的地址为0x7000)。


int temp = *a,  定义一个临时变量temp,用来存储指针swap_a 所指向变量a所存储的值,所以临时变量temp此时存储的值为3,如下图:

 

*a = *b, 表示将将指针swap_b所指向变量b的内容赋值给指针swap_a所指向变量a的内容,所以执行完毕之后,内存图大致如下:


最后一句代码 *b = temp, 就是将temp所存储的内容赋值给指针swap_b所指向的变量b存储的内容,所以执行完这句话之后,内存图大致如下:


当程序执行到swap函数 右边 “}” 结束后,此时,表示函数swap已经结束,而变量temp是局部变量,所以此时它也会被立刻销毁,所以最终的内存结构图大致而下:

 

通过上面两个例子的图解,相信大家对指针的概念有了初步的了解,下面的内容,我就直接讲解其内容不做画图处理了,如果自己感兴趣的话,也可以画图尝试尝试。

三、 字符数组与字符串常量

1. 字符数组

[objc] view plain copy

1. char str[] = "good";  

2. while (*str != '\0') {  

3.    putchar(*str++);  

4. }  

注意:字符数组名是一个常量指针,不能进行类似于 str = str + 1 或者 str++ 等操作,所以上面的代码是错误的。而且str存放的是数组第一个元素的地址。如果想了解更所的关于地址方面的知识,可以参考我前面讲解的内容C语言:内存地址分析 & sizeof和strlen用法总结》

2. 字符串常量

[objc] view plain copy

1. charchar *str2 = "good";  

2. while (*str2 != '\0') {  

3.     putchar(*str2++);  

4. }  

注意: "good"本身就是一个常量内容,它存放在只读存储区,并且有自己的地址,而变量str2中存放的内容就是常量 "good"所在的地址。所以上述str2++操作完全是正确的。

比如下面的Demo

[objc] view plain copy

1. const charchar *str = "hello";  

2. printf("address: %p\t%s\t%c\n", &str,str, *str);  

3. str++;  

4. printf("address: %p\t%s\n", &str,str);  

打印的结果为:

address: 0x7fff5fbff6b8helloh

address: 0x7fff5fbff6b8ello

所以str的值就是指针所在位置及后面字符的值,而*str取得的就是指针所在位置字符的值。

四、函数指针

示例代码:

[objc] view plain copy

1. int add(int num1, int num2) {  

2.     return num1 + num2;  

3. }  

4.   

5. int main(int argc, const charchar * argv[]) {  

6.     printf("result:%d", add(1, 2));  

7.     printf("\n%p", add);   

8.     int (*p)(int,int) = add;   

9.     printf("\n%d", p(3, 4));  

10.     return 0;  

11. }  

函数指针,顾名思义,就是一个指向函数的指针。函数指针的目的就是为了实现方法的回调。而回调不是本节讲解的重点,我就不做具体说明了。上面的Demo中定义了一个函数add,它是一个有两个参数,返回值为int的函数。printf("\n%p", add) 打印出来的结果就是函数add入口点的地址。

[objc] view plain copy

1. int (*p)(int,int) = add;  

就是自定义一个函数指针p指向具体的函数add。需要注意的一点是:add是常量地址,不能被修改;而指针p是变量地址,可以被修改。

五、 泛型指针

泛型指针就是在定义的时候还不知道指针的具体类型,直到调用的时候才确定类型,并且进行相应的强制类型转换工作,完成任务。泛型指针的形式就是void *。下面用泛型指针实现一个冒泡排序,代码如下:

[objc] view plain copy

1. void init_array(intint *a, int n) {  

2.       

3.     srand(time(NULL));  

4.       

5.     for (int i = 0; i < n; i++) {  

6.         a[i] = rand() % 100;  

7.     }  

8. }  

9.   

10. int cmp_array(voidvoid *a, voidvoid *b) {  

11.     int num1 = *((intint *)a);  

12.     int num2 = *((intint *)b);  

13.     return num1 > num2;  

14. }  

15.   

16. void swap_array(voidvoid *a, voidvoid *b) {  

17.     int temp = *((intint *)a);  

18.     *((intint *)a) = *((intint *)b);  

19.     *((intint *)b) = temp;  

20. }  

21.   

22. // 这里的void * 就是 泛型指针,需要进行指针转换,达到自己想要的结果  

23. void sort_array(intint *a, int n, int (*cmp)(voidvoid *, voidvoid *), void (*swap)(voidvoid *, voidvoid *)){  

24.     int i,j;  

25.     // 冒泡排序  

26.     for (i = 0; i < n; i++) {  

27.         for (j = 0; j < n - i - 1; j++) {  

28.             if (cmp(&a[j], &a[j+1])) {  

29.                 swap(&a[j], &a[j+1]);  

30.             }  

31.         }  

32.     }  

33. }  

34.   

35. int main(int argc, const charchar * argv[]) {  

36.       

37.     int a[10];  

38.     init_array(a, 10);  

39.     sort_array(a, 10, cmp_array, swap_array);  

40.     return 0;  

41. }  


c语言中,书写起来看起来是有点复杂了,其实现在的高级语言没有必要这么麻烦了,用泛型就可以解决问题了,但是这些高级语言的泛型底层还是依赖于c语言的泛型指针。

六、更为复杂的指针

注:以下的测试代码均为在64位系统处理所得。

1) 指针数组

[objc] view plain copy

1. charchar *p[10];   

2. printf("sizeof(p):%lu \t sizeof(*p):%lu\n",sizeof(p), sizeof(*p));  

1. p存放的是一个指针数组的首地址,而指针数组中每一个元素又是指向char *类型元素的地址。

2. sizeof(p)计算的是数组字节大小,输出80,而sizeof(*p)是计算首元素中存放内容的大小,而存放的内容是地址,所以结果为8。

3. p+1 就是 p[1], 每一个地址中存放的就是char *类型元素的地址,即8个字节,所以p+1的地址是在首元素地址的基础上面加8。

最终打印结果:

sizeof(p):80 sizeof(*p):8

 

2) 指针的指针

[objc] view plain copy

1. charchar **pl;   

2. printf("sizeof(pl):%lu \t sizeof(*pl):%lu, \t **pl:%lu \t pl:%p \t pl+1:%p\n",sizeof(pl), sizeof(*pl), sizeof(**p), p, p+1);  

1. pl是指针的指针,所以pl指向的是一个指针的地址,所以sizeof(pl)结果为8, 打印出来的pl是一个地址,由于pl指向的是一个“指针的地址”,而“指针的地址”存放的是char类型变量的地址,所以占8个字节,所以pl+1 比pl大8。

2. *pl指向char类型变量的,所以它存放的是char类型变量的地址,所以sizeof(*pl)结果为8。

3. **pl,获取char类型变量的值,由于是char类型,所以sizeof(**p)结果为1。

最终打印结果:

sizeof(pl):8 sizeof(*pl):8, **pl:1 pl:0x7fff5fbff6b0 pl+1:0x7fff5fbff6b8

 

3) 函数指针

[objc] view plain copy

1. char (*pt)(void);  

1. pt存放的是函数的首地址,所以sizeof(pt)结果为8

2. *pt 获取的是函数内部的代码块,所以sizeof(*pt) 和 pt + 1 均没有实际意义

 

4) 数组指针

[objc] view plain copy

1. char (*pk)[10]; // 数组指针  

2. printf("sizeof(pk):%lu \t sizeof(*pk):%lu, pk:%p \t *pk:%p \t pk + 1:%p \t *pk + 1: %p\n",sizeof(pk), sizeof(*pk),pk,*pk, pk + 1, *pk + 1);  

1. char (*pk)[10];相当于二维数组 char a[][10]

2. pk相当于指向二维数组的指针,存放的是地址,所以sizeof(pk)结果为8;pk存放的是整个二维数组的首地址; pk + 1 就是相当于指针移动了一个一维数组的距离,所以地址在pk的基础上面加了10.

3. *pk就是取得二维数组第一行的值,它是一个含有10个char元素的一维数组,所以sizeof(*pk)结果为10;*pk存放的是二维数组中第一维数组的首地址; *pk + 1就是在一维数组的基础上,移动了一个char的距离,所以地址在*pk基础上面加1。

4. 所以 pk和*pk打印的地址结果是一样的。

最终打印结果:

sizeof(pk):8 sizeof(*pk):10, pk:0x7fff5fc27190 *pk:0x7fff5fc27190 pk + 1:0x7fff5fc2719a *pk + 1: 0x7fff5fc27191

 

 

1.考题一:程序代码如下:

void Exchg1(int x, int y)
{
int tmp;
tmp=x;
x=y;
y=tmp;
printf (“x=%d,y=%d\n”,x,y)
}
void main()
{
int a=4,b=6;
Exchg1 (a,b) ;
printf(“a=%d,b=%d\n”,a,b)
}

输出的结果

x=____, y=____

a=____, b=____

问下划线的部分应是什么,请完成。

2.考题二:代码如下。

Exchg2(int *px, int *py)
{
int tmp=*px;
*px=*py;
*py=tmp;
print(“*px=%d,*py=%d\n”,*px,*py);
}
main()
{
int a=4;
int b=6;
Exchg2( &a,&b);
Print (“a=%d,b=%d\n”, a, b);
}

输出的结果为:

*px=____, *py=____

a=____, b=____

问下划线的部分应是什么,请完成。

3.考题三:

Exchg2(int &x, int &y)
{
int tmp=x;
x=y;
y=tmp;
print(“x=%d,y=%d\n”,x,y);
}
main()
{
int a=4;
int b=6;
Exchg2(a,b);
Print(“a=%d,b=%d\n”, a, b);
}

出的结果:

x=____, y=____

a=____, b=____

问下划线的部分输出的应是什么, 请完成。

你不在机子上试,能作出来吗?你对你写出的答案有多大的把握?

正确的答案 ,想知道吗?(呵呵,让我慢慢地告诉你吧!)

好,废话少说,继续我们的探索之旅了。

我们都知道:C语言中函数参数的传递有:值传递,地址传递,引用传递这三种形式。题一为值 传递,题二为地址传递,题三为引用传递。不过,正是这几种参数传递的形式,曾把我给搞得晕头转向 。我相信也有很多人与我有同感吧?

下面请让我逐个地谈谈这三种传递形式。

二、函数 参数传递方式之一:值传递

1.值传递的一个错误认识

先看题一中Exchg1函数的定义:

void Exchg1(int x, int y)   //定义中的x,y变量被称为Exchg1函数的形式参数
{
int tmp;
tmp=x;
x=y;
y=tmp;
printf(“x=%d,y=% d\n”,x,y)
}

问:你认为这个函数是在做什么呀?

答:好像是对参数 x,y的值对调吧?

请往下看,我想利用这个函数来完成对a,b两个变量值的对调,程序如下:

void main()
{
int a=4,b=6;
Exchg1 (a,b)     //a,b变量为 Exchg1函数的实际参数。
/  printf(“a=%d,b=%d\n”,a,b)
}

我问:Exchg1 ()里头的  printf(“x=%d,y=%d\n”,x,y)语句会输出什么啊?

我再问:Exchg1 ()后的  printf(“a=%d,b=%d\n”,a,b)语句输出的是什么 ?

程序输出的结果是:

x=6 , y=4

a=4 , b=6  //为什么不是a=6,b=4呢?

奇怪,明明我把a,b分别代入了x,y中,并在函数里完成了两个变量值的交换,为什么a,b变量 值还是没有交换(仍然是a==4,b==6,而不是a==6,b==4)?如果你也会有这个疑问,那是因为你跟本 就不知实参a,b与形参x,y的关系了。

2.一个预备的常识

为了说明这个问题,我先给出 一个代码:

int a=4;

int x;

x=a;

x=x+3;

看好了没,现在我问 你:最终a值是多少,x值是多少?

(怎么搞的,给我这个小儿科的问题。还不简单,不就是a==4   x==7嘛!)

在这个代码中,你要明白一个东西:虽然a值赋给了x,但是a变量并不是x变量哦 。我们对x任何的修改,都不会改变a变量。呵呵!虽然简单,并且一看就理所当然,不过可是一个很重 要的认识喔。

3.理解值传递的形式

看调用Exch1函数的代码:

main()
{
int a=4,b=6;
Exchg1(a,b) //这里调用了Exchg1函数  
printf(“a=% d,b=%d”,a,b)
}

Exchg1(a,b)时所完成的操作代码如下所示。
int x=a;//←
int y=b;//←注意这里,头两行是调用函数时的隐含操作
int tmp;
tmp=x;
x=y;
y=tmp;

请注意在调用执行Exchg1函数的操作中我人为地加上 了头两句:

int x=a;

int y=b;

这是调用函数时的两个隐含动作。它确实存在, 现在我只不过把它显式地写了出来而已。问题一下就清晰起来啦。(看到这里,现在你认为函数里面交 换操作的是a,b变量或者只是x,y变量呢?)

原来 ,其实函数在调用时是隐含地把实参a,b 的 值分别赋值给了x,y,之后在你写的Exchg1函数体内再也没有对a,b进行任何的操作了。交换的只是x, y变量。并不是a,b.当然a,b的值没有改变啦!函数只是把a,b的值通过赋值传递给了x,y,函数里头 操作的只是x,y的值并不是a,b的值。这就是所谓的参数的值传递了。

哈哈,终于明白了,正是 因为它隐含了那两个的赋值操作,才让我们产生了前述的迷惑(以为a,b已经代替了x,y,对x,y的操 作就是对a,b的操作了,这是一个错误的观点啊!)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值