1.什么是定义?什么是声明?两者的区别是什么?
所谓定义就是创建一个对象,为这个对象分配一块内存,同时给这块内存取一个名字,这个名字就是我们常说的对象名或变量名。但是注意,这个名字一旦和这块内存关联起来,他们就是一个整体,生死相依,不离不弃。并且这块内存的位置也不能被改变。一个对象或者变量,在一个区域内(全局,文件,函数,循环等)只能被定义一次,如果定义多次,编译器会提示你重复定义同一个变量或对象。
再来说一下声明:
第一层含义:告诉这个编译器,这个名字已经匹配到一块内存上路了,后面的代码用变量火女对象实在别的地方定义的。定义是原件,原件唯一有效,那么声明就像复印件,可以重复打印,可以多次声明。
第二层含义:告诉编译器,这个变量或者对象的名字已经被占用了,别的地方,不能在使用这个名字作为其他的变量或者对象名;比如在电影票,你买了一张票后提示他人该位置已经被其他上占用了。
这种声明最典型的例子就是函数参数的声明,例如:
int func(int a, char b);
举个例子:A),int a;B),extern int a;
这样疏理后,应该能够区分出:A是定义,B是声明。
那么到定义和声明的根本区别是什么:是内存。
定义创建对象,并为这个对象分配了内存;而声明只是将与内存关联的对象名进行外域可见性的扩充,看不到,可以继续声明。
2.什么是变量
其值在其作用域内可以改变的量称为变量。一个变量有一个名字,在内存中占据一定的存储空间。变量在使用前必须要定义,每个变量都有自己的地址。变量依据其定义的类型,分为不同类型,如整型变量、字符型变量、浮点型变量、指针型变量等。变量的值可以发生改变,意味着它可以被覆盖、被写入、被赋值。每个变量必须要有一个名字和它所在内存空间绑定。
3.全局变量和局部变量的区别
a.全局变量申明位置在所有代码块之外,不存储于堆栈,作用域为从申明到文件尾,如果申明为static,不允许从其他文件访问。
b.局部变量申明位置在代码块起始处,存储于堆栈,作用域为整个代码块,如果申明为static,变量不存储于堆栈中,它的值在整个程序的执行期一直保持。
c.形式参数申明位置在函数头部,存储于堆栈,作用域为整个函数,不允许申明为static。
4.static的使用
a、修饰变量
当static来修饰一个变量时,就注定了这个变量的可见范围和生命周期;
(1)当修饰全局变量时
static int flat1 = 0;
int flat2 = 0;
这两个变量存储在全局数据区,flat1只在本文件可见,其他文件中不可见;flat2可在其他文件中通过声明extern int flat2来使用;
(2)当修饰局部变量时
void fun(void){
static int temp1;
int temp2 = 0;
......................
return;
}
函数中,temp1为局部静态变量,存储在全局数据区,temp2为局部变量,存储在栈上;但是随着函数的退出,temp2的生命周期也就结束,但是temp1依然有效,只不过可见范围为本函数内,下次再次进入本函数时,对temp1的任何修改都是在上次修改的基础上进行,也就是说temp1有记忆性。
b、修饰函数
static修饰的函数主要是在本文件中使用的函数,不对外提供,Linux内核中的任何文件中都有此类型的静态函数;
static inline void enable_noirq(void){
................
}
使用static修饰函数的好处就是,所有文件可以定义同名的函数,不用考虑重名导致的编译报错;
5.extern的使用
修饰符extern用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”。
a. extern修饰变量的声明。
举例来说,如果文件a.c需要引用b.c中变量int v,就可以在a.c中声明extern int v,然后就可以引用变量v。能够被其他模块以extern修饰符引用到的变量通常是全局变量。还有很重要的一点是,extern int v可以放在a.c中的任何地方,比如你可以在a.c中的函数fun定义的开头处声明extern int v,然后就可以引用到变量v了,只不过这样只能在函数fun作用域中引用v罢了,这还是变量作用域的问题。对于这一点来说,很多人使用的时候都心存顾虑。好像extern声明只能用于文件作用域似的。
b. extern修饰函数声明。
从本质上来讲,变量和函数没有区别。函数名是指向函数二进制块开头处的指针。如果文件a.c需要引用b.c中的函数,比如在b.c中原型是int fun(int mu),那么就可以在a.c中声明extern int fun(int mu),然后就能使用fun来做任何事情。就像变量的声明一样,extern int fun(int mu)可以放在a.c中任何地方,而不一定非要放在a.c的文件作用域的范围中。对其他模块中函数的引用,最常用的方法是包含这些函数声明的头文件。
使用extern和包含头文件来引用函数有什么区别呢?
extern的引用方式比包含头文件要简洁得多。extern的使用方法是直接了当的,想引用哪个函数就用extern声明哪个函数。这样做的一个明显的好处是,会加速程序的编译(确切的说是预处理)的过程,节省时间。在大型C程序编译过程中,这种差异是非常明显的。
6.const的作用
const是C语言的一种关键字,起受保护,防止以外的变动的作用。可以修饰变量,参数,返回值甚至函数体,const可以提高程序的健壮性。
a.const修饰参数。const只能修饰输入参数。
(1)、如果输入参数是指针型的,用const修饰可以防止指针被意外修改。
(2)、如果参数采用值传递的方式,无需const,因为函数自动产生临时变量复制该参数。
(3)、非内部数据类型的参数,需要临时对象复制参数,而临时对象的构造,析构,复制较为费时,因此建议采用前加const的引用方式传递非内部数据类型。而内部数据类型无需引用传递。
b.const修饰函数返回值。
(1)、函数返回const指针,表示该指针不能被改动,只能把该指针赋给const修饰的同类型指针变量。
(2)、函数返回值为值传递,函数会把返回值赋给外部临时变量,用const无意义!不管是内部还是非内部数据类型。
(3)、函数采用引用方式返回的场合不多,只出现在类的赋值函数中,目的是为了实现链式表达。
c.const+成员函数。任何不修改数据成员的函数都应该声明为const类型,如果const成员函数修改了数据成员或者调用了其他函数修改数据成员,编译器都将报错!
7.typedef和#define的区别
可以把typedef看成是一种“封装”类型----在声明之后不能再往里面增加别的东西。它和宏的区别体现在两个方面
a.可以用其他类型说明符对宏类型名进行扩展,但对typedef所定义的类型名却不能这样做。
#define peach int
unsigned peach i; 没问题
typedef int peach;
unsigned peach i; 语法错误
b.连续几个变量的声明中,用typedef定义的类型能够保证声明中所有的变量均为同一种类型,而用#define定义的类型无法保证
#define int_ptr int *
int_ptr chalk, cheese;
经过宏扩展
int *chalk, cheese;
chalk是一个指针,cheese是一个整型。
typedef int* int_ptr;
int_ptr chalk,cheese;
chalk,cheese都为整型指针
8.运算符分类
C语言一共有34种运算符,10种运算类型:算术运算符(+、-、*、/、%)、关系运算符(>、>=、==、!=、<、<=)、位运算符(>>、<<、==、!=、<、<=)、逻辑运算符(!、||、&&)、条件运算符、(?:)指针运算符(&、*)、赋值运算符(=)、逗号运算符(,)、求字节运算符(sizeof)、强制类型转换运算符((类型名))、其他(下标[]、分量、函数);若按参与运算的对象个数,C语言运算符可分为单目运算符(如!)、双目运算符(如+、-)和三目运算符(如?:)
9.位运算的分类和用法
a. 按位与运算 按位与运算符"&"是双目运算符。其功能是参与运算的两数各对应的二进位相与。只有对应的两个二进位均为1时,结果位才为1 ,否则为0。参与运算的数以补码方式出现。
例如:9&5可写算式如下: 00001001 (9的二进制补码)&00000101 (5的二进制补码) 00000001 (1的二进制补码)可见9&5=1。
按位与运算通常用来对某些位清0或保留某些位。例如把a 的高八位清 0 , 保留低八位, 可作 a&255 运算 ( 255 的二进制数为0000000011111111)。
main(){
int a=9,b=5,c;
c=a&b;
printf("a=%d/nb=%d/nc=%d/n",a,b,c);
}
b. 按位或运算 按位或运算符“|”是双目运算符。其功能是参与运算的两数各对应的二进位相或。只要对应的二个二进位有一个为1时,结果位就为1。参与运算的两个数均以补码出现。
例如:9|5可写算式如下: 00001001|00000101
00001101 (十进制为13)可见9|5=13
main(){
int a=9,b=5,c;
c=a|b;
printf("a=%d/nb=%d/nc=%d/n",a,b,c);
}
c. 按位异或运算 按位异或运算符“^”是双目运算符。其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。参与运算数仍以补码出现,例如9^5可写成算式如下: 00001001^00000101 00001100 (十进制为12)
main(){
int a=9;
a=a^15;
printf("a=%d/n",a);
}
d.左移和右移
左移运算符“<<”是双目运算符。其功能是把“<< ”左边的运算数的各二进位全部左移若干位,由“<<”右边的数指定移动的位数,高位丢弃,低位补0.
右移运算符“>>”是双目运算符。其功能是把“>> ”左边的运算数的各二进位全部右移若干位,“>>”右边的数指定移动的位数。但注意:对于有符号数,在右移是,符号位将随同移动,当为正数时,最高位补0;而为负数时,符号位为1,最高位是补0或是补1取决于编译系统的规定。
10.条件运算符和条件表达式的使用
条件运算符为?和:,它是一个三目运算符,即有三个参与运算的量。由条件运算符组成条件表达式的一般形式:表达式1?表达式2:表达式3
求值规则:当表达式1为真,则表达式2作为条件表达式的值,否则以表达式3作为条件表达式的值。
还要注意以下几点:
a.条件运算符的运算优先级低于关系运算符和算术运算符,但高于赋值符。
b.条件运算符“?”和“:”是一对运算符,不能分开单独使用。
c.条件运算符的结合方向是自右至左。