C语言(1.C语言和C程序)
C语言:一门通用的计算机编程语言
由于互联网各厂对C语言的使用和修改,导致C语言的通用性越来越差,国际标准化组织将C语言进行标准化。常用的标准为C11,C89,C99,C90。
ANSI C为最初的标准,以后经常能看到这个标准
C语言一共有32个关键字,但是C99标准后有增加了5个关键字,目前主流编译器对C99的支持并不好,所以在这里我们默认使用C90的标准,即只使用32个关键字
注释:用于对代码进行解释说明的,注释不参与编译过程,也不是代码的一部分
C语言标准定义的注释格式如下:
/* 注释内容 */
注意:该注释不能嵌套
在我们常见的代码中还见过行注释:
//注释内容
行注释是C++的语法,在C语言标准里未定义,但是绝大多数编译器都支持行注释,且可以嵌套
C语言的实现中有两种环境,分别是翻译环境和执行环境
源代码转换成可执行程序要经过2个步骤:编译 + 链接
编译又可以细分为3个步骤:预编译 + 编译 + 汇编
以Linux系统下的gcc编译器演示编译过程,将源文件test.c 和add.c进行编译和链接:
Linux命令:gcc -E test.c -o test.i
,gcc -E add.c -o add.i
生成文件:test.i 和 add.i(源文件处理后的文件)
作用:将源文件进行处理
Linux命令:gcc -S test.i
,gcc -S add.i
生成文件:test.s 和 add.s(汇编代码)
作用:将源文件转换成汇编代码
语法分析
词法分析
语义分析
符号汇总:将全局符号进行修改后汇总
C语言符号修改:符号前加_,如:main变_main
Linux命令:gcc -c test.s
,gcc -c add.s
生成文件:test.o 和 add.o(二进制文件,里面都是二进制指令,通常叫目标文件)
作用:将汇编代码转换成二进制指令
生成文件:a.out(可执行程序)
作用:将多个 .o文件 和 链接库 链接成一个可执行文件
合并段表:就是将多个.o文件按照特定内容进行合并
合并符号表并重定向:将多个.o文件的符号表进行合并
声明的标识符中无效地址会被该标识符的真实内存地址取代
C程序的内存分布,只介绍几个简单的,笼统的说一下
C语言中有些存在的预定义符号可以供我们使用,它们都是库中用#define定义的符号
__FILE__ //源文件路径,字符串
__LINE__ //当前行号,int
__DATE__ //编译日期,字符串
__TIME__ //编译时间,字符串
__STDC__ //标准C,遵循ANSI C,其值为1,否则未定义
VS2019不遵循ANSI,gcc遵循,__STDC__值为1
#define NAME stuff
注意:
举个栗子:
#define MAX 1000 //将MAX定义为整型常量1000
#define REG register //将register标识符定义为更简洁的标识符REG
#define DO_FORVER for(;;) //将一段语句定义为一个标识符
#define BEBUG_PRINT printf("file:%stline:%dtdate:%sttime:%sn," //使用做续行符__FILE__, __LEIN__, __DATE__, __TIME__)
宏(macro):#define允许把参数替换到文本中,这种实现称为宏或定义宏
如:定义一个宏SQUARE,参数为x,替换内容为x*x
#define SQUARE(x) x*x
使用SQUARE,并传递参数5
SQUARE(5);
预编译期间,源代码的此语句就被替换为
5*5;
注意:
1. 宏中的每个参数和表达式建议带上小括号,为避免替换后运算的优先级跟预想的不一样
1. 使用宏时,不要传入带副作用的参数,否则结果是难以预测的
如:1. 使用宏计算5+1的平方,并加上2,结果却为13,错误
#define SQUARE(x) x*x //宏定义
SQUARE(5+1) + 2; //使用该宏
5+1*5+1 + 2; //替换后的式子
将宏的参数加上小括号后,计算结果为38,正确
#define SQUARE(x) ((x)*(x)) //加了括号的宏定义
SQUARE(5+1) + 2; //使用该宏
((5+1)*(5+1)) + 2; //替换后的式子
如:2. 将a++和b++作为参数传入一下宏
#define MAX(A, B) (((A)>(B)) ? (A) : (B)) //宏定义int a=5, b=8;
int c = MAX(a++, b++); //宏调用int c = (((a++)>(b++)) ? (a++) : (b++)); //替换后的式子,结果为a=6,b=10,c=9
在程序预编译时,会对#define定义的宏或标识符进行替换,步骤如下
注意:
char str[] = "SQUARE(5)";
在宏的参数中使用该符号
#参数名:将该参数加上 “” 替换到替换文本中
//宏定义
#define PRINT(FORMAT, VALUE) printf("the value of "#VALUE" is "FORMAT"n", VALUE)int a = 10;
PRINT("%d", a); //宏调用//替换后的式子,VALUE替换为a,#VALUE替换为"a"
printf("the value of ""a"" is ""%d""n", 10);
参数名##参数名:将两边的符号合成一个符号
//宏定义
#define ADD_TO_SUM(NUM, VALUE) sum##NUM += valueint sum5 = 10;
ADD_TO_SUM(5, 10); //调用宏sum5 += 10; //替换后的式子,sum和5拼接,合成sum5
注意:这样的连接必须产生一个合法的标识符,否则就是未定义的
宏的优点:
宏的缺点:
宏和函数的区别:
属性 | #define定义宏 | 函数 |
---|---|---|
代码长度 | 每次调用都会替换,除非宏小,否则程序长度大幅增加 | 函数只出现在一个地方,每次调用函数都是调用这一个地方的代码 |
执行速度 | 更快 | 存在调用和返回的开销,相对慢一点 |
操作符优先级 | 直接替换容易产生优先级问题,建议书写时使用括号 | 将参数直接拷贝,对参数进行操作,操作符优先级问题不容产生 |
带有副作用的参数 | 直接替换导致每次使用参数都会产生副作用,容易产生不可预料的后果,所以不要使用带副作用的参数 | 形参只在传值的时候求一次值,结果更容易控制 |
参数类型 | 参数类型无关,只要操作数合法,都能使用 | 参数类型必须一一对应 |
调试 | 宏不方便调试 | 函数可以逐语句调试 |
递归 | 不能递归 | 可以递归 |
用于移除一个宏定义
#undef NAME //移除NAME符号的定义
注意:一个符号要被重新定义,那么它要先移除
在编译一个程序时,我们可以使用条件编译语句来决定内容是否被编译
#if 常量表达式//...
#endif
if - else if
使用方式一样#if 常量表达式1//...
#elif 常量表达式2//...
#elif 常量表达式3//...
#else//...
#endif
判断该符号定义时,执行编译,有两种语句
//第一种,用#if判断defined中的符号
#if defined(symbol)//...
#endif//第二种,直接用#ifdef判断符号
#ifdef symbol//...
#endif
判断该符号未定义时,执行编译,也是两种语句,并且跟上面两种对应
//第一种,用!对defined取反
#if !defined(symbol)//...
#endif//第二种,用#ifndef判断符号,中间多个n哦
#ifndef symbol//...
#endif
跟if语句使用一样,可以嵌套使用
#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif
#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif
#endif
#if
,#ifdef
,#ifndef
都有一个#endif
作为结束标志,并且匹配最近的判断#endif
后面建议加上注释,用来标识是由哪个符号判断的结束指令//使用注释标识#if指令的结束标志
#if __DEBUG__//...
#endif //__DEBUG__
#include指令包含头文件,预处理器先删除这条指令,在将头文件中的内容粘贴到源文件中
#include "filename.h"
查找策略:先在源文件目录下查找,如果该头文件未找到,则在库函数头文件下查找,若是还没有则编译错误
#include <filename.h>
查找策略:直接在库函数头文件下查早,若是没有则编译错误
如:test.h中包含了add.h,add.h中包含了stdio.h,test.h中嵌套包含了stdio.h
若是头文件很多,并且各自有很多引用,则容易发生头文件的重复包含
注意:
""
和<>
包含头文件的区别是查找策略不同""
可以用来包含库文件,但是不建议 一个头文件被包含了10次,那么源文件中就有10个该头文件的内容,也会被编译10次,大大增加了代码长度,所以头文件被重复包含是非常糟糕的
解决重复引用有两种方式
如果__TEST_H__
没有被定义,则定义该符号,并完成头文件的编译,否则不对条件指令内的内容进行编译
#ifndef __TEST_H__
#define __TEST_H__//头文件内容#endif //__TEST_H__
#pragma once
在头文件前加上该指令
#pragma once
#error
#pragma
#line
...
不做介绍,自己了解#pragma pack()在结构体已介绍
本文发布于:2024-02-01 01:23:50,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170672183232820.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |