C语言初阶——指针

一、指针三连问

1.1 什么是指针?

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

1.2 为什么使用指针?

提高查找效率

总结:指针是地址,指针变量是一个变量,用来存放内存单元的地址,通过指针能找到以它为地址的内存单元。

1.3 指针怎么用?

举个栗子:

#include <stdio.h>
int main()
{
    
    
    int a = 10;//在内存中开辟一块空间
    int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
               //将a的地址存放在p变量中,p就是一个之指针变量。
    return 0;
}

这里需要知道计算机当中是如何编址的?

对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的是产生一个电信号正电/负电(1或
者0)
那么32根地址线产生的地址就会是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001

11111111 11111111 11111111 11111111

这里就有2的32次方个地址。
每个地址标识一个字节,那我们就可以得出:
2 32 B y t e = 2 32 / 1024 K B = 2 32 / 1024 / 1024 M B = 2 32 / 1024 / 1024 / 1024 G B = 4 G B \begin{aligned} 2^{32}Byte \\ &=2^{32}/1024KB \\ &=2^{32}/1024/1024MB \\ &=2^{32}/1024/1024/1024GB\\ &=4GB \end{aligned} 232Byte=232/1024KB=232/1024/1024MB=232/1024/1024/1024GB=4GB
4G的空闲进行编址
同样的方法,64位机器,如果给64根地址线,就可以得出:
2 64 B y t e = 2 64 / 1024 K B = 2 64 / 1024 / 1024 M B = 2 64 / 1024 / 1024 / 1024 G B \begin{aligned} 2^{64}Byte \\ &=2^{64}/1024KB \\ &=2^{64}/1024/1024MB \\ &=2^{64}/1024/1024/1024GB\\ \end{aligned} 264Byte=264/1024KB=264/1024/1024MB=264/1024/1024/1024GB
对于一般计算机来说不会将64位地址总线全部用到,通常只用8G的空闲进行编址
这里我们就明白:

32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节
那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。

总结:
指针是用来存放地址的,地址是唯一标示一块地址空间的。
指针的大小在32位平台是4个字节,在64位平台是8个字节。

二、指针和指针的类型

2.1 指针类型

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 类型变量的地址。
举个栗子来理解指针类型的定义

#include<stdio.h>
 
 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", pc-1);
    printf("%p\n", pi);
    printf("%p\n", pi+1);
    printf("%p\n", pi-1);
	return 0;
}

结果
在这里插入图片描述

可以看到在32位操作系统下指针变量是4个字节,64位操作系统下指针变量是8个字节。

总结:对指针加1其实是加上指针所指向类型的大小,指针的类型决定了指针向前或者向后走一步有多大(距离)。

2.2 指针的解引用

#include<stdio.h>

int main()
{
    
    
	//2.2指针解引用
	int n = 0x11223344;
	char *pc = (char *)&n;
	int *pi = &n;
	*pc = 0;
	*pi = 0;
	 return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

从调试的结果图中可以看出,当指针变量类型为char类型时,对指针解引用只能操作一个字节,而当指针变量类型为int类型时,对指针解引用可以一次操作四个字节。

总结:

指针的类型决定了对指针解引用的时候有多大的权限,即能操作几个字节。

三、野指针

3.1 概念

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

3.2 野指针成因

3.2.1 指针未初始化

#include <stdio.h>
int main()
{
    
       
    int *p;//局部变量指针未初始化,默认为随机值
    *p = 20;
    return 0;
}

3.2.2 指针越界访问

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

3.2.3 指针指向的空间释放

可以看以下博客的第三部分
05-C语言进阶——动态内存管理

3.3 如何规避野指针

  • 指针初始化
  • 小心指针越界
  • 指针指向空间释放即置NULL
  • 指针使用之前检查有效性

四、指针运算

4.1 指针加减整数

#define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)
{
    
    
     *vp++ = 0;
}

对指针加1其实是加上指针所指向类型的大小,指针的类型决定了指针向前或者向后走一步有多大(距离)。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

4.2 指针减指针

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

在这里插入图片描述
总结

两个指针相减,代表两个指针经历的元素个数。

  • 元素类型由指针表明
  • 两个指针必须指向同一块内存才有意义(通常是同一数组、字符串)

4.3 指针的关系运算

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]);
    return 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]);

	int *p = arr;	//指针变量p存放的数组首元素的地址
	int arrSize = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < arrSize; i++)
	{
    
    
		printf("%d ", p[i]);
		printf("%d ", *(p+i));
		printf("%d ", arr[i]);
		printf("%d \n", *(arr+i));
	}
	return 0;
}

在这里插入图片描述
由结果可以看出我们p+i其实计算的就是数组arr下表为i的地址

六、二级指针

指针变量是变量,那么是变量就会有地址,二级指针就是用来存放指针变量地址的。

通过一个图可以理解
在这里插入图片描述

a的地址存放在pa中,pa的地址存放在ppa中
pa是一级指针,ppa是二级指针

对于二级指针运算

	int a = 10;

	int *pa = &a;
	printf("%d\n", *pa);
	int **ppa = &pa;

	int b = 20;
	*ppa = &b;		//等价于pa=&b
	printf("%d\n", *pa);
	**ppa = 30;	//	等价于*pa=30 等价于b=30
	printf("%d\n", *pa);
	printf("%d\n", b);

结果
在这里插入图片描述

这里对**ppa的操作有两步

  • 第一个解引用*ppa,找到的是pa的地址,因此将b的地址赋值给pa
  • 第二个解引用**ppa,首先通过*ppa找到pa,然后对pa进行解引用操作:*pa,找到的就是b

猜你喜欢

转载自blog.csdn.net/qq_40076022/article/details/111289771
今日推荐