本文主要是介绍C语法总结 底层设计和声明,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
位运算符
使用底层技术进行一些位操作,可以编写加密,视频程序,以及需要高速执行或高效利用空间的程序非常有用。但是过度依赖底层操作会导致程序丧失可移植性。如果一定要使用尽量将操作限定在特定的模块中而不要分散在各处。
符号 | 含义 |
<< | 左移位 |
>> | 右移位 |
~ | 按位求反 |
& | 按位与 |
^ | 按位异或 |
| | 按位或 |
为了可移植性,最好仅对无符号数进行移位运算
优先级来说,依次是 ~ ,&, ^, |
//这些操作符不会改变原有变量的值,所以需要如下 i <<= 2; k = i & j; unsigned short i , j, k; i = 21; // i is 21 ( 0000 0000 0001 0101 ) j = 56; // j is 56 ( 0000 0000 0011 1000 ) k = ~i; // k is 65514( 1111 1111 1110 1010 ) k = i & j; // k is 16( 0000 0000 0001 0000 ) k = i ^ j; // k is 45( 0000 0000 0010 1101 ) k = i | j; // k is 61( 0000 0000 0011 1101 ) //设置i 的第n位置,惯用写法,假设要设置的位置存放在j中 i |= 1<<j; //如果j是3,则1<<j为0x0008 //要清楚第n位置 i &= ~(1<<j); //如果j是3,则1<<j是0x0008,取反就是0XFFF7,再 //跟i 进行与运算 //要测试第n位置是否被设置了 if( i & 1<<j ); //用位运算访问位域 //修改位域,将二进制值101存入变量i 的第4至第6位 i = i & ~0x0070 | 0x0050; //假设j 包含了需要存到到i 的第4位至第6位的值,因为 | 优先级低括号可以去掉 i = (i & ~0x0070) | (j<<4); //获取位域,获取变量i 的0至2位 j = i & 0x0007; //如果位域不在i 的右端,需要先移位在提取,如果要获取i 的4到6位 j = (i >> 4) & 0x0007;
结构中的位域
struct file_date {unsigned int day : 5;unsigned int month : 4;unsigned int year : 7; }; //这里的day占5个比特,month占4个比特,year占7个比特
为了提高可移植性:将所有的位域声明为unsigned int 或signed int
其他底层技术
//定义占一个字节的类型 typedef unsigned char BYTE;//占2个字节的类型 typedef unsigned short WORD;//模拟x86的cpu寄存器 union {struct {WORD ax, bx, cx, dx;} word;struct {BYTE al, ah, bl, bh, cl, ch, dl, dh;} byte; } regs; regs.byte.ah = 0x12; regs.byte.al = 0x34; printf("AX= %hx\n",regs.word.ax); //AX = 1234
当存储的数据多于一个字节时,有两种存储方式,大头派和小头派
x86的CPU是小头派,也就是高位后存储,0x1234在内存中就是34 12
大头派如IBM的CPU或者是java虚拟机,是先存储高位再存储低位,0x1234在内存中的位置就是12 34
//可以打印出当前程序的地址 printf("main address = %x\n", (unsigned int)main);//volatile 跟java中的volatile类似,表示当前的内存是易变的,即每次读取时都会 //重新向内存中读取一次,而不会读取寄存器中的值 while(缓冲区未满) {//等待输入buffer[i] = *p;if(buffer[i++] == '\n') {break;} }//有的编译器会检查到p和 *p都没有改变,于是对程序进行优化,变成: 在寄存器中存储*p while(缓冲区未满) {//等待输入buffer[i] = 存储在寄存器中的值;if(buffer[i++] == '\n') {break;} } //将p声明为volatile可以避免编译器做优化
声明语法
声明的语法规则
存储类型 类型限定符 类型说明符 声明符(也就是变量)
存储类型包括: auto,static,extern,register
类型限定符包括: const,volatile,restrict(C99语法,只能限定指针)
类型说明符包括: void,char,short, int,long, float,double,single, unsigned
类型说明符的出现顺序是无关的int unsigned long 和 long unsigned int 完全一样
struct {int x, y} 或者typedef创建的类型名也是类型说明符
C99中还有函数说明符inline,只能用于函数
//存储类型 类型限定符 类型说明符 声明符
extern const unsigned long int a[10];
存储类型
C程序中每个变量都具有以下3个性质:
1.存储期限 变量存储期限决定了为变量预留和内存被释放的时间。
性质 | 说明 |
存储期限 | 变量存储期限决定了为变量预留和内存被释放的时间 自动存储期限 的变量在所属块被执行时获得内存单元,并在块终止时释放内存单元,从而 导致变量失去值 静态存储期限 的变量在程序原型期间占有同一个存储单元,也就是允许无限期地保留它 的值 |
作用域 | 变量的作用域是指可以引用变量的那部分程序文本 块作用域 变量从声明的地方一直到所在块的末尾都是可见的 文件作用域 变量从声明的地方一直到所在文件的末尾都是可见 |
链接 | 变量的链接确定了程序的不同部分可以共享此变量的范围 外部链接 变量可以被程序中的几个(或许全部)文件共享 内部链接 变量只能属于单独一个文件,但此文件中的函数可以共享这个变量 无链接 变量属于单独一个函数而且根本不能被共享 |
//静态存储期限, 文件作用域,外部链接 int i;//自动存储期限,块作用域, 无链接 void f(void) {int j; }
auto存储类型
只对属于块的变量有效,auto变量具有自动存储期限,块作用域,无链接。auto存储类型几乎从来不用明确
的指明,因为对于在块内声明的变量,它是默认的。
static存储类型
可以用于全部变量,而无需考虑变量的声明位置,当变量在块外部时static说明变量具有内部链接;当变量在块内时候static把变量存储期限从自动变量静态
//静态存储期限, 文件作用域, 内部链接 static int i;//静态存储期限, 块作用域, 无链接 void f(void) {satic int j; }
把块外部的变量i 声明为static时,文件内部的函数都可以访问这个变量但是其他文件中的函数不可以,static的此种用法可以用来实现一种称为信息隐藏的技术
static变量具有以下一些性质:
1.块内的static变量只在程序执行前进行一次初始化,而auto变量则会在每次出现时进行初始化
2.每次函数被递归调用时,它都会获得一组新的auto变量,但是如果函数含有static变量,那么此函数的全部
调用都可以共享这个static变量
3.虽然函数不应该返回指向auto变量的指针,但是函数返回指向static变量的指针是没有错误的
extern存储类型
可以使几个源文件共享一个变量,这种共享方式和共享函数很类似,把函数定义在一个源文件中,然后在需要调用此函数的其他文件中放置声明,共享外部变量的方式和此方式非常类似
//这样的声明不会给变量i 分配存储单元,只是提示编译器需要访问定义在其他地 //方的变量(可能稍后在同一个文件中,但更常见是在另一个文件中) //extern可以用于所有类型的变量,包括数组,声明数组时可以省略数组长度 extern int i;//对变量进行初始化的extern声明是变量的定义 extern int i = 0; //等价于 int i =0;//extern声明中的变量始终具有静态存储期限,变量的作用域依赖于声明的位置, //如果声明在块内部,那么变量具有块作用域;否则变量具有文件作用域//静态存储期限,文件作用域, 什么链接? extern int i;//静态存储期限,块作用域,什么链接? void f(void) {extern int j; }
确定extern类型变量的链接有一定难度。如果变量在文件中较早的位置(任何函数定义的外部)声明为static,那么它具有内部链接;否则(通常情况下)变量具有外部链接。
register存储类型
这种存储类型要求编译器把变量存储在寄存器中,而不是像其他变量一样保留在内存中
register变量具有和auto变量一样的存储期限(自动),作用域(块作用域),链接(无链接)
但是寄存器没有地址,所以对register变量做 &操作,也就是取地址操作是非法的。
函数的存储类型
函数的存储类型只有extern和static
在函数的声明处定义extern说明函数具有外部链接,也就是允许其他文件调用此函数;
声明为static说明此函数是内部链接只有当前文件才可以调用。
如果不加说明则函数具有外部链接
//外部链接 extern int f( int i );//内部链接 static int g( int i );//外部链接 int h( int i );
建议使用static存储类型,这样的好处是:
1.更容易维护,对此函数内部的修改不会影响其他函数,也容易定位问题
2.减少了"名字空间污染",声明static函数具有内部链接,所以其他文件中可以重新使用这个函数的名字
带有外部链接的大量函数名可能导致C程序员所说的"名字空间污染",即不同文件中的名字意外地发生了
冲突,使用static存储类型可以有效解决这个问题。
int a; extern int b; static int c;void fun(int d, register int s) {auto int g;int h;static int i;extern int j;register int k; }
上面定义的变量(注意没有函数f )
名字 | 存储期限 | 作用域 | 链接 |
a | 静态 | 文件 | 外部 |
b | 静态 | 文件 | 不确定 |
c | 静态 | 文件 | 内部 |
d | 自动 | 块 | 无 |
e | 自动 | 块 | 无 |
g | 自动 | 块 | 无 |
h | 自动 | 块 | 无 |
i | 静态 | 块 | 无 |
j | 静态 | 块 | 不确定 |
k | 自动 | 块 | 无 |
类型限定符
包括 const,volatile,和C99中的restrict(只能用于指针)
const用于声明变量是只读的
const int n = 10;
把对象声明为const有以下几种好处:
1.const是文档格式,声明对象是const类型可以提示任何阅读程序的人,该对象的值不会改变
2.编译器可以检查程序没有特意的视图改变该对象的值
3.当为特定类型的应用(如嵌入式)编写程序时,编译器可以用单词const来识别需要存储到ROM(只读存储器)
中的数据
const和#define的区别
1.可以用#define指令为数值,字符或字符串创建名字。const可用于产生任何类型的只读对象,包括数组,
指针,结构和联合
2.const对象遵循与遍历相同的作用域规则,而用#define创建的常量不受这些规则的限制,特别是不能用
#define创建具有作用域的常量
3.和宏的值不同,const对象的值可以在调试器中看到
4.不同于宏,const对象不可以用于常量表达式
5.对const对象应用取地址运算符& 是合法的,因为他有地址,宏没有地址
没有绝对的原则说明何时使用#define以及何时使用const。这里建议对表示数字或字符的常量使用#define。
这样就看以把这些常量作为数组维数,并且在switch语句或其他要求常量表达式的地方使用它们
volatile修饰这个变量为可变的,就是每次去读这个变量的时候,都不会从寄存器中读取,而直接去读内容,以防止编译器做优化
restrict 这个关键字只能用于指针,表示之后指针指向的内容都必须由这个指针还修改,而不能通过其他途径修改
//表示s1和s2的地址不能重叠,否则函数可能不能执行 void * memcpy(void * restrict s1, const void * restrict s2, size_t n);//s1和s2的地址可以重叠 void * memove(void * s1, const void * s2, size_t n);
声明符
无论声明有多么复杂,都有下面两条规则来解释:
1.始终从内往外读声明符,换句话说,定位声明的标示符,并且从此处开始解释声明。
2.在做选择时,始终使[]和()优先于* ,如果* 在标示符的前面,而标示符后边跟着(),那么标示符表示函数而不是指针。
//[]优先级比* 高,所以ap是数组指针 int *ap[10];//fp是标识符,由于*在标识符前面,且后边跟着(),()优先级更高,所以fp是返回 //指针的函数 float *fp(float);//pf的 类型 1.指针指向,2是具有int类型的实际参数函数,3.返回void类型值 void (*pf) (int);//某个特定的声明是不合法的 //函数不能返回数组 int f(int)[];//函数不能返回函数 int g(int)(int);//函数类型的数组也是不合法的 int a[10](int);//一个复杂的声明 int *(*x[10])(void); //首先x[10]表明是一个数组,然后*x[10]表示一个指针指向的数组, //void说明是不带有实际参数的函数,最后int 表示返回的是一个指向int类型的 //指针具有 自动存储期限 的变量没有默认初始值
具有静态存储期限 的变量默认初始值为0
所以静态存储期限的指针变量默认就是空指针,而自动存储期限的指针默认值可能是任意内容
但是为了阅读和代码风格,所有的变量包括(静态存储期限)都应该提供显示的初始化。
内联函数
C99中的新关键字 inline。这个关键字只能作用于函数
如果一个函数被调用了上亿次,那么定义为inline,可以让编译器更好的优化,编译成机器代码增加执行速度。
inline类似于register和restrict关键字,都是用于提升性能的。
inline double average(double a, double b) {return (a + b)/2; }average具有外部链接,但是编译器并没有考虑average的定义是外部定义(它是内联定义)所以视图在别的文件中调用average将被认为是错误的。
1.可以将函数改成内部链接
static inline double average(double a, double b) {return (a + b)/2; }
2.或者将函数放到一个头文件中
C99中规定如果特定文件中某个函数的所有顶层声明中都有inline都没有extern,则该函数定义在该文件中是内联的。如果在程序的其他地方使用该函数(包含其内联定义的文件也算),则需要在另一个文件中为其提供外部定义。
C99中对具有外部链接的内联函数(未对具有内部链接的内联函数做约束)做了如下限制:
1.函数中不能定义可改变的static变量
2.函数中不能引用具有内部链接的变量
这样该函数可以定义同时为static和const变量,但是每个内联定义都需要分别创建该变量的副本。
gcc仅当使用 -O 命令行选项请求进行优化时,才会对函数进行“内联”。
这篇关于C语法总结 底层设计和声明的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!