请回答c语言-指针初阶
指针初阶这里我们简单解释指针,后面指针进阶的时候再深入提高
1. 什么是指针
内存
若要谈及指针我们肯定要谈到内存,内存我们把它划分为一个个小的内存单元,内存单元的编号,就是地址。
之前在初识c语言(下)提到过的内存结构可以参考下图

32位和64位
我们知道通常我们有32位机器和64位机器
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0)
- 那么32根地址线产生的地址就会是232种
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
…
11111111 11111111 11111111 11111111
每个地址标识一个字节,那我们就可以给(2^32Byte == 2^32/1024KB ==232/1024/1024MB==232/1024/1024/1024GB == 4GB) 4G的空闲进行编址。
同样的方法,那64位机器,如果给64根地址线,那就能编址更大的空间。
也因此
- 32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。
- 64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
小结:
指针就是地址,口语中常说的指针值得是指针变量
2. 指针和指针类型
我们说变量有不同的类型,整形,浮点型等。指针也是有其类型的
指针的定义方式是 type * 变量名
char *pc = NULL;
int *pi = NULL;
short *ps = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL;
char* 类型的指针是为了存放 char 类型变量的地址。
short* 类型的指针是为了存放 short 类型变量的地址。
int* 类型的指针是为了存放 int 类型变量的地址。
虽然类型不同但是大小是一样的都是四个字节
int main()
{
char* pc;
int* pa;
double* pd;
printf("%d\n", sizeof(pc));
printf("%d\n", sizeof(pa));
printf("%d\n", sizeof(pd));
}
如果我们用char类型的指针变量来存放int变量会如何?
int main()
{
int a = 0x11223344;
char* pc = &a;//int*
*pc = 0;
return 0;
}
只会有1个字节的内存发生变化
那指针类型的意义是什么?
看后面的解释
2.1 指针+-
整数
int main()
{
int n = 10;
char* pc = (char*)&n;
int* pi = &n;
printf("%p\n", &n);
printf("%p\n", pc);
printf("%p\n", pc + 1);
printf("%p\n", pi);
printf("%p\n", pi + 1);
return 0;
}
通过结果可以看到,指针每走一步的大小取决于指针本身的类型char
就是1字节而int
就是4字节
2.2 指针的解引用
int main()
{
int n = 0x11223344;
char* pc = (char*)&n;
int* pi = &n;
*pc = 0;
*pi = 0;
return 0;
}
解引用操作改变值前后,两个指针指向地址存储的数据被改变了
小结:
现在能够回答之前的问题了
指针的类型有什么意义
- 决定了在解引用的时候一次访问几个字节–指针的权限
比如: char* 的指针解引用就只能访问一个字节,而float* 的指针的解引用就能访问四个字节。
- 决定了指针向前或向后走一步,一步会走几个字节
3. 野指针
- 野指针: 就是指针指向的位置是随机的、不正确的、没有明确限制的
3.1 什么造成了野指针
- 指针定义时不进行初始化的话,默认是随机值
int main()
{
int* p;
*p = 20;
return 0;
}
甚至都报错了
-
指针越界访问
当指针指向的范围超出数组arr的范围时,p就是野指针
int main()
{
int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 0; i <= 11; i++)//11已经超了
{
*(p++) = i;
}
return 0;
}
也是直接报错了
- 指针指向的空间释放
- 动态内存分配过程中释放后指针没置空
free(cur);
cur=NULL;
当free
了cur
指向的地址的数据之后,cur指针就成为了野指针,所以按照习惯我们要置空
还有一种被释放的情况:
- 局部变量的地址出了函数就没了
int* test()
{
int a = 10;
return &a;
}
int main()
{
int*p = test();
*p = 20;
return 0;
}
当进入test函数的时候a的空间创建了,但是出来的时候因为是局部变量就销毁了,返回主函数就出问题了,这块空间找不到了,p就变为野指针了
3.2规避野指针
- 指针初始化
int main() { int a = 10; int p1 = &a; int* p2 = NULL; return 0; }
两种方法,要么给指针赋值,要么就指空
-
小心指针越界
-
指针指向空间释放即使置NULL
这个前面刚讲过
- 避免返回局部变量的地址
- 指针使用之前检查有效性
养成好习惯
int main() { int a = 10; int* pa = &a; int* p = NULL; if (p != NULL) { statement; } if (pa != NULL) { statement; } return 0; }
4. 指针运算
4.1 指针+-
整数
栗子1
int main()
{
int arr[10] = { 0 };
int*p = arr;
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i < sz; i++)
{
*(p + i) = i;//指针+-整数
}
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
利用指针加减整数倒着打印数组的值
int* q = arr + sz - 1;
for (i = 0; i < sz; i++)
{
//printf("%d ", *q--);
printf("%d ", *(q - i));
}
4.2 指针-指针
能做指针与指针之间减法的前提是两个指针指向同一块空间
随意空间相减肯定是不能减的
int main()
{
int a[10] = { 0 };
printf("%d\n", &a[9] - &a[0]);
printf("%d\n", &a[0] - &a[9]);
return 0;
}
由此说明指针和指针相减得到的是元素的个数,而且高地址减低地址个数为正,低减高为负
利用指针-指针
利用指针-指针实现一个my_strlen
函数求字符串长度
//模拟实现strlen
int my_strlen(char* s)
{
char* start = s;
while (*s != '\0')
{
s++;
}
return s - start;
}
int main()
{
char arr[] = "Valar_Dohaeris";
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
4.3 指针的关系运算
指针关系运算就是指针比大小
下面这段代码的意义就是从后到前面,将value
数组里面下标为N_VALUES
全部改为0
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
*vp = 0;
}
for(vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 0;
}
如果用这样的写法,而不是第一种写法写,大部分编译器都能通过,但是标准并不保证它可行。我们还是应该避免这样写,
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
5. 指针和数组
数组和指针本身是两种事物,但是由于数组的创建要开辟空间,就有地址,而数组名恰好等于数组首元素地址
#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]);
return 0;
}
由于数组的地址是连续的,我们可以通过指针来访问地址
5.1 指针来访问数组
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr;//p存放的是数组首元素的地址
既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个就成为可能。
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,0 };
int* p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i < sz; i++)
{
printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p + i);
}
return 0;
}
所以p+i
其实计算的是数组arr
下标为i的地址
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
int* p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
6. 二级指针
ppa
就是二级指针,存放的是一级指针的地址
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;
return 0;
}
7. 指针数组
什么是指针数组?
指针数组就是存放指针的数组
int arr[10];//整形数组 - 存放整形的数组就是整形数组
char ch[5];//字符数组 - 存放的是字符
int* parr[5];//整形指针的数组
char* pch[5];
int main()
{
//int arr[10];//整型数组 - 存放整型的数组
//char ch[5];//字符数组 - 存放字符的数组
//指针数组 - 存放指针的数组
int a = 10;
int b = 20;
int c = 30;
int* arr2[3] = { &a, &b, &c };//存放整型指针的数组
int i = 0;
for (i = 0; i < 3; i++)
{
printf("%d ", *(arr2[i]));
}
return 0;
}
8.补充
指针练习题:
8.1 练习一
#include <stdio.h>
int main()
{
int arr[] = {1,2,3,4,5};
short *p = (short*)arr;
int i = 0;
for(i=0; i<4; i++)
{
*(p+i) = 0;
}
for(i=0; i<5; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
8.2 练习二
unsigned long pulArray[] = {6,7,8,9,10};
unsigned long *pulPtr;
pulPtr = pulArray;
*(pulPtr + 3) += 3;
printf(“%d,%d\n”,*pulPtr, *(pulPtr + 3));
最后应该输出6,12 答案不难,搞清楚关系之后就很简单了
小结:
老铁们觉得有收获的话一定要给个赞,多多评论哦