|
知识路径: > 嵌入式系统软件基础知识 > 嵌入式系统程序设计 > 嵌入式程序设计语言 > 嵌入式C/C++程序设计要求 > C程序设计基础 >
|
被考次数:34次
被考频率:高频率
总体答错率:56%  
知识难度系数:
|
由 软考在线 用户真实做题大数据统计生成
|
相关知识点:29个
|
|
|
|
C程序是由函数组成的,其基本要素有预处理指令、常量、变量、宏、运算符和表达式、流程控制和语句等。
|
|
|
|
在C程序中以#开头的行被称为预处理指令,这些指令是ANSI C统一规定的。编程时可使用预处理命令来扩展C语言的表示能力,提高编程效率。对C源程序进行编译之前,首先由预处理器对程序中的预处理指令进行处理。
|
|
|
文件包含#include、宏定义#define、条件编译#ifdef、#ifndef是常用的预处理命令,在头文件assert.h中定义宏assert(断言),用于测试表达式的值,若表达式的值为0,则显示错误信息并终止程序的运行。
|
|
|
|
在C程序中用好宏定义可以提高程序的可移植性、可读性,减少出错。对于嵌入式系统而言,为了达到性能要求,也常用宏作为一种代替函数的方法。例如,用宏求解两个数据对象的较小者。
|
|
|
|
再如,用#define声明一个常数,忽略闰年情况下表示一年有多少秒。
|
|
|
|
通过使用预定义宏可以返回程序的某些状态,以方便交叉编译和调试,每个预定义宏的名称一两个下画线字符开头和结尾,这些预定义宏不能被取消定义(#undef)或由编程人员重新定义。例如,用_FUNC_打印函数名、_LINE_打印行号,以定位程序中打印该信息的函数和位置。常用的几个预定义宏如下。
|
|
|
_DATE_当前源文件的编译日期,格式为“Mmm dd yyyy”的字符串字面量
|
|
|
_TIME_当前源文件的编译时间,格式为“hh:mm:ss”的字符串字面量
|
|
|
|
|
_LINE_当前程序行的行号,表示为十进制整型常量
|
|
|
_STDC_若当前编译器符合ISO标准,那么该宏的值为1,否则未定义
|
|
|
_STDC_HOSTED_(C99)如果当前是宿主系统,则该宏的值为1,否则为0
|
|
|
|
编写嵌入式应用程序时经常会遇到一种情况,当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句,这时就需要使用条件编译。条件编译命令最常见的形式为:
|
|
|
|
其作用是:当标识符已经被定义过(一般是用#define命令定义),则对程序段1进行编译,否则编译程序段2,其中#else部分也可以没有。
|
|
|
在所有的预处理指令中,#pragma最为复杂,其作用是设定编译器的状态或者是指示编译器完成一些特定的动作,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。#pragma pack有多种形式,#pragma pack(n)表示变量以n字节对齐。
|
|
|
在网络程序中采用#pragma pack(1)(即变量紧缩),可以减少网络流量以及兼容各种系统,避免由于系统对齐方式不同而导致的解包错误。
|
|
|
|
在C程序中,数据都具有类型,通过数据类型定义了数值范围以及可进行的运算。
|
|
|
C的数据类型可分为基本数据类型(内置的类型)和复合数据类型(用户定义的类型)。内置的类型是指C语言直接规定的类型,用户定义的类型在使用以前必须先定义,枚举、结构体和共用体类型都是用户定义类型。
|
|
|
C的基本数据类型有字符型(char)、整型(int)、浮点型(float、double),如下表所示。
|
|
|
|
|
void类型也是一种基本类型,void不对应具体的值,只用于一些特定的场合,例如用于定义函数的参数类型、返回值、函数中指针类型等进行声明,表示没有或暂未确定类型。
|
|
|
C程序中的数据以变量、常量(包括字面量和const常量等)表示,它们都具有类型属性。
|
|
|
|
变量本质上指代存储数据的内存单元,变量的定义(definition)指示编译器为变量分配存储空间,还可以为变量指定初始值。在一个C程序中,一个变量有且仅有一个定义。当C程序文件中需要引用其他程序文件中定义的变量时,就需要进行声明。
|
|
|
变量声明(declaration)用来表明变量的类型和名字,当定义变量时即声明了它的类型和名字。可以通过使用extern关键字声明变量名。
|
|
|
|
|
在嵌入式C程序设计中,用volatile修饰变量时,即告知编译器该变量的值无任何持久性,不要对它进行任何优化。因为用volatile定义的变量可能会在其所在程序外被改变,因此需要从其所在的内存位置或设备端口重新读取,而不是使用其寄存器中的缓存值。
|
|
|
|
字面量(literal)是指数据在源程序中直接以值的形式呈现,在程序运行中不能被修改,表现为整型、浮点型和字符串类型。
|
|
|
默认情况下,整型字面量以十进制形式表示,前缀0表示是八进制常数,前缀0x或0X表示是十六进制常数。同样,一个整型常数也可以加U或u后缀,指定为是unsigned类型。
|
|
|
以0作为八进制常数的前导符号并不符合人们的习惯,可能造成潜在的程序错误。
|
|
|
|
|
浮点型字面量总是假定为double型,除非有字母F或f后缀,才被认为是float型;若有后缀L或1,则被处理为long double型。实型常量也可以表示成指数形式,例如0.004可以表示成4.0E-3或4.0e-3,其中E或e代表指数。
|
|
|
字符字面量用一对单引号括起来,例如‘A’。对于不能打印的特殊字符,可以用它们的编码指定。还有一些转义字符,如’\n'表示换行、’\r'表示回车等。
|
|
|
用双引号括起来的零个或多个字符则构成字符串型字面值。例如,
|
|
|
|
|
常量修饰符const的含义是其所修饰的对象为常量(immutable)。若一个变量被修饰为const,则该变量的值就不能被其他语句修改。例如:
|
|
|
|
C程序中常用宏定义的方式在源程序中为常量命名。例如:
|
|
|
|
const常量与宏定义常量有所不同:const常量有数据类型,而宏定义常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,不进行类型安全检查,并且在字符替换可能会产生意料不到的错误。有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。在C++程序中使用const常量。
|
|
|
|
在C程序中使用的变量名、函数名、标号以及用户定义数据类型名等统称为标识符。除库函数的函数名由系统定义外,其余都由用户自定义。
|
|
|
|
.标识符必须以字母a~z、A~Z或下画线开头,后面可跟任意个字符,这些字符可以是字母、下画线和数字,其他字符不允许出现在标识符中;
|
|
|
|
.标识符的长度在C89标准中规定31个字符以内,在C99标准中规定63个字符以内;
|
|
|
.C语言中的关键字(保留字)有特殊意义,不能作为标识符;
|
|
|
.标识符最好使用具有一定意义的字符串,便于记忆和理解。变量名一般用小写字母,用户自定义类型名的开头字母大写。
|
|
|
通常来说,一段程序代码中所用到的名字并不总是有效和可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。同一个名字在不同的作用域可能表示不同的对象。
|
|
|
C程序中的名字有块作用域、函数作用域、函数原型作用域和文件作用域之分,作用域可以是嵌套的。
|
|
|
一般情况下,尽可能将变量定义(声明)在最小的作用域内,并且为其设置初始值。
|
|
|
|
|
数组是一种集合数据类型,它由多个元素组成,每个元素都有相同的数据类型,占有相同大小的存储单元,且在内存中连续存放。每个数组有一个名字,数组中的每个元素有一个序号(称为下标),表示元素在数组中的位序(位置),数组的维数和大小在定义数组时确定,程序运行时不能改变。
|
|
|
|
|
其中,“类型说明符”指定数组元素的类型;“数组名”的命名规则与变量相同;“常量表达式”的值表示数组元素的个数,必须是一个正整数。例如:
|
|
|
|
在C程序中,数组元素的下标总是从0开始的,如果一个数组有n个元素,则第一个元素的下标是0,最后一个元素的下标是n-1。例如,在上面定义的temp数组中,第一个元素是temp[0],第二个元素是temp[1],以此类推,最后一个元素是temp[99]。访问数组元素的方法是通过数组名及数组名后的方括号中的下标。例如:
|
|
|
|
程序员需确保访问数组元素时下标的有效性,访问一个不存在的数组元素(例如temp[100]),可能会导致严重的错误。
|
|
|
定义数组时就给出数组元素的初值,称之为初始化,数组的初始化与简单变量的初始化类似。初值放在一对花括号中,各初值之间用逗号隔开,称为初始化表。例如:
|
|
|
|
对于没有给出数组元素个数而给出了初始化表的数组定义,编译器会根据初值的个数和类型,为数组分配相应大小的内存空间。初始化表中值的个数必须小于或等于数组元素的个数。
|
|
|
对于“int primes[10]={1,2,3,5,7};”,前5个数组元素的初值分别为1,2,3,5,7,后5个元素的初值都为0。
|
|
|
|
|
其中,“类型说明符”指定数组元素的类型,“常量表达式1”指定行数,“常量表达式2”指定列数。例如,可以定义一个二维数组:
|
|
|
|
这个数组在内存中占用能存放12个double型数据且地址连续的存储单元。
|
|
|
|
可以用sizeof计算数组空间的大小,即字节数。例如,
|
|
|
|
二维数组可以看作元素是一维数组的一维数组,三维数组可看作元素是二维数组的一维数组,以此类推。
|
|
|
|
|
字符串是一个连续的字符系列,用特殊字符’\0’结尾。字符串常用字符数组来表示。数组的每一个元素保存字符串的一个字符,并附加一个空字符,表示为“\0”,添加在字符串的末尾,以标识字符串结束。如果一个字符串有n个字符,则至少需要长度为n+1的字符数组来保存它。
|
|
|
一个字符串常量用一对双引号括起来,如Welcome,编译系统自动在每一字符串常量的结尾增加’\0’结尾符。字符串可以由任意字符组成,一个长字符串可以占两行或多行,但在最后一行之前的各行需用反斜杠结尾,如A String Can be write on multilines可等价地表示为:
|
|
|
|
需要注意的是,"A"与'A’是不同的,"A"是由两个字符(字符'A'与字符’\0’)组成的字符串,而后者只有一个字符。最短的字符串是空字符串"",它仅包含一个结尾符‘\0'。
|
|
|
|
枚举就是把一种类型数据可取的值逐一列举出来。枚举类型是一种用户定义的数据类型,其一般定义形式为:
|
|
|
|
其中,“枚举类型名”右边花括号中的内容称为枚举表,枚举表中的每一项称为枚举成员,枚举成员是常量。枚举成员之间用逗号隔开,方括号中的“整型常数”是枚举成员的初值。
|
|
|
如果没有为枚举成员赋初值,即省掉了标识符后的“=整型常数”时,编译系统为每一个枚举成员赋予一个不同的整型值,第一个成员为0,第二个成员为1,以此类推。当枚举类型中的某个成员赋值后,其后的成员则按依次加1的规则确定其值。例如:
|
|
|
|
此时,eBLUE=6、Eyellow=7、Eburgundy=41。
|
|
|
|
结构体、位域和共用体类型在程序中需要用户进行定义,同时用typedef定义数据类型的别名。
|
|
|
|
利用结构体类型可以把一个数据元素的各个不同的数据项聚合为一个整体。结构体类型的声明格式为:
|
|
|
|
例如,一个复数z=x+yi包含了实部x和虚部y两部分(x和y为实数),可以定义一个表示复数的结构体类型,并用typedef为结构体类型命名为Complex:
|
|
|
|
在该定义中,Complex是这个结构体类型的名字,re和im是结构的成员。一般情况下,对结构体变量的运算必须通过对其成员进行运算来完成,成员运算符“.”用来访问结构体变量的成员,方式为:
|
|
|
|
例如,定义结构体变量z,将-4和5分别赋值给一个复数z的实部成员变量和虚部成员变量:
|
|
|
|
z.re和z.im相当于普通的double型变量。结构体外的变量名和结构体中的成员名相同时不会发生冲突。一个结构体变量的存储空间长度不少于其所有成员所占空间长度之和。
|
|
|
结构体数据的空间中可能产生填充信息,因为对大多数处理器而言,访问按字或者半字对齐的数据速度更快,当定义结构体时,编译器为了性能优化,可能会将它们按照半字或字对齐。
|
|
|
例如,下面两个结构体变量structA和structB的成员相同但排列顺序不同,用sizeof计算其所占用存储空间的字节数,sizeof(structA)的值为8,sizeof(structB)的值为12。其存储空间中的填充处理如下图所示。
|
|
|
|
|
|
有些信息在存储时只需要一个或几个二进制位,而不是完整的字节空间,这时可通过位域的方式来处理,即将一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。
|
|
|
|
|
其中,位域列表的形式为:类型说明符位域名:位域长度。
|
|
|
例如,定义了下面的位域结构变量bit后,可以为其位域赋值:
|
|
|
|
|
|
|
|
|
不能直接引用联合类型的变量,只能引用其成员。用“.”运算符引用共用体变量的成员,引用方式为:
|
|
|
|
|
一个共用体变量的存储空间的大小等于其占用空间最大的成员的大小,所有成员变量占用同一段内存空间,如下图所示。
|
|
|
|
|
|
C语言提供了丰富的运算符,包括算术运算符、关系运算符、逻辑运算符、位运算符、条件运算符、赋值运算符、逗号运算符及其他运算符。根据运算符需要的操作数个数,可分为单目运算符(一个操作数)、双目运算符(两个操作数)和三目运算符(三个操作数)。
|
|
|
表达式总是由运算符和操作数组成,它规定了数据对象的运算过程。
|
|
|
|
运算符的作用是将数值变量的值增加1或减少1。自增或自减运算符只能作用于变量而不能作用于常量或表达式。
|
|
|
++value称为前缀方式,value++称为后缀方式,其区别是:前缀方式先将变量的值增1,然后取变量的新值参与表达式的运算;后缀方式是先取变量的值参与表达式的运算,然后再将变量的值增加1。自减运算同理。
|
|
|
|
关系运算符用于数值之间的比较,包含等于(==)、不等于(!=)、小于(<)、小于或等于(<=)、大于(>)、大于或等于(>=)这6种,结果的值为1(表示关系成立)或为0(表示关系不成立)。
|
|
|
不能用关系运算符对字符串进行比较,因为被比较的不是字符串的内容本身,而是字符串的地址。例如,“HELLO”<“BYE”是用“HELLO”的地址与“BYE”的地址来比较大小,这没有意义。
|
|
|
|
逻辑与(&&)、逻辑或(||)、逻辑非(!)的运算结果为1(表示true)或为0(表示false)。“逻辑非”是单目运算符,它将操作数的逻辑值取反。“逻辑与”是双目运算符号,其含义是“当且仅当两个操作数的值都为true时,逻辑与运算的结果为true”。“逻辑或”的含义是“当且仅当两个操作数的值都为false时,逻辑或运算的结果为false”。
|
|
|
例如,逻辑表达式!20的结果是0,10&&5的结果是1,10||5.5的结果是1,10&&0的结果是0。
|
|
|
在C程序中,由逻辑运算符“&&”“||”构造的表达式采用短路计算方式求值,即对于“a&&b”,a为1(假)时不需要再计算b的值就可以确定该表达式的结果为0(假),对于“a||b”,a为1(真)时不需要再计算b的值就可以确定该表达式的结果为1(真)。
|
|
|
例如,对于逻辑表达式((year%4==0)&&(year%100!=0)||(year%400==0)),若year%4的结果不是0(例如year的值为2001),就不需要再计算year%100的值了,因为此时“&&”运算的结果已经确定为0(即条件不成立)。
|
|
|
|
赋值运算符(=)的作用是将一个表达式的值赋给一个变量,可进行组合赋值。例如:
|
|
|
|
书写组合表达式时,可能存在的潜在错误是书写错误,例如将“+=”写成了“=+”,这类错误在编译阶段无法识别,只能在程序的运行结果不符合预期时再进行排查。
|
|
|
C程序中,常出现将比较相等的运算符号“==”误用为“=”(赋值运算符)的情况,编程时需要特别注意。
|
|
|
|
(1)条件运算符是C中唯一的三目运算符,也称为三元运算符,它有三个操作数:
|
|
|
|
(2)多个表达式可以用逗号组合成一个表达式,即逗号表达式。逗号运算符带两个操作数,结果是右操作数。逗号表达式的一般形式是:表达式1,表达式2,……,表达式n,它的值是表达式n的值。逗号运算符的用途仅在于解决只能出现一个表达式的地方却要出现多个表达式的问题。
|
|
|
|
位运算符要求操作数是整型数,并按二进制位的顺序来处理它们。C/C++提供6种位运算符,如下表所示,为简化起见,设整数字长(word)为16位。
|
|
|
|
|
赋值运算符也可与位运算符组合,产生&=、|=、^=、<<=、>>=等组合运算符。
|
|
|
例如,用宏定义通过位运算得到一个字的高位和低位字节。
|
|
|
|
|
sizeof用于计算表达式或数据类型的字节数,其运算结果与系统相关。例如,对于下面的数组定义,可用“sizeof(a)/sizeof(int)”计算出数组a的元素个数为7。
|
|
|
|
|
在混合数据类型的运算过程中,系统自动进行类型转换。例如,一个int型操作数和一个long型操作数进行运算时,将int类型数据转换为long类型后再运算,结果为long型;一个float型操作数和一个double型操作数的运算结果是double型。这称为类型提升。
|
|
|
在程序中也可以进行数据类型的强制转换(显式类型转换),一般形式为:
|
|
|
|
需要注意,(int)(x+y)是将(x+y)转换为int型,而(int)x+y是将x转换为int型后再与y相加。对变量进行显式类型转换只是得到一个所需类型的中间变量,原来变量的类型并不发生变化。
|
|
|
当不得已混合使用类型时,一个比较好的习惯是使用强制类型转换。强制类型转换可以避免编译器隐式转换带来的错误,同时也给维护人员传递一些有用信息。
|
|
|
|
C程序中输入/输出操作都由输入/输出标准库函数(在头文件stdio.h中声明)完成,常见的有格式化输出函数printf和格式化输入函数scanf,以及文件操作函数fopen、fprintf和fscanf等。
|
|
|
|
语句是构成程序的一种基本单位,用来描述数据定义或声明、运算和控制过程。下面主要介绍描述基本流程控制的分支(选择)、循环结构等语句,包括if、switch、for、while、do-while、break、continue、return等。
|
|
|
|
表示分支(选择)结构的语句有if语句和switch语句。
|
|
|
(1)if语句。if语句用于表达根据一定的条件在两条流程中选择一条执行的情况。if语句的一般形式为:
|
|
|
|
其含义是当给定的条件p满足(即表达式p的值不为0)时,执行语句1,否则执行语句2。语句1和语句2中必须且仅能执行其中的一条。在if语句的简单形式中,可以省略else及其子句“语句2”。良好的C编程风格提倡将语句1和语句2用“{”“}”括起来。
|
|
|
if语句能够嵌套使用,即一个if语句能够出现在另一个if语句里。使用if语句的嵌套形式需要注意else的配对情况,C规定:else子句总是与离它最近且没有else相匹配的if语句配对。
|
|
|
例如,下面语句(a)、(b)中,else与if的匹配不同。
|
|
|
|
在语句(a)中,else与if(x<5)匹配,该语句的含义是:当x大于0且小于5时,执行y=x+1;,若x大于或等于5,则执行y=x-1;。
|
|
|
在语句(b)中,else与if(x>0)匹配,该语句含义是:当x大于0且小于5时,执行y=x+1;,若x小于或等于0,则执行y=x-1;。
|
|
|
(2)switch语句。switch语句用于表示从多分支的执行流程中选择一个来执行的情况。
|
|
|
|
|
switch语句的执行过程可以理解为:首先计算表达式p的值,然后自上而下地将其结果值依次与每一个常量表达式的值进行匹配(常量表达式的值的类型必须与“表达式”的类型相同)。如果匹配成功,则执行该常量表达式后的语句系列。当遇到break时,则立即结束switch语句,否则顺序执行到switch中的最后一条语句。default是可选的,如果没有常量表达式的值与“表达式”的值匹配,则执行default后的语句系列。需要注意的是,表达式p的值必须是字符型或整型。
|
|
|
编译时通常根据switch语句中各case后面的常量表达式来构造一个跳转表,从而在确定表达式p的值之后可以快速定位到相应的语句位置开始执行,而不是逐一与各常量表达式的值进行比较。
|
|
|
|
C提供的循环语句有while、do-while和for,循环体部分应使用语句块符号(即大括号)括起来。
|
|
|
(1)while语句。while语句的一般形式为:
|
|
|
|
while语句的含义是首先计算表达式p(称之为循环条件)的值,如果其值不为0(即为真),则执行“循环体语句”(称为循环体)。这个过程重复进行,直至“表达式”的值为0(假)时结束循环。
|
|
|
(2)do-while语句。do-while语句的一般形式为:
|
|
|
|
do-while语句的含义是先执行循环体语句,再计算表达式p,如果表达式p的值不为0,则继续执行循环体语句,否则循环终止。
|
|
|
|
|
|
|
②计算表达式2(循环条件),如果其结果不为0,则执行循环体语句(循环体),否则循环终止。
|
|
|
|
|
for语句在形式上比实现相同控制逻辑的while语句更为简洁和紧凑。
|
|
|
|
break语句用在switch语句中时,用于跳出switch语句,结束switch语句的执行。
|
|
|
break语句在循环体中时,其作用是终止循环并结束循环语句的执行。在多重(层)循环控制中,break的作用只限于终止(并跳出)一重(层)循环控制结构,其作用不能到达更外层的循环控制。
|
|
|
|