直接引用

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

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

 
 

【C语言】11-指针的更多相关文章

  1. 瘋子C语言笔记(指针篇)

    指针篇 1.基本指针变量 (1)定义 int i,j; int *pointer_1,*pointer_2; pointer_1 = &i; pointer_2 = &j; 等价于 i ...

  2. C语言函数指针基础

    本文写的非常详细,因为我想为初学者建立一个意识模型,来帮助他们理解函数指针的语法和基础.如果你不讨厌事无巨细,请尽情阅读吧. 函数指针虽然在语法上让人有些迷惑,但不失为一种有趣而强大的工具.本文将从C ...

  3. 为什么C/C++语言使用指针

    这是参加面试时,面试官问的一道开放性题目. 问题是:为什么C/C++语言使用指针? 这个问题一问出来,直接被面试官秒杀了,面试官大神,你怎么不按套路出牌啊? 说好的malloc和new的区别呢?说好的 ...

  4. 浅谈c语言的指针

    对于非计算机专业的同学,c语言的指针往往就是老师的一句“指针不考“就带过了.c语言的指针号称是c语言的灵魂,是c语言中最精妙的部分. 指针本质上也是变量,也就是一段内存,只是他的特殊之处是他存储的数据 ...

  5. C#委托与C语言函数指针及函数指针数组

    C#委托与C语言函数指针及函数指针数组 在使用C#时总会为委托而感到疑惑,但现在总新温习了一遍C语言后,才真正理解的委托. 其实委托就类似于C/C++里的函数指针,在函数传参时传递的是函数指针,在调用 ...

  6. C语言二重指针与malloc

    (内容主要源于网上,只是加入了些自己的剖析) 假设有一个二重指针: char **p; 同时有一个指针数组 char *name[4]; 如何引用p呢? 首先我们有程序代码如下 #include &l ...

  7. C语言的指针变量

    C语言的指针变量 在C语言中,变量是固定范围的存储空间,它存储的是赋给他的值, 比如: ; /* 这里是定义一个整型变量a,并把12这个值存储在a的地址空间上 这个地址空间是系统随机分配的,对用户是透 ...

  8. Android For JNI(五)——C语言多级指针,结构体,联合体,枚举,自定义类型

    Android For JNI(五)--C语言多级指针,结构体,联合体,枚举,自定义类型 我们的C已经渐渐的步入正轨了,基础过去之后,就是我们的NDK和JNI实战了 一.多级指针 指针的概念我们在前面 ...

  9. “对外部(局部)变量的访问”是C语言函数指针的最大弱点

    1.“对外部(局部)变量的访问”是C语言函数指针的最大弱点 . #include <stdio.h> #include <stdlib.h> /* 结构体定义 */ struc ...

  10. go语言学习--指针的理解

    Go 的原生数据类型可以分为基本类型和高级类型,基本类型主要包含 string, bool, int 及 float 系列,高级类型包含 struct,array/slice,map,chan, fu ...

随机推荐

  1. Java单链表、双端链表、有序链表实现

    单链表: insertFirst:在表头插入一个新的链接点,时间复杂度为O(1) deleteFirst:删除表头的链接点,时间复杂度为O(1) 有了这两个方法,就可以用单链表来实现一个栈了,见htt ...

  2. 64. 海明距离(Hamming Distance)

    [本文链接] http://www.cnblogs.com/hellogiser/p/hamming-distance.html [介绍] 在信息领域,两个长度相等的字符串的海明距离是在相同位置上不同 ...

  3. 32.C++不能被继承的类[C++ Final Class]

    [题目] 用C++设计一个不能被继承的类. [分析] 这是Adobe公司2007年校园招聘的最新笔试题.这道题除了考察应聘者的C++基本功底外,还能考察反应能力,是一道很好的题目. 在Java中定义了 ...

  4. HAST 使用笔记

    1.环境 2台 freebsd 9.2的机器,/home分区为260G,需要将其转为hast块设备 2.安装 (1)先umount /home,然后注释掉/etc/fstab上的/home记录: # ...

  5. Hadoop中操作HDFS出现异常的解决方法

    Hadoop环境搭建成功后,一般会运行一个小例子,这时候就涉及到了对HDFS文件系统的操作,对于刚开始学习Hadoop的初学者一般会多次的进行name节点的格式化操作,最后导致上传文件会抛出异常,通过 ...

  6. mybatisforeach循环,传入多个参数

    上代码: controller: @RequestMapping(value = "/findPage", method = RequestMethod.POST) @Respon ...

  7. Linux设置IP

    进入 vi /etc/sysconfig/network-scripts/ifcfg-eth0  root # ifconfig eth0 192.168.22.232 root # route ad ...

  8. 搭建邮局(邮件服务器) - hmailserver

    1.查看服务器mx是否解析成功 nslookup  set type=mx   2.hmailserver服务器 smtp设置     3.foxmail 设置   4.使用webmail(after ...

  9. 【Chrome】手动下载和安装Adblock Plus的方法

    由于强大的GFW,导致很多Google的站点没法访问,也就没法直接安装chrome的插件了,网页都打不卡, 那么几乎是必备的Adblock Plus如何下载安装呢? 1.访问官方网站: https:/ ...

  10. C++的那些事:数据与类型

    一.前言 最近在看C++Primer第5版,先前已经看过第4版,但是发现第5版在整个知识布局与个别知识的讲解上跟第4版差别还是挺大的,尤其是新增了C++11的内容,正如孟岩老师在第5版前言中所讲:“现 ...