目录
位运算符有:&(按位与)、|(按位或)、^(按位异或)、~ (按位取反)。
41,学习static定义静态变量的用法。
1.【静态局部变量】:静态局部变量的效果跟全局变量有一拼,但是它位于函数体内部,极有利于程序的模块化。
#include <stdio.h>
void fn(void)
{
int n = 10;
printf("n=%d\n", n);
n++;
printf("n++=%d\n", n);
}
void fn_static(void)
{
static int n = 10;
printf("static n=%d\n", n);
n++;
printf("n++=%d\n", n);
}
int main(void)
{
fn();
printf("--------------------\n");
fn_static();
printf("--------------------\n");
fn();
printf("--------------------\n");
fn_static();
return 0;
}
运行结果如下:
n=10
n++=11
--------------------
static n=10
n++=11
--------------------
n=10
n++=11
--------------------
static n=11
n++=12
--------------------------------
2.【静态全局变量】:静态全局变量仅对当前文件可见,其他文件不可访问,其他文件可以定义与其同名的变量,两者互不影响
能够有效地降低程序模块之间的耦合,避免不同文件同名变量的冲突,且不会误使用
3.【静态函数】:静态函数只能在声明它的文件中可见,其他文件不能引用该函数;不同的文件可以使用相同名字的静态函数,互不影响
42,学习使用auto定义变量的用法。 (一般不用)
C语言中提供了存储说明符 auto,register,extern,static 说明的四种存储类别。四种存储类别说明符有两种存储期:自动存储期 和 静态存储期。
其中auto和register对应自动存储期。具有自动存储期的变量在进入声明该变量的程序块是被建立,它在该程序块活动时存在,退出该程序块时撤销。
关键字auto,它可用于定义局部变量。但自从所有的非全局变量的缺省值假定为auto以来,auto就几乎很少使用了,
有些时候并不能很确定一个变量应该具备的数据类型,此时auto关键字可以派上用场。auto存储类型说明符声明了一个自动变量,auto对象和变量被存储在栈中,它的生命周期仅存在于它的声明所在的块(block)中,即一个只在块运行时有效的变量。auto变量的声明能包含初始化。因为有auto存储类型的变量并不自动的初始化,应该在声明时显式的初始化它们,或在同一个块内赋给它们初始值。未初始化的auto变量的值是未定义的。
44,学习使用external的用法
在源文件A里定义的函数,在其它源文件里是看不见的(即不能访问)。为了在源文件B里能调用这个函数,应该在B的头部加上一个外部声明:
这样,在源文件B里也可以调用那个函数了。
【注意区别】:在A里是定义,在B里是声明。
一个函数只能(也必须)在一个源文件里被定义,但是可以在其它多个源文件里被声明。定义引起存储分配,是真正产生那个实体。而声明并不引起存储分配。打一个粗俗的比方:在源文件B里声明后,好比在B里开了一扇窗,让它可以看到A里的那个函数。
与extern对应的关键字是static,被它修饰的全局变量和函数只能在本模块中使用。
45,学习使用register定义变量的方法。(一般不用)
使用register 暗示编译程序相应的变量将将被频繁使用,因此尽可能的将变量存在CPU内部寄存器中,而不是通过内存寻址访问,以提高效率,加快存取速度。【尽可能----但不是绝对!】
使用register修饰符有几点限制:
1、register变量必须是能被CPU寄存器所接受的类型,这通常意味着register变量必须是一个单个的值,并且其长度应小于或等于整型的长度。但是,有些机器的寄存器也能存放浮点数。
2、因为register变量可能不存放在内存中,所以不能用取址符运算符“ & ”来获取地址
46,宏#define命令练习。
宏不是语句,结尾不用加“;”,否则会被替换进进程中
#表示这是一条预处理指令
【#运算符】:#的作用就是将#后边的宏参数进行字符串的操作,也就是将#后边的参数两边加上一对双引号使其成为字符串。例如a是一个宏的形参,则替换文本中的#a被系统转化为"a",这个转换过程即为字符串化。
【##运算符】: ##运算符也可以用在替换文本中,它的作用起到粘合的作用,即将两个宏参数连接为一个数
#define TEST(param1,param2) (param1##param2)
int num =TEST(12,34);
printf("num=%d\n",num);
输出结果为:num=1234
举例1:(带参数的宏定义)
#include<stdio.h>
#define SQ(x) (x)*(x) //这是带参数的宏定义
int main()
{
printf("%d",9/SQ(3)) ;
}
输出的结果为
使用#define定义的代码,在编译前是直接替换程序中的代码, 所以实质上计算的是
【 #define与typedef区别】
举例2:
#define SQUARE(x) x*x
int main()
{
int a = 5;
printf("SQUARE(a): %d\n",SQUARE(a)); //这个值为25
printf("SQUARE(a+1): %d\n", SQUARE(a + 1)); //第一反应是打印36,其实打印的是11
}
这样输出的结果是
【分析】:编译器在处理SQUARE(a+1)时,原本运算应为:(a+1)*(a+1),实际所做运算为 a+1*a+1 = 2*a+1 ——因为没有加括号,运算符的优先级发生变化,导致运算的顺序出错。
要想能够真正使用好宏定义,那么在读别人的程序时,一定要记住先将程序中对宏的使用全部替换成它所代表的字符串,不要自作主张地添加任何其他符号,完全展开后再进行相应的计算,就不会写错运行结果。如果是自己编程使用宏替换,则在使用简单宏定义时,记得加上括号表示优先级
举例3:——将变量宏定义为一种字符
#define LAG >
#define SMA <
#define EQ ==
#include <stdio.h>
int main()
{
int i,j;
printf("请输入两个数字:\n");
scanf("%d %d",&i,&j);
if(i LAG j)
printf("%d 大于 %d \n",i,j);
else if(i EQ j)
printf("%d 等于 %d \n",i,j);
else if(i SMA j)
printf("%d 小于 %d \n",i,j);
else
printf("没有值。\n");
return 0;
}
【宏定义的好处】
1、方便程序的修改
如:π的值根据精度要求,可以变化
2、完成简单的函数调用功能,提高程序运行效率
在发生函数调用时,需要保留调用函数的现场,以便子函数执行结束后能返回继续执行,同样在子函数执行完后要恢复调用函数的现场,这都需要一定的时间,而宏定义是在预处理阶段即进行了 宏展开,在执行时不需要转换,效率较高。
【有参宏与函数区别】
- 在宏定义#define COUNT(M) M*M中的形参不分配内存单元,不做类型定义,只是简单的文本替换,而函数int count(int x)中形参x是局部变量,会在栈区分配内存单元,所以要做类型定义,而且实参与形参之间是值传递。而宏只是符号代换,不存在值传递。
宏定义也可以定义表达式或多个语句
#define A(a,b,c) ({a=1;b+=1;c=3;a+b+c;})
#include <stdio.h>
int main()
{
int a;
int b=1;
int c;
int d;
d=A(a,b,c);
printf("%d,%d,%d,%d\n" ,a,b,c,d);
return 0;
}
输出为 1,2,3,6
这篇文章关于宏定义的解释很详细——
宏定义#define详解 - long_ago - 博客园
49,#if #ifdef和#ifndef的综合应用。
#define:定义一个预处理宏
#undef :取消宏的定义
#if :编译预处理中的条件命令,相当于C语法中的if语句
#ifdef :判断某个宏是否被定义,若已定义,执行随后的语句
#ifndef: 与#ifdef相反,判断某个宏是否未被定义
#elif :若#if, #ifdef, #ifndef或前面的#elif条件不满足,则执行#elif之后的语句,相当于C语法中的else-if
#else :与#if, #ifdef, #ifndef对应, 若这些条件不满足,则执行#else之后的语句,相当于C语法中的else
#endif: 条件命令的结束标志.(条件命令有——#if, #ifdef, #ifndef)
defined :与#if, #elif配合使用,判断某个宏是否被定义
#include <stdio.h>
#define MAXIMUM(x,y)(x>y)?x:y
#define MINIMUM(x,y)(x>y)?y:x
int main(){
int a=10;
int b=20;
#define MAX //定义一个预处理宏MAX
//如果这个宏定义被定义,则执行随后语句
#ifdef MAX
printf("1The lager one is %d\n", MAXIMUM(a,b));
#else
printf("2The lower one is %d\n", MINIMUM(a,b));
#endif
#ifdef MIN
printf("3The lower one is %d\n", MINIMUM(a,b));
#else
printf("4The lager one is %d\n", MAXIMUM(a,b));
#endif
#undef MAX //取消宏定义MAX
#ifdef MAX
printf("5The lager one is %d\n", MAXIMUM(a,b));
#else
printf("6The lower one is %d\n", MINIMUM(a,b));
#endif
#define MIN//定义一个预处理宏MIN
#ifdef MIN
printf("7The lower one is %d\n", MINIMUM(a,b));
#else
printf("8The lager one is %d\n", MAXIMUM(a,b));
#endif
}
输出的结果为:——【即输出了第 1 4 6 7条语句】
50,#include 的应用练习。
扩展名为.h的文件,在C语言中被称为header file, 也就是头文件。
引用其它头文件,格式为 #include <xxx.h> 或 #include "xxx.h"
include后面用<>和""是有一定的区别的
在编译器查找头文件的时候,会在两个区域分别查找。一个是 系统头文件区域 ,即类似于stdio.h一类的C库函数头文件区。另一个是自定义头文件区,比如当前目录,以及其它自定义的目录。用<>时,编译器会先在系统区域查找,然后再查找自定义区域。而用" "时则相反。
51~53按位与、或、异或
位运算符有:&(按位与)、|(按位或)、^(按位异或)、~ (按位取反)。
优先级从高到低,依次为~、&、^、|
【有个口诀】:单 算 移 关 与,异 或 逻 条 赋
具体含义:单目运算符——算术运算符(+-*/)——移位——关系运算符(大小比较)——按位与——按位异或——按位或——逻辑运算符——条件运算符——赋值运算符
1、按位与
按位与操作 0&0=0; 0&1=0; 1&0=0; 1&1=1;(负数按补码形式参加按位与)
——注意,负数得到的结果仍是补码,需要转回原码得到十进制数值。
- 例子:10&9: 0000 1010 & 0000 1001 = 0000 1000 = 8
- 与运算”的特殊用途:
(1)清零。如果想将一个单元清零,即使其全部二进制位为0,只要与一个各位都为零的数值相与,结果为零。
(2)取一个数中指定位
方法:找一个数,对应X要取的位,该数的对应位为1,其余位为零,此数与X进行“与运算”可以得到X中的指定位。
2、按位或运算符(|)
-
参加运算的两个对象,按二进制位进行“或”运算。
-
运算规则:0|0=0; 0|1=1; 1|0=1; 1|1=1;
-
例如:3|5 即 0000 0011 | 0000 0101 = 0000 0111 因此,3|5的值得7。
-
负数按补码形式参加按位或运算,得到的结果仍是补码,需要转回原码得到十进制数值。
-
或运算”特殊作用:常用来对一个数的某个位置,令其为1——对应X要取的位,该数的对应位为1,其余位为零
3、异或运算符(^)
-
参加运算的两个数据,按二进制位进行“异或”运算。
-
运算规则:0^0=0; 0^1=1; 1^0=1; 1^1=0;——即,相同为0,相异为1
-
【负数 举例】例如:10 ^ -9 即 0000 1010 ^ 1111 0111 = 1111 1101(补码) 原码即为1000 0011 即10^-9 = -3
-
“异或运算”的特殊作用:
(1)使特定位翻转 找一个数,对应X要翻转的各位,该数的对应位为1,其余位为零,此数与X对应位异或即可。
(2)与0相异或,保留原值 ,X ^ 0000 0000 = 1010 1110。
(3)交换a和b【两种巧妙的方法】
通过 异或 来交换:
a = a ^ b;
b = a ^ b;
a = a ^ b;
通过 加减法 来交换
a = a - b;
b = a + b;
a = b - a;
【异或---分析】:首先用a保存了a^b的 值,再用b = a ^ b = (a^b)^b = a^b^b = a^(b^b)=a^0=a,这样就成功的实现了b = a;接着又用a = a^b = (a^b)^b(第一个b还是原来的b,而第二个b已经是a的值,因为前面已经实现了交换)= (a^b)^a = a^a^b = 0^b = b,这样就实现了a = b;于是,就成功的实现了a,b两个值的交换。
【加减法---分析】:首先用a保存a-b的值,再用b=a+b=(a-b)+b=a;实现了b=a;然后用a=b-a (此时,b=a,而a=a-b)=a-(a-b)=b;实现了a=b。于是,就成功的实现了a,b两个值的交换。
4、取反运算符(~)
取反,即0变1,1变0;
-
使一个数的最低位为零,可以表示为:a&~1。
- ~1的值为1111111111111110,再按“与”运算,最低位一定为0。因为“~”运算符的优先级比算术运算符、关系运算符、逻辑运算符和其他运算符都高。
5、左移运算符(<<)
将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。
若左移时舍弃的高位不包含1,则每左移一位,相当于该数乘以2。
6. 右移运算符(>>)
将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。
无符号右移运算符(>>>):右移后左边空出的位用零来填充。移出右边的位被丢弃。
7. 复合赋值运算符
不同长度的数据进行位运算
如果两个不同长度的数据进行位运算时,系统会将二者按右端对齐,然后进行位运算。