【简介】温故知新,“三日不玩,手上长刺”,代码也是如此。另一方面,你必须填补你自己挖的洞。在埋下了4种编程语言的伏笔,实现了Javacript、Python和Java。本来想把C/C++一起整理一下,但是涉及到面向对象的设计技术,最后整理一下从0到1吧。C语言简洁易用灵活,可以直接访问物理地址并执行高效的位操作。生成的目标文件质量高,执行效率高,但这是相对而言,效率还是比汇编语言低15%左右。数据处理特别是图像处理能力强,便携性也好。关键字ANSIC共有32个关键字和9种控制语句,按照约定编成顺口溜。签名返回时,无符号大小写继续default.register转到自动联合,做shortlongstruct.voidtypedefswitchextern,volatilechardoubleconst.ifbreakstaticint,enumsizeofelsefloat。在C99中又增加了5个关键字inlinerestrict_Bool_Complex_Imaginary,后来在C11中又增加了7个关键字_Alignas_Alignof_Atomic_Static_assert_Noreturn_Thread_local_Generic,所有这些关键字,不仅要了解,还要知道它们的典型应用场景。数据结构C语言为用户提供了丰富的数据结构,也允许用户定义复杂的数据结构。C语言提供的数据结构以数据类型的形式给出,C的数据类型分为:基本类型数值类型字符类型枚举类型构造类型数组类型结构类型联合类型指针类型数据可分为常量和变量,习惯上常量用大写字母,变量用小写字母。数值类型要注意数值的范围不同。字符常量是用单引号括起来的单个字符,也允许以“\”开头的特殊字符常量。枚举类型是原始数据类型,而不是构造类型,因为它不能分解为任何原始类型。在编译中,枚举元素被视为常量,因此称为枚举常量。它们不是变量,不能给它们赋值。数组是有序数据的集合,数组中的每个元素都属于同一种数据类型,用统一的数组名和下标来唯一确定数组中的元素。结构体是C语言提供的一种数据结构。一般形式如下:struct结构名{成员列表}变量名列表;通常,可以使用宏来获取结构内的偏移量:#undefoffsetofstruct#defineoffsetofstruct(TYPE,ELEMENT)((size_t)&((TYPE*)0)->ELEMENT)#endifunion也是派生类型,语法与结构体相同,区别在于其成员共享存储空间。联合定义了一组共享内存块的替代值。变量在内存中的地址称为变量的指针,这是C语言的精髓,下面将单独介绍。C语言还提供了非常丰富的运算符集,主要包括以下34种运算符:算术运算符:+、-、*、/、++等关系运算符:>、<、==、!=等逻辑运算符:&&,||,!等价:>>、<<、~等赋值:等号(=)及其扩展赋值运算符(+=、-=、*=、/=等)指针:*、&使用各种运算符进行运算对象被连接起来形成表达式。指针C语言的核心是指针,它的灵活性和超长都来自于指针。指针提供了动态操作内存的机制,加强了对数据结构的支持,实现了访问硬件的功能。指针是保存内存地址的变量。定义指针时,必须指定它所指向的变量的类型。任何指针都是某种类型的变量。当通过指针访问指针指向的内存区域时,指针指向的类型决定了编译器将把该内存区域的内容当作什么。需要注意的是,指针的类型(即指针本身的类型)和指针指向的类型是两个概念。void指针类型,即没有指定指向哪种数据类型的指针变量。void指针可以指向任何类型的数据,任何类型的指针都可以直接给void指针赋值。但是,如果需要将指针的值赋值给另一种类型的指针,则需要进行强制类型转换。在指针定义语句的类型前加上const,表示指向的对象是常量。一个指针变量可以指向另一个指针,一个指针指向一个指针。程序中的函数代码也占用内存空间,而每个函数都有一个地址,所以指针也可以指向函数,指向函数地址的指针称为函数指针。总之,指针可以指向的对象是没有限制的,它可以是变量、数组元素、动态分配的内存块和函数。正确理解指针变量和函数指针的声明,例如:(*(void(*)())0)();注意*p()和(*p)()的区别,前者表示函数的返回值是一个Pointer类型,后者表示p是指向函数的指针。指针的典型用法:直接访问系统内存引用函数构造链式数据结构引用动态分配的数据结构实现引用调用传递数组参数访问和迭代数据元素表示字符串作为其他值函数的别名一个大程序可以分成几个Applet模块,每个模块用于实现特定的功能,这个模块称为功能。一个C程序可以由一个主函数和几个子函数组成。main函数调用其他函数,其他函数之间也可以相互调用。同一个函数可以被一个或多个函数调用任意次数。从用户的角度来看,函数可以分为库函数和自定义函数。从函数本身来看,可以分为带参数和不带参数两种。在传递参数的过程中,要根据需要进行值传递和地址传递,即形参和实参。只有当函数调用发生时,函数中的形参才会被分配内存单元。调用结束后,形参占用的内存单元也被释放。该函数应该在同一个文件中调用它的地方之前定义,否则它会默认返回一个整数。如果调用函数和被调用函数不在同一个文件中,返回值类型不同,连接时会报错。如果被调用函数的参数包括char、short、float等,则必须在调用该函数的文件中声明该函数,参数类型必须包含在括号中。本质上,函数表示法就是指针表示法,函数名被求值成为函数的地址,然后将函数参数传递给函数。程序栈是支持函数执行的内存区域,通常与堆共享,包括返回地址、本地数据存储、参数存储、栈指针和基指针(运行时管理栈的指针)。当系统创建一个堆栈帧时,它按照声明的相反顺序将参数压入帧,最后压入局部变量。从函数返回指针时可能存在的潜在问题:返回未初始化的指针返回指向无效地址的指针返回指向局部变量的指针返回指针但不释放内存函数指针可以按不确定的顺序执行函数在编译时。void(*foo)()使用函数指针时一定要小心,因为c不会检查参数传递是否正确,建议使用fptr作为前缀。函数指针数组允许根据特定条件选择要执行的函数。将指针传递给指针允许参数指针指向不同的内存地址。内存存储C中主要有4种存储类型:auto只能用来标识局部变量的存储类型。对于局部变量,auto是默认的存储类型,不需要显式声明。所以auto标识的变量存放在栈区。extern用于声明全局变量。如果全局变量没有被初始化,它会被存放在BBS区,编译时它的值会被自动赋值为0。如果已经初始化,则存放在数据区。全局变量,不管是否初始化,都有整个程序运行的生命周期。为了节省内存空间,在当前文件中使用extern声明其他文件中定义的全局变量时,不会为其分配内存空间。.寄存器变量从内存传送到CPU寄存器后常驻CPU寄存器,所以寄存器会大大提高效率,因为变量从内存传送到寄存器的过程中多条指令省略了循环。static不管是全局的还是局部的,都是存放在数据区的,它的生命周期就是整个程序。如果是静态局部变量,则其作用域在一对{}内;如果它是一个静态全局变量,它的作用域就是当前文档。静态变量如果没有被初始化,会自动初始化为0。静态变量只能初始化一次。使用内存时,应用和释放应该配对。本着谁申请谁释放的原则,释放后指针应该置为NULL。常见的内存使用问题有以下三种:野指针:Free后指针没有清空,继续使用指针;内存泄漏:申请越界后内存不释放:数组索引和内存访问溢出避免内存越界,数组的索引必须要检查有效值,最好使用字符串操作API如strncpy、strncat等。需要检查内存拷贝的大小,避免出现野指针。如果条件允许,可以自己实现内存池管理,按字节划分内存池(比如8字节的整数倍)。每次分配的内存地址空间在开始和结束位置初始化特殊值,然后使用单独的线程每隔很短的时间扫描内存池中的每个有效块,进行内存整理。在动态分配存储字符串的空间时(malloc方法),注意不要忘记字符串需要多分配一个字节来保存以'\0'结尾的字符串。编译C语言的编译过程包括预编译->语法分析->代码生成->优化->汇编->连接。预编译器进行宏替换、词法分析并创建符号表。语法分析包括语义分析以创建语法树。代码生成器生成中间代码,优化器负责指令优化,汇编器生成汇编代码,最后链接器生成目标文件和可执行文件。链接器在目标模块中检查是否与外部对象同名,如果没有命名冲突,则将其添加到加载模块中。函数和已初始化的全局变量(包括初始化为0的)是强符号,未初始化的全局变量是弱符号。该符号的含义是指向同一个内存块进行同名的读写操作,即使这些操作分散在不同的.o中。对它们来说,以下三个规则适用:同名的强符号只能有一个,否则编译器会报“重复定义”错误。允许一个强符号和多个弱符号,但定义时会选择强符号。当多个弱符号相同时,链接器选择占用内存空间最大的一个。记住比较运算符==不要误写成赋值符号=,反之亦然,两者有很大区别。词法分析采用从左到右的贪心法,例如a---b等价于a---b,不等价于a---b;预编译通常是在C编译系统编译程序之前,对程序中的一些特殊命令进行“预处理”,然后将预处理后的结果与源程序进行编译处理,得到目标代码,以“#”开头的行成为预处理指令.带参数的宏与函数非常相似。引用函数时,实参也写在函数名后的括号内,要求实参个数与形参个数相等。但是,它们之间还是有区别的:参数的使用不同。.调用函数时,先计算实参表达式的值,再引入形参;该宏仅执行简单的字符替换。处理机制不同。函数调用是在程序运行时处理的,必须分配内存;编译时进行宏展开,不分配内存单元,不发生值传递处理,对返回值定义也没有不同的要求。定义函数时,实参和形参都必须定义类型;定义宏时,没有预处理器提供条件编译的功能。可以根据不同的条件编译不同的程序部分,从而生成不同的目标代码文件,这对于程序的移植和调试非常有用。条件编译有三种形式:#ifdefidentifiercodes1#elsecodes2#endif#ifdefidentifiercodes3#endif#ifndefidentifiercodes4#elsecodes5#endif头文件一般通过头文件调用库函数。很多时候,源代码是不方便(或不允许)发布给用户的,只要把头文件和二进制库提供给用户即可。用户只需要根据头文件中的接口声明调用库函数即可,无需关心接口是如何实现的。编译器从库中提取相应的代码。头文件还强制执行类型安全检查。如果接口的实现或使用方式与头文件中的声明不一致,编译器会指出错误。这个简单的规则可以大大减轻程序员调试和改错的负担。尖括号引入的头文件是在include文件目录中查找(include目录是用户在设置环境时设置的),而不是在源文件目录中查找。使用双引号表示首先查找当前源文件目录,如果找不到则在包含目录中查找。用户在编程时,可以根据自己的文件所在目录选择一定的命令形式。程序框架和库C语言的程序框架由头文件、变量声明、主函数和子函数组成。在C中无处不在的helloword看起来像这样:#include
