(一)指针的定义

1. 指针是内存中一个最小单元的编号,也就是地址

2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量

int main()
{
int a=10;
int* p=&a; //指针变量
return 0;
}

这样,我们就可以理解为:

通过&取出变量的内存地址,把地址放在一个变量中,这个变量就是指针变量。

总结:

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

1.一个单元大小为一个字节

2.编址

对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电

平(低电压)就是(1或者0);那么32根地址线产生的地址就会是:

00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
...
11111111 11111111 11111111 11111111

这里就有2^32个地址;每个地址标识一个字节,那我们就可以给 (2^32Byte == 2^32/1024KB ==

232/1024/1024MB==232/1024/1024/1024GB == 4GB) 4G的空闲进行编址。


32位机器上,地址用4个字节空间来存储,一个指针大小为4字节

64为机器上,地址用8个字节空间来存储,一个指针大小为8字节


总结:

1.指针是用来存放地址的,地址是唯一标示一块地址空间的;

2.指针的大小在32位平台是4个字节,在64位平台是8个字节。

(二)指针和指针类型

#include<stdio.h>
int main()
{
printf("%d\n", sizeof(char*));
printf("%d\n", sizeof(short*));
printf("%d\n", sizeof(int*));
printf("%d\n", sizeof(double*));
return 0;
}

【C语言有这个就够了】五.指针_数组

可以看到,笔者的电脑是64位,所以无论什么类型,大小都为8

那既然如此,我们为什么还要有不同类型的指针?

举个例子:

#include<stdio.h>
int main()
{
int a = 0x1122334455667788;//两个16进制位是八个2进制位正好占一个字节
int* pa = &a;
char* pc = &a;
printf("%p\n", pa);
printf("%p\n", pc);
return 0;
}

这段代码,我们发现两个输出结果相同

【C语言有这个就够了】五.指针_#include_02

但细心观察会发现:

【C语言有这个就够了】五.指针_i++_03

深入研究:我们发现不同类型有其独特的作用


1.决定指针进行解引用时可以访问空间的大小

  • int:
#include<stdio.h>
int main()
{
int a = 0x1122334455667788;//两个16进制位是八个2进制位正好占一个字节
int* pa = &a;
*pa=0;
return 0;
}

【C语言有这个就够了】五.指针_数组_04

*pa=0后

【C语言有这个就够了】五.指针_i++_05

  • char:

int main()
{
int a = 0x1122334455667788;//两个16进制位是八个2进制位正好占一个字节
char* pa = &a;
*pa = 0;
return 0;
}

【C语言有这个就够了】五.指针_i++_06

*pa = 0后

【C语言有这个就够了】五.指针_数组_07

我们发现int类型改变了所有字节,而char类型只改变了第一个字节

所以指针类型有意义:决定指针进行解引用时可以访问空间的大小

int*p   *p可以访问4个字节
char*p *p可以访问1个字节
doubl*p *p可以访问8个字节

2.指针类型决定了指针的步长(指针+-整数)

【C语言有这个就够了】五.指针_i++_08

我们发现int类型加1后,向后跳了4个字节(跳过一个整型值的大小);

char加1后,向后跳了1个字节(跳过了一个字符的大小)。

(double 8 个字节)

例如:

int:

#include<stdio.h>
int main()
{
int arr[10] = { 0 };
int*p=arr;
//char* p = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = 1;
}
return 0;
}

【C语言有这个就够了】五.指针_数组_09

char:

#include<stdio.h>
int main()
{
int arr[10] = { 0 };
//int*p=arr;
char* p = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = 1;
}
return 0;

【C语言有这个就够了】五.指针_#include_10

(三)野指针

1.概念

野指针就是指指针的位置是不可知的

2.成因

2.1指针未初始化

int main()
{
int a;//局部变量不初始化,默认是随机值;
int *p; //局部的指针变量,未初始化,默认为随机值
*p=20;//内存中随便找一个地址放20
return 0;
}

2.2指针越界访问

#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0; i<=12; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*p= i;
p++;
}
return 0; }

2.3指针指向的内存空间释放

后面会细讲,这里只做简介:

int* test()
{
int a=10;
return &a;返回地址后,a被销毁,其地址释放
}

int main()
{
int *p=test();
*p=20;
return 0;
}

3.如何避免

1.指针初始化

2.小心指针越界

3.指针指向空间释放即使置NULL(NULL用来初始化指针,给指针赋值)

4. 避免返回局部变量的地址

5. 指针使用之前检查有效性

(四)指针运算

1.指针+-整数

#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10};
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = arr;
for (i = 0; i < sz; i++)
{
printf("%d ", *p);
p = p + 1; //p++
}
return 0;
}

此处用上了指针加整数


#define N_VALUES 5
float values[N_VALUES];
float *vp;
for (vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;
}

先将vp地址对应的值改为0,再后置++发生作用,将vp地址想后移动1。

2.指针-指针

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

【C语言有这个就够了】五.指针_#include_11


我们可以看到指针-指针结果是两个指针之间的距离(两个指针之间元素个数加1)同时,当小地址-大地址时,结果为负:


【C语言有这个就够了】五.指针_#include_12

一般情况,两个相减的指针要位于同一个数组空间;


  • 模拟实现strlen

int my_strlen(char *s) 
{
char *p = s;
while(*p != '\0' )
p++;
return p-s;
}

3.指针的关系运算

两种代码在绝大多数编译器中都可以运行,但第一种更优

for(vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 0;
}
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
*vp = 0;
}

允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与 指向第一个元素之前的那个内存位置的指针进行比较。

(五)指针和数组

关于数组名,我们看个例子:

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

【C语言有这个就够了】五.指针_数组_13

我们发现:

  • arr相当于arr[0],是首元素的地址;
  • &arr看似和arr,arr[0]相同,其实取出的是整个数组的地址。

对代码做出变形,可以看到&arr型在加1后

【C语言有这个就够了】五.指针_#include_14

&arr在加1后,地址加了40(图中是16进制表达式),相当于跳过了跳过了一个数组(一个int 4字节)

  • sizeof(数组名)计算的是整个数组的大小。

再有:

#include<stdio.h>
int main()
{
int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%p========%p\n", p + i, &arr[i]);
}
return 0;
}

【C语言有这个就够了】五.指针_i++_15

我们可以看到p+i是数组arr下标为i的地址

如此,我们可以直接通过指针访问数组

#include<stdio.h>
int main()
{
int arr[10] = {0};
int* p = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i; //对数组内容赋值
}
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
//printf("%d ", *(p + i));
}
return 0;
}

(六)二级指针

#include<stdio.h>
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;//ppa就是二级指针变量
int*** pppa = &ppa;//int**说明ppa的类型,*pppa说明pppa是指针

**ppa = 20;
printf("%d\n", **ppa);
printf("%d\n", a);
return 0;
}

【C语言有这个就够了】五.指针_数组_16

**ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a .

(七)指针数组

指针数组————数组

数组指针————指针

实例:

#include<stdio.h>
int main()
{
int a = 10;
int b = 20;
int c = 30;
int i = 0;
//int* pa = a;
//int* pb = b;
//int* pc = c;
//整形数组存放整型
//字符数组存放数组
//指针数组存放指针
int* arr2[3]={&a,&b,&c};//将地址放入创建好的指针数组中
for (i = 0; i < 3; i++)
{
printf("%d ", *(arr2[i]));//arr2中的数据为指针,通过*解引用
}
return 0;
}

【C语言有这个就够了】五.指针_数组_17

与以下代码对比

#include<stdio.h>
int main()
{
int a = 10;
int b = 20;
int c = 30;
int i = 0;
//int* pa = a;
//int* pb = b;
//int* pc = c;
//整形数组存放整型
//字符数组存放数组
//指针数组存放指针
int arr2[3]={a,b,c};
for (i = 0; i < 3; i++)
{
printf("%d ", arr2[i]);
}

return 0;
}


【C语言有这个就够了】五.指针_数组_17