C语言——预处理和指针
C语言——预处理和指针
- 预处理
- 宏
- 宏定义
- 宏的作用域
- 带参的宏
- 文件包含
- 条件编译
- 指针
- 指针的概念
- 指针的定义
- 指针变量初始化
- 指针+一维整型数组
预处理
编程的流程分为:编辑、编译、运行、调试四个阶段;
预处理属于编译阶段,编译过程又可以分为:预处理、编译、汇编、链接;
预处理:
预处理是将代码中相关的预处理命令执行最终生成只包含c语言代码的文件,详细来说预处理过程实质上是处理“#”,将#include包含的头文件直接拷贝到.c当中;将#define定义的宏进行替换;将#if #else #endif定义的无用代码过滤掉,同时将代码中没用的注释部分删除等。
预处理所完成的基本上是对源程序的“替代”工作。经过此种替代,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。
编译,编译是对语法进行检查将源代码生成汇编代码。
汇编,汇编是将汇编代码生成机器代码。对于被翻译系统处理的每一个C语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。
目标文件由段组成。通常一个目标文件中至少有两个段:
1、代码段:该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般却不可写。
2、数据段:主要存放程序中要用到的各种全局变量或静态的数据。一般数据段都是可读,可写,可执行的。
链接,链接是将使用的其他代码链接到一起生成可执行文件。详细来说链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。
下面详细说明预处理的过程,预处理指令有宏定义、文件包含、条件编译;
宏
宏定义
宏定义的语法形式:
#define 标识符 字符串
或者是#define 宏名 宏值
预处理命令都是以#开头的
例如:#define n 10
注意在定义宏时在宏值后面是不可以加分号的如果加了分号在预处理进行文本替换的时候会一并把分号一起替换了,比如说我在定义#define N 10的后面加上分号可以看到在进行预处理时N会被替换成10;
宏名的命名遵循标识符的命名规则,在定义宏名时为了区分宏名和普通变量名通常把宏名写成大写,比如#define N 10,这个宏定义的含义是将来代码中出现的的N都代表10,在编写代码是可以用N来表示10。这个本质就是在预处理的时候会进行文本替换也就是把宏名替换成宏值。通过预处理指令可以看到上述效果:
通过预处理指令gcc -E testH.c -o testH.i把testH.c文件只做预处理操作得到的目标文件testH.i打开testH.I可以看出宏名N被替换成了10。
宏的作用域
宏的作用域是从定义位置开始往下发挥作用
#include <stdio.h>int main(void)
{printf("N = %d\n", N);return 0;
}#define N 10void test(void)
{printf("N = %d\n", N);
}
预处理后的代码为:
编译上述代码看到说main函数中的N未定义的错误,然后通过预处理指令对testH.c只做预处理操作可以看到main函数中的N并没有报错,进一步说明宏的作用域是从定义位置开始往下发挥作用。如果我们想限制宏的作用域应该怎么做呢?我可以通过**#define 宏名 宏值 #undef 宏名**来限制宏的作用域,通过下面的例子来详细说明:
上述代码中我把#define N 10宏定义限制在main函数的范围内,然后对testH.c文件只做预处理操作可以看出在进行预处理时只有main函数的N替换成了宏值10而test()函数中的N并没有替换成宏值10。所以**#define 宏名 宏值 #undef 宏名**具有限制宏的作用域的作用。
带参的宏
语法:
#define 宏名(参数) 宏值
比如说#define ADD(a, b) a+b这个宏在预处理时会把代码中的ADD(a, b)都替换成a+b从而实现两个数相加的效果,在形式上看着带参的宏有点像函数实际上带参的宏和函数是有本质上的区别的:
1、带参宏和函数的处理阶段不一样,宏是在预处理阶段而函数是在编译阶段;
2、二者的使用阶段也不一样,宏是在预处理阶段就使用结束了而函数是在调用的时候才会进行使用,宏的本质是进行文本的原样替换而函数的使用本质上是函数代码的调用,,宏的参数只是进行文本替换用的不会进行语法检查,而函数的参数是有类型的在编译阶段会进行类型的检查。
宏的副作用
使用宏是可能会是运算优先级发生改变下面以一个具体的例子说明吧;
在调用宏时理想的结果是先让1+2和3+4求和然后再将二者的和相乘可以在进行文本的原样替换时把MUL(1 + 2, 3 + 4)替换成了1 + 2*3+4所以计算结果是11;为了避免发生这样的结果通常在进行宏定义时该加括号的加括号。
文件包含
文件包含分为
1、#include <>
2、#include “”
二者的区别是:查找头文件的方式不一样,<>是到系统默认的路径去找头文件而""实现到当前目录下寻找如果没有再到系统默认的目录下寻找。
条件编译
条件编译总共有三种形式:
1、
# ifdef 标识符
程序段 1
#else
程序段 2
#endif
它的作用是若所指定的标识符已经被# define 命令定义过,则 在程序编译阶段编译程序段 1; 否则编译程序段 。其中# else 部分可以没有。
2、
#ifndef 标识符
程序段 1
#else
程序段 2
#endif
上述形式只是第一行与第一种形式不同:将 “ifdef” 改为 “ifndef” 。它的作用是若标识符未被定义过则编译程序段 1; 否则编译程序段 2。这种形式与第一种形式的作用相反。
3、
#if 表达式
程序段1
#else
程序段2
#endif
它的作用是当指定的表达式值为真(非零)时就编译程序段 1; 否则编译程序段 。可以事先给定条件,使程序在不同的条件下执行不同的功能。
指针
指针的概念
指针就是地址而地址就是内存单元的编号。指针也是一种数据类型是专门用来处理地址这种数据的类型。
指针的定义
数据类型 变量名
语法:
基类型 * 变量名
其中基类型包含整型、浮点型、字符型、数组类型、指针类型、结构体类型 、函数类型等等;
该类型表示指针类型指向的内存空间所存放的数据是什么样的类型;
**“*”**表示表示此时定义的是一个 指针类型 的变量;
变量名符合标识符的命名规则;
举个例子说明吧:
int a = 10; //表示a的内存空间中存放的是整型类型的数据;
float b = 10;//表示b的内存空间中存放的是浮点型类型的数据;
int p = &a;
int p = &b;
其中&a表示a所在内存空间的首地址,表示获得了一块 可以存在int型数据的内存空间的地址。
int p;
int 含义 首先表示是一个 指针类型,表示指向int型数据的指针类型 。
指针变量的引用
int a = 10;
int *p = &a; 这里p指向a,因为p中保存了a的地址;
“*”是指针运算符,它是一种单目运算符,且运算的对象只能是地址;
*p:表示访问p所指向的基类型的内存空间这种访问是间接访问可以通过a直接访问;
*p的访问完整流程是:
1、首先拿出p中地址,到内存中定位
2、偏移出sizeof(基类型)大小的一块空间
3、将偏移出的这块空间,当做一个基类型变量来看
p最后的运算效果相当于就是一个基类型的变量,也就是p等价于a;
指针变量初始化
如果指针变量没有初始化此时就是随机值,该指针叫野指针。野指针对程序的执行是有风险的所以在初始化的时候必须让指针有明确的指向例如:
int a = 1;
int *p = &a;
int *p = NULL;此时p表示的是一个空指针,p的地址编号是0;
指针的赋值:
int *p;
p = NULL;
定义多个指针变量:
int*p,*q;
*是用来修饰变量的表示此时定义的是一个指针类型的变量;而不能写成int *p,q;如果这样写p代表的是一个指针变量而q是一个int类型的变量。
指针的作为函数的参数一个重要功能就是实现被调修改主调那么如何实现被调修改主调呢?
其实指针作为函数的参数通过把背调的地址传过来然后就能通过这个地址找到其在内存中所存在的位置,从而访问内存空间中存放的数据来实现被调修改主调的效果。
指针作为函数参数:
形参是一个指针类型的变量,用来接受实参而实参是要操作内存空间的地址;
实参是要修改谁就传谁的地址,且被调函数中一定要有*p运算;
下面以一个例子来说明值传递和址传递要注意的问题:
#include <stdio.h>void minMax(int a, int b, int *max, int *min)
{*max = a > b ? a : b;*min = a < b ? a : b;
}int main(void)
{int a = 0, b = 0;int max = 0, min = 0;scanf("%d %d", &a, &b);minMax(a, b, &max, &min);printf("max = %d min = %d\n", max, min);return 0;
}
通过上述代码我们可以看到在进行函数的传参时既有值传递也有址传递那什么时候用值传递什么时候用址传递呢?如果你想要通过形参去改变实参那么就要用址传递的形式。就比如说上述代码中我要从函数中带出一个最大值和一个最小值但是不能有返回值,那么就要实现形参改变实参的效果通过值传递是实现不了这个效果的所以max和min采用了址传递的方式,我们想改变的是max和min而a和b这两个数是不需要改变的所以采用值传递就可以了。
指针+一维整型数组
如果要定义一个一维数组指针那我们要定义一个什么类型的指针呢?谁又能代表数组首元素的地址呢?首先我们得理解数组名的含义,1、数组名代表数组的类型;2、数组名代表数组首元素的地址;由数组名的含义我们可以知道数组名的首元素可以代表数组首元素的地址,数组首元素也就是a[0]而a[0]对应的数据类型是int型代表a取了一块int类型数据的地址也就是int类型,所以a的类型是int型,这样我们在定义一个一维数组指针是要定义一个int*类型的指针例如:int a[50];int p = a;指的是创建一个int类型的变量也就是创建了一个指针p,p指向的是a数组所在的内存空间的首地址,p所指向内存空间里存放的数据的数据类型是int型。
指针的访问方式;
下面以一个具体的例子来说明指针的访问方式:
#include <stdio.h>void printArray(int *a, int len)
{int i = 0;for(i = 0; i < len; ++i){printf("%d\n", *(a + i));}
}int main(void)
{int a[] = { 1, 2, 3, 4, 5};printArray(a, 5);return 0;
}
上述程序实现了一个数组元素打印的过程,通过把数组首元素的地址传给函数在进行数组遍历的时候就能通过数组首元素的地址找到该数组所在的内存空间,在遍历数组元素时通过指针运算来控制指针的偏移使指针指向数组每一个元素所在空间的地址然后通过指针运算符来实现对数组元素的访问,从而实现数组元素的打印功能。其中(a + i)等价于a[i](a[i]还可以写成i[a]因为*(a + i)等价于*(i + a))。
总的来说数组作为函数参数 :
第一,形参要是数组形式,其本质上是一个指针类型变量例如int *a;除了传进一个指针还要传进去数组长度方便对数组的遍历;
第二,实参是数组名和数组长度,数组名代表的是数组首地址。
相关文章:

C语言——预处理和指针
C语言——预处理和指针 预处理宏宏定义宏的作用域带参的宏 文件包含条件编译 指针指针的概念指针的定义指针变量初始化指针一维整型数组 预处理 编程的流程分为:编辑、编译、运行、调试四个阶段; 预处理属于编译阶段,编译过程又可以分为&…...

iptables防火墙(一)
目录 1、Linux防火墙基础 2、iptables的四表五链结构 2.1 iptables的四表五链结构介绍 2.2 四表五链 2.2.1 四表 2.2.2 五链 2.3 包过滤的匹配流程 2.3.1 规则链之间匹配顺序 2.3.2 规则链内部的处理规则 2.3.3 数据包过滤的匹配流程 3、 编写防火墙规则 3.1 iptabe…...
(leetcode学习)50. Pow(x, n)
实现 pow(x, n) ,即计算 x 的整数 n 次幂函数(即,xn )。 示例 1: 输入:x 2.00000, n 10 输出:1024.00000示例 2: 输入:x 2.10000, n 3 输出:9.26100示例 …...

QT 5.12.0 for Windows 安装包 QT静态库 采用源码静态编译生成
qt-5.12.0-static.zip 下载地址(资源整理不易,下载使用需付费,且文件较大,不能接受请勿浪费时间下载): 链接:https://pan.baidu.com/s/1ftfHFG_jGFwVaOAvBVrNFg?pwdtvtp 提取码:tvtp...

【生成式人工智能-三-promote 神奇咒语RL增强式学习RAG】
如何激发模型的能力 提示词 promotCoTRL 增强式学习Reforcement learning提供更多的资料提供一些范例Incontext- learning 任务拆解让模型自己检查错误让模型多次生成答案Tree of Thoughts让模型使用其他工具RAG写程序POT其他工具 让多个模型合作参考 在模型不变的情况下&#…...
C++连接oracle数据库连接字符串
//远程连接,需要安装oracle客户端sprintf(szConnect4, ("Provider OraOLEDB.Oracle.1; Password %s; Persist Security Info True; User ID %s; Data Source \"(DESCRIPTION (ADDRESS_LIST (ADDRESS (PROTOCOL TCP)(HOST %s)(PORT 1521)) )(CONN…...

判断字符串是否接近:深入解析及优化【字符串、哈希表、优化过程】
本文将详细解析解决这个问题的思路,并逐步优化实现方案。 问题描述 给定两个字符串 word1 和 word2,如果通过以下操作可以将 word1 转换为 word2,则认为它们是接近的: 交换任意两个现有字符。将一个现有字符的每次出现转换为另…...
C 和 C++ 中信号处理简单介绍
信号处理是编程中一个重要的主题,特别是在需要处理异步事件和错误情况的系统中。在 C 和 C 语言中,信号处理机制提供了一种优雅的方式来响应特定的系统事件,例如用户中断、异常情况或其他信号。在这里,我将详细介绍 C 和 C 中信号…...

什么是云边协同?
当今信息技术高速发展的时代,"云边协同"(Edge Cloud Collaboration)已经成为一个备受关注的话题。它涉及到云计算和边缘计算的结合,为数据处理、存储和应用提供了全新的可能性。本文将介绍云边协同的概念、优势以及在不…...

YOLOv5改进 | 主干网络 | 将backbone替换为MobileNetV2【小白必备教程+附完整代码】
秋招面试专栏推荐 :深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 💡💡💡本专栏所有程序均经过测试,可成功执行💡💡💡 专栏目录: 《YOLOv5入门 改…...

ARMxy边缘计算网关用于过程控制子系统
在现代工业生产中,过程控制系统的优化对于提高生产效率、保证产品质量、降低能源消耗等方面都具有重要意义。而 ARMxy 工控机作为一种高性能、高可靠性的工业控制设备,正逐渐成为过程控制系统优化的新选择。 ARMxy 工控机采用了先进的 ARM 架构处理器&am…...

Python | TypeError: unsupported operand type(s) for +=: ‘int’ and ‘str’
Python | TypeError: unsupported operand type(s) for : ‘int’ and ‘str’:深度解析 在Python编程中,遇到“TypeError: unsupported operand type(s) for : ‘int’ and ‘str’”这类错误通常意味着你尝试将一个整数(int)和…...
什么是开源什么是闭源?以及它们之间的关系
开源软件(Open Source Software) 定义:开源软件是指其源代码可以被公众访问和使用的软件。用户可以查看、修改和增强软件的源代码。 许可:通常遵循特定的开源许可证,如GNU通用公共许可证(GPL)、…...
SpringBoot+Mybatis Plus实际开发中的注解
SpringBoot+Mybatis Plus实际开发中的注解 实体类Service层Mapper层Controller层启动类配置类SpringBoot+Mybatis Plus实际开发中的注解 实体类 @Data : 底层实现了getter、setter、toString、hashCode、equals 和无参构造@AllArgsConstructor: 底层实现了有参构造@NoArgsCon…...

【香橙派系列教程】(八)一小时速通Python
【八】一小时速通Python 本章内容服务于香橙派下的开发,用C语言的视角来学习即可,会改就行。 详细说明,请看链接:python全篇教学 Python是一种动态解释型的编程语言,Python可以在Windows、UNIX、MAC等多种操作系统上 使用&…...
了解JavaScript 作用、历史和转变
JavaScript 是一种即时执行的脚本语言,其代码在浏览器环境中通过内置的 JavaScript 引擎被动态地一行接一行地解释执行。这一特性赋予了开发者极高的灵活性和效率,因为代码修改后能立即生效,无需经历编译过程,从而加速了开发周期和…...

遗传算法与深度学习实战——生命模拟与进化论
遗传算法与深度学习实战——生命模拟与进化论 0. 前言1. 模拟进化1.1 代码实现1.2 代码改进 2. 达尔文进化论3. 自然选择和适者生存3.1 适者生存3.2 进化计算中的生物学 小结系列链接 0. 前言 生命模拟通过计算机模拟生物体的基本特征、遗传机制、环境互动等,试图模…...
rt-thread H7 使用fdcan没有外接设备时或发送错误时线程被挂起的解决方案
一、问题查找 使用的开发版是硬石的H7芯片型号STM32H743IIT6,测试时发现如果外面没有连接CAN设备,程序调用CAN发送时会一直等待发送反馈,导致相关线程挂起。 在线仿真时发现是卡在can.c文件的168行_can_int_tx函数:rt_co…...

exptern “C“的作用,在 C 和 CPP 中分别调用 openblas 中的 gemm 为例
openblas提供的sgemm有两种方式,一种是通过cblas,另一种是直接声明并调用 sgemm_ 其中,cblas方式是更正规调用方法; 1,调用openblas的 sgemm 的两种方式 1.1 c语言程序中使用 sgemm hello_sgemm.c #include <st…...

如何提前预防网络威胁
一、引言 随着信息技术的迅猛进步,网络安全议题愈发凸显,成为社会各界不可忽视的重大挑战。近年来,一系列网络安全事件的爆发,如同惊雷般震撼着个人、企业及国家的安全防线,揭示了信息安全保护的紧迫性与复杂性。每一…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...

视频字幕质量评估的大规模细粒度基准
大家读完觉得有帮助记得关注和点赞!!! 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用,因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型(VLMs)在字幕生成方面…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...
C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...
4. TypeScript 类型推断与类型组合
一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式,自动确定它们的类型。 这一特性减少了显式类型注解的需要,在保持类型安全的同时简化了代码。通过分析上下文和初始值,TypeSc…...
Java 与 MySQL 性能优化:MySQL 慢 SQL 诊断与分析方法详解
文章目录 一、开启慢查询日志,定位耗时SQL1.1 查看慢查询日志是否开启1.2 临时开启慢查询日志1.3 永久开启慢查询日志1.4 分析慢查询日志 二、使用EXPLAIN分析SQL执行计划2.1 EXPLAIN的基本使用2.2 EXPLAIN分析案例2.3 根据EXPLAIN结果优化SQL 三、使用SHOW PROFILE…...

python基础语法Ⅰ
python基础语法Ⅰ 常量和表达式变量是什么变量的语法1.定义变量使用变量 变量的类型1.整数2.浮点数(小数)3.字符串4.布尔5.其他 动态类型特征注释注释是什么注释的语法1.行注释2.文档字符串 注释的规范 常量和表达式 我们可以把python当作一个计算器,来进行一些算术…...

RFID推动新能源汽车零部件生产系统管理应用案例
RFID推动新能源汽车零部件生产系统管理应用案例 一、项目背景 新能源汽车零部件场景 在新能源汽车零部件生产领域,电子冷却水泵等关键部件的装配溯源需求日益增长。传统 RFID 溯源方案采用 “网关 RFID 读写头” 模式,存在单点位单独头溯源、网关布线…...

AI书签管理工具开发全记录(十八):书签导入导出
文章目录 AI书签管理工具开发全记录(十八):书签导入导出1.前言 📝2.书签结构分析 📖3.书签示例 📑4.书签文件结构定义描述 🔣4.1. 整体文档结构4.2. 核心元素类型4.3. 层级关系4.…...

Continue 开源 AI 编程助手框架深度分析
Continue 开源 AI 编程助手框架深度分析 一、项目简介 Continue 是一个模块化、可配置、跨平台的开源 AI 编程助手框架,目标是让开发者能在本地或云端环境中,快速集成和使用自定义的 LLM 编程辅助工具。它通过支持 VS Code 与 JetBrains 等主流 IDE 插件…...