A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

[学习交流] C指针的艺术(全)

© xushengbin 中级黑马   /  2016-5-12 12:47  /  14522 人查看  /  149 人回复  /   5 人收藏 转载请遵从CC协议 禁止商业使用本文

If you give someone a program , you will frustrate them for a day ; if you teach them how to program , you will frustrate them for a lifetime , but for you , maybe I just this person .我是你们的圣彬老师 , 希望接下来的一段时间能够和大家一起探讨、学习 !
C语言是一门面向过程的语言 , 相比现在比较流行的面向对象 , 它更多的让我们理解 , 体会其中的奥义 , 在学习C语言过程中 , 大家要多思考 , 多理解 , 多敲 ,往往大家会有几个症状 :
  • 上课能听懂 , 但是就是敲不出来,简称难产! 那是因为你听懂我的中国话 , 并没有听懂我的编程思想
  • 写作业代码难产 , 怎么破 :
    • 尝试把思路用伪代码的方式写出来
      • 写不出来 : 没有思路
      • 能写出来 : 有思路,但是基本语法基础不扎实
    • 对着伪代码写代码 :
      • 如果能写出来说明只是思路不清晰
      • 如果还是不行说明基础语法也不懂 所以要对症下药 , 但是唯一的药就是 先模仿 , 先学习 , 增强代码感觉 , 再敲出来 , 输入输出相结合.


最后 , 我想对哪些已经开始自学的同学说 , 可能你们会困惑 , 不懂 , 不理解 , 不会应用 , 甚至不知所云 . 可实际上 , 无论学什么 , 都是努力才可以学到真东西, 只有真正掌握技术的人 , 才有可能去享用它 , 如果你中途放弃了 , 之前所有的努力和付出都会变得没有价值 . 学习游泳难吗 ? 掌握英语口语难吗 ? 可能难 , 但在掌握了的人眼中 , 这根本不算什么 , "就这么回事呀" 只要你相信自己一定可以学得会 , 学得好 , 最终的结果一定是 , 你对着别人说 : "学编程---就这么回事 !

附件是Markdown的笔记 , 大家可以直接下来看, 有目录
指针入门
  • 什么是指针

    • 指针就是地址
  • 什么是指针变量

    • 存放地址的变量 、 变量的内容是地址
  • 为什么要用指针

直接获取/修改变量的值 :
  1. int num = 10;

  2. num = 20;

  3. 请问num的值是否有改变 ?  有
复制代码
使用指针可以 间接的获取/修改变量的值 :
  1. int num = 10;

  2. num = 20;

  3. 请问num的值是否有改变 ?  有
复制代码
认清指针 :
  1. int num = 10;
  2. int *p = &num
  3. *p = 20;
  4. 为什么  int *p = 地址 ?
  5. 而         *p = 值 ?
  6. 在定义的时候 :
  7. int *p; 的   *    是类型说明符   说明定义的这个变量是一个指针变量
  8. 在使用的时候 :
  9. *p   的 *     是操作符     p所指向的地址对应的内容
  10. 再解释一次  :
  11.     int *p;   //定义一个指针变量
  12.     p = &num ;   //  p指向num变量的地址  / p这个指针变量存放的是num的地址
  13.     *p ;        //    p指向的地址 (num的地址)  *一下 , 拿到地址对应的内容
复制代码



二级指针
What : 指针的指针就是二级指针 / 存放指针的地址的指针变量就是二级指针
  1. int num = 10;

  2. int *p = #

  3. int **p1 = &p;   // 这个就是二级指针

  4. p1  ---->   指针变量p的地址

  5. *p1  ------>   指针变量p的地址所对应的内容啊 ?  --->  &num

  6. **p1  ------>   *p / *&num  ---->  10
复制代码



指针与数组
  • 数组指针 / 指针数组

    • xxx指针 : 指向xxx的指针
      • 数组指针 : 指向数组的指针
      • 函数指针 : 指向函数的指针
      • 结构体指针 : 指向结构体的指针
      • 字符串指针 : 指向字符串的指针
      • 常量指针 : 指向常量的指针

  1. * 指针数组 :  
  2.         是一个指针还是数组 ? 数组 !  什么数组 ?  数组元素是地址(指针)的数组
  3. 学习方法 :
  4.     要学习数组指针 , 就得了解数组名的本质

  5.     我们说过使用指针就是间接的获取或者修改变量的值

  6.     那么数组指针也是同样道理


  7. int main(){

  8.     int arr[5] = {1,2,3,4,5};

  9.     int *p = arr;

  10.     //3中方法遍历数组元素

  11.     //1 . 下标法

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

  13.         printf("%d",arr[i]);

  14.     }
  15.     //2.  数组名发
  16.     for(int i = 0 ; i < 5 ; i++){

  17.         printf("%d",*(arr+i);

  18.     }

  19.     //3.  指针法
  20.     for(int i = 0 ; i < 5 ; i++){

  21.         printf("%d",*(p+i);   //  *(p+i) ==  *p++  


  22.     }

  23.     return 0;

  24. }
复制代码


总结 :
如果你能用以上3总方法遍历数组元素 , 那么这个总结由你们来写
整型数组 对比 指针数组

  1. //int类型的数组 , 数组名为arr , 元素个数为3
  2. int arr[3] = {1,2,3};



  3. //定义3个整型变量
  4. int num1 = 10 , num2 = 20 , num3 = 30;

  5. //指针数组
  6. int *pArr[3] = {&num1,&num2,&num3};

  7. for(int i = 0;i < 3 ;i++){

  8.     printf("%d\n",*pArr[i]);  //除了这个方法还有吗?


  9. /*   
  10.     pArr  数组名 : 指向的第一个元素的地址 :  那么问题来了  是不是就是&num1的 ?  肯定不是

  11.     正确的是 :
  12.         pArr  数组名 : 指向的第一个元素的地址 : &pArr[0]   

  13.         *pArr  ---->  * &pArr[0] ---> pArr[0]  --->  &num1

  14.         **pArr  ---->   * &num1  --->  num1

  15. */

  16.         printf("%d\n",**(pArr+i));

  17.         //   *pArr[i] == **(pArr+i)


  18. }
复制代码

二维数组指针
what : 指向二维数组的指针
  1. int arr[2][3] = {1,2,3,4,5,6};

  2. int (*pa)[3] = arr;

  3. for(int i = 0 ; i < 2 ; i++){

  4.     for(int j = 0 ; j < 3 ; j++){


  5.         //直接遍历 :  下标法遍历  /  数组名法遍历

  6.         printf("%d",arr[i][j]);

  7.         printf("%d",*(*(arr+i)+j));

  8.         //间接遍历 :   二维数组指针遍历
  9.          printf("%d",*(*(pa+i)+j));

  10.     }

  11. }
复制代码



指针数组进阶
  1. //二维数组
  2. int arr[2][3] = {1,2,3,4,5,6};

  3. //指针数组
  4. int *pArr[2] = {arr[0],arr[1]};


  5. //  使用二维数组直接遍历
  6. for(int i = 0 ; i < 2 ; i++){

  7.     for(int j = 0; j < 3 ; j++){

  8.         printf("%d\t",arr[i][j]);


  9.     }

  10.     printf("\n");

  11. }

  12. //   使用指针数组间接遍历

  13. for(int i = 0;i < 2;i++){

  14.     for(int j = 0 ; j< 3 ; j++){

  15.         printf("%d\n",*(pArr[i]+j);

  16.         //   pArr[i] ---> arr[i]
  17.         //   pArr[i]+j  --->  arr[i]+j
  18.         //   *(pArr[i]+j) ---->  *(arr[i]+j)  --->  arr[i][j]

  19.         //  pArr  --->   &pArr[0]
  20.         //   *(pArr+i)  --->   pArr[i]

  21.     }

  22.         //   *(pArr[i]+j)  ==  *(*(pArr+i)+j)

  23. }
复制代码





指针与函数的关系
  • 指针变量作为函数的参数
  1. 使用一个函数交换两个变量的值

  2. void changeNum(int *pNum1,int *pNum2){

  3.     int temp = *pNum1;

  4.     *pNum1 = *pnum2;

  5.     *pNum2 = temp;

  6. }


  7. int main(){

  8.     int num1 = 10 , num2 = 20;

  9.     changeNum(&num1,&num2);

  10.     //  此刻 , 你再打印num1 , num2 的值 , 看看是否有被改变 ? 答案是肯定的

  11.     return 0;

  12. }
复制代码




  • 这就是指针作为函数的参数的体现

指针函数
  • 指针函数 : 这是一个函数 , 返回值是指针(地址) 的函数
  1. #include <stdio.h>

  2. int *getArr(){

  3.     int arr[5] = {1,2,3,4,5};


  4.     return arr;
  5. }


  6. int main(int argc, const char * argv[]) {

  7.     int *p = getArr();

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

  9.         printf("%d\n",*(p+i));

  10.     }

  11.     return 0;
  12. }

  13. 大家觉得此处能正常输出吗 ? 答案是否定的

  14. int arr[5] ; 这个是一个局部变量 , 作用域结束后 , 会被释放 , 所以我们保存此地址 , 是没有意义的 , 地址对应的内容已经被释放了
复制代码



  • 如何解决 :
我这里有2个解决方案 :
方法一 :
  1. 1)
  2. int arr[5] = {1,2,3,4,5};这个地方   加  static修饰
  3. static int arr[5] = {1,2,3,4,5}; 这样 , 此数组就是静态区的, 也就是全局区 , 生命周期会延长至程序结束.
复制代码
方法二 :
  1. 2)   使用malloc方式 , 创建堆区的动态内存空间   
  2. int *p = (int *)malloc(sizeof(int)*4);
  3. *(p) = 1;
  4. *(p+1) = 2;
  5. *(p+2) = 3;
  6. *(p+3) = 4;
  7. *(p+4) = 5;

  8. 使用此方法别忘了用完以后使用free()函数手动销毁堆区申请的空间欧
复制代码



函数指针
  • 函数指针 What : 这到底是怎么鬼 ? 指向函数的指针 , 是一个指针
这个跟以后我们学习OC中的block有关联 , 我们要先尽量理解一下
先带大家回忆一下函数 :
  • 无参无返回值函数
  • 有参无返回值函数
  • 无参有返回值函数
  • 有参有返回值函数
无参无返回值函数 :需求 : 打印一行星星
  1. #include <stdio.h>

  2. void printf_Star(){

  3.     printf("******\n");

  4. }

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

  6.     //定义函数指针
  7.     //先定义 , 后赋值
  8.     void (*p)();

  9.     //赋值 此处一定是函数名
  10.     p = printf_Star;

  11.     //使用函数指针间接调用函数
  12.     p();

  13.     return 0;
  14. }

  15. 输出结果 : ******
复制代码

有参无返回值函数 :需求 : 打印3行星星
  1. void printf_Star(int n){

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

  3.         printf("******\n");

  4.     }

  5. }

  6. int main(int argc, const char * argv[]) {

  7.     //定义函数指针
  8.     //定义的同时初始化    此处一定是函数名
  9.     void (*p)(int) = printf_Star;

  10.     //使用函数指针间接调用函数
  11.     p(3);

  12.     return 0;
  13. }

  14. 输出结果 :
  15. ******
  16. ******
  17. ******
复制代码

无参有返回值函数 :需求 : 返回一个1000以内的随机数
  1. #include <stdio.h>
  2. #include <stdlib.h>

  3. int getRandNum(){

  4.     int randNum = arc4random_uniform(1000);

  5.     return randNum;
  6. }

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

  8.     //定义函数指针
  9.     //定义的同时初始化    此处一定是函数名
  10.     int (*p)() = getRandNum;

  11.     //使用函数指针间接调用函数
  12.     printf("随机数 : %d\n",p());

  13.     return 0;
  14. }
复制代码

有参数有返回值函数 :需求 : 传2个值, 返回它们的商
  1. float division(int num1,int num2){

  2.     //   int / int 得到的还是int  所以需要强制类型装换
  3.     return num1 / (float)num2;
  4. }

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

  6.     //定义函数指针
  7.     //定义的同时初始化    此处一定是函数名
  8.     float (*p)(int num1,int num2) = division;

  9.     //使用函数指针间接调用函数
  10.     printf("商为 : %.2f\n",p(67,3));

  11.     return 0;
  12. }
复制代码


指针与字符串在讲这个指针与字符串之前 , 大家要先了解一下内存的5大区域 :
  • 栈区
  • 堆区
  • 全局区(静态区) 又称之为静态区 , 其中已经初始化的全局(静态)变量在数据段(数据区) , 未初始化的全局(静态)变量在BSS段
  • 常量区
  • 代码区
当然内存的区域远不止这么简单 , 比我们认知的复杂的多 , 所以C语言内存的5大区域也可能有不同说法 , 但是都大同小异 , 但是要确保准确性
验证
  1. #include <stdio.h>
  2. #include <stdlib.h>

  3. //  全局变量  在全局区
  4. int a = 1;

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

  6.     //局部变量在栈区
  7.     int num = 10;

  8.     //malloc函数申请动态内存空间,堆区
  9.     int *p = malloc(sizeof(int)*4);

  10.     //static修饰的局部变量为静态变量 , 在静态区(全局区)
  11.     static int b = 10;

  12.     // "apple"在常量区
  13.     char *str = "apple";


  14.     printf("栈区: %p\n堆区: %p\n静态区:%p\n常量区:%p\n全局区:%p\n代码区:暂不作验证,OC阶段讲解\n",&num,p,&b,str,&a);

  15.     return 0;
  16. }

  17. 栈区: 0x7fff5fbff7cc
  18. 堆区: 0x1001015b0
  19. 静态区:0x100001024
  20. 常量区:0x100000f44
  21. 全局区:0x100001020
  22. 代码区:暂不作验证,OC阶段讲解

  23. 栈区一般都是0x7fff开头的 , 堆区 : 0x100....   后6位有值 , 之前2个0
  24. 大家注意看静态区和全局区  尾号 : 1024  --->  1020  差4个字节 , 其实静态区和全局区是一个区
  25. 全局区一般都是 0x10000xxxx   后四位有值 , 之前是4个0
  26. 常量区 : 一般都是0x100000xxx  后三位有值  , 之前都是5个0
复制代码


学习指针与字符串就3个问题 :
  • 定义字符串方式
  1. 1. 用字符数组的方式
  2.     char str[6] = "apple";


  3. 2. 用指针的方式
  4.     char *str = "apple";
复制代码


  • 分别在哪个区域
  1. 前者保存在栈区 , 数组元素为6个 , 字符串长度为5
  2. 后者保存在常量区 , 指针变量str指向了常量区的"apple"的地址,字符串长度为5
复制代码

  • 值得注意的问题
  • 前者字符串内容可改 , 比如说 str[3] = 'p' , 是可以的 , 后者 *(str+3) = 'p' , 是错误的 , 因为常量区的内容是可读不可写的 , 不可改
  1.    char str[5] = "apple";

  2.     str[4] = 'p';

  3.     printf("%s\n",str);   // 这是没问题的

  4. ----------------------------------------

  5.     char *str = "apple";

  6.     *(str+4) = 'p';

  7.     printf("%s\n",str);  //  这个是错误的
复制代码


  • 从键盘输入问题 :
  1. char str[5];

  2. scanf("%s",str);

  3. 此处正确没问题

  4. ----------------------------------

  5. char *str;

  6. scanf("%s",str);

  7. 此处错误 , 野指针错误 , str没有任何指向

  8. ------------------------------------
  9. 解决办法 :
  10. char *str = (char *)malloc(sizeif(char)*10);

  11. scanf("%s",str);

  12. 此处正确没问题
复制代码

const与指针搭配的用法
  • const与变量搭配使用
  1. const int num = 1;

  2. num++;     //错误

  3. num = 20;  //错误

  4. Cannot assign to variable 'num' with const-qualified type 'const int'
复制代码


  • 总结 : const修饰的变量 , 可读不可改
  • const与指针变量搭配使用
const 在 * 前面 : 指针的指向可以变 , 地址对应的值不可变
  1. int num = 10;

  2. const int *p = #
  3. int const *p = #

  4. 指针的指向可以变 , 地址对应的值不可变

  5. p = &num2; //这个可以

  6. 但是 *p = 30;  // 这个错误  Read-only variable is not assignable
复制代码

const 在 * 后面 : 指针的指向不可以变 , 地址对应的值可变
  1. int num = 10;

  2. int * const p = #

  3. 指针的指向不可以变 , 地址对应的值可变

  4. p = &num2 ;  //这个错误  Read-only variable is not assignable

  5. *p = 10;   //  这个可以
  6. 理解:  1.
  7. const int *p;    常量指针   指向常量的指针   所以值不能变,指向可变
  8. int const *p;

  9. int * const p;   指针常量   这是个常量      指向不可变,值可变
复制代码


最后 , 如果还是没法懂 , 就强行记忆 :
  • const 在 * 的左侧 指向可变,值不能变
  • const 在 * 的右侧 指向不可变 值可变
  • const 在 * 的两侧 指向和值都不可以变


结构体与指针
What : 指向结构体变量地址的指针
回顾结构体 : 区分结构体类型 , 结构体变量 , 结构体别名
结构体类型
  1. //结构体类型 :  这个仅仅只是定义的类型
  2. struct Car{
  3.     int lunzi; //轮子
  4.     char *pp;  //品牌
  5. };
复制代码


结构体变量
  1. //结构体类型 :  这个仅仅只是定义的类型
  2. struct Car{
  3.     int lunzi; //轮子
  4.     char *pp;  //品牌
  5. }car;

  6. 变量名为 : car
复制代码


结构体类型起别名
  1. typedef struct Car{
  2.     int lunzi; //轮子
  3.     char *pp;  //品牌
  4. }iCar;

  5. 对结构体类型 :struct Car 起了别名: iCar
复制代码

到此我们就要用指针给结构体结合使用了
只有结构体指针才有的特殊用法 : p->结构体的成员变量   
  1.   struct Car{

  2.         int lunzi;

  3.         char *pp;

  4.     }car;

  5.     struct Car *p = &car;

  6.     p->lunzi = 10;

  7.     p->pp = "BMW";

  8.     printf("lunzi = %d,pp = %s\n",p->lunzi,p->pp);
  9.     //   p->lunzi   ==   (*p).linzi
复制代码

游客,如果您要查看本帖隐藏内容请回复


149 个回复

倒序浏览
分享给更多的学习小伙伴
回复 使用道具 举报
厉害厉害,为圣彬老师点赞!
回复 使用道具 举报
  
回复 使用道具 举报
圣彬老师赞!!!小伙伴们加油~需要学习资源可以去站内的资源库去取 http://bbs.itheima.com/thread-299785-1-1.html
回复 使用道具 举报
Simpon 发表于 2016-5-12 14:53
圣彬老师赞!!!小伙伴们加油~需要学习资源可以去站内的资源库去取 http://bbs.itheima.com/thread-29978 ...

你的资源库超赞 , 超给力
回复 使用道具 举报
Simpon 发表于 2016-5-12 14:53
圣彬老师赞!!!小伙伴们加油~需要学习资源可以去站内的资源库去取 http://bbs.itheima.com/thread-29978 ...

你的资源库超赞 , 超给力
回复 使用道具 举报
Simpon 发表于 2016-5-12 14:53
圣彬老师赞!!!小伙伴们加油~需要学习资源可以去站内的资源库去取 http://bbs.itheima.com/thread-29978 ...

你的资源库超赞 , 超给力
回复 使用道具 举报
Simpon 发表于 2016-5-12 14:53
圣彬老师赞!!!小伙伴们加油~需要学习资源可以去站内的资源库去取 http://bbs.itheima.com/thread-29978 ...

你的资源库超赞 , 超给力
回复 使用道具 举报
Simpon 发表于 2016-5-12 14:53
圣彬老师赞!!!小伙伴们加油~需要学习资源可以去站内的资源库去取 http://bbs.itheima.com/thread-29978 ...

你的资源库超赞 , 超给力
回复 使用道具 举报
Simpon 发表于 2016-5-12 14:53
圣彬老师赞!!!小伙伴们加油~需要学习资源可以去站内的资源库去取 http://bbs.itheima.com/thread-29978 ...

你的资源库超赞 , 超给力
回复 使用道具 举报
Simpon 发表于 2016-5-12 14:53
圣彬老师赞!!!小伙伴们加油~需要学习资源可以去站内的资源库去取 http://bbs.itheima.com/thread-29978 ...

你的资源库超赞 , 超给力
回复 使用道具 举报
Simpon 发表于 2016-5-12 14:53
圣彬老师赞!!!小伙伴们加油~需要学习资源可以去站内的资源库去取 http://bbs.itheima.com/thread-29978 ...

你的资源库超赞 , 超给力
回复 使用道具 举报
Simpon 发表于 2016-5-12 14:53
圣彬老师赞!!!小伙伴们加油~需要学习资源可以去站内的资源库去取 http://bbs.itheima.com/thread-29978 ...

你的资源库超赞 , 超给力
回复 使用道具 举报
不错,..,,,,,,
回复 使用道具 举报
签个到。。打个卡。。
回复 使用道具 举报
签个到....
回复 使用道具 举报
好给力, 我喜欢,超赞!!!!!
回复 使用道具 举报
超给力,顶一个
回复 使用道具 举报
签个到,打个卡,洗洗码代码~~~
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马