引入
在C语言的学习过程中,指针是躲不掉的一大困难,开始的时候,可能你会觉得初始化整形指针和解引用不过如此,但是当类型逐渐复杂起来以后,没有对指针和类型的深入理解,想要看懂和很好的运用指针就比较困难了,希望通过我的这篇文章,让大家对指针能有更好更深入的认识。
指针
指针是什么?
在计算机中,必不可少的不只是CPU的运算,还需要有内存对数据的存储,然而在存储数据的过程中,必然会面对这样的问题:那么多的内存单元,数据到底存在哪?CPU运算时又要从哪读取数据呢?这时候就要想了:如果你有一个朋友住在了宾馆,让你去找他,你会怎么找呢?肯定不是一个一个房间敲开找,要问道朋友的房间号,才能一次性找到他的位置。那么我们是否可以给每个内存都给一个编号呢?答案是,可以。这便有了指针。
1.内存被划分成一个个的单元,一个内存单元大小是一个字节
2.每个内存单元给一个编号,这个编号就是地址,C语言中简称:指针
大概就是以下的换算
内存单元的编号==地址==指针
指针变量地址
#include <stdio.h> int main() { int a = 10; int *pa = &a;//*说明pa是一个指针变量(指向int类型),其中存放的是指针,也就是a的地址 //&a表示的是取出a变量的地址,然后通过语句赋给pa *pa = 20;//此处的*为解引用,相当于通过pa存的地址找到a,可以理解为*pa==a printf("%d %d\n",*pa,a);//此时打印的两值都为20 return 0; }
通过以上的讲解,大概认识了指针是个什么东西,并且了解了其简单使用了,下面专们来讲讲指针变量的大小。
指针变量大小:
指针变量专门用来存地址,指针变量的大小取决于地址存放所要多大的空间
1.32位机器上:地址线32根,二进制32bit位,要将此地址存起来,需要四个字节的空间
2.64位机器:同理,一个地址需要八个字节
注:地址和类型所占的空间不同,64位机器就像一个相比32位机器更大的宾馆,所需要的编号位数需要更大,但是每个房间的大小却不变一样。故:int类还是占4个字节的空间,只是找到这个空间的编号地址变复杂了而已。
指针变量类型
在刚刚的代码块中,我们谈到了int *pa,意思是pa是一个指针,指向的变量是int类型的数据,那我们合理联想一下,是不是有int*,就有char*,long long int*呢?答案是:是的,char *p意思是开辟指向char类型的指针空间,long long int*就是指向long long int类型的。前面提到的指针解引用*p中,int *p中的*p可以访问4个字节的空间,而char *p的*p可以访问1个字节的空间,以此类推。
指针加减整数
根据上述指针变量类型,我们就可以对指针进行加减运算,见下方代码
#include<stdio.h> int main() { int a = 10; int *pa = &a; char *pc = (char*)&a; //pa + 1---->地址 + 4 //pc + 1---->地址 + 1 //以上加减跳过的字节数是以指针类型为依据的,注意观察 return 0; }
而在C语言中,有数组这样的一种数据类型,他的下标随地址的增加而增加,且在内存中连续存放,那我们是否可以通过指针而非[i]来访问数组元素呢?,见代码
#include<stdio.h> int main() { int arr[10]={0,1,2,3,4,5,6,7,8,9}; int *p = arr;//数组名是首元素地址 for(int i = 0;i < 10;i++){ printf("%d ",arr[i]); } for(int i = 0;i < 10;i++){ printf("%d ",*(p + i));//上下两块代码打印的结果是一样的,其实运行的效果也是一样的 } return 0; }//代码可以CV自测一下
指针减指针
指针减指针得到的是int类型的数据,但是相减时一定要满足这样一个条件,就是:两个相减的指针指向的是同一块空间。eg:指向同一个数组里的不同元素,这样大指针减小指针便可以得到指针间的元素个数了。
#include<stdio.h> int main() { int arr[10] = {1,2,3,4,5,6,7,8,9,10}; printf("%d\n",&arr[9]-&arr[0]);//将打印指针间的元素个数 return 0; }
void类型指针
在指针中,有这样一种特殊的指针,那就是void*(无具体类型指针),在用指针变量存放不同类型的地址时,不是相同类型的地址,是不能相互赋值的,要想放入指针变量类型不同的地址,只有两个办法,一个是强制类型转换,另一个便是void*,见代码
char a = 'm'; int *p = &a;//这样写会报错 int *p = (int*)&a;//将a的地址类型强制转换成int*,放入时不报错 void *p = &a;//将a的地址放入void*类型的p中,不报错
void*类型的指针可以放其他任意类型的指针,比如char*,int*等等,虽然放的指针类型很多,但还是有一定的局限性
注:
1.void*类型不能直接解引用
2.void*类型指针不可加减整数
虽然void*可以包含许多类型的指针,但这恰恰让其只知道指向的位置,却不知道访问多大的空间,故产生以上问题。
加餐const
const使变量有了常属性,见下代码
#include<stdio.h> int main() { const int a = 10;//给a加上const a = 20;//这时修改a的值时编译器就会报错 int *pa = &a; *pa = 20;//这时候a的值将被成功改掉,pa执政相当于一个后门,间接改a const int *pb = &a;//给*pb加上const属性 *pb = 0;//这时候通过*pb来改a时就会报错 return 0; }
其中,const虽然有了常属性,但本质上还是属于变量范畴(通常叫:常变量),const对于变量只是在语法上做了限制。
当const放在不同位置时,起到的作用也是不同的
const int a = 10;//不能通过a改变a变量的值 const int *pa = &a;//不能通过*pa改变a的值,但pa指针可以被直接改变 int const *pb = &a;//pb和pa的性质相同 int *const pc = &a;//可以通过*pc改变a的值,但pc的值不能直接改变 const int * const pd = &a;//不可通过*pd改变a,同时也不能直接改变pd
注意观察,其实不难发现,根据const所在的位置,就可以判断其在哪个变量上做了限制了。
指针关系运算(比大小)
比的是地址的高低(感觉没啥讲的)
野指针
我们在C语言学习过程中,肯定不免听到别人说“野指针”这三个字,现在我们来聊聊野指针吧。
现在我来给大家生成一个野指针
#include<stdio.h> int main() { int *ptr; *ptr = 20;//非法访问 return 0; }
其中ptr就是一个野指针,也就是所指向不可知地址的指针 ,由于生成的指针ptr指向方向不定,所以在访问或改变其指向的值的时候是极其不安全的,因为没有人会知道他指向的是哪一块空间。
避免生成野指针:
1.给指针初始化
2.不知道初始化什么,给指针赋NULL
3.指针用完记得置NULL
4.避免返回局部变量的地址
(在局部变量销毁,空间释放后局部变量指针就成为野指针而引发错误)
assert断言
如果你在为害怕误用指针而烦恼,建议来看看assert吧,它能一定程度上帮助你判断指针是否可以使用,在assert.h中定义了宏assert();
#include<stdio.h> #include<assert.h> int main() { int *pa = NULL; assert(pa != NULL);//当pa为空指针时报错 return 0; }
传值和传址
在函数调用过程中,有时候会遇到传递参数的问题,但有时候单纯传递数却无法解决一些问题
比如:写一个函数用来交换两个变量的值
#include<stdio.h> void Swap1(int x,int y) { int tmp = x; x = y; y = tmp; } void Swap2(int* pa,int* pb) { int tmp = *pa; *pa = *pb; *pb = tmp; } int main() { int a = 10; int b = 20; Swap1(a,b); printf("%d %d\n",a,b);//这里将会打印 10 20,可以看到变量并没有交换 Swap2(&a,&b); printf("%d %d\n",a,b);//这里打印 20 10,变量成功交换 return 0; }
可以看出,当要用来改变传递变量的值的时候,可以传递它的地址,而直接传递其数值就达不到这样的效果。
传址:让函数与主调函数建立联系
二级指针
指针的内存用来存放地址数据,那么能否创建一个指向指针变量的指针呢?
当然可以,见代码
#include<stdio.h> int mian() { int a = 10; int * p = &a;//取a的地址 int ** pp = &p;//取指针p的地址,pp为二级指针 int *** ppp = &pp;//取二级指针pp的地址,ppp为三级指针(用的很少) //其中**pp==a printf("%d\n",**p); return 0; }
指针数组
什么是指针数组,字面意思:存放指针的数组
#include<stdio.h> int main() { int a = 10; int b = 20; int c = 30; int* arr[3] = {&a,&b,&c};//其中arr数组的每个元素都为int类型的指针变量 return 0; }
数组指针
也是字面意思:指向数组的指针
注:在看这些名字的时候,可以注意一下它的命名特点和方式,比如数组在后其本质就是数组,指针在后其就是指针。
int arr[10] = {1,2,3,4,5,6,7,8,9,10}; int (*parr)[10] = &arr;//其中parr是数组指针,&arr取得是整个数组的指针 //[]的优先级 > * //*代表parr是一个指针,而[10]代表parr指向的数组有十个元素,每个元素是int类型 //parr + 1会跳过整个数组 //用parr访问元素(*parr)[i]
二维数组传参
#include<stdio.h> void print1(int arr[3][5],int r,int c){....}//这两种传参方式都对 void print2(int (*arr)[5],int r,int c){....} int main() { int arr[3][5]; print(arr,3,5);//二维数组传参 return 0; }
函数指针
字面意思:指向函数的指针
#include<stdio.h> int Add(int x,int y) { return x + y; } int main() { int (*pf)(int,int)=&Add; int ret = (*pf)(4,9); int (*pf2)(int,int) = Add;//Add本身就可以作为地址赋给pf2 int ret2 = (*pf2)(4,9); int ret3 = pf(4,9);//调用是pf可不用解引用 printf("%d %d %d\n",ret,ret2,ret3);//打印的三个值是相同的 return 0; }
函数指针数组
总的来说其实还是要看名字,就是存放函数指针的数组
使用举例
#include<stdio.h> 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; } void menu() { printf("1.add\n"); printf("2.sub\n"); printf("3.mul\n"); printf("4.div\n"); printf("0.exit\n"); } int main() { int a, b; menu(); int input; int (*parr[5])(int, int) = { NULL,add,sub,mul,div }; //上面的函数指针数组里分别放了四个函数指针 scanf("%d", &input); while (input < 5 && input>0) { scanf("%d%d", &a, &b); int ret = parr[input](a, b);//通过调用函数指针数组来间接访问各个函数 printf("%d", ret); } return 0; }
以上的代码,完成了一个简单的计算器,同时规避了一堆if else的冗杂,这便是函数指针数组的一个妙用。
总结
以上讲了指针的基本类型,但是关于指针的细节还有很多很多,再往后深入就是要注意指针的不同类型和运算,才是深入了解和应用指针的关键。那么今天的指针内容就先分享到这里,如果感觉对你还有帮助的话,记得留一个小小的赞再走哦!