当前位置: 首页 > news >正文

华宇网站建设泉州企业网站维护定制

华宇网站建设,泉州企业网站维护定制,新人做外贸流程,深圳市住建局官网入口C语言高级部分 一、内存大话题1.0、内存就是程序的立足之地#xff0c;体现内存重要性。1.1、内存理解#xff1a;内存物理看是有很多个Bank#xff08;就是行列阵式的存储芯片#xff09;#xff0c;每一个Bank的列就是位宽 #xff0c;每一行就是Words#xff0c;则存…C语言高级部分 一、内存大话题1.0、内存就是程序的立足之地体现内存重要性。1.1、内存理解内存物理看是有很多个Bank就是行列阵式的存储芯片每一个Bank的列就是位宽 每一行就是Words则存储单元数量行数words×列数位宽×Bank的数量通常也用M×W的方式来表示芯片的容量或者说是芯片的规格/组织结构。M是以位宽为单位的总容量单位是兆 W代表位宽 单位是bit。计算出来的芯片容量也是以bit为单位但用户可以采用除以8的方法换算为字节Byte。比如8M×8这是一个8bit位宽芯片有8M个存储单元总容量是64Mbit8MB。 1.2、c语言中其实没有bool类型以0表示假非0表示真则在内存存储是以int型存放的。如果想要表示真假可以用int/char型做替换在c中就有bool xtrue/false;   1.3、内存对齐内存对齐提高访问效率速度编译器一般默认是4字节对齐1.4、char/int/short/long/float/double型放在内存的长度和解析作用。int *0使0地址指向一个int型。又比如0000111010101可以解析成int型也可以解析成float型。   1.5、Linux内核是面向对象的而c语言是面向过程的但可以用结构体内嵌指针变成面向对象。如  struct student { int age;    //变量 int lenth;   //将相当于一个类有变量有函数 char *name; void (*eat)(void);  //函数指针}1.6、栈的理解 1 运行时自动分配自动回收栈是自动管理的程序员不需要手工干预。方便简单。表现在汇编代码编译时会自动编译成汇编码实现函数调用完立即改变栈顶 2 反复使用栈内存在程序中其实就是那一块空间程序反复使用这一块空间。(硬件上有个寄存器用来存放栈的栈顶地址栈是有大小的空间)3 脏内存栈内存由于反复使用每次使用后程序不会去清理因此分配到时保留原来的值。4 临时性函数不能返回栈变量的指针因为这个空间是临时的5 栈会溢出因为操作系统事先给定了栈的大小如果在函数中无穷尽的分配栈内存总能用完。栈的操作怎么出栈怎么入栈是由具体硬件来干预程序员只要明白原理就可以了 但是要给相应的栈寄存器赋值。当调用函数时变量会自动放在栈中入栈当函数调用完后栈会自动出栈.( 6 )  栈的 发展有四种情况满增栈满减栈空增栈空减栈至于是那种要根据编译器决定而s5pv210 是满减栈。1.7、堆的理解 1操作系统堆管理器管理堆管理器是操作系统的一个模块堆管理内存分配灵活按需分配。 2大块内存堆内存管理者总量很大的操作系统内存块各进程可以按需申请使用使用完释放。     3脏内存堆内存也是反复使用的而且使用者用完释放前不会清除因此也是脏的。4临时性堆内存只在malloc和free之间属于我这个进程而可以访问。在malloc之前和free之后都不能再访问否则会有不可预料的后果。5程序手动申请释放手工意思是需要写代码去申请malloc和释放free。记住不要把申请的地址给搞丢了 不然自己用不了也释放不了申请一段内存可以是: malloc(10*sizeof ( int ) ); 原型void *malloc(size_t size);  //指针函数 size_t是宏定义int 都是便于可移植性 返回一个内存地址void *可以看出希望申请的内存用来存放什么就强制类型什么。     calloc( 10,sizeof ( int ) ); 原型void *calloc(size_t nmemb, size_t size);// nmemb个单元每个单元size字节void *realloc(void *ptr, size_t size);// 改变原来申请的空间的大小的ptr是原来申请内存的指针size是想要重新申请内存的大小使用就是*p112   ;    *(P3)110申请失败返回NULL,申请成功返回一个地址申请之后一定要检验NULL!p用完一定要   free ( p )  释放后不是不能用是不应该使用了。可以给它“洗盘子‘pNULL;  其实申请的内存并不能真正改变大小原理是先重新申请一段内存然后把原来申请的内存上的内容复制到新的内存上然后释放掉原来的内存返回新的指针。(6) 在申请内存时malloc0其实也是成功的因为系统规定少于一定数目的大小都申请规定的大小如在win32系统下申请少于32字节的地址最后申请到的空间是32字节。1.8、内存里的数据  1代码段存放代码二进制、常量char *plinux,则”linux“存放在代码段是不可更改的 2 数据段:  存放非0全局变量、静态局部变量局部只属于函数的不是整个程序的3 bss     :  存放为0的全局变量/为0的静态局部变量、存放未初始化全局变量/静态局部变量注意const int a9; 有两种存放方式第一种确实存放在代码段让a不能修改第二种是仍然存放在数据段中让编译器来判断如果有改变的代码就会报错。 至于那种是不确定的像单片机就属于第一种。1.9、《1》一个源文件实际上是以段为单位编译成连接成可执行文件a .out ;这个可执行文件总的说是分为数据段代码段自定义段数据段还可以细分成 .bbs 段。而杂段会在执行的时候拿掉。所以a.out分为杂段数据段存放的是非0全局变量.bbs段代码段。《2》内存实际上被划分了两大区域一个是系统区域另一个是用户区域而每一个区域又被划分成了几个小区域有堆栈代码区.bbs区数据区存放的是非0全局变量。《3》对于有操作系统而言  当我们在执行a.out可执行文件时执行这个文件的那套程序会帮我们把杂段清掉然后把相应的段加载到内存对应的段。对于裸机程序而言我们是使用一套工具将a.elf的可执行程序给清掉了所有段的符号信息把 纯净的二进制做成.bin格式的烧录文件。所以我们加载到内存的程序是连续的也就是说代码段和数据段、.bbs段都是连续的。当然栈空间是我们自己设置的。而且在裸机中我们不能使用malloc函数因为我们使用的只是编译器、连接器工具没有集成库函数没有定义堆空间区。《4》大总结多程序运行情况 在Linux系统中运行cdw1.out时运行这个文件的那套程序会帮我们把相应的段加载到内存对应的段。然后操作系统会把下载到内存的具体物理地址与每条命令32位的链接地址映射到TTB中一段内存空间当我们又运行cdw2.out时同样也像cdw1.out一样加载进去并映射到TTB表中。而且这两个.out文件默认都是链接0地址逻辑当cpu发出一个虚拟地址Linux中程序逻辑地址通过TTB查找的物理地址是不一样的。所以对于每一个程序而言它独占4G的内存空间看不到其他程序。二、位操作2.1  ~(0u)是全12.2 位与     位或 |            位取反~       位异或^2.3、位与、位或、位异或的特点总结位与任何数其实就是1或者0与1位与无变化与0位与变成0位或任何数其实就是1或者0与1位或变成1与0位或无变化位异或任何数其实就是1或者0与1位异或会取反与0位异或无变化2.4、左移位 与右移位  C语言的移位要取决于数据类型。对于无符号数左移时右侧补0相当于逻辑移位对于无符号数右移时左侧补0相当于逻辑移位对于有符号数左移时右侧补0叫算术移位相当于逻辑移位对于有符号数右移时左侧补符号位如果正数就补0负数就补1叫算术移位2.5、小记常与  1 拿来 做位运算。让他取反、移位 得到想要的数。2.6、直接用宏来置位、复位最右边为第1位。 置位置1复位置0  #define SET_NTH_BIT(x, n)  (x | ((1U)(n-1)))#define CLEAR_NTH_BIT(x, n) (x ~((1U)(n-1)))三、指针—精髓 3.1  printf(%p \n); 其中%p表示输出一个指针就是指针变量其存放的那个地址可以理解为输出一个地址。3.2  int* p1, p2 ;     等同于 int *p1;   int  p2;  int 其不能改变因为”是一个常数。3.3 ( 代码规范性 )在定义指针时同时赋值为NULL在用指针时先判断它是不是NULL。尤其是在malloc申请内存后free(p);则一定要让pNULL3.4  C/C中对NULL的理解 #ifdef _cplusplus// 定义这个符号就表示当前是C环境#define NULL 0// 在C中NULL就是0#else#define NULL (void *) 0// 在C中NULL是强制类型转换为void *的0#endif3.5、修饰词const (修饰变量为常量应该理解为不应该去变它当作常量而并非永远不能改变,当然要看具体运行环境在gcc,const 这种就可以采用指针方式修改但是在VC6.6中就不可以修改)其虽然是当作常数但是仍然存放在数据段中用指针仍然可以改变值。第一种const int *p;   第二种int const *p;第三种int * const p;第四种const int * const p;3.6、 数组 int a[2]; 其中a是指首元素的首地址a是整个数组的收地址数组指针其这个指针指向一个数组他们的值是一样的但意义不一样可以参照 int a; int *pa; 来理解。数组和指针天生姻缘在于数组名int a[3]; int* pa;是可以的但是 int *pa;就会报错尽管他们的值是一样的但意义不一样所以是不允许的除非强制类型转换。在访问时是a[0],其实编译器会把它变成*a0)的方式只是用a[0]看起来更方便封装了一下而已实质还是指针。3.7、 siziof()是一个运算符测试所占内存空间如 int a[100] ;sizeof(a)400; 与strlen( )要有所区别他是测字符串实际长度的不包括‘\0‘如果给strlen传的参数不是一个字符串则它会一直去找直到 找到第一个 ‘\0’然后再计算其长度。如 char a[]chen; char *pa; 则strlen(p)4; 3.8、 当数组作为一个形参时其实参是一个数组名也可以是指针其本质就是指针意义是首元素的首地址则传过去只影响形参的第一个元素。形参数组的地址被实参数组地址所绑定实参的大小会丢失所以往往会传一个int num 大小进去。3.9、 结构体做为形参时应尽量用指针/地址方式来传因为结构体变量有时会占很大效率很低。4.0、 int *pu; p存放的是变量u的地址而p的意思就是变量p本身的地址。4.1、当要传参的个数比较多时我们可以打包成一个结构体传参的个数越多其开销就更大 4.2    一个函数作用其实就是输入输出参数可以作为输入返回可以作为输出但是当要返回多个输出时这时候就不够用了所以常常返回值用来判断程序又没有出错而参数       就是当作输入输出的输入时可以加const表示它没必要去修改而输出都是指针因为要改变它的值只能采用地址传递这种方式。比如char *strcpy(char *dest,const char *src)四、C语言复杂表达式4.1、在表达式中要看符号的优先级和结合性。4.2、在理解内存时内存0地址在最底下至上地址逐渐增加。4.3、int *p;是定义的一指针变量p而int  ( *p)[4]也是一个指针变量p也可以这样想凡是遇到*p什么的判断他是指针后就可以说他是指针变量包括函数指针。4.4、一个函数 int maxint a ,int b;   则他的函数指针是 int ( *p  ) (int ,int );其意思就是定义这个类型的函数指针变量p; pmax是赋值引用是p();则相当于max()调用这个函数。  函数指针必须和原函数的类型一样。4.5 函数指针其实就是为了做结构体内嵌指针的这样就构成了高级语言中的类。再一个就是上述4.4中pmax;也是可以的它和pmax,值和意义都是一样的这个和数组有所区别数组的a和a的值虽然一样但是意义完全不一样。int a[4];a有两层意思第一层是数组名a表示整个数组的地址第二层表示首元素的首地址。4.6  int (*p[4])(int ,int)其意思是函数指针数组一个4长度的数组里面存了4个函数指针。4.7 printf在做输出时其机制是缓冲行来输出即当遇到一个\n后再打印出来即使再多printf没有遇到\n,都不是一个一个打印。\r是回车\n是换行前者使光标到行首后者使光标下移一格通常敲一个回车键即是回车又是换行\r\n。Unix中每行结尾只有“换行即“\n”Windows中每行结尾是“换行回车”即“\r\n”Mac中每行结尾是“回车”。scanf();里面不要加\n符。4.8 在一个c文件中有时候会多次引入一个.h文件所以在写.h文件时要写 #ifndef _FINE_  #define _FINE_ XXXXXXXXXXXXXXXXXXX#endif 4.9、typedef int *intType; const  intType  p,其意思是指针p为const 4.9.1 对于typedef的定义如typedef const int cdw;  可以这样理解typedef就是给一个类型区别名的那么系统会自动识别该类型如果typedef const int char 则就报错。4.9.2 在开发中经常会typedef int  int32_t ; typedef short int16_t; 这样做的目的是便于在不同平台下的移植如果当在另一个平台下int 是64位的但是我的项目中都是用的int32_t;  所以只需要修改int32_t就可以了我可以让他typedef short  int32_t;这样我只更改一次其余的都改了做到一改全改。4.9.3  int **p; int *a[4]; pa;可以这样理解首先它是指针数组既然是数组则a即表示数组名又表示首元素的首地址a[0]是一个一重指针而a是a[0]的地址             那么a就是一个二重指针{  一重指针的地址就是二重指针变量所以有pa;  而 int a[4][3] ,a和一维数组的意思是一样的如 int a[3][6],int *p ;pa[0];所以不能pa,int *a[3][3],int **p;pa[0];} 4.9.4、二维数组是为了简化编程平面型。数组以下标示方式访问其实是编译器封装起来的实质是指针访问。int (*p)[5];   int a[2][5];则有  pa; 关键是要把二维数组抽象成n行n列         用指针访问方式理解二维数组可以看作是一个方格子的矩阵比如a[2][5],那么就是2行5列的10个小格子第一行可以收纳起来变成一个指向一维数组的指针第二行也是如此 这样收纳后就变成了一个新的数组a[2]每一个格子存放的是它收纳的第一个元素的地址如a[0]存放的是第一行第一列元素的地址“a”[1]存放的是第二行第一列的地址 再与一维数组相联系一维数组名即表示数组名又表示数组第一个元素的地址所以a[2][5]中的a表示“a[2]数组第一个元素的地址那么再把pa;层层推递pi表示 指向第几行的地址*pi表示取第几行的值而这个值存放的是第几行一列元素的首地址*pij 表示指向第几行第几列的地址最后在引用这个地址**pij 就表示第几行第几列的值了。  一重指针-----------一维数组  二重指针-----------指针数组  数组指针-----------二维数组 函数指针-----------普通函数五、数组字符串结构体共用体枚举5.1、c语言中定义一个字符串: char a[6]{l,i,n,u,x,\0};    \0的字符编码为0就是NULL;也就是说内存中遇到0翻译成字符是就是\0,或这是NULL;char a[6]linux;char *plinux;5.2、 sizeof(a)6是运算符其意思是所占空间大小包括字符串后面的‘\0,strlen(a5是一个函数其意思是字符串的长度。strlen( p)其中p只有是字符指针变量才有意义       它的形参是数组变量是没有意义的因为strlen是根据什么时候遇到 \0,才结束测试字符串的长度就算数组不初始化也是有长度的。char *plinux; sizeof(p)永远等于4因为p是指针变量存的是地址。所以总结sizeof()是拿来测数组的大小strlen是拿来测试字符串的长度。5.3、结构体用 . 或者是 -访问内部变量其实质是用的指针访问。如  struct student {       int a;double b;char c;}s1;则s1.a 12;实质就是int *p(int *) s1*p12    首先a是int 型所以是强制类型 int * 其次是就是算地址然后强制类型地址应该是int 型然后加减不然的话系统s1.b12.2;实质就是double *p (double *) ((int)s14),*p12.2;   不知道是以int 型加减还是以float型加减还是以char型加减所以 应当 ints1; 而且因为地址是s1.cc;实质就是 char *p(char *) ((int)s112); *pc;                  4字节的所以必须是int型。5.4、对齐方式 1猜测如果是32位系统那么编译器默认是4字节对齐64位系统那么编译器默认是8字节对齐因为32位或64位一次性访问效率是最高的。 21结构体首地址对齐编译器自身帮我们保证会给它分配一个对齐的地址因为结构体自身已经对齐了那么第一个变量也就自然对齐所以我们才可以想象成第一个变量从0地址存放        2结构体内部的各个变量要对齐。3整个结构体要对齐因为定义结构体变量s1时再定义变量s2时如果s1没有对齐就坑了s2,所以也要保证整个结构体对齐。无论是按照几字节对齐我们都可以联想到内存实际的安排。1字节对齐那么不管int float double 类型在每4个格子的内存挨着存放。2字节对齐也是一样的想法举一个列子如果第一个变量是char 型第二个变量是int型那么0地址存放char型1地址空着2地址存放int型地址部分3地址存放int型地址部分然后上排最右4、5地址存放int型高址部分。4字节对齐如果第一个变量是char型第二个变量是int型那么0地址存放char型123地址空着从4地址开始存放int最后给变量分配完内存空间后必须要保证整个结构体对齐下一个结构体的存放起始地址是n字节对齐整数倍如是4字节对齐那么最后short算成4字节 以保证整个结构体对齐。整个结构体对齐如2字节对齐2的整数倍只要是0、2、4地址就行了如果是4字节对齐4的整数倍就必须是0、4地址。8字节对齐8的整数倍3猜测4字节/8字节其实是针对int型/double型的比如0地址是char型那么4字节对齐int型、float型就必须从4地址开始存放那么8字节对齐int型就必须从4地址存放double型就必须从8地址开始存放.小于几字节对齐的那些如char型和short型只要能按照规则存放就行了。4对齐命令1需要#prgama pack(n)开头以#pragma pack()结尾定义一个区间这个区间内的对齐参数就是n。(不建议使用)如s1占5个字节s2占8字节默认#pragma pack(1)struct stu1{结构体本身以及变量 对齐规则2字节对齐2的整数倍只要是0、2、4地址就行了4字节对齐4的整数倍就必须是0、4地址8字节对齐8的整数倍就必须是0、8、16char c;int a;//short d;}s1;struct stu2  {char c;int a;//short d;}s2;2gcc推荐的对齐指令__attribute__((packed))  __attribute__((aligned(n)))在VC中就不行没有定义这个命令(1)__attribute__((packed))使用时直接放在要进行内存对齐的类型定义的后面然后它起作用的范围只有加了这个东西的这一个类型。packed的作用就是取消对齐访问。(2)__attribute__((aligned(n)))使用时直接放在要进行内存对齐的类型定义的后面然后它起作用的范围只有加了这个东西的这一个类型。它的作用是让整个结构体变量     整体进行n字节对齐注意是结构体变量整体n字节对齐而不是结构体内各元素也要n字节对齐内部元素按照默认对齐方式5.5、offsetof宏#define offsetof( TYPE, MEMBER) ((int) ((TYPE *)0)-MEMBER)   (1)offsetof宏的作用是用宏来计算结构体中某个元素和结构体首地址的偏移量其实质是通过编译器来帮我们计算。(2)offsetof宏的原理我们虚拟一个type类型结构体变量然后用type.member的方式来访问那个member元素继而得到member相对于整个变量首地址的偏移量。(3)学习思路第一步先学会用offsetof宏第二步再去理解这个宏的实现原理。(TYPE *)0  这是一个强制类型转换把0地址强制类型转换成一个指针这个指针指向一个TYPE类型的结构体变量。 实际上这个结构体变量可能不存在但是只要我不去解引用这个指针就不会出错。((TYPE *)0)-MEMBER(TYPE *)0是一个TYPE类型结构体变量的指针通过指针指针来访问这个结构体变量的member元素然后对这个元素取地址又因为 改地址是从0地址开始算的所以这个地址就是相对起始地址的偏移量。5.6  container_of宏 #define container_of(ptr, type, member) ({\const typeof(((type *)0)-member) * __mptr (ptr);\(type *)((char *)__mptr - offsetof(type, member)); })      两条语句然后用{ } \表示提示编译器本行因为屏幕不够链接下一行。用#也就是宏定义时如果本行不够要用 \ 提示编译器接着是下一行的。必须要用   \ 猜测因为宏定义一行就算结束了。(1)作用知道一个结构体中某个元素的指针反推这个结构体变量的指针。有了container_of宏我们可以从一个元素的指针得到整个结构体变量的指针继而得到结构体中其他元素的指针。(2)typeof关键字的作用是typepef(a)时由变量a得到a的类型typeof就是由变量名得到变量数据类型的。(3)这个宏的工作原理先用typeof得到member元素的类型定义成一个指针然后用这个指针减去该元素相对于整个结构体变量的偏移量偏移量用offsetof宏得到的减去之后得到的就是整个结构体变量的首地址了再把这个地址强制类型转换为type *即可。5.7 p是一个地址intp6 和char *6效果是一样的第一种是将地址p当作int型加减第二种是将地址p做为char *指针他每次加减都是一字节一字节相加减的如果是 int *P6,那么他每次加减都是按照4字节一跳。就相当于加了4*65.8  小端模式变量的高地址存放在高地址低地址存放在低地址   通信模式也要分大小端先发送/接受的是高地址还是低地址大端模式变量的高地址存放在低地址低地址存放在高地址测试有用共用体 union 和指针方式来测试基本思路是让 int a1; 看低地址 char 是0还是1 变量都是从地址开始存放只是变量的高地址和低地址先存放谁不确定。不能用位与来测因为存放和读取都是按照某一个方式来的结果永远都是一样的。int a1; char b(char)a;这种方式不可以测试因为不管大小端它都以变量a的低地址部分赋给b;5.9、枚举类型(int型)                                                         这样写默认从第一个常量开始是01234.........也可以自己赋值,但是每一个值是不一样的否则逻辑上出错。typedef enum workday{MON, // MON 1;   TUE,WEN,THU,FRI,}DAY;typedef enum weekend{SAT,SUN,}DAY;*// /错误2枚举成员重名编译时报错redeclaration of enumerator ‘MON’typedef enum workday{MON, // MON 1;TUE,WEN,THU,FRI,}workday;typedef enum weekend{MON,SAT,SUN,}weekend;六、C语言宏定义与预处理、函数和函数库看博客strcyp原函数6.1、源码.c-(预处理)-预处理过的 .i 文件-(编译)-汇编文件.S-(汇编)-目标文件.o-(链接)-elf可执行程序预处理用预处理器编译用编译器汇编用汇编器链接用链接器这几个工具再加上其他一些额外的会用到的可用工具合起来叫编译工具链。gcc就是一个编译工具链。1预处理的意义(1)编译器本身的主要目的是编译源代码将C的源代码转化成.S的汇编代码。编译器聚焦核心功能后就剥离出了一些非核心的功能到预处理器去了。(2)预处理器帮编译器做一些编译前的杂事。如(1)#include#include 和#include 的区别(2)注释(3)#if #elif #endif#ifdef(4)宏定义备注 gcc中只预处理不编译的方法 -o生成可执行文件名    -c只编译不链接   -E 只预处理不编译   -I ( 是大i,不是L )编译时从某个路径下寻找头文件  . /当前   (1)gcc编译时可以给一些参数来做一些设置譬如gcc xx.c -o xx可以指定可执行程序的名称譬如gcc xx.c -c -o xx.o可以指定只编译不连接也可以生成.o的目标文件。(2)gcc -E xx.c -o xx.i可以实现只预处理不编译。一般情况下没必要只预处理不编译但有时候这种技巧可以用来帮助我们研究预处理过程帮助debug程序。(3)链接器链接的时候是把目标文件二进制通过有序的排列组合起来如 star.s  main.c   led.c 这三个源文件分别被编译成三个目标文件  每个目标文件有很多函数集合。链接的时候会根据运行思路把这些杂乱的函数给排列组合起来不是把目标文件简单的排列组合。(4)当生成可执行程序之后这个可执行程序里面有很多符号信息有个符号表里面的符号与一个地址相对应如 函数名max对应一个地址虽然这个程序有符号信息但是为什么还是可以执行呢因为如windows的exe程序有专门的一套程序来执行这个.exe 文件就好比压缩文件就有一套 “好压”的软件然后去压缩执行.rar .zip的文件而这套程序就把这些符号信息给过滤掉然后得到纯净的二进制代码最后把他们加载到内存中去。 (5) debug版本就是有符号信息而Release版本就是纯净版本的。可用strip工具 strip是把可执行程序中的符号信息给拿掉以节省空间。Debug版本和Release版本objcopy由可执行程序生成可烧录的镜像bin文件。 6.2、预处理 1头文件有”“是本目录去找找不到就去库头文件找和   只到库头文件去找库头文件可以自己制作用  -I ( 是大i,不是L )参数去寻找路径。头文件在预处理时会把文件的内容原封不动的赋值到 c 文件里面。 2注释在预处理时把注释全部拿掉。        注意#define zf 1   再判断 #undef zf 2   时也是通过的。其意思是有没有定义过zf.3条件编译当作一个配置开关   #define NUM     表示定义了NUM,则执行下一条语句且NUM用空格替代而且预处理会删掉条件编译留下正确的执行语句。4宏定义#define cdw 1  在预处理阶段会替代那些宏可以多重替代宏也可以表示多个语句如 #define cdw printf(cdw\n) ; printf(zf\n); cdw这条语句会直接展开还有带参宏#define max(a,b)  (ab)  注意的是带参宏一定要 不然有时候会引起错误每一个”形参“都应该要#define year 365*24*60*60*60*60  安理说是可以的但是year是int型的已经超过了范围所以要把它搞成无符号长整形。#define year 365*24*60*60*60*60ul )  这样才是正确的宏定义的变量是不占内存空间的直接替换减少开销但是变量替换是不进行类型检查函数的变量要占用空间、要压栈等操作就需要很大的开销但是调用函数时编译器会检查函数变量的类型是否相同。内联函数集合普通函数、宏定义的两个优势它直接就地展开直接替换减少开销同时编译器也会检查变量的类型。但是函数体积要小不然效率反而很低至于原因暂时不详。6.3、内联函数对函数就地展开像宏定义一样这样减少开销同时也检查变量的类型。但是必须函数的内部体积小才用这种方式以达到更好的效率。体积大的函数就作为普通函数。内联函数通过在函数定义前加inline关键字实现。6.4、条件编译的应用做一个调试开关。#define  DEBUG                                                        #undef  DEBUG 是注销 DEBUG 宏#ifdef DEBUG#define debug(x)   printf(x)#else#define debug(x)  #endif 6.5、函数(1)整个程序分成多个源文件一个文件分成多个函数一个函数分成多个语句这就是整个程序的组织形式。这样组织的好处在于分化问题、便于编写程序、便于分工。(2)函数的出现是人程序员和架构师的需要而不是机器编译器、CPU的需要。(3)函数的目的就是实现模块化编程。说白了就是为了提供程序的可移植性。1函数书写的一般原则第一遵循一定格式。函数的返回类型、函数名(男女厕所)、参数列表太多用结构体等。第二一个函数只做一件事函数不能太长也不宜太短一个屏幕的大小原则是一个函数只做一件事情。第三传参不宜过多在ARM体系下传参不宜超过4个。如果传参确实需要多则考构体打包考虑第四尽量少碰全局变量函数最好用传参返回值来和外部交换数据不要用全局变量。2 之所以函数能被调用根本实质是在编译时检查到了该函数的声明不是因为函数定义了当然也要定义才行只是不是本质。6.6、递归函数自己调用自己的函数常用举例阶乘   int jiecheng( int n)                6.7、函数库1静态链接库其实就是商业公司将自己的函数库源代码经过只编译不连接形成.o的目标文件然后用ar工具将.o文件归档成.a的归档文件.a的归档文件又叫静态链接库文件。商业公司通过发布.a库文件和.h头文件来提供静态库给客户使用客户拿到.a和.h文件后通过.h头文件得知库中的库函数的原型然后在自己的.c文件中直接调用这些库文件在连接的时候链接器会去.a文件中拿出被调用的那个函数的编译后的.o二进制代码段链接进去形成最终的可执行程序。2动态链接库本身不将库函数的代码段链接入可执行程序只是做个标记。然后当应用程序在内存中执行时运行时环境发现它调用了一个动态库中的库函数时会去加载这个动态库到内存中然后以后不管有多少个应用程序去调用这个库中的函数都会跳转到第一次加载的地方去执行不会重复加载。也就是在运行时会把库函数代码放入内存中然后多个程序要用到库函数时就从这段内存去找而静态链接对于多程序就是重复使用库函数比较占内存。 (1) gcc中编译链接程序默认是使用动态库的要想静态链接需要显式用-static来强制静态链接。(2) 库函数的使用需要注意3点第一包含相应的头文件第二调用库函数时注意函数原型第三有些库函数链接时需要额外用-lxxx来指定链接第四如果是动态库要注意-L指定动态库的地址。6.8、常见的两个库函数1C库中字符串处理函数包含在string.h中这个文件在ubuntu系统中在/usr/include中字符串函数  如memcpy内存字符串复制直接复制到目标空间确定src和dst不会overlap重复则使用memcpy效率高memmove内存字符串复制先复制到一个内存空间然后再复制到目标空间确定会overlap或者不确定但是有可能overlap则使用memove比较保险memset                         strncmpmemcmp                       strdupmemchr                         strndupstrcpy                             strchrstrncpy                           strstrstrcat                              strtokstrncat                            。。。strcmp2 数学函数math.h    需要加 -lm     就是告诉链接器到libm中去查找用到的函数。    C链接器的工作特点因为库函数有很多链接器去库函数目录搜索的时间比较久。为了提升速度想了一个折中的方案链接器只是默认的寻找几个最常用的库如果是一些不常用的库中的函数被调用需要程序员在链接时明确给出要扩展查找的库的名字。链接时可以用-lxxx来指示链接器去到libxxx.so中去查找这个函数。6.9、自制静态链接库 (1)第一步自己制作静态链接库,首先使用gcc -c只编译不连接生成.o文件然后使用ar工具进行打包成.a归档文件库名不能随便乱起一般是lib库名称后缀名是.a表示是一个归档文件。   注意制作出来了静态库之后发布时需要发布.a文件和.h文件。 (2)第二步使用静态链接库把.a和.h都放在我引用的文件夹下然后在.c文件中包含库的.h然后直接使用库函数。备注1.a 文件前缀一定要加lib ,如 libzf.a ; 链接属性 -l小L,表示库名属性-L表示库的路径。所以gcc cdw.c -o cdw -lzf -L ./include -I(大i) ./include2 头文件“ ”表示外部自定义如果没加路径属性默认当前路径找如果在其他文件夹下必须用 -I(大i) 路径。用表示的头文件一种是在编译器下的库文件找第二种是自己定义的库文件找但是要定义其路径。3 在makefile文件中用到gcc/arm-gcc 那么在shell中就用相应的编译器 gcc/arm-gcc .4 nm ./include/libmax.a   查看max库的信息有哪些 .o 文件 .o文件有哪些函数。举例makefile:     arm-gcc aston.c -o  aston.o -carm-ar   -rc libaston.a   aston.o 6.9.1、自制动态链接库 1动态链接库的后缀名是.so对应windows系统中的dll静态库的扩展名是.a . 2第一步创建一个动态链接库。 gcc aston.c -o aston.o -c -fPIC                                (-fPIC表示设置位置无关码)gcc -o libaston.so aston.o -shared                          -shared表示使用共享库注意做库的人给用库的人发布库时发布libxxx.so和xxx.h即可。第二步使用自己创建的共享库。gcc cdw.c -o cdw -lmax.so -L ./动态链接库 -I ./动态链接库第三步上述编译成功了但是在 ./cdw 执行时会报错原因是采用动态链接在可执行文件只是做了一个标记标记是使用了哪个函数库的哪个函数。并没有将库函数加载到源文件中所以可执行文件很小在执行时需要立即从系统里面找到使用到的函数库然后加载到内存中在linux系统中默认是从 /usr/bin 中寻找不确定如果使用shell中运行会先执行环境变量的路径然后再查找 /usr/bin;所以我们可以用两种办法解决运行的问题第四步将动态库 libmax.so 复制到 /usr/lib 下面但是如果以后所有的库都这样放的话会越来越臃肿导致运行速度变慢一个一个查找或者是新添加一个环境变量export LD_LIBRARY_PATH$LD_LIBRARY_PATH:/mnt/hgfs/share/include   将库 libmax.so 复制到这个路径下面这样就可以运行了。3使用 ldd 命令判断一个可执行文件是否能运行成功 ldd cdw   linux-gate.so.1  (0xb77a8000)  libmax.so not found                                  //发现 not found意思就是没有找到对应的函数库libc.so.6 /lib/i386-linux-gnu/libc.so.6 (0xb75e2000)/lib/ld-linux.so.2 (0xb77a9000)七、存储类作用域生命周期链接属性7.1、概念词存储类栈、堆、数据区、.bss段、.text段作用域代码块作用范围也就是变量作用的范围生命周期变量的诞生和死亡链接属性外链接属性、内链接属性、无连接属性7.2、Linux下的内存映射分配情况、组织情况其中有关进程的空间如进程控制块、页表等都是在内核里面的。文件区是映射外部文件的如打开记事本那么这个文件临时存放在文件区域。7.3、存储类关键字 1 auto 自动的(一个用法修饰局部变量在定义变量时可以省略)  【外链接与第二个c文件链接】【内链接只与本c文件链接】【无连接:就是无链接】 2 static 静态的有两个用法第一个是修饰局部变量意思是当作全局变量存放在数据区作用域只是定义的那个函数范围生命周期和整个程序一样属于无连接 第二个是修改全局变量/函数意思是这个全局变量/函数只在当前c文件有效其他c文件是不能使用它的属于内链接普通全局变量属于外连接3register  寄存器一个用法修饰变量作用是让编译器把这个变量放在寄存器中当这个变量频繁的被使用时用这个方法可以提高效率但有时候不一定就放在寄存器因为寄存器是有限的没有剩余的寄存器了4extern (一个用法修饰全局变量表示该文件要使用的这个变量在另外一个c文件中已经定义了其一个声明的作用不能初始化) 5volatile (一个用法修饰变量表示对这个变量的语句不要去优化)  (1) volatile的字面意思可变的、易变的。C语言中volatile用来修饰一个变量表示这个变量可以被编译器之外的东西改变。编译器之内的意思是变量的值的改变是代码的作用编译器之外的改变就是这个改变不是代码造成的或者不是当前代码造成的编译器在编译当前代码时无法预知。譬如在中断处理程序isr中更改了这个变量的值譬如多线程中在别的线程更改了这个变量的值譬如硬件自动更改了这个变量的值一般这个变量是存在寄存器或许当其他进程要用到这个寄存器时就把这个寄存器的变量给改变了同时也就改变了这个变量(2) 以上说的三种情况中断isr中引用的变量多线程中共用的变量硬件会更改的变量都是编译器在编译时无法预知的更改此时应用使用volatile告诉编译器这个变量属于这种可变的、易变的情况。编译器在遇到volatile修饰的变量时就不会对改变量的访问进行优化就不会出现错误。(3) 编译器的优化在一般情况下非常好可以帮助提升程序效率。但是在特殊情况volatile下变量会被编译器想象之外的力量所改变此时如果编译器没有意识到而去优化则就会造成优化错误优化错误就会带来执行时错误。而且这种错误很难被发现。(4) volatile是程序员意识到需要volatile然后在定义变量时加上volatile如果你遇到了应该加volatile的情况而没有加程序可能会被错误的优化。如果在不应该加volatile而加了的情况程序不会出错只是会降低效率。所以我们对于volatile的态度应该是正确区分该加的时候加不该加的时候不加如果不能确定该不该加为了保险起见就加上。举例子1 int a3 b,c              ba;cb;那么编译器会优化成 cba3; 如果此时遇到上述三种情况突然改变了a的值那么对于没有优化前是对的但是对于优化后那么c仍然是3就会出错。所以当我们程序员知道这个变量会发生改变时就应该加 volatile提示编译器不要帮我们做优化。举列子2int square(volatile int *ptr){return *ptr * *ptr;}这段代码的有个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方但是由于*ptr指向一个volatile型参数编译器将产生类似下面的代码int square(volatile int *ptr){int a,b;a *ptr;b *ptr;return a * b;}由于*ptr的值可能被意想不到地该变因此a和b可能是不同的。结果这段代码可能返不是你所期望的平方值正确的代码如下long square(volatile int *ptr){int a;a *ptr;return a * a;}6 restrict (1)c99中才支持的所以很多延续c89的编译器是不支持restrict关键字gcc支持的。(2)restrict 作用是让编译器对这个变量做一些优化让它提高效率。下面的网站有列子。(3)restrict只用来修饰指针不能修饰普通变量,它表示只能该指针才能修改它的内容。如用memcpy时两个内存存在重叠的现象。(4)memcpy和memmove的区别  void *memcpy( void * restrict dest ,const void * restrict src,sizi_t n)这样它可以优化成memmove原理的方式当存在重叠时先复制到一段内存空间然后再把它复制到目的空间7.4、作用域 (1)全局变量起名字一般是 g_a;  (2)名字加前缀表示 7.5、总结 1局部变量地址由运行时在栈上分配得到多次执行时地址不一定相同函数不能返回该类变量的地址指针作为返回值。 2为什么要分为数据段和.bbs段因为当加载到内存重定位时如果这些数据包括0一个一个的复制会降低效率为0的变量直接清内存就可以了这样提高效率。3在头文件声明全局变量时 extern int a;  声明函数就是 void int max(int a, int b);4写程序尽量避免使用全局变量尤其是非static类型的全局变量。能确定不会被其他文件引用的全局变量一定要static修饰。(因为全局变量占内存的时间是最长的要看你的变量是不是需要这么长的时间这样可以节约内存空)八、一些杂散但值得讨论的问题8.1、操作系统的理解 1它是一个管理阶级者管理所有资源负责调配优化等操作。这样想象就像裸机一样的话要实现LED闪烁的进程、串口传输的进程、蜂鸣器等这些他们都要抢占一些资源这个时候没有操作系统就乱成一锅粥当有了OS的时候它就专门负责资源的调配让各个任务都能很好的实施起一个决策者的作用。 2如果我们要做一个产品软件系统到底应该是裸机还是基于操作系统呢本质上取决于产品本身的复杂度。只有极简单的功能、使用极简单的CPU譬如单片机的产品才会选择用裸机开发一般的复杂性产品都会选择基于操作系统来开发。3操作系统负责管理和资源调配应用程序负责具体的直接劳动他们之间的接口就是API函数。当应用程序需要使用系统资源譬如内存、譬如CPU、譬如硬件操作时就通过API向操作系统发出申请然后操作系统响应申请帮助应用程序执行功能。8.2、C库函数和API的关系 1从内核的角度看需要考虑提供哪些有用的API函数并不需要关注它们如何被使用。故编程时用API函数会感觉到不好用没有做优化。系统只负责做出一个可以用的API没有考虑到用户使用的方便性。所以c库函数对API做了一些优化让用户使用库函数更容易达到我们想要的目的。 2库函数实质还是用的API或者调用了一个API,也或者调用了更多的API,只不过是做了更多的优化。比如 库函数fopen 而API是open.3有的库函数没有用到API,比如strcpy函数复制字符串和atoi函数转换ASCII为整数因为它们并不需要向内核请求任何服务。8.3、不同平台windows、linux、裸机下库函数的差异(1)不同操作系统API是不同的但是都能完成所有的任务只是完成一个任务所调用的API不同。(2)库函数在不同操作系统下也不同但是相似性要更高一些。这是人为的因为人下意识想要屏蔽不同操作系统的差异因此在封装API成库函数的时候尽量使用了同一套接口所以封装出来的库函数挺像的。但是还是有差异所以在一个操作系统上写的应用程序不可能直接在另一个操作系统上面编译运行。于是乎就有个可移植性出来了。(3)跨操作系统可移植平台譬如QT、譬如Java语言。8.4、1main函数的写法int main(int argc,char **argv)  ;  int  main(int argc ,char *argv[ ] ); 这两种写法是一样的。二重指针等同于指针数组。2不管是主函数还是功能函数它都应该有一个返回值而主函数的返回值是给调用的那个人的/main函数从某种角度来讲代表了我当前这个程序或者说代表了整个程序。main函数的开始意味着整个程序开始执行main函数的结束返回意味着整个程序的结束。谁执行了这个程序谁就调用了main。谁执行了程序或者说程序有哪几种被调用执行的方法一个程序当然会运行那么就是调用了main( ).3inux下一个新程序执行的本质(1)表面来看linux中在命令行中去./xx执行一个可执行程序(2)我们还可以通过shell脚本来调用执行一个程序(3)我们还可以在程序中去调用执行一个程序fork exec总结我们有多种方法都可以执行一个程序但是本质上是相同的。linux中一个新程序的执行本质上是一个进程的创建、加载、运行、消亡。linux中执行一个程序其实就是创建一个新进程然后把这个程序丢进这个进程中去执行直到结束。新进程是被谁开启在linux中进程都是被它的父进程fork出来的。分析命令行本身就是一个进程在命令行底下去./xx执行一个程序其实这个新程序是作为命令行进程的一个字进程去执行的。总之一句话一个程序被它的父进程所调用。结论main函数返回给调用这个函数的父进程。父进程要这个返回值干嘛父进程调用子进程来执行一个任务然后字进程执行完后通过main函数的返回值返回给父进程一个答复。这个答复一般是表示子进程的任务执行结果完成了还是错误了。0表示执行成功负数表示失败,正规的要求返回失败的原因返回-1表示什么返回-2又表示什么然后父进程好做相应的处理(4) main函数的返回值应当是int 型。父进程要求是int型的如果写成 float 型则返回就为0这样是要出错的。8.5 用shell脚本来看main的返回值。如#!/bin/sh                      其文本格式为 .sh./a.outecho $?8.6、argc、argv与main函数的传参当我们的父进程不需要传参时就用 int main(void);当我们需要传参时就应该是 int main(int argv ,char *argc[ ]);它默认本身就是一个参数占了argv[0]这个位置它里面存的是 ./a.out (这个相应变化)如 ./a.out boy girl ;则 argv3;                    argc[0]./a.out;                 argc[1]boy; argc[2]girl ;    printf%s\n,argc[0];解释argv表示传了多少个参数argc实质是存的一个指针也就是一个地址只是没有一个被绑定的变量名而已。这个地址指向一个字符串一般字符串都和指针相关。所以可以称之为字符串数组每一个都存了一个字符串。在程序内部如果要使用argc那么一定要先检验argv,先检验个数然后使用。8.7、void类型的本质即使空型又是未知类型看具体情况。比如一个函数void表示不返回 void *malloc(20);就是未知类型。(1)编程语言分2种强类型语言和弱类型语言。强类型语言中所有的变量都有自己固定的类型这个类型有固定的内存占用有固定的解析方法弱类型语言中没有类型的概念所有变量全都是一个类型一般都是字符串的程序在用的时候再根据需要来处理变量。就如makefile、html语言。(2)C语言就是典型的强类型语言C语言中所有的变量都有明确的类型。因为C语言中的一个变量都要对应内存中的一段内存编译器需要这个变量的类型来确定这个变量占用内存的字节数和这一段内存的解析方法。(3)void类型的正确的含义是不知道类型不确定类型还没确定类型、未知类型但是将来一定有类型。(4)void *a;编译器可以通过定义了一个void类型的变量含义就是说a是一个指针而且a肯定有确定的类型只是目前我还不知道a的类型还不确定所以标记为void。void “修饰”的是指针因为指针就是内存地址它不知道指向的另一个变量是哪一种类型而变量一定是确定的void a就是错误的。8.9、C语言中的NULL,   NULL在C/C中的标准定义(1)NULL不是C语言关键字本质上是一个宏定义其保护指针的作用不要让他乱开枪。(2)NULL的标准定义#ifdef _cplusplus // 条件编译c环境#define NULL 0#else#define NULL (void *)0 // 这里对应C语言的情况#endif解释C的编译环境中编译器预先定义了一个宏_cplusplus程序中可以用条件编译来判断当前的编译环境是C的还是C的。NULL的本质解析NULL的本质是0但是这个0不是当一个数字解析而是当一个内存地址来解析的这个0其实是0x00000000代表内存的0地址。(void *)0这个整体表达式表示一个指针这个指针变量本身占4字节地址在哪里取决于指针变量本身但是这个指针变量的值是0也就是说这个指针变量指向0地址实际是0地址开始的一段内存。如 char *pNULL; 因为0地址本身就不是我们来访问的所以 *p时是不可访问的。在程序运行的逻辑上就不会出错。正规写 int *p NULL;// 定义p时立即初始化为NULLp xx;if (NULL ! p){*p // 在确认p不等于NULL的情况下才去解引用p}(1)\0是一个转义字符他对应的ASCII编码值是0内存值是0一个char空间。(2)0是一个字符他对应的ASCII编码值是48内存值是int型48一个char空间。(3)0是一个数字没有ASCll编码 内存值是int型0一个int空间。(4)NULL是一个表达式是强制类型转换为void *类型的0内存值是0内存地址一个int空间。8.9.1、运算中的临时匿名变量1“小动作”高级语言在运算中允许我们大跨度的运算。意思就是低级语言中需要好几步才能完成的一个运算在高级语言中只要一步即可完成。譬如C语言中一个变量i要加1在C中只需要i即可看起来只有一句代码。但实际上翻译到汇编阶段需要3步才能完成第1步从内存中读取i到寄存器第2步对寄存器中的i进行加1第3步将加1后的i写回内存中的i。2 float a12.3; int b(int)a;    (int )a 就是匿名变量;先找一个内存空间里面存inta 然后把这个值赋值给b;最后匿名值销毁。float a; int b10; ab/3;  左边是3.00000 右边是3其中有个匿名变量先找一个内存空间里面存  b/3; 然后把它再转换成float型再赋值个a;最后匿名值销毁。8.9.2 分析DEBUG宏学习级                                         #define  DEBUG                                                        #undef  DEBUG 是注销 DEBUG 宏#ifdef DEBUG#define debug(x)   printf(x)#else#define debug(x)  #endif 应用级                                        #ifdef DEBUG#define DBG(...) fprintf(stderr, DBG(%s, %s( ), %d): , __FILE__, __FUNCTION__, __LINE__); fprintf(stderr, __VA_ARGS__)#else#define DBG(...)#endif解  释1...表示变参提示编译器不要对参数个数斤斤计较不要报错 其实完全可以把 ...换成 cdw 也是可以的只是非要装一下逼而已。2 _FILE_ 和 _FUNCTION_和 _LINE_ 都是c库函数的宏定义分别表示要输出的这句话属于哪个文件名、属于哪个函数名、在第几行。3 在 fprintf(stderr,cdw);其中stderr是c库函数中宏定义了的这是VC6.0找到的        #define stderr (_iob[2])    也就是说stderr是一个被宏定义了的指针它是标准错误输出流对象stderr输出到屏幕上。fprintf是C/C中的一个格式化写—库函数位于头文件stdio.h中其作用是格式化输出到一个流/文件中重点是流/文件printf()函数是格式化输出函数, 一般用于向标准输出设备按规定格式输出重点是标准输出设备有时候输出的不一定显示在屏幕上只是编译器规定显示到屏幕上而已。总结也就是说printf其实不是输出屏幕上的只是这个标准输出设备中编译器规定显示到屏幕上而已而真正输出到屏幕是fprintf(stderr,cdw);其中stderr就是输出到屏幕上的流。它也可以 fprintf( FILE *stream, const char *format,...),这个就是输出到文件流中的。比如一般情况下你这两个语句运行的结果是相同的没有区别只有一下情况才有区别运行你的程序的时候命令行上把输出结果进行的转向比如使用下面的命令把你的程序a.c运行的结果转向到记事本文件a.txta.exe a.txt  在这样的情况如果使用printf输出错误信息会保存到a.txt文件里面如果使用fprintf输出错误会显示在屏幕上。4上面中的__VA_ARGS__也是一个宏定义表示预处理时实际的参数。如DBG(tiaoshi.\n);则允许的效果是      DBG(debug.c, main( ), 14): tiaoshi.内核级                                  { #ifdef DEBUG_S3C_MEM #define DEBUG(fmt, args...)printk(fmt, ##args)#else#define DEBUG(fmt, args...)do {} while (0)#endif}九、链表状态机与多线程9.1、链表是一个一个的节点每一个节点分为两部分一部分是数据区可以由多个类型的数据另一部分是指向下一个节点的指针结构体定义里面的变量并没有生成是不占空间的相当于声明的作用。9.2、链表的数据存放在内存的那个空间呢栈不灵活不能用date数据段所以只能用堆内存申请一个节点的大小并检测NULL, 要使用它就得清理它因为上一个进程用了这段内存存的是脏数据然后对这个节点内存赋值链接起来.9.3、当要改变头节点是也就是要给headp赋值时必须传 head地址即 形参struct student *head这样才能真正改变不然传一个 struct student head只是单纯的赋值。9.4、在scanf(%d,(s-age)) 一定要注意studeny *s;     s-age访问的是一个变量而不要理解成地址所以要加scanf要注意9.5、细节1在 .h文件中声明一个函数要用分号而且是英文符号.用无头节点的方式需要修改头指针位置所以比较复杂详情http://blog.csdn.net/leo115/article/details/86026212 定义一个node *headNULL,想要改变head值通过函数传参是不行的因为head是一个地址传参过去只是赋值给另一个指针而已只能修改它指向的数据而本身地址是不能修改的所以要先返回修改好的地址然后再headnode_add(head)3定义、用指针都应该想到NULL,如 node *headNULL; node *new(node *)mallo(sizeof(node));if(NULL!new){      }4在结构体想定义一个字符串时不要用 char *name; 应该要用char name[10];如果使用第一种的话编译通过执行错误因为为name赋值时就要放在代码段中而代码段已确定了所以报段错误。9.6、头节点、头指针、第一个节点头节点是一个节点头节点的下一个指向第一个节点头节点的数据一般存的是链表长度等信息也可以是空头指针指向头节点。链表可以没有头节点但不能没有头指针。头节点可以想成数组的0位置其余节点当作从1开始所以有头节点的长度可以定义为就是含有真实数据节点的个数。9.7、删除一个节点应该做的事如果这个节点的数据不重要一定要记住free()掉你逻辑上删除其实仍然存在内存中的,头节点的好处就是函数返回值int可以帮助我们一些信息而没有头节点有时必须返回head;9.8、单链表之逆序9.9、单链表的优点和缺点优点单链表是对数组的一个扩展解决了数组的大小比较死板不容易扩展的问题。使用堆内存来存储数据将数据分散到各个节点之间其各个节点在内存中可以不相连节点之间通过指针进行单向链接。链表中的各个节点内存不相连有利于利用碎片化的内存。缺点单链表各个节点之间只由一个指针单向链接这样实现有一些局限性。局限性主要体现在单链表只能经由指针单向移动一旦指针移动过某个节点就无法再回来如果要再次操作这个节点除非从头指针开始再次遍历一次因此单链表的某些操作就比较麻烦算法比较有局限。回忆之前单链表的所有操作插入、删除节点、 遍历、从单链表中取某个节点的数·····因为单链表的单向移动性导致了不少麻烦。总结单链表的单向移动性导致我们在操作单链表时当前节点只能向后移动不能向前移动因此不自由不利于解决更复杂的算法。9.9.1、 内核链表的思想是 1先做一个纯链表没有数据区只有节点的链接方法。然后要做一个链表出来直接用纯链表然后稍加修改就可以了。  2内核中__的方法不要轻易使用是给内核用的否则容易出错用户应该使用没有__的方法如__list_add() ; list_add();3内核默认是头指针头节点的思路。4其实质就是操作里面内嵌 纯链表这个变量再利用controf宏来访问结构体的数据。详情见驱动。9.9.2、状态机 1概念其实就是有多种状态切换如电脑的休眠、关机、睡眠。 2类型(1)Moore型状态机特点是输出只与当前状态有关与输入信号无关。相对简单考虑状态机的下一个状态时只需要考虑它的当前状态就行了。(2)Mealy型状态机的特点是输出不只和当前状态有关还与输入信号有关。状态机接收到一个输入信号需要跳转到下一个状态时状态机综合考虑2个条件当前状态、输入值后才决定跳转到哪个状态。3理解要时时刻刻检查当前状态用循环switch(状态)然后根据输入信号进行更多的处理转换到其他状态。十、增补知识10.1、一个字节可以表示8位字符字符真的有256种128~255表示西欧字符是不常见详情见文档。 字符相加的时候会自动转成 int型加。10.2、在C中默认的基础数据类型均为signed现在我们以char为例说明(signed) char与unsigned char之间的区别。首先在内存中char与unsigned char没有什么不同都是一个字节唯一的区别是char的最高位为符号位因此char能表示-127~127,unsigned char没有符号位因此能表示0~255这个好理解8个bit最多256种情况因此无论如何都能表示256个数字。10.3、为什么在链接时需要一个链接地址因为数据是要放在一个模拟地址内存空间的它要把这个数据先加载到寄存器才能给cpu使用那么寄存器怎么知道是哪个内存地址位置呢是因为在编译时编译出像 ldr r0 0x12345678  ,而这个0x12345678就是内存地址再编译出像 ldr  r1,[r0] ,这样就可以拿到0x12345678内存位置的数据了10.4、printf 变参10.5、arm-2009q3.tar.bz2 这套编译器自带了函数库比如有strcmp , malloc ,printf 等但是有些库函数我们却不能用他们比如printf,因为这个函数默认是同过屏幕输出的而我们常用uart调试。感觉malloc也不能用因为我们不知道内存哪一块做了堆内存只有系统才知道。10.6、清bss段编译器可能已经帮我们做了只是在重定位那节因为要重定位那部分内存空间并没有清0 所以要手动编程清bss段。
http://www.pierceye.com/news/720359/

相关文章:

  • 西安网站优化招聘网多个网站 备案吗
  • 宣威网站wordpress 园林模板
  • 宁夏政务大厅城乡建设厅口网站怎么用抓爬工具做网站
  • 电影网站怎么建设深圳企业营销型网站
  • 天津工程建设网官方网站wordpress 静态化插件
  • 洛阳公司青峰做的企业网站设计本app
  • 宁波网站建设设计高效的设计公司
  • c2c网站架构免费推广网站工具
  • 网站建设案例基本流程图咨询公司名字大全
  • 成功的电子商务网站设计律师推广网站排名
  • 东莞桥头网站建设合肥商城网站建设
  • 做网站的准备什么合肥制作网页设计
  • 医院门户网站建设规划柳州建设厅官方网站
  • 公司建网站怎么建做网站的公司都很小吗
  • 手机cms建站系统重庆怎么站seo
  • 益阳建设局网站网站 设计 趋势
  • 奉贤网站建设网站制作金融企业如何做好网络推广
  • 范湖网站建设团队建设银行激活网站
  • 旅游网站开发网站设计报告书邢台旅游景点大全排名 免费
  • 如何创建div做网站推荐佛山伦教网站设计
  • 建设电子商务网站前的市场分析网站后台ftp
  • 华丽的网站模板律所网站建设
  • 网站 管理系统搜索关键词的方法
  • 网站桥页也叫设计班级网站建设
  • 安庆网站建设工作室方维网络科技有限公司
  • 手机网站开发利用流程做网盟行业网站的图片广告的销售
  • 厦门建公司网站怎样自做网站
  • 兰州市网站建设公司无锡上海网站建设
  • 轻骑铃木摩托车官网资源专业网站优化排名
  • 做电影网站赚钱吗中企网站建设