上章回顾
宏定义特点和注意细节 条件编译特点和主要用处 文件包含的路径查询规则 C语言扩展宏定义的用法
第二章
第二章
C语言编程实践 C语言编程实践
预习检查
异或的运算符是什么 宏定义最主要的特点是什么 请列举条件编译三大用法 在文件包含中<> 和“” 有什么区别 如何取得整数register中的第五位值
本章结构
C语言编程实践 C语言编程实践
嵌入式家园 www.embedclub.comC语言编程调试
上海嵌入式家园-开发板商城 http://embedclub.taobao.com/
位运算 位运算
位段 位段
指针特性 指针特性
C语言关键词 C语言关键词
C语言编程要点 C语言编程要点
C语言常犯错误 C语言常犯错误
C语言编程调试
课程目标
了解位运算的实际应用。 了解C语言几个重要关键词的特性和他们之间区别 熟悉C语言编程要点 熟悉C语言常见的语法错误以及解决方法 了解如何去定位错误以及解决错误
2 C语言编程实践
位运算
指针特性
C语言关键词 C语言几个以混淆的概念 C语言编程易犯毛病集合
2.1 位运算
按位与运算
按位或运算
按位异或运算
求反运算
左移运算
右移运算
2.1 位运算 位运算符的含义
位运算是指进行二进制位的运算。 功能:
c语言提供对内存单元的二进制位的操作,使得c语言能够编写系统软件.
位运算符
&: 按位与 |: 按位或 ^: 按位异或 ~ : 取反 <<: 左移 >>: 右移
2.1 位运算 要点:
位运算除~以外,均为二目运算; 运算对象只能为整型或字符型数据. 各个位运算符号的使用:
2.1.1按位与运算
按位与──&
格式:x&y 主要用途:取(或保留)1个数的某(些)位,其余各位置0
规则:对应位均为1时才为1,否则为0:3&9=1。 例如,3&9=1: 0011
& 1001
────
0001=1
2.1.2 按位或运算
按位或──| 格式:x|y
主要用途:参与运算的两数各对应的二进位相或。只要 对应的二个二进位有一个为1时,结果位就为1
规则: :对应位均为0时才为0 ,否则为1:3|9=11 。 例如,3|9=11: 0011
嵌入式家园 www.embedclub.com1011=11
| 1001
────
上海嵌入式家园-开发板商城 http://embedclub.taobao.com/
2.1.2 按位异或运算 按位异或──^
格式: x^y 规则:对应位相同时为0,不同时为1:3^9=10 。
主要用途:使1个数的某(些)位翻转(即原来为1的位变为 0,为0的变为1),其余各位不变
例子:
00001001 ^00000101
00001100 (十进制为12) 嵌入式家园 www.embedclub.com
上海嵌入式家园-开发板商城 http://embedclub.taobao.com/
2.1.3求反运算 求反── ~
格式:~ y 规则:各位翻转,即原来为1的位变成0,原来为0的位
变成1:在IBM-PC机中,~0=0xffff,~9=0xfff6。 主要用途:间接地构造一个数,以增强程序的可移植性
2.1.4 左移运算
按位左移──<< 格式:x<< 位数
规则:使操作数的各位左移,低位补0,高位溢出: 5<<2=20。
2.1.5 右移运算
按位左移──>>
格式:x>>位数
规则:使操作数的各位右移,移出的低位舍弃;高位:
对无符号数和有符号中的正数,补0; 有符号数中的负数,取决于所使用的系统:补0的称为“逻辑右移”,
补1的称为“算术右移”。例如,20 >> 2=5 说明
x、y和“位数”等操作数,都只能是整型或字符型数据。除按位取反 为单目运算符外,其余均为双目运算符。
参与运算时,操作数x和y,都必须首先转换成二进制形式,然后再执 行相应的按位运算。
例如,5<<2=20:0101 → 10100, 20 >> 2=5:10100 → 00101。
2.1.6 位运算例子 例2
题目:从键盘上输入1个正整数给int变量num,输出由8~11位构成的数(从低位、0号开始编号)
基本思路:
(1)使变量num右移8位,将8~11位移到低4位上。 (2)构造1个低4位为1、其余各位为0的整数。 (3)与num进行按位与运算。 /*程序功能:输出一个整数中由8~11位构成的数*/
main()
{ int num, mask;
}
printf("Input a integer number: ");
scanf("%d",&num);
num >>= 8; /*右移8位,将8~11位移到低4位上*/
mask = ~ ( ~0 << 4); /*间接构造1个低4位为1、其余各位为0的整数*/ printf("result=0x%x ", num & mask);
程序运行情况:
程序运行情况:
Input a integer number:1000 ←┘ Input a integer number:1000 ←┘
result=0x3
result=0x3
程序说明:~ ( ~0 << 4) 程序说明:~ ( ~0 << 4)
按位取0的反,为全1;左移4位后,其低4位为0,其余各位为1; 按位取0的反,为全1;左移4位后,其低4位为0,其余各位为1;
嵌入式家园 www.embedclub.com再按位取反,则其低4位为1,其余各位为0。这个整数正是我们所需要
再按位取反,则其低4位为1,其余各位为0。这个整数正是我们所需要 上海嵌入式家园-开发板商城 http://embedclub.taobao.com/
的。
的。
2.1.6 位运算例子 例1
题目:从键盘上输入1个正整数给int变量num,按二进制位输出该数。
#include "stdio.h" main()
{
}
int num, mask, i;
printf("Input a integer number: ");
scanf("%d",&num);
mask = 1<<15; /*构造1个最高位为1、其余各位为0的整数(屏蔽字)*/ printf("%d=" , num);
for(i=1; i<=16; i++)
{
} printf("B ");
putchar(num&mask ? ’1’ : ‘0’); num <<= 1;
if( i%4==0 ) putchar(‘,’);
/*输出最高位的值(1/0)*/ /*将次高位移到最高位上*/ /*四位一组,用逗号分开*/
1000=0000,0011,1110,1000B
程序运行情况:
程序运行情况:
Input a integer number:1000
Input a integer number:1000
1000=0000,0011,1110,1000B
2.3 指针特性 数据指针
函数指针 数组与动态申请
2.3.1 数据指针
指针所具有的对绝对地址单元内容的读写能力
指针直接操作内存多发生在如下几种情况
某I/O芯片被定位在CPU的存储空间而非I/O空间,而且寄存器对应于 某特定地址;
两个CPU之间以双端口RAM通信,CPU需要在双端口RAM的特定单 元(称为mail box)书写内容以在对方CPU产生中断;
读取在ROM或FLASH的特定单元所烧录的汉字和英文字模。
2.3.1 数据指针
实例:
unsigned char *p = (unsigned char *)0xF000FF00; *p=11;
数据指针的运算
指针自增自减操作的结果取决于指针指向的数据类别
int *p = (int *)0xF000FF00; p++(或++p) p = p+sizeof(int), p--(或--p)->p = p-sizeof(int)。
2.3.2 函数指针 函数指针的三个问题
C语言中函数名直接对应于函数生成的指令代码在内存中的地址,因此函 数名可以直接赋给指向函数的指针;
调用函数实际上等同于“调转指令+参数传递处理+回归位置入栈”,本 质上最核心的操作是将函数生成的目标代码的首地址赋给CPU的PC寄存
器; 因为函数调用的本质是跳转到某一个地址单元的code去执行,所以可以"
调用"一个根本就不存在的函数实体。
2.3.2 函数指针 函数指针实例
/* 定义一个无参数、无返回类型的 */ typedef void (*lpFunction) ( );
/* 函数指针类型 */
/*定义一个函数指针,指向*/
lpFunction lpReset = (lpFunction)0xF000FFF0;
/* CPU启动后所执行第一条指令的位置 */ lpReset(); /* 调用函数 */
函数指针练习!
2.3.3 数组与动态申请 动态申请内存方式可以用较大的数组替换
尽可能的选用数组,数组不能越界访问;
如果使用动态申请,则申请后一定要判断是否申请成功了,并且
malloc和free应成对出现!
2.4 C语言关键词
const
typedef 与define define 与 enum static
2.4.1 const
const --只读
说说以下的区别
const int a;
int const a;
const int *a;
int * const a;
int const * a const;
const的优势 关键字const的作用是为给读你代码的人传达非常有用的信息 防止代码被无意的修改
2.4.1 const
const --只读
说说以下的区别
const int a;
int const a;
const int *a;
int * const a;
int const * a const;
const的优势
关键字const的作用是为给读你代码的人传达非常有用的信息 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码 防止便利被无意的代码修改
2.4.2 typedef 与define #define
预处理指令 在编译预处理时进行简单的替换, 不作正确性检查 不需要再在内存中分配变量空间 调试程序无法检查用#define说明的常量 用#under指令取消
typedef
声明一个新的类型名代替已有的类型名
在编译时处理的
不实际分配内存空间
2.4.2 typedef 与define
#define实例
#define your_int int * your_int a, b;
typedef例子
typedef int * your_int; your_int a, b
结果
结果
相当于int *a, b; 只是简单的宏替 换
a, b 都为指向int的指针
即
即
int *a;
int *a;
int b;
int *b;
2.4.3 define 与 enum #enum 特点
用enum关键字说明的常量由编译程序自动生成,程序员不需要用手 工对常量一一赋值。
用enum关键字说明常量使程序更清晰易读,因为在定义enum常量 的同时也定义了一个枚举类型标识符。
在调试程序时通常可以检查枚举常量。
需要分配内存来存储常量
2.4.3 define 与 enum
#enum 例子 enum Error_Code
{
OUT_OF_MEMORY, INSUFFICIENT_DISK_SPACE, LOGIC_ERROR, FILE_NOT_FOUND
};
2.4.3 define 与 enum #enum 相对#define的优势
使程序更容易维护
使程序更易读
使程序调试起来更方便
2.4.4 static
static的特点
本地的全局变量
限制变量的作用域
设置变量的存储域
static 的作用 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持
其值不变。
在模块内(但在函数体外),一个被声明为静态的变量可以被模块
内所用函数访问,但不能被模块外其它函数访问。
在模块内,一个被声明为静态的函数只可被这一模块内的其它函数
调用
2.5 C语言编程要点
C语言存储空间布局
Heap与stack
static特性应用
字符串常量 array_name和&array_name的异同 强制数据类型转换的总结
2.5.1 C程序存储空间布局 C语言的组成
正文段
初始化数据段(数据段)
非初始化数据段(bss段) 栈
堆
2.5.1 C程序存储空间布局 C语言存储布局
栈
| |/ /| |
堆 未初始化 初始化 正文段
2.5.2 Heap与stack Heap与stack
Stack的空间由操作系统自动分配/释放,Heap上的空间手动分配/释 放。
Stack空间有限,Heap是很大的自由存储区
内存分配域
malloc函数分配的内存空间即在堆上 程序在编译期对变量和函数分配内存都在栈上进行 程序运行过程中函数调用时参数的传递也在栈上进行
2.5.3 static特性应用
全局静态变量 局部静态变量 静态函数 static实例应用
2.5.3.1全局静态变量
定义:全局变量之前加上关键字static
特性:
内存中的位置:静态存储区
初始化:未经初始化的全局静态变量会被程序自动初始化为
作用域:从文件定义之处开始到文件结尾。
引用模式
引用头文件的方式 extern关键字
2.5.3.1全局静态变量 实例分析:
teststatic1.c
//teststatic2.c
void display();
static int n;
extern int n;
/*定义全局静态变量,自 动初始化为0,仅在本文 件中可见*/
void display()
int main() {
n = 20; printf("%d ",n); display();
return 0;
{
}} 结果:
结果:
文件分别编译通过,但link的时候teststatic1.c中的变量n找 文件分别编译通过,但link的时候teststatic1.c中的变量n找
不到定义嵌入式家园 www.embedclub.com不到定义
上海嵌入式家园-开发板商城 http://embedclub.taobao.com/
n++;
printf("%d ",n);
2.5.3.1全局静态变量 优势
不会被其他文件所访问,修改
其他文件中可以使用相同名字的变量,不会发生冲突。
静态局部变量和普通局部变量的区别
static局部变量只被初始化一次,下一次依据上一次结果值; static函数在内存中只有一份,普通函数在每个被调用中维持一份拷
贝
2.5.3.2 局部静态变量 定义
特征
特征
局部变量之前加上关键字static 。
内存中的位置:静态存储区 初始化:未经初始化的全局静态变量会被程序自动初始化为0
作用域:作用域仍为局部作用域
局部变量改变为静态变量后是改变了它的存储方式即改变了它的生
存期。
全局变量改变为静态变量后是改变了它的作用域, 限制了它的使用 范围
2.5.3.3 静态函数 定义
在函数的返回类型前加上关键字static 特性
优势
只是在声明他的文件当中可见,不能被其他文件所用
其他文件中可以定义相同名字的函数,不会发生冲突
静态函数不能被其他文件所用
2.5.3.3 静态函数 实例分析:
}
printf("staticdis() has been called "); }
teststatic1.c
//teststatic2.c
void display();
static void staticdis(); int main()
{
void display() {
display(); staticdis(); renturn 0;
printf("display() has been called "); }
结果:
结果:
staticdis();
static void staticdis() {
文件分别编译通过,但是连接的时候找不到函数staticdis()的定义 文件分别编译通过,但是连接的时候找不到函数staticdis()的定义
2.5.3.4 statics实例 例:统计次数功能
void count(); int main()
{
}
int i;
for (i = 1; i <= 3; i++)
count(); return 0;
结果:
}
void count() {
I have been called 1 times.
static num = 0; num++;
printf("I have been called
%d",num,"times ");
结果:
I have been called 1 times.
I have been called 2 times.
I have been called 2 times.
I have been called 3 times.
I have been called 3 times.
2.5.4 array_name和&array_name的异同 array_name
指向数组中第一个元素的指针
&array_name
例:
指向整个数组的指针
char a[MAX];
/*array of MAX characters*/ /*p为指向数组的指针*/
char *p = a;
/*该语句是不正确的,pa的类型为'char *',而&a的类型为 char (*pb)[MAX] = &a; /*该语句是正确的,pb的类型为'char (*)[MAX]'*/
char *pa = &a; 'char (*)[MAX]’*/
2.5.4 array_name和&array_name的异同 使用例子分析
#include<stdio.h> int main()
{
结果:
}
char a[5] = {'a','b','c','d',' '}; char *p = a;
1245040
/*运行下面这句后, gcc 提示的错误为:cannot convert
from ‘char (*)[5]’ to ‘char *’,&a的类型应该是指向一个数 abcd
组的指针*/
//char *pa = &a; /*所以,应该定义一个指向相同类型和大小的数组的指针
来获得“&a”的值*/
char (*point_to_str)[5];
point_to_str = &a; printf("%x %x ",&p, &point_to_str); printf("%s %s ", p, point_to_str);
结果:1245044 1245044
1245040
abcd
abcd
abcd
2.5.4 强制数据类型转换 使用例子分析
main() {
}
unsigned a,b; int i,j; a=65535; i=-1;
结果:
j=a;
b=i; printf("(unsigned)%u→(int)%d ",a,j); printf("(int)%d→(unsigned)%u ",i,b);
(unsigned)65535→(int)-1 (unsigned)65535→(int)-1
结果:
(int)-1→(unsigned)65535 (int)-1→(unsigned)65535
2.5.5 强制数据类型转换的总结 转换规则
说明:
说明:
横向箭头表示必须的转换
double
float 高
long
unsigned
int
char,short 低
横向箭头表示必须的转换
纵向箭头表示当运算符两边的运算数为不同类型时的转换
纵向箭头表示当运算符两边的运算数为不同类型时的转换
低-》高 ==》形式上改变, 数据的实质内容 不变
低-》高 ==》形式上改变, 数据的实质内容 不变
高-》低 ==》可能有些数据丢失
高-》低 ==》可能有些数据丢失
2.5.5 强制数据类型转换的总结 转换规则
把赋值运算符右侧表达式的类型转换为左侧变量的类型
转换细节
浮点型与整型 单、双精度浮点型 char型与int型
int型与long型 无符号整数
阶段小节
常见的位运算符有哪些
什么是数据指针
如何定义一个函数指针
const有那些特点,主要应用在哪些方面
static主要有几种用法,各自特点是什么
C强制数据类型转化要注意哪些方面
2.6 C语言编程易犯毛病集合
书写标识符时,忽略了大小写字母的区别
忽略了变量的类型,进行了不合法的运算
将字符常量与字符串常量混淆 忽略了“=”与“==”的区别
忘记加分号
多加分号
输入字符的格式与要求不一致 switch语句中漏写break语句 忽视了while和do-while语句在细节上的区别 定义数组时误用变量
2.6.1书写标识符时,忽略了大小写字母的区别 分析
main() {
int a=5;
printf("%d",A); }
注意:C认为大写字母和小写字母是两个不同的字符
2.6.2 忽略了变量的类型,进行了不合法的运算 分析
main() {
float a,b;
printf("%d",a%b); }
注意:整型变量a和b可以进行求余运算,而实型变量 则不允许进行“求余”运算
2.6.3 将字符常量与字符串常量混淆
分析
char c; c= "a";
注意:
字符常量是由一对单引号括起来的单个字符 字符串常量是一对双引号括起来的字符序列 以“