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

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© xushengbin 中级黑马   /  2016-5-12 12:47  /  19968 人查看  /  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 个回复

正序浏览
it is a good source
回复 举报
没看到啊,奇怪
回复 举报
nane ,怎么看不到了
回复 举报
看看这个,写的好详细
回复 举报
指针感觉好难的样子
回复 举报
好!!支持!感谢分享!
回复 举报
老师太给力了!!
回复 举报
不错,学习了
回复 举报
这个相当给力啊
回复 举报
大神请收下我的膝盖
回复 举报
好好好好
回复 举报
haoduoya .
回复 举报
论坛真是学习资料宝库啊,之前,没有好好利用。
回复 举报
这块正好不会呢,来的太及时了.我去看去啦
回复 举报
圣彬老师大赞!!!我们会努力~
回复 举报
强强强强强强强强强强强强强强强!
回复 举报
很好很轻大,给你100个赞!
回复 举报
排队报名中
回复 举报
签个到,加油加油
回复 举报

签个到,打个卡,洗洗码代码~~~
回复 举报
您需要登录后才可以回帖 登录 | 加入黑马