掌握C语言指针,轻松解锁代码高效性与灵活性

这篇具有很好参考价值的文章主要介绍了掌握C语言指针,轻松解锁代码高效性与灵活性。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

掌握C语言指针,轻松解锁代码高效性与灵活性

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:C语言学习
贝蒂的主页:Betty‘s blog

1. 指针与地址

1.1 概念

我们都知道计算机的数据必须存储在内存里,为了正确地访问这些数据,必须为每个数据都编上号码,就像门牌号、身份证号一样,每个编号是唯一的,根据编号可以准确地找到某个数据。而这些编号我们就将其称为地址或者指针

1.2 指针变量

数据在内存中的地址称为指针,如果一个变量存储了一份数据的指针(地址),我们就称它为指针变量

那我们如何使用指针变量呢?

  1. datatype *name;

*表示这是一个指针变量,datatype表示该指针变量所指向的数据的类型

例如:

int* p1;//指向一个整型的指针
char* p2;//指向一个字符的指针
float* p3;//指向一个单精度浮点数的指针
double* p4;//指向一个双精度浮点数的指针

1.3 &和*

我们早在学习scanf时候就用过取地址符&,它是将某个变量的地址取出来,而解引用*的意思就是通过某个地址找到该地址存储的变量。可能解释起来比较抽象,我们可以通过一个不恰当的例子形象说明一下。

掌握C语言指针,轻松解锁代码高效性与灵活性

首先我们可以得到如下几个关系:

	int a = 1;//第一个客户,&a为0x00000001
	int b = 2;//第二个客户,&b为0x00000002
	int c = 3;//第三个客户,&c为0x00000003

然后我们可以通过指针变量把他们地址存储进去

	int* pa = &a;//把a的地址存进去
	int* pb = &b;//把b的地址存进去
	int* pc = &c;//把c的地址存进去

在酒店中,我们可以通过门牌号准确找到每个客户。同理,我们也可以通过每个地址准确找到每个变量。

	printf("a=%db=%dc=%d", *pa, *pb, *pc);//通过*解引用地址找到对应的值

输出结果 a=1 b=2 c=3

并且我们可以通过指针变量进行赋值。

*pa = 4;
*pb = 5;
*pc = 6;
printf("a=%d b=%d c=%d\n", *pa, *pb, *pc);

输出结果:a=4 b=5 c=6

1.4 void*指针和NULL

(1)void*是一种特殊的指针类型,它可以指向任意类型的数据,就是说可以用任意类型的指针对 void 指针赋值。

void*p1;
int*p2;
p1=p2;//这是被允许的
  • 但是却不能把void*指针赋值给任意指针类型,也不能直接对其解引用
void*p1;
int *p2;
p2=p1;//不能这样赋值
*p1//不能直接对void*解引用

(2)NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址会报错

int*p=NULL;//初始化指针

1.5 指针变量的大小

我们知道,现在常见的计算机分为32位机器64位机器。32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后是1或者0,那我们把32根地址线产⽣的2进制序列当做⼀个地址,那么⼀个地址就是32个bit位,需要4个字节才能存储。同理,64位机器需要8个字节才能存储。

我们可以通过以下代码来验证一下。

int main()
{
	printf("%zd ", sizeof(char*));
	printf("%zd ", sizeof(short*));
	printf("%zd ", sizeof(int*));
	printf("%zd ", sizeof(double*));
	return 0;
}

输出结果:

32位机器:4 4 4 4

64位机器:8 8 8 8

2. 指针的基本运算

2.1 指针+-整数

我们先观察一下如下代码的地址变化

#include <stdio.h>
int main()
{
	int n = 10;
	char* p1 = (char*)&n;//将int*强转为char*
	int* p2 = &n;
	printf("%p\n", &n);
	printf("%p\n", p1);
	printf("%p\n", p1 + 1);//p1向后移动一位
	printf("%p\n", p2);
	printf("%p\n", p2 + 1);//p2向后移动一位
	return 0;
}

输出 :

&n=005DF8D4
p1=005DF8D4
p1+1=005DF8D5
p2=005DF8D4
p2+1=005DF8D8

我们可以看出, char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。由此我们得出结论:指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)。

  • 因为每次代码运行时,系统都会重新分配内存,所以输出结果每次都不会一样,但是规律是一样的

我们知道数组在内存中是连续存储的(地址由低到高),所以我们只需要只要首元素的地址就能找到数组所有元素的地址,而一维数组的数组名恰恰就是我们首元素的地址。

 假设有数组int arr[10]={1,2,3,4,5,6,7,8,9,10} 
arr 1 2 3 4 5 6 7 8 9 10
下标 0 1 2 3 4 5 6 7 8 9

那我们如何通过指针访问每个元素呢?

代码参考如下:

#include <stdio.h>
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = &arr[0];//&arr[0]=arr
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);//计算数组元素个数
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));//因为数组元素连续存储,所以可以通过+-整数找到之后元素
	}
	return 0;
}

输出结果:1 2 3 4 5 6 7 8 9 10

2.2 指针-指针

指针-指针其实是指在同一空间内,两个指针之间的元素个数

知道这点之后,我们可不可以自己实现一个字符串库函数strlen()呢?

思路如下:

思路:首先定义两个指针p1,p2,让两个指针指向首元素,然后让一个指针p2循环++,直到指向‘\0’就停止,最后返回p2-p1,就能得到字符串的长度

掌握C语言指针,轻松解锁代码高效性与灵活性

代码如下:

int my_strlen(char* p1)
{
	char* p2 = p1;//使两个指针都指向首元素
	while (*p2)
	{
		p2++;
	}
	return p2 - p1;//返回两指针直接的元素的个数就是其长度
}
int main()
{
	char arr[] = "abcdef";
	int len = my_strlen(arr);//计算arr字符串的长度
	printf("%d\n", len);
	return 0;
}
  • 参考贝蒂string.h大全

2.3 指针的关系运算

我们知道了指针变量本质是存放的地址,而地址本质就是十六进制的整数,所以指针变量也是可以比较大小的

代码示例:

#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]);
	while (p < arr + sz) //指针的⼤⼩⽐较
	{
		printf("%d ", *p);//打印数组每个元素
		p++;
	}
	return 0;
}

3. const修饰

我们知道变量是可以改变的,但是在有些场景下,我们不希望变量改变,那我们该怎么办呢?这就是我们接下来要讲的const的作用啦。

3.1 const修饰变量

简单来说,经过const修饰的变量,可以当做一个常量,而常量是不能改变的。

	int a = 1;//a可修改的
	const int b = 2;
	b=3;//b不可修改的

但是可以通过指针间接修改.

代码如下:

int main()
{
	const int b = 2;
	int* p = &b;
	*p = 3;//通过指针间接修改
	return 0;
}

3.2 const修饰指针

我们知道const的作用后,就可以看看下面几段代码。

	int a = 10;
	const int* p = &a;
	*p = 20;//是否可以
	p = p + 1;//是否可以

通过测试我们发现,*p无法改变成20,但是p可以改变成p+1.

那如果把const调换一下位置,又会出现什么情况呢~

	int a = 10;
    int* const p = &a;
	*p = 20;//是否可以
	p = p + 1;//是否可以

再次测试之后我们发现,*p可以被赋值为20,但是p不能赋值为p+1了

通过上述测试,我们大致可以总结出两个结论。

• const如果放在的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本⾝的内容可变。
• const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。

4. assert断言

assert是一个宏,它的头文件为<assert.h>,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报错终⽌运⾏。这个宏常常被称为“断⾔”。

举一个简单的例子:

assert(a>0);
  1. 如果a的确大于0,assert判断为真,就会通过。

  2. 如果a不大于0,assert判断为假,就会报错。

所以assert常常用于检查空指针问题,以防止程序因为空指针的问题而出错。

int *p=NULL;
assert(p);//空指针是0,0为假,就会报错

5. 传值调用与传址调用

5.1 传值调用

我们前面学习函数时候,遇到过这样一段代码。

#include<stdio.h>
void swap(int x, int y)//返回类型为void表示不返回值
{
    int temp = 0;//定义一个临时变量
    temp = x;//把x的值赋给temp
    x = y;//把y的值赋给x
    y = temp;//把temp的值赋给y,完成交换操作
}
int main()
{
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);
    printf("交换前:a=%d,b=%d\n", a, b);
    swap(a, b);//交换函数
    printf("交换后:a=%d,b=%d\n", a, b);
    return 0;
}

输入:3 5

输出:交换后a=3 ,b=5

为什么两个值并没有交换呢,这是因为形参只是实参的一份临时拷贝,对形参改变,根本不会改变实参。如果忘记的同学可以再去温习一下贝蒂的函数小课堂

5.2 传址调用

那我们想在函数中改变实参的值,那又该如何改变呢?

其实很简单,我们学了指针,知道可以通过地址间接访问该变量的值,所以我们只需要把地址传给函数,在函数中通过地址访问实参,并进行交换。

代码如下:

#include<stdio.h>
void swap(int*x, int*y)//通过指针变量接受地址
{
    int temp = 0;//定义一个临时变量
    temp = *x;//把*x的值赋给temp
    *x = *y;//把*y的值赋给*x
    *y = temp;//把temp的值赋给*y,完成交换操作
}
int main()
{
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);
    printf("交换前:a=%d,b=%d\n", a, b);
    swap(&a, &b);//将地址传给函数
    printf("交换后:a=%d,b=%d\n", a, b);
    return 0;
}

掌握C语言指针,轻松解锁代码高效性与灵活性

6. 野指针

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

6.1 野指针成因

(1)指针未初始化

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

因为p是随机值,所以对p解引用,系统无法通过p的地址找到对应的空间,所以出错造成野指针

(2)数组越界访问

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	int i = 0;
	for (i = 0; i < 11; i++)
	{
		//数组下标是0到9
		printf("%d ", *(arr + i));
	}
	return 0;
}

掌握C语言指针,轻松解锁代码高效性与灵活性

  • 一般出现这种较大的随机值,一般都是数组越界访问

(3)指针指向空间释放

#include <stdio.h>
int* test()
{
	int n = 10;
	return &n;//返回n的地址
}
int main()
{
	int* p = test();//用p接受n的地址
	printf("%d\n", *p);//打印出n的值
	return 0;
}

这段代码乍一看,好像并没有什么问题,但是大家在学习函数的时候知道,在函数中定义的变量是临时变量,一旦出了作用域就会销毁。

一旦销毁,系统就无法访问该空间,而通过指针我们还可以访问该空间,这就造成了冲突,所以出错,造成野指针。

6.2 解决方法

(1) 初始化

NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址会报错。如下是NULL在编译器中的定义:

ifdef __cplusplus

define NULL 0

else

define NULL ((void *)0)

endif

#include <stdio.h>
int main()
{
	int* p=NULL;//用空指针初始化,让其有指向位置
	//*p = 20;NULL地址不能读写
	return 0;
}

(2) 小心越界访问

我们在使用数组时候,一定要对数组的元素个数有一个清晰的把控,不然就很容易出现越界访问的情况。

(3) 不能返回临时变量的地址

临时变量出了作用域就会销毁,系统会回收该空间,所以我们要尽量避免指针指向已经销毁的空间,尤其在函数中,不能返回临时变量的地址。文章来源地址https://www.toymoban.com/news/detail-837848.html

到了这里,关于掌握C语言指针,轻松解锁代码高效性与灵活性的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • RunnerGo:轻量级、全栈式、易用性和高效性的测试工具

    随着软件测试的重要性日益凸显,市场上的测试工具也日益丰富。RunnerGo作为一款基于Go语言研发的开源测试平台,以其轻量级、全栈式、易用性和高效性的特点,在测试工具市场中逐渐脱颖而出。 RunnerGo是一款轻量级的测试工具,使用Go语言研发,具有轻便灵活的特点。与

    2024年02月10日
    浏览(43)
  • JDK、Tomcat、Redis、MySQL集齐,教你如何搭建高效性能测试项目

    作为一个软件工程师,怎能不知道如何搭建一个性能测试项目呢?性能测试是一个软件工程师不可或缺的一环,因为一款软件性能好坏直接关系到用户体验,而用户体验又是一个软件能否成功的最重要的因素。本文将带你一步步搭建一个性能测试项目,包括如何搭建 JDK、To

    2024年02月04日
    浏览(42)
  • 掌握Go语言:Go语言类型转换,解锁高级用法,轻松驾驭复杂数据结构(30)

    在Go语言中,类型转换不仅仅局限于简单的基本类型之间的转换,还可以涉及到自定义类型、接口类型、指针类型等的转换。以下是Go语言类型转换的高级用法详解: Go语言类型转换的高级用法 1. 自定义类型之间的转换 在Go语言中,可以使用类型别名或自定义类型来创建新的

    2024年04月09日
    浏览(69)
  • 【C语言初阶】带你轻松掌握指针基础知识(1)——指针的定义,类型,大小

    君兮_的个人主页 勤时当勉励 岁月不待人 C/C++ 游戏开发 Hello,这里是君兮_,最近刚回家有点懒,从今天开始恢复更新并开始更新新的刷题系列,我们先继续更新0基础入门C语言的内容,今天给大家带来的是指针方面的内容,但由于是初阶,一些高级的用法我们放在进阶篇再讲

    2024年02月12日
    浏览(44)
  • 【C语言初阶】带你轻松掌握指针基础知识完结篇——野指针,指针运算,指针和数组,二级指针

    君兮_的个人主页 勤时当勉励 岁月不待人 C/C++ 游戏开发 Hello,这里是君兮_,今天继续给大家更新0基础入门C语言的内容,我们这次主要更新的依然是初阶指针的基础知识 废话不多说咱们直接开始吧!! 概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有

    2024年02月16日
    浏览(46)
  • “智能查单轻松实现批量快递查询,高效掌握快递物流信息!“

    亲爱的用户,你是否常常为了查询大量快递单号而感到烦恼?不用担心,我们已经为你提供了一种高效、智能的解决方案!现在,只需一键操作,即可实现批量快递查询,迅速了解每个单号的详细物流信息。 首先,我们要进入快递批量查询高手主页面,并点击高级设置, 在

    2024年02月12日
    浏览(44)
  • 视频批量高效剪辑,支持将视频文件转换为音频文件,轻松掌握视频格式

    在数字化时代,视频内容日益丰富,管理和编辑这些视频变得愈发重要。然而,传统的视频剪辑软件往往操作复杂,难以满足高效批量处理的需求。现在,一款全新的视频批量剪辑神器应运而生,它支持将视频文件一键转换为音频文件,同时轻松掌握各种视频格式,让你的视

    2024年04月14日
    浏览(66)
  • 晨曦记账本筛选支付宝收支明细,轻松掌握账户总花销,高效管理财务!

    晨曦记账本是一款专业的财务管理软件,可以帮助用户轻松记录和管理自己的收支明细。通过使用晨曦记账本,用户可以轻松筛选出支付宝的收支明细,更好地掌握自己的财务状况。 首先,第一步,我们要进入晨曦记账本主页面,在左上方的根据旁下拉列表选择“收支账户”

    2024年02月07日
    浏览(49)
  • 码上加速,低代码解锁高效交付案例

    站长工作台,致力于为京东物流所有站长、运营管理人员提供高效工作平台,拥有多元化的业务形态。我们力求 提升团队研发效率 、 实现敏捷业务交付 ,以打造一支具备灵活性、高度协作和强适应能力的敏捷团队。 站长工作台的报表页面和任务卡片页面,大多数的UI风格和

    2024年02月22日
    浏览(44)
  • 解锁更高效的AIGC工具:现代大语言模型工具推荐

    AI技术的普及已经在近年来不断增长。这种技术已经改变了我们与电脑的互动方式,让我们能够更高效、更自然地完成任务。本文将展示10个基于ChatGPT和GPT-3 AI模型构建的最强大的资源,使您更容易充分利用它们的潜力。因此,如果您想利用AI技术改进生活或工作,这篇文章是

    2024年02月09日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包