C语言扫盲及深化学习

c语言特点:
(1)效率高
(2)控制性强
(3)硬件亲和性好
(4)可移植性高

一、关于注释

  c语言中注释不能嵌套,因此注释代码时一定要注意源代码中是否已经存在注释。要从逻辑上删除一段代码,利用预编译指令更加安全有效,其格式为:

if 0

   statment;
#endif

二、关于函数参数

  如果函数参数带有const,则表示函数将不会改变调用者传递的参数。

三、关于传址和传值

  在c语言中,标量和常量是按值传递,而数组是传址调用,传值会有临时拷贝,被调函数内部修改该参数对调用函数来说是无效的。所有传递给函数的参数都是按值传递的,但是数组是按引用传递。

四、关于c语言的编译链接过程

  源码 -> 编译(预处理 + 解析语义 .o文件,即目标代码)-> 链接(目标文件 + 库文件) -> 可执行文件

  可执行文件的运行,会绑定一个启动程序,用于处理一些其他事项。

五、关于链接属性

  链接属性的作用:标识符的链接属性决定如何处理在不同文件中出现的标识符,标识符的作用域与连接属性有关。

简单的讲,当有多个源文件时,链接属性可以更好的处理所有文件之中的标识符变量,尤其是多个文件中出现的同一个标识符,使程序结构更明晰。

  链接属性的类型(以文件为维度):external->文件外部可访问;internal -> 只能在文件内部访问
  链接属性对应的关键字:extern 和static,extern表示可访问其他文件中相同的标识符(包括函数和变量),比如可以在file1中定义int a;然后在file2中声明 extern int a;则表示的是file2中的变量a是引用file1中的变量a,也就是说这两个文件中的a是同一个变量。
  一般的,全局变量和函数名的默认链接属性为external,而局部变量默认为internal
  可以用extern和static关键字修改默认链接属性,如全局变量 static int a;表示a的连接属性为文件内部;而static int function()表示函数function的连接属性为文件内部,即只能在该文件中调用该函数;而对于局部变量extern int b,则表示b为其他文件中定义的变量,即其连接属性为external。

注意:static除了以上表示链接属性的用法之外,还有一种用法,就是将static用于局部变量,对于局部变量sttaic int c来说,其不是改变c的链接属性,而是改变c的存储位置,使得c不再存储在进程的栈空间中,而是编译时将局部变量c存储在进程的.data段(初始化的静态数据区),因此,在该进程中当下一次访问变量c时,c的值为最近修改后的值。这就是static常用的三大用法,即修饰全局变量、修饰函数和修饰局部变量。简单讲,static的主要作用就是定位

六、const和static关键字作用总结

  c语言中static三大作用:修饰全局变量、修饰函数和修饰局部变量。当static修饰全局变量和函数时,表示的是链接属性的特征,表示该全局变量或者是函数的链接属性为internal,只有在其所在的文件内可以被访问与调用;而当static修饰局部变量时,不再表示链接属性的特征,而是表示该局部变量的存储位置在.data段(初始化的静态数据区:因为静态局部变量默认情况下会被编译器初始化为0)。
c语言中const常常存在于三种形式:const常量、const与指针和const与形参。const常量就是在一般变量前面加上const修饰,表示该变量不可更改,因此叫做const常量,即给字面值一个名字;const与形参表示的是在函数的声明中将参数用const来修饰,const修饰的形参表示在函数内部该参数不可被更改,这样既能表明编程者的意图,也能防止错误;而const与指针的情况比较复杂,因为涉及到指针和其所指代的内容两个因素,因此,const修饰的是指针还是其所指代的内容,是一个特别容易混淆的点。如下所示作以区分:
(1)int const *pci
  写成如上形式,const修饰的是什么呢?有一个简单的记忆法(首先要求将星号跟随变量名作为一个书写习惯,如上述的代码不应写成 int* const pci,虽然也没错,但是习惯问题),因此const修饰的是*pci,而*pci的语义是指针所指代的内容,所以说该种写法表示指针可以修改,但是其所指代的内容不可更改。
(2)int * const pci
  显而易见,该种写法const修饰的是pci指针,所以其语义是pci是一个常量指针,指针的值(即所指地址)不可修改;而指针所指代的内容是普通变量,可以修改。
(3)int const * const pci
  该种写法就容易理解了,即表示的是指针和其所指代的内容都不可更改。

对于以上const的技法,有一个口诀:即看*与const的相对位置,右定值,左定向(左右是指*的位置)

七、关于右值和左值

  在c语言中,所谓左值指的是存储位置,而右值就是指值。例如:
a = b + 5;
其中, a是个左值,因为他标识了一个可以存储结果值的地点(相当于门牌号),而b + 5 是一个右值,他是一个值(相当于屋内的东西)。
结论:凡是可以清晰表示地址概念的都可以作为左值,凡是可以清晰表示值的概念的,都可以作为右值。

八、可变参数列表实现

  c语言可通过stdarg.h中定义的方法实现可变参数列表,这个头文件声明了一个va_list的类型和三个宏操作:
(1)va_start:初始化va_list,将va_list变量设置为指向可变参数部分的第一个参数。
(2)va_arg:访问指定参数的值,并且使指针指向下一个可变参数。
(3)va_end:访问结束。在访问最后一个参数后,调用该函数,可变参数访问结束。
(4)va_list:用于访问参数列表的未确定部分

九、数组与指针的区别

  数组名和指针都具有指针值,都可以进行间接访问和数组下标访问,但是,声明数组和指针时,其内存状态存在很大的不同,这一点需要重点理解。比如下面的例子:
int a[5]
int *b
  声明a时,编译器首先为数组a开辟5个int大小的内存空间,然后创建数组名a为常量,并将其指向这段空间的起始内存地址;而声明指针b时,则只是为指针变量本身开辟一个内存空间,其值可能不会被初始化,不会指向任何有意义的地址空间;但是b++合法,b不合法,而a++不合法(a是常量),a合法。

十、指向数组的指针

int matrix[3][10];
int (*p)[10] = matrix;
p是一个指针,是一个指向拥有10个整型元素的数组的指针。

对于多维数组做函数参数的情况来讲,要特别注意其函数原型的声明形式。
func(int (*p)[10]);
func(int matrix[][10]);
以上两个参数的语义是一样的,都是指:指向包含10个整型元素的数组的指针,其中,一维以后的数组长度在形参中不可省略;而不能将二维数组的形参声明为func(int **p),该参数的语义是声明一个指向整型指针的指针,与指向数组的指针不是一个概念,要特别注意。

十一、字符串操作函数注意事项(string.h)

(1)计算字符串长度函数--strlen
strlen函数的返回值为size_t,其在stddef.h定义为无符号整数,因此使用时可能会出现意想不到的结果,比如无符号整数和0比较,将不会得到正确的结果,因此在使用其返回值时要将其强制转换为int类型
(2)不受限制的字符串函数--strcpy\strcat\strcmp
这些函数在使用时不会自动检查目标数组的长度,因此程序员在使用时必须时刻牢记检查目标数组的长度,以免越界产生不可预想的错误。
(3)长度受限的字符串函数--strncpy\strncat\strncmp
strncpy:若长度小于dst数组长度,剩余的自动补NUL字符;若长度大于或等于dst长度,此时结果不会以NUL字符结尾
strncat:该函数总是在结果字符串后面添加一个NUL字符
(4)字符串分割函数--strtok
strtok可以将一个字符数组根据不同的分隔符分割成不同的字符子串,第一个参数为NULL时,表示要查找下一个分割符。但是有一点需要注意,strtok会修改原来的字符数组

十二、内存操作函数

  内存操作函数对应于字符串操作函数,只是内存操作函数不对NUL字符进行识别
  主要包括:memcpy、memmove、memcmp、memchr、memset

十三、链表

  双链表的优势:
(1)可以以任何方向或忽前忽后的遍历链表
(2)允许从任何一端开始遍历

十四、字符串数组的实现

  在c语言中,字符串数组的存储实现只有两种方式:
(1)字符指针数组,即char buffer[10] = {"123","abc",...},该种方式一般只用于存储已知的常量字符串,不用于程序运行时赋值的情况。
(2)字符型二维数组,即char[][10],该方式可用于运行时字符串的赋值,注意:可以使用char (
p)[10]指针按行访问二维数组,效果就是用指针p来分别访问每个字符串

十五、关于指针的高级声明

(1)int f; //一个整型变量
(2)int *f; //一个指向整型变量的指针
(3)int *f,g; //f是指针,g是普通变量
(4)int f(); //函数声明,返回值为int
(5) int *f(); //函数声明,返回值为int型指针
(6)int (*f)(); //函数指针,指向函数调用起始点在内存中的位置
(7) int* (*f)(); //函数指针,函数返回值为int*
(8)int f[]; //数组声明
(9)int *f[]; //指针数组声明
(10)int f()[]; //非法声明,因为函数只能返回标量,不能返回数组
(11)int f[](); //非法声明,函数数组非法,因为函数代码长度不一,不能存于数组中
(12)int (*f[])(); //函数指针数组
(13)int *(*f[])(); //函数指针数组,函数返回值为int*类型
(14)int (*f)(int, float); //ANSI C标准中的函数指针声明,加入参数使声明更加明确
(15)int *(*g[])(int, float); //函数指针数组
注意:函数指针在使用时,一定要先声明函数原型,然后将函数指针初始化(使其指向一个具体函数),之后便可以向函数名一样使用该函数指针。

十六、怎样理解解引用符号*

  解引用,其实就是解除引用,关于它的类型,可以根据声明一步步上推。例如:
对于char *a; 解引用 *a的类型就是char;
对于char **b;解引用 *b的类型就是char *,也就是一个字符串,而 **b的类型则为char,是一个字符。

十七、字符串常量的进阶理解

  实质上,字符串常量出现在表达式中的时候,实际上是一个char型指针。比如:
(1)"xyz" + 1 => y
(2) "xyz"[2] => z
(3) "0123456789ABCDEF"[value % 16] => 二进制转换为字符,并以16进制打印

十八、预处理指令

  理解一个概念:什么是宏?允许把参数替换到文本中,这种实现通常称为宏或定义宏。
(1)#define:两种形式
a. #define name stuff:stuff可以是任何文本,不局限于整型常量
b. #define name(parameter-list) stuff: 宏
(2)宏和函数的优劣比较
a. 函数的调用和返回需要一定的开销(函数调用栈),而宏只是替换,没有函数栈的变化
b. 宏的参数没有类型,所以宏定义的表达式操作可以针对任何满足该操作的类型,而函数必须指明参数类型,即宏是与类型无关的。如下例子:
#define MAX(a,b) (a) > (b) ? (a) : (b) //可以用于任何满足>符号比较的类型值
c.宏参数可以传递类型,而函数不可以。
#define MALLOC(n, type) \ ((type *)malloc((n) * sizeof(type)))
该宏的调用:MALLOC(5, int);

注意:宏后面不要有分号。
由上可知,宏就是原原本本的文本替换,在替换时,一定要把所有的参数都带入替换,避免出现带副作用的宏参数,导致不易察觉的错误出现。

(3)#undef:用于移除一个已经存在的宏定义
(4)条件编译指令
`#if constant-expression
  statements

elif constant-expression

  statements

else

  statements

endif`

如果constant-expression为非零值,则statements语句被编译,否则被忽略。
(5)条件编译的一大用处是解决多重包含的问题(一个文件被多次包含在一个源文件中),使用方法如下(在头文件中使用):
`#ifndef _HEADERNAME_H

define _HEADERNAME_H 1

/*all stuff */

endif`

(6)其他预编译指令:#error、#line、#progma

十九、c语言中的I/O

(1)流的分类:
a.文本流:用于处理文本,一般会对流数据进行格式化处理,以满足文本的处理要求,如在行末添加换行符等。
b.二进制流:不对流数据进行任何改变,保持其在程序或者是在设备/文本中的原本形式,适用于非文本处理。
(2)I/O函数处理数据的三种基本形式:单个字符、文本行、二进制数据,对应的函数/函数族如下所示;
a.单个字符:getchar/putchar
b.文本行:gets/puts(未格式化输入输出)、scanf/printf(格式化输入输出)
c.二进制数据:fread/fwrite

posted @ 2019-02-18 21:11  Nolan24  Views(224)  Comments(0Edit  收藏  举报