c语言printf输出中文中在printf语句中的内容后面为什么会多出一个0?本来只需要输出"除数不能为0!"

2、输出结果显示框,选择标记,可以复制输出结果,不必要截屏

5、彻底关闭-----关闭工作空间

6、工具-选项-格式,调整字体

7、编译--链接--执行,编译--链接之后产生一个可执行文件.exe,然后vc软件请求CPU执行该文件,执行之后的结果返回到软件显示

8、ASCII码就是一种硬性规定,字符存储本质上与整数一样

9、字节就是存储数据的单位,并且是硬件所能访问的最小单位,1字节=8位

功能:将从键盘输入的字符转化为输入控制符所规定格式的数据,然后存入以输入参数的值为地址的变量中。
2、scanf("非输入控制符 输入控制符",输入参数);
原则:使用scanf前最好先使用printf输出提示信息;
scanf中尽量不要使用非输入控制符,尤其是 ;
应该编写代码对用户的非法输入做适当的处理

逻辑运算符------ ! &&(与)左边为假,右边不执行 \(或)左边为真,右边不执行

C语言对真假表示:非0为真【1】,0为假【0】

12、流程控制-----------程序代码执行的顺序

13、文件命名时会自动加后缀.cpp,命名时不要加‘.’,否则系统不会自动加后缀.cpp,程序就没法执行,果需要加点的话,后面一定要加上.cpp

14、程序 = 算法 + 语言,1、流程 2、每个语句的功能 3、试数

15、for的范围为一个语句

18、十进制转化r进制,一直除r取即可余,余数从下往上倒序排序,

19、编程时尽量i++ 和 ++i单独成为一个语句,不要用于复合语句

从左到右执行,表达式的值为最后一项的值。

21、while(默认控制一个语句) //内部表达式不一定执行
格式:while(表达式)

22、while与for的比较,二者在逻辑上完全等价,但for的逻辑性更强
}//注意A和3的顺序不能更换,这是由for的顺序决定的。

1、用于终止循环、用于终止switch、 break不能直接用于if,除非if属于for循环内部的子句,但break终止的还是外部的for循环
2、用于多个for循环时,只用终止距离它最近的循环

用于跳过本次循环余下的语句,转去判断是否需要执行下次循环

25、数组中的元素就是变量,
用于大量同类型数据的存储与使用,
为多个变量连续分配存储空间,
所有的变量数据类型一致,
所有变量所占的字节大小一致

只有在定义数组的时候才可以整体赋值,其他情况整体赋值都是错误的
只有在定义数组的时候,‘5’即数字,表示数组长度,其他情况均表示下标,下标从0开始
一维数组名代表第一个元素的地址

1、为相似的大量的数据设计,相当于设计的一个工具,有利于程序的模块化 格式:函数类型 函数名 (参数类型 形式参数)
3、函数返回值的类型也称为函数的类型,因为如果函数名前的返回值类型 { {
和函数执行体中的return表达式的类型不一致的话,则最终函数返回值的 return 10; return;或者没有return
类型以函数名前的返回值类型为准。 } }

1、是用来终止整个被调函数的,向主调函数返回
2、如果表达式为空,则只终止函数,不返回 ( 适用于函数类型为void )

30、一个程序有且只有一个主函数
主函数可以调用普通函数
主函数是程序的入口、出口

31、浮点数是不能准确存储的

逻辑上:能够完成特定功能的独立的代码块
能够接受数据【当然也可以不接收数据】
能够对接收的数据进行处理
能够将数据处理的结果返回【当然也可以不返回任何值】
总结:函数是一个工具,它是为了解决大量类似问题而设计的,函数可以当作一个黑匣子

函数的返回值 函数的名字(函数的形参列表)
函数放在main函数前面的话不需要函数声明,放在后面的话,必须要函数声明,函数声明必须与是语句,例如:int max ( int a, int b);注意分号,
目的是告诉编译器该函数已经定义过了。

33、函数定义一定要放在函数调用前面,先定义,后调用,如果函数定义放在了函数调用后面,则在函数调用前面要加上函数前置声明。
对库函数的声明是通过 # include < 库函数所在的文件的名字.h>来实现的。

34、形参和实参的个数,位置,类型相互兼容(整形和浮点型),,,,等必须严格对应。

35、如何在软件开发中合理的设计函数来解决实际问题?
1、一个函数的功能尽量独立,单一
2、多学习,多模仿牛人的代码

局部变量:在一个函数内部定义的变量或者函数的形参,只能在本函数内部使用
全局变量:在所有函数的外部定义的变量,使用范围为从定义位置开始到整个程序结束
注意的问题:在一个函数内部,如果定义的局部变量的名字和全局变量的名字一样,局部变量会屏蔽全局变量。

37、指针和地址同一个含义,指针变量或者地址变量

int * p; //p是变量的名字,int * 为数据类型,表示p变量存放的是int类型变量的地址
//所谓int *类型,就是存放int变量地址的类型

1、p保存了i的地址,因此p指向了i;
2、p不是i,i也不是p,修改p不影响i,修改i不影响p;
3、如果一个指针变量指向了某个普通变量,则
4、*p就是以p的内容(i的地址)为地址的变量

38、 指针就是地址,地址就是指针,
地址就是内存单元的编号;
指针变量是存放地址的变量;
指针和指针变量是两个不同的概念,但是通常我们叙述时会把指针变量简称为指针

表示一些复杂的数据结构
快速的传递数据,减少内存的损耗
使函数返回一个以上的值
是理解面向对象语言中引用的基础
总结:指针是c语言的灵魂

地址:内存单元的编号,从0开始的非负整数,范围:【一般4G, 0--4G-1】
指针:指针就是地址,地址就是指针,指针变量就是存放内存单元编号的变量
指针本质就是一个操作受限的非负整数,只能进行减运算

2、定义指针变量; int * p; 定义了一个名字叫p的变量,int * 表示p只能存放整形数据的地址
3、指针运算符;该运算符放在已经定义好的指针变量的前面,如果p是一个已经定义好的指针变量,则 * p表示以p的内容为地址的变量

43、如何通过被调函数修改主调函数普通变量的值
1.实参必须为该普通变量的地址
2.形参必须为指针变量
3.在被调函数中通过 【 * 形参名 = ........ 】 的方式就可以修改主调函数相关变量的值

一维数组名,是个指针常量,存放的是一维数组第一个元素的地址,a == &a[0];

确定一个数组需要几个参数【如果一个函数要处理一个一维数组,则需要接收该数组的哪些信息】
、 数组第一个元素的地址

指针变量不能+和*/,只能相减【如果两个指针变量指向的是同一块连续空间中的不同存储空间,则可以相减】
一个指针变量到底占用几个字节
功能:返回值就是该数据类型所占的字节数
功能:返回值就是该变量所占字节数

假设 p 指向char类型变量【1个字节】
假设 q 指向char类型变量【4个字节】
假设 r 指向char类型变量【8个字节】
p q r本身所占字节数是否一样?答:一样,都是4个字节,与所指向的变量类型无关
总结:一个指针变量,无论他指向的变量占几个字节,该指针变量只占4个字节
一个变量的地址,是用该变量首字节的地址来表示

45、数组是一片连续的地址空间,字节的地址用4个字节去保存,32根地址总线,4*8=32,共4G大小
指针变量均占4个字节,只有用malloc分配的空间是动态,其他均为静态

46、关于改变形参能否改变实参的总结
如果不用指针是无法通过形参去改变实参的,因为程序会临时单独为形参分配一个内存存储空间,和实现的
内存存储空间是不同的,所以改变形参无法改变实参,并且函数调用结束,会释放形参的空间。
对于指针来说,将实参数组名 a【数组的起始地址】和数组长度 5 传递给形参 int * pArr, int len,
则pArr也就作为数组名【整形指针类型】,指向了是a数组的内存存储空间的起始地址,len赋值为了数组长度,
此时形参pArr 和len 可以唯一的确定实参中a数组,二者是完全等价的,系统并没有单独为形参临时开辟存储空间,
由于pArr指向了实参a数组,所以对于pArr的操作是直接建立在实参a数组的内存空间上的,pArr == a;
关键:理解清楚对形参的处理的内存是否是实参的内存,归结到底是内存存储的问题
47、动态内存分配【重点,难点】
1、数组长度必须事先制定,且是能是常整数,不能是变量
2、传统形式定义的数组,系统为该数组分配的存储空间就会一直存在,程序员无法手动编程释放存储空间,只能在本函数运行完毕时由系统自动释放
3、数组长度不能在函数运行过程中动态的扩充或缩小
数组长度一旦定义,就不能改变
4、A函数定义的数组,在A函数运行期间可以被其他函数使用
A函数结束之后,其中定义的数组就不再能被其他函数使用
传统方式定义的数组不能跨函数使用
为什么需要动态分配内存
动态数组很好的解决了传统数组的四个缺陷

动态内存分配举例--动态数组的构造

静态内存和动态内存的比较
静态内存是由系统自动分配,由系统自动释放
动态内存是由程序员手动分配,手动释放,函数终止内存不释放

49、C语言没有规定int类型占用4个字节,不同的编译软件可能有不同的字节数

为什么需要结构体?为了表示一些复杂事物,而普通的基本类型无法满足需求
什么叫结构体?把一些基本类型数据组合在一起形成的一个新的复合数据类型
3种方式,//第一种[推荐使用],这只是定义了一个新的数据类型,没有定义变量

定义的同时可以整体初始化
如果定义完之后,只能单个赋值
如何取出结构体变量中的每一个成员
1、结构体变量名.成员名
2、指针变量名->成员名 (更常用) //在计算机内部会被转化成(*指针变量名).成员名来执行,二者等价

pst所指向的结构体变量中的age这个成员,
结构体变量不能进行加减乘除,但可以相互赋值,st1 = st2;是可以的
结构体变量和结构体变量指针作为函数参数传递的问题【推荐使用结构体指针变量作为函数参数传递】
动态构造存放学生信息的结构体数组
动态构造一个数组,存放学生成绩,并按成绩输出
把一个事物所有可能的取值一一列举出来

^------按位异或【相同为0,不同为1】
<<----按位左移,右边补0,左移n位==乘以2的n次方,执行速度快
>>----按位右移,左边一般补0,也可能补1,右移n位==除以2的n次方,【前提是数据不能丢失,溢出】
位运算符的现实意义:对数据的操作精确到每一位

也叫 符号-绝对值码,最高位0表示正,1表示负,其余二进制位是该数字绝对值的二进制位
原码简单易懂,加减运算复杂,0的表示不唯一,存在加减乘除四中运算,增加cpu的复杂度

运算不便,也没有在计算机中应用

表示数值平移n位,n称为移码量
移码主要用于浮点数的阶码的存储
正整数转二进制:除2取余,直至商为零,余数倒数排序
负整数转二进制:先求与该负数相对应的正整数的二进制代码,然后将所有位取反,末尾加1,不够位数时,左边补1
如果首位是0,则表明是正整数,按普通方法求
如果首位是1,则表明是负整数,将所有位取反,末尾加1,所得数字就是该负数的绝对值【验证的话要写够32位,因为机器默认补0】

在Visual C++ 6.0中的一个int 类型的变量所能存储的数字范围?
int【4个字节,32位】类型变量能存储的最大正数用十六进制表示:7FFFFFFF
int【4个字节,32位】类型变量能存储的绝对值最大负整数用十六进制表示:


最小负数的二进制代码?
最大正数的二进制代码?
数字超过最大正数会怎样?

通俗定义:解题的方法和步骤

狭义定义:对存储数据的操作
对不同的存储结构要完成某一功能所执行的操作是不一样的
这说明算法是依附于存储结构的,不同的存储结构,所执行的算法是不一样的

广义定义:广义的算法也叫泛型,无论数据是如何存储的,对该数据的操作都是一样的

我们至少有两种结构可以存储数据
缺点:需要一个连续的很大内存,插入和删除元素效率低

首结点:存放第一个有效数据的节点
尾节点:存放最后一个有效数据的节点
头结点的数据类型和首节点的类型是一模一样的
头结点是首节点前面那个节点
头结点并不存放有效数据
设置头结点的目的是为了方便对链表的操作
头指针:存放头结点地址的指针
确定链表只需要一个元素【头指针】
优点:插入删除元素效率很高,不需要一个很大的连续内存
缺点:查找某个元素的效率低

51、66.6在C中默认为double类型,66.6f或者66.6F则为float类型,并且浮点型数据存储是非准确存储的
在使用结构体的时候,编译一下,便于输入结构体成员
在函数调用中,要想改变某个数的值,只能发送该值的地址作为实参,其他方式均不可能修改该值
给指针类型的变量命名时前面最好加个p
一个指针变量永远只占4个字节,因为一个变量的地址永远只用它第一个字节的地址表示,认为是起始地址,
从开始往下1个或者4个或者8个字节作为一个变量是由变量数据类型决定的,char,int ,double
硬件按字节编号【地址,8位】
char * p = &i;//p存放变量i的第一个字节的地址,并且p指向一个字节,所以p+1,指针以1个字节为单位移动
int * p = &i;//p存放变量i的第一个字节的地址,并且p指向从首地址向下4个字节的变量,即p指向4个字节,并将其作为一个变量,所以p+1,指针以4个字节为单位移动
double * p = &i;//p存放变量i的第一个字节的地址,并且p指向从首地址向下8个字节的变量,即p指向8个字节,并将其作为一个变量,所以p+1,指针以8个字节为单位移动
53、 整形数组的赋值和输出都只能借助于for循环
字符型数组【字符串】可以直接输入直接输出

54、默认位数不够,左边补0
字符本质上使用整数存储的
构造函数如果只需要判断真假【true false】,函数类型可以设置为【bool】类型
逻辑运算符的结果只能是真或假【0 or 1】
分配内存:操作系统把某一块内存空间的使用权利分配给该程序
释放内存:操作系统把分配给该程序的内存空间的使用权利收回,该程序不能够再使用这一块内存空间
附注:释放内存不是把该内存的内容清零
1、变量为什么必须初始化?不初始化则变量就是垃圾值
2、详细说明系统是如何执行int i = 5;这个语句的?
1、Vc++6.0软件请求操作系统为i分配 存储空间
2、操作系统会在内存中寻找一块空闲区域,把该区域当做i来使用
3、VC++6.0会把i和这块空闲区域关联起来,今后对i的操作就是对这块空闲区域的操作
4、把5存储到i所关联的内存区域中
附注:所谓内存区域就是内存的一块存储单元
3、函数的优点:避免重复操作,有利于程序的模块化
4、什么是指针?什么是地址?什么是指针变量?三者的关系?
地址是内存单元的编号 指针就是地址 指针和地址是一个概念
指针变量就是存放内存单元编号的变量 指针变量和指针是两个不同的概念,只不过习惯称指针变量为指针
5、请写出静态变量和动态变量的异同?
不同:静态变量是系统自动分配,自动释放,程序员无法在程序运行的过程中手动分配,也无法手动释放
静态变量是在栈中分配的,只有在函数终止之后,静态变量的存储空间才被系统自动释放
动态变量是由程序员手动分配手动释放的,程序员可以在程序运行过程中手动释放
动态变量是在堆中分配,程序员可以在程序运行的任何时刻都可以手动释放动态变量的空间,不需要等到函数结束
6、重点:流程控制,函数,指针,静态内存和动态内存
在多层循环中,break只能终止最内层包裹它的那个循环
else只能跟离他最近的if匹配

56、二进制全部为零的含义 ---00000 的含义
2、字符串结束标记符‘

关注百问科技并将它设为星标

不错过任何一篇嵌入式干货

本文参考诸多资料,详细介绍常用的几种预处理功能。因成文较早,资料来源大多已不可考证,敬请谅解。全文字数2万,阅读时间50分钟,建议先收藏。

预处理(或称预编译)是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所作的工作。预处理指令指示在程序正式编译前就由编译器进行的操作,可放在程序中任何位置。

预处理是C语言的一个重要功能,它由预处理程序负责完成。当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。

C语言提供多种预处理功能,主要处理#开始的预编译指令,如宏定义(#define)、文件包含(#include)、条件编译(#ifdef)等。合理使用预处理功能编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。

C语言源程序中允许用一个标识符来表示一个字符串,称为“宏”。被定义为宏的标识符称为“宏名”。在编译预处理时,对程序中所有出现的宏名,都用宏定义中的字符串去代换,这称为宏替换或宏展开。

宏定义是由源程序中的宏定义命令完成的。宏替换是由预处理程序自动完成的。

在C语言中,宏定义分为有参数和无参数两种。下面分别讨论这两种宏的定义和调用。

无参宏的宏名后不带参数。其定义的一般形式为:

其中,“#”表示这是一条预处理命令(以#开头的均为预处理命令)。“define”为宏定义命令。“标识符”为符号常量,即宏名。“字符串”可以是常数、表达式、格式串等。

宏定义用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名。这只是一种简单的文本替换,预处理程序对它不作任何检查。如有错误,只能在编译已被宏展开后的源程序时发现。

注意理解宏替换中“换”的概念,即在对相关命令或语句的含义和功能作具体分析之前就要进行文本替换。

注意,这种情况下使用const定义常量可能更好,如const int MAX_TIME = 1000;。因为const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行简单的字符文本替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误。

本意是定义pa和pb均为int型指针,但实际上变成int* pa,pb;。pa是int型指针,而pb是int型变量。本例中可用typedef来代替define,这样pa和pb就都是int型指针了。

因为宏定义只是简单的字符串代换,在预处理阶段完成,而typedef是在编译时处理的,它不是作简单的代换,而是对类型说明符重新命名,被命名的标识符具有类型定义说明的功能。

宏名一般用大写字母表示,以便于与变量区别。宏定义末尾不必加分号,否则连分号一并替换。宏定义可以嵌套。

可用#undef命令终止宏定义的作用域。

使用宏可提高程序通用性和易读性,减少不一致性,减少输入错误和便于修改。如数组大小常用宏定义。预处理是在编译之前的处理,而编译工作的任务之一就是语法检查,预处理不做语法检查。宏定义写在函数的花括号外边,作用域为其后的程序,通常在文件的最开头。字符串" "中永远不包含宏,否则该宏名当字符串处理。

宏定义不分配内存,变量定义分配内存。

C语言允许宏带有参数。在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数。

对带参数的宏,在调用中,不仅要宏展开,而且要用实参去代换形参。

带参宏定义的一般形式为:

在字符串中含有各个形参。

带参宏调用的一般形式为:

在宏定义中的形参是标识符,而宏调用中的实参可以是表达式。

在带参宏定义中,形参不分配内存单元,因此不必作类型定义。而宏调用中的实参有具体的值,要用它们去代换形参,因此必须作类型说明,这点与函数不同。函数中形参和实参是两个不同的量,各有自己的作用域,调用时要把实参值赋予形参,进行“值传递”。而在带参宏中只是符号代换,不存在值传递问题。

在宏调用时,用实参5去代替形参x,经预处理宏展开后的语句为y=5+1。

上述这种实参为表达式的宏定义,在一般使用时没有问题;但遇到如area=SQ(a+b);时就会出现问题,宏展开后变为area=a+b*a+b;,显然违背本意。

相比之下,函数调用时会先把实参表达式的值(a+b)求出来再赋予形参r;而宏替换对实参表达式不作计算直接地照原样代换。因此在宏定义中,字符串内的形参通常要用括号括起来以避免出错。

进一步地,考虑到运算符优先级和结合性,遇到area=10/SQ(a+b);时即使形参加括号仍会出错。因此,还应在宏定义中的整个字符串外加括号,

综上,正确的宏定义是#define SQ(r) ((r)*(r)),即宏定义时建议所有的层次都要加括号。

【例5】带参函数和带参宏的区别:

本例意在说明,把同一表达式用函数处理与用宏处理两者的结果有可能是不同的。

调用Square函数时,把实参i值传给形参x后自增1,再输出函数值。因此循环5次,输出1~5的平方值。调用SQUARE宏时,SQUARE(j++)被代换为((j++)*(j++))。在第一次循环时,表达式中j初值为1,两者相乘的结果为1。相乘后j自增两次变为3,因此表达式中第二次相乘时结果为3*3=9。同理,第三次相乘时结果为5*5=25,并在此次循环后j值变为7,不再满足循环条件,停止循环。

从以上分析可以看出函数调用和宏调用二者在形式上相似,在本质上是完全不同的。

  • 宏名和形参表的括号间不能有空格。
  • 宏替换只作替换,不做计算,不做表达式求解。
  • 函数调用在编译后程序运行时进行,并且分配内存。宏替换在编译前进行,不分配内存。
  • 函数只有一个返回值,利用宏则可以设法得到多个值。
  • 宏展开使源程序变长,函数调用不会。
  • 宏展开不占用运行时间,只占编译时间,函数调用占运行时间(分配内存、保留现场、值传递、返回值)。

包括基本用法(及技巧)和特殊用法(#和##等)。

#define可以定义多条语句,以替代多行的代码,但应注意替换后的形式,避免出错。宏定义在换行时要加上一个反斜杠””,而且反斜杠后面直接回车,不能有空格。

将程序中出现的PI全部换成3.1415926。

编码时所有的表达式(y*y+3*y)都可由M代替,而编译时先由预处理程序进行宏替换,即用(y*y+3*y)表达式去置换所有的宏名M,然后再进行编译。

注意,在宏定义中表达式(y*y+3*y)两边的括号不能少,否则可能会发生错误。如s=3*M+4*M在预处理时经宏展开变为s=3*(y*y+3*y)+4*(y*y+3*y),如果宏定义时不加括号就展开为s=3*y*y+3*y+4*y*y+3*y,显然不符合原意。因此在作宏定义时必须十分注意。应保证在宏替换之后不发生错误。

3. 得到指定地址上的一个字节或字:

4. 求最大值和最小值:

以后使用MAX (x,y)或MIN (x,y),就可分别得到x和y中较大或较小的数。

但这种方法存在弊病,例如执行MAX(x++, y)时,x++被执行多少次取决于x和y的大小;当宏参数为函数也会存在类似的风险。所以建议用内联函数而不是这种方法提高速度。不过,虽然存在这样的弊病,但宏定义非常灵活,因为x和y可以是各种数据类型。

Gcc编译器将包含在圆括号和大括号双层括号内的复合语句看作是一个表达式,它可出现在任何允许表达式的地方;复合语句中可声明局部变量,判断循环条件等复杂处理。而表达式的最后一条语句必须是一个表达式,它的计算结果作为返回值。MAX_S和TMAX_S宏内就定义局部变量以消除参数副作用。

注意,MAX_S和TMAX_S宏虽可避免参数副作用,但会增加内存开销并降低执行效率。若使用者能保证宏参数不存在副作用,则可选用普通定义(即MAX宏)。

5. 得到一个成员在结构体中的偏移量(lint 545告警表示"&用法值得怀疑",此处抑制该警告):

6. 得到一个结构体中某成员所占用的字节数:

7. 按照LSB格式把两个字节转化为一个字(word):

8. 按照LSB格式把一个字(word)转化为两个字节:

9. 得到一个变量的地址:

10. 得到一个字(word)的高位和低位字节:

11. 返回一个比X大的最接近的8的倍数:

12. 将一个字母转换为大写或小写:

注意,UPCASE和LOCASE宏仅适用于ASCII编码(依赖于码字顺序和连续性),而不适用于EBCDIC编码。

13. 判断字符是不是10进值的数字:

14. 判断字符是不是16进值的数字:

15. 防止溢出的一个方法:

16. 返回数组元素的个数:

17. 对于IO空间映射在存储空间的结构,输入输出处理:

18. 使用一些宏跟踪调试:

若编译器未遵循ANSI标准,则可能仅支持以上宏名中的几个,或根本不支持。此外,编译程序可能还提供其它预定义的宏名(如__FUCTION__)。

__DATE__宏指令含有形式为月/日/年的串,表示源文件被翻译到代码时的日期;源代码翻译到目标代码的时间作为串包含在__TIME__中。串形式为时:分:秒。

如果实现是标准的,则宏__STDC__含有十进制常量1。如果它含有任何其它数,则实现是非标准的。

可以借助上面的宏来定义调试宏,输出数据信息和所在文件所在行。如下所示:

20. 实现类似“重载”功能

C语言中没有swap函数,而且不支持重载,也没有模板概念,所以对于每种数据类型都要写出相应的swap函数,如:

21. 1年中有多少秒(忽略闰年问题) :

该表达式将使一个16位机的整型数溢出,因此用长整型符号L告诉编译器该常数为长整型数。

宏定义必须写在函数外,其作用域为宏定义起到源程序结束。如要终止其作用域可使用#undef命令:

表示PI只在main函数中有效,在func1中无效。

主要涉及C语言宏里#和##的用法,以及可变参数宏。

在C语言的宏中,#的功能是将其后面的宏参数进行字符串化操作(Stringfication),简单说就是将宏定义中的传入参数名转换成用一对双引号括起来参数名字符串。#只能用于有传入参数的宏定义中,且必须置于宏定义体中的参数名前。例如:

这样,每次divider(除数)为0时便会在标准错误流上输出一个提示信息。

注意#宏对空格的处理:

当传入参数名间存在空格时,编译器会自动连接各个子字符串,每个子字符串间只以一个空格连接。如str= example1( abc def)会被扩展成 str="abc def"。

又如要做一个菜单项命令名和函数指针组成的结构体数组,并希望在函数名和菜单项命令名之间有直观的、名字上的关系。那么下面的代码就非常实用:

然后,就可用一些预先定义好的命令来方便地初始化一个command结构的数组:

COMMAND宏在此充当一个代码生成器的作用,这样可在一定程度上减少代码密度,间接地也可减少不留心所造成的错误。

还可以用n个##符号连接n+1个Token,这个特性是#符号所不具备的。如:

当用##连接形参时,##前后的空格可有可无。

连接后的实际参数名,必须为实际存在的参数名或是编译器已知的宏定义。

凡是宏定义里有用'#'或'##'的地方,宏参数是不会再展开。如:

INT_MAX和A都不会再被展开,多加一层中间转换宏即可解决这个问题。加这层宏是为了把所有宏的参数在这层里全部展开,那么在转换宏里的那一个宏(如_STR)就能得到正确的宏参数。

@#称为字符化操作符(charizing),只能用于有传入参数的宏定义中,且必须置于宏定义体的参数名前。作用是将传入的单字符参数名转换成字符,以一对单引号括起来。

可变参数宏的一般形式为:

省略号代表一个可以变化的参数表,变参必须作为参数表的最右一项出现。使用保留名__VA_ARGS__ 把参数传递给宏。在调用宏时,省略号被表示成零个或多个符号(包括里面的逗号),一直到到右括号结束为止。当被调用时,在宏体(macro body)中,那些符号序列集合将代替里面的__VA_ARGS__标识符。当宏的调用展开时,实际的参数就传递给fprintf ()。

注意:可变参数宏不被ANSI/ISO C++所正式支持。因此,应当检查编译器是否支持这项技术。

在标准C里,不能省略可变参数,但却可以给它传递一个空的参数,这会导致编译出错。因为宏展开后,里面的字符串后面会有个多余的逗号。为解决这个问题,GNU CPP中做了如下扩展定义:

若可变参数被忽略或为空,##操作将使编译器删除它前面多余的逗号(否则会编译出错)。若宏调用时提供了可变参数,编译器会把这些可变参数放到逗号的后面。

同时,GCC还支持显式地命名变参为args,如同其它参数一样。如下格式的宏扩展:

这样写可读性更强,并且更容易进行描述。

用GCC和C99的可变参数宏, 可以更方便地打印调试信息,如:

结合第4节的“条件编译”功能,可以构造出如下调试打印宏:

//以10进制格式日志整型变量

//以16进制格式日志整型变量

//以字符串格式日志字符串变量

//调试定位信息打印宏

//调试跟踪宏,在待日志信息前附加日志文件名、行数、函数名等信息

文件包含命令行的一般形式为:

通常,该文件是后缀名为"h"或"hpp"的头文件。文件包含命令把指定头文件插入该命令行位置取代该命令行,从而把指定的文件和当前的源程序文件连成一个源文件。

在程序设计中,文件包含是很有用的。一个大程序可以分为多个模块,由多个程序员分别编程。有些公用的符号常量或宏定义等可单独组成一个文件,在其它文件的开头用包含命令包含该文件即可使用。这样,可避免在每个文件开头都去书写那些公用量,从而节省时间,并减少出错。

对文件包含命令要说明以下几点:

包含命令中的文件名可用双引号括起来,也可用尖括号括起来,如#include "common.h"和#include<math.h>。但这两种形式是有区别的:使用尖括号表示在包含文件目录中去查找(包含目录是由用户在设置环境时设置的include目录),而不在当前源文件目录去查找;

使用双引号则表示首先在当前源文件目录中查找,若未找到才到包含目录中去查找。用户编程时可根据自己文件所在的目录来选择某一种命令形式。

一个include命令只能指定一个被包含文件,若有多个文件要包含,则需用多个include命令。文件包含允许嵌套,即在一个被包含的文件中又可以包含另一个文件。

一般情况下,源程序中所有的行都参加编译。但有时希望对其中一部分内容只在满足一定条件才进行编译,也就是对一部分内容指定编译的条件,这就是“条件编译”。有时,希望当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。

条件编译功能可按不同的条件去编译不同的程序部分,从而产生不同的目标代码文件。这对于程序的移植和调试是很有用的。

条件编译有三种形式,下面分别介绍。

如果标识符已被#define命令定义过,则对程序段1进行编译;否则对程序段2进行编译。如果没有程序段2(它为空),#else可以没有,即可以写为:

这里的“程序段”可以是语句组,也可以是命令行。这种条件编译可以提高C源程序的通用性。

由于在程序中插入了条件编译预处理命令,因此要根据NUM是否被定义过来决定编译哪个printf语句。而程序首行已对NUM作过宏定义,因此应对第一个printf语句作编译,故运行结果是输出了学号和成绩。

程序首行定义NUM为字符串“OK”,其实可为任何字符串,甚至不给出任何字符串,即#define NUM也具有同样的意义。只有取消程序首行宏定义才会去编译第二个printf语句。

如果标识符未被#define命令定义过,则对程序段1进行编译,否则对程序段2进行编译。这与#ifdef形式的功能正相反。

如果常量表达式的值为真(非0),则对程序段1 进行编译,否则对程序段2进行编译。因此可使程序在不同条件下,完成不同的功能。

【例7】输入一行字母字符,根据需要设置条件编译,使之能将字母全改为大写或小写字母输出。

在程序第一行定义宏CAPITAL_LETTER为1,因此在条件编译时常量表达式CAPITAL_LETTER的值为真(非零),故运行后使小写字母变成大写(C LANGUAGE)。

本例的条件编译当然也可以用if条件语句来实现。但是用条件语句将会对整个源程序进行编译,生成的目标代码程序很长;而采用条件编译,则根据条件只编译其中的程序段1或程序段2,生成的目标程序较短。如果条件编译的程序段很长,采用条件编译的方法是十分必要的。

在大规模开发过程中,特别是跨平台和系统的软件里,可以在编译时通过条件编译设置编译环境。

例如,有一个数据类型,在Windows平台中应使用long类型表示,而在其他平台应使用float表示。这样往往需要对源程序作必要的修改,这就降低了程序的通用性。可以用以下的条件编译:

如果在这组条件编译命令前曾出现命令行#define WINDOWS 0,则预编译后程序中的MYTYPE都用float代替。这样,源程序可以不必作任何修改就可以用于不同类型的计算机系统。

2. 包含程序功能模块

例如,在程序首部定义#ifdef FLV:

如果不许向别的用户提供该功能,则在编译之前将首部的FLV加一下划线即可。

调试程序时,常常希望输出一些所需的信息以便追踪程序的运行。而在调试完成后不再输出这些信息。可以在源程序中插入以下的条件编译段:

如果在它的前面有以下命令行#define DEBUG,则在程序运行时输出file指针的值,以便调试分析。调试完成后只需将这个define命令行删除即可,这时所有使用DEBUG作标识符的条件编译段中的printf语句不起作用,即起到“开关”一样统一控制的作用。

4. 避开硬件的限制。

有时一些具体应用环境的硬件不同,但限于条件本地缺乏这种设备,可绕过硬件直接写出预期结果:

调试通过后,再屏蔽TEST的定义并重新编译即可。

5. 防止头文件重复包含

头文件(.h)可以被头文件或C文件包含。由于头文件包含可以嵌套,C文件就有可能多次包含同一个头文件;或者不同的C文件都包含同一个头文件,编译时就可能出现重复包含(重复定义)的问题。

在头文件中为了避免重复调用(如两个头文件互相包含对方),常采用这样的结构:

//真正的内容,如函数声明之类

<标识符>可以自由命名,但一般形如__HEADER_H,且每个头文件标识都应该是唯一的。

事实上,不管头文件会不会被多个文件引用,都要加上条件编译开关来避免重复包含。

其中有个变量定义,在VC中链接时会出现变量var重复定义的错误,而在C中成功编译。

(1) 当第一个使用这个头文件的.cpp文件生成.obj时,var在里面定义;当另一个使用该头文件的.cpp文件再次(单独)生成.obj时,var又被定义;然后两个obj被第三个包含该头文件.cpp连接在一起,会出现重复定义。

(2) 把源程序文件扩展名改成.c后,VC按照C语言语法对源程序进行编译。在C语言中,遇到多个int var则自动认为其中一个是定义,其他的是声明。

(3) C语言和C++语言连接结果不同,可能是在进行编译时,C++语言将全局变量默认为强符号,所以连接出错。C语言则依照是否初始化进行强弱的判断的(仅供参考)。

(1) 把源程序文件扩展名改成.c。

综上,变量一般不要定义在.h文件中。

  • 预处理功能是C语言特有的功能,它是在对源程序正式编译前由预处理程序完成的。程序员在程序中用预处理命令来调用这些功能。
  • 宏定义是用一个标识符来表示一个字符串,这个字符串可以是常量、变量或表达式。在宏调用中将用该字符串代换宏名。
  • 宏定义可以带有参数,宏调用时是以实参代换形参。而不是“值传递”。
  • 为了避免宏替换时发生错误,宏定义中的字符串应加括号,字符串中出现的形式参数两边也应加括号。
  • 文件包含是预处理的一个重要功能,它可用来把多个源文件连接成一个源文件进行编译,结果将生成一个目标文件。
  • 条件编译允许只编译源程序中满足条件的程序段,使生成的目标程序较短,从而减少了内存的开销并提高了程序的效率。
  • 使用预处理功能便于程序的修改、阅读、移植和调试,也便于实现模块化程序设计。

宏参数被完全展开后再替换入宏体,但当宏参数被字符串化(#)或与其它子串连接(##)时不予展开。在替换之后,再次扫描整个宏体(包括已替换宏参数)以进一步展开宏。结果是宏参数被扫描两次以展开参数所(嵌套)调用的宏。

若带参数宏定义中的参数称为形参,调用宏时的实际参数称为实参,则宏的展开可用以下三步来简单描述(该步骤与gcc摘录稍有不同,但更易操作):

1) 用实参替换形参,将实参代入宏文本中;

2) 若实参也是宏,则展开实参;

3) 继续处理宏替换后的宏文本,若宏文本也包含宏则继续展开,否则完成展开。

其中第一步将实参代入宏文本后,若实参前遇到字符“#”或“##”,即使实参是宏也不再展开实参,而当作文本处理。

上述展开步骤示例如下:

6.2 宏的其他注意事项

1. 避免在无作用域限定(未用{}括起)的宏内定义数组、结构、字符串等变量,否则函数中对宏的多次引用会导致实际局部变量空间成倍放大。

2. 按照宏的功能、模块进行集中定义。即在一处将常量数值定义为宏,其他地方通过引用该宏,生成自己模块的宏。严禁相同含义的常量数值,在不同地方定义为不同的宏,即使数值相同也不允许(维护修改后极易遗漏,造成代码隐患)。

1) 预编译时用宏定义值替换宏名,编译时报错不易理解;

2) 跟踪调试时显示宏值,而不是宏名;

3) 宏没有类型,不能做类型检查,不安全;

4) 宏自身没有作用域;

5) 只读变量和宏的效率同样高。

注意,C语言中只读变量不可用于数组大小、变量(包括数组元素)初始化值以及case表达式。

4. 用inline函数代替(类似功能的)宏函数。好处如下:

1) 宏函数在预编译时处理,编译出错信息不易理解;

2) 宏函数本身无法单步跟踪调试,因此也不要在宏内调用函数。但某些编译器(为了调试需要)可将inline函数转成普通函数;

3) 宏函数的入参没有类型,不安全;

5) inline函数会在目标代码中展开,和宏的效率一样高;

注意,某些宏函数用法独特,不能用inline函数取代。当不想或不能指明参数类型时,宏函数更合适。

括号会暗示阅读代码者该宏是一个函数。

6. 带参宏内定义变量时,应注意避免内外部变量重名的问题:

若宏参数名或宏内变量名不加前缀下划线,则ASSIGN1(c)将会导致编译报错(t.d被替换为t.c),ASSIGN2(d)会因宏内作用域而导致外部的变量d值保持不变(而非改为5)。

7. 不要用宏改写语言。例如:

C语言有完善且众所周知的语法。试图将其改变成类似于其他语言的形式,会使读者混淆,难于理解。

//执行成功,释放资源并返回

//执行并进行错误处理

2) 存在一个独立的代码块,可进行变量定义,实现比较复杂的逻辑处理。

注意,该代码块内(即{…}内)定义的变量其作用域仅限于该块。此外,为避免宏的实参与其内部定义的变量同名而造成覆盖,最好在变量名前加上_(基于如下编程惯例:除非是库,否则不应定义以_开始的变量)。

3) 若宏出现在判断语句之后,可保证作为一个整体来实现。

a) 因为if分支后有两条语句,else分支没有对应的if,编译失败;

b) 假设没有else,则SAFE_DELETE中第二条语句无论if判断是否成立均会执行,这显然违背程序设计的原始目的。

那么,为了避免这两个问题,将宏直接用{}括起来是否可以?如:

的确,上述问题不复存在。但C/C++编程中,在每条语句后加分号是约定俗成的习惯,此时以下代码

其else分支就无法通过编译(多出一个分号),而采用do{…}while(0)则毫无问题。

使用do{...} while(0)将宏包裹起来,成为一个独立的语法单元,从而不会与上下文发生混淆。同时因为绝大多数编译器都能够识别do{...}while(0)这种无用的循环并优化,所以该法不会导致程序的性能降低。

C语言不仅提供了丰富的数据类型,而且还允许由用户自己定义类型说明符,也就是说允许由用户为数据类型取“别名”。类型定义符typedef即可用来完成此功能。

typedef定义的一般形式为:

其中原类型名中含有定义部分,新类型名一般用大写表示,以便于区别。

例如,有整型量int a,b。其中int是整型变量的类型说明符。int的完整写法为integer,为增加程序的可读性,可把整型说明符用typedef定义为typedef int INTEGER。此后就可用INTEGER来代替int作整型变量的类型说明,如INTEGER a,b等效于int a,b。

用typedef定义数组、指针、结构等类型将带来很大的方便,不仅使程序书写简单而且意义更为明确,因而增强了可读性。

有时也可用宏定义来代替typedef的功能,但是宏定义是由预处理完成的,而typedef则是在编译时完成的,后者更为灵活方便。

此外,采用typedef重新定义一些类型,可防止因平台和编译器不同而产生的类型字节数差异,方便移植。如:

对了,设备树视频课程全部录完了,学员评价这是最实惠最详细最精益求精的设备树教程,有4节免费,感兴趣的可以看看:设备树全部视频录制完毕,一共6课29节,加量不加价,现在还是69元,说不定以后会涨价,不参加双11价格战,早学早受益

微信/手机:,验证:进群

邀您加入学员微信群,第一时间掌握课程进度

名额有限,前提你是产品用户

如果你家里恰好有个一二年级的小朋友的话,请他解释一下最合适不过了,3除以5,商就是0,余数是3。

我要回帖

更多关于 c语言printf输出中文 的文章