【C语言】:指针__初阶

这里写目录标题

  • 1、什么是指针
  • 2、指针和指针类型
    • 2.1指针的解引用
    • 2.2 指针+ -整数
  • 3、野指针
    • 3.1野指针成因
    • 3.2如何规避野指针
  • 4、指针运算
    • 4.1 指针+、- 整数
    • 4.2 指针-指针
    • 4.3指针的运算关系
  • 5、指针和数组
  • 6、二级指针
  • 7、指针数组

1、什么是指针

①指针是内存中最小单元的编号,也就是地址。
②平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量。

每个内存都有一个唯一的编号,这个编号也被称为地址,也就是说编号=地址。
总结:指针就是地址,口语中说的指针通常指的是指针变量。

#include<stdio.h>
int main()
{
	int a = 10; //在内存中开辟一块空间
	int* pa = &a;//pa是专门用来存放地址(指针)的,这里的pa就被称为指针变量
	return 0;
}

我们可以通过&(取地址操作符)取出变量的内存起始地址,把地址可以存放到一个变量中,这个变量就是指针变量。

2、指针和指针类型

那我们通过取地址操作符(&)拿到的地址是⼀个数值,⽐如:0x006FFD70,这个数值有时候也是需要存储起来,⽅便后期再使⽤的,那我们把这样的地址值存放在哪⾥呢?答案是:指针变量中

#include <stdio.h>
int main()
{
	int a = 10;
	int* pa = &a;//取出a的地址并存储到指针变量pa中
	return 0;
}

指针变量也是⼀种变量,这种变量就是⽤来存放地址的,存放在指针变量中的值都会理解为地址。
我们看到pa的类型是 int* ,我们该如何理解指针的类型呢?

int a = 10;
int * pa = &a;

这⾥pa左边写的是 int* , * 是在说明pa是指针变量,⽽前⾯的 int 是在说明pa指向的是整型(int)
类型的对象。
在这里插入图片描述
那么指针类型有何意义?
我们写出两个代码来调试一下,对比出内部的变换

//代码1
#include <stdio.h>
int main()
{
 int n = 0x11223344;
 int *pa = &a; 
 *pa = 0; 
 return 0;
}

在这里插入图片描述

//代码2
#include <stdio.h>
int main()
{
 int a = 0x11223344;
 char *pa = &a;
 *pa = 0;
 return 0;
}

在这里插入图片描述
调试我们可以看到,代码1会将a的4个字节全部改为0,但是代码2只是将n的第⼀个字节改为0。

int *的指针解引用访问4个字节
char *的指针解引用访问1个字节
short *的指针解引用访问2个字节
float *的指针解引用访问4个字节
double *的指针解引用访问8个字节

结论:指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。

⽐如: char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字节。

2.1指针的解引用

我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针)指向的对象,这⾥必须学习⼀个操作符叫解引⽤操作符(*)。

#include<stdio.h>
int main()
{
	int a = 100;
	int* pa = &a;
	*pa = 0;	
	return 0;
}

*pa 的意思就是通过pa中存放的地址,找到指向的空间,pa其实就是a变量了;所以pa = 0,这个操作符是把a改成了0。这⾥是把a的修改交给了pa来操作,这样对a的修改,就多了⼀种的途径,写代码就会更加灵活。

2.2 指针+ -整数

#include <stdio.h>
int main()
{
	int n = 10;
	char* pc = (char*)&n; 
	int* pi = &n;
	printf("%p\n", pc); 
	printf("%p\n", pi);
	printf("%p\n", pc + 1); 
	printf("%p\n", pi + 1); 
	return 0;
}

在这里插入图片描述
从上面的代码中可以看到,指针类型决定+1、-1操作时的步长。

3、野指针

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

3.1野指针成因

  1. 指针未初始化
#include <stdio.h>
int main()
{ 
	int *p;//局部变量指针未初始化,默认为随机值
	*p = 20;
	return 0;
}
  1. 指针越界访问
#include <stdio.h>
int main()
{
	int arr[10] = {0};
	int *p = &arr[0];
	int i = 0;
	for(i=0; i<=11; i++)
	{
		*(p++) = i;//当指针指向的范围超出数组arr的范围时,p就是野指针
	}
	return 0;
}
  1. 指针指向的空间释放
#include <stdio.h>
int* test()
{
	int n = 100;
	return &n;
}
int main()
{
	int*p = test();
	printf("%d\n", *p);
	return 0;
}

3.2如何规避野指针

  1. 明确知道指针应该初始化为谁的地址,就直接初始化。
  2. 不知道指针初始化为什么值,暂时初始化为NULL
#include <stdio.h>
int main()
{
	int num = 10;
	int*p1 = &num;
	int*p2 = NULL;//p2是一个空指针,没有指向任何有效的空间,这个指针不能直接使用
	int p3 ;//野指针
	return 0;
}

4、指针运算

4.1 指针+、- 整数

因为数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸⽠就能找到后⾯的所有元素。在这里插入图片描述

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

4.2 指针-指针

指针-指针运算的前提条件是:指针和指针指针指向了同一块空间。

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("%d\n", &arr[9] - &arr[0]);//9
	printf("%d\n", &arr[0] - &arr[9]);//-9
	return 0;
}

在这里插入图片描述

指针-指针得到的数组的绝对值:是指针和指针之间的元素个数。

4.3指针的运算关系

地址是有大小的,指针的关系运算就是比较指针的大小。

#define N_VALUES 5
float values[N_VALUES];
float *vp;

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

可简化为如下:

#define N_VALUES 5
float values[N_VALUES];
float *vp;

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

实际上在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
标准规定:

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

5、指针和数组

指针变量的大小是4/8个字节,专门是用来存放地址的。

数组是一块连续的空间,可以存放1个或多个类型相同的数据。

他们之间存在联系是:

数组名是数组首元素的地址,数组名=地址=指针,数组是首元素的地址,又是连续存放的,所以通过指针就可以遍历访问数组。

我们可以举个例子来说明一下:

#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;
}

在这里插入图片描述
从运行出来的结果中我们可以看到,数组名和数组首元素的地址是一样的。

结论:数组名表示的是数组首元素的地址。
(但是这里有两个例外:sizeof(数组名),&数组名)

这样写代码就是可行的:

int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int* p = arr;//p存放的是数组首元素的地址

那么我们就可以直接通过指针来访问数组。
如下:

#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 + i));//p+i其实计算的是数组arr下标为i的地址。*(p+i)就是指针的解引用
	}
	return 0;//输出结果就是1 2 3 4 5 6 7 8 9 10
}

6、二级指针

int a = 10;
int* p = &a;
//p是一级指针变量,指针变量也是变量,变量是在内存中开辟空间的,是变量就有地址
int** pp = &p;
//pp就是二级指针变量,二级指针变量就是用来存放一级指针变量的地址
int*** ppp = &pp;
//ppp指向的pp的类型是int**,第三个*说明ppp是指针

我们可以画个图来说明一下
在这里插入图片描述
对于二级指针的运算有:

  • *ppa通过对ppa中的地址进行解引用,这样找到的是pa,*ppa其实访问的就是pa。

7、指针数组

我们可以知道的是

  • 整型数组:存放整型的数组
  • 字符数组:存放字符的数组

因此我们可以引申出指针数组的含义就是

指针数组:存放指针(地址)的数组

在这里插入图片描述

int* arr3[5];

arr3是一个数组,有五个元素,每个元素是一个整型指针。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值