赞
踩
指针的概念:
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. 指针的运算。
在指针的类型中我们知道有一种指针类型为字符指针 char* ;
一般使用:
- int main()
- {
- char ch = 'w';
- char *pc = &ch;
- *pc = 'w';
- return 0;
- }
还有一种使用方式如下:
- int main()
- {
- const char* pstr = "hello bit.";//这里是把一个字符串放到pstr指针变量里了吗?
- printf("%s\n", pstr);
- return 0;
- }
代码 const char* pstr = "hello bit.";
特别容易让同学以为是把字符串 hello bit 放到字符指针 pstr 里了,但是/本质是把字符串 hello bit. 首字符的地址放到了pstr中。
相当于先创建了一个字符变量(实际没有),然后在用字符数组指向他的首元素地址。
那就有可这样的面试题:
- #include <stdio.h>
- int main()
- {
- char str1[] = "hello bit.";
- char str2[] = "hello bit.";
- const char *str3 = "hello bit.";
- const char *str4 = "hello bit.";
- if(str1 ==str2)
- printf("str1 and str2 are same\n");
- else
- printf("str1 and str2 are not same\n");
-
- if(str3 ==str4)
- printf("str3 and str4 are same\n");
- else
- printf("str3 and str4 are not same\n");
-
- return 0;
- }
输出: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相同。
在《指针》章节我们也学了指针数组,指针数组是一个存放指针的数组。
这里我们再复习一下,下面指针数组是什么意思?
int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5]; //二级字符指针的数组
数组指针是指针?还是数组?
答案是:指针。
我们已经熟悉:
整形指针: int * pint; 能够指向整形数据的指针。
浮点型指针: float * pf; 能够指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针。
下面代码哪个是数组指针?
int *p1[10]; int (*p2)[10]; //p1, p2分别是什么?解释:
int (*p)[10]; //解释:p先和*结合,优先级高,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。 //这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。
对于下面的数组:
int arr[10];
arr 和 &arr 分别是啥?
我们知道arr是数组名,数组名表示数组首元素的地址。
那&arr数组名到底是啥?
我们看一段代码:
- #include <stdio.h>
- int main()
- {
- int arr[10] = {0};
- printf("%p\n", arr);
- printf("%p\n", &arr);
- return 0;
- }
可见数组名和&数组名打印的地址是一样的。 难道两个是一样的吗?
我们再看一段代码:
- #include <stdio.h>
- int main()
- {
- int arr[10] = { 0 };
- printf("arr = %p\n", arr);
- printf("&arr= %p\n", &arr);
- printf("arr+1 = %p\n", arr+1);
- printf("&arr+1= %p\n", &arr+1);
- return 0;
- }
arr = 000000A4F717F568
&arr= 000000A4F717F568
arr+1 = 000000A4F717F56C
&arr+1= 000000A4F717F590
根据上面的代码我们发现,其实&arr和arr,虽然值是一样的(首地址的值),但是意义不一样的。实际上: &arr 表示的是数组的地址,整个数组的,而不是数组首元素的地址。
本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40.
- &arr表示的是数组指针 arr表示的是数组的首元素地址
那数组指针是怎么使用的呢?
既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。
- #include <stdio.h>
- int main()
- {
- int arr[10] = {1,2,3,4,5,6,7,8,9,0};
- int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
- //但是我们一般很少这样写代码
- //弄巧成拙,我们应该在合适的时候使用数组指针
- return 0;
- }
一个数组指针的使用:
- #define _CRT_SECURE_NO_WARNINGS 1
- #include <stdio.h>
-
-
- print(int(*p)[5],int r,int s) //因为数组指针指向的是第一行的地址,
- //所以后面要给上[5]才能遍历到此行每列的元素
- {
- int i = 0;
- for (i = 0; i < r; i++)
- {
- int j = 0;
- for (j = 0; j < s; j++)
- {
- printf("%d ", *((*p + i) + j));
- //(*p + i)表示数组每行的首元素地址
- //*((*p + i) + j))表示此行向后遍历j次之后解引用取值
- }
- printf("\n");
- }
-
-
- }
-
- int main()
- {
- int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
- print(arr, 3, 5);
-
- return 0;
- }
简而言之
指针数组,就是存放指针的数组
数组指针,就是存放数组的指针
在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?
- #include <stdio.h>
- void test(int arr[])//ok?
- {}
- void test(int arr[10])//ok?
- {}
- void test(int *arr)//ok?
- {}
- void test2(int *arr[20])//ok?
- {}
- void test2(int **arr)//ok?
- {}
- int main()
- {
- int arr[10] = {0};
- int *arr2[20] = {0};
- test(arr);
- test2(arr2);
- }
- #include <stdio.h>
- void test(int arr[])//创建数组,不需要创建大小,拿数组接收,OK的
- {}
- void test(int arr[10])//创建数组,拿数组接收,OK的
- {}
- void test(int* arr)//创建的数组,实际穿的数组首元素也是指针
- //拿指针接收,OK的
- {}
- void test2(int* arr[20]) // 创建的指针数组,实际存放的是指针,传参的是数组首元素也是指针
- // 拿指针接收,OK的
- {}
- void test2(int** arr)// 创建的是指针数组,arr2代表首元素地址,而arr2存放的都是指针,
- // 一级指针的地址,拿二级指针接收,OK的
- {}
- int main()
- {
- int arr[10] = { 0 }; //数组
- int* arr2[20] = { 0 }; //指针数组
- test(arr);
- test2(arr2);
- }
- void test(int arr[3][5])//ok?
- {}
- void test(int arr[][])//ok?
- {}
- void test(int arr[][5])//ok?
- {}
- //总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
- //因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
- //这样才方便运算。
- void test(int *arr)//ok?
- {}
- void test(int* arr[5])//ok?
- {}
- void test(int (*arr)[5])//ok?
- {}
- void test(int **arr)//ok?
- {}
- int main()
- {
- int arr[3][5] = {0};
- test(arr);
- }
答案
- void test(int arr[3][5])//简单粗暴,编译器会创建一个空间来接收一样的参数,OK的
- {}
- void test(int arr[][])//要规定大小,不OK。
- {}
- void test(int arr[][5])//数组规定列的大小就可以正常使用,OK的
- {}
- //总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
- //因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
- //这样才方便运算。
- void test(int* arr)//创建的是二维数组,传参过来代表的是一整行的地址,用一维指针接收,不OK
- {}
- void test(int* arr[5])// 创建的是二维数组,用指针数组接收,
- // 要么是二维数组,要么是数组指针才可以,不OK
- {}
- void test(int(*arr)[5])//创建的是二维数组,用数组指针接收,OK的
- {}
- void test(int** arr)//创建的是二维数组,用二维指针接收
- //二级指针是接收一级指针的指针,不OK
- {}
- int main()
- {
- int arr[3][5] = { 0 };
- test(arr); //注意:传参的是一维数组arr[3]的首地址
- }
- #include <stdio.h>
- void print(int *p, int sz)
- {
- int i = 0;
- for(i=0; i<sz; i++)
- {
- printf("%d\n", *(p+i));
- }
- }
- int main()
- {
- int arr[10] = {1,2,3,4,5,6,7,8,9};
- int *p = arr;
- int sz = sizeof(arr)/sizeof(arr[0]);
- //一级指针p,传给函数
- print(p, sz);
- return 0;
- }
思考:
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
void test1(int *p) {} //test1函数能接收什么参数? void test2(char* p) {} //test2函数能接收什么参数?答案:数组名,指针,地址
- #include <stdio.h>
- void test(int** ptr)
- {
- printf("num = %d\n", **ptr);
- }
- int main()
- {
- int n = 10;
- int*p = &n;
- int **pp = &p;
- test(pp);
- test(&p);
- return 0;
- }
思考:
当函数的参数为二级指针的时候,可以接收什么参数?
void test(char **p) { } int main() { char c = 'b'; char*pc = &c; char**ppc = &pc; char* arr[10]; test(&pc); test(ppc); test(arr); return 0; }
首先看一段代码:
- #include <stdio.h>
- void test()
- {
- printf("hehe\n");
- }
- int main()
- {
- printf("%p\n", test);
- printf("%p\n", &test);
- return 0;
- }
输出的结果:
00007FF74D2F13CA
00007FF74D2F13CA
输出的是两个地址,这两个地址是 test 函数的地址。 那我们的函数的地址要想保存起来,怎么保存? 下面我们看代码:
- void test()
- {
- printf("hehe\n");
- }
- //下面pfun1和pfun2哪个有能力存放test函数的地址?
- void (*pfun1)();
- void *pfun2();
首先,能给存储地址,就要求pfun1或者pfun2是指针,那哪个是指针? 答案是:
pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。
阅读两段有趣的代码:
- //代码1
- (*(void (*)())0)();
- //代码2
- void (*signal(int , void(*)(int)))(int);
代码1
我们可以分为三个部分
(*(void (*)())0)(); //函数指针部分
(*(void (*)())0)(); //解引用部分
(*(void (*)())0)(); //函数调用部分
所以我们分三步理解
- 首先把0强制类型转变为void (*)(),变成函数指针类型
- 在对函数指针类型进行解引用
- 然后对数指针类型解引用之后的数进行函数调用,调用为空
代码2
我们将函数区分出来
void (*signal(int , void(*)(int)))(int);
所以我可以先看红色部分
可以知道这是一个函数指针类型
而signal(int , void(*)(int))) 是一个函数
函数包括了整形和一个函数指针类型
signal 函数的返回类型也是void(*)(int),该函数指针指向的函数有一个int类型的参数,返回类型是void
代码2太复杂,如何简化:
- typedef void(*pf_t)(int);
- pf_t signal(int, pf_t);
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。