C语言-指针

变量指针:变量的地址

指针变量:存放指针的变量,本质是变量

数组指针:指向数组的指针,本质是指针

指针数组:存放指针的数组,本质是数组

指针

 #include <stdio.h>
 /**
 * 指向一维数组的指针
 */
 int t_p1()
 {
     // 创建一个一维数组
     int arr[] = {10,20,30,40,50};
     // 计算数组大小
     int len = sizeof(arr) / sizeof(arr[0]);
     // 创建一个数组指针指向一维数组arr
     int (*p)[len] = &arr;
     // 借助数组指针遍历数组
     for (int i = 0; i < len; i++)
     {
     // p 指向 arr这个数组,p存储了arr这个数组的地址
     // 如果通过指针访问数组:*p *和[]在一起,[]的优先级大于*
     printf("%-4d", (*p)[i]);
     }
     printf("\n");
 }
 /**
 * 指向二维数组的指针
 */
 int t_p2()
 {
     // 创建一个二维数组
     int arr[][3] = {
     {10,20,30},
     {100,200,300},
     {1000,2000,3000}
     };
     // 获取行和列的容量
     int row_len = sizeof(arr) / sizeof(arr[0]);
     int col_len = sizeof(arr[0]) / sizeof(arr[0][0]);
     // 方式1,二维数组指针指向二维数组 不推荐
     int (*p)[][3] = &arr;
     // 遍历数组
     for (int i = 0; i < row_len; i++)
     {
     for (int j = 0; j < col_len; j++)
     {
     printf("%-6d", (*p)[i][j]);
     }
 }
 printf("\n");
 // 方式2,一维数组指针指向二维数组,本质上是一体维数组指针指向二维数组的行(默认首行)推荐
 // &arr:获取该二维数组的地址,范围作用于整个数组
 // arr:数组名默认指向第一个元素,这里就是行,默认首行,范围作用于整个行,等价于 &arr[0]
 // 数组参与指针运算,会降级为指针
 int (*p1)[3] = arr;
 for (int i = 0; i < row_len; i++)
 {
 for (int j = 0; j < col_len; j++)
 {
 printf("%-6d",p1[i][j]);
 printf("%-6d",(*(p1+i))[j]);
 printf("%-6d",*(p1[i]+j));// 列偏移
 printf("%-6d",*(*(p1+i)+j));
 }
 }
 printf("\n");
 }
 ​
 int main(int argc,char *argv[])
 {
     t_p1();
     t_p2();
 }

数组指针

 ( ) > [] > *

表示形式含义地址/值
a二维数组名,指向一维数组arr[0],0行首地址2000
a[0],(arr+0),a0行0列元素地址,数组降级为指针2000
a + 1,&a[1]1行首地址2008
a[1],*(a + 1)1行0列元素a[1][0]的地址2008
a[1]+2,*(a+1)+2,&a[1][2]1行2列元素a[1][2]的地址2012
*(a[1]+2),*(*(a+1)+2),a[1][2]1行2列的元素的值元素值为:10

注:二维数组,数组整体的地址值 == 数组中0行元素的地址值 == 数组中0行0列的元素的地址值

例:用指向元素的指针变量输出二维数组元素的值

 #include <stdio.h>
 ​
 int main() 
 {
     int a[3][4] = {10,20,3,40,100,200,300,400,1000,2000,3000,4};
     
     int *p = a[0];   // | *(a+0) | *a ;
     
     //获取元素的个数 = 行容量 * 列容量
     int len =(sizeof(a)/sizeof(a[0])) * (sizeof(a[0]) / sizeof(a[0][0]));
     
      //使用单层循环遍历二维数组
      for(; p <a[0] + len ; p++)
      {
         //每4个一行 p - p1 = (p地址 - p1地址) / sizeof(type) 
          if((p-a[0]) % 4 == 0 && p != a[0])  printf("\n");
          
          printf("%-6d",*p); 
       } 
       
       printf("\n");
       
     return 0;
     
 }

例:数组指针-输出二维数组任意行任意列的元素的值

 #include <stdio.h>
 ​
 int main()
 {
     int a[2][3] = {12,11,10,13,16,15};
     
     int (*p)[3] = a;
     
     int row,col;
     
     printf("请输入行号和列号:\n");
     scanf("%d,%d",&row,&col);
     
     printf("a[%d][%d] = %d,%d,%d,%d\n",row,col,*(*(p+row)+col),(*(p+row))[col],*(p[row]+col),p[row][col]);
     
     return 0;
 }

指针数组

指针数组是一个数组,数组中每一个元素都是一个指针

特点:

1.先有指针,后有数组

2.指针数组的本质是一个数组,只是数组中的每一个元素是指针

 数据类型 *数组名[容量];

例:

 ​
 #include <stdio.h>
 ​
 int main()
 {
     int a = 10,b = 20, c = 30;
     
     //先有指针,后有数组
     int *p[3] = {&a,&b,&c};
     
     int len = sizeof(p) / sizeof(p[0]);
     
     for(int i = 0;i < len;i++)
     {
         printf("%-3d",*p[i]);
     }
      
     return 0;
  } 

建议:我们一般使用指针数组处理字符串

数组指针和指针数组的区别

对比项指针数组数组指针
定义数组元素均为指针的数组指向一个完整数组的指针
存储内容存储多个指针,每个元素指向不同内存地址存储单个指针,指向一个完整的数组(首地址)
内存分配每个指针元素独立分配内存,可能分散指向的数组内存是连续的,指针本身存储数组的首地址
语法示例int *a[5] 元素为5个int*指针int (*a)[5] 指向5个int的数组的指针
访问方式通过下标访问指针元素,再解引用:*a[i]先解引用指针得到数组,再访问元素:(*a)[i]
使用场景管理多个独立指针(如字符串数组,动态结构体数组)操作多维数组(如传递二维数组的行指针)
内存布局[a1] -> 数据1 [a2] -> 数据2 ... a ->[数据1][数据2]...
示例代码int a =1,b=2;int *a[] = {&a,&b};int a[2][3]={1,2,3,4,5,6},int(*a)[3] = a

字符数组与字符指针

在C语言 中表示一个字符串有以下两种方法:

1.数组形式:用字符数组存放一个字符串

2.指针形式:用字符指针指向一个字符串

例:

 #include <stdio.h>
 //方式一:使用字符数组实现字符串
 ​
 void test1()
 {
     char a[] = "I LOVE YOU";
     //数组名是一个常量,不支持再赋值
     //a = "YI YI";  //编译报错,常量不支持修改,替代方案:strcpy(a,"YI YI"); 
     printf("%s\n",a); //数组在传参时,会被降级为指针
      
 }
 ​
 //方式二:使用字符指针指向字符串
 void test2()
 {
     char *a = "I LOVE YOU";  //指针a指向一个字符串常量 
     //改变a的指向
     a = "YI OI";
     
     printf ("%s\n",a); 
  } 
  
  int main()
  {
     test1();
     test2();
     
     char b[100]; 
     char *a = b; //局部变量,如果未初始化,默认是随机值,如果是指针变量,地址值就是随机 
     
     printf("请输入一个字符串:\n");
     scanf("%s",a);
     printf("%s\n",a);
  }

注意:字符数组和字符指针变量都能实现字符串的存储与运算。(字符指针---> 字符类型的指针变量)

字符数组和字符指针的联系

字符数组由元素组成,每个元素中存放一个字符;而字符指针(指向char类型的指针变量)中存放的是地址;

只能对字符数组中的各个元素赋值,而不能用赋值语句对整个字符数组赋值

 char a[3] = {};   //等价于{'\0'};等价于{0};
 a [2] = 'A';     //正确,对字符数组的元素赋值
 ​
 a = {'e','a','c'};  //错误,数组名是常量,不能对其进行整体赋值

字符数组名虽然代表地址,但是数组名的值不能改变,因为数组名是常量

 char b = 'A';
 char a[50] = {};
 ​
 char *p = a;  //指针p指向数组第一个元素,p存储的是a数组中的第一个元素的地址
 p = &b;     //改变指针p的指向,使其指向变量a,p存储的是a的地址
 ​
 a = &b; //数组名虽然是指针,但是数组名同时也是常量,所以不能赋值

 ​

对于字符串中字符的存取,可以用下标法,也可以用指针法。

 
#include <stdio.h>
 int main(int argc,char *argv[])
 {
     // 使用两种方式创建字符串
     char str1[] = "你好,双哥哥!";
     char *str2 = "你好,豪哥哥!";// 0x11
     // 赋值测试
     // str1 = "你好,强哥哥!"; // 错误,数组一旦创建,就无法改变其值。
     str2 = "你好,帅哥哥!";// 0x12
     // 打印测试
     printf("%s,%s\n", str1, str2);
     // 测试从控制台获取一个字符串
     // char *str;// 此时默认值是NULL,NULL对应的空间地址0x00000000,这块空间拒绝访问
     // printf("请输入一个字符串:\n");
     // scanf("%s",str);
     // printf("输出-%s\n",str);// 输出NULL
     // 注意:从控制台接收一个字符串只能用字符数组
     char *b = "I LOVE YOU!";
     printf("%c,%c\n%c,%c\n%s,%s\n",a[2],*(a+2),b[2],*(b+2),a+2,b+2);// L,L L,L
     1 2
     return 0;
 }

 ​

字符串作为形参

实参与形参都可以是字符数组(在函数内边不能在对字符串常量中的字符做出修改

 void fun(char str[], int len) {..}
 void main()
 {
 char str[] = "hello";
 int len = sizeof(str) / sizeof(str[0]);
 fun(str, len)
 }

实参用字符数组,形参用字符指针

 void fun(char *str, int len) {..}
 void main()
 {
 char str[] = "hello";
 int len = sizeof(str) / sizeof(str[0]);
 fun(str, len)
 }

形参和实参都是字符指针。(在函数内部不能对字符串常量中的字符做修改)

 #include <stdio.h>
 void fun(char *str, int len) {
     printf("%s\n", str);// 0x1000 str依然指向“hello"这个常量空间
     // *(str+1) = 'E';// 编译错误 不能修改"hello"这个常量空间的数据
     // str[2] = 'L'; // 编译错误
     str = "zhangsanfeng"; // 0x2000 此时并没有改变常量空间的数据,只是改变了指针的指向
     printf("%s\n", str);
     }
     void main()
     {
     char *str = "hello"; // 0x1000 str指向的"hello"是一个常量空间,常量空间不支持修改
     int len = sizeof(str) / sizeof(str[0]);
     fun(str, len); // 0x1000
 }

实参是字符指针,形参是字符数组(在函数内部不能对字符串常量中的字符做修改)

 #include <stdio.h>
 ​
 void fun(char str[], int len) {
     printf("%s\n", str);// 0x1000 str依然指向“hello"这个常量空间
     // *(str+1) = 'E';// 编译错误 不能修改"hello"这个常量空间的数据
     // str[2] = 'L'; // 编译错误
     str = "zhangsanfeng"; // 此时并没有改变常量空间的数据,只是改变了指针的指向
     printf("%s\n", str);
 }
     void main()
 {
     // char str[] = {'h','e','l','l','o','\0'}; // 这个可以看做是字符串变量,这个是支持修改元素
     char *str = "hello"; // 0x1000 str指向的"hello"是一个常量空间,常量空间不支持修改
     int len = sizeof(str) / sizeof(str[0]);
     fun(str, len);// 0x1000
 }

注:

  1. 字符数组在创建的时候,会在内存中开辟内存空间,内存空间可以存放字符数据;字符指针在创建的时候,需要依赖于字符数组,字符指针在内存开辟的内存空间中,存放的是数组元素的地址。字符指针的创建依赖于字符数组,字符数组可以独立存在,而字符指针不能独立存在。

2.字符数组可以初始化,但是不能赋值;字符指针可以初始化,也可以赋值。

 char str1[] = "hello"; // 对数组初始化
 str1 = "hi"; // 对数组赋值,此时错误
 ​
 str1[0] = 'H'; // 对数组中的元素赋值,此时正确

例:字符指针作为函数参数:用函数调用实现字符串的复制和长度

 
#include <stdio.h>
 /**
 * 定义一个函数,实现字符串拷贝
 * @param source 拷贝的源字符串,该字符串不能被修改
 * @param dest 需要拷贝的目标数组
 * @return 字符串的长度
 */
 int _str_cpy(const char *source, char *dest)
 {
     // 定义一个循环变量
     register int i = 0;
     // 遍历循环
     while (source[i] != '\0')
     {
     // 实现拷贝
     *(dest + i) = *(source + i); // 等价于 dest[i] = source[i];
     i++;
     }
     // 拷贝结束,一定要给dest中插入\0
     *(dest + i) = '\0';
     return i;
 }
 int main(int argc,char *argv[])
 {
     char source[20],dest[20];
     printf("请输入一个字符串:\n");
     scanf("%s", source);
     int size = _str_cpy(source, dest);
     printf("字符串:%s的长度是%d\n", dest, size);
     return 0;
 }

例:字符指针作为函数的参数-给定一个字符串,截取start到end之间的字符串,含头不含尾

 #include <stdio.h>
 /**
 * 定义一个函数,实现字符串的截取
 * @param source 源字符串(字符数组、字符串常量、字符指针)
 * @param start 开始位置
 * @param end 结束位置
 * @param dest 目标数组(字符数组)
 * @return 目标字符串长度
 */
 int str_substr(const char *source, int start, int end, char *dest)
 {
     register int i = 0, k = 0;
     // 遍历字符串
     while (source[i] != '\0')
     {
     // 根据start和end截取
     if (i >= start && i < end) // 含头不含尾
     {
     *(dest + k) = *(source + i); // hello
     k++;
     }
     i++;
     }
     *(dest + k) = '\0';
     return k;
 }
 ​
 int main(int argc,char *argv[])
 {
     char *str = "abcdefg";// cde
     char dest[26];
     int len = str_substr(str,2,5,dest);
     printf("%s,%s,%d\n", str, dest, len);// abcdefg,cde,3
     return 0;
 }

函数指针与指针函数

指数函数

本质上是上函数,这个函数的返回值类型是指针,这个函数称之为指针函数。(返回值是指针的函数叫做指针函数)

 返回类型* 函数名(形参列表)
 {
     函数体;
     return 指针;
 }
 ​
 // 写法2
 返回类型 *函数名(形参列表)
 {
     函数体;
     return 指针;
 }
 int *get(int a)
 {
     int *p = &a;
     return p;
 }
 ​
 int main()
 {
     int *a = get(5);
     printf("%d\n", *a);
 }

注:

在函数中不要直接返回一个局部变量的地址。因为函数调用完毕后,随着栈帧的回收,变量空间会销毁,使得返回的地址就不明确,此时返回的指针叫做野指针。

解决方案:

如果非要访问,可以给这个局部变量添加(定义的时候添加) static ,可以延长它的生命周期,从而避免野指针(尽量少用,因为存在内存泄漏)

 #include <stdio.h>
 int *add(int a, int b)
 {
     static int sum;
     sum = a + b;
     return &sum;// 执行完return 作为函数作用域的布局变量sum的空间被释放
 }
 ​
 int main(int argc,char *argv[])
 {
     int *res = add(5,3); // 接收到了地址,但是地址对应的空间已经释放
     printf("%d\n", *res);
     return 0;
 }

例:有若干个学生,每个学生有4门成绩,要求在用户输入学号(int id)后,能输出该学生的全部成绩(float scores[4]),用指针函数实现。

 
#include <stdio.h>
 /**
 * 定义一个函数,要求输入学号,返回该学号对应学生的4门成绩
 * @param all:所有人的成绩传进来 查找源头
 * @param id:要检索学生的学号
 * @return id对应学生的4门成绩
 */
 float* search(float (*all)[4], int id)
 {
     // 定义一个指针变量,用来接收查询到的学生的所有成绩
     float *pt;
     pt = *(all + id);// 行偏移 {10,20,30,40}
     return pt;
 }
 ​
 int main(int argc,char *argv[])
 {
     // 准备一个二维数组,存储3个学生的成绩
     float scores[3][4] = {
     {60,70,80,90}, // 0x2000 0x2004 0x2008 0x200C
     {66,77,88,99}, // 0x2010 0x2014 0x2018 0x201C
     {61,71,81,91}
     };
     // 定义一个变量,用来接收学生学号
     int id;
     printf("请输入学生学号(0~2):\n");
     scanf("%d", &id);
     printf("第%d个学生的成绩:\n", id);
     // 创建一个指针,用来接收成绩
     float *p;// 0x11 --> 0x2000
     p = search(scores, id);// 0x21 --> 0x2000
     // 遍历
     for (; p < scores[id] + 4; p++)
     {
         printf("%5.2f\t", *p);
     }
     // *(p + i)
     printf("\n");
     return 0;
 }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值