知识导图 知识导图
首页
iOS知识
计算机软件
  • 即时通讯网 (opens new window)
  • 开发常用网站 (opens new window)
首页
iOS知识
计算机软件
  • 即时通讯网 (opens new window)
  • 开发常用网站 (opens new window)
  • MD

  • Vue

  • C语法

    • C语法Day1-函数
    • C语法Day2-运算
    • C语法Day3-结构
    • C语法Day4-指针
      • C语法Day5-预处理
      • C语法Day6-文件
      • C语法Day7-标准库
      • C语法Day8-编译
    • C++语法

    • 汇编语言

    • 软件编程及算法
    • C语法
    2023-05-28
    目录

    C语法Day4-指针

    # C指针

    指针是初学者比较难理解的一个概念,很容易绕晕。

    在理解指针之前,学者需要先了解 数据存储和内存分配的一些基本概念,

    存储介质:我们数据都是存储在硬盘和内存条等物理介质上的,这好比图书馆的书架

    数据:数据一块一块的存储好比是书架上的书

    定义一个整形变量:

    int a = 5; // 定义一个整形变量,系统将分配一块内存给变量a。而内存中存储数据5 
    

    定义一个变量a 好比在书架某个位置上放置了一本书,而数据在内存中的地址好比书架号

    通常我们根据书架号在【哪个区,哪一排,哪一列】来找到书本的,而找书通常是由【人】来找。

    那么在内存中又是如何找到对应的数据呢?

    # 内存地址&

    **&a:**获取变量a的内存地址

    int a = 5;
    printf("a的内存地址:%p ", &a);
    
    // 打印结果:a的内存地址:0x7ffde30e7ba4
    

    在变量a前添加&,即能获取变量a在内存中的内存地址0x7ffde30e7ba4 (相当于书架的编号),这个内存地址对应的好比书架的某一个盒子,而更换这个【盒子】上的书本,等同于直接修改了变量a的值

    现实中图书馆是【人】走动来找到书的,那么内存条中找书架的又是谁呢?

    # 转译内存地址值 *

    *(&a): 通过【 “指针” 】星号【 * 】来获取内存地址对用的数据值

    看一段代码:

    int a = 5;
    printf("a的内存地址:%p ", &a); // 打印a的地址 0x7ffde30e7ba4
    printf("a = %d", *(&a)); // 打印a的值5
    

    a 变量;

    &a 指 a的内存地址;

    *(&a) 指向 a的内存地址上的值 = a 的值,那么可以理解为: a = *(&a)

    # 【 * p 】 指针变量

    ⚠️ 当声明一个变量并且在变量名前使用 【*】修饰的时候,那么这个变量就表示存储对应数据类型的内存地址的对象,比如

    int a = 5;
    float b = 5.123;
    
    int *p; // 定义指针变量p
    // int 声明的指针变量p 只能存储 int 类型数据的内存地址
    p = &a;
    
    // 下面错误,类型不匹配
    p = &b;
    
    int    *ip;    // ip:一个整型的指针
    double *dp;    // dp:一个 double 型的指针 
    float  *fp;    // fp:一个浮点型的指针 
    char   *ch;    // ch:一个字符型的指针 
    
    // 指针 ip,dp,fp,ch 都是一个代表内存地址的长的十六进制数,但是地址上存储的数据都是不相同的。
    

    # 混淆解释 1

    上面两种使用【*】的场景不同,含义也是不一样的,这也是容易被指针绕晕的一个地方:

    int a = 5;
    // 第一种 转换 :*(&a) 中的 * 表示获取内存地址中的 数据
    printf("a = %d", *(&a)); // 打印a的值5
    
    
    // 第二种 指针变量:这里的 * 用在【变量声明】的时候,表示这个被 * 修饰的变量是个存储数据对应内存地址变量
    int *p; // 定义指针变量p 
    p = &a;
    

    # 混淆解释 2

    其中,指针变量p是单独的变量,只不过存储来变量a的内存地址信息。

    int a = 5;
    int *p;
    p = &a;  
    
    /*
    其中
    *p = a = 5
    
    变量p存储的值是变量a 的内存地址
    p = &a
    
    &p != &a
    */ 
    
    // 指针变量也有自己的内存地址,只不过存储的是地址数据
    printf("p 变量的地址: %p \n", &p);
    printf("a 变量的地址: %p \n", &a);
    
    // 打印结果
    // p 变量的地址: 0x7fff5c7bd670 
    // a 变量的地址: 0x7fff5c7bd668 
    

    # 【 * * p】 指向指针的指针变量

    单个星【*】定义中修饰表示指向一个数据对象的指针

    两个星【**】定义中修饰表示指向一个数据对象的指针 的 指针

    int a = 5;
    int *p;
    int **p2;
    
    p = &a;  
    p2 = &p;
    
    /*
    
    p2 = &p = p指针对象的内存地址
    
    *p2 = p = &a = a数据对象内存地址
    
    **p2 = *p = a = 5
    
    */
    

    # 空指针 NULL

    指针声明时,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值,一个0x0的不允许访问的地址。

    int  *ptr = NULL;
    // ptr 的地址是 0x0
      
    // 为确保逻辑完整型,通常需要进行非空判断
    if(ptr)     /* 如果 p 非空,则完成 */
    if(!ptr)    /* 如果 p 为空,则完成 */
    
    

    # 指针的算术运算

    前面打印内存地址时看到,内存地址是由 16 进制数据表示的 (比如 0x7ffe7840333c),也即是数据存储的位置由序号标记

    既然位置是数据类型,那么存储地址的指针变量同样可以进行算术运算 :++、--、+、-。

    下面通过一维数组打印连续对象内存地址:

    #include <stdio.h>
     
    const int MAX = ;
     
    int main () {
       int  var[] = {5, 80, 178, 4};
       int  i, *ptr;
     
       /* 指针中的数组地址 */
       ptr = var;
       for ( i = 0; i < MAX; i++) {
          printf("下标%d值 = %d; 存储地址 = %p\n", i, *ptr, ptr );
          /* 指向下一个位置 */
          ptr++;
       }
       return 0;
    }
    

    结果:

    $gcc -o main main.c -lm
    $main
    下标0值 = 5; 存储地址 = 0x7ffec2384170
    下标1值 = 80; 存储地址 = 0x7ffec2384174
    下标2值 = 178; 存储地址 = 0x7ffec2384178
    下标3值 = 4; 存储地址 = 0x7ffec238417c
    

    ⚠️ 我们发现,指针++ 移动一次,地址变化为4,刚好是int 类型的字节长度,而不是简单的+1。

    因此,指针移动是通过移动对应值类型占用内存字节长度来实现的。

    同样指针递减也是一个道理,

    递增递减都是连续内存地址,且知道数据类型对应的字节数

    # 指针的比较

    指针存储是数据信息,可以算术运算,也可以进行比较运算 ==、< 和 > 等等

    和运算一样,比较的是地址数据,而不是地址上存储的数据

    # 数组如何通过下标取值

    上面介绍了指针运算后,我们就很容易理解数组是如何取值的了

    int  var[] = {10, 100, 200, 300, 400};
    
    ⚠️:数组在内存分配的时候是一块连续的内存地址
    
    通过下标3取值 var[2]:
      首先var名分配时就存储了首地址,获取指向var[0]的地址,var[0] = 0x7fffba178d64
      现在移动2次,因为是int(int 这里4字节算),所以 + 8 ,计算结果是 0x7fffba178d6c
      这样就直接获取了var[2] 的地址,在直接 * 运算取值就好了
      
      因此,发现数组由于地址是连续存储的,可以简单计算得到对应的地址,而无需通过遍历等等复杂操作。
      
    

    # 指针数组

    // 值数组,存储数据值
    int age[3];
    
    // 指针数组,存储地址
    int *ptr[3];
    
    

    比如:

    #include <stdio.h>
     
    int main ()
    {
       int  var[4], *ptr[4]; // 声明一个指针数组ptr
       for (int i = 1; i < 4; i++)
       {  
    	  var[i] = i * 5;
        ptr[i] = &var[i]; /* 赋值为整数的地址 */
    	  printf("*ptr下标%d值 = %p; 地址上存储的值 = %d\n", i, ptr[i], *ptr[i]);
       }
       
       return 0;
    }
    

    输出结果:

    $gcc -o main *.c -lm
    $main
    *ptr下标1值 = 0x7ffd2ee55da4; 地址上存储的值 = 5
    *ptr下标2值 = 0x7ffd2ee55da8; 地址上存储的值 = 10
    *ptr下标3值 = 0x7ffd2ee55dac; 地址上存储的值 = 15
    

    # 字符串数组

    #include <stdio.h>
     
    int main ()
    {
       // 指向字符的指针数组
       const char *names[] = {
                       "张三",
                       "李四",
                       "王五",
       };
       for (int i = 0; i < 3; i++)
       {
          printf("姓名 names[%d] = %s\n", i, names[i] );
       }
       return 0;
    }
    

    输出结果:

    $gcc -o main *.c -lm
    $main
    姓名 names[0] = 张三
    姓名 names[1] = 李四
    姓名 names[2] = 王五
    

    # 函数中使用指针

    # 函数指针参数-地址传值

    #include <stdio.h>
    
    /* 函数声明 */
    void tswap(int *x, int *y);
    
    int main ()
    {
       /* 局部变量定义 */
       int a = 100;
       int b = 200;
    
       printf("交换前 x: %d; y: %d\n",a, b);
       tswap(&a, &b); //通过指针传递方式
       
       return 0;
    }
    
    /* 函数实现 */
    void tswap(int *x, int *y) 
    {
      int tmp = *x;
      x = y;
      y = &tmp;
      printf("交换后 x: %d; y: %d\n",*x, *y);
    }
    

    结果:数据发生交换

    $gcc -o main *.c -lm
    $main
    交换前 x: 100; y: 200
    交换后 x: 200; y: 100
    

    # 函数返回-指针

    #

    #include <stdio.h>
    
    /* 函数声明 */
    int * tswap(int x, int y);
    static int total;
    
    int main ()
    {
       int *t = tswap(100, 200);
       printf("函数返回时 total: %d; ret_t: %d\n",total, *t);
       *t += 100; 
       printf("修改返回后 total: %d; ret_t: %d\n",total, *t);
       
       return 0;
    }
    
    /* 函数实现 */
    int * tswap(int x, int y) 
    {
      total = x + y;
      return &total;
    }
    

    结果:

    $gcc -o main *.c -lm
    $main
    函数返回时 total: 300; ret_t: 300
    修改返回后 total: 400; ret_t: 400
    

    修改对象 t 后,全局变量 total 也同时发生改变,说明他们是引用同一个内存地址

    # 函数指针

    #include <stdio.h>
    
    void tswap(int x, int y);
    static int total;
    
    int main ()
    {
      // 函数指针声明
    	void(*p_fun)(int, int) = &tswap; // ⚠️ &tswap 中 & 可以省略
      // 通过函数指针p_fun 调用tswap 函数
      p_fun(100, 200);
       
      return 0;
    }
    
    /* 函数实现 */
    void tswap(int x, int y) 
    {
      printf("函数调用 x: %d; y: %d\n", x, y);
    }
    

    结果:

    $gcc -o main *.c -lm
    $main
    函数调用 x: 100; y: 200
    

    # 完整示例

    #include <stdio.h>
    
    /* 函数声明 */
    int * tswap(int *x, int *y);
    // 全局变量 total
    static int total;
    
    int main ()
    {
       /* 局部变量定义 */
       int a = 100;
       int b = 200;
     
       /* 调用函数来交换值
        * &a 表示指向 a 的指针,即变量 a 的地址 
        * &b 表示指向 b 的指针,即变量 b 的地址 
       */
       printf("交换前 x: %d; y: %d\n",a, b);
       
       // int *t = tswap(&a, &b); // 正常调用函数
       // 定义函数指针,并通过函数指针调用函数
       int * (*p_fun)(int *, int *) = tswap;
       
       //通过指针传递参数,通过函数指针调用函数
       int *t = p_fun(&a, &b); 
      
       printf("函数返回时 total: %d; ret_t: %d\n",total, *t);
      
       *t += 100; 
       printf("修改返回后 total: %d; ret_t: %d\n",total, *t);
       return 0;
    }
    
    /* 函数实现 */
    int * tswap(int *x, int *y) 
    {
      int tmp = *x;
      x = y;
      y = &tmp;
      printf("交换后 x: %d; y: %d\n",*x, *y);
    
      total = *x + *y;
      return &total;
    }
    

    结果:

    $gcc -o main *.c -lm
    $main
    交换前 x: 100; y: 200
    交换后 x: 200; y: 100
    函数返回时 total: 300; ret_t: 300
    修改返回后 total: 400; ret_t: 400
    

    通常我们说的指针变量是指向一个整型、字符型或数组等变量,而函数指针是指向函数。

    函数指针可以像一般函数一样,用于调用函数、传递参数。函数指针是指向函数的指针变量。

    整形变量的指针:指向内存地址,只有一个地址

    数组指针:指向数组下标0的对象地址

    函数指针:指向函数调用的地址,类似OC中的 IMP

    # 指向数组的指针

    // 声明一维数组
    double balance[50];
    
    // 1、数组名是一个指向数组中第一个元素的常量指针
    // balance 是一个指向 &balance[0] 的指针,即数组 balance 的第一个元素的地址
    
      double *p;
    	double balance[10];
    	p = balance;
    
    // 2、使用数组名作为常量指针是合法的,下面两种方式等同
     
      int value = balance[4];
    	int value = *(balance + 4);
    
    

    通过指针访问数组值:

    一旦您把第一个元素的地址存储在 p 中,您就可以使用 p、(p+1)、*(p+2) 等来访问数组元素

    #include <stdio.h>
     
    int main () {
       /* 带有 5 个元素的整型数组 */
       double balance[5] = {1000.0, 2.0, 3.4, 17.0, 50.0};
       double *p; // p表示变量(指针),*p表示一个double值
       int i; // 整形变量i
     
       p = balance; // 数组第一个元素地址指针赋值给p
     
       /* 
          balance :指向首个数据的地址(指针)
          balance+1 :指向第二个数据的地址(指针)
          
          *balance : 首个数据的地址对应的值
       		*(balance+1) :第二个数据地址对应的值
       		
       		因为数组地址是连续的,因此可以通过直接地址+1的形式获取下一个值
          以下输出数组中每个元素的值 */
       for ( i = 0; i < 5; i++ ) {
         	// 使用指针的数组值
          printf("*(p + %d) : %f\n",  i, *(p + i) );
          // 使用 balance 作为地址的数组值
          printf("*(balance + %d) : %f\n",  i, *(balance + i) );
       }
      
       return 0;
    }
    
    // 在上面的实例中,p 是一个指向 double 型的指针,这意味着它可以存储一个 double 类型的变量。一旦我们有了 p 中的地址,*p 将给出存储在 p 中相应地址的值
    

    # 指向结构体的指针

    # 定义

    1、定义指向结构的指针变量struct_pointer:

    struct Books *struct_pointer;
    

    2、结构的指针变量 赋值:

    struct_pointer = &Book1;
    

    3、结构的指针访问结构的成员,您必须使用 -> 运算符,如下所示:

    struct_pointer->title;
    

    # 实例:结构体指针作函数参数

    #include <stdio.h>
    #include <string.h>
     
    struct Books
    {
       char  title[50];
       char  author[50];
       char  subject[100];
       int   book_id;
    };
     
    /* 函数声明 */
    void printBook( struct Books *book );
    
    int main( )
    {
       struct Books Book1;        /* 声明 Book1,类型为 Books */
     
       /* Book1 详述 */
       strcpy( Book1.title, "C Programming");
       strcpy( Book1.author, "Nuha Ali"); 
       strcpy( Book1.subject, "C Programming Tutorial");
       Book1.book_id = 6495407;
    
       /* 通过传 Book1 的地址来输出 Book1 信息 */
       printBook( &Book1 );
    
       return 0;
    }
    
    // 函数实现:参数是结构体指针,传入是要么是指针变量,要么是结构体地址
    void printBook( struct Books *book )
    {
       printf( "Book title : %s\n", book->title);
       printf( "Book author : %s\n", book->author);
       printf( "Book subject : %s\n", book->subject);
       printf( "Book book_id : %d\n", book->book_id);
    }
    

    当上面的代码被编译和执行时,它会产生下列结果:

    Book title : C Programming
    Book author : Nuha Ali
    Book subject : C Programming Tutorial
    Book book_id : 6495407
    Book title : Telecom Billing
    Book author : Zara Ali
    Book subject : Telecom Billing Tutorial
    Book book_id : 6495700
    
    C语法Day3-结构
    C语法Day5-预处理

    ← C语法Day3-结构 C语法Day5-预处理→

    Theme by Vdoing
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式