指针应该算得上是c语言的精华,但也是难点。

非常多教程或者博客都有对其具体的解说与分析。

我这一节的内容,也是解说指针。但我会尽量使用图解的方式,使大家非常easy理解及掌握。

一、基本使用

先来看看以下的代码:

int i = 3;
int *p;
p = &i; printf("i 存放的内容的值: %d, i 自己所在的地址: %p\n", i, &i); printf("p 存放的地址的值: %p; p 自己所在的地址: %p; p 存放的地址所指所存放内容的值: %d", p, &p, *p); return 0;

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

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

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

1. int i = 3; 这句话运行完成之后,变量i中的内容是3,如果 变量i本身的内存地址为 "0x8000"。

2. int *p; 仅仅是为指针变量p申请了一块内存地址,如果它的内存地址为 "0x7000",此时变量p中存放的内容为nil。

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

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

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

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

二、 交换两个整数的值

演示样例代码例如以下:

void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
} int main(int argc, const char * argv[]) { int a = 3, b = 5; swap(&a ,&b); printf("a: %d; b: %d", a ,b); return 0;
}

当程序运行完 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. 字符数组

char str[] = "good";
while (*str != '\0') {
putchar(*str++);
}

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

2. 字符串常量

char *str2 = "good";
while (*str2 != '\0') {
putchar(*str2++);
}

注意: "good"本身就是一个常量内容。它存放在仅仅读存储区,而且有自己的地址,而变量str2中存放的内容就是常量 "good"所在的地址。

所以上述str2++操作全然是正确的。

比方以下的Demo

const char *str = "hello";
printf("address: %p\t%s\t%c\n", &str,str, *str);
str++;
printf("address: %p\t%s\n", &str,str);

打印的结果为:

address: 0x7fff5fbff6b8helloh

address: 0x7fff5fbff6b8ello

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

四、函数指针

演示样例代码:

int add(int num1, int num2) {
return num1 + num2;
} int main(int argc, const char * argv[]) {
printf("result:%d", add(1, 2));
printf("\n%p", add);
int (*p)(int,int) = add;
printf("\n%d", p(3, 4));
return 0;
}

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

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

就是自己定义一个函数指针p指向详细的函数add。

须要注意的一点是:add是常量地址,不能被改动;而指针p是变量地址。能够被改动。

五、 泛型指针

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

void init_array(int *a, int n) {

    srand(time(NULL));

    for (int i = 0; i < n; i++) {
a[i] = rand() % 100;
}
} int cmp_array(void *a, void *b) {
int num1 = *((int *)a);
int num2 = *((int *)b);
return num1 > num2;
} void swap_array(void *a, void *b) {
int temp = *((int *)a);
*((int *)a) = *((int *)b);
*((int *)b) = temp;
} // 这里的void * 就是 泛型指针。须要进行指针转换,达到自己想要的结果
void sort_array(int *a, int n, int (*cmp)(void *, void *), void (*swap)(void *, void *)){
int i,j;
// 冒泡排序
for (i = 0; i < n; i++) {
for (j = 0; j < n - i - 1; j++) {
if (cmp(&a[j], &a[j+1])) {
swap(&a[j], &a[j+1]);
}
}
}
} int main(int argc, const char * argv[]) { int a[10];
init_array(a, 10);
sort_array(a, 10, cmp_array, swap_array);
return 0;
}

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

六、更为复杂的指针

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

1) 指针数组

char *p[10];
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) 指针的指针

char **pl;
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) 函数指针

char (*pt)(void);

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

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

4) 数组指针

char (*pk)[10]; // 数组指针
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

C语言:具体解释指针的更多相关文章

  1. 3.1 Go语言基础之指针

    区别于C/C++中的指针,Go语言中的指针不能进行偏移和运算,是安全指针. 要搞明白Go语言中的指针需要先知道3个概念:指针地址.指针类型和指针取值. 一.Go语言中的指针 Go语言中的函数传参都是值 ...

  2. C语言之漫谈指针(下)

    C语言之漫谈指针(下) 在上节我们讲到了一些关于指针的基础知识: 详见:C语言之漫谈指针(上) 本节大纲: 零.小tips 一.字符指针 二.指针数组与数组指针 三.数组传参与指针传参 四.函数指针及 ...

  3. Swift3.0语言教程使用指针创建和初始化字符串

    Swift3.0语言教程使用指针创建和初始化字符串 Swift3.0语言教程使用指针创建和初始化字符串苹果的Swift团队花了不少功夫来支持C的一些基础特性.C语言中为我们提供了指针,Swift也不例 ...

  4. C语言中的指针数组

    C语言中的指针数组是什么,像 char *a[]={"ddd","dsidd","lll"}; 这里讲一下注意如果我们使用了a也就是首元素的 ...

  5. 【ZZ】C 语言中的指针和内存泄漏 & 编写高效的C程序与C代码优化

    C 语言中的指针和内存泄漏 http://www.ibm.com/developerworks/cn/aix/library/au-toughgame/ 本文讨论了几种在使用动态内存分配时可以避免的陷 ...

  6. GO语言中的指针

    http://www.tizgrape.com/?p=100 Go语言中的指针语法和C++一脉相承,都是用*作为符号,虽然语法上接近,但是实际差异不小. Go使用var定义变量: var v6 *in ...

  7. 由链表初始化看C语言的二级指针

    先来看C语言创建链表.插入节点和遍历链表的一段代码: #include <stdio.h> #include <stdlib.h> typedef int ElemType; ...

  8. C语言 > 数组和指针

    C语言 数组和指针 const: 关于指针和const需要注意一些规则.首先,把const数据或非const数据的地址初始化为指向const的指针或为其赋值是合法的. 然而,只能把非const数据的地 ...

  9. C语言结构体指针的引用问题

    在写栈的一个应用时遇见这样的一个问题 SqStack s; s->base = (int*)malloc(sizeof(int)*10); 通过这样一个代码引用的时候,会导致程序出现异常 经过一 ...

随机推荐

  1. Description Resource Path Location Type Cannot change version of project fac(导入maven项目出现红叉问题)

    项目现象如下: 这是由于你的 Maven 编译级别是 jdk太低了 解决方法: 1.在eclipse的工程上选择属性,在选择Project Facets里面中选择Dynamic web Module, ...

  2. 用jQuery设置多个css样式

    $("#show_one").css({"position":"fixed","top":"0px" ...

  3. 使用Python进行多线程检查.moe三位剩余有效域名

    翻看博客看到一段不错的代码 虽然近期没有购买域名的需求 不过日后有购买域名的需求的话 稍作修改直接使用还是很方便的 import threading import requests import js ...

  4. WordPress 不错的插件

    Akismet – 防止垃圾评论 WP-PostViews Plus - 页面访问量统计 All in One SEO Pack – 搜索引擎优化的插件,自动优化搜索引擎. WP Super Cach ...

  5. js的运算小数点的问题

    问题这样的: 37.5*5.5=206.08 (JS算出来是这样的一个结果,我四舍五入取两位小数) 我先怀疑是四舍五入的问题,就直接用JS算了一个结果为:206.08499999999998 怎么会这 ...

  6. UVA-12186 Another Crisis 树形dp

    题目链接:https://cn.vjudge.net/problem/UVA-12186 题意 给出n, T和一棵树,树上每个节点需要选择T%个直属子节点. 问根节点一共需要选择几个节点. 思路 思路 ...

  7. [原创]Linux 下 redis 链接一次

    刚接触 Linux ,在 Linux 下安装 redis 链接redis 出现了以下问题  Could not connect to Redis at 127.0.0.1:6379: Connecti ...

  8. mysql 密码的破解

    现在的主流的数据库一般是mysql  ,sql  server ,  oracle. 有的时候我们忘记了数据库密码的时候我们要怎么办,破解别人的数据库的密码的时候我们要怎么搞  忘记密码是一件很头痛的 ...

  9. 我有一个idea,但是没有钱,又没技术怎么办?

    我想你还少讲一件事,就是同时如果你也没什么明确的商业计划,恭喜,那你有机会成为马云第二,因为他曾说过自己的成功要素就是「没钱」.「不懂技术」.「没有计划」,要是这么刚好让你从事互联网产业,我看不出三年 ...

  10. 逆向project第003篇:跨越CM4验证机制的鸿沟(上)

    一.前言 <冠军足球经理>系列作为一款拟真度极高的足球经营类游戏.赢得过无数赞誉,而CM4可以说是这个传奇的起点. 可是在游戏安装过程中.当用户输入完序列号之后.程序并不会对用户的输入进行 ...