首页 > 其他分享 >高级C语言7

高级C语言7

时间:2024-05-06 19:11:31浏览次数:13  
标签:__ 头文件 函数 代码 高级 C语言 printf define

预处理:

程序员所编译C代码不能被直接编译,它需要一段程序把它先翻译一下,被翻译过程预处理,负责翻译的程序叫预处理器,被翻译的指令叫预处理指令,C代码中以#开头的都是预处理指令。

gcc -E xxx.c 查看C代码的预处理结果,显示在终端
gcc -E xxx.c -o xxx.i 把预处理的结果保存到文件中,以.i结尾的文件也被称为预处理文件。

文件包含指令:

​ #include 预处理指令的功能是导入一个头文件到当文件中,它用两中使用方法:

方法1:#include <file_name.h>

​ 从系统指定的路径查找并导致头文件,一般用于导入标准库、系统、第三方库的头文件。getch.h

/usr/include

方法2:#include "file_name.h"

​ 从系统当前路径查找并导致头文件,如果没有再从系统指定的路径查找并导致头文件,一般用于导入自定义头文件。

​ 操作系统通过设置环境变量来指定头文件查找路径,或者设置编译器参数

​ gcc xxx -I /path(大写i)

宏替换指令:

定义宏名:

​ #define 宏名 会被替换的内容

使用宏名:

​ printf("%d\n",宏名);

​ 注意:在预处理阶段,预处理器会把代码使用的宏名替换成宏名后面的那段内容。

宏常量:

​ #define 宏名 字面值数据

​ 给没有意义的字面值数据,取一个有意义名字,代替它,这样可以提高代码的可读性、可扩展性,还可以方便代码扩展。

#include <stdio.h>

#define NUM 100
#define ARR_LEN 10
void show_arr(int arr[], int len) {
    for (int i = 0; i < len; ++i) {
        printf("%d%c", arr[i], " \n"[i == len - 1]);
    }
}

int main() {
    int num = 100, len = 10;
    printf("%d\n",NUM);
    printf("%d\n",num);
    int arr[ARR_LEN] = {1,12,3,4,5};
    len = 100;
    ARR_LEN = 100;
    show_arr(arr,ARR_LEN);
} 
宏表达式:

​ #define 宏名 表达式、操作、更复杂的标识符

#include <stdio.h>                                                                                      
typedef struct Student {
    int id;
    char name[20];
    char sex;
    float score;
} Student;

#define STU_FORMAT "%d %s %c %f"

#define pf printf
#define sf scanf

#define YEAR_SEC 3600*24*365lu

int main() {
    Student stu = {1001,"hehe",'w',88.8};
    printf("请输入学生信息:");

    sf(STU_FORMAT,&stu.id,stu.name,&stu.sex,&stu.score);
    pf(STU_FORMAT,stu.id,stu.name,stu.sex,stu.score);
}
定义宏常量和宏表达式要注意的问题:

​ 由于宏常量和宏表达式可能使用在表达式中,因此在定义宏常量和宏表达式的末尾不要加分号。

​ 一般宏名全部大写以作区分

​ (局部变量全部小写,全局变量首字母大写,循环变量i、j、k、函数名全部小写+下划线、数组arr、字符串str、指针p)

枚举常量与宏常量的区别:

​ 提前:它们都可以提高代码的可读性,给没有意义字面值数据取一个有意义的名字。

​ 1、从安全角度来说枚举常量要比宏常量安全,因为宏常量是通过替换实现的,在替换过程中可能会导致新的错误,如:有与宏名重名和函数或变量。

​ 2、但枚举常量没有宏常量方便,枚举常量只能是整型数据,而宏常量可以是任何类型的,甚至是复杂的表达式,宏常量的使用范围更广。

​ 总结:如果是大量的整型字面值数据建议定义为枚举常量,如果少量的或整型以外的类型的字面值数据建议定义为宏常量。

预定义的宏:
编译器预定义的宏:
__FILE__ 获取当前文件名
__func__ 获取当前函数名
__LINE__ 获取当前行号
__DATE__ 获取当前日期
__TIME__ 获取当前时间
__WORDSIZE 获取当前编译器的位数
// 适合用来显示警告、错误信息。
#include <stdio.h>
int main() {
    printf("%s\n",__FILE__);
    printf("%s\n",__func__);
    printf("%d\n",__LINE__);
    printf("%s\n",__DATE__);
    printf("%s\n",__TIME__); 
    printf("%d\n",__WORDSIZE);
}
标准库预定义的宏:
// limits.h 头文件中定义的所有整数类型最大值、最小值
#define SCHAR_MIN (-128)
#define SCHAR_MAX 127 
#define UCHAR_MAX 255

#define SHRT_MIN  (-32768)
#define SHRT_MAX  32767
#define USHRT_MAX 65535

#define INT_MIN  (-INT_MAX - 1)
#define INT_MAX  2147483647
#define UINT_MAX  4294967295U

#define LLONG_MAX   9223372036854775807LL
#define LLONG_MIN   (-LLONG_MAX - 1LL)
#define ULLONG_MAX  18446744073709551615ULL
PATH_MAX

// stdlib.h 头文件定义两个结标志
#define EXIT_SUCCESS (0)
#define EXIT_FAILURE (-1)

// stdbool.h 头文件定义了bool、true、false
#define bool    _Bool
#define true    1
#define false   0

// libio.h 头文件定义了NULL
#define NULL ((void*)0) 

宏函数:

什么是宏函数:

​ 宏函数不是真正的函数,而是带参数的宏替换,只是使用方法像函数而已。

​ 在代码中使用宏函数,预处理时会经历两次替换,第一次把宏函数替换成它后面的一串代码、表达式,第二次把宏函数中的参数替换到表达式中。

#define 宏名(a,b,c,...) a+b*c
定义宏函数要注意的问题:

​ 1、假如宏函数执行复杂的多条语句,可能会因为在if分支中缺少大括号而出现问题,可以使用大括号包括,进行保护,避免if的影响。

#define 宏名(a,b,c,...) {代码1; 代码2; ...}

2、、可以通过加大括号解决问题1,但是如果if后面有else,也会出现问题

3、因此linux内核和C++开源的代码中,经常会在宏定义中使用do-while(0)来保证代码安全,除此之外还可以起到:

​ 在宏函数中定义同名变量、而不会冲突(语句块定义)

​ 还可以在解决代码冗余问题上替换goto的效果

4、宏函数后面的代码不能直接换行,如果代码确定太长,可以使用续行符换行。

#define 宏名(a,b,c,...) {  \
	代码1; \
	代码2; \
	 ...   \
}
#define 宏名(a,b,c,...) do {  \
	代码1; \
	代码2; \
	...   \
} while(0)

​ 5、为了防止宏函数出现二义性,对宏参数要尽量多加小括号。

​ 二义性:就是使用宏函数的环境不同、参数不同,造成宏函数有多执行规则,会出现出乎意料的执行结果,这种宏函数的二义性,设计宏函数时要尽量杜绝。

调用宏函数要注意的问题:

​ 1、传递给宏函数的参数不能使用自变运算符,因为我们无法知道参数在宏代码中会被替换多少次。

​ 2、宏函数没有返回值,只是个别宏函数表达式有计算结果。

普通函数与宏函数的优缺点?

宏函数的优点:

​ 1、执行速度快,它不是真正的函数调用,而是代码替换,不会经历传参、跳转、返回值。

​ 2、不会检查参数的类型,因此通用性强。

宏函数的缺点:

​ 1、由于它不是真正的函数调用,而是代码替换,每使用一次,就会替换出一份代码,会造成代码冗余、编译速度慢、可执行文件变大。

​ 2、没有返回值,最多可以有个执行结果。

​ 3、类型检查不严格,安全性低。

​ 4、无法进行递归调用。

普通函数的优点:

​ 1、不存在代码冗余的情况,函数的代码只会在代码段中存储一份,使用时跳转过去执行,执行结束后再返回,还可以附加返回值。

​ 2、安全性高,会对参数进行类型检查。

​ 3、可以进行递归调用,实现分治算法。

函数的缺点:

​ 1、相比宏函数它的执行速度慢,调用时会经历传参、跳转、返回等过程,该过程耗费大量的时间。

​ 2、类型专用,形参什么类型,实参必须是什么类型,无法通用。

什么样的代码适合封装成宏函数?

​ 1、代码量少,即使多次使用也不会造成代码段过度冗余。

​ 2、调用次数少,但执行次数多,也就是宏函数会在循环语句中调用。

​ 3、函数的功能对返回值没有要求,也就是函数的功能不是通过返回值达到的。

封装一个malloc、free函数:
#include <stdio.h>
#include <stdlib.h>
void* _my_malloc(const char* filename, const char* func, size_t line, size_t size) {
	void *ptr = malloc(size);
	printf("%s %s %u %p\n", filename, func, line, ptr);
	return ptr;
}
#define my_malloc(size) _my_malloc(__FILE__, __func__, __LINE__, size)
#define my_free(ptr) do {\
	printf("%s %s %u %p\n", __FILE__, __func__, __LINE__, ptr);\
	free(ptr);} while (0);
int main() {
	int *p = my_malloc(40);
	my_free(p);

}
实现一个通用的变量交换函数。
#include <stdio.h>
// 只适合数值型交换、数据可能溢出
#define SWAP(a, b) do {(a) = ((a) + (b)), (b) = ((a) - (b)); (a) = ((a) - (b));} while (0);
// 数据不溢出,只适合整型数据,并且不能是同一个值
#define SWAP(a, b) do {a = a ^ b; b = a ^ b; a = a ^ b;} while (0);
// 不能交换结构变量 浪费内存
#define SWAP(a, b) do {tong double t = a; a = b; b = t;} while (0);
// 可以交换任意类型,多一个参数
#define SWAP(a, b, type) do {type t = a; a = b; b = t;} while (0):
// 只能在GNU系列编译器下使用
#define SWAP(a, b) \
	do {typedef(a) (t) = (a); (a) = (b); (b) = (t);} while (0);
int main() {
	
}

条件编译:

​ 条件语句(if、switch、for、while、do while)会根据条件选择执行哪些代码,条件编译就是预处理器根据条件选择哪些代码参与下一步的编译。

负责条件编译的预处理指令有:

#if #ifdef #ifndef #elif #else #endif
头文件卫士:

​ 这种固定写法,在头文件中使用,它能防止头文件被重复包含,所有的头文件都要遵循这个规则。

#ifndef FILE_H // 判断FILE_H宏是否正在,不存在则条件为真
#define FILE_H // 定义FILE_H宏

// 头文件卫士能保证此处不重复出现

#endif//FILE_H // #ifndef的结尾
注释代码:
// 只能注释单行代码,早期的编译器不支持该用

/* 多行注释,但不能嵌套 */

#if 0|1
可注释大块代码,可以嵌套    
#endif 
版本、环境判断:
#if __WORDSIZE == 64
	typedef long int        int64_t;
#else
	typedef long long int       int64_t;
#endif


// 判断是否是Linux操作系统:
#if __linux__

#endif 

// 判断是否是Windows操作系统:
#if __WIN32 | __WIN32__ | __WIN64__

#endif 



// 判断gcc还是g++:
int main() {
#if __cplusplus
    printf("你使用是g++编译器\n");
#else
    printf("你使用是gcc编译器\n");
#endif
}

DEBUG宏:

​ 专门用于调试程序的宏函数,这种宏函数在程序测试、调试、试运行阶段执行,在程序正式上线阶段不执行,这类函数会根据DEBUG宏是否定义确定执行的流程。

一些操作提示,如:xxx操作成功,xxx操作失败,分配内存的记录、释放内存的记录,这类型消息开发人员、测试人员需要看到,但用户不需要看到。

不常用的预处理指令:

#line <常整数> 设置当前代码的行号,目前没有发现它有什么用

#error "在预处理阶段提示错误信息",一旦预处理遇到它,将不再继续编译,它不能单独使用必须与条件判断系列语句配合使用 

#warning "在预处理阶段提示警告信息" 不能建议单独使用,最好与条件判断系列语句配合使用。

#pragma GCC poison <标识符> 把标识符设置病毒,禁止在代码中使用

#pragma pack(n)  设置最大对齐和补齐字节数
  每个系统在进行对齐和补齐都有一个最大对齐和补齐字节数n,也就是超出n字节按n字节计算,例如:linux32系统n=4,windows32 n=8
设置要求:
	1、n < 系统默认的最大对齐、补齐字节数,往大了调整没有意义,速度不会提升还会导致内存浪费。
	2、n必须是2的x次方,也就是必须是1、2、4、8、16这一类的整数
宏函数的变长参数:
#define func(...) __VA_ARGS__

注意:这种用法必须配合,printf/fprintf/sprintf系列支持变长参数的函数使用。

在编译时定义宏:
gcc xxx.c -D ARR_LEN=3 
-D ARR_LEN=3 <=> #define ARR_LEN 3 跟在代码中定义宏的效果一样

gcc xxx.c -D DEBUG
-D DEBUG <=> #define DEBUG

标签:__,头文件,函数,代码,高级,C语言,printf,define
From: https://www.cnblogs.com/sleeeeeping/p/18175675

相关文章

  • 07. C语言程序执行流程控制
    【有条件执行语句】ifesle语句ifelse语句根据一个条件确定是否执行一段代码,执行条件是一个布尔值,布尔值为true则执行,为false则不执行,同时可以设置不符合条件时执行的语句。if(执行条件){  符合条件时执行的代码;}else{  不符合条件时执行的代码;}使用事项:1......
  • C语言加强
    变量、指针和关键字两个口诀:变量变量,能变,就是能读能写,必定在内存(RAM)里指针指针,保存的是地址,32位处理器中的地址都是32位的,无论是什么类型的指针变量,都是4字节指针对于32位处理器里面,地址是32位的,所以指针的大小为4字节,sizeof(p)=4,sizeof(*p)=指针所指向的......
  • 高级C语言6
    结构:什么是结构:​ 是一种由程序员设计的复合数据类型,它由若干个其它类型的成员组成,用于统一描述事物的各项属性。​ 使用各类型的变量也可以描述事物的各项属性(如:通讯录项目),但使用麻烦且容易出错,没有使用结构方便,安全性高、统一性高,同时结构也是面向对象编程的基础。​ 基础C......
  • 高级C语言5
    输出缓冲区:​ 当我们使用标准库的输出系列函数打印数据到屏幕,数据并不会立即显示到屏幕上,而先存储到一块内存中,我们把这块内存称为输出缓冲区,等满足相关条件后,再从缓冲区中显示到屏幕,相关条件有:1、从输出状态切换到输入状态。2、缓冲区满了,1k=1024个字节,系统会把缓冲区中所有数......
  • 深入了解Appium:Capability 高级配置技巧解析
    简介Appium的除了基础的Capability设置,还提供了许多辅助配置项,用于优化自动化测试。这些配置项旨在执行基础配置之外的附加操作。例如:指定设备别名、设备ID或是设置超时时间等,虽然这些不是必需的选项,但是为了实现更高效的测试,通常也建议依据测试的情况适当的添加。xcuites......
  • 高级C语言2
    计算机的内存长什么样子?1、计算机中的内存就像一叠非常厚的“便签”,一张便签就相当于一个字节的内存,一个字节有8个二进制位2、每一张“便签”都有自然排序的一个编号,计算机是根据便签的编号来访问、使用"便签"3、CPU会有若干个金手指,每根金手指能感知高低电平,高电平转换成1,低电......
  • 高级C语言1
    一、程序的内存分段:(进程映像)​ 当执行程序的运行命令后,操作系统会给程序分配它所需要的内存,并划分成以下内存段供程序使用:text代码段:​ C代码被翻译成二进制指令后存储在可执行文件中,当可执行文件被操作系统执行时,它会把里面的二进制指令(编译后的代码)加载到这个内存段,它里面......
  • 06. C语言指针
    【指针】C语言使用数据名调用数据,数据名相当于C语言的直接寻址,直接寻址只能调用固定数据,而指针是间接寻址,指针存储了另一个数据的地址,使用指针调用数据时首先取指针存储的内存地址,之后使用此地址调用数据,使用间接寻址有如下几点优势:1.统一数据的调用方式,因为指针是调用数据的中间......
  • 标准C语言5
    进制转换:​ 现在的CPU只能识别高低两种电流,只能对二进制数据进行计算。​ 二进制数据虽然可以直接CPU计算识别,但不方便书写、记录,把二进制数据转换成八进制是为了方便记录在文档中。​随着CPU的不断发展位数不断增加,由早期的8位逐渐发展成现在的64位,因此八进制就不能满......
  • 标准C语言4
    一、函数什么是函数:function​函数就是一段具有某一项功能的代码集合,它是C语言中管理代码的最小单位,把具有某项功能的若干行代码封装在函数中方便管理代码且方便重复调用。函数的分类:标准库函数:​ C语言标准委员会为C语言以函数形式提供了一些基础功能,这些函数被封装在li......