Fork me on GitHub
指针

指针[收藏]

直接引用

1. 回想一下,之前我们是如何更改某个变量的值?
我们之前是通过变量名来直接引用变量,然后进行赋值:
char a;
a = 10;
 
2. 看上去是很简单,其实程序内部是怎么操作的呢?
其实,程序对变量的读写操作,实际上是对变量所在的存储空间进行写入或取出数据。就上面的代码而言,系统会自动将变量名a转换为变量的存储地址,根据地址找到变量a的存储空间,然后再将数据10以2进制的形式放入变量a的存储空间中。
 
 
3. 通过变量名引用变量,由系统自动完成变量名和其存储地址之间的转换,称为变量的"直接引用"方式
 

一、什么是指针?

1.我们已经知道,"直接引用"是直接通过变量名来读写变量

2.C语言中还有一种"间接引用"的方式(以变量a为例):首先将变量a的地址存放在另一个变量中,比如存放在变量b中,然后通过变量b来间接引用变量a,间接读写变量a的值。这就是"间接引用"。

如果程序通过"间接引用"的方式来修改a的值,可以这样做:先根据 变量名b 获取 变量b 的地址ffc2,取出变量b中存储的内容ffc1,也就是变量a的地址,再根据变量a的地址ffc1找到a的存储空间,然后修改里面的数据。

3.总结一句:用来存放变量地址的变量,就称为"指针变量"。在上面的情况下,变量b就是个"指针变量",我们可以说指针变量b指向变量a。

 


二、指针的定义

一般形式:类名标识符  *指针变量名;

int *p;

float *q;
  • "*"是一个说明符,用来说明这个变量是个指针变量,是不能省略的,但它不属于变量名的一部分
  • 前面的类型标识符表示指针变量所指向的变量的类型,而且只能指向这种类型的变量

 


三、指针的初始化

1.先定义后初始化

复制代码
复制代码
1 // 定义int类型的变量a
2 int a = 10;
3 
4 // 定义一个指针变量p
5 int *p;
6 
7 // 将变量a的地址赋值给指针变量p,所以指针变量p指向变量a
8 p = &a;
复制代码
复制代码

注意第8行,赋值给p的是变量a的地址&a

 

2.在定义的同时初始化

复制代码
复制代码
// 定义int类型的变量a
int a = 10;

// 定义一个指针变量p
// 并将变量a的地址赋值给指针变量p,所以指针变量p指向变量a
int *p = &a;
复制代码
复制代码

 

3.初始化的注意

指针变量是用来存放变量地址的,不要给它随意赋值一个常数。下面的写法是错误

int *p; 
p = 200; // 这是错误的

 


四、指针运算符

1.给指针指向的变量赋值

复制代码
复制代码
 1 char a = 10;
 2 printf("修改前,a的值:%d\n", a);
 3 
 4 // 指针变量p指向变量a
 5 char *p = &a;
 6 
 7 // 通过指针变量p间接修改变量a的值
 8 *p = 9;
 9 
10 printf("修改后,a的值:%d", a);
复制代码
复制代码

当程序刚执行完第5行代码时,内存中大概的分布情况是这样的

,a值是10,p值就是变量a的地址ffc3。

注意下第5、第8行,都有个"*",它们的含义是不一样的:

(1) 第5行的"*"只是用来说明p是个指针变量

(2) 第8行的"*"是一个指针运算符,这里的*p代表根据p值ffc3这个地址访问对应的存储空间,也就是变量a的存储空间,然后将右边的数值9写入到这个存储空间,相当于 a = 9;,于是内存中就变成这样了

输出结果为:,可以发现,我们通过变量p间接修改了变量a的值。

 

2.取出指针所指向变量的值

指针运算符除了可以赋值之外,还可以用于取值

复制代码
复制代码
1 char a = 10;
2  
3 char *p;
4 p = &a;
5 
6 char value = *p;
7 printf("取出a的值:%d", value); 
复制代码
复制代码

输出结果:第6行的*p的意思是:根据p值(即变量a的地址)访问对应的存储空间,并取出存储的内容(即取出变量a的值),赋值给value

 

3.使用注意

在指针变量没有指向确定地址之前,不要对它所指的内容赋值。下面的写法是错误

int *p; 
*p = 10; //这是错误的

应该在指针变量指向一个确定的变量后再进行赋值。下面的写法才是正确

复制代码
复制代码
// 定义2个int型变量
int a = 6, b;

// 定义一个指向变量b的指针变量p
int *p;
p = &b;

// 将a的值赋值给变量b
*p = a;
复制代码
复制代码

 


五、指针的用途举例

1.例子1

前面我们通过指针变量p间接访问了变量a,在有些人看来,觉得指针变量好傻B,直接用变量名a访问变量a不就好了么,干嘛搞这么麻烦。别着急,接下来举个例子,让大家看看指针还能做什么事情。

现在有个要求:写一个函数swap,接收2个整型参数,功能是互换两个实参的值。

1> 如果没学过指针,你可能会这样写

复制代码
复制代码
 1 void swap(char v1, char v2) {
 2     printf("更换前:v1=%d, v2=%d\n", v1, v2);
 3     
 4     // 定义一个中间变量
 5     char temp;
 6     
 7     // 交换v1和v2的值
 8     temp = v1;
 9     v1 = v2;
10     v2 = temp;
11     
12     printf("更换后:v1=%d, v2=%d\n", v1, v2);
13 }
14 
15 int main()
16 {
17     char a = 10, b = 9;
18     printf("更换前:a=%d, b=%d\n", a, b);
19     
20     swap(a, b);
21     
22     printf("更换后:a=%d, b=%d", a, b);
23     return 0;
24 }
复制代码
复制代码

输出结果:,虽然v1和v2的值被交换了,但是变量a和b的值根本就没有换过来。因为基本数据类型作为函数实参时,只是纯粹地将值传递给形参,形参的改变并不影响实参。

我们可以简要分析一下这个过程:

* 在第20行中,将变量a、b的值分别传递给了swap函数的两个形参v1、v2

* 在第8行中,将v1的值赋值给了temp

* 在第9行中,将v2的值赋值给了v1

* 在第10行中,将temp的值赋值给了v2

就这样,v1和v2的值被交换了,但是a和b的值一直都没有改变

 

2> 如果学了指针,就应该这样写

复制代码
复制代码
 1 void swap(char *v1, char *v2) {
 2     // 中间变量
 3     char temp;
 4     
 5     // 取出v1指向的变量的值
 6     temp = *v1;
 7     
 8     // 取出v2指向的变量的值,然后赋值给v1指向的变量
 9     *v1 = *v2;
10     
11     // 赋值给v2指向的变量
12     *v2 = temp;
13 }
14 
15 int main()
16 {
17     char a = 10, b = 9;
18     printf("更换前:a=%d, b=%d\n", a, b);
19     
20     swap(&a, &b);
21     
22     printf("更换后:a=%d, b=%d", a, b);
23     return 0;
24 }
复制代码
复制代码

先看看输出结果:,变量a和b的值终于换过来了。

解释一下:

(在16位编译器环境下,一个指针变量占用2个字节)

* 先注意第20行,传递是变量的地址。因此swap函数的形参v1指向了变量a,v2指向了变量b

* 第6行代码是取出v1指向的变量的值,也就是变量a的值:10,然后赋值给变量temp

* 第9行代码是取出v2指向的变量(变量b)的值,然后赋值给v1指向的变量(变量a)

* 第12行代码是将temp变量的值赋值给v2指向的变量(变量b)

相信你已经感受到指针的强大了,如果没有指针,在一个函数的内部根本改变不了外部的实参。

 

2.例子2

接下来再举一个指针的实用例子。默认情况下,一个函数只能有一个返回值,有了指针,我们可以实现函数有"多返回值"。

现在有个要求:写一个函数sumAndMinus,可以同时计算2个整型的和与差,函数执行完毕后,返回和与差(注意了,这里要返回2个值)

复制代码
复制代码
// 计算2个整型的和与差
int sumAndMinus(int v1, int v2, int *minus) {
    // 计算差,并赋值给指针指向的变量
    *minus = v1 - v2;
    
    // 计算和,并返回和
    return v1 + v2;
}

int main()
{
    // 定义2个int型变量
    int a = 6, b = 2;

    // 定义2个变量来分别接收和与差
    int sum, minus;

    // 调用函数
    sum = sumAndMinus(a, b, &minus);
    
    // 打印和
    printf("%d+%d=%d\n", a, b, sum);
    
    // 打印差
    printf("%d-%d=%d\n", a, b, minus);
    return 0;
}
复制代码
复制代码

输出结果:,和与差都由同一个函数计算并返回出来。和是函数的直接返回值,差是通过函数的第3个指针参数间接返回。

因此有了指针,我们可以让函数有"无限个"返回值。

 


六、关于指针的疑问

刚学完指针,都可能有一大堆的疑惑,这里我列出几个常见的疑惑吧。

1.一个指针变量占用多少个字节的内存空间?占用的空间是否会跟随所指向变量的类型而改变?

在同一种编译器环境下,一个指针变量所占用的内存空间是固定的。比如,在16位编译器环境下,任何一个指针变量都只占用2个字节,并不会随所指向变量的类型而改变。

 

2.既然每个指针变量所占用的内存空间是一样的,而且存储的都是地址,为何指针变量还要分类型?而且只能指向一种类型的变量?比如指向int类型的指针、指向char类型的指针。

其实,我觉得这个问题跟"数组为什么要分类型"是一样的。

* 看下面的代码,利用指针p读取变量c的值

复制代码
复制代码
1 int i = 2;
2 char c = 1;
3 
4 // 定义一个指向char类型的指针
5 char *p = &c;
6 
7 // 取出
8 printf("%d", *p);
复制代码
复制代码

这个输出结果应该难不倒大家:,是可以成功读取的。

如果我改一下第5行的代码,用一个本应该指向int类型变量的指针p,指向char类型的变量c

int *p = &c;

我们再来看一下输出:c的原值是1,现在取出来却是513,怎么回事呢?这个要根据内存来分析

根据变量的定义顺序,这些变量在内存中大致如下图排布:

其中,指针变量p和int类型变量i各占2个字节,char类型的c占一个字节,p指向c,因此p值就是c的地址

1> 最初的时候,我们用char *p指向变量c。当利用*p来获取变量c的值时,由于指针p知道变量c是char类型的,所以会从ffc3这个地址开始读取1个字节的数据:0000 0001,转为10进制就是1

2> 后来,我们用int *p指向变量c。当利用*p获取变量c的值时,由于指针p认为变量c是int类型的,所以会从ffc3这个地址开始读取2个字节的数据:0000 0010 0000 0001,转为10进制就是513

可见,给指针分类是多么重要的一件事,而且一种指针最好只指向一种类型的变量,那是最安全的。

    我很喜欢怪兽师傅的一句话: 
           路漫漫其修远兮,吾将上下而求索。 
          如果还行,如果您不忙的话,  请看右下角,不管是好是坏,轻松点击下!  
 
分类: ACM

指针[收藏]

2014-03-10 12:39 by Jeff Li, 451 visits, 网摘收藏编辑
摘要:直接引用1. 回想一下,之前我们是如何更改某个变量的值?我们之前是通过变量名来直接引用变量,然后进行赋值:char a;a = 10;2. 看上去是很简单,其实程序内部是怎么操作的呢?其实,程序对变量的读写操作,实际上是对变量所在的存储空间进行写入或取出数据。就上面的代码而言,系统会自动将变量名a转换为变量的存储地址,根据地址找到变量a的存储空间,然后再将数据10以2进制的形式放入变量a的存储空间中。3. 通过变量名引用变量,由系统自动完成变量名和其存储地址之间的转换,称为变量的"直接引用"方式一、什么是指针?1.我们已经知道,"直接引用"是直接通过变量 阅读全文

hd1007

2013-11-16 11:33 by Jeff Li, 9 visits, 网摘收藏编辑
摘要:#include #include #include using namespace std;struct node{double x,y;}point[100000];int n;bool hAlignLess(node p1,node p2){if(p1.x != p2.x) return p1.x > 1; double curmin = getMin(solve(l,mid),solve(mid+1,r)); for(i=l;i<=r;i++) for(j=i+1;j<=i+5 && j<=r;j++) { curmin = getMi... 阅读全文

UVa OJ 120

2013-11-15 12:11 by Jeff Li, 10 visits, 网摘收藏编辑
摘要:Background背景Stacks and Queues are often considered the bread and butter of data structures and find use in architecture, parsing, operating systems, and discrete event simulation. Stacks are also important in the theory of formal languages.栈和队列常常被视为数据结构中的面包和黄油,广泛应用在体系结构、分析、操作系统和离散事件等领域。栈同时也在形式语言理论中发 阅读全文

ACM--string常见用法

2013-11-11 10:06 by Jeff Li, 37 visits, 网摘收藏编辑
摘要:在ACM竞赛中,常常需要将读入的数字的每位分离出来,如果采用取余的方法,花费的时间就会太长,这时候,我们可以将读入的数据当成字符串来处理,这样就方便、省时多了。下面这个程序演示了求一个整数各位的和:#include #include using namespace std;int main(){ string s = "123455"; int sum = 0; for(int i = 0;i#include using namespace std;int main(){ char a[100],b[100],c[100]; sscanf("122 3ss 3ww 阅读全文

ACM学习<3>

2013-10-30 08:20 by Jeff Li, 18 visits, 网摘收藏编辑
摘要:排序算法: 基本:冒泡,快速,选择,堆,插入,shell 多路并归:1.冒泡排序: 思想:交换排序,通过相邻的交换来达到排序的目的。 流程: 1.对数组中的各数据,依次比较相邻两个元素的大小。 2.如果前面的大,交换。经过一轮,可把最小的排好。 3.然后用同样的方法,把剩下的数据排好。最后从小到大排好相应的数据。#include #include #define SIZE 10using namespace std;void BubbleSort(int *a,int len){ int temp; for(int i=0;ii;j--) { if(a[j-... 阅读全文

ACM学习<二>

2013-10-24 09:01 by Jeff Li, 316 visits, 网摘收藏编辑
摘要:穷举算法思想: 一句话:就是从所有可能的情况,搜索出正确的答案。步骤: 1.对于一种可能的情况,计算其结果。 2.判断结果是否满足,YES计算下一个,no继续步骤1,然后判断下个可能的情况。实例: 孙子算经--鸡兔同笼:头35,脚94,几鸡几兔? #include //头文件using namespace std;int qiongju(int head, int foot , int *chicken,int *rabbit) //穷举算法{ int... 阅读全文

ACM学习大纲

2013-10-23 11:31 by Jeff Li, 53 visits, 网摘收藏编辑
摘要:1 推荐题库•http://ace.delos.com/usaco/美国的OI 题库,如果是刚入门的新手,可以尝试先把它刷通,能够学到几乎全部的基础算法极其优化,全部的题解及标程还有题目翻译可以baidu 一个叫NOCOW 的网站。•http://livearchive.onlinejudge.org/上面有全部的赛区真题,绝大部分都可以提交,不适合当题库刷,不过在这里找题非常方便。•http://poj.org/不解释了,中国最知名的oj,题量非常之大,历史也很悠久,推荐刷一些代表性的题目。•http://acm.timus.ru/Ural 大学的oj,国外oj 中非常好的一个,题目非常锻炼 阅读全文

ACM学习<一>

2013-10-23 11:28 by Jeff Li, 15 visits, 网摘收藏编辑
摘要:c++指针|指针入门什么是指针? 其实指针就像是其它变量一样,所不同的是一般的变量包含的是实际的真实的数据,而指针是一个指示器,它告诉程序在内存的哪块区域可以找到数据。这是一个非常重要的概念,有很多程序和算法都是围绕指针而设计的,如链表。开始学习 如何定义一个指针呢?就像你定义一个其它变量一样,只不过你要在指针名字前加上一个星号。我们来看一个例子: 下面这个程序定义了两个指针,它们都是指向整型数据。int*pNumberOne;int*pNumberTwo; 你注意到在两个变量名前的“p”前缀了吗?这是程序员通常在定义指针时的一个习惯,以提高便程序的阅读性,表示这是个指针。现在让我们来... 阅读全文

五大常用算法之一:分治算法

2013-10-20 21:54 by Jeff Li, 10 visits, 网摘收藏编辑
摘要:分治算法一、基本概念 在计算机科学中,分治法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)…… 任何一个可以用计算机求解的问题所需的计算时间都与其规模有关。问题的规模越小,越容易直接求解,解题所需的计算时间也越少。例如,对于n个元素的排序问题,当n=1时,不需任何计算。n=2时,只要作一次比较即可排好序。n=3时只要作3次比较即可,…。而当n较大时,问题 阅读全文

五大常用算法之二:动态规划算法(DP)

2013-10-18 09:15 by Jeff Li, 22 visits, 网摘收藏编辑
摘要:一、基本概念 动态规划过程是:每次决策依赖于当前状态,又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划。二、基本思想与策略 基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。 由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一 阅读全文

DP的学习

2013-10-18 09:11 by Jeff Li, 18 visits, 网摘收藏编辑
摘要:DP在ACM的算法里面可算是重中之重,题目类型千变万化,题目难度差异也很大.是一种很讲究技巧的算法,而且代码实现相对容易,1y率非常高(除有些bt数据外).总之DP就是一向非常重要,又非常博大精深的算法.我们学校的Roba的大牛在这方面就有很深的造诣. 说一下自己这几天接触的初级DP,DP中最重要的往往是状态和状态之间的转移,找到状态转移方程,用递归或者是递推的方式列出方程,题目也就迎仞而解了,而所谓的难题往往是初看不知所措,找不出状态转移方程,或者根本连什么是状态的都看不清,下面分析一下有关DP的非常经典的3个问题: 1.求局部最大和 大意是这样给你一个数组,在其中取任意连续多个,使其... 阅读全文

ACM 要学

2013-10-15 21:39 by Jeff Li, 33 visits, 网摘收藏编辑
摘要:初期:一.基本算法: (1)枚举. (poj1753,poj2965)(2)贪心(poj1328,poj2109,poj2586)(3)递归和分治法.(4)递推.(5)构造法.(poj3295)(6)模拟法.(poj1068,poj2632,poj1573,poj2993,poj2996)二.图算法:(1)图的深度优先遍历和广度优先遍历.(2)最短路径算法(dijkstra,bellman-ford,floyd,heap+dijkstra)(poj1860,poj3259,poj1062,poj2253,poj1125,poj2240)(3)最小生成树算法(prim,kruskal)(poj1 阅读全文
posted on 2014-03-10 20:26  HackerVirus  阅读(386)  评论(0编辑  收藏  举报