hey! Ladies and Gentlemen.欢迎大家来看望我,对,我就是指针(pointer),被很多人吐槽,也被人说好。我希望大家了解过我以后,能够爱上我。
目录
前言
了解基本情况
指针是什么
指针的类型
指针的初始化
指针的解引用
请不要让我变成野孩子
指针运算
指针和数组
升级的我(二级指针)
指针的大小
深入了解我
✨字符指针
✨指针数组(储存指针的数组)
✨数组指针 (指向数组的指针)
✨数组传参和指针传参
✨函数指针
✨函数指针数组
✨指向函数指针数组的指针
✨回调函数
前言
大家在了解我之前 ,有必要清楚的知道数据在内存中是如何存取的。在我们写程序时,通常会定义一些变量,当程序进行编译时,系统会根据用户定义的变量类型分配一定长度的存储单元,储存用户需要存入的数据。在计算机的内存中,一个存储单元有一个地址,你定义了一个变量,该变量就有一个地址。我们形象的用一个比喻内存吧!有一栋宿舍楼,每间宿舍楼都有一个标号,我们就把这个标号认定为是地址,每间宿舍内有八张床,有人坐着的床位就认为是1,有人躺着的床位认为是0。当我们对变量的一些操作,例如赋值、取值。实际是对该变量的地址对应的内存操作。赋值时根据地址向内存中写入数据、取值时根据地址向内存中拿数据。
说明:一个存储单元可存储的大小的是8个二进制位,也就是一个字节。
例子:当我们定义了一个变量,但是并没有给该变量初始化,可以通过变量的地址,找到该地址中存储的内容。
说明:一个存储单元一个地址
了解基本情况
指针是什么
通常会说指针嘛,不就是就是地址吗。一个地址对应内存中的一个存储单元,地址指向该存储单元,为了形象化大家就把地址称为指针。指针变量就是储存指针的变量。在C语言中使用的时候,通常我们会把指针变量简单称为指针,指针变量和普通变量的定义方式不同,其次不同的是存储的内容一般是一个地址。
指针和指针变量的关系
1.指针可以称为地址,地址也可称为指针
2.指针变量是存放内存地址的变量
3.指针和指针变量是两个不同的概念,但要注意的是,通常我们在使用时会把指针变量简为指针,实际上含义并不相同。
//指针变量定义方式:数据类型 *指针变量名 = 地址int a = 10;int *p = &a;//p是一个指针,p中储存a的地址, 指针p指向a
指针的类型
通常来说,我储存了一个变量或对象的地址,我的类型和该变量或对象类型相同。我的类型也决定了我的访问权限,如果我是一个char*类型,我就只能能访问1个字节,是int *就只能访问4个字节。
int a = 10;int *p1 = &a;//和p1*号结合,说明p1是一个指针,指针的类型是int *char b = 'a';char *p2 = &b;//char *类型指针float c = 1.0;float *p3 = &c;//float *类型指针
指针的初始化
如果你在定义我的时候,你没有我其他变量的地址,不妨初始化一个空地址给我,也就是NULL。既是善待我也是善待你自己。
char *pc = NULL;short *ps = NULL;int *pi = NULL;long *pl = NULL;float *pf = NULL;double *pd = NULL;
指针的解引用
大家定义好以后,并给了我地址。解引用就是通过你给我的地址,我能通过你给定的地址,访问这块地址在内存中的内容。举前言的例子,取值时,相当于你让我去看看宿舍内哪些床是躺着人的,哪些是坐着人,然后向你汇报。赋值时相当于你让去把宿舍该躺下的人 让他们躺着,该坐着的的人让他们坐着。通常使用*+指针变量使用。
例子:
int a = 10;int *p = &a;//通过取地址操作符&,获取a的地址,保存在指针变量p中*p = 5;//向变量a的地址写入数据5int b = *p;//取出变量a地址中的内容赋给b
请不要让我变成野孩子
这个名字给我取的,为啥称我是“野孩子”,也就是我是一块未知的地址,这个地址可能是随机的(不初始化),不正确的(指针越界)。如果你让我变为野孩子,那么我会让你失望的。
int main(){int *p;//局部变量未初始化,默认为随机值*p= 5;//error 对未知的内存区域写入操作return 0;}
int main(){int arr[10] = {0};int *p = arr;int i = 0;for(i=0; i<=11; i++) {*(p++) = i;//当指针指向的地址超出arr可用的范围时,p就是野指针}return 0;}
避免让我变成野孩子,从你做起!
1.养成初始化的习惯
2.小心我越界访问
3.若指向的是动态开辟的内存地址,释放该内存后将我置为NULL
4.不要返回局部变量的地址给我
5.使用之前,检查我存储的地址是否有效。
大家能做到这几点,我谢谢大家了!
指针运算
//指针+-整数int arr[5] = {1,2,3,4,5};int *p = arr;//p指针是int *类型,+1指针指向的地址向高地址偏移四个字节,偏移量看指针的类型大小//设p变量开始储存的地址是0x00000000,+1后指针指向的是0x00000004地址
//指针-指针int my_strlen(char *s){ char *p = s;while(*p != ''){p++; } return p-s;//可计算字符串长度,偏移元素个数}
指针和数组
定义一个数组,数组会在内存中开辟一块连续的内存空间。数组名表示的是数组首元素的地址。既然是一个地址,那么就能给赋给指针变量,我就能通过该地址来存取。&arr表示的整个数组的地址。
大家可以发现:p+i就可以访问数组下标为i的地址,通过地址间接访问数组内容。
数组下标和指针的关系
int arr[10] ={0};arr[0] = 1//等同于*(arr+0) = 1arr[1] = 2//等同于*(arr+1) = 2
C语言中程序中,开辟出可以使用的内存,只要知道其地址,都能对内存进行访问。
升级的我(二级指针)
上面介绍的我都是一级的,来看看升级过后的我吧!之前我介绍的都是储存一个变量的地址升级过后的我就能储存我兄弟的地址。二级指针就是储存一级指针地址的指针。
你可以通过一次解引用访问指针变量p中的内容,两次解引用访问指针变量p地址储存的内容
指针的大小
因为我储存的是一个地址,在32位机器下,用32个二进制位表示,也就是四个字节,所以我的大小就是4字节。在64位机器下,用64个二进制位表示,8个字节,所以我的大小就是8字节。
深入了解我
✨字符指针
一般使用方法
int main(){char ch = 'w';char *pc = &ch; *pc = 'a'; return 0;}
其他使用方式
知识补给站:C/C++会把常量字符串存储到单独的一个内存区域,当多个指针变量指向同一个字符串的时候,他们实际会指向同一块内存区域。但是如果用相同的常量字符串取初始化不同的字符数组就会开辟出不同的内存块。
经典例题:
#include int main(){char str1[] = "Hello World";char str2[] = "Hello World";char* str3 = "Hello World";char* str4 = "Hello World";if (str1 == str2) { printf("str1 = str2"); }else {printf("str1 != str2"); }if (str3 == str4){printf("str3 = str4");}else {printf("str3 != str4");}return 0;}
✨指针数组(储存指针的数组)
//arr1是一个数组,数组中每一个元素储存都是int *类型的int *arr1[5] = {NULL};//atr是一个数组,数组中每一个元素都是char *类型的指针char *str[5] = {NULL};//arr2是一个数组,数组中每一元素都是char **类型的指针char **arr2[5] = {NULL};
✨数组指针 (指向数组的指针)
不知道大家会不会把指针数组和数组组成混淆
指针数组是一个数组,数组内部储存的是指针
数组指针是一个指针,指向一个数组的指针
int *p1[10] = {NULL};//指针数组int arr[10] = {0};int (*p2)[10] = &arr;/*数组指针,指针的类型是int[10]*通常我们会说整形指针,字符指针、浮点指针,他们储存的是对应数据类型的地址,数组指针也相同指向的是一个数组类型的指针。说明:C/C++中数组是内置的数据类型,但不是基础的数据类型,是构造的数据类型p1 + 1 地址偏转4个字节P2 + 1 地址偏转40个字节*/
解释:[]的优先级高于*,所以()让p先和*结合,说明p是一个指针变量,指向的是一个大小为10个整形数据的数组。
int (*arr2[2])[5]//arr2先与[]结合,说明arr2是一个数组。
int (*arr2[2])[5]//arr2是一个数组,数组中有2个元素,每个元素的类型是int[5] *,存放数组指针的数组。
说明:二维指针名表示二维数组中第一行的地址 。
✨数组传参和指针传参
一维数组传参
void test1(int arr[])//√{}void test2(int arr[10])//√这个10写在里面无任何意义,但是该写法没错,形参部分的数组大小可以省略{}void test3(int *arr)//√{}void test4(int *arr[10])//√{}void test5(int **arr)//√{}int main(){int arr1[10] = {0};int *arr2[10] = {0}; test1(arr1);test2(arr1); test3(arr1);test4(arr2);//arr2是指针数组,arr2是该数组的首元素的地址,数组内,存的又是一级指针的地址test5(arr2);return 0;}
二维数组传参
void test1(int arr[3][5])//√{}void test2(int arr[][])//×{}void test3(int arr[][5])//√{}void test4(int *arr[5])//x{}void test5(int (*arr)[5])//√{}void test6(int **arr)//x{}int main(){int arr[3][5] = {0}; test1(arr);test3(arr); test5(arr);}
总结:二维数组传参,函数形参的设计可以省略第一个[]的数字,因为对于一个二维数组,可以不知道有多少行,但是必须要知道一行有多少个元素。我们在定义二维数组的时候也可以省略行,但是不能省略列。
✨函数指针
大家都知道,调用函数时,会在栈区开辟一块内存区域,既然有该函数开辟内存,那么该块内存就有一个地址。
test是函数的首地址,&test一个指向函数test的地址
//定义一个函数指针 函数返回值类型 (*指针变量名)(形参列表) = 函数地址//使用方法 解引用(实际参数)void test(){printf("Hello World");}int main(){ void (*p1)() = &test;//p1是一个指针,指针的类型是void (*)() (*p1)();// return 0;}
//错误定义方法:函数返回值类型 *指针变量名(形参列表)void test(){}int main(){void *p2() = test;//errorreturn 0;}
✨函数指针数组
//定义方法:返回值类型 (*指针变量名[数组大小])(形参列表) ={地址1,地址2...};//p是一个数组,数组中的指针是 返回值类型 (*)(形参列表)类型的函数指针int add(int x, int y){return x+y;}int sub(int x, int y){return x-y;}int mul(int x, int y){return x*y;}int div(int x, int y){return x/y;}int main(){int (*p[4])(int x,int y) ={add,sub,mul,div};//p是一个数组,数组中存储的是函数指针 int i=0;for(i=0; i<4; i++) {printf("%d",(*p[i])(5,5));}return 0;}
✨指向函数指针数组的指针
int main(){//p是一个指针,指针类型是void (*)()void (*p)() = NULL;//parr是一个数组,数组中元素的类型是void (*)()void (*parr[5])() = {NULL}//pfarr是一个指针,指向的是一个指针数组,指针数组中元素的类型是void (*)()void(*(*pfarr)[5])() = NULL; return 0;}
✨回调函数
什么是回调函数:如果把函数的指针(地址)作为参数转递给另一个函数,当这个指针被用来调用其所指向的函数,我们就称为回调函数。回调函数不是由该函数的实现方直接调用。
int int_cmp(int a, int b){return a-b;}void test(int (*p)(int , int ))//函数指针接收参数{ int t = (*p)(5,6); if(t>0) {printf("a>b"); }else if(t == 0) { printf("a=b");} else{ printf("a<b"); }}int main(){test(int_cmp); return 0;}
小题:解释如下代码的含义
(*(void (*)())0)()//void (*)()是一种函数指针类型,把0强制转化为该种类型的指针。意味着调用0地址处,一个返回值类型是void无参的函数,0本来不是一个指针,经过强制转换后变成函数指针,对函数指针解引用就是调用该函数
void (*signal(int , void (*)(int)))(int)//signal是一个函数函数的返回值类型是void (*)(int )signal(int, void (*)(int))//表示的是一个函数的声明,signal函数名//可简化typedef void (*pfun)(int )pfun signal(int , pfun );
看到这里,你应该有所收获吧!感谢你的了解,更感谢您的支持。