当前位置: 首页 > news >正文

C语言——深入解析C语言指针:从基础到实践从入门到精通(二)


🏡润下:个人主页

  🔥个人专栏: 《C语言基础》

🏔️山高万仞,只登一步!

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

文章目录

  • 一. const修饰指针
    • 1.1 const修饰变量
    • 1.2 const修饰指针变量
  • 二. 野指针
    • 2.1 野指针成因
      • 2.1.1指针未初始化
      • 2.1.2 指针越界访问
      • 2.1.3 指针指向的空间释放
    • 2.2 如何规避野指针
      • 2.2.1 初始化指针
      • 2.2.2 小心指针越界
      • 2.2.3 指针不用时要置NULL
        • 2.2.4 避免返回局部变量的地址
  • 三. assert断言
  • 四. 指针的使用和传值调用
    • 4.1 strlen的模拟实现
    • 4.2 传值调用和传址调用
  • 总结

前面介绍了指针的一些基本概念,本节介绍const修饰指针,野指针,assert断言,指针的调用方法

一. const修饰指针

const是常属性,不能改变

1.1 const修饰变量

变量是可以修改的,如果用const修饰变量,那么变量就不能被修改。

#include<stdio.h>
int main()
{int m = 0;m = 20;//可以修改const int n = 0;n = 20;//不可以修改return 0;
}

n还是变量,只不过不能修改是在语法层面上做了修饰是常变量
但是如果把变量的地址放在一个指针变量中,绕过n,使用n的地址对n进行修改,那么通过指针是可以改变变量的值

int main()
{const int n = 0;int* pn = &n;*pn = 20;printf("%d\n", n);return 0;
}

结果发现n的值被改变了,但是我们不想把变量的值改变,那么就应该用const限制指针的修改范围。

1.2 const修饰指针变量

const修饰指针变量可以放在*的左边,或者右边,结果是不一样的

int * p
int const *p
int * const p
int const * const p

const放在*的左边,修饰限定的是指针p所指向的对象的值不能修改,但是可以修改所指的对象

int main()
{int n = 20;int const* pn = &n;*pn = 10;//errprintf("%d\n", *pn); return 0;
}

const放在*的右边,const修饰限定的是指针p,不能改变所指向的对象,但是所指对象的值可以改变。

int main()
{int n = 20;int m = 300;int * const pn = &n;*pn = 10;printf("%d\n", *pn);pn = &m;//errprintf("%d\n", *pn);return 0;
}

两边都有const
那么结果显现既不能改变指针所指对象,也不能改变所指对象的值

画图解释
在这里插入图片描述

二. 野指针

野指针就是指针所指向的空间不确定

2.1 野指针成因

2.1.1指针未初始化

int main()
{int* p;//err 局部变量指针不初始化为随机值。*p = 20;//p为野指针return 0;
}

在这里插入图片描述

2.1.2 指针越界访问

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = &arr[0];int i = 0;for ( i = 0; i < 11; i++){printf("%d\n", *p);p++;}return 0;
}

在这里插入图片描述

2.1.3 指针指向的空间释放

int* test()
{int n = 100;return &n;
}
int main()
{int* p = test();printf("hehe\n");printf("%d\n", *p);return 0;
}

在这里插入图片描述

局部变量在函数调之后会销毁,当*p找到n的空间的时候,里面的内容是不对的,此时p为野指针

2.2 如何规避野指针

2.2.1 初始化指针

如果知道指针指向的哪里的空间就直接赋值地址,如果不知道指向哪里就赋值NULL,NULL是C语言中定义的一个标识符常量,值为0,0也是地址但是这个地址是无法使用的,读这个地址会报错

int *p=NULL

初始化:


int  main()
{int num = 1000;int* p1 = &num;int* p2 = NULL;//p2是野指针return 0;
}

2.2.2 小心指针越界

程序申请哪些空间就访问哪些空间,不能超出访问空间范围,即不能越界访问

2.2.3 指针不用时要置NULL

当指针指向一块空间的时候,我们可以通过指针访问该空间,当不再访问这片空间的时候,及时把该指针置NULL。
只要是NULL指针就不访问,访问时要判断指针是不是NULL

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = &arr[0];int i = 0;for ( i = 0; i < 10; i++){printf("%d ", *(p++));}p = NULL;p = &arr[0];//让p重新获得地址if (p==NULL){printf("是空指针\n");}else{printf("不是空指针\n");}return 0;
}
2.2.4 避免返回局部变量的地址

局部变量在函数调用结束后就会销毁,如果返回局部变量的地址就会导致指针指向错误的空间,return返值其实是先放在寄存器中,等到回到主函数时再把寄存器中的值,赋值给要打印的变量。

int* test()
{int n = 120;return &n;
}
int main()
{int r = test();printf("%d\n", r);return  0;
}

三. assert断言

assert.h文件定义,在运行时符合条件就运行,不符合条件就报错,这个 宏 称为断言

assert(p!=NULL);

判断是否等于NULL,如果不等于NULL就继续运行,如果等于就终止运行,并且给出报错信息。

#include<assert.h>
int main()
{int a = 10;int* pa = NULL;assert(pa != NULL);printf("%d\n", *pa);return 0;
}

在这里插入图片描述

#include<assert.h>
int main()
{int a = 10;int* pa = NULL;pa = &a;assert(pa != NULL);printf("%d\n", *pa);return 0;
}

在这里插入图片描述
assert()宏接受一个表达式作为参数,如果表达式为真,返回非零值,程序继续运行,如果表达式为假,返回值为0,assert()就会报错,错误会在屏幕上输出一条错误信息:显示没有通过的表达式,以及包含这个表达式的文件名和行号
在这里插入图片描述
assert()好处:
1.能自动识标识文件和出问题的行号
2.有一种无需修改就能开启和关闭assert()的机制

#define NDEBUG
#include<assert.h>

在头文件前面加一句#define NDEBUG作用相当于不在断言,把assert关掉了
但是,必须确定程序没有问题。
如果程序有问题可以把#define NDEBUG注释掉,这用就可以重新启动assert()
assert()缺点
引入了额外审查,会导致程序运行时间增加
一般在Debug环境中使用,在Release版本中assert()是不起作用的。

四. 指针的使用和传值调用

4.1 strlen的模拟实现

函数原型

size_t strlen(const char* str)

str接受字符串的起始地址,然后统计字符串中\0之前的个数,返回最终长度
模拟实现:

#include<stdio.h>
int my_strlen(char* str)
{int count = 0;while (*str){count++;str++;}return count;
}
int main()
{char arr[10] = { "abcdefg" };int len = my_strlen(arr);printf("%d\n", len);return 0;
}

优化

#include<stdio.h>
#include<assert.h>
int my_strlen(const char* str)
{int count = 0;assert(str);while (*str){count++;str++;}return count;
}
int main()
{char arr[10] = { "abcdefg" };int len = my_strlen(arr);printf("%d\n", len);return 0;
}

4.2 传值调用和传址调用

有一些问题必须要用指针解决
例:

void Swap(int x, int y)
{int tmp = x;x = y;y = tmp;
}
int main()
{int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交换之前a和b的值:a=%d b=%d\n", a, b);Swap(a, b);printf("交换之后a和b的值:a=%d b=%d\n", a, b);return 0;
}

结果:
在这里插入图片描述

x,y是独立的空间,形参的改变不会影响实参的结果
在这里插入图片描述
具体参考C语言——函数(超详细分析)中,实参和形参的关系

想要通过形参来改变实参可以借助指针

void Swap(int* px, int* py)
{int tmp = *px;*px = *py;*py = tmp;
}
int main()
{int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交换之前a和b的值:a=%d b=%d\n", a, b);Swap(&a, &b);printf("交换之后a和b的值:a=%d b=%d\n", a, b);return 0;
}

在这里插入图片描述
传址调用可以把函数和主函数真正的建立联系
只是需要主调函数中的变量实现计算,可以用传值调用
函数内部需要修改主调函数的值,就需要传址调用

总结

在这里插入图片描述

http://www.dtcms.com/a/477571.html

相关文章:

  • JSAR 空间小程序开发全指南:从环境搭建到跨场景应用落地
  • 驻马店网站建设价格上海人才市场招聘网
  • http 长链接和短链接
  • Java:将 Word 文档转换为密码保护的 PDF 文件
  • 267-基于Django的携程酒店数据分析推荐系统
  • Redis中Geospatial 实际应用指南
  • React水合技术:优化SSR和CSR的完美结合
  • 【六级】全国大学英语六级历年真题及答案解析PDF电子版(2015-2025年6月)
  • Adware Zap - Malware Cleaner for Mac v2.12.0 轻量级广告和恶意软件清理工具
  • 从底层到上层的“外挂”:deque、stack、queue、priority_queue 全面拆解
  • 淘客网站做弹窗广告注册公司的网址是什么
  • 域名是否就是网站网站建站网站建站
  • 李宏毅机器学习笔记21
  • 自动化脚本快速批量处理
  • 哈尔滨建工建设有限公司织梦网站后台如何做百度优化
  • 第 96 场周赛:三维形体投影面积、救生艇、索引处的解码字符串、细分图中的可到达节点
  • 网站建设宁夏凤凰云什么是电子商务系统
  • 用php做电子商务网站微信做商城网站
  • 【LeetCode】146. LRU 缓存
  • Linux Cgroup与Device Whitelist详解
  • 恶意代码防范技术与原理(二)
  • Facebook广告投放:地域定向流量不精准?x个优化指南
  • 【Linux指令 (三)】从理解到熟悉:探索Linux底层逻辑与指令的高效之道,理解Linux系统理论核心概念与基础指令
  • 2025年10月实时最新获取地图边界数据方法,省市区县街道多级联动【文末附实时geoJson数据下载】
  • 基于单片机的燃气热水器智能控制系统设计
  • 江苏省建设厅网站怎么登不上html网页代码编辑器
  • 云服务器怎么架设网站wordpress删除月份归档
  • go语言返回值 于defer的特殊原理
  • 《线性代数》---大学数学基础课程
  • 【Go】---流程控制语句