【原创】C语言高级用法-学习笔记.doc_第1页
【原创】C语言高级用法-学习笔记.doc_第2页
【原创】C语言高级用法-学习笔记.doc_第3页
【原创】C语言高级用法-学习笔记.doc_第4页
【原创】C语言高级用法-学习笔记.doc_第5页
已阅读5页,还剩18页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1. #的作用#是将其后的变量直接转换为字符串void WriteLog(arg) printf(%s=%d,#arg,arg)2. #的作用#的作用是连接两个变量如#define combine_converse_str(x,y) x#yint Err;int Num;printf(%s,combine_converse_str(Err,Num);结果为:ErrNum再如:int a=1;int b=2;int ab=3;printf(%d,a#b);结果为: 3后面那句相当于printf(%d,ab);3. do while(0)作用1,空的宏定义避免warning:#define foo() dowhile(0)2,存在一个独立的block,可以用来进行变量定义,进行比较复杂的实现。3,用在宏定义上并且当宏定义需要定义成多条语句的组合时,可以保证这几条语句是一个整体的,并且可以消除使用这个宏定义后添加;所带来的报错,例如:#define aaa(x,y) do int a=0; int b=0; 函数1(x); 函数2(y); while(0)调用aaa(x,y); 时不会报错并且如果使用:if (m) aaa(1,2);else aaa(3,4);时运行也不会使某些语句没有被运行到。4. 数组名的使用A 数组名作为函数参数:数组名用在某个函数A的参数中时,处于函数传递效率原因,会被强制转换成了指针,此后在函数A内就完全是一个值等于对应数组首地址的指针变量,可自加自减等注意!只有在作为函数参数的情况下才会将数组名强制转换成指针,其他的都不会转换。另:数组名作为函数参数传递时,函数声明的写法有多种int aaa(char x)int aaa(char x1) int aaa(char x100)int aaa(char *x)作为函数参数时的参数声明也和普通的数组定义一样要合法,比如不能 int aaa(char x0),因为定义一个数组也不能用 char x0来定义,编译器在检查参数的声明合法后,如果发现参数是数组,就会强制转换成指针,所以int aaa(char x1)和int aaa(char x100)的结果是一样的。B 数组名用在sizeof上:结果返回的是整个数组所占空间的大小,而不是一个指针的长度C 数组名与&和*:a与&a与&a0的值都相等,a是数组名,&a是整个数组地址,&a0是数组首元素的地址,他们都相等int a5=1,2,3,4,5;printf(a=%xn,a); printf(&a=%xn,&a); printf(*&a=%xn,*&a);printf(&a0=%xn,&a0); printf(*&a0=%xn,*&a0);printf(*(&a0)=%xn,*(&a0);printf(*(int *)(&a)=%xn,*(int *)(&a);printf(*(*(&a)=%xn,*(*(&a);结果:a=12ff34&a=12ff34*&a=12ff34 -*与&消掉,*&a=a=0x12ff34&a0=12ff34*&a0=1 -*与&消掉,*&a0=a0=1 *(&a0)=1 -*与&消掉,*(&a0)=*&a0=a0=1*(int *)(&a)=1 -*与&之间有个强制类型转换(int *),无法直接消掉,所以按照括号次序来运算(int *)(&a)=0x12ff34, *(int *)(&a)=*(0x12ff34)=1*(*(&a)=1 -*与&消掉,*(*(&a)=*a=1D 指针数组:定义:基类型*数组名使用:像普通的数组一样使用,数组名代表首地址,数组名0表示第一个元素的内容,以此类推。常见的是字符串数组,例如:char*Names=Bill,Sam,Jim;实际内存中保存的就是即首地址0x12ff0c0x00423018,0x0042f2f4,0042201c,其中0x00423018地址所保存的就是”Bill”,所以数组名Name=0x12ff0c, &Name=0x12ff0c,*Name=0x00423018,*Name=0x42printf(Name=%xn,Name); printf(&Name=%xn,&Name); printf(*&Name=%xn,*&Name); printf(&Name0=%xn,&Name0); printf(*Name=%xn,*Name);printf(*Name=%xn,*Name);printf(Name0=%xn,Name0);printf(*Name0=%xn,*Name0);printf(*&Name0=%xn,*&Name0);printf(*(&Name0)=%xn,*(&Name0);printf(*(int *)(&Name)=%xn,(*(int *)(&Name); printf(*(*(&Name)=%xn,(*(*(&Name);结果是Name=&Name=*&Name=&Name0=0x12ff0c*Name=Name0=*&Name0=*(&Name0)=(*(int *)(&Name)=(*(*(&Name)=0x00423018*Name=*Name0=0x42注意!指针是一级,数组是又一级,所以指针数组是二级指针,定义对应的指针变量要用二级指针,比如 int *p=Name; 不能直接用int *p=Name5. 常量的定义和指针常量的定义及使用:1.常量的定义:const int a=10; /在其他地方不允许修改a的值,否则会编译不通过.也可以写成 int const a=10; /一样的2.指针常量的定义和使用:有3种定义: const int *pi; 和 int const *pi; 以及 int * const pi;1)前两种情况一样的,没有区别。1)如果const修饰在*pi前(前2种情况)则不能改的是*pi(即不能类似这样:*pi=50;赋值)而不是指pi.2)如果const是直接写在pi前(后一种情况)则pi不能改(即不能类似这样:pi=&i;赋值)。6. 函数参数传递的4种类型:1.值传递void Exchg1(int x, int y) int tmp; tmp = x; x = y; y = tmp; printf(x = %d, y = %dn, x, y); main() int a = 4,b = 6; Exchg1(a, b); printf(a = %d, b = %dn, a, b); return(0); 2.地址传递void Exchg2(int *px, int *py) int tmp = *px; *px = *py; *py = tmp; printf(*px = %d, *py = %d.n, *px, *py); main() int a = 4; int b = 6; Exchg2(&a, &b); printf(a = %d, b = %d.n, a, b); return(0); 3.引用传递void Exchg3(int &x, int &y) int tmp = x; x = y; y = tmp; printf(x = %d,y = %dn, x, y); main() int a = 4; int b = 6; Exchg3(a, b); printf(a = %d, b = %dn, a, b); return(0); 4.变长参数传递:使用省略号指定参数表使用的宏及其定义(x86中):#define va_start _crt_va_start#define va_arg _crt_va_arg#define va_end _crt_va_endtypedef char * va_list;#define _ADDRESSOF(v) ( &(v) )#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & (sizeof(int) - 1) )#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )#define _crt_va_arg(ap,t) ( *(t *)(ap += _INTSIZEOF(t) - _INTSIZEOF(t) )#define _crt_va_end(ap) ( ap = (va_list)0 )实例:#include stdafx.h#include stdio.h#include string.h#include stdarg.h/*至少需要一个确定的参数,注意括号内的省略号*/ int demo(char *fmt, .) va_list argp; int argno = 0;char *para; va_start(argp, fmt); while (1) para = va_arg(argp, char *); if (strcmp(para, ) = 0) break; printf(Parameter #%d is: %sn, argno, para); argno+; va_end(argp); return 0; int main() demo(DEMO1, This, is, a, demo!, ); return 0;运行结果:详解:1. 函数的参数和局部变量都是存储在栈里2. 栈的内存分配是按从高地址到低地址分配的(而其他的正常是从小到大分配,比如全局变量定义等),即先分配高地址的,再分配低地址的内存3. 所有的函数参数(不管是定长参数还是变长参数)入栈都是从右到左入栈,如调函数a(x,y,z), 则z先入栈,并存在栈底(高地址),其次y,其次x。4. 数据在内存里的存储默认都按照字节对齐来存储(x86按照4字节对齐),如:int aaa(char a, char b, char c)return (int)(a+b+c);调用函数aaa(5,6,7);时,内存中是这样的:所以,如果是数值,则会直接存储在栈中,如果是指针类型(如字符串),则会把指针值(即字符串首地址的值)存储在栈中,从右到左依次按照从高地址到低地址存储。如实例的程序调用demo(DEMO1, This, is, a, demo!, );得到的堆栈为:即0x0012FEFb开始往低地址方向的部分为此函数的栈空间,存入的值分别为0x00422038,0x00422058,0x0042204C,0x00422034,0x00422044,0x0042203C,这6个地址分别所指向的实际上就是静态存储区存储”,demo!, a, is,This,DEMO1的地方,见图另:如果改成demo(DEMO1, 1, 2, a, demo!, );则其中的数字1,2直接保存在栈中,见图5. #define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & (sizeof(int) - 1) )的运算结果实际上就是当1=sizeof(n)=4时取4,5=sizeof(n)=1);return 0;这个函数编译出来为test3.exe,然后在dos命令行中调用,得到:D:test3.exe aaa bbb cccargc=4argv=test3.exeargv=aaaargv=bbbargv=ccc其中,test3.exe文件名本身也算一个参数,所以dos命令行调用test3.exe时,操作系统传递给test3.exe的实参实际上就是参数个数是4,参数为“test3.exe”,“aaa”,”bbb”,”ccc”17. 哪些语句句尾要用分号?C语言规定,所有的语句必须要以分号;结尾,而预编译命令不属于语句,末尾不加分号,如#include #if #else #elif #endif #define等18. volatile关键字的作用volatile修饰的量是不稳定的,可能会经常被改变或者被其他线程使用并改变其值,所以用volatile关键字就限制了执行器在执行的时候每次访问其值必须去内存中去取,而不能被编译器优化在内部寄存器中。19. typedef的用法1. 用于对普通数据类型定义别名:例如,typedef unsigned int UINT;2. 用于定义指针:例如,typedef int *PINT; (注意!虽然*与PINT之间没有空格,但是实际上跟typedef int* PINT是一样的,*是跟前面的结合的。)3. 用于对结构体/枚举/联合体类型定义别名:例如: typedef unionint uid;unsigned char mid; ID_U;4. 用来定义数组:例如,typedef int aNum2; aNum x;5. 用来定义函数相关,一般都是定义函数指针:例如typedef int (*pf)(int, char);6. 用来简化复杂的定义,其实就是前面几个的综合。注意!定义普通类型和指针以及结构体/联合体/枚举,都是“typedef 原定义 别名;” 的方式,而定义数组和函数指针是按照“替换原定义的变量”的定义方式来定义,是不同的。typedef使用范例:#include stdafx.h#include stdio.htypedef unsigned int UINT;typedef structUINT uid;char mid; STU_T;typedef STU_T *STU_T_REF;typedef int aArray2;typedef void (*fFunc)(int);void print1(int x)printf(%d,x);int main(int argc, char* argv)UINT i=3;STU_T st1;STU_T_REF tStu;aArray b;fFunc fTestPrint;tStu = &st1;tStu-uid = i;b0=1;b1=2;fTestPrint = &print1;fTestPrint(tStu-uid);return 0;20. typedef 和 define区别1. 作用上,typedef是定义一个类型,使用时与普通的变量类型用法完全相同;define是宏替换2. 语法上,typedef: typedef char INT8; define: #define INT8 charl typedef 在执行时执行 - #define 在预编译时执行l typedef 是一条语句,后要分号 - #define 是预编译指令,后无需分号l typedef 别名/替换者在后,原名在前- #define 别名/替换者在前,原名在后(这点可以这样记忆:执行前,使用前;执行后,使用后。#define是预编译,typedef是语句,所以#define的执行是前,我们使用的那个也在前,即#define INT8 char ,INT8在前,原名在后;typedef的执行是后,我们使用的那个也在后,即typedef char INT8; 我们使用的INT8也在后)typedef只能用于类型定义 -#define 不受限制,除了类型外还可以是数值等typedef不能带参数 -#define 可以带参数3. 作用范围上,语句typedef (int *) PINT; PINT a,b;与#define PINT (int *)PINT a,b;中的PINT a,b;是不一样的,前者相当于int *a;int *b; 而后者相当于int *a,b;即int *a; int b;21. 复杂定义的剖解方法和用typedef简化可以用“右左法则”来分析,所谓右左法则,就是先找到没有定义的变量,这个就是分析的起点,也是变量的最终属性,从这个起点,根据优先级顺序开始分析,如优先级相同,则按照先右再左分析,遇到()就根据()是最高优先级来分析,跳出()后同样按先右后左分析,以此类推。例如:int* (*a5)(int, char*); 找到a是未定义变量,所以这就是起点,优先级比*高,所以a与5先结合,a5是一个具有5个元素的数组,再与*结合,得到每个元素都是指针,跳出()再向右结合(),得到前面的指针(数组里的每个都是指针的元素)指向一个函数,函数的参数是(int, char *),返回值是int *。void (*b10) (void (*)(); b是一个有10个元素的数组,每个元素都是指针,这些指针中每一个指针指向一个函数,函数的参数是void(*)()(实际上它又是个函数指针,指针所指向的函数的参数为空,返回值为void),返回值是voiddouble(*)()(*pa)9; pa是一个指针,这个指针指向具有9个元素的数组,9个元素中每一个都是double(*)(),即指针,指针指向一个函数,函数的参数为空,返回值为double。使用typedef简化:用typedef来对复杂定义做简化很简单,从小到大,抠出合适的定义单位(从阅读理解和使用上合适),然后用别名代替,最后在前面加个typedef关键字即可。例如:1)int* (*a5)(int, char*); -首先从小到大,抠出合适的定义单位,a5是个数组,方便理解,别名用ARRAY替代,则成了 int* (*ARRAY)(int, char*);前加一个typedef即可,typedef int *(*ARRAY)(int,char*)2). void (*b10)(void (*)(); typedef void (*pfA)(void (*)(); pfA b10; 进一步化简- typedef void (*pfB)(); typedef void (*pfA)(pfB); pfA b10;3). double(*(*pa)9)();typedef double(*PF)(); PF (*pa)9;22. struct enum和union的定义以及用typedef来定义定义:struct/enum/union 类型名 成员类型 成员名; 成员类型 成员名; ; (其中如果直接在后定义变量的话,可以省略类型名不写)声明:struct/enum/union类型名 变量名;例如:定义:struct student_schar * name; int age; int class; int number;声明:struct student_s stu_a;用typedef来定义:typedef struct/enum/union 类型名 成员类型 成员名; 别名;(其中可以省略类型名)声明:别名 变量名;例如:定义:typedef struct student_tdchar *name; int age; int class; int number; STUDENT_T;使用:STUDENT_T stu_a;用typedef来定义结构体/枚举/联合体实际上就是:typedef 原定义 别名; 与定义普通的类型是一样的,如typedef char INT8;23. typedef的2个使用陷阱1. 用在const或volatile修饰的语句时的陷阱typedef用在const修饰的语句和用在volatile修饰的语句的注意点是一样的,下面以const为例说明。typedef int *PINT;const PINT p1;上面2条语句,先用typedef声明了之后,再用const修饰,实际结果不是const int * p1; 而是int * const p1; 根我们想象的不一样的!不能直接类似#define那样替换的!因为typedef定义的PINT是地址,所以const修饰的是地址,也就是p1不能被修改,而不是整个(int *p1)!二维的也类似,typedef int *PPINT; int i=2; int *p=&i; PPINT p1=&p; 是p1不能被修改,而不是(*p1)!2. typedef不能跟存储类关键字同时使用typedef在语法上属于存储类关键字(如auto, static, extern, mutable,register等),不能再跟另外的存储类关键字同时使用了,虽然它并不实际影响存储特性。如:不能typedef static int SINT; 编译会提示“more than one storage class specified使用了一个以上的存储类关键字”24. 为什么要使用extern “C”extern “C”是用来修饰extern声明的,使extern按照C的形式去声明,而不是C+的形式,是为了兼容C与C+混合编程时防止c+程序调用c程序时找不到对应函数而设置的。详细解释: c+为了实现面相对象而引入函数重载(重载就是允许同样的函数名而参数不同,因为面相对象就要求能进行函数重载,因为面相对象是把一个行为(即方法/函数)对应的各种情况(即不同类型的参数,比如说println函数是打印,但是有时可能传递的参数只是字符串,有时候既有数字,又有字符串,不止一个,可能各种使用情况参数类型和个数都不同)都封装在对象内部,而不直接对外呈现),通过改变函数的编译方法来实现,实际上就是编译时用函数构建符号表的时候生成的对应的函数名不同,比如同样的函数foo(int,int),c语言编译在符号表中是_foo,而c+编译在符号表中却是_foo_int_int)。正是由于这点,所以.cpp代码在调用函数时总是寻找按照c+编译的符号表的形式来寻找函数名,而.c文件编译出来的放在符号表的函数名却不是c+所想象那样的,所以.cpp文件在调用c文件函数时就找不到函数了。所以需要使用extern “C”来修饰extern,意思是告诉C+编译器,这句extern要按照c的方式去寻找函数。具体的结合实例的解析和extern “C”的使用方法如下:以以下例子说明:/*-test6.cpp-*/extern int add(int x,int y);int main(i

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论