寥寥数笔,记录我的C语言盲点笔记,只为曾经经历过,亦有误,可交流。
1.typedef来定义一个函数指针类型的方法,定义一个新的函数指针类型。
:建立一个类型别名的方法很简单,在传统的变量声明表达式里用类型名替代变量名,然后把关键字typedef加在该语句的开头”。
typedef int (*PFUN)(): 定义PFUN这个函数指针类型,由编译器自动来完成,比如PFUN pfun;定义了一个函数指针,类似于抽象出一种新的变量类型。
typedef unsigned short (*pTaskEventHandlerFn)( unsigned char task_id, unsigned short event );
pTaskEventHandlerFn tasksArr[];//申明函数指针的数组
使用typedef将一个类型名字替代原先申明时的变量名,然后类型名可以用来申明定义变量。
2.有初始化的数组定义可以省略方括号中的数组大小。
例如,下面的代码中数组定义为5个元素:
int a[]={2,4,6,8,10};
编译时必须知道数组的大小。通常,声明数组时方括号内的数字决定了数组的大小。有初始化的数组定义又省略方括号中的数组大小时,编译器统计花括号之间的元素个数,以求出数组的大小。
例如,下面的代码产生相同的结果:
static int a1[5]={1,2,3,4,5};
static int a2[]={1,2,3,4,5};
2,栈顶一般为低地址,函数入栈一般参数从右往左进行。
3.for(a;b;c){e}执行顺序:
先进行a, 然后是b, 执行e, 在执行c.进一步执行b,执行e,c.直到b不再满足退出循环。
4.union数据类型,内存是重叠的,完全一样
typedef union {
uint32 time32;
uint16 time16[2];
uint8 time8[4];
} osalTime_t;
对time32的赋值后,访问其他类型时依旧是原来的内存。三个成员占据一样的数据空间,起始地址都是一样的,一旦一个成员数值变化就会出现其他成员取值也发生变化
5.inline内联函数总结
内联函数具有代码量小且直接编译进可执行文件中,不进行call调用,也就是在不同的文件的函数如果调用内联函数,则不同的函数都会出现该内联函数的可执行代码。
但是,如果不重复调用的话,内联函数带来的好处就是执行更加快速,无需再call,以及函数参数的出入栈SP,保护栈帧,跳出函数,清空栈帧等各种耗时。起到快速执行的效果。故内联函数的代码量都较小。
总之是以增加执行文件大小来获取程序执行速度的提高。
6.(int *)0,表示指向一个整形的地址,地址值为0,即空指针所在的地方,该处一般不允许写操作,以免破坏系统,但可以读取。
7.如果一个函数要被其他文件中函数使用,定义时加extern关键字,在没有加extern和static关键字时,一般编译器会默认是extern类型的,因此你在其他函数里调用也可以使用。
8.结构体之offsetof宏详细解析 ,#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*)0)->MEMBER) (include/linux/stddef.h)
9.32位机读取一个字节,实际底层的汇编做了进一步的处理。
比如读取一个字节,实际返回的是一个32位,经过屏蔽移位取得你要的地址所在的数。写入时也一样,会先保持4字节其他字节不变,然后单独把要写入的字节做改变。由编译器根据你的数据类型来完成实际的实现。
32字节的读取最为高效。
10.中断发生时,未完成服务程序的处理时,将会忽略挂起Pending,优先级高的中断可以获取中断使用权。
另一个同样的中断发生,直到当前中断处理完成,才允许再次中断进入。
11.switch case误区。
switch case往往对不同的命令进行分类操作,但是如果出现遇到对应case做完处理后,如果没有遇到break就会接着默认执行下面的case内容(不再去case,因为只有刚进入switch才会去匹配case,已经进入后,case匹配忽略)。直到遇到带break后为止跳出case,否则一直执行完。
因为只在第一次才做switch判断跳到对应的内容去执行。所以,好的程序基本是需要break的,但是有些情况(多个情况对应处理的内容要一样,即前面的几个类型可以不做处理,不需要break,直到最后一个做处理,实现多个类型执行相同的功能,但不增加代码量,更直接)。
总之,只有在第一次才做判断与选择,进入以后就是安装顺序执行,不break会一直执行到switch结束
12 C语言中的感叹号!逻辑取反,不同于~的按位取反
!=:表示不等于。!(1)表示为0,!(0)表示为1,有取反之意,但只能用在0和1上面。不等于0的会变为0,等于0的变为1
13.C语言+和<<遇到的一个BUG问题。
在这里+的优先级高于<<左右移。所以比如a<<8+b,实际是从右往左赋值,8+b相加后来移动a,所以不实现两个字节合并为一个16位的数据。故要加(a<<8)+b才能实现本来的意图,这个误区需要注意。
14.:#define PINMUX0_31_28 0x00000001u,0x00000001UL
u表示无符号,UL unsigned long int 32位,unsigned long long 64位
15数组指针和指针数组的区别
数组指针,为一个指针,int (*a)[10];指向一个数组,类似于函数指针
指针数组,为一个数组,int* a[10];类似于指针函数,返回的是一个指针。
16.window下面绝对路径的文件需要用双斜杠来表示\\
17 printf("c=%#x\n",c);什么意思?
%#表示的输出提示方式,如果是8进制,在前面加0,如果是十进制,不加任何字符,如果是十六进制,会自动加上0x
18 静态变量在编译的时候初始化,所以初值必须是常量(可以是常数、定义成常数的宏、C++编译器中用const修饰的常量),静态变量不能用变量赋初始值,但在运行时是可以用变量赋值的。
19 assert函数,用于判断是否为真,满足就继续执行,不然直接终止程序的运行
20.做到4个字节对齐,使用如下方式:
((CSL_CacheRegsOvly)CSL_CACHE_REGS)->L1DIWC = ((byteCnt+3)>>2);
字节数不为4的倍数时,需要多保留一个字的数量,故加3,1+3,2+3,3+3.4字节对齐的方式,
获得4字节对齐后的字节数字:
byte_num = (byte + 3) & 0xfc; 这样字节数必为4的倍数。
21 for循环中如果break,则i++不再执行。
22.temp = (temp+1)&7.表示temp从0到7变化,循环。类似于temp=8时赋值回0.
23.unsigned char *p;
unsigned char a[];
p=a;这样正确。
unsigned int p1= a;错误,因为是指针类型赋值给了整形的数据类型,类型不匹配。
unsigned char * p[10]:申明一个指针数组,10个指针,每个指针指向char类型的空间。
unsigned char (*p)[10]申明一个数组指针,指向含有10个char类型的空间,指针维护一个10的空间,偏移1加10个空间。
24.
多个if会进行分支处理,分别判断后进行是否执行。
而if 和多个else if等配合时,一旦出现某个满足条件时,不再执行下面的else if的判断,确保只进入一个分支。
25. char a[3][4]. char **p =a;出现编译错误,类型不匹配不能赋值
因为a表示的是一个指向一维数组的指针,即char (*a)[3]这个类型.a+1是会偏移3个单位。
p是一个指针的指针,称为二级指针。所以两者类型不一样。
26.数组名取地址
在《C和指针》p142中说到,在以下两中场合下,数组名并不是用指针常量来表示,就是当数组名作为sizeof操作符和单目操作符&的操作数时。 sizeof返回整个数组的长度,而不是指向数组的指针的长度。 取一个数组名的地址所产生的是一个指向数组的指针,而不是一个指向某个指针常量的指针。
所以&a后返回的指针便是指向数组的指针(类似于(*p)[3]),跟a(一个指向a[0]元素的指针)在指针的类型上是有区别的。&a+1是偏移一个数组sizeof大小的尺寸,因为&a是指向数组指针的地址,是一个数值指针。
但是作为形参时,一级数组名就转为了一个普通的一级指针而已。
27. 二维数组名不能简单的理解为2级指针,而是要从数组的系统结构上去了解。
个人认为二维数组名其实就是一个指针,但是他指向的数据类型是一维数组的这么个数据类型。即数值指针,其实可以理解为功能和二维数组名类似。至于数据访问,完全是编译器来自动完成栈区的访问。
28.数值名不能修改,是因为在栈区域内已经定好了ebp,基地址。访 问数组都是基于这个ebp来进行的。所以,给数组名赋值变得没有意义,肯定不允许修改ebp的。所以在编译阶段就报错。
29.
fseek和发tell配合来完成确定文件大小的过程。
fseek(fPtr,0,SEEK_END);
file_size = ftell(fPtr);
fseek(fPtr,0,SEEK_SET),位置指针复位进行读写操作
30.两数交换不使用第三个缓冲区,三条代码的方法:主要通过运算符来实现。
A = A^B;
B = A^B;
A = B^A;
A = A+B;
B = A-B;
A = A-B;
c语言编写经验逐步积累3