第九周:指针

简介: C语言独有的东西,也是这门语言最独特的地方,在后续的数据结构中你会经常看到指针的

9.1指针

9.1-1取地址运算

运算符&

  1. scanf(("%d",&i);里的&
  2. 获得变量的地址,它的操作数必须是变量   int i;printf("%x",&i);
  3. 地址的大小是否与int相同取决于编译器   int i;printf("%p",&i);

&不能取的地址

  1. &不能对没有地址的东西取地址,需要有明确的变量
  2. &(a+b)可以
  3. &(a++)不行
  4. &(++a)不行

试试这些&

  1. 变量的地址      被分配在相邻紧挨着的地方
  2. 相邻的变量的地址
  3. &的结果的sizeof
  4. 数组的地址
  5. 数组单元的地址
  6. 相邻的数组单元的地址

9.1-2 指针

scanf

  1. 如果能够将取得的变量的地址传递给一个函数,能否通过这个地址在那个函数内访问这个变量? 可以
  2. scanf("%d",&i);
  3. scanf()的原型应该是怎么样的?我们需要一个参数能保存别的变量的地址,如何表达能够保存地址的变量?

指针

就是保存地址的变量

  1. int i
  2. int* p = &i;
  3. int* p,q;
  4. int  *P,q;
  5. 这个*不管是靠近int还是靠近p,最后实际指向的都是p

指针变量

变量的值是内存的地址

  1. 普通变量的值是实际的值
  2. 指针变量的值是具有实际值的变量的地址

作为参数的指针

  1. void f(int *p);
  2. 在被调用的时候得到了某个变量的地址:int i = 0;f(&i);
  3. 在函数里面可以通过这个指针访问外面的这个i

访问那个地址上的变量*

  1. *是一个单目运算符,用来访问指针的值所表示的地址上的变量
  2. 可以做右值也可以做左值
  3. int k = *p;   *p = k + 1;

*左值之所以叫左值

  1. 是因为出现在赋值号左边的不是变量,而是值,是表达式计算的结果;
  2. a[0] = 2; *p = 3;
  3. 是特殊的值,所以叫做左值

指针的运算符&*

1.互相反作用

  1. &yptr  ->(&yptr) -> *(yptr的地址) ->得到那个地址上的变量 ->yptr
  2. &yptr  ->&(yptr) ->&(y) ->得到y的地址,也就是yptr ->yptr

传入地址

为什么 int i;scanf("%d",i);编译没有报错?

但是运行会报错,只是刚好凑巧你传进去的整数大小跟地址大小一样,编译会拿你传进去的整数当做地址去运行,将整数传到了不该传的地方去了,所以运行就一定会出错

指针应用场景一

交换两个变量的值

voidswap(int*pa, int*pd)

{

   intt=*pa;

   *pa=*pb;

   *pb=t;

}

指针应用场景二

  1. 函数返回多个值,某些值就只能通过指针返回
  2. 传入的参数实际上是需要保存带回的结果的变量

指针应用场景二b

  1. 函数返回运算的状态,结果通过指针返回
  2. 常见的套路是让函数返回特殊的不属于有效范围内的值来表示出错:-1或0(在文件操作会看到大量例子)
  3. 但是当任何数值都是有效的可能结果时,就得分开返回了
  4. 后续的语言(c++,java)采用了异常机制来解决这个问题

指针最常见的错误

定义了指针变量,还没有指向任何变量,就开始使用指针了

9.1-3指针与数组

传入的数组成了什么?

intisPrime( intx;intknownPrimes[],intnumberOfKnownPrimes)

{

   intret=1;

   inti;

   for( i=0; i<numberOfknownPrimes;i++){

       if( x%knownPrimes[i] ==0){

           ret=0;

           break;

       }

   }

   returnret;

}

函数参数表中的数组实际上是指针

  1. sizeof(a) == sizeof(int*)
  2. 但是可以用数组的运算符[]进行运算

数组参数

以下四种函数原型是等价的

  1. int sum(int*ar,int n);
  2. int sum(int*,int);
  3. int sum(int*ar[],int n);
  4. int sum(int[],int);

数组变量是特殊的指针

数组变量本身表达地址,所以

  1. int a[10];int*P = a; //无需用&取地址
  2. 但是数组的单元表达的是变量,需要用&取地址
  3. a == &a[0]

[]运算符可以对数组做,也可以对指针做:

  1. p[0]<==>a[0]

*运算符可以对指针做,也可以对数组做:

  1. *a = 25;

数组变量是const的指针,所以不能被赋值

  1. int a[] <==> int *const a = ....

9.1-4 指针与const

指针可以是const,值也可以是const

指针是const

表示一旦得到某个变量的地址,不能再指向其他变量

  1. int *const q = &i;   //q是const   q指向i这个事实不能被改变
  2. *q = 26; //OK
  3. q++;error

所指是const

表示不能通过这个指针去修改那个变量(并不能使得那个变量成为const)

  1. const int*p = &i;
  2. p = 26; //error!(p)是const
  3. i = 26; //ok
  4. p = &j;  //ok

判断哪个被const了的标志是const在*的前面还是后面

  1. int i;
  2. const int*  p1 = &i;
  3. int const*  p2 = &i;
  4. int  *const p3 = &i
  5. 2跟3其实是一样的,指针所指向的东西不可被修改
  6. 4则是表示指针不可被修改

转换

  1. 总是可以把一个非const的值转化成const的

void f(const int* x);

int a = 15;

f(&a);//ok

const int b = a;

f(&b);//ok

b = a + 1;//error!

当要传递的参数的类型比地址大的时候,这是常用的手段:既能用比较少的字节数传递值给参数,又能避免函数对外面的变量的修改

const数组

  1. const int a[] = {1,2,3,4,5,6,};
  2. 数组变量已经是const的指针了,这里的const表明数组的每一个单元都是const int
  3. 所以必须通过初始化进行赋值

保护数组值

因为把数组传入函数时传递的是地址,所以那个函数内部可以修改数组的值

为保护数组不被函数破坏,可以设置参数为const

  1. int sum(const int a[],int length);

9.2指针运算

9.2-1 指针是可计算的

1+1=2?

  1. 给一个指针加1表示要让指针指向下一个变量

int a[10];

int *P = a;

*(p+1)——>a[1];

  1. 如果指针不是指向一片连续分配的空间,如数组,则这种运算没有意义

指针计算

这些算术运算可以对指针做:

  1. 给指针加、减一个整数(+,+=,-,-=)
  2. 递增递减(++/--)
  3. 两个指针相减

*p++

  1. 取出p所指的那个数据来,完事之后顺便把p移到下一个位置去
  2. *的优先级虽然高,但是没有++(单目运算符)高
  3. 常用于数组类的连续空间操作
  4. 在某些CPU上,这可以直接被翻译成一条汇编指令

指针比较

  1. <,<=,>,>=,!=都可以对指针做
  2. 比较它们在内存中的地址
  3. 数组中的单元的地址肯定是线性递增的

0地址

  1. 当然你的内存中有0地址,但是0地址通常是个不能随便碰的地址
  2. 所以你的指针不应该具有0值
  3. 因此可以用0地址来表示特殊的事情:
  1. 返回的指针是无效的
  2. 指针没有被真正的初始化(先初始化为0)
  1. NULL是一个预定定义的符号,表示0地址
  1. 有的编译器不愿意你用0地址来表示0地址

指针的类型

  1. 无论指向什么类型,所有的指针的大小都是一样的,因为都是地址
  2. 但是指向不同类型的指针是不能直接相互赋值的
  3. 这是为了避免用错指针

指针的类型转换

  1. void*表示不知道指向什么东西的指针
  1. 计算时与char*相同(但不相通)
  1. 指针也可以转换类型
  1. intp = &i;voidq = (void*)p;
  1. 这并没有改变p所指向的变量的类型,而是让后人用不同的眼光通过p看他所指的变量
  1. 我不在当你时int啦,我认为你就是个void!

用指针来做什么?

  1. 需要传入较大的数据时用作参数
  2. 传入数组后对数组做操作
  3. 函数返回不止一个结果
  1. 需要用函数来修改不止一个变量
  1. 动态申请的内存...

9.2-2动态内存分配

输入数据

  1. 如果输入数据时,先告诉个数,然后再输入,要记录每个数据
  2. int a = (int)malloc(n*sizeof(int));

malloc

#include

void*malloc(size_t size);

  1. 向malloc申请的空间是以字节为单位的
  2. 返回的结果是void*,需要类型转换为自己需要的类型(比如int)
  1. (int)malloc(nsizeof(int))

没空间了?

  1. 如果申请失败则返回0,或者叫做NULL
  2. 可以自己测测看自己电脑系统能给多少空间

free()

  1. 把申请得来的空间还给"系统"
  2. 申请过的空间,最终都应该要还的
  1. 出来混的,迟早都是要还的
  1. 只能还申请来的空间的首地址
  2. free(0)?

free()常见问题

  1. 申请了没free->长时间运行内存逐渐下降
  1. 新手:忘了
  2. 不够老的老手:找不动合适的free的时机
  1. free过了再free
  2. 地址变过了,直接去free

9.2-3 函数间传递指针

好的模式

  1. 如果程序中要用到动态分配的内存,并且会在函数之间传递,不要让函数申请内存后返回给调用者
  2. 因为十有八九调用者会忘了free,或找不到合适的时机来free
  3. 好的模式是让调用者自己申请,传地址进函数,函数再返回这个地址出来

在同一个地方malloc和free

除非函数的作用就是分配空间,否则不要再函数中malloc然后传出去用

函数返回指针

  1. 返回指针没问题,关键是谁的地址?
  1. 本地变量(包括参数)?函数离开后这些变量就不存在了,指针所指的是不能用的内存
  2. 传入的指针?没问题
  3. 动态申请的内存?没问题
  4. 全局变量->以后会解释

函数返回数组

  1. 如果一个函数的返回类型是数组,那么它实际返回的也是数组的地址
  2. 如果这个数组是这个函数的本地变量,那么回到调用函数那里,这个数组就不存在了
  3. 所以只能返回(和返回指针是一样的)
  1. 传入的参数:实际就是在调用者那里
  2. 全局变量或者动态分配的内存
目录
相关文章
|
人工智能 编译器 程序员
第八周:数组
数组的魅力才刚刚开始,在其他地方你都能看到他
92 0
|
C++
2015级C++第8周项目 数组和指针
【项目1 - 数组作数据成员】参考解答   下面是设计好的一个工资类(Salary): class Salary { public: void set_salarys( );//输入职工工资(输入-1标志着工资输入结束),工资保存到salary数组中,实际人数保存到number中; void add_salarys(int x); //给每个
1072 0
|
C++
2014秋C++第19周 项目 单链表/枚举
课程主页在http://blog.csdn.net/sxhelijian/article/details/39152703,课程资源在云学堂“贺老师课堂”同步展示,使用的帐号请到课程主页中查看。 【项目1 - 动态链表体验】下面是一个建立动态链表的程序。阅读程序,在草稿纸上画出链表建立的过程,借此学会如何建立链表。然后按要求改造程序。#include &lt;iostream&gt; usi
1346 0
|
C++ 开发工具 IDE
2014秋C++第16周 项目 指针和数组、函数
课程主页在http://blog.csdn.net/sxhelijian/article/details/39152703,课程资源在云学堂“贺老师课堂”同步展示,使用的帐号请到课程主页中查看。  【项目1-数组的排序】按给定部分程序的要求,用指向数组的指针变量作为形式参数编写函数,完成排序。重点体会:(1)排序算法,可以是冒泡,也可以是选择;(2)用指向数组的指针变量作为形式参数,用数组名(
1255 0
|
C++
2014秋C++ 第15周项目 指针基础
课程主页在http://blog.csdn.net/sxhelijian/article/details/39152703,课程资源在云学堂“贺老师课堂”同步展示,使用的帐号请到课程主页中查看。  【项目1-打入“内部”寻“内幕”】下面是一段使用了指针的程序。请利用“单步”执行的方式运行程序,深刻理解“指针变量存储的是地址值”、“通过指针变量间接访问”等概念。将在执行过程中留下深刻映像的界
1040 0
|
测试技术 C++
2013级C++第17周项目——字符串、指针应用
课程主页在:http://blog.csdn.net/sxhelijian/article/details/11890759 【项目1-体会函数参数传递】 1、下面三段程序都试图通过定义函数,实现实际参数在必要时的交换,哪些能够实现,哪些不能实现?通过运行程序和单步执行,让自己对这一过程更加清楚,然后请在博文中给出明确的回答,并用你自己的话展开描述。) (1) #include &
1246 0
|
C++
2013级C++第16周项目——初识指针
课程主页在:http://blog.csdn.net/sxhelijian/article/details/11890759 第一部分 程序分析   阅读下面的程序,先写出其运行结果,再运行对比。  读不懂怎么办?支招1:按课堂上老师讲课的方式,将变量对应内存的“框子”画出来,用大脑当CPU,写出变量的变化过程;支招2:单步执行程序,在监视(watch)窗口中观察变量的动态变化,从而掌握程
1190 0
|
C++
C++第5周项目4 - 指针操作学生类
课程首页地址:http://blog.csdn.net/sxhelijian/article/details/7910565,本周题目链接:http://blog.csdn.net/sxhelijian/article/details/8723847 【项目4】设计一个学生类Student,包括学号(num)和成绩(score)。建立一个对象数组,通过初始化,设置5个学生的数据,要求:  (
1037 0
|
C++ iOS开发
C++第2周项目3——用结构体数组作计算
课程首页地址:http://blog.csdn.net/sxhelijian/article/details/7910565,本周题目链接:http://blog.csdn.net/sxhelijian/article/details/8635385 【项目3-用结构体数组作计算】接项目2,(1)从文件中读出数据,存放到你定义的结构体数组中,即项目2(1);(2)求出每名同学的总分(可以在读
1198 0
|
C++
C++程序设计-第2周结构体应用
第一部分 程序阅读   下面的程序建立起了如图所示的动态链表。阅读程序,在草稿纸上画出链表建立的过程,借此学会如何建立链表。可以通过单步执行以辅助理解 #include &lt;iostream&gt; using namespace std; struct Student { long num; float score; struct Student *next; }; int m
1552 0