嵌入式0基础开始学习 ⅠC语言(7)指针

0.问题引入

        int a = 5;   
        a = 1024; //把1024存放到变量a的地址中去
        b = a; // 取变量a的值,赋值给b
          ===>在c语言中,任何一个变量,都有两层含义
                (1)代表变量的存储单元的地址:变量的地址 ===> lvalue
                (2)代表该变量的值: ===> rvalue
                
             对于变量的访问,只有两种情况
             read: 从变量的地址中取值
             write : 把一个数值,写到变量的地址中去
            
        问题:
              如果我们知道了一个变量的地址,是不是我们就可以通过该变量的地址去访问这个变量
                    可以的
                如何通过一个变量的地址去访问这个变量呢?
                   ====> 指针                

1.对象的访问方式

       (1)直接访问:通过对象名去访问
            直接访问有一个缺陷?
                 直接访问受对象的作用域的影响(限制)
                //   局部变量不是永久的,函数返回的时候,局部变量就会被删除
                
       (2)间接访问:通过对象的地址去访问,指针访问
            只要你知道了对象的地址,就可以去任何地方访问它,不受作用域的影响       

2.什么是指针?指针的概念            

        存储单元的地址:
                  分配给每一个变量的内存单元都有一个编号,这个编号就是我们说的存储单元的地址,
                  并且是按字节来编址。
        
        在c语言中,指针的概念与地址的概念差不多,你可以认为指针就是一个地址,一个变量的地址
        我们也称为变量的指针。
        
        & 取地址符:
                单目运算符, ”取xxx对象的地址“
                
        通过一个对象的指针去访问它,首先要解决 对象的指针(地址)的存储问题
        这个时候需要定义一个变量来保存它的地址,这个变量我们称为”指针变量“       

3.指针变量

        什么是指针变量?
                指针变量也是一个变量,也是用来保存数据的,只不过指针变量是用来保存其他对象的地址的。
        如何定义一个指针变量?
              回顾:   普通变量的定义
                               变量的类型    变量名;
                        在定义指针变量的时候,为了区分在指针变量的前面加一个*,来表示他是一个指针变量
                        
            ====》
                 指针变量的定义:
                        指向对象的类型 * 对象名
                        
                        ”指向对象的类型“:
                                指针变量指向的对象的类型,不是指针的类型。
                                ”指向“:
                                         假如一个变量p保存了变量a的地址,
                                         那么,我们就说p指向a
                                例子:
                                        int a  = 1024;
                                         p = &a ;   // 把a的地址赋值给p
                                                    // p保存了对象a的地址
                                                    // p 指向a
                                                    // p ---> 指针变量
                                                 这个p该如何定义?
                                                      指向对象的类型 * 指针变量名
                                                       typeof(a) * p
                                                       ===> int * p;
                            练习:
                                    char c[10];
                                    char * p;
                                    p = &c[9];
                                  分析过程    
                                           p保存了c[9]的地址;
                                           ====> p指向c[9]
                                                p是一个指针变量

4.与指针相关的运算符

            & :取地址符
            * :指向运算符
                    单目运算符
                    用法:
                            * 地址   <=> 地址对应的那个变量(对象)
                        int a;
                        * a  // ERROR
                        
                    例子:
                           int a = 1024;
                           *(&a)  ===>  *对象a的地址
                                  ===>  地址对应的那个对象                                                              
                            
                            int b = *(&a);  <==> b = a
                            
                        so
                            *(&a) <==> *&a <==> a
                            * & ===>可以直接约掉
                            
                            NOTE:
                                 & *a <==> a  ???? 不可以
                                 & * 不能约
                        
                        char ch ;
                        * (&ch) = 'A'; // *(&ch) ==>ch ,在此处代表的是变量ch的左值,”变量的地址“
                        char c = *&ch; // *(&ch) 在此处代表的是变量ch的右值,”变量的值“
                                                        
                代码分析:
                            int a = 5;
                            假如我们要定义一个变量p,来保存a的地址,
                            int *p;
                            p = &a;                                                  
                                      // 把变量a 的地址赋值给p
                                        p是一个指针变量
                                        那么,它也有左值,右值
                                        p的右值:p的存储单元中的内容  ===> &a
                                        p的左值:p本身存储单元的地址  ===> &p
                                        
                                        * p :在此处p代表 p的右值,p的值
                                            * p <==>*(&a) <==> a;
                                            a = 1024; <==> *(&a) ==>1024 ==> *p =1024
                                            
                        请大家写一个程序,来证明p的右值就是&a?                    
                            #include <stdio.h>
                            int main()
                            {
                               
                            }
               

5.指针变量作为函数的参数

            练习:  如下函数该这么设计?该怎么调用
                    void func(int *x,int *y)
                    {
                      int temp;
                      temp = *x; // ===> temp = *(&a)
                      *x  = *y; // *(&a) = *(&b)
                      *y = temp; // *(&b) = temp                       
                       
                    }
                    void func1(int *x,int *y) // 不可以的   
                    {
                      int *temp;
                      temp = x;
                      x= y;
                      y = temp;
                    }                    
                    int main()
                    {
                        int a = 5;
                        int b = 6;
                        
                        int *p = &a;
                        int *q = &b;
                        
                        func(p,q); //调用这个函数的目的是:为了交换变量a和b的值
                        
                        printf("a==%d\n",a);//6
                        printf("b==%d\n",b);//5
                        
                    }                    
        传的还是”值“,传的还是”实参的值“,
        ”实参的值可能是某个对象的地址“

                例子:
                        void func(int *a,int *b)
                        {
                            int *t; //定义了一个指针t,但是t没有赋值,不知道这个t保存了
                                      哪个对象的地址;不代表它里面没有值
                                      如果是一个指针变量的话,不知道它指向的对象是谁
                                      *t  ==>t指向的那一个对象
                                           如果我们去操作*t ,分为两种情况
                                           read :
                                                  int m = *t; //有可能t指向的那一个对象不可读。
                                                  // 可能导致内存的非法访问 =>段错误
                                           write:
                                                   *t = 1024;//有可能t指向的那一个对象不可写
                                                  // 可能导致内存的非法访问 =>段错误
                                                   
                                                 
                                           
                            *t = *a; //可能导致”段错误“
                            *a =*b;
                            *b = *t; //可能导致”段错误“
                        }                        
            像这个例子中的t这样的指针,我们称之为”野指针“。
            野指针:
                   指向一个未知单元(未知对象)的指针,称之为”野指针“
                   使用野指针,可能造成内存的非法访问 ===> 段错误
                   int *p;
                // *p = 1024;  属于操作野指针
                // int b = *p ; 属于操作野指针
                   int a;
                    p = &a; // 不属于操作野指针

            空指针:
                    NULL
                   在计算机中,地址为0的存储空间是不存在的
                   如果一个指针的值为0(NULL),表示这个指针指向了
                   空(NULL),像这中指向0(NULL)的指针,我们称之为
                   空指针
                   int *p = NULL;
                       p它不是野指针
                       
                段错误原因:内存的非法访问
                         (1)数组越界,可能导致段错误
                              int a[10];
                              a[100] = 1024;
                         (2)非法使用指针(指针乱指)(野指针)

6.  数组与指针

        数组元素与普通变量是一样的,也是有自己的地址。
        数组元素也有左值和右值,并且数组元素的地址相邻的。
      ===>数组名可以代表首元素的地址(首地址)。
         例子:
                int a[5];
                      a是一个数组名, 数组名a当作指针来看: a <===> &a[0]
                      我要定义一个指针变量p,来保存a[0]的地址
                      该如何定义p?
                      
                       int *p = &a[0] //OK
                             如果把数组名a当作指针来看, a ===> &a[0];
                             <===> p = a; // OK
                             a[0] = 1024;
                             <===> *p = 1024;
                             <===> *a = 1024;
                            
                    p是指向a[0],那么能不能通过p去访问a[1]?
                              可以的
                              
                        *p   ===>*&a[0] ==>a[0]
                        a[0]和a[1]的地址是相邻的
                               p + 4 ==&a[1] ????不对的
                        p + 1 ===> &a[1]
                              通过指针p来访问a[1];
    
    指针做加减的问题: int a[5]
            p + i  (p是一个指针,i是一个整数)
                    不是简单的加减值,而是加减i个指向单元的长度
            p+1  ===> 往后面挪了一个指向单元的长度
                   p = > &a[0]
                   p+1 => 往后面挪了一个int单元的长度
                        (p+1) = >&a[1]
            
            例子:
                 有p = &a[0],把数值100赋值给a[0],有多少种表示方法?
                   int a[10];
                   int *p = &a[0];
                   
                   a[0] = 100;
                   *p = 100;
                   *&a[0]  = 100;
                   *a = 100;
                   **&p = 100;
                   **&a = 100;
                   p[0] = 100;
                   *(p+0) = 100;
                   *(a+0)= 100;
                   *(&a[1]-1) = 100;
                   .....
                   
            练习:
                    int a[10] = {1,2,3,4,5,6,7,8,9,10};
                    
                    int *p = &a[2];
                    通过指针p去访问每一个元素的值,将其依次输出。

7.多维数组与指针

            (1)在C语言中所有数组都是一维数组
            (2)数组名可以当作指向第0个元素类型的指针常量,
                 并且数值上为第0个元素的首地址
                
                假如有:
                        int a[3][4]
                           //int [4] a[3]
                           a[0] _ _ _ _
                           a[1] _ _ _ _
                           a[2] _ _ _ _
                           
                    表达式      表达式的含义
                      a           (1)当作指针
                                      &a[0]
                                  (2)代表整个数组
                    
                     a[0]          a[0]又是一个一维数组名
                                      (1)代表为a[0]的整个数组
                                      (2)当作指针
                                             &a[0][0]
                                            
                    &a[0][0]       元素a[0][0]的地址                                            
               -------------------------------------------------                
                     a+1           数组名a当作指针
                                       ==> &a[0] + 1
                                       ==> &a[1]
                                        取整个一维数组a[1]的地址
                    &a[1]          取整个一维数组a[1]的地址

                     &a              数组名a只能代表整个数组
                                      &a:取整个二维数组的地址

                     &a+1           数组名a代表整个数组
                                    &a+1 : 往后面挪了整个二维数组a的长度(12个int)
                ------------------------------------------------
                    a[1]+2         a[1]是一个一维数组名,只能当作指针
                                   ===>    &a[1][0] + 2
                                   ===> &a[1][2] :元素a[1][2]的地址
                    
                    *(a+1)+2     *(&a[0]+1)+2
                                 ==> *(&a[1])+2
                                 ==> a[1] + 2
                                   ==> &a[1][0] + 2
                                 ==> &a[1][2] :元素a[1][2]的地址
                ------------------------------------------------
                    *(a[1]+2)      a[1]是一个一维数组名,只能当作指针
                                  ==> *(&a[1][0] + 2)
                                 ==> *(&a[1][2])
                                 ==>a[1][2]:元素a[1][2]
                    
                    *(*(a+1)+2)  ==> *(*(&a[1])+2)
                                 ==>*(a[1]+2)
                                 ==>*(&a[1][0]+2)
                                 ==>*(&a[1][2])
                                 ==>a[1][2]:元素a[1][2]
                    
                    指针常量:
                             指针本身不能够改变,但是指向的空间里面的内容是可以改变的
                             如: 数组名作为指针
                             int m,n;
                             int *const a = &m; // a就是一个指针常量
                             *a = 1024;// ok
                             a = &n ; // ERROR
                            
                    常量指针:
                            是一个指向常量的指针,指向的对象是常量,那个对象是不能改变的
                            但是指针是可以改变的(也就是说可以保存其他的地址)
                            如:  字符串指针
                            
                            int m = 1024,n;
                            const int *b = &m;  ===> int const *a;
                            *a = 250;//ERROR
                            a = &n ; // ok
                           

8.指针数组与数组指针

            (1) 指针数组
                    指针数组是一个数组,只不过它里面的每一个元素都是相同类型的指针罢了!!!
                        定义数组:
                                  数组元素的类型   数组名[元素个数]
                                  
                            例子:
                                  int * p[4]; //指针数组
                                         //定义了一个数组,数组名为p,里面含有4个元素
                                           每一个元素类型都是 int *.
            (2) 数组指针
                    数组指针是一个指针,只不过这个指针使用来指向一个数组的
                    (这个指针保存的地址是一个数组的地址罢辽!!!)
                           例子:
                                 int a[4];
                                 您能不能定义一个指针p来保存数组a的地址呢?
                                       指针定义:
                                                 指向对象的类型 * p;
                                                 typeof(a) *p;
                                                 int [4] *p;
                                                 =>int (*p) [4];// p是一个指针,它指向的对象是一个int[4]类型的数组
                                                
            练习:1.
                    int a[10] = {1,2,3,4,5,6,7,8,9,10};
                    printf("a = %p, a + 1 = %p, &a + 1 = %p\n",
                    a, a + 1, &a + 1);
                    ......
                    
                    
                   2.分析如下程序的输出结果。
                    int a[5] = {1,2,3,4,5};
                    int * ptr = (int *)(&a + 1);
                    
                    printf("%d  %d\n", *(a + 1), *(ptr - 1));//2 5
                    --------------------------------------
                    int a[5] = {1,2,3,4,5};
                    int * ptr = (int *)&a + 1;
                    
                    printf("%d  %d\n", *(a + 1), *(ptr - 1)); // 2 1
                    
                    3.
                        int b[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
                            //int[4] b[3]
                            //b[0] _ _ _ _
                            //b[1] _ _ _ _
                            //b[2] _ _ _ _
                        
                        printf("**b = %d, *(b[0] + 1) = %d\n",
                            **b, *(b[0] + 1) );  
                            
                            **b : b当作一个指针
                                ===>**(&b[0])
                                ===>*b[0]  //b[0]又是一个数组名,当指针来看
                                ===>*(&b[0][0])
                                ===>b[0][0]
                                
                            *(b[0]+1):把b[0]当指针来看
                                ===> *(&b[0][0]+1)
                                ===>b[0][1]
                               
                                  
                            
                    
                    4. 有int b[3][4]; 假如要定义一个指针变量p,
                        来保存b[0][0]的地址,该如何定义?
                                //int[4] b[3]
                                //b[0] _ _ _ _
                                //b[1] _ _ _ _
                                //b[2] _ _ _ _
                                
                            typeof(b[0][0]) *p;
                             ====> int *p;
                             p = &b[0][0];
                                 //typeof(p) ==> int *
                             p = b[0]; // ok

9.字符串指针          

            
        字符串是什么?
            字符串就是一串字符,在c语言中是没有字符串类型的。
            C语言的字符串是通过 char *(字符型指针)来实现的。
            C语言的字符串,是用”“(双引号)引起来的一串字符来表示。
            并且字符串后面默认会加一个’\0',表示字符串结束的标志。
                   如;
                        ”abcde“  ==>5
                        "1"     ==> 1
                        '123'   不是字符串,也不是字符啊
                        '\012'  不是字符串 ,是字符                        
                        "\012"    ==>是字符串,有一个字符  
                        ""         ==>是字符串,空串    
                
            只需要保存字符串的首地址(首字符的地址)就可以了    
            从首地址开始找到第一个’\0',前面的这些字符就是字符串里面的字符
                C语言中的字符串(如:”sssssss“)保存在一个叫做.rodata(只读数据)的内存区域中。
 
            如;
                "12345"
                在程序运行时,系统会为这个字符串在.rodata中分配6个字节大小的空间给它
                
                ”12345“的值,就是首字符的地址。
                typeof(”12345“)
                    ====》typeof(&’1‘)
                    ====》typeof('1') *
                    ====> const char *

            例子:
                    char *p = "12345";
                        p保存的是字符串首字符的地址,&’1‘
                        那么咱们是不是可以通过指针p来访问’1‘呢
                             可以
                          因为: p = &’1‘;
                          char m = *p ; <===> m = '1'
                          *p = 'A';//ERROR  因为p指向的对象是一个常量,不可以改变的
                              字符串就相当于一个常量指针
                     
                    问题:
                          字符’1‘和’2‘的地址是不是连续的呢?
                                是连续的
                            既然是连续的,那么咱们也可以通过指针p来访问’2‘
                            
                                p = &’1‘;
                                p+1 ===>往后面挪了一个char类型的单元长度   《===》 &’2‘
                                *(p+1) ==> *(&'2') ===> '2'
                                printf("%c\n",*p); // 1
                                printf("%c\n",*(p+1));// 2
                                
                                p+= 1; // OBJK  
                                ==>
                                     p = p+1;
                                     p = &'2';
                                *(p+1) = 'B';//ERROR
        
        字符数组:
                char s[5] ={'a','b','c','d','e'};
                    sizeof(s) == 5
                    char ch = *(s+1); <==> ch = s[1]
                    *(s+1) = 'B' ; // <===> s[1] = 'B'
                    s+= 1; //ERROR ==》 s = s+1 因为s是一个指针常量(数组名作为指针)为指针常量

                char s[] = {'a','b','c','d','e'};
                     sizeof(s) == 5;
                     可以省略元素个数
                char s[] = {"abcde"};
                      <===>  char s[] = {'a','b','c','d','e','\0'};
                             sizeof(s) = 6;
                            *(s+1) = ’B‘ ; // 可以,因为s是一个字符数组,数组区域是可读可写的
                            printf(”%s\n“,(s+1)); //Bcde    
                            
                            %s -> char *
                            把后面的哪个地址(指针)当作是一个字符串的首地址,一个一个字符的输出,
                            直到遇到'\0'结束,’\0' 不打印    

            练习;
                    1.分析如下程序的输出结果
                      
                        p = "abcde";
                        printf("%s\n",p);//abcde

                        char s[] = "12345";
                        s[1] = ’B‘;
                        printf("%s\n",s);//1B345

                    2.写一个函数,用来求一个字符串的长度(包含了多少个有效字符)
                        (1)确定函数名
                            My_Strlen : 用来求一个字符串的长度
                        (2)确定参数列表(形参列表)
                            const char *s
                        (3)确定返回值的类型
                            返回值:
                                    有效字符的个数(int)
                        (4)代码的实现

                            int My_Strlen(const char *s)
                            {
                               int count = 0;
                               while(*s) // *s !='\0'
                               {
                                  count ++;
                                  s++;
                               }
                               return count;
                            }    
                            int main()
                             {
                                 char q="123\0a0345";                                  
                                 scanf("%s",q);
                                // int a ;
                                // a = My_Strlen();
                                // printf(a)
                                printf("%d\n",My_Strlen((const char *)q));
                             }

10.几个常用的字符串的处理函数(标准库里面的)

(1)strlen :用来求一个字符串的长度

             #include <string.h>

             int strlen(const char *s);
            
             @s : 要计算长度的那一个字符串的首地址
             const char * 类型: 表示在程序运行的过程中,指针指向的字符串不能被修改
             返回值:  返回字符串的有效字符个数(不包括‘\0’);
            
             例子:
                    strlen("abcde") == 5
                    char s[4] = {'1','0'};
                        sizeof(s) == 4
                        strlen(s) == 2
                    strlen("abcd\nabc") == 8
                    strlen("123\123abc\0abc") == 7
                    strlen("123\01a3abc\0abc") == 9
                    strlen("123\000abc") ==3
                    strlen("123\0x123") == 3
                    strlen("123\x123456gad") == 7

(2)strcpy/strncpy

        字符串拷贝函数
          NAME
       strcpy, strncpy - copy a string

SYNOPSIS
       #include <string.h>

       char *strcpy(char *dest, const char *src);
         strcpy: 用来把src指向的字符串,拷贝到dest指向的空间中去,直到遇到'\0'结束。
        
         @dest: 目的地(dest必须是一个可写的空间)
         @src : 从哪里来
         返回值:
                 返回拷贝之后,目的地字符串的首地址
                
            例子:
                  char s[6];
                  strcpy(s,"12345");
                  printf("%s\n",s); //12345
                  
                  char *p = "abcde";
                  char *q = "12345";
                  strcpy(p,q); //ERROR
                  
            strcpy 有一个小小的BUG!!!
            因为他没有考虑到数组越界的问题,有可能会导致内存的非法访问
            
         char *strncpy(char *dest, const char *src, size_t n);
            
            strncpy: 为了解决strcpy的这个bug的情况,功能是类似的,只不过
                      它顶多拷贝n个字符到 dest
                      
                      到底拷贝多少字符?(<=n)
                      (1)遇到了\0拷贝结束的,此时\0也会被拷贝
                      (2)已经拷贝了n个字符(后面的\0不会自动拷贝,除非最后一个字符是\0)
                      
                例子:
                      char s[10];
                      strncpy(s,"1234567890",10);
                      
                      strncpy(s,"123",10);
                      
                练习:  
                      char s1[8]
                      char s2[8] = {"ABCDE"};
                      strncpy(s1,"123456789",8);
                      
                      printf("%s\n",s1);
                      printf("%s\n",s2);

(3)strcmp/strncmp

            字符串的比较函数
            那么字符串该如何比较勒?
                        一个一个字符进行PK对应的ASCII的值
                                if  c1 >c2
                                        返回 >0
                                if  c1 < c2
                                        返回 < 0
                                if  c1 == c2
                                        则继续比较下一个字符,
                                        如果全部相等则返回0

                            strcmp("123","ABC"); <0
                            strcmp("123","123\0ABC"); ==0
                            strcmp("1234","123"); >0
                           
                            char *s = "GONG";
                            strncmp(s,"GONGJIANWEN",4) ==0

                 #include <string.h>

                   int strcmp(const char *s1, const char *s2);

                   int strncmp(const char *s1, const char *s2, size_t n);
                   
                            strncmp它的功能与strcmp类似,
                            只不过它只比较s1,s2前面的n个字符
                            
(4)strcat/strncat

            NAME
       strcat, strncat - concatenate(连接) two strings

SYNOPSIS
       #include <string.h>

       char *strcat(char *dest, const char *src);

       char *strncat(char *dest, const char *src, size_t n);
                    
                     strcat: 用来把src指向的字符串,拷贝到dest指向的字符串的末尾
                         
                     @dest :指向目标字符串(一段可写的空间)
                             dest指向的存储空间必须要足够的大,why?(越界)
                     @src  :指向原始字符串(表示将要被拷贝的那一个字符串)
                     返回值:
                             如果成功,返回连接后的字符串的首地址dest
                             如果失败,返回NULL

                例子:
                      char s1[12] = {"ABCD"};
                      strcat(s1,"12345");
                      printf("%s\n",s1);//ABCD12345
                        
                        当然这个函数也有一个小小的BUG,你懂的
                        so strncat 就是用来修复strcat的这个bug的    
        
                    strncat :
                              把src指向的字符串拷贝到dest指定的字符串的末尾
                              但是最多拷贝n个字符
                            (1)遇到\0拷贝结束,此时\0也会被拷贝
                                    char s1[12] = {"ABCD"};
                                    strncat(s1,"12345",8);//ABCD12345
                            (2)如果要是没有遇到\0,但是以及拷贝了n个字符了,也结束
                                    char s1[12]={"ABCD"};
                                      strncat(s1,"123456789",8); //ABCD12345678

            练习:
                    分析如下程序的输出结果
                            char s[12];
                            strcat(s,"12345");
                            printf("%s\n",s); //随机的值
                            
                    写一个函数,把一个十进制的数字字符串,转成一个整数
                                "12345"
                                        =>12345
                                "-12345"
                                        =>-12345
                                "+12345"
                                        =>12345                                
                        
                            /*
                                my_atoi : 将一个十进制的数组字符串,转换成一个整数
                                @s :指向十进制数组字符串的  const char *s
                                返回值:  整数值
                            */
                            int my_atoi(const char *s)
                            {
                                 int minux = 0; //符号   0 --负数   1 -- 正数
                                 int d = 0; // 保存当前位的数值
                                 int num = 0; //整数的值
                                 if(*s == '-')
                                 {
                                    minus = 0;
                                    s++;
                                 }
                                 else if(*s == '+')
                                 {
                                    minus = 1;
                                    s++;
                                 }
                                 else
                                 {
                                    minus = 1;
                                 }
                                 while(*s) // *s!='\0'
                                 {
                                    d = *s-'0';
                                    num = num *10 +d;
                                    s++;
                                 }
                                 if(minus==0)
                                 {
                                    num = num *(-1);
                                 }
                                 return num;
                            }
                            int main()
                            {
                               char s[256];
                               scanf("%s",s);
                               printf("%d\n",my_atoi((const char *)s);
                               //printf("%d\n",atoi());
                            }    

                其实呢,atoi这个函数是标准库中的一个函数,大家是可以直接去用的,
        NAME
atoi, atol, atoll - convert a string to an integer

SYNOPSIS
       #include <stdlib.h>

       int atoi(const char *nptr);
       long atol(const char *nptr);
       long long atoll(const char *nptr);

        在c语言中,不仅是变量,数组有地址,其实我们的函数也是有地址的。
        只要是有地址的东西,那么我们就可以定义一个指针变量去保存这个地址
        并且可以通过这个这个指针去访问指向的对象。
        
             函数地址 ----> 函数指针       

11.函数指针

        什么是函数指针?
                函数指针也是一个指针,只不过这个指针指向的是一个函数
                也就是说这个指针保存了函数的地址
                
        (1)函数指针该如何定义
               指针定义:
                        指向对象的类型 * 指针变量名;
                函数的类型如何表述?
                        函数的类型三要素:
                                函数的返回值类型  (函数的参数类型列表)
                            例子:
                                  int sum(int a,int b);
                                  int (int , int)===>这个是一个类型,是用来描述一个类似与sum函数的!
                               
                                  int * abc(int a,float b)
                                  {}
                                  描述abc的类型:
                                                函数的返回值类型 (函数的参数类型列表)
                                                    int * (int,float)
                                                    ====> 是一个返回int*型,带一个int和float的函数类型

                                                定义一个指针变量q,用来保存abc的地址:
                                                        指向对象的类型 *q;
                                                        typeof(abc) *q;
                                                        ===>int * (int ,float) *q;
                                                        ===>int * *q(int , float);
                                                        ===>int * (*q)(int,float);

                                  需要定义一个指针变量p,来保存函数sum的地址,该如何定义呢?
                                            int (*p)(int,int);
                                            指向对象的类型 *p;
                                            typeof(sum)* p;
                                            ===>int (int,int)* p;
                                            ===>int (*p)(int ,int);
                                
                        函数指针的定义方法;
                                    指向函数的返回值类型 (*指针变量名)(指向函数的形参类型列表);
                                例子;
                                   1.  char ** func(void)
                                       {}
                                      请大家定义一个函数指针变量p,来保存函数func的地址
                                        char ** (*p)(void);
                                        
                                   2.请各位大佬定义一个指针变量q,来保存如下函数的地址
                                       数组名a作为函数的参数,是当作指针来看的
                                       => &a[0]   typeof(&a[0]) ==> int *
                                       //int arr_sum(int * a,int n)
                                     int arr_sum(int a[],int n);
                                     {}
                                       => int (*q)(int *,int);
                                       
        (2)该怎么将函数的地址赋值给函数指针变量呢?
                  p -> 函数指针
                  p = 函数的地址
                函数的地址怎么去获取呢?
                    &对象名 ==> 取对象的地址

                函数的地址:
                        &函数名
                        or
                        函数名:在c语言中,函数名本身就代表函数的首地址
                    例子:
                            int sum_array(int *a,int n)
                            {}
                            //定义一个函数指针p
                            int (*p)(int *,int);
                            //将函数sum_array的地址赋值给p;
                            p = &sum_array;
                            //or
                            p = sum_array;
                                        //此时p指向函数sum_array

         (3)怎么通过函数指针取访问这一个函数呢?
                    函数调用:
                              函数名(实参列表);
                    a,
                         p = sum_array;
                         sum_array(a,5);
                        <==> p(a,5);

                    b,
                         p = &sum_array;
                         *p = *&sum_array;
                         sum_array(a,5);
                         ===>(*p)(a,5);
                        
                结论:    通过函数指针去调用指向的函数,有两种方式
                             p为函数的指针
                             (1)p(实参列表)
                             (2)(*p)(实参列表)
                    
                    练习;
                          1.首先写一个函数用来求一维数组中所有元素之和
                            然后在main中定义一个指针p,通过p去调用sum_array.
                            
                            #include <stdio.h>
                            //int sum_array(int a[],int n)
                            int sum_array(int *a,int n)
                            {
                                int i,sum=0;
                                for(i=0;i<n;i++)
                                {
                                   sum += a[i];
                                }
                                return sum;
                            }
                            int main()
                            {
                               int m[10] = {0,1,2,3,4,5,6,7,8,9};
                               int (*p)(int *,int);
                               p = &sum_array;
                               int n = *p(m,10);
                               printf("%d\n",n);
                            }

12.数组作为函数的参数的问题

            数组作为函数的参数的话,数组名都是当作指针来看!!!
            
            把数组作为函数的形参:
                    一维数组
                               a,数组元素类型 数组名[] , 元素个数
                               b,数组元素类型 * 指针变量名 ,元素个数

                    二维数组
                               a,数组元素类型 数组名[][列数] ,行数
                               b,数组元素类型 (*指针变量名)[列数],行数
                                                        
                    练习:
                          首先写一个函数(sum_2_array)用来求二维数组中所有元素之和,
                          然后,在main中定义一个函数指针q,通过q来调用sum_2_array.                          
                            int sum_2_array(int (*a)[4], int m)
                            {
                                int i, j ;
                                int sum = 0;
                                for(i = 0; i < m; i++)
                                {
                                    for(j = 0; j < 4; j++)
                                    {
                                        sum += a[i][j];
                                    }
                                }

                                return sum;
                            }
                            int main()
                            {
                                int m[10] = {0,1,2,3,4,5,6,7,8,9};

                                //定义一个函数指针p,来保存sum_array
                                int  (* p)(int *, int );

                                //将函数的地址赋值给 p
                                p = &sum_array; //p = sum_array

                                //通过p来调用sum_array函数
                                int n = p(m, 10);
                                printf("%d\n", n);
                                int x = (*p)(m, 10);
                                printf("%d\n", n);

                                int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};

                                //定义一个函数指针q,来保存sum_2_array
                                int  (*q)(int (*)[4], int);

                                //将函数的地址赋值给 q
                                q = &sum_2_array; //q = sum_2_array

                                //通过函数指针q来调用它所指向的那一个函数
                                int y = q(a, 3);
                                //int y = (*q)(a, 3);
                                printf("%d\n", y);
                            }
             
                保存普通变量地址的  变量 ==> 指针变量
                指针变量也有地址,那么保存指针变量的地址的  变量 ==> 二级指针   

13.二级指针以及多级指针

                int a= 1024;
                可以定义一个指针变量p来保存a的地址
                    int *p;
                    p = &a; //p指向a

                p本身也有地址,我们可以定义一个指针变量p2来保存p的地址:
                    typeof(p) *p2;
                    int * *p2;
                         // p2二级指针,它保存的是一个一级指针的地址
                         //要分清楚到底是几级指针,怎么去区分?
                         // QTMD,你只要知道他是一个指针,(保存的是谁的地址)就可以了
                    p2 = &p; //p2指向p
                    **p2 = **&p = *p = *&a = a

14.main 函数的参数的问题

        int main()
        {}    
    在linux下面,程序运行的时候,可以带参数的,只不过所有的参数都当作是字符串来处理
        如:
              ”./a.out“ "123""456""789"......
              参数多个字符串 ==>字符串的数组
              
              char * argv[] ={"./a.out","123","456”,“789”};
              
            在运行main函数的时候,可以把上面的那个数组传递给main函数的
            so
            在linux下面的c语言程序的main的参数可以有如下定义:
            元素个数,数组元素类型 数组名[]
            or
            元素个数,数组元素类型 * 指针变量名
            
        ==》
             main函数的写法:
                     int main(int argc,char *argv[])
                     {}
                     or
                     int main(int argc,char **argv)
                     {}
        ==》
            在程序运行时,给main函数传递的参数,它在main函数中是如何表示的呢?
              ./a.out 123 456
                    int main(int argc,char *argv[])
                    {
                        //char *argv[] = {"./a.out","123","456","789"};
                        //int argc = 4;
                        //argv[0] -> "./a.out";
                        //argv[1] -> "123";
                        //argv[2] -> "456";
                        //argv[3] -> "789";
                    }
                    
            练习:
                1.  请大家写一个程序,将main函数的参数一个一个全部输出,
                    并且打印main的参数个数
                  int main(int argc,char **argv)
                  {
                     printf("argc==%d\n",argc);
                     int i;
                     for(i=0;i<argc;i++)
                     {
                        printf("argv[%d]=>%s\n",i,argv[i]);
                     }
                  }
                  
              2.  请各位大佬们,输入两个整型数据字符串,
                  完成两个整数相加
                  
                  ./a.out 123 456
                  ==> 579
                  
                  /*
                if(argc != 3)
                {
                    printf("Input error\n");
                    return 0;
                }

                int sum = atoi(argv[1]) + atoi(argv[2]);
                printf("sum == %d\n", sum);
                */
               

15.动态内存分配函数

    main()
    {
       int n;
       scanf("%d",&n);
       int a[n];  // 动态数组,根据用户的输入,数组元素的个数动态改变的。
                  //上述方法,在一些编译器中可能不被认可
                  // 那么,咱们还有没有其他的办法呢?
    }
    
    malloc/realloc/calloc
    free
    
    NAME
       malloc, free, calloc, realloc - allocate and free dynamic memory

SYNOPSIS
       #include <stdlib.h>
             malloc: 用来动态分配一个size大小的空间,并且把分配到内存空间的首地址返回。
                     malloc分配的空间的生存期,是随进程持续性。malloc分配的空间一旦分配
                     给你,它就不会自动释放,一定要你自己去调用free 或 你的进程消亡了。
                    
                 void *malloc(size_t size);
                     @size:
                            要分配的内存空间的大小
                    返回值:
                            成功返回分配的内存空间的首地址
                            失败返回NULL。
                            
            例子:
                  1.  int a[n];
                     //开辟了sizeof(int) * n 连续的内存空间
                     //现在相当于a数组名当作指针来看,a指向了n*sizeof(int)的连续的内存空间
                    
                     <=> int *a =(int *)malloc(n * "乘号" sizeof(int))
                       // 现在a就相当于是有n个int类型元素的数组啦。
                       // a就相当于数组名
                       
                  2. char *p = (char *)malloc(100);
                        // p指向了一个100字节的内存空间
                        // 通过p指针操作去访问这段空间。
                        
                    char ch;
                     p = &ch;
                        // p指向ch,那么,上面100字节的空间就没有任何指针指向它了
                        //这100个字节的内存是不是就访问不了,但是这段内存他是存在的。
                        //并且也不能够去分配给别人。。这样的内存我们称之为垃圾内存。
                        // 把这样的一种现象称之为“内存泄露”
                        
                        malloc 可能会导致内存泄露(产生一些垃圾内存)
                        比如说,上述的例子    
             ----------------------------------------------------
                    free:用来释放ptr指向的内存的    
                         void free(void *ptr);        
                        @ptr:必须是malloc/realloc/calloc 这三个函数申请的内存
                              释放后的这部分数据可能存在并且维持原来的值,也有可能被清空
                              或者也有可能被修改为了其他的值。释放之后,本质上来说,是不被
                              允许去访问那块内存了,因为那块内存不属于你了,但是有些编译器
                              还是允许去访问,但是呢访问的结果是不确定的。
                        so,建议大家  将内存free掉了之后,立马将那个内存的指针指向NULL。    
              ----------------------------------------------
    calloc : 作用类似与malloc,也是用来分配动态内存空间的
             不过他是数组分配函数,既然是数组,那么这个函数带有两个参数

             void *calloc(size_t nmemb, size_t size);
                  @nmemb : 表示分配多少个数组元素
                  @size :表示每一个元素占多少个字节
                  so,它总共分配的连续空间大小为:nmemb * size
                  
                calloc(n,size) <==> malloc(n*size)
                
            例子:
                   int a[10];
                        ==> int *a = (int *) calloc (10,sizeof(int));
                        ==> int *a = (int*)malloc(10*sizeof(int));
                    
                    char ch[5];
                       ==>char *ch =(char*)calloc(5,sizeof(char));
                       ==>char *ch =(char*)malloc(5*sizeof(char));
                -----------------------------------------------
    realloc: 用来把ptr指向的动态内存,拓展到size大小
          void *realloc(void *ptr, size_t size);  
                @ptr: 由malloc/realloc/calloc 返回的动态内存的地址
                @size: 将原来的空间,拓展到size大小

                    ptr = NULL  
                            realloc(NULL,size) <=> malloc(size)
                    ptr != NULL
                            拓展
                           1.size 》 原来大小
                                       realloc 用来把ptr指向的内存,扩展到size字节,原来的内存内容保持不变,
                                       后面新增的内容不会初始化
                                         
                           2.size == 0
                                    realloc(ptr,0) <===> free(ptr)
                           3.size < 原来大小                           
                                     这种情况,作者都没有考虑,这种行为结果是未定义的(什么结果都可能会发生)
                                    
                练习: 咱们在一个函数中,值可以有哪些方式来传递?
                       (1)对象的指针
                       (2)通过函数的返回值
                       (3)全局变量
                       
                        请大家设计一个函数,分配指定的内存块(100bytes)
                        #include <stdio.h>
                        char *q = NULL;
                        void func_1(void)
                        {
                             q = (char*)malloc(100);
                        }
                        int main()
                        {
                            char *p =NULL;
                            //调用func函数,目的是让p指向100bytes的首地址
                            func_1();
                            p = q;
                            free(p);
                        }

练习:
        1.实现strncpy函数
         
         char * my_strncpy(char *dest ,const char *src , int n )
         {
               int i ;
               for(i=0;i<n&&src[i]!='\0';i++)
               {
                  dest[i] = src[i];
               }
               while(i<n)
               {
                   dest[i] = '\0';
                   i++;
               }
               return dest;
         }
        
        2.实现strcmp函数
         #include <assert.h>
        
        int my_strcmp(const char * str1,const char * str2)
        {
             //assert :断言
            assert(str1!=NULL && str2 != NULL);
            int ret =0;
            while(!(ret = *str1-*str2))
            {
                 str1++;
                 str2++;
            }
            if(ret > 0)
            {
                ret = 1;
            }
            else if(ret<0)
            {
                ret = -1;
            }
            return ret;
            
        }

        3.n个人围成一圈,编号从1到n,从编号为1的人开始报数,报到m的人离队,
          下面的人接着从1开始报数,依次下去,写程序求最后剩下一个人的编号!
            /*
                 Baoliu_num : 剩下的一个人的编号是几
                 @a : 数组名
                 @n : n个人
                 @m : 报道m离队
                 返回值:    返回剩下的那一个人的编号
            */
            int Baoliu_num(int *a,int n,int m)
            {
                 int out= 0;   //记录已经离队多少
                
                 int count = 0; // 记录当前的报数
                 int i;
                 int *p = a;
                 for(i = 0;i<n;i++)
                 {
                     *(p+i) = i+1;
                 }
                 i = 0; //i为目前报道第几人
                 while(out<n-1)
                 {
                     if(*(p+i)!=0)
                     {
                        count ++;
                     }
                     if (count == m)
                     {
                         coun t =0;
                         *(p+i) = 0;
                         out++;
                     }
                     i++;
                     if(i==n)
                     {
                        i = 0;
                     }
                 }
                 for(i=0;i<n;i++)
                 {
                    if(*(p+i)!=0)
                    {
                       printf("%d\n",*(p+i));
                       break;
                    }
                 }
            }
            
        4. 分析如下代码是否正确,为什么?
        
        (1)
            int  main()
            {
                char a;
                char * str = &a;
                strcpy(str, "hello");
                printf("%s\n", str);
                
                return 0;
            }
            错误的,在调用strcpy的时候,出现了越界,一旦越界就可能导致段错误
            造成”段错误“,后面的程序就不会被执行,没有输出结果。
        (2)
            char * GetMemory(void)
            {
                char p[] = "hello world";
                
                return p;
            }
               
            int main()
            {
                char * str = NULL;
                str = GetMemory();
                printf("%s\n", str); //
            }
            
            编译错误:数组是不能进行返回
            返回的是指向”栈空间“的指针,函数结束,栈空间内存已经被收回,
            那么那块内存可能就不能够被访问了,而且内存的内容是不可知的。
            

        (3)
            char * s = "AAA";
            printf("%s", s);
            s[0] = 'B';
            printf("%s", s);
            
            因为”s[0]='B'“,s指向的是一个 .rodata的只读空间
            不能进行写的操作
            

        (4)
            swap(int * p1 , int * p2)
            {
                int * p ;
                *p = *p1;
                *p1 = *p2;
                *p2 = *p;
            }
            p是一个野指针,后面的操作都是在操作野指针,
            操作野指针,可能会导致内存的非法访问,出现段错误

           (5)

下列程序的输出结果是?(5)

int main()

{

char a[10]={9,8,7,6,5,4,3,2,1,0},*p=a+5;

printf("%d",*--p);

}

(6)下列程序的运行结果是?  (3  6)

void fun(int *a,int *b)

{

int *k;

k=a;

a=b;

b=k;

}

int main()

{
int a=3,b=6,*x=&a,*y=&b;

fun(x,y);

printf("%d %d",a,b,);

}

(7)以下程序的输出结果是?(7)

#include <stdio.h>

#include<string.h>

main()

{

char b1[8]="abcdefg",b2[8],*pb=b1+3;

while(--pb>=b1)

strcpy(b2,pb);

int  l=strlen(b2);

pritntf("%d\n",strlen(b2));

}

(8)若有一些定义和语句,错误的是?  (A)

#include <stdio.h>

int a=4,b=3,*p,*q,*w;

p=&a;q=&b;w=q;q=NULL

A,*q=0   B,w=p; C,*p=a D,*p=*w;

(9)以下程序的输出结果是?  (空格)

int main()

{

char *p="abcdefgh",*r;

long *q;

q=(long*)p;

q++;

r=(char*)q;

printf("%s\n",r);

}

  • 8
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值