初阶C-指针总结与坑

指针的定义

在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。

总的来说,指针就是变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)

  1. 指针的大小在32位平台上是4个字节,在64位平台上是8个字节。
  2. 指针是存放地址才出现的,地址是为了标示一块地址空间的。
  3. 指针让地址有地方存放,指针让内存的访问更加方便。

指针和指针类型

有这么一个代码

int num = 10;
p = #

要将&num保存到p中,p就是一个指针变量,它的类型是有相对应的。

char *pc = NULL;
int *pi = NULL;
short *ps = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL;

由上面的代码段可以看到,指针的定义方式就是 type + * 。 也就是char*的指针存放char类型变量的地址。short*的指针存放short类型变量的地址。int*的指针存放int类型的变量的地址。

普通的指针变量,指针类型,表示指针指向的变量的类型。一个普通的指针,知道对应这个变量的房间号,以及房间的大小。

void*的使用:只知道房间号,但是不关注房间大小。

  • void*类型不能解引用
  • void*类型的指针不能和整数进行相加减
  • void*类型的两个指针也不能相减

然而这样一个代码

int a = 10;
char* p = &a;
printf("%d\n",*p);

打印的结果是多少?打印的结果还是10。但是int*和char*类型是不兼容的,但是我们在存放数据时,要考虑到大端和小端的情况,低位在低地址 *p的解引用 对于char*得看它是怎么放置的。

通俗来说,int*本身有4个字节,但是非要让它char*去表示,那么只能去考虑一个字节。打印出它的第一个地址存储的数据。但这第一个地址是怎么存放数据的,我们要考虑大端与小端的情况。因为就一个 0a,可能在4个字节中,最末尾的位置,也可能是第一个位置。这都是根据机器设定的字节序来得出结果的。

这里说一下机器的字节序:

大端:低位在高地址上

小端:低位在低地址上

指针 +/- 整数

首先站在上帝视角,对指针进行运算是一件非常危险的事情,这也是一个埋下大坑的开始!!

指针的加减与普通的数字加减不太一样。

char* p = NULL;
p = p + 1;
//结果为1
short* p = NULL;
p = p + 1;
//结果为2
int* p = NULL;
p = p + 1;
//结果为4
double* p = NULL;
p = p + 1;
//结果为8

指针和整数+1并不是地址+1,而是跳过当前指向的元素。-1是往低地址跳过一个元素(可以说是往前跳)

指针-指针
int arr[] = {1, 2, 3, 4};
int* p1 = arr;
int* p2 = arr + 3;
printf("%d\n",p2 - p1);

指针相减,就是指针加减整数的逆运算。指针相减就是看指针之间隔了多少个元素。

语法是允许,但是很多情况下是没有意义的。比如两个不同类型的去相减,没有什么实际的意义。除非两个指针指向了同一个连续的内存空间,此时才是有意义的。

指针的解引用

解引用:"*"的作用是引用指针指向的变量值,引用其实就是引用该变量的地址,“解”就是把该地址对应的东西解开,解出来,就像打开一个包裹一样,那就是该变量的值了,所以称为“解引用”。

也就是说,解引用是返回内存地址中保存的值。

指针的类型决定了对指针解引用的时候有多大的权限。

比如:char*的指针解引用就只能访问一个字节,而int*的指针的解引用就能访问四个字节

这里补充一个const的使用。

const int* p => p 指向的内容不能修改

int const *p => p指向的内容不能修改

int* p const p => p 的指向不能修改

二级指针

二级指针并没有很难理解,就是将一直指针看成一个常量,在引用一个指针。

可以类比于二维数组,它其实还是一个一维数组,只是每个元素是一个一维数组。

二级指针

当我们觉得不太好理解时,可以这样

typedef int* int_ptr
int a = 10;
int_ptr p = &a;
int_ptr* pp = &p;

将int*定义成一个变量来代替,这样我们看着就比较方便了。

指针和数组名

指针和数组名往往是可以相互转化的。

int arr[] = {1,2,3,4}
int* p = arr + 1;

此时的含义是,数组的首地址元素+1,跳过这个当前指向的元素,此时*p为2。

&arr => 数组指针:是一个指针,指向一个数组的指针。

指针数组 vs 数组指针

int a[] = { 1, 2, 3, 4};
printf("%p\n",a);//指向的int
printf("%p\n",&a);
//得出的地址是一样的,改变一下
printf("%p\n",&a + 1);
//这直接跳过了整个数组,指向了整个数组
int* a[] = { 0 };//每个元素是一个指针

指针的大坑

第一组

int a[] = { 1, 2, 3, 4};
printf("%d\n",sizeof(a));//数组的大小,16
printf("%d\n",sizeof(a + 0));//a此时变成了指针,4(32位系统下)
printf("%d\n",sizeof(*a));//a变成首元素的指针,得到了一个整数,4
printf("%d\n",sizeof(a + 1));//a此时变成了指针,4
printf("%d\n",sizeof(a[1]));//第二个元素的大小,4
printf("%d\n",sizeof(&a));//此时是数组指针,指针就一定占4个字节!
printf("%d\n",sizeof(*&a));//此时先位数组指针,指向整个数组,每个数组内元素解引用,还原数组,16
printf("%d\n",sizeof(&a + 1));//数组指针+1还是数组指针,还是为4
printf("%d\n",sizeof(&a[0]));//a[0]取元素,整数,取完地址为int*,还是为4

第二组

char a[] = { 'a', 'b', 'c', 'd', 'e', 'f'};
printf("%d\n",sizeof(a));//类型为char型,占1个字节,共6个
printf("%d\n",sizeof(a + 0));//变成指针,4
printf("%d\n",sizeof(*a));//转成指针,解引用,得到是字符a,1
printf("%d\n",sizeof(a[1]));//1
printf("%d\n",sizeof(&a));//数组指针,4
printf("%d\n",sizeof(&a + 1));//还是4,数组指针+1还是指针
printf("%d\n",sizeof(&a[0] + 1));//char* 还是一个指针

printf("%d\n",strlen(a));// strlen从当前位置找到\0,如果找不到则越界,那么就是未定义行为
printf("%d\n",strlen(a + 0));//strlen()函数都当成char*指针来处理,数组中没有\0,依然是未定义行为
printf("%d\n",strlen(*a));//得到一个字符,不能放到char*里,间接级别不同,所以依然是未定义行为
printf("%d\n",strlen(a[1]));//得到一个字符,都是未定义i行为
printf("%d\n",strlen(&a));//依然是个未定义行为
printf("%d\n",strlen(&a + 1));//未定义行为
printf("%d\n",strlen(&a[0] + 1));//未定义行为

第三组

char* p = 'abcdef';
printf("%d\n",sizeof(p));//只要是指针,4个字节
printf("%d\n",sizeof(p + 1));//还是指针,4个字节
printf("%d\n",sizeof(*p));//解引用,char类型,得到a,1个字节
printf("%d\n",sizeof(p[0]));//等价与*p,1
printf("%d\n",sizeof(&p));//指针取地址,就是二级指针,4
printf("%d\n",sizeof(&p + 1));//二级指针,4
printf("%d\n",sizeof(&p[0]));//a取地址,就是指向a的指针,+1就是指向b的指针,还是4

printf("%d\n",strlen(p));//6,‘\0’不取,strlen()不算在其长度中
printf("%d\n",strlen(p + 1));//5,p指向a,p+1指向b,从b开始到结束,只占了5个
printf("%d\n",strlen(*p));//得到是一个字符,不能用strlen(),未定义行为
printf("%d\n",strlen(p[0]));//未定义行为
printf("%d\n",strlen(&p));//二级指针不能进行,还是未定义行为
printf("%d\n",strlen(&p + 1));//未定义行为
printf("%d\n",strlen(&p[0] + 1));//a的指针,+1为b的指针,此时从b到结束,5
第四组
char a[] = "abcdef";
printf("%d\n",sizeof(a));//7,还有'\0'得算上,strlen()到\0结束,不计算入内
printf("%d\n",sizeof(a + 0));//此时变为了指针,4
printf("%d\n",sizeof(*a));//解引用得到字符a,1
printf("%d\n",sizeof(a[1]));//1
printf("%d\n",sizeof(&a));//还是4,数组指针
printf("%d\n",sizeof(&a + 1));//4,还是数组指针
printf("%d\n",sizeof(&a[0] + 1));//是一个char*指针,4

printf("%d\n",strlen(a));//6,不计算\0
printf("%d\n",strlen(a + 0));//还是指向首元素的地址,6
printf("%d\n",strlen(*a));//解引用得到字符,未定义
printf("%d\n",strlen(a[1]));//未定义行为
printf("%d\n",strlen(&a));//数组名取地址,变为数组指针,得到的是首元素地址,依次往后去找。得到的还是6
printf("%d\n",strlen(&a + 1));//数组指针+1跳出整个数组了,找不到\0了,未定义行为
printf("%d\n",strlen(&a[0] + 1));//指向b,直到\0,为5
第五组
int a[3][4] = { 0 };
printf("%d\n",sizeof(a));//3*4*4=48
printf("%d\n",sizeof(a[0][0]));//4
printf("%d\n",sizeof(a[0]));//16,第一行
printf("%d\n",sizeof(a[0] + 1));//4,数组指针,指针就是4
printf("%d\n",sizeof(*(a[0] + 1)));//*(p + 1) => p[1],还是指针,即a[0][1] + 1,解引用,为4
printf("%d\n",sizeof(a + 1));//变为指针,还是为4
printf("%d\n",sizeof(*(a + 1));//*(a + 1) => a[1],所以数组为第二行,16
printf("%d\n",sizeof(&a[0] + 1));//得到为数组指针,+1还是数组指针,4
printf("%d\n",sizeof(*(&a[0] + 1)));//数组a[0],&为数组指针,在+1跳到第二个元素,在解引用为a[1],16
printf("%d\n",sizeof(*a));//*a => a[0] ,16
printf("%d\n",sizeof(a[3]));//下标越界,sizeof在编译过程中得到类型,长度为4个元素的数组。

这些大坑多种形式,真的需要没事就看看,指针在C中是一个很重要的工具,在之前的静态顺序表中已经出现过,在链表中还出现了二级指针,所以需要多理解,多思考!

猜你喜欢

转载自blog.csdn.net/skrskr66/article/details/84710010