C语言指针之必须要掌握的指针基础知识

 更新时间:2021年09月26日 14:54:39   作者:程序里的酒  
这篇文章主要介绍了C语言指针必须要掌握的基础知识,文中实例讲解的很清晰,有不太懂的同学可以研究下,希望能够给你带来帮助

一、指针概述

指针是个变量,存放内存单元的地址(编号)。

指针的创建

在定义指针变量的时候,在变量前面加上' * ',代表这个变量是一个指针,再往前面加上一个类型名,就代表指针的类型,称为XX指针。

指针的初始化:

使用&(取地址操作符)可以获得变量的地址,将其赋值给已经定义好的指针变量,需要它们的类型相同,类型不同的时候可以使用强制转换。如果暂时不知道需要存放什么地址的时候,可以先让它指向NULL((void*)0),NULL本质就是0,0这个地址是不允许存储的。

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

指针的大小

指针可以是不同的类型,char*、int*、float*、double*、long*等我们熟知的类型,那指针在内存中占多大的空间呢?

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

00000000 00000000 00000000 00000000
……
111111111 111111111 111111111 111111111
共有232个地址,每一个地址能指向一个内存单元(一字节),那么有232个内存单元。

我们知道一个内存单元的大小为一字节,一根地址线是32位01组成的,那么要存放指针(地址),也就需要32个bit,也即4字节。所以在32位平台下不论是什么类型的指针都是4个字节。

同理,在64位平台下有64根地址线,每个地址是64位,所以需要8个字节。

如何使用指针

使用' * '操作符对指针进行解引用,可以获取到指针指向空间的值

我们来运行下方代码。

#include <stdio.h>
int main()
{
	int n = 0x11223344;
	char* pc = (char*)&n;//将n的地址(指针)强制转换成char* 类型
	int* pi = &n;
	*pc = 0;//改变它指向空间的值
	*pi = 0;
	return 0;
}

我们开始调试代码,查看n的内存,最左边是地址,右边是以16进制显示内存里存放的数据,而n也是以16进制存进去的,两个16进制代表一个字节,如下图:

在这里插入图片描述

当执行完语句 *pc=0;

在这里插入图片描述

因为pc指向的是char* 类型,只能访问一个字节,所以只能修改一个字节。

再执行*pi = 0;

在这里插入图片描述

此时n的内存数据全部变成了0,说明int*类型的指针可以访问4个字节。

小结:

指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个节)。 比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。

二级指针

指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里? 这就是 二级指针 。
即存放指针变量的地址的指针,二级指针指向的空间的值是一个一级指针。

int main()
{
	int a = 20;
	int* p = &a;
	int** pp = &p;
	printf("%d\n", *p);//解引用即可拿到指针指向空间里面的值
	printf("%d\n", *p); //对二级指针,需要两次解引用才能拿到最开始的值
	return 0;
}

在这里插入图片描述

在这里插入图片描述

二、野指针

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

形成野指针的原因

1.指针未初始化

#include <stdio.h>
int main()
{
	int *p;//局部变量指针未初始化,默认为随机值
	*p = 20;//往一个随机的地址(不属于本程序的空间)里面放入数据,是不允许的
	return 0;
}

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.指针指向的空间释放

当动态开辟的内存被释放后,此时的指针就属于野指针,它指向的位置它不能正常访问。关于动态内存分配,后续会细讲。

int main()
{
	//开辟一个整形空间
	int* p = (int*)malloc(sizeof(int));
	//释放该空间
	free(p);
	//此时p即为野指针,因为它指向的空间已经无法访问,一般需要将其置空
	//将其指向空指针,防止后续调用出错
	p = NULL;
	return 0;
}

如何规避野指针

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

三、指针的基本运算

关于指针的运算一般有两个,指针+指针,语义是合法,但没有意义。

  • 指针± 整数
  • 指针-指针

指针± 整数

对int和char类型的指针分别进行+1操作,%p以16进制打印

int main()
{
	int n = 10;
	char* pc = (char*)&n;//将n的地址(指针)强制转换成char* 类型
	int* pi = &n;
	printf("&n:    %p\n", &n);
	printf("pc:    %p\n", pc);
	printf("pc + 1:%p\n", pc + 1);
	printf("pi:    %p\n", pi);
	printf("pi + 1:%p\n", pi + 1);
	return 0;
}

在这里插入图片描述

小结:

指针的类型决定了指针向前或者向后走一步有多大(距离)。

指针-指针

int main()
{
	int n = 10;
	char* pc = (char*)&n;//将n的地址(指针)强制转换成char* 类型
	int* pi = &n;
	printf("&n:    %p\n", &n);
	printf("pc:    %p\n", pc);
	printf("pc + 1:%p\n", pc + 1);
	printf("pi:    %p\n", pi);
	printf("pi + 1:%p\n", pi + 1);
	return 0;
}

在这里插入图片描述

小结:

指针-指针等于它们之间相差的类型数据的个数。即本例的第10个元素和第1个元素之间相差9。

四、指针和数组

一起来看一下数组名是什么

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

在这里插入图片描述

可见数组名和数组首元素的地址是一样的。

结论: 数组名表示的是数组首元素的地址。也是一个指针

那么我们就可以用一个指针来代替数组名,如下代码:

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

在这里插入图片描述

注意:

数组名在下面两种情况下不是首元素的地址

1.sizeof(数组名) - 这里的数组名不是首元素的地址,是表示整个数组的,这里计算的是整个数组的大小,单位还是字节

2.&数组名 - 这里的数组名不是首元素的地址,是表示整个数组的,拿到的是整个数组的地址

int main()
{
	int arr[10] = { 0 };
	int sz = sizeof(arr);
	printf("sizeof(arr)计算的是整个数组的大小:%d\n", sz);
	printf("数组首地址:      %p\n", arr);
	printf("数组首元素地址:  %p\n", &arr[0]);
	printf("数组的地址:      %p\n", &arr);
	printf("数组首地址+1:    %p\n", arr + 1);
	printf("数组首元素地址+1:%p\n", &arr[0] + 1);
	printf("数组的地址+1:    %p\n", &arr + 1);
	//数组名确实是首元素的地址
	//但是有2个例外:
	//1. sizeof(数组名)  - 这里的数组名不是首元素的地址,是表示整个数组的,这里计算的是整个数组的大小,单位还是字节
	//2. &数组名 - 这里的数组名不是首元素的地址,是表示整个数组的,拿到的是整个数组的地址
	//
	return 0;
}

在这里插入图片描述

五、指针数组

存放指针的数组。

int main()
{
	int a = 1;
	int b = 2;
	int c = 3;
	int* arr[10] = { &a,&b,&c };
	for (int i = 0; i < 3 ; i++)
	{
		printf("%d ", *(arr[i]));
	}
	return 0;
}

在这里插入图片描述

总结

指针是C语言非常重要的一部分,内容繁多不易懂,本文仅介绍了一些基本知识,后续还会深入了解指针。

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注脚本之家的更多内容!

相关文章

  • C语言植物大战数据结构二叉树堆

    C语言植物大战数据结构二叉树堆

    这篇文章主要为大家介绍了C语言植物大战数据结构二叉树堆的图文示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-05-05
  • C语言 超详细介绍与实现线性表中的带头双向循环链表

    C语言 超详细介绍与实现线性表中的带头双向循环链表

    带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单
    2022-03-03
  • C++ OpenCV裁剪图片时发生报错的解决方式

    C++ OpenCV裁剪图片时发生报错的解决方式

    在图像处理中,我们经常根据需要截取图像中某一区域做处理,下面这篇文章主要给大家介绍了关于C++ OpenCV裁剪图片时发生报错的解决方式,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2022-07-07
  • 基于C++编写一个键盘提示音程序

    基于C++编写一个键盘提示音程序

    首先讲一下思路,这次制作的小黑子相当于键盘提示音,输入J,N,T,M,会发出“鸡你太美”的声音,连续按下JNTM则会发出“你干嘛啊,哎呦”的声音,感兴趣的可以了解一下
    2023-03-03
  • C语言键盘控制走迷宫小游戏

    C语言键盘控制走迷宫小游戏

    这篇文章主要为大家详细介绍了C语言键盘控制走迷宫小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-06-06
  • QT中对话框的使用示例详解

    QT中对话框的使用示例详解

    这篇文章主要为大家详细介绍了Qt中对话框的使用,以及QMessageBox类中常见函数的使用,文中的示例代码讲解详细,感兴趣的小伙伴可以学习一下
    2022-11-11
  • 深入剖析C++中的struct结构体字节对齐

    深入剖析C++中的struct结构体字节对齐

    要求数据内存的起始地址的值是某个数k的倍数,这就是所谓的内存对齐,本文就来深入剖析C++中的struct结构体字节对齐,需要的朋友可以参考下
    2016-05-05
  • C++11中union的使用方法示例

    C++11中union的使用方法示例

    这篇文章主要给大家介绍了关于C++11中union的使用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-09-09
  • C语言详解函数与指针的使用

    C语言详解函数与指针的使用

    C语言这门课程在计算机的基础教学中一直占有比较重要的地位,然而要想突破C语言的学习,对函数和指针的掌握是非常重要的,本文将具体针对函数和指针的关系做详尽的介绍
    2022-04-04
  • C语言实现简易通讯录(静态版本)的代码分享

    C语言实现简易通讯录(静态版本)的代码分享

    这篇文章主要为大家详细介绍了如何录音C语言实现一个简易的通讯录(静态版本),文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-10-10

最新评论