温斯顿吴 ☀️摩诃般若波罗蜜

《C程序设计语言(The C Programming Language)》读书笔记

2017-08-26

C语言并不是一种大型语言,也不需要用一本很厚的书来描述。

第1章 导言

C语言源文件中允许出现空格、制表符或换行符之处,都可以使用注释。 printf函数并不是C语言本身的一部分,C语言本身并没有定义输入、输出功能,但是ANSI标准定义了printf函数的行为。

printf的格式理解:

%d     十进制整数
%6d    宽度为6个字符的十进制整数
%f     浮点数
%6f    宽度为6个字符的浮点数
%.2f   保留2位小数的浮点数
%6.2f  宽度为6个字符,保留2位小数的浮点数

在允许使用某种类型变量值的任何场合,都可以使用该类型的更复杂的表达式。

在程序中直接使用数字,比如300、20等“幻数”,不是一个好习惯,不能很好地提现其意义,应该使用#define定义。

EOF定义在头文件中,是一个整型数(-1)。

赋值操作是一个表达式,并且具有一个值,即赋值后左边变量保存的值,因此赋值可以作为更大的表达式的一部分出现。

n1 = nw = nc = 0;
// 等价于
n1 = (nw = (nc = 0));

单引号中的字符表示一个整型值,该值等于此字符在机器字符集中对应的数值,称为字符常量(它只不过是小的整型数值的另一种写法而已)。

printf("%d\n", '\n');         // 10
printf("%d\n", ('\n'+'\n'));  // 20

由于main本身也是函数,因此也可以向其调用者返回一个值,该调用者实际上就是执行环境,一般来说,返回值0表示正常终止,非0表示出现异常情况。

需要进行函数声明的一个原因是编译器可以很容易地检测出函数调用中参数数目和类型方面的错误。

C语言中的字符串常量以字符数组的形式存储,并以’\0’标志字符串的结束。printf函数中的格式规范%s规定对应的参数必须是以这种形式表示的字符串。

外部变量必须定义在所有函数之外,且只能声明一次,定义后编译程序将为它分配存储单元。在每个需要访问外部变量的函数中必须声明相应的外部变量。声明时可以用extern语句显式地声明,也可以通过上下文隐式声明(外部变量与使用它的函数定义在同一个源文件中,且定义在函数之前)。

在ANSI C中,如果要声明空参数表,则必须使用关键字void进行显式声明。

第2章 类型、运算符和表达式

字符串常量可以在编译时连接。

编译时可以将多个字符串常量连接起来:

printf("%s\n","AAA" "BBB");
// 等价于
printf("%s\n","AAABBB");

存储字符串的物理存储单元数比括在双引号中的字符数多一个(’\0’)。

默认情况下外部变量和静态变量将被初始化为0,未经显式初始化的自动变量的值为未定义。

对数组而言,const限定符指定数组所有元素的值都不能被修改。

取模运算符%不能应用于float或double。

当一个运算符的几个操作数类型不同时,就需要通过一些规则把它们转换为某种共同类型,一般来说自动转换是指把“比较窄的”操作数转换为“比较宽的”操作数,并且不丢失信息的转换。

int atoi(char s[]){
	int i,n;
	n = 0;
	for(i = 0; s[i] >= '0' && s[i] <= '9'; ++i){
        n = 10 * n +(s[i] - '0')
    }
    return n;
}
int lower(int c){
	if( c >= 'A' && c <= 'Z'){
		return c + 'a' -'A';
    }
    else{
        return c;
    }
}

表达式++n先将n的值递增1,然后再使用变量n的值,而表达式n++则是先使用变量n的值,然后再将n的值递增1。

按位与运算常用于屏蔽某些二进制位:

n = n & 0177;

按位或操作常用于将某些二进制位置为1:

x = x | SET_ON;

对于:

a[i] = i++;

数组下标i是引用旧值还是引用新值,对于这种情况不同编译器的解释不同。

第3章 控制流

int binsearch(int x, int v[], int n){
	int low,high,mid;
	low = 0;
	high = n -1;
	while(low <= high){
        mid = (low + high)/2;
        if(x < v[mid]){
            high = mid -1;
        }
        else if(x > v[mid]){
            low = mid + 1;
        }
        else{
            return mid;
        }
    }
    return -1;
}

倒置字符串中各个字符的位置

void reverse(char s[]){
	int c,i,j;
	for(i = 0,j = strlen(s)-1; i < j; i++,j--){
		c = s[i];
		s[i] = s[j];
		s[j] = c;
    }
}

删除字符串尾部的空格、制表符、换行符等

void trim(char s[]){
	int n;
	for( n = strlen(s)-1; n >= 0; n--){
        if(s[n] !=' ' && s[n] != '\t' && s[n] != '\n'){
        	break;
        }
    }
    s[n+1] = '\0';
    return n;
}

goto语句最常见的用法是终止程序在某些深度嵌套的结构中的处理过程,例如多重循环。

第4章 函数与程序结构

程序可以看成是变量定义和函数定义的集合。C语言程序可以看成由一系列的外部对象构成,这些外部对象可能是变量和函数。外部变量定义在函数之外,因此可以在许多函数中使用。

构成C语言程序的函数与外部变量可以分开进行编译,一个程序可以存放在几个文件中,原先已编译过的函数可以从库中进行加载。

外部变量或函数的作用域从声明它的地方开始,到其所在的文件的末尾结束。此外,如果要在外部变量的定义之前使用该变量,或者外部变量的定义与变量的使用不在同一个源文件中,则必须在相应的变量声明中强制性地使用关键字extern。

在一个程序的所有源文件中,一个外部变量只能在某个文件中定义一次,而其他文件可以通过extern声明来访问它(定义外部变量源文件中也可以包含对该外部变量的extern声明)。

对于中等规模的程序,最好只用一个头文件存放程序中各部分共享的对象。

用static声明限定外部变量与函数,可以将其后声明的对象的作用域限定为被编译源文件的剩余部分。因此,通过static限定外部对象,可以达到隐藏外部对象的目的。

static也可以用于声明内部变量,static类型的内部变量同自动变量一样,是某个特定函数的局部变量,只能在该函数中使用,但与自动变量不同的是,不管其所在函数是否被调用,它一直存在,而不像自动变量那样,随着所在函数的被调用和退出而存在和消失。

register声明用于告诉编译器该变量在程序中使用频率较高,建议编译器将其放在寄存器中:

register int x;
register char c;

无论寄存器变量实际上是不是存放在寄存器中,它的地址都是不能访问的。

在不进行显式初始化的情况下,外部变量和静态变量都将初始化为0,而自动变量和寄存器变量的初始值则没有定义。对于外部变量与静态变量来说,初始化表达式必须是常量表达式,且只能初始化一次,对于自动变量与寄存器变量,则在每次进入函数或程序块时都将被初始化。

当使用字符串常量初始化字符数组时,会在字符数组最后自动添加一个’\0’,即:

char pattern[] = "ould";

实际效果如下:

char pattern[] = {'o','u','l','d','\0'};
void qsort(int v[], int left, int right){
	int i,last;
	void swap(int v[],int i,int j);

	if(left >= right){
		return ;
	}

	swap(v,left,(left + right)/2);
	last = left;
	for(i = left +1; i<= right; i++){
		if(v[i] < v[left]){
			swap(v, ++last,i);
		}
	}
	swap(v, left, last);
	qsort(v,left,last-1);
	qsort(v,last+1,right);
}

void swap(int v[], int i, int j){
	int temp;
	temp = v[i];
	v[i] = v[j];
	v[j] = temp;
}

预处理器是编译过程中单独执行的第一个步骤。

在使用#include预处理命令时,如果文件名用引号包括,则在源文件所在位置查找该文件,如果在该位置没有找到文件,或者如果文件名是用尖括号括起来的,则将根据相应的规则查找该文件,这个规则同具体的实现有关。被包含的文件本身也可包含#include指令。

也可以把一个较长的宏定义分成若干行,这时需要在待续的行末尾加上一个反斜杠\。#define定义的名字的作用域从其定义点开始,到被编译的源文件的末尾结束。宏定义中也可以使用前面出现的宏。宏定义也可以带参数。

可以使用#undef指令取消名字的宏定义。

如果宏替换文本中的参数与##相邻,则该参数将被实际参数替代,##与前后的空白符将被删除:

#define paste(front,back) front##back

则宏调用paste(name,1)的结果为name1。

预处理条件语句:

#if !defined(HDR)
#define HDR
...
#endif

或者:

#ifndef HDR
#define HDR
...
#endif

第5章 指针与数组

指针是一种保存变量地址的变量。

地址运算符&只能应用于内存中的对象,即变量与数组元素,不能作用于表达式、常量或register类型的变量。

通过数组下标所能完成的任何操作都可以通过指针来实现:pa[i]与*(pa+i)是等价的。

当把数组名传递给一个函数时,实际上传递的是该数组第一个元素的地址。在函数形参定义中char s[]和char *s是等价的。

指针与整数之间不能相互转换,但0是唯一例外,常量0可以赋值给指针,指针也可以和常量0进行比较。程序中经常用符号常量NULL代替0。

如果两个指针指向同一个数组的成员,那么它们之间就可以进行==、!=、<、>=等关系比较运算。

对于指向数组成员的指针p,在计算p+n时,n将根据p指向的对象的长度按比例缩放,而p指向的对象的长度则取决于p的声明。 基于指针计算字符串的长度

int strlen(char *s){
	char *p = s;
	while(*p != '\0'){
		p++;
	}
	return p-s;
}
int strcpy(char *s,char *t){
	while(*s++ = *t++);
}

指向函数的指针

// 定义
int (*comp)(void *, void *)

// 调用
if((*comp)(v[i],v[left]) < 0)

第6章 结构

struct声明定义了一种数据类型:

struct {...} x,y,z;

struct初始化:

struct point maxpt = {320,200};

可以通过如下形式引用某个特定结构中的成员:

结构名.成员

通过结构的指针访问结构成员:

struct point *pp;
(*pp).x

因为结构指针的使用频度非常高,为了使用方便,C语言提供了一种简写方式,对于指向结构的指针p,可以用如下形式引用结构成员:

p->结构成员

自引用的结构定义 一个包含其自身实例的结构是非法的,但是在结构体重声明指向其自身类型的指针是合法的。

struct tnode {
	char *word;
	int count;
	struct tnode *left;
	struct tnode *right;
}

typedef:

typedef struct tnode *Treeptr;
typedef struct tnode{
	char *word;
	int count;
	Treeptr left; // struct tnode *left;
	Treeptr right; // struct tnode *right;
} Treenode;


Treeptr talloc(void){
	return (Treeptr)malloc(sizeof(Treenode));
}

联合是可以(在不同时刻)保存不同类型和长度的对象的变量,编译器负责跟踪对象的长度和对其要求。读取的类型必须是最近一次存入的类型。

联合只能用其第一个成员类型的值进行初始化。

位字段:直接定义和访问一个字中的位字段的能力,而不需要通过按位逻辑运算。位字段是字中相邻位的集合。

struct {
	unsigned int is_keyword : 1;
	unsigned int is_extern  : 1;
	unsigned int is_static  : 1;
}flags;

以上定义了一个变量flags,它包含3个一位的字段,冒号后的数字表示字段的宽度(二进制位数)。

第7章 输入与输出

printf定义宽度:

printf(":%-15.10s:","hello,world");

变长参数表

int printf(char *fmt, ...)

宏va_start用于初始化指向第一个无名参数的指针,va_list用于定义指向每个无名参数的指针。

第8章 UNIX系统接口

详略。

附录A 参考手册

详略。

附录B 标准库

详略。

附录C 变更小结

详略。


微信公众号:时空波隐者
文章目录