目录
语法. 3
NULL,TRUE,FALSE 3
大小端存储 4
类型转换 4
转义字符 5
运算符的优先级 5
表达式(a=b=c) 7
*pa++=*pb++ 7
值的比较(浮点,指针) 8
循环语句的小技巧 8
常量 const enum define code 8
全局变量定义在.h 10
函数 11
函数声明与定义 11
函数堆栈(?) 12
函数调用规范(*) 12
函数连接规范(?) 12
参数传递 12
返回值 12
存储类型&作用域 12
递归与迭代 13
断言 assert 13
函数中的const 保护 14
指针 14
指针的初始化 14
指针运算 14
数组 15
函数指针》》》》》》》》》 16
结构,位域,联合体 16
位域 16
联合体 union 16
枚举 16
结构体对齐 17
一些代码抄抄 17
Printf 17
编译 18
预处理文件包含 18
宏定义 #与## 18
编译器常量 20
语法.
NULL,TRUE,FALSE
-
C语言并没有定义 TRUE 与 FALSE,可以通过宏定义来实现。
1 2 3 4 5 |
uint8_t * pnum ; uint8_t real_num=0; uint32_t num =0x12345678; pnum =(uint8_t*)# real_num=*pnum;//Keil c51中,real_num=0x12,大端模式 |
-
由宏定义可得知 TRUE =1,而在C语言 if(条件)、while(条件) 中,条件为TRUE是(!0) ,所以不能用 自定义的TRUE来当判断条件。
1 2 3 4 |
int abc =0x11; if(abc==TRUE)//条件不成立,TRUE=1 { } |
-
True,flase推荐用法 if(flag)// 如果为true;If (!flag) //如果为FALSE如果判断一个变量是否为0,使用 if(a==0),而不要使用 if(!a),如果是判断是否为空指针,则使用NULL避免误解
-
NULL 在 STRING.H 中定义,是可以赋值给任何类型指针的值0,但是在C++ 中允许从0到任何指针类型的隐式转换,即定义为数0.
1 2 3 |
#ifndef NULL #define NULL ((void *) 0L) #endif |
大小端存储
-
大端存储:高字节存低地址,低字节存高地址,高字节在前,低字节在后,并将最高字节的地址作为变量的首地址。字符串存储。它要求变量在内存中的存放地址必须自然对齐(基本数据类型-short、int、double 的变量不能简单地存储在内存的任意地址,他们的起始地址必须能够被他们的大小整除,在32 位平台中,int和指针变量的地址需要被4整除,short 被 2整除) why?跨寄存器?
-
小端存储:高字节在高地址,低字节在低地址,低字节在前,高字节在后,并把最低字节的地址作为变量的首地址。并不要求自然对齐。
大端存储"abcd" |
0x12345678 |
|
小端存储"abcd" |
0x12345678 |
||
地址 |
char |
long |
|
地址 |
char |
long |
0x0001 |
a |
0x12 |
|
0x0001 |
d |
0x78 |
0x0002 |
b |
0x34 |
|
0x0002 |
c |
0x56 |
0x0003 |
c |
0x56 |
|
0x0003 |
b |
0x34 |
0x0004 |
d |
0x78 |
|
0x0004 |
a |
0x12 |
-
大小端判断代码
-
* 强制指针
-
1 2 3 4 5 |
uint8_t * pnum ; uint8_t real_num=0; uint32_t num =0x12345678; pnum =(uint8_t*)# real_num=*pnum;//Keil c51中,real_num=0x12,大端模式 |
-
联合体
1 2 3 4 5 6 7 8 |
union test_array { uint32_t a; uint8_t b[4]; } num_union; uint8_t real_num=0; num_union.a =0x12345678; real_num=num_union.b[0];//Keil C51 real_num=0x12; |
类型转换
标准C语言中,int为默认类型,不明确函数形参或者返回值的时候,则为int 类型。(PC)
-
隐式转换 char ->int->long->float->double
-
强制转换 ,一定要区分值的截断与内存的截断
-
基本类型转换 double a=10.23; int b=(int)a; //合法正确,舍去小数位,值的截断
-
基本类型转换 数据溢出 double a= 1.25e+12; int b =(int)a;//数据溢出
-
内存指针转换 double a=1000.25;int *p=(int*)&a;//并不会造成内存截断,但是数据内容由于是(int*),所以转换的数据是错误的。且这个数据是不可预料的。
-
内存指针转换(溢出)int a=100;double b=(double *)a;//不允许,数据内存溢出了。
-
转义字符
区分回车与换行,输入回车(\r),输出换行(\n)。换行一般用语文件,把键盘输入的"回车字符转换为"换行"字符保存而非直接保存"回车字符",换行字符还用于程序的输出控制,指示终端输出从新行开始,回车用于键盘输入。有些函数 getchar () 可以把键盘输入的回车转换为换行字符返回。_@高质量程序设计指南
运算符的优先级
优先级 |
运算符 |
名称或含义 |
使用形式 |
结合方向 |
说明 |
1 |
[] |
数组下标 |
数组名[常量表达式] |
左到右 |
|
() |
圆括号 |
(表达式)/函数名(形参表) |
|
||
. |
成员选择(对象) |
对象.成员名 |
|
||
-> |
成员选择(指针) |
对象指针->成员名 |
|
||
2 |
- |
负号运算符 |
-表达式 |
右到左 |
单目运算符 |
(类型) |
强制类型转换 |
(数据类型)表达式 |
|
||
++ |
自增运算符 |
++变量名/变量名++ |
单目运算符 |
||
-- |
自减运算符 |
--变量名/变量名-- |
单目运算符 |
||
* |
取值运算符 |
*指针变量 |
单目运算符 |
||
& |
取地址运算符 |
&变量名 |
单目运算符 |
||
! |
逻辑非运算符 |
!表达式 |
单目运算符 |
||
~ |
按位取反运算符 |
~表达式 |
单目运算符 |
||
sizeof |
长度运算符 |
sizeof(表达式) |
|
||
3 |
/ |
除 |
表达式/表达式 |
左到右 |
双目运算符 |
* |
乘 |
表达式*表达式 |
双目运算符 |
||
% |
余数(取模) |
整型表达式/整型表达式 |
双目运算符 |
||
4 |
+ |
加 |
表达式+表达式 |
左到右 |
双目运算符 |
- |
减 |
表达式-表达式 |
双目运算符 |
||
5 |
<< |
左移 |
变量<<表达式 |
左到右 |
双目运算符 |
>> |
右移 |
变量>>表达式 |
双目运算符 |
||
6 |
> |
大于 |
表达式>表达式 |
左到右 |
双目运算符 |
>= |
大于等于 |
表达式>=表达式 |
双目运算符 |
||
< |
小于 |
表达式<表达式 |
双目运算符 |
||
<= |
小于等于 |
表达式<=表达式 |
双目运算符 |
||
7 |
== |
等于 |
表达式==表达式 |
左到右 |
双目运算符 |
!= |
不等于 |
表达式!= 表达式 |
双目运算符 |
||
8 |
& |
按位与 |
表达式&表达式 |
左到右 |
双目运算符 |
9 |
^ |
按位异或 |
表达式^表达式 |
左到右 |
双目运算符 |
10 |
| |
按位或 |
表达式|表达式 |
左到右 |
双目运算符 |
11 |
&& |
逻辑与 |
表达式&&表达式 |
左到右 |
双目运算符 |
12 |
|| |
逻辑或 |
表达式||表达式 |
左到右 |
双目运算符 |
13 |
?: |
条件运算符 |
表达式1? 表达式2: 表达式3 |
右到左 |
三目运算符 |
14 |
= |
赋值运算符 |
变量=表达式 |
右到左 |
|
/= |
除后赋值 |
变量/=表达式 |
|
||
*= |
乘后赋值 |
变量*=表达式 |
|
||
%= |
取模后赋值 |
变量%=表达式 |
|
||
+= |
加后赋值 |
变量+=表达式 |
|
||
-= |
减后赋值 |
变量-=表达式 |
|
||
<<= |
左移后赋值 |
变量<<=表达式 |
|
||
>>= |
右移后赋值 |
变量>>=表达式 |
|
||
&= |
按位与后赋值 |
变量&=表达式 |
|
||
^= |
按位异或后赋值 |
变量^=表达式 |
|
||
|= |
按位或后赋值 |
变量|=表达式 |
|
||
15 |
, |
逗号运算符 |
表达式,表达式,… |
左到右 |
从左向右顺序运算 |
容易误导的
当表达式存在同一个优先级的时候,这个时候需要结合性处理,所有的赋值符(包括复合赋值)都具有右结合性,就是在表达式中最右边的操作最先执行,然后从右到左依次执行。这样,c先赋值给b,然后b在赋值给a,最终a的值是c。类似地,具有左结合性的操作符(如位操作符"&"和"|")则是从左至右依次执行。
a=b+c+d
=是右结合的,所以先计算(b+c+d),然后再赋值给a
+是左结合的,所以先计算(b+c),然后再计算(b+c)+d
C语言中具有右结合性的运算符包括所有单目运算符以及赋值运算符(=)和条件运算符。其它都是左结合性。
表达式(a=b=c)
-
在 && 的表达式中,尽量把最有可能false 的语句排前,在|| 的表达式中,尽量把最可能TRUE的语句排前面,因为C中逻辑表达式判断采用突然死亡法判断,只要前面的条件满足了,则不需要判断后面的语句。
-
If(a
-
a=b=c, b=c;a=b; 因为=为右结合
*pa++=*pb++
*pa=*pb; //*高于++高于=,++,=右结合
Pa++;pb++;
值的比较(浮点,指针)
-
If((double a )==(double b)),这里的判断需要考虑到编译环境的数据精度.
-
空指针判断使用 if(p==NULL),而不要使用if(p==0)//整数比较
循环语句的小技巧
-
遍历二维数组使用先行后列的方式,因为数组行地址是连续的。
-
如果循环体内存在逻辑判断,在循环次数很大的情况下宜将判断条件置在外部。一方面因为判断条件的存在使得编译器无法对循环进行优化,并且这样可以减少N-1次判断。
for(i=0;i<N;i++) { if(condition) dosomething(); else doothers(); } |
if(condition) { for(i=0;i<N;i++) dosomething(); } else { for(i=0;i<N;i++) doothers(); } |
常量 const enum define code
-
Const 对比 #define 的优点,const 包含数据类型,编译器可以进行静态检查,#define只是进行简单的字符替换,,有时会出现错误,边际效应。关于#define 参考http://klqulei23.blog.163.com/blog/static/1323153372012722111721750/
-
char * const cp : 定义一个指向字符的指针常数
const char* p : 定义一个指向字符常数的指针
char const* p : 等同于const char* p看*和const谁离右边的定义指针名最近,*离得近的话表示该指针指向一个常量字符串,不能通过该指针改变字符串的内容;const离得近的话表示这是一个常量指针,指针指向的位置一开始就确定,不能改变。
-
Const, C语言中,用const 定义的常量实际上他是一个不能修改的变量,会分配存储空间(外连接),但是可以绕过编译器检查来修改它(指针)。
1 2 3 4 5 6 7 8 9 10 11 |
#include int main(int argc, char *argv[]) { const char abc=100; //abc=12;//编译器检查出错 char * ptr =(char*)&abc;//绕过编译器检查 *ptr=12; printf("abc=%d\r\n",abc);//abc=12; printf("*ptr=%d\r\n",*ptr); return 0; } |
-
单片机里面有一块区为CODE 区,定义在code区的时候即使用指针修改,也不生效。
1 2 3 4 5 6 7 8 9 |
void main() { char code abc=10; char * ptr =(char*)&abc; *ptr=12;//由于CODE 关键字,abc=10 printf("abc=%d\r\n",abc);//abc=12; printf("*ptr=%d\r\n",*ptr); while(1); } |
-
枚举常量 enum 不可改变,编译阶段就直接报错。
全局变量定义在.h
全局变量我们一般使用的是在某个.c文件中定义,然后其他c文件要用的,直接extern声明一下就能用了。C语言中的全局变量的作用域是整个源程序,只能定义一次。但是Ucos 2 的全局变量定义在.h中,与一般做法不一样。一般情况下,全局变量不可以定义在可被多个.C文件包含的头文件中,但是ucos利用编译机制巧妙做到了,将全局变量放在一块比较容易管理。比如 OS_EXT int g_Var;就是第一次被包含的时候 编译为 int g_Var,然后第二次被其他文件包含的时侯编译为 extern int g_Var。
C编译器在原文件中编译到include这条语句的时候,是将对应的头文件内容拷贝到该处的,也就是说在头文件中定义的全局变量,其实还是定义到了该源文件中。那么如果另外一个源文件也想引用这个全局变量怎么办呢?是不是同理用include包含该头文件就可以了?如果你这样做了,编译器在编译的时候是不会报错的,因为你的程序语法没有任何错误,但是在连接(link...)的时候错误就来了。在VC++6.0环境中会报出:error LNK2001: unresolved external symbol "int a" (?a@@3HA)这样的错误。这是为什么呢?每一个源文件编译之后都会产生一个OBJ或O文件(我们叫它目标文件),之前说过源文件中的include 相当于将该头文件中的所有语句放在该处,全局变量也就相应的添加进来,问题就在这里,既然全局变量被相应包含进来,两个原文件中就都分别定义了该变量,但是前面也说过,全局变量的作用域是整个源程序,只能有定义一次,然而在这个源程序中该变量被定义了两次,当然要报错了。怎么解决呢?参考ucos
/* file 1---------------------------OS_uCOS_II.h */
#ifndef OS_uCOS_II_H
#define OS_uCOS_II_H
#ifdef OS_GLOBALS
#define OS_EXT
#else
#define OS_EXT extern
#endif
OS_EXT int g_Var;
#endif
/* file 2---------------------------OS_uCOS_II.c*/
#define OS_GLOBALS
#include "uCOS_II.h"
/* file3----------------------------------TEST.c*/
#include "uCOS_II.h"
void fun(int x)
{
g_Var=x;
}
/* file4----------------------------------MAIN.c*/
#include "uCOS_II.h"
void main(void)
{
g_Var=10;
}
/* ------------------------------end of program*/
首先,我们可以看到全局变量g_Var在文件TEST.C和MAIN.C中有引用,它的定义是在头文件OS_uCOS_II.h 中,但是在定义的该变量用到了宏OS_EXT,但是该宏是否被"赋值"取决于是否定义OS_GLOBALS宏,也就是说OS_EXT是否被关键字extern替代是有条件的;然后,我们可以看到OS_GLOBALS宏在文件OS_uCOS_II.c中被定义,并且该文件包含了头文件OS_uCOS_II.h,根据include替代原则,头文件OS_uCOS_II.h所有信息将被包含进来,OS_GLOBALS被首先定义,那么OS_EXT就被定义为空,也即全局变量g_Var在该文件中被定义。同理可分析TEST.c文件和MAIN.c文件,由于这两个源文件只是包含头文件OS_uCOS_II.h,而没有#define OS_GLOBALS,因此OS_EXT将被关键字extern 替代,全局变量就只是作为声明出现。因此也就不会出现多次定义的连接错误。废话了这么多,其实就是条件编译语句的作用。
在移植到STM32/keil/ucos 2.9中,并没有包含
1 2 3 4 |
#ifndef OS_MASTER_FILE #define OS_GLOBALS //这里是重点,只有这个C文件来定义变量 #include "includes.h" #endif |
而其他C文件则是这样的
1 2 3 |
#ifndef OS_MASTER_FILE #include "ucos_ii.h" #endif |
函数
函数声明与定义
-
函数声明包括了 函数的返回值,作用域(extern | static),函数名,函数的形参(形参可以省略写形参变量名,只写类型)如 int sum(char ,char);
-
函数的定义则是实际的函数体。
对函数的"定义"和"声明"不是一回事。"定义"是指对函数功能的确立,包括指定函数名,函数值类型、形参类型、函数体等,它是一个完整的、独立的函数单位。而"声明" 的作用则是把函数的名字、函数类型以及形参类型、个数和顺序通知编译系统,以便在调用该函数时系统按此进行对照检查(例如函数名是否正确,实参与形参的类 型和个数是否一致)。从程序中可以看到对函数的声明与函数定义中的函数首部基本上是相同的。因此可以简单地照写已定义的函数的首部,再加一个分号,就成为 了对函数的"声明"。在函数声明中也可以不写形参名,而只写形参的类型。 在C语言中,函数声明称为函数原型(function prototype)。它的作用主要是利用它在程序的编译阶段对调用函数的合法性进行全面检查。
说明:
<1> 以前的C版本的函数声明方式不是采用函数原型,而只是声明函数名和函数类型。
如:float add(); 不包括参数类型和参数个数。系统不检查参数类型和参数个数。新版本也兼容这种用法,但不提倡这种用法,因为它未进行全面的检查。
<2> 实际上,如果在函数调用前,没有对函数作声明,则编译系统会把第一次遇到的该函数形式(函数定义或函数调用)作为函数的声明,并将函数类型默认为int 型。如一个max函数,调用之前没有进行函数声明,编译时首先遇到的函数形式是函数调用"max(a, b)",由于对原型的处理是不考虑参数名的,因此系统将max()加上int作为函数声明,即int max(); 因此不少教材说,如果函数类型为整型,可以在函数调用前不必作函数声明。但是使用这种方法时,系统无法对参数的类型做检查。或调用函数时参数使用不当,在 编译时也不会报错。因此,为了程序清晰和安全,建议都加以声明为好。
<3> 如果被调用函数的定义出现在主调函数之前,可以不必加以声明。因为编译系统已经先知道了已定义的函数类型,会根据函数首部提供的信息对函数的调用作正确性检查。
<4> 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调用函数中不必对所调用的函数再作声明。http://blog.csdn.net/zhongguoren666/article/details/8477908
函数堆栈(?)
进入函数前保存环境变量和返回地址,进入函数是保存实参的拷贝,函数体内保存局部变量。
函数调用规范(*)
默认函数输入参数的入栈顺序是函数原型中形参从右至左的原则。
参考文献:01_C语言的函数调用过程
02_C函数的调用规范
函数连接规范(?)
高质量程序设计指南 P101
参数传递
-
形参 输入参数放前面,输出参数放置前面,例如:cpy src->dst :void cpy(char* dst_,char* src),传递参数指针如果只做为输入,类型前加const 限定 void cpy(char* dst_,const char* src), const的含义是 src is a point to char const
-
当传递的参数过多时,尽量封装结构
-
尽量不使用类型和数目不定的参数列表('如int printf(const char* format[,argument]…))
C对齐不进行严格的静态类型安全检查(不检查参数类型)
返回值
-
C语言中默认返回值是int 类型。
-
如果函数需要返回值,那么将正常值与返回标志区分,即正常值通过输出形参返回。
-
Retuen (a+b) 优于 char c;c=a+b; returne c; 减少开销(拷贝等过程)
-
少用 static局部变量,因为这样导致相同输入,不同输出
存储类型&作用域
-
存储类型 extern、auto、static、register
局部变量默认为 auto类型,除非用static 或者register 限定。
全局变量默认为 static,如果没有使用extern 只能在本编译单元内使用。
Auto 、register 只能限定局部变量和常量,搞不懂register这个寄存器变量。
假如这么定义:register code char a=0x01;
-
当局部变量和全局变量同名,则使用局部变量
Static 和 全局变量不同的地方在于作用域
-
内连接,外连接,无连接。同名函数报错因为非静态函数为外连接,而局部变量不会,因为是他是内连接
递归与迭代
-
递归是重复调用函数自身实现循环。迭代是函数内某段代码实现循环,而迭代与普通循环的区别是:循环代码中参与运算的变量同时是保存结果的变量,当前保存的结果作为下一次循环计算的初始值。递归循环中,遇到满足终止条件的情况时逐层返回来结束。迭代则使用计数器结束循环。当然很多情况都是多种循环混合采用。任何递归都可以拆分为迭代,但是可能比较复杂,程序结构变混乱。
-
递归函数在进入下一轮时并没有退出,堆栈并没有销毁,函数内的局部变量都是动态创建的,每次进入才会创建,每次结束才会销毁(出栈),堆栈自动增长。开销很大,而递归只是对局部变量反复计算,开销相对较小。
递归举例:2分法查询已经排序的数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
int Find(int *ary,int index,int len,int value) { if(len==1)//最后一个元素 { if (ary[index]==value)return index;//成功查询返回索引 return -1;//失败,返回-1 } //如果长度大于1,进行折半递归查询 int half=len/2; //检查被查值是否大于上半部分最后一个值,如果是则递归查询后半部分 if(value>ary[index+half-1]) return Find(ary,index+half,half,value); //否则递归查询上半部分 return Find(ary,index,half,value); } |
迭代举例:1到100 求和
1 2 3 4 5 |
int v=1; for(i=2;i<=100;i++) { v=v+i; } |
断言 assert
断言assert是宏定义,在debug 阶段使用,用来检查参数的有效性。一般在函数的入口处,检查参数是否有效。
断言是捕捉非法状况,而非错误。例如在 malloc 中可能返回 NULL,这是错误,但不非法。
跟踪语句 tracer 是指示作用,与断言不同
函数中的const 保护
-
修饰输入参数
-
修饰指针指向的内容
-
修饰指针本身
-
行参传递时先拷贝,所以值传递一般无需修饰。当输入参数(值传递)只使用初值,修饰之。
-
修饰返回值
指针修饰,则赋值时只能也是用 const char *a =getstr();//没意义个人觉得
值修饰,避免这种 getstr()=100;//基本也没必要
指针
指针的初始化
指针初始化时,"="的右操作数必须为内存中数据的地址,不可以是变量,也不可以直接用整型地址值(但是int*p=0;除外,该语句表示指针为空指针,等价int*p=NULL)。此时,*p只是表示定义的是个指针变量,并没有间接取值的意思。不确定的指针最好开始定义为NULL,使用的时候先判断是否为空。
Int *p =(int*)0x12345;
Char * p ="abcde";
指针运算
-
*是右结合的,指针的运算的基本单位是sizeof(type),可以通过强制转换改变type大小。
1 2 3 4 5 6 7 8 9 10 11 |
int a=100; int b=0; int * ptr_a=&a; //括号必须加,否则的话=(char*)(ptr_a++) ((char *)ptr_a)++; printf("&a=%p\n",&a);//&a=0028FF44 //ptr_a=0028FF45 printf("((char *)ptr_a)++=%p \n",ptr_a); (char *)ptr_a++;//地址+4 //ptr_a=0028FF45+4 printf("((char *)ptr_a)++=%p \n",ptr_a); |
-
b=* ptr_a++; 与b=(* ptr_a)++;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
int main(int argc, char *argv[]) { int a=100,b=0; int * ptr_a=&a; b=* ptr_a++; //因为 * 与 ++ 同优先级,并且是右结合 //所以= b=*(ptr_a++); //++ 是后计算的,所以先b=*a;再ptr_a++ //ptr_a size=4 printf("&a=%p\n",&a); printf("ptr_a=%p\n",ptr_a); printf("a=%d\n",a); printf("b=%d\n",b); return 0; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
int main(int argc, char *argv[]) { int a=100,b=0; int * ptr_a=&a; b=(* ptr_a)++; //1.取ptr_a =100 //2.b=(*ptr_a) //3.(* ptr_a)++,a+=1; printf("&a=%p\n",&a); printf("ptr_a=%p\n",ptr_a); printf("a=%d\n",a); printf("b=%d\n",b); return 0; } |
-
void test(char * & p)//C++
数组
-
数组命本身就是一个指针,int a[] 等价于 int * const a, a is a const point to int. a=&a[0];
-
初始化
-
1. 指定元素个数 int a[10];//创建10个 int
-
2.不指定元素个数,直接初始化,int a[]={1,2};//创建2个int,注意编译器可能不检查下标此时,即a[100]=9;不会报错
-
同时指定个数和初值,初值不够=0
-
-
二维数组是以行序优先的,a[4][3]=*((a+4*3)+1),有4行 3列,地址分布是先分配行,a[0][0],a[0][1],…….,a[0] 代表的是一个地址,是一个一维数组名,所以二维数组与二级指针是不一样的。即 int ** b != a
-
二维数组传递参数的时候一定要指定除了第一纬以外的所有纬的长度。 Void test ( const int a[][3],int count),why ,因为数组地址是按照先行后列排序的,地址转换=首地址*每行原素个数+第几列,即=a+i*3+j,没有3(length),无法完成计算。
Int a[10]= int * const a;
Int b[3][4]=int (*const b)[4]
Int c[3][4][5]=int (*const c)[4][5]
同时,下面这四种方式定义等价,n=3
a[i][j]
*(a[i]+j)
(*(a+i))+j
*(*(a+i)+j) =è ((a+i*sizeof(int)*n)+j*sizeof(int))
函数指针》》》》》》》》》
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
结构,位域,联合体
位域
按位存储,节省空间,配合联合体使用还可以提取某几位。 语法: 位域名:长度
1 2 3 4 5 6 7 8 9 10 11 |
union wy { struct { unsigned char x1:2; unsigned char x2:2; unsigned char x3:2; unsigned char x4:2; }cn; unsigned char s; } tmp; |
联合体 union
共用内存,不产生开销,只是解释方式不一样,可用作大小端解析。
枚举
声明:Enum week{sum,mon,tue,wed,thu,fri=7,sat};// sat=8,枚举定义 week mytoday=sat;
匿名枚举可做const , 如 enum {A1 =0x10;,A2=0x20},可替代宏定义也。
-
结构体包含枚举变量
1 2 3 4 5 6 7 8 9 10 11 |
//#define length =sizeofof(int) //结构大小=length+length*(num/length)//四入五也入 //=4+16=20 struct abc_class { enum num_class { num=13, num1=9 }qq; char mm[num]; }abc_1; |
-
结构体包含枚举声明
1 2 3 4 5 6 7 8 9 |
//结构大小=num=13 struct abc_class { enum num_class { num=13, num1=9 };//只是声明,不占空间 char mm[num]; }abc_1; |
结构体对齐
一些代码抄抄
Printf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
void Uart_Printf(char *fmt,...) { va_list ap; char string[256];
va_start(ap,fmt); vsprintf(string,fmt,ap); Uart_SendString(string); va_end(ap); } void Uart_SendString(char *pt) { while(*pt) Uart_SendByte(*pt++); } void Uart_SendByte(int data) { if(data=='\n') { while(!(rUTRSTAT0 & 0x2)); WrUTXH0('\r'); } while(!(rUTRSTAT0 & 0x2)); //Wait until THR is empty. WrUTXH0(data); } |
编译
预处理文件包含
#include用于包含头文件,有两种形式:#include
尖括号形式表示被包含的文件在系统目录中。如果被包含的文件不一定在系统目录中,应该用双引号形式。在双引号形式中可以指出文件路径和文件名。如果在双引号中没有给出绝对路径,则默认为用户当前目录中的文件,此时系统首先在用户当前目录中寻找要包含的文件,若找不到再在系统目录中查找。
对于用户自己编写的头文件,宜用双引号形式。对于系统提供的头文件,既可以用尖括号形式,也可以用双引号形式,都能找到被包含的文件,但显然用尖括号形式更直截了当,效率更高。
./表示当前目录,../表示当前目录的父目录。
宏定义 #与##
-
什么是宏
宏定义的作用一般是用一个短的名字代表一个长的代码序列。宏定义包括无参数宏定义和带参数宏定义两类。宏名和宏参数所代表的代码序列可以是任何意义的内容,如类型、常量、变量、操作符、表达式、语句、函数、代码块等。但要尤其注意的是宏名和宏参数必须是合法的标识符,其所代表的内容及意义在宏展开前后必须一直是独立且保持不变的,不能分开解释和执行(括号的意义-layty)。
无参数宏定义。用一个用户指定的称为宏名的标识符来代表一个代码序列,这种定义的一般形式为#define 标识符 代码序列。其中#define之后的标识符称为宏定义名(简称宏名),在宏定义#define之前可以有若干个空格、制表符,但不允许有其它字符,宏名与代码序列之间用空格符分隔。
带参数宏定义。带参数宏定义进一步扩充了无参数宏定义的能力,这时的宏展开既进行宏名的替换又进行宏参数的替换。带参数的宏定义的一般形式为#define 标识符(参数表) 代码序列,其中参数表中的参数之间用逗号分隔,在代码序列中必须要包含参数表中的的参数。在定义带参数的宏时,宏名与左圆括号之间不允许有空白符,应紧接在一起,否则变成了无参数的宏定义。带参数宏调用提供的实在参数个数必须与宏定义中的形式参数个数相同。
宏定义的有效范围称为宏名的作用域,宏名的作用域从宏定义的结束处开始到其所在的源代码文件末尾。宏名的作用域不受分程序结构的影响。如果需要终止宏名的作用域,可以用预处理指令#undef加上宏名。
宏名一般用大写字母,以便与变量名区别。如有必要,宏名可被重复定义,被重复定义后,宏名原先的意义被新意义所代替。
宏定义代码序列中必须把""配对,不能把字符串""拆开。例如#define NAME "vrmozart不合法,应为#define NAME "vrmozart"。
宏定义代码序列中可以引用已经定义的宏名,即宏定义可以嵌套。
-
多行宏
宏定义在源文件中必须单独另起一行,换行符是宏定义的结束标志,因此宏定义以换行结束,不需要分号等符号作分隔符。如果一个宏定义中代码序列太长,一行不够时,可采用续行的方法。续行是在键入回车符之前先键入符号\,注意回车要紧接在符号\之后,中间不能插入其它符号,当然代码序列最后一行结束时不能有\。注意多行宏在调用时只能单独一行调用,不能用在表达式中或作为函数参数。
-
宏展开
预处理器在处理宏定义时,会对宏进行展开(即宏替换)。宏替换首先将源文件中在宏定义随后所有出现的宏名均用其所代表的代码序列替换之,如果是带参数宏则接着将代码序列中的宏形参名替换为宏实参名。宏替换只作代码字符序列的替换工作,不作任何语法的检查,也不作任何的中间计算,一切其它操作都要在替换完后才能进行。如果宏定义不当,错误要到预处理之后的编译阶段才能发现。
源代码中的宏名和宏定义代码序列中的宏形参名必须是标识符才会被替换,即只替换标识符,不替换别的东西,像注释、字符串常量以及标识符内出现的宏名或宏形参名则不会被替换。例如:
#define NAME vrmozart,
源代码//NAME、/*NAME*/、"NAME"、my_NAME_blog中的宏名都不会被替换。
#define BLOG(name) my_name_blog="name",宏定义代码序列中的宏形参名name也都不会被替换。
如果希望宏定义代码序列中标识符内出现的宏形参名能够被替换,可以在宏形参名与标识符之间添加连接符##,在宏替换过程中宏形参名和连接符##一起将被替换为宏实参名。##用于把宏参数名与宏定义代码序列中的标识符连接在一起,形成一个新的标识符。例如:
#define BLOG(name) my_##name,BLOG(vrmozart)表示my_vrmozart
#define BLOG(name) name##_ blog,BLOG(vrmozart)表示vrmozart_ blog
#define BLOG(name) my_##name##_blog,BLOG(vrmozart)表示my_vrmozart_ blog
如果希望宏定义代码序列中的宏形参名被替换为宏实参名的字符串形式(即在宏实参名两端加双引号"),而不是替换为宏实参名,可以在宏定义代码序列中的宏形参名前面添加符号#。#用于把宏参数名变为一个字符串形式。例如:
#define STR(name) #vrmozart,STR(vrmozart)表示"vrmozart"
当宏参数是另一个宏的时候,需要注意的是宏定义代码序列中有用#或##的宏参数是不会再展开。
-
较长的宏中的逗号运算符
#define ECHO(s) (get(s), puts(s))
ECHO(str); /* 替换为 (gets(str), puts(str)); */
-
宏中的空格
#define f (x) ((x)-1)
解析成为 f代表了,变成了一个无参数的宏
(x) ((x)-1)
但是在宏定义的调用中,空格不影响,也就是说f(3)与f (3)效果是一样的都=2.
-
宏定义中的参数与结果都需要加括号,且宏参数不得有自加或者自减等改变自身操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#define abs(x) x>0?x:-x //当处理 abs(a-b),第二个-a-b明显出错了,所以参数需要用括号包含起来 abs(a-b)---> a-b>0?a-b:-a-b //同样的,返回值也需要括号,如下 abs(a)+1---> a>0? a:-a+1 //当参数和返回值都有括号,还是可能出错 #define max(a,b) ((a)>(b)):(a):(b)) //当参数被调用两次的时候考虑表达式是否会被调用两次或者变化 biggest=x[0]; i=1; while(i<n) biggest=max(biggest,x[i++]) //解析成如下,这里i多加了一次,出错 biggest=((biggest)>(x[i++]):(biggest),(x[i++])) //更正为如下: biggest=x[0]; for(i=1;i<n;i++) biggest=max(biggest,x[i]) |
解决max版本,使用局部变量,当前嵌套的话还是不行的
static int max_temp1,max_temp2; #define max(p,q) (max_temp1=(p),max_temp2=(q),\ max_temp1>max_temp2?max_temp1:max_temp2) |
-
有时候宏展开会很大(反复调用)
Max(a, Max(b, Max(c)))
-
宏不是语句-断言assert
当宏放在 语句中,需要考虑其是否真的按照本意执行
//第一次定义
#define assert(e) if(!e) assert_error(__FILE__,__LINE__)
//尝试如下判断
if(x>0 && y>0)
assert(x>y);
else
assert(y>x);
//看上去没啥子问题,展开一下
if(x>0 && y>0)
if(!(x>y)) assert_error(__FILE__,__LINE__);
else
if(!(y>x)) assert_error(__FILE__,__LINE__);
//还是没问题,仔细配对下 if else
if(x>0 && y>0)
if(!(x>y))
assert_error(__FILE__,__LINE__);
else
if(!(y>x)) assert_error(__FILE__,__LINE__);
//如果给assert 加上{}
#define assert(e) {if(!e) assert_error(__FILE__,__LINE__)}
//编译会报错,因为展开后是这样的,else 前面多了个;
if(x>0 && y>0)
{if(!(x>y)) assert_error(__FILE__,__LINE__);};
else
{if(!(y>x)) assert_error(__FILE__,__LINE__);};
//所以最终运用了 || 的优先级,如果前面的条件成立了,则后面的不执行
#define assert(e) \
((void) ( (e) || assert_error(__FILE__,__LINE__) ))
这里也可以使用 do { }while(0) 包含语句 参考
http://blog.csdn.net/tjxtujingxuan/article/details/40628611
-
宏与 typdef
#define T1 struct foo*;
typedef struct foo *T2;
//类型上,T1和T2 完全相同,但是当同时定义多个变量
T1 a,b;
T2 a,b;
//T1
stuuct foo *a ,b; //一个指针,一个结构体
--------------------------------------------以上摘自C陷阱与缺陷
-
#和##
# 替换宏为字符串
## 连接两个标识符
首先用实参替换形参,将实参代入宏文本中;
然后如果实参也是宏,则展开实参;
最后再继续处理宏替换后的宏文本,若宏文本也包含宏则继续展开,否则完成展开。
但是有个例外,那就是第一步后,将实参代入宏文本后,实参之前如果遇到字符"#"或"##",即使实参是宏,也不再展开实参,而是当作文本处理。
-
#只能修饰带参数的宏,将实参的字符序列(而不是实参代表的值)转换为字符串常量
#define STRING(x) #x #x #x // STRING(abc)…………>"abcabc"
#defien TEXT(x) "class" #x "info" //TEXT(abc)………..>"classabcinfo"
-
##用于合并左右序列,非字符串,所以使用的时候必须保证产生的标识符有意义
#define CLASS(name) class##name // CLASS(sys)------->classsys
#defien ME(x,y) x##y##x //ME(me,to)-------->metome
编译器常量
符合ANSI的预定义宏:
__DATE__:表示当前源文件编译时的日期,格式为:月/天/年(Mmm dd yyyy)。
__FILE__:表示当前正在处理的源文件名称。
__LINE__:表示当前正在处理的源文件的行,可以用#line指令修改。
__STDC__:表示是ANSI C标准。只有在编译器选项指定了/Za,并且不是编译C++程序时,被定义为常整数1;否则未定义。
__TIME__:表示当前源文件的最近编译时的时间,格式为:小时/分/秒(hh:mm:ss)。
__TIMESTAMP__:表示当前源文件的最近修改日期和时间,格式为:Ddd Mmm dd hh:mm:ss yyyy,其中Ddd是星期的缩写。
Microsoft相关的宏:
_ATL_VER:定义了ATL的版本。
_CHAR_UNSIGNED:设置默认的char类型是unsigned的。只有在编译器选项/J指定时才有定义。
__CLR_VER:指定了应用程序编译时的通用语言运行时(CLR)的版本。格式为:Mmmbbbbb,其中M是CLR的主版本,mm是CLR的次版本,bbbbb是build号。
__cplusplus_cli:只有在用/clr,/clr:pure或/clr:safe编译时才有定义。__cplusplus_cli的值是200406。
__COUNTER__:为一个整数,从0开始,每出现一次,其值增加1。可以使用__COUNTER__作为前缀来产生唯一的名字。
__cplusplus只有在编译C++程序时才有定义,一般用于区分C程序和C++程序。
_CPPLIB_VER:在程序中如果包含了任意C++标准库头文件,则_CPPLIB_VER有定义。用于显示正在使用的头文件的版本。
_CPPRTTI:用于标识编译器是否指定了RTTI。如果编译器选项中设定了/GR(打开运行时类型信息机制-RTTI),则_CPPRTTI有定义。
_CPPUNWIND:用于标识编译器是否打开异常处理。如果编译器选项中设定了/GX,则_CPPRTTI有定义。
_DEBUG:用于标识是Debug模式。在编译器指定了/LDd,/MDd或/MTd时才有定义。
_DLL:当编译器选项指定了/MD或/MDd(Multithread DLL)时才有定义。
__FUNCDNAME__:只有在函数内部才有效。返回该函数经编译器修饰后的名字。如果编译器选项中设定了/EP或/P,则__FUNCDNAME__是未定义。
__FUNCSIG__:只有在函数内部才有效,并且返回该函数的签名。一个函数的签名由函数名、参数列表、返回类型、内含的命名空间组成。如果它是一个成员函数,它的类名和const/volatile限定符也将是签名的一部分。在64位操作系统中,__cdecl是默认的函数调用方式。如果编译器选项中设定了/EP或/P,则__FUNCSIG__是未定义。
__FUNCTION__:只有在函数内部才有效。返回该函数未经修饰的名字。如果编译器选项中设定了/EP或/P,则__FUNCTION__是未定义。
_INTEGRAL_MAX_BITS:表示整数类型的最大位数(bits)。
_M_ALPHA:为DEC ALPHA平台定义。(现在已不支持)
_M_CEE:当使用/clr的任意形式(/clr:oldSyntax, 例如/clr:safe)编译时被定义。
_M_CEE_PURE:当使用/clr:pure编译时被定义。
_M_CEE_SAFE:当使用/clr:safe编译时被定义。
_M_IX86:为x86处理器架构定义。当值为300时说明是80386,值是400时说明是80486
_M_IA64:为Itanium处理器家族的64位处理器(IA64)架构定义。
_M_IX86_FP:表示编译器选项/arch的值。0:/arch未指定;1:指定/arch:SSE;2:指定/arch:SSE2
_M_MPPC:为Power Macintosh平台定义。(现在已不支持)
_M_MRX000:为MIPS平台定义。(现在已不支持)
_M_PPC:为PowerPC平台定义。(现在已不支持)
_M_X64:为x64处理器架构定义。
_MANAGED:当编译器选项指定/clr时定义,其值为1。
_MFC_VER:指定MFC版本。例如:0x0700表示MFC version 7。
_MSC_BUILD:表示编译器版本号的修订号部分。修订号是以时期进行分割的版本号的第四部分。例如:如果VC++编译器的版本号是15.00.20706.01,则_MSC_BUILD的值为1。
_MSC_EXTENSIONS:当指定编译器选项/Ze(默认)时有定义,其值为1。
_MSC_FULL_VER:表示编译器的主,次版本号及build号。主版本号是整个版本号的第一部分,次版本号是整个版本号的第二部分,build号是整个版本号的第三部分。例如: 如果VC++编译器的版本号是15.00.20706.01,则_MSC_FULL_VER的值为150020706。可以在命令行键入cl /?来查看编译器的版本号。
_MSC_VER:表示编译器的主,次版本号。例如: 如果VC++编译器的版本号是15.00.20706.01,则_MSC_VER的值为1500。
__MSVC_RUNTIME_CHECKS:当指定编译器选项/RTC之一(/RTCs或/RTCu或/RTC1)时有定义。
_MT:当指定编译器选项/MD或/MDd(Multithreaded DLL)或/MT或/MTd(Multithreaded)时有定义。
_NATIVE_WCHAR_T_DEFINED:当指定编译器选项/Zc:wchar_t(将wchar_t视为内置类型)时有定义。
_OPENMP:当指定编译器选项/openmp时有定义,返回一个表示Visual C++中的OpenMP的日期的整数。
_VC_NODEFAULTLIB:当指定编译器选项/Zl(忽略默认库名)时有定义。
_WCHAR_T_DEFINED:当指定编译器选项/Zc:wchar_t或工程中包含的系统头文件中定义了wchar_t时有定义。
_WIN32:为Win32和Win64应用程序定义。总有定义。
_WIN64:为Win64应用程序定义。
_Wp64:当指定编译器选项/Wp64时有定义。
① #line
#line指令用于重新设定当前由__FILE__和__LINE__宏指定的源文件名字和行号。
#line一般形式为#line number "filename",其中行号number为任何正整数,文件名filename可选。#line主要用于调试及其它特殊应用,注意在#line后面指定的行号数字是表示从下一行开始的行号。
② #error
#error指令使预处理器发出一条错误消息,然后停止执行预处理。
#error 一般形式为#error info,如#error MFC requires C++ compilation。
③ #pragma
#pragma指令可能是最复杂的预处理指令,它的作用是设定编译器的状态或指示编译器完成一些特定的动作。
#pragma一般形式为#pragma para,其中para为参数,下面介绍一些常用的参数。
#pragma once,只要在头文件的最开始加入这条指令就能够保证头文件被编译一次。
#pragma message("info"),在编译信息输出窗口中输出相应的信息,例如#pragma message("Hello")。
#pragma warning,设置编译器处理编译警告信息的方式,例如#pragma warning(disable:4507 34;once : 4385;error:164)等价于#pragma warning(disable:4507 34)(不显示4507和34号警告信息)、#pragma warning(once:4385)(4385号警告信息仅报告一次)、#pragma warning(error:164)(把164号警告信息作为一个错误)。
#pragma comment(…),设置一个注释记录到对象文件或者可执行文件中。常用lib注释类型,用来将一个库文件链接到目标文件中,一般形式为#pragma comment(lib,"*.lib"),其作用与在项目属性链接器"附加依赖项"中输入库文件的效果相同。
Sub 设置代码表格() ' author: code4101 ' 设置代码表格 宏 ' ' ' 背景色为morning的配色方案,RGB为(229,229,229) With Selection.Tables(1) With .Shading .Texture = wdTextureNone .ForegroundPatternColor = wdColorAutomatic .BackgroundPatternColor = 15066597 End With .Borders(wdBorderLeft).LineStyle = wdLineStyleNone .Borders(wdBorderRight).LineStyle = wdLineStyleNone .Borders(wdBorderTop).LineStyle = wdLineStyleNone .Borders(wdBorderBottom).LineStyle = wdLineStyleNone .Borders(wdBorderVertical).LineStyle = wdLineStyleNone .Borders(wdBorderDiagonalDown).LineStyle = wdLineStyleNone .Borders(wdBorderDiagonalUp).LineStyle = wdLineStyleNone .Borders.Shadow = False .AutoFitBehavior (wdAutoFitContent) '自动调整大小 End With With Options .DefaultBorderLineStyle = wdLineStyleSingle .DefaultBorderLineWidth = wdLineWidth050pt .DefaultBorderColor = wdColorAutomatic End With
' 段落无首行缩进,行间距为固定值12磅 With Selection.ParagraphFormat .LeftIndent = CentimetersToPoints(0) .RightIndent = CentimetersToPoints(0) .SpaceBefore = 0 .SpaceBeforeAuto = False .SpaceAfter = 0 .SpaceAfterAuto = False .LineSpacingRule = wdLineSpaceExactly .LineSpacing = 12 .KeepWithNext = False .KeepTogether = False .PageBreakBefore = False .NoLineNumber = False .Hyphenation = True .FirstLineIndent = CentimetersToPoints(0) .OutlineLevel = wdOutlineLevelBodyText .CharacterUnitLeftIndent = 0 .CharacterUnitRightIndent = 0 .CharacterUnitFirstLineIndent = 0 .LineUnitBefore = 0 .LineUnitAfter = 0 .MirrorIndents = False .TextboxTightWrap = wdTightNone .AutoAdjustRightIndent = True .DisableLineHeightGrid = False .FarEastLineBreakControl = True .WordWrap = True .HangingPunctuation = True .HalfWidthPunctuationOnTopOfLine = False .AddSpaceBetweenFarEastAndAlpha = True .AddSpaceBetweenFarEastAndDigit = True .BaseLineAlignment = wdBaselineAlignAuto End With ' 清除原有的段落底纹 Selection.ParagraphFormat.Shading.BackgroundPatternColor = wdColorAutomatic End Sub
Sub 输入连续数字() ' author: code4101 行数 = InputBox("请输入代码终止行数", "输入行数", "50") For i = 1 To 行数 - 1 Selection.TypeText Text:=i Selection.TypeParagraph Next Selection.TypeText Text:=行数 End Sub |