当前位置:   article > 正文

指针的详解①( 字符指针,指针数组,数组指针,数组参数、指针参数,函数指针)_字符串指针 形参

字符串指针 形参

指针的概念:

1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。


内存会划分为一个个的内存单元
每个内存单元都有一个独立的编号 - 编号也称为地址
地址在C语言中也被称为指针

指针(地址) 需要存储起来 - 存储到变量中,这个变量就被称为指针变量

2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。


指针 (地址) 的大小是固定的4/8个字节 (32位平台/64位平台)

地址是物理的电线上产生

32位机器 - 32根地址线 -包括( 1/0 )
32个0/1组成的二进制序列,把这个二进制序列就作为地址,32个bit位才能存储这个地址也就是需要4个字节才能存储所以指针变量的大小就是4个字节
同理64位机器上,地址的大小是64个0/1组成的二进制序列,需要64个bit位存储,也就是8个字节。所以指针变量的大小是8个字节。

3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。

4. 指针的运算。

1. 字符指针


在指针的类型中我们知道有一种指针类型为字符指针 char* ;

一般使用:

  1. int main()
  2. {
  3. char ch = 'w';
  4. char *pc = &ch;
  5. *pc = 'w';
  6. return 0;
  7. }

还有一种使用方式如下:

  1. int main()
  2. {
  3. const char* pstr = "hello bit.";//这里是把一个字符串放到pstr指针变量里了吗?
  4. printf("%s\n", pstr);
  5. return 0;
  6. }

代码 const char* pstr = "hello bit.";

特别容易让同学以为是把字符串 hello bit 放到字符指针 pstr 里了,但是/本质是把字符串 hello bit. 首字符的地址放到了pstr中。

相当于先创建了一个字符变量(实际没有),然后在用字符数组指向他的首元素地址。

 那就有可这样的面试题:

  1. #include <stdio.h>
  2. int main()
  3. {
  4. char str1[] = "hello bit.";
  5. char str2[] = "hello bit.";
  6. const char *str3 = "hello bit.";
  7. const char *str4 = "hello bit.";
  8. if(str1 ==str2)
  9. printf("str1 and str2 are same\n");
  10. else
  11. printf("str1 and str2 are not same\n");
  12. if(str3 ==str4)
  13. printf("str3 and str4 are same\n");
  14. else
  15. printf("str3 and str4 are not same\n");
  16. return 0;
  17. }

输出:str1 and str2 are not same
           str3 and str4 are same

因为这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针 指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会 开辟出不同的内存块。

简单来说,就是str1,str2是新建的数组变量,所以开辟的内存空间肯定会不同。

而str3,str4因为是创建了const ,使* str3/4 不能对hello bit 进行修改,而3 4都指向的是同一个数,不能修改就没有必要多创建一个内存地址来分配,所以系统就智能的选择了同一块地址。

所以str1和str2不同,str3和str4相同。

2. 指针数组


在《指针》章节我们也学了指针数组,指针数组是一个存放指针的数组。

这里我们再复习一下,下面指针数组是什么意思?

int* arr1[10];         //整形指针的数组

char *arr2[4];         //一级字符指针的数组

char **arr3[5];        //二级字符指针的数组

3. 数组指针 


3.1 数组指针的定义

数组指针是指针?还是数组?

答案是:指针。

我们已经熟悉:

整形指针: int * pint; 能够指向整形数据的指针。

浮点型指针: float * pf; 能够指向浮点型数据的指针。

那数组指针应该是:能够指向数组的指针。

下面代码哪个是数组指针?

  1. int *p1[10];
  2. int (*p2)[10];
  3. //p1, p2分别是什么?

解释:

  1. int (*p)[10];
  2. //解释:p先和*结合,优先级高,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。
  3. //这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

3.2 &数组名VS数组名

对于下面的数组:

int arr[10];

arr 和 &arr 分别是啥?

我们知道arr是数组名,数组名表示数组首元素的地址。

那&arr数组名到底是啥?

我们看一段代码:

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int arr[10] = {0};
  5. printf("%p\n", arr);
  6. printf("%p\n", &arr);
  7. return 0;
  8. }

 

可见数组名和&数组名打印的地址是一样的。 难道两个是一样的吗?

我们再看一段代码:

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int arr[10] = { 0 };
  5. printf("arr = %p\n", arr);
  6. printf("&arr= %p\n", &arr);
  7. printf("arr+1 = %p\n", arr+1);
  8. printf("&arr+1= %p\n", &arr+1);
  9. return 0;
  10. }

arr = 000000A4F717F568
&arr= 000000A4F717F568
arr+1 = 000000A4F717F56C
&arr+1= 000000A4F717F590


根据上面的代码我们发现,其实&arr和arr,虽然值是一样的(首地址的值),但是意义不一样的。

实际上: &arr 表示的是数组的地址,整个数组的,而不是数组首元素的地址。

本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40.

  • &arr表示的是数组指针 arr表示的是数组的首元素地址

3.3 数组指针的使用

那数组指针是怎么使用的呢?

既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int arr[10] = {1,2,3,4,5,6,7,8,9,0};
  5. int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
  6. //但是我们一般很少这样写代码
  7. //弄巧成拙,我们应该在合适的时候使用数组指针
  8. return 0;
  9. }

一个数组指针的使用:

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include <stdio.h>
  3. print(int(*p)[5],int r,int s) //因为数组指针指向的是第一行的地址,
  4. //所以后面要给上[5]才能遍历到此行每列的元素
  5. {
  6. int i = 0;
  7. for (i = 0; i < r; i++)
  8. {
  9. int j = 0;
  10. for (j = 0; j < s; j++)
  11. {
  12. printf("%d ", *((*p + i) + j));
  13. //(*p + i)表示数组每行的首元素地址
  14. //*((*p + i) + j))表示此行向后遍历j次之后解引用取值
  15. }
  16. printf("\n");
  17. }
  18. }
  19. int main()
  20. {
  21. int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
  22. print(arr, 3, 5);
  23. return 0;
  24. }

简而言之

指针数组,就是存放指针的数组

数组指针,就是存放数组的指针

4. 数组参数、指针参数


在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?

4.1 一维数组传参

  1. #include <stdio.h>
  2. void test(int arr[])//ok?
  3. {}
  4. void test(int arr[10])//ok?
  5. {}
  6. void test(int *arr)//ok?
  7. {}
  8. void test2(int *arr[20])//ok?
  9. {}
  10. void test2(int **arr)//ok?
  11. {}
  12. int main()
  13. {
  14. int arr[10] = {0};
  15. int *arr2[20] = {0};
  16. test(arr);
  17. test2(arr2);
  18. }

 答案:

  1. #include <stdio.h>
  2. void test(int arr[])//创建数组,不需要创建大小,拿数组接收,OK的
  3. {}
  4. void test(int arr[10])//创建数组,拿数组接收,OK的
  5. {}
  6. void test(int* arr)//创建的数组,实际穿的数组首元素也是指针
  7. //拿指针接收,OK的
  8. {}
  9. void test2(int* arr[20]) // 创建的指针数组,实际存放的是指针,传参的是数组首元素也是指针
  10. // 拿指针接收,OK的
  11. {}
  12. void test2(int** arr)// 创建的是指针数组,arr2代表首元素地址,而arr2存放的都是指针,
  13. // 一级指针的地址,拿二级指针接收,OK的
  14. {}
  15. int main()
  16. {
  17. int arr[10] = { 0 }; //数组
  18. int* arr2[20] = { 0 }; //指针数组
  19. test(arr);
  20. test2(arr2);
  21. }

4.2 二维数组传参

  1. void test(int arr[3][5])//ok?
  2. {}
  3. void test(int arr[][])//ok?
  4. {}
  5. void test(int arr[][5])//ok?
  6. {}
  7. //总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
  8. //因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
  9. //这样才方便运算。
  10. void test(int *arr)//ok?
  11. {}
  12. void test(int* arr[5])//ok?
  13. {}
  14. void test(int (*arr)[5])//ok?
  15. {}
  16. void test(int **arr)//ok?
  17. {}
  18. int main()
  19. {
  20. int arr[3][5] = {0};
  21. test(arr);
  22. }

答案

  1. void test(int arr[3][5])//简单粗暴,编译器会创建一个空间来接收一样的参数,OK的
  2. {}
  3. void test(int arr[][])//要规定大小,不OK。
  4. {}
  5. void test(int arr[][5])//数组规定列的大小就可以正常使用,OK的
  6. {}
  7. //总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
  8. //因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
  9. //这样才方便运算。
  10. void test(int* arr)//创建的是二维数组,传参过来代表的是一整行的地址,用一维指针接收,不OK
  11. {}
  12. void test(int* arr[5])// 创建的是二维数组,用指针数组接收,
  13. // 要么是二维数组,要么是数组指针才可以,不OK
  14. {}
  15. void test(int(*arr)[5])//创建的是二维数组,用数组指针接收,OK的
  16. {}
  17. void test(int** arr)//创建的是二维数组,用二维指针接收
  18. //二级指针是接收一级指针的指针,不OK
  19. {}
  20. int main()
  21. {
  22. int arr[3][5] = { 0 };
  23. test(arr); //注意:传参的是一维数组arr[3]的首地址
  24. }

4.3 一级指针传参

  1. #include <stdio.h>
  2. void print(int *p, int sz)
  3. {
  4. int i = 0;
  5. for(i=0; i<sz; i++)
  6. {
  7. printf("%d\n", *(p+i));
  8. }
  9. }
  10. int main()
  11. {
  12. int arr[10] = {1,2,3,4,5,6,7,8,9};
  13. int *p = arr;
  14. int sz = sizeof(arr)/sizeof(arr[0]);
  15. //一级指针p,传给函数
  16. print(p, sz);
  17. return 0;
  18. }

思考:

当一个函数的参数部分为一级指针的时候,函数能接收什么参数?

  1. void test1(int *p)
  2. {}
  3. //test1函数能接收什么参数?
  4. void test2(char* p)
  5. {}
  6. //test2函数能接收什么参数?

 答案:数组名,指针,地址

 

4.4 二级指针传参

  1. #include <stdio.h>
  2. void test(int** ptr)
  3. {
  4. printf("num = %d\n", **ptr);
  5. }
  6. int main()
  7. {
  8. int n = 10;
  9. int*p = &n;
  10. int **pp = &p;
  11. test(pp);
  12. test(&p);
  13. return 0;
  14. }

思考:

当函数的参数为二级指针的时候,可以接收什么参数?

  1. void test(char **p)
  2. {
  3. }
  4. int main()
  5. {
  6. char c = 'b';
  7. char*pc = &c;
  8. char**ppc = &pc;
  9. char* arr[10];
  10. test(&pc);
  11. test(ppc);
  12. test(arr);
  13. return 0;
  14. }

5. 函数指针

首先看一段代码:

  1. #include <stdio.h>
  2. void test()
  3. {
  4. printf("hehe\n");
  5. }
  6. int main()
  7. {
  8. printf("%p\n", test);
  9. printf("%p\n", &test);
  10. return 0;
  11. }

输出的结果:

00007FF74D2F13CA
00007FF74D2F13CA

输出的是两个地址,这两个地址是 test 函数的地址。 那我们的函数的地址要想保存起来,怎么保存? 下面我们看代码:

  1. void test()
  2. {
  3. printf("hehe\n");
  4. }
  5. //下面pfun1和pfun2哪个有能力存放test函数的地址?
  6. void (*pfun1)();
  7. void *pfun2();

首先,能给存储地址,就要求pfun1或者pfun2是指针,那哪个是指针? 答案是:

pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。

阅读两段有趣的代码:

  1. //代码1
  2. (*(void (*)())0)();
  3. //代码2
  4. void (*signal(int , void(*)(int)))(int);

代码1

我们可以分为三个部分

(*(void (*)())0)(); //函数指针部分

(*(void (*)())0)(); //解引用部分

(*(void (*)())0)(); //函数调用部分

所以我们分三步理解

  1. 首先把0强制类型转变为void (*)(),变成函数指针类型
  2. 在对函数指针类型进行解引用
  3. 然后对数指针类型解引用之后的数进行函数调用,调用为空

代码2

我们将函数区分出来

void (*signal(int , void(*)(int)))(int);

所以我可以先看红色部分

可以知道这是一个函数指针类型

signal(int , void(*)(int))) 是一个函数

函数包括了整形和一个函数指针类型

signal 函数的返回类型也是void(*)(int),该函数指针指向的函数有一个int类型的参数,返回类型是void

代码2太复杂,如何简化:

  1. typedef void(*pf_t)(int);
  2. pf_t signal(int, pf_t);

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/编程冒险家/article/detail/63205
推荐阅读
相关标签
  

闽ICP备14008679号